summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.bazelrc63
-rw-r--r--.github/issue_template.md20
-rw-r--r--.gitignore2
-rw-r--r--AUTHORS8
-rw-r--r--BUILD43
-rw-r--r--CODE_OF_CONDUCT.md92
-rw-r--r--CONTRIBUTING.md147
-rw-r--r--Dockerfile8
-rw-r--r--LICENSE202
-rw-r--r--Makefile47
-rw-r--r--README.md161
-rw-r--r--WORKSPACE252
-rw-r--r--g3doc/README.md2
-rw-r--r--g3doc/logo.pngbin27719 -> 0 bytes
-rw-r--r--kokoro/build.cfg23
-rw-r--r--kokoro/common.cfg29
-rw-r--r--kokoro/do_tests.cfg9
-rw-r--r--kokoro/docker_tests.cfg9
-rw-r--r--kokoro/go.cfg20
-rw-r--r--kokoro/go_tests.cfg1
-rw-r--r--kokoro/hostnet_tests.cfg9
-rw-r--r--kokoro/kvm_tests.cfg9
-rw-r--r--kokoro/make_tests.cfg9
-rw-r--r--kokoro/overlay_tests.cfg9
-rw-r--r--kokoro/release.cfg1
-rw-r--r--kokoro/root_tests.cfg9
-rw-r--r--kokoro/simple_tests.cfg9
-rw-r--r--kokoro/syscall_tests.cfg9
-rwxr-xr-xkokoro/ubuntu1604/10_core.sh30
-rwxr-xr-xkokoro/ubuntu1604/20_bazel.sh28
-rwxr-xr-xkokoro/ubuntu1604/25_docker.sh35
-rwxr-xr-xkokoro/ubuntu1604/30_containerd.sh76
-rwxr-xr-xkokoro/ubuntu1604/40_kokoro.sh54
-rwxr-xr-xkokoro/ubuntu1604/build.sh20
l---------kokoro/ubuntu1804/10_core.sh1
l---------kokoro/ubuntu1804/20_bazel.sh1
l---------kokoro/ubuntu1804/25_docker.sh1
l---------kokoro/ubuntu1804/30_containerd.sh1
l---------kokoro/ubuntu1804/40_kokoro.sh1
-rwxr-xr-xkokoro/ubuntu1804/build.sh20
-rw-r--r--pkg/abi/BUILD14
-rwxr-xr-xpkg/abi/abi_state_autogen.go4
-rw-r--r--pkg/abi/linux/BUILD76
-rwxr-xr-xpkg/abi/linux/linux_state_autogen.go68
-rw-r--r--pkg/abi/linux/netfilter_test.go45
-rw-r--r--pkg/amutex/BUILD18
-rwxr-xr-xpkg/amutex/amutex_state_autogen.go4
-rw-r--r--pkg/amutex/amutex_test.go97
-rw-r--r--pkg/atomicbitops/BUILD22
-rw-r--r--pkg/atomicbitops/atomic_bitops_test.go261
-rwxr-xr-xpkg/atomicbitops/atomicbitops_state_autogen.go4
-rw-r--r--pkg/binary/BUILD18
-rwxr-xr-xpkg/binary/binary_state_autogen.go4
-rw-r--r--pkg/binary/binary_test.go266
-rw-r--r--pkg/bits/BUILD57
-rwxr-xr-xpkg/bits/bits32.go25
-rwxr-xr-xpkg/bits/bits64.go25
-rwxr-xr-xpkg/bits/bits_state_autogen.go4
-rw-r--r--pkg/bits/bits_template.go44
-rw-r--r--pkg/bits/uint64_test.go116
-rw-r--r--pkg/bpf/BUILD34
-rwxr-xr-xpkg/bpf/bpf_state_autogen.go22
-rw-r--r--pkg/bpf/decoder_test.go146
-rw-r--r--pkg/bpf/interpreter_test.go797
-rw-r--r--pkg/bpf/program_builder_test.go157
-rw-r--r--pkg/compressio/BUILD19
-rwxr-xr-xpkg/compressio/compressio_state_autogen.go4
-rw-r--r--pkg/compressio/compressio_test.go290
-rw-r--r--pkg/control/client/BUILD16
-rwxr-xr-xpkg/control/client/client_state_autogen.go4
-rw-r--r--pkg/control/server/BUILD15
-rwxr-xr-xpkg/control/server/server_state_autogen.go4
-rw-r--r--pkg/cpuid/BUILD33
-rw-r--r--pkg/cpuid/cpuid_parse_test.go142
-rwxr-xr-xpkg/cpuid/cpuid_state_autogen.go68
-rw-r--r--pkg/cpuid/cpuid_test.go241
-rw-r--r--pkg/eventchannel/BUILD44
-rw-r--r--pkg/eventchannel/event.proto27
-rw-r--r--pkg/eventchannel/event_test.go146
-rwxr-xr-xpkg/eventchannel/eventchannel_go_proto/event.pb.go85
-rwxr-xr-xpkg/eventchannel/eventchannel_state_autogen.go4
-rw-r--r--pkg/fd/BUILD18
-rwxr-xr-xpkg/fd/fd_state_autogen.go4
-rw-r--r--pkg/fd/fd_test.go136
-rw-r--r--pkg/fdchannel/BUILD18
-rwxr-xr-xpkg/fdchannel/fdchannel_state_autogen.go4
-rw-r--r--pkg/fdchannel/fdchannel_test.go131
-rwxr-xr-x[-rw-r--r--]pkg/fdchannel/fdchannel_unsafe.go0
-rw-r--r--pkg/fdnotifier/BUILD17
-rwxr-xr-xpkg/fdnotifier/fdnotifier_state_autogen.go4
-rw-r--r--pkg/flipcall/BUILD34
-rwxr-xr-x[-rw-r--r--]pkg/flipcall/ctrl_futex.go0
-rwxr-xr-x[-rw-r--r--]pkg/flipcall/flipcall.go0
-rw-r--r--pkg/flipcall/flipcall_example_test.go112
-rwxr-xr-xpkg/flipcall/flipcall_state_autogen.go4
-rw-r--r--pkg/flipcall/flipcall_test.go404
-rwxr-xr-x[-rw-r--r--]pkg/flipcall/flipcall_unsafe.go0
-rwxr-xr-x[-rw-r--r--]pkg/flipcall/futex_linux.go0
-rwxr-xr-x[-rw-r--r--]pkg/flipcall/io.go0
-rwxr-xr-x[-rw-r--r--]pkg/flipcall/packet_window_allocator.go0
-rw-r--r--pkg/fspath/BUILD29
-rw-r--r--pkg/fspath/builder.go104
-rw-r--r--pkg/fspath/builder_test.go58
-rw-r--r--pkg/fspath/builder_unsafe.go27
-rw-r--r--pkg/fspath/fspath.go182
-rw-r--r--pkg/fspath/fspath_test.go143
-rw-r--r--pkg/gate/BUILD23
-rwxr-xr-xpkg/gate/gate_state_autogen.go4
-rw-r--r--pkg/gate/gate_test.go189
-rw-r--r--pkg/ilist/BUILD58
-rwxr-xr-xpkg/ilist/ilist_state_autogen.go38
-rwxr-xr-x[-rw-r--r--]pkg/ilist/interface_list.go (renamed from pkg/ilist/list.go)15
-rw-r--r--pkg/ilist/list_test.go240
-rw-r--r--pkg/linewriter/BUILD17
-rwxr-xr-xpkg/linewriter/linewriter_state_autogen.go4
-rw-r--r--pkg/linewriter/linewriter_test.go81
-rw-r--r--pkg/log/BUILD30
-rw-r--r--pkg/log/json_test.go64
-rwxr-xr-xpkg/log/log_state_autogen.go4
-rw-r--r--pkg/log/log_test.go70
-rw-r--r--pkg/memutil/BUILD11
-rwxr-xr-xpkg/memutil/memutil_state_autogen.go4
-rw-r--r--pkg/metric/BUILD41
-rw-r--r--pkg/metric/metric.proto68
-rwxr-xr-xpkg/metric/metric_go_proto/metric.pb.go297
-rwxr-xr-xpkg/metric/metric_state_autogen.go4
-rw-r--r--pkg/metric/metric_test.go252
-rw-r--r--pkg/p9/BUILD54
-rw-r--r--pkg/p9/buffer_test.go31
-rw-r--r--pkg/p9/client_test.go104
-rw-r--r--pkg/p9/messages_test.go468
-rwxr-xr-xpkg/p9/p9_state_autogen.go4
-rw-r--r--pkg/p9/p9_test.go188
-rw-r--r--pkg/p9/p9test/BUILD88
-rw-r--r--pkg/p9/p9test/client_test.go2129
-rw-r--r--pkg/p9/p9test/p9test.go329
-rw-r--r--pkg/p9/pool_test.go64
-rwxr-xr-x[-rw-r--r--]pkg/p9/transport_flipcall.go0
-rw-r--r--pkg/p9/transport_test.go231
-rw-r--r--pkg/p9/version_test.go145
-rw-r--r--pkg/procid/BUILD34
-rw-r--r--pkg/procid/procid_net_test.go21
-rwxr-xr-xpkg/procid/procid_state_autogen.go4
-rw-r--r--pkg/procid/procid_test.go85
-rw-r--r--pkg/rand/BUILD14
-rwxr-xr-xpkg/rand/rand_state_autogen.go4
-rw-r--r--pkg/refs/BUILD39
-rw-r--r--pkg/refs/refcounter_test.go172
-rwxr-xr-xpkg/refs/refs_state_autogen.go81
-rwxr-xr-xpkg/refs/weak_ref_list.go173
-rw-r--r--pkg/seccomp/BUILD52
-rwxr-xr-xpkg/seccomp/seccomp_state_autogen.go4
-rw-r--r--pkg/seccomp/seccomp_test.go505
-rw-r--r--pkg/seccomp/seccomp_test_victim.go117
-rw-r--r--pkg/secio/BUILD21
-rwxr-xr-xpkg/secio/secio_state_autogen.go4
-rw-r--r--pkg/secio/secio_test.go126
-rw-r--r--pkg/segment/BUILD31
-rw-r--r--pkg/segment/set_state.go25
-rw-r--r--pkg/segment/test/BUILD53
-rw-r--r--pkg/segment/test/segment_test.go564
-rw-r--r--pkg/segment/test/set_functions.go42
-rw-r--r--pkg/sentry/BUILD14
-rw-r--r--pkg/sentry/arch/BUILD50
-rwxr-xr-xpkg/sentry/arch/arch_state_autogen.go193
-rw-r--r--pkg/sentry/arch/registers.proto55
-rwxr-xr-xpkg/sentry/arch/registers_go_proto/registers.pb.go367
-rw-r--r--pkg/sentry/context/BUILD14
-rwxr-xr-xpkg/sentry/context/context_state_autogen.go4
-rw-r--r--pkg/sentry/context/contexttest/BUILD22
-rw-r--r--pkg/sentry/context/contexttest/contexttest.go188
-rw-r--r--pkg/sentry/control/BUILD48
-rwxr-xr-xpkg/sentry/control/control_state_autogen.go4
-rw-r--r--pkg/sentry/control/proc_test.go164
-rw-r--r--pkg/sentry/device/BUILD20
-rwxr-xr-xpkg/sentry/device/device_state_autogen.go52
-rw-r--r--pkg/sentry/device/device_test.go59
-rw-r--r--pkg/sentry/fs/BUILD137
-rw-r--r--pkg/sentry/fs/README.md229
-rw-r--r--pkg/sentry/fs/anon/BUILD21
-rwxr-xr-xpkg/sentry/fs/anon/anon_state_autogen.go4
-rw-r--r--pkg/sentry/fs/copy_up_test.go183
-rw-r--r--pkg/sentry/fs/dev/BUILD35
-rwxr-xr-xpkg/sentry/fs/dev/dev_state_autogen.go130
-rw-r--r--pkg/sentry/fs/dirent_cache_test.go247
-rwxr-xr-xpkg/sentry/fs/dirent_list.go173
-rw-r--r--pkg/sentry/fs/dirent_refs_test.go418
-rwxr-xr-xpkg/sentry/fs/event_list.go173
-rw-r--r--pkg/sentry/fs/fdpipe/BUILD50
-rwxr-xr-xpkg/sentry/fs/fdpipe/fdpipe_state_autogen.go27
-rw-r--r--pkg/sentry/fs/fdpipe/pipe_opener_test.go522
-rw-r--r--pkg/sentry/fs/fdpipe/pipe_test.go505
-rw-r--r--pkg/sentry/fs/file_overlay_test.go275
-rw-r--r--pkg/sentry/fs/filetest/BUILD20
-rw-r--r--pkg/sentry/fs/filetest/filetest.go61
-rwxr-xr-xpkg/sentry/fs/fs_state_autogen.go632
-rw-r--r--pkg/sentry/fs/fsutil/BUILD120
-rw-r--r--pkg/sentry/fs/fsutil/README.md207
-rwxr-xr-xpkg/sentry/fs/fsutil/dirty_set_impl.go1274
-rw-r--r--pkg/sentry/fs/fsutil/dirty_set_test.go38
-rwxr-xr-x[-rw-r--r--]pkg/sentry/fs/fsutil/file_range_set_impl.go (renamed from pkg/segment/set.go)526
-rwxr-xr-xpkg/sentry/fs/fsutil/frame_ref_set_impl.go1274
-rwxr-xr-xpkg/sentry/fs/fsutil/fsutil_state_autogen.go363
-rw-r--r--pkg/sentry/fs/fsutil/inode_cached_test.go389
-rw-r--r--pkg/sentry/fs/g3doc/inotify.md122
-rw-r--r--pkg/sentry/fs/gofer/BUILD67
-rwxr-xr-xpkg/sentry/fs/gofer/gofer_state_autogen.go115
-rw-r--r--pkg/sentry/fs/gofer/gofer_test.go310
-rw-r--r--pkg/sentry/fs/host/BUILD85
-rw-r--r--pkg/sentry/fs/host/descriptor_test.go78
-rw-r--r--pkg/sentry/fs/host/fs_test.go380
-rwxr-xr-xpkg/sentry/fs/host/host_state_autogen.go138
-rw-r--r--pkg/sentry/fs/host/inode_test.go112
-rw-r--r--pkg/sentry/fs/host/socket_test.go246
-rw-r--r--pkg/sentry/fs/host/wait_test.go70
-rw-r--r--pkg/sentry/fs/inode_overlay_test.go470
-rw-r--r--pkg/sentry/fs/lock/BUILD60
-rwxr-xr-x[-rw-r--r--]pkg/sentry/fs/lock/lock_range.go (renamed from pkg/segment/range.go)39
-rw-r--r--pkg/sentry/fs/lock/lock_range_test.go136
-rwxr-xr-xpkg/sentry/fs/lock/lock_set.go1270
-rwxr-xr-xpkg/sentry/fs/lock/lock_state_autogen.go106
-rw-r--r--pkg/sentry/fs/lock/lock_test.go1059
-rw-r--r--pkg/sentry/fs/mount_test.go271
-rw-r--r--pkg/sentry/fs/mounts_test.go105
-rw-r--r--pkg/sentry/fs/path_test.go289
-rw-r--r--pkg/sentry/fs/proc/BUILD74
-rw-r--r--pkg/sentry/fs/proc/README.md332
-rw-r--r--pkg/sentry/fs/proc/device/BUILD11
-rwxr-xr-xpkg/sentry/fs/proc/device/device_state_autogen.go4
-rw-r--r--pkg/sentry/fs/proc/net_test.go74
-rwxr-xr-xpkg/sentry/fs/proc/proc_state_autogen.go681
-rw-r--r--pkg/sentry/fs/proc/seqfile/BUILD37
-rwxr-xr-xpkg/sentry/fs/proc/seqfile/seqfile_state_autogen.go58
-rw-r--r--pkg/sentry/fs/proc/seqfile/seqfile_test.go279
-rw-r--r--pkg/sentry/fs/proc/sys_net_test.go125
-rw-r--r--pkg/sentry/fs/ramfs/BUILD39
-rwxr-xr-xpkg/sentry/fs/ramfs/ramfs_state_autogen.go94
-rw-r--r--pkg/sentry/fs/ramfs/tree_test.go80
-rw-r--r--pkg/sentry/fs/sys/BUILD25
-rwxr-xr-xpkg/sentry/fs/sys/sys_state_autogen.go34
-rw-r--r--pkg/sentry/fs/timerfd/BUILD20
-rwxr-xr-xpkg/sentry/fs/timerfd/timerfd_state_autogen.go25
-rw-r--r--pkg/sentry/fs/tmpfs/BUILD52
-rw-r--r--pkg/sentry/fs/tmpfs/file_test.go72
-rwxr-xr-xpkg/sentry/fs/tmpfs/tmpfs_state_autogen.go108
-rw-r--r--pkg/sentry/fs/tty/BUILD48
-rwxr-xr-xpkg/sentry/fs/tty/tty_state_autogen.go202
-rw-r--r--pkg/sentry/fs/tty/tty_test.go56
-rw-r--r--pkg/sentry/fsimpl/ext/BUILD88
-rw-r--r--pkg/sentry/fsimpl/ext/README.md117
-rw-r--r--pkg/sentry/fsimpl/ext/assets/README.md36
-rw-r--r--pkg/sentry/fsimpl/ext/assets/bigfile.txt41
-rw-r--r--pkg/sentry/fsimpl/ext/assets/file.txt1
l---------pkg/sentry/fsimpl/ext/assets/symlink.txt1
-rw-r--r--pkg/sentry/fsimpl/ext/assets/tiny.ext2bin65536 -> 0 bytes
-rw-r--r--pkg/sentry/fsimpl/ext/assets/tiny.ext3bin65536 -> 0 bytes
-rw-r--r--pkg/sentry/fsimpl/ext/assets/tiny.ext4bin65536 -> 0 bytes
-rw-r--r--pkg/sentry/fsimpl/ext/benchmark/BUILD16
-rw-r--r--pkg/sentry/fsimpl/ext/benchmark/benchmark_test.go193
-rwxr-xr-xpkg/sentry/fsimpl/ext/benchmark/make_deep_ext4.sh72
-rw-r--r--pkg/sentry/fsimpl/ext/block_map_file.go200
-rw-r--r--pkg/sentry/fsimpl/ext/block_map_test.go157
-rw-r--r--pkg/sentry/fsimpl/ext/dentry.go56
-rw-r--r--pkg/sentry/fsimpl/ext/directory.go308
-rw-r--r--pkg/sentry/fsimpl/ext/disklayout/BUILD50
-rw-r--r--pkg/sentry/fsimpl/ext/disklayout/block_group.go137
-rw-r--r--pkg/sentry/fsimpl/ext/disklayout/block_group_32.go72
-rw-r--r--pkg/sentry/fsimpl/ext/disklayout/block_group_64.go93
-rw-r--r--pkg/sentry/fsimpl/ext/disklayout/block_group_test.go26
-rw-r--r--pkg/sentry/fsimpl/ext/disklayout/dirent.go72
-rw-r--r--pkg/sentry/fsimpl/ext/disklayout/dirent_new.go61
-rw-r--r--pkg/sentry/fsimpl/ext/disklayout/dirent_old.go49
-rw-r--r--pkg/sentry/fsimpl/ext/disklayout/dirent_test.go26
-rw-r--r--pkg/sentry/fsimpl/ext/disklayout/disklayout.go50
-rw-r--r--pkg/sentry/fsimpl/ext/disklayout/extent.go139
-rw-r--r--pkg/sentry/fsimpl/ext/disklayout/extent_test.go27
-rw-r--r--pkg/sentry/fsimpl/ext/disklayout/inode.go274
-rw-r--r--pkg/sentry/fsimpl/ext/disklayout/inode_new.go96
-rw-r--r--pkg/sentry/fsimpl/ext/disklayout/inode_old.go117
-rw-r--r--pkg/sentry/fsimpl/ext/disklayout/inode_test.go222
-rw-r--r--pkg/sentry/fsimpl/ext/disklayout/superblock.go471
-rw-r--r--pkg/sentry/fsimpl/ext/disklayout/superblock_32.go76
-rw-r--r--pkg/sentry/fsimpl/ext/disklayout/superblock_64.go95
-rw-r--r--pkg/sentry/fsimpl/ext/disklayout/superblock_old.go105
-rw-r--r--pkg/sentry/fsimpl/ext/disklayout/superblock_test.go27
-rw-r--r--pkg/sentry/fsimpl/ext/disklayout/test_utils.go30
-rw-r--r--pkg/sentry/fsimpl/ext/ext.go135
-rw-r--r--pkg/sentry/fsimpl/ext/ext_test.go917
-rw-r--r--pkg/sentry/fsimpl/ext/extent_file.go237
-rw-r--r--pkg/sentry/fsimpl/ext/extent_test.go265
-rw-r--r--pkg/sentry/fsimpl/ext/file_description.go86
-rw-r--r--pkg/sentry/fsimpl/ext/filesystem.go443
-rw-r--r--pkg/sentry/fsimpl/ext/inode.go219
-rw-r--r--pkg/sentry/fsimpl/ext/regular_file.go159
-rw-r--r--pkg/sentry/fsimpl/ext/symlink.go111
-rw-r--r--pkg/sentry/fsimpl/ext/utils.go94
-rw-r--r--pkg/sentry/fsimpl/memfs/BUILD56
-rw-r--r--pkg/sentry/fsimpl/memfs/benchmark_test.go464
-rw-r--r--pkg/sentry/fsimpl/memfs/directory.go187
-rw-r--r--pkg/sentry/fsimpl/memfs/filesystem.go544
-rw-r--r--pkg/sentry/fsimpl/memfs/memfs.go300
-rw-r--r--pkg/sentry/fsimpl/memfs/regular_file.go154
-rw-r--r--pkg/sentry/fsimpl/memfs/symlink.go36
-rw-r--r--pkg/sentry/fsimpl/proc/BUILD50
-rw-r--r--pkg/sentry/fsimpl/proc/filesystems.go25
-rw-r--r--pkg/sentry/fsimpl/proc/loadavg.go40
-rw-r--r--pkg/sentry/fsimpl/proc/meminfo.go77
-rw-r--r--pkg/sentry/fsimpl/proc/mounts.go33
-rw-r--r--pkg/sentry/fsimpl/proc/net.go338
-rw-r--r--pkg/sentry/fsimpl/proc/net_test.go78
-rw-r--r--pkg/sentry/fsimpl/proc/proc.go16
-rw-r--r--pkg/sentry/fsimpl/proc/stat.go127
-rw-r--r--pkg/sentry/fsimpl/proc/sys.go51
-rw-r--r--pkg/sentry/fsimpl/proc/task.go261
-rw-r--r--pkg/sentry/fsimpl/proc/version.go68
-rw-r--r--pkg/sentry/hostcpu/BUILD21
-rwxr-xr-xpkg/sentry/hostcpu/hostcpu_state_autogen.go4
-rw-r--r--pkg/sentry/hostcpu/hostcpu_test.go52
-rw-r--r--pkg/sentry/hostmm/BUILD18
-rwxr-xr-xpkg/sentry/hostmm/hostmm_state_autogen.go4
-rw-r--r--pkg/sentry/inet/BUILD17
-rwxr-xr-xpkg/sentry/inet/inet_state_autogen.go26
-rw-r--r--pkg/sentry/kernel/BUILD239
-rw-r--r--pkg/sentry/kernel/README.md108
-rw-r--r--pkg/sentry/kernel/auth/BUILD69
-rwxr-xr-x[-rw-r--r--]pkg/sentry/kernel/auth/atomicptr_credentials_unsafe.go (renamed from third_party/gvsync/atomicptr_unsafe.go)26
-rwxr-xr-xpkg/sentry/kernel/auth/auth_state_autogen.go164
-rwxr-xr-xpkg/sentry/kernel/auth/id_map_range.go62
-rwxr-xr-xpkg/sentry/kernel/auth/id_map_set.go1270
-rw-r--r--pkg/sentry/kernel/contexttest/BUILD18
-rw-r--r--pkg/sentry/kernel/contexttest/contexttest.go40
-rw-r--r--pkg/sentry/kernel/epoll/BUILD52
-rwxr-xr-xpkg/sentry/kernel/epoll/epoll_list.go173
-rwxr-xr-xpkg/sentry/kernel/epoll/epoll_state_autogen.go99
-rw-r--r--pkg/sentry/kernel/epoll/epoll_test.go54
-rw-r--r--pkg/sentry/kernel/eventfd/BUILD35
-rwxr-xr-xpkg/sentry/kernel/eventfd/eventfd_state_autogen.go27
-rw-r--r--pkg/sentry/kernel/eventfd/eventfd_test.go78
-rw-r--r--pkg/sentry/kernel/fasync/BUILD17
-rwxr-xr-xpkg/sentry/kernel/fasync/fasync_state_autogen.go32
-rw-r--r--pkg/sentry/kernel/fd_table_test.go192
-rw-r--r--pkg/sentry/kernel/futex/BUILD56
-rwxr-xr-xpkg/sentry/kernel/futex/atomicptr_bucket_unsafe.go37
-rwxr-xr-xpkg/sentry/kernel/futex/futex_state_autogen.go75
-rw-r--r--pkg/sentry/kernel/futex/futex_test.go530
-rwxr-xr-xpkg/sentry/kernel/futex/waiter_list.go173
-rw-r--r--pkg/sentry/kernel/g3doc/run_states.dot99
-rw-r--r--pkg/sentry/kernel/g3doc/run_states.pngbin234152 -> 0 bytes
-rwxr-xr-xpkg/sentry/kernel/kernel_state_autogen.go1182
-rw-r--r--pkg/sentry/kernel/memevent/BUILD32
-rw-r--r--pkg/sentry/kernel/memevent/memory_events.go111
-rw-r--r--pkg/sentry/kernel/memevent/memory_events.proto29
-rwxr-xr-xpkg/sentry/kernel/pending_signals_list.go173
-rw-r--r--pkg/sentry/kernel/pipe/BUILD66
-rwxr-xr-xpkg/sentry/kernel/pipe/buffer_list.go173
-rw-r--r--pkg/sentry/kernel/pipe/buffer_test.go32
-rw-r--r--pkg/sentry/kernel/pipe/node_test.go320
-rwxr-xr-xpkg/sentry/kernel/pipe/pipe_state_autogen.go132
-rw-r--r--pkg/sentry/kernel/pipe/pipe_test.go139
-rwxr-xr-xpkg/sentry/kernel/process_group_list.go173
-rw-r--r--pkg/sentry/kernel/sched/BUILD21
-rw-r--r--pkg/sentry/kernel/sched/cpuset_test.go44
-rwxr-xr-xpkg/sentry/kernel/sched/sched_state_autogen.go4
-rw-r--r--pkg/sentry/kernel/semaphore/BUILD51
-rwxr-xr-xpkg/sentry/kernel/semaphore/semaphore_state_autogen.go115
-rw-r--r--pkg/sentry/kernel/semaphore/semaphore_test.go172
-rwxr-xr-xpkg/sentry/kernel/semaphore/waiter_list.go173
-rwxr-xr-x[-rw-r--r--]pkg/sentry/kernel/seqatomic_taskgoroutineschedinfo_unsafe.go (renamed from third_party/gvsync/seqatomic_unsafe.go)38
-rwxr-xr-xpkg/sentry/kernel/session_list.go173
-rw-r--r--pkg/sentry/kernel/shm/BUILD29
-rwxr-xr-xpkg/sentry/kernel/shm/shm_state_autogen.go74
-rwxr-xr-xpkg/sentry/kernel/socket_list.go173
-rw-r--r--pkg/sentry/kernel/table_test.go110
-rwxr-xr-xpkg/sentry/kernel/task_list.go173
-rw-r--r--pkg/sentry/kernel/task_test.go69
-rw-r--r--pkg/sentry/kernel/time/BUILD19
-rwxr-xr-xpkg/sentry/kernel/time/time_state_autogen.go56
-rw-r--r--pkg/sentry/kernel/timekeeper_test.go156
-rw-r--r--pkg/sentry/kernel/uncaught_signal.proto37
-rwxr-xr-xpkg/sentry/kernel/uncaught_signal_go_proto/uncaught_signal.pb.go119
-rw-r--r--pkg/sentry/limits/BUILD29
-rwxr-xr-xpkg/sentry/limits/limits_state_autogen.go36
-rw-r--r--pkg/sentry/limits/limits_test.go43
-rw-r--r--pkg/sentry/loader/BUILD51
-rwxr-xr-xpkg/sentry/loader/loader_state_autogen.go57
-rwxr-xr-xpkg/sentry/loader/vdso_bin.go5
-rw-r--r--pkg/sentry/memmap/BUILD59
-rwxr-xr-xpkg/sentry/memmap/mappable_range.go62
-rwxr-xr-xpkg/sentry/memmap/mapping_set_impl.go1270
-rw-r--r--pkg/sentry/memmap/mapping_set_test.go260
-rwxr-xr-xpkg/sentry/memmap/memmap_state_autogen.go93
-rw-r--r--pkg/sentry/mm/BUILD144
-rw-r--r--pkg/sentry/mm/README.md280
-rwxr-xr-xpkg/sentry/mm/file_refcount_set.go1274
-rwxr-xr-xpkg/sentry/mm/io_list.go173
-rwxr-xr-xpkg/sentry/mm/mm_state_autogen.go388
-rw-r--r--pkg/sentry/mm/mm_test.go230
-rwxr-xr-xpkg/sentry/mm/pma_set.go1274
-rwxr-xr-xpkg/sentry/mm/vma_set.go1274
-rw-r--r--pkg/sentry/pgalloc/BUILD87
-rwxr-xr-xpkg/sentry/pgalloc/evictable_range.go62
-rwxr-xr-xpkg/sentry/pgalloc/evictable_range_set.go1270
-rwxr-xr-xpkg/sentry/pgalloc/pgalloc_state_autogen.go146
-rw-r--r--pkg/sentry/pgalloc/pgalloc_test.go168
-rwxr-xr-xpkg/sentry/pgalloc/usage_set.go1274
-rw-r--r--pkg/sentry/platform/BUILD40
-rwxr-xr-xpkg/sentry/platform/file_range.go62
-rw-r--r--pkg/sentry/platform/interrupt/BUILD20
-rwxr-xr-xpkg/sentry/platform/interrupt/interrupt_state_autogen.go4
-rw-r--r--pkg/sentry/platform/interrupt/interrupt_test.go99
-rw-r--r--pkg/sentry/platform/kvm/BUILD70
-rwxr-xr-xpkg/sentry/platform/kvm/kvm_state_autogen.go4
-rw-r--r--pkg/sentry/platform/kvm/kvm_test.go533
-rw-r--r--pkg/sentry/platform/kvm/testutil/BUILD15
-rw-r--r--pkg/sentry/platform/kvm/testutil/testutil.go75
-rw-r--r--pkg/sentry/platform/kvm/testutil/testutil_amd64.go135
-rw-r--r--pkg/sentry/platform/kvm/testutil/testutil_amd64.s98
-rw-r--r--pkg/sentry/platform/kvm/virtual_map_test.go93
-rwxr-xr-xpkg/sentry/platform/platform_state_autogen.go24
-rw-r--r--pkg/sentry/platform/ptrace/BUILD37
-rwxr-xr-xpkg/sentry/platform/ptrace/ptrace_state_autogen.go4
-rw-r--r--pkg/sentry/platform/ring0/BUILD53
-rw-r--r--pkg/sentry/platform/ring0/defs.go121
-rw-r--r--pkg/sentry/platform/ring0/defs_amd64.go136
-rwxr-xr-xpkg/sentry/platform/ring0/defs_impl.go538
-rwxr-xr-x[-rw-r--r--]pkg/sentry/platform/ring0/entry_impl_amd64.s (renamed from pkg/sentry/platform/ring0/entry_amd64.s)64
-rw-r--r--pkg/sentry/platform/ring0/gen_offsets/BUILD26
-rw-r--r--pkg/sentry/platform/ring0/gen_offsets/main.go24
-rw-r--r--pkg/sentry/platform/ring0/offsets_amd64.go92
-rw-r--r--pkg/sentry/platform/ring0/pagetables/BUILD106
-rw-r--r--pkg/sentry/platform/ring0/pagetables/pagetables_amd64_test.go75
-rwxr-xr-xpkg/sentry/platform/ring0/pagetables/pagetables_state_autogen.go4
-rw-r--r--pkg/sentry/platform/ring0/pagetables/pagetables_test.go156
-rwxr-xr-x[-rw-r--r--]pkg/sentry/platform/ring0/pagetables/walker_empty.go (renamed from pkg/sentry/platform/ring0/pagetables/walker_amd64.go)98
-rwxr-xr-xpkg/sentry/platform/ring0/pagetables/walker_lookup.go255
-rwxr-xr-xpkg/sentry/platform/ring0/pagetables/walker_map.go255
-rwxr-xr-xpkg/sentry/platform/ring0/pagetables/walker_unmap.go255
-rwxr-xr-xpkg/sentry/platform/ring0/ring0_state_autogen.go4
-rw-r--r--pkg/sentry/platform/ring0/x86.go264
-rw-r--r--pkg/sentry/platform/safecopy/BUILD31
-rw-r--r--pkg/sentry/platform/safecopy/LICENSE27
-rwxr-xr-xpkg/sentry/platform/safecopy/safecopy_state_autogen.go4
-rw-r--r--pkg/sentry/platform/safecopy/safecopy_test.go617
-rw-r--r--pkg/sentry/safemem/BUILD29
-rw-r--r--pkg/sentry/safemem/io_test.go199
-rwxr-xr-xpkg/sentry/safemem/safemem_state_autogen.go4
-rw-r--r--pkg/sentry/safemem/seq_test.go196
-rw-r--r--pkg/sentry/sighandling/BUILD14
-rwxr-xr-xpkg/sentry/sighandling/sighandling_state_autogen.go4
-rw-r--r--pkg/sentry/socket/BUILD24
-rw-r--r--pkg/sentry/socket/control/BUILD24
-rwxr-xr-xpkg/sentry/socket/control/control_state_autogen.go36
-rw-r--r--pkg/sentry/socket/epsocket/BUILD50
-rwxr-xr-xpkg/sentry/socket/epsocket/epsocket_state_autogen.go56
-rw-r--r--pkg/sentry/socket/hostinet/BUILD37
-rwxr-xr-xpkg/sentry/socket/hostinet/hostinet_state_autogen.go4
-rw-r--r--pkg/sentry/socket/netfilter/BUILD24
-rwxr-xr-xpkg/sentry/socket/netfilter/netfilter_state_autogen.go4
-rw-r--r--pkg/sentry/socket/netlink/BUILD34
-rwxr-xr-xpkg/sentry/socket/netlink/netlink_state_autogen.go38
-rw-r--r--pkg/sentry/socket/netlink/port/BUILD18
-rwxr-xr-xpkg/sentry/socket/netlink/port/port_state_autogen.go22
-rw-r--r--pkg/sentry/socket/netlink/port/port_test.go82
-rw-r--r--pkg/sentry/socket/netlink/route/BUILD19
-rwxr-xr-xpkg/sentry/socket/netlink/route/route_state_autogen.go20
-rw-r--r--pkg/sentry/socket/rpcinet/BUILD59
-rw-r--r--pkg/sentry/socket/rpcinet/conn/BUILD17
-rwxr-xr-xpkg/sentry/socket/rpcinet/conn/conn_state_autogen.go4
-rw-r--r--pkg/sentry/socket/rpcinet/notifier/BUILD16
-rwxr-xr-xpkg/sentry/socket/rpcinet/notifier/notifier_state_autogen.go4
-rwxr-xr-xpkg/sentry/socket/rpcinet/rpcinet_state_autogen.go4
-rw-r--r--pkg/sentry/socket/rpcinet/syscall_rpc.proto353
-rwxr-xr-xpkg/sentry/socket/rpcinet/syscall_rpc_go_proto/syscall_rpc.pb.go3938
-rwxr-xr-xpkg/sentry/socket/socket_state_autogen.go24
-rw-r--r--pkg/sentry/socket/unix/BUILD35
-rw-r--r--pkg/sentry/socket/unix/transport/BUILD40
-rwxr-xr-xpkg/sentry/socket/unix/transport/transport_message_list.go173
-rwxr-xr-xpkg/sentry/socket/unix/transport/transport_state_autogen.go191
-rwxr-xr-xpkg/sentry/socket/unix/unix_state_autogen.go28
-rw-r--r--pkg/sentry/state/BUILD24
-rwxr-xr-xpkg/sentry/state/state_state_autogen.go4
-rw-r--r--pkg/sentry/strace/BUILD52
-rw-r--r--pkg/sentry/strace/strace.proto50
-rwxr-xr-xpkg/sentry/strace/strace_go_proto/strace.pb.go247
-rwxr-xr-xpkg/sentry/strace/strace_state_autogen.go4
-rw-r--r--pkg/sentry/syscalls/BUILD22
-rw-r--r--pkg/sentry/syscalls/linux/BUILD92
-rwxr-xr-xpkg/sentry/syscalls/linux/linux_state_autogen.go80
-rwxr-xr-xpkg/sentry/syscalls/syscalls_state_autogen.go4
-rw-r--r--pkg/sentry/time/BUILD53
-rw-r--r--pkg/sentry/time/LICENSE27
-rw-r--r--pkg/sentry/time/calibrated_clock_test.go186
-rw-r--r--pkg/sentry/time/parameters_test.go486
-rw-r--r--pkg/sentry/time/sampler_test.go183
-rwxr-xr-xpkg/sentry/time/seqatomic_parameters_unsafe.go54
-rwxr-xr-xpkg/sentry/time/time_state_autogen.go4
-rw-r--r--pkg/sentry/unimpl/BUILD30
-rwxr-xr-xpkg/sentry/unimpl/unimpl_state_autogen.go4
-rw-r--r--pkg/sentry/unimpl/unimplemented_syscall.proto27
-rwxr-xr-xpkg/sentry/unimpl/unimplemented_syscall_go_proto/unimplemented_syscall.pb.go91
-rw-r--r--pkg/sentry/uniqueid/BUILD14
-rwxr-xr-xpkg/sentry/uniqueid/uniqueid_state_autogen.go4
-rw-r--r--pkg/sentry/usage/BUILD22
-rwxr-xr-xpkg/sentry/usage/usage_state_autogen.go50
-rw-r--r--pkg/sentry/usermem/BUILD59
-rw-r--r--pkg/sentry/usermem/README.md31
-rwxr-xr-xpkg/sentry/usermem/addr_range.go62
-rw-r--r--pkg/sentry/usermem/addr_range_seq_test.go197
-rwxr-xr-xpkg/sentry/usermem/usermem_state_autogen.go49
-rw-r--r--pkg/sentry/usermem/usermem_test.go424
-rw-r--r--pkg/sentry/vfs/BUILD57
-rw-r--r--pkg/sentry/vfs/README.md197
-rw-r--r--pkg/sentry/vfs/context.go37
-rw-r--r--pkg/sentry/vfs/debug.go22
-rw-r--r--pkg/sentry/vfs/dentry.go347
-rw-r--r--pkg/sentry/vfs/file_description.go213
-rw-r--r--pkg/sentry/vfs/file_description_impl_util.go254
-rw-r--r--pkg/sentry/vfs/file_description_impl_util_test.go141
-rw-r--r--pkg/sentry/vfs/filesystem.go155
-rw-r--r--pkg/sentry/vfs/filesystem_type.go70
-rw-r--r--pkg/sentry/vfs/mount.go411
-rw-r--r--pkg/sentry/vfs/mount_test.go465
-rw-r--r--pkg/sentry/vfs/mount_unsafe.go356
-rw-r--r--pkg/sentry/vfs/options.go123
-rw-r--r--pkg/sentry/vfs/permissions.go121
-rw-r--r--pkg/sentry/vfs/resolving_path.go453
-rw-r--r--pkg/sentry/vfs/syscalls.go217
-rw-r--r--pkg/sentry/vfs/testutil.go139
-rw-r--r--pkg/sentry/vfs/vfs.go135
-rw-r--r--pkg/sentry/watchdog/BUILD17
-rwxr-xr-xpkg/sentry/watchdog/watchdog_state_autogen.go4
-rw-r--r--pkg/sleep/BUILD25
-rw-r--r--pkg/sleep/empty.s15
-rwxr-xr-xpkg/sleep/sleep_state_autogen.go4
-rw-r--r--pkg/sleep/sleep_test.go542
-rw-r--r--pkg/state/BUILD79
-rwxr-xr-xpkg/state/addr_range.go62
-rwxr-xr-xpkg/state/addr_set.go1274
-rw-r--r--pkg/state/object.proto140
-rwxr-xr-xpkg/state/object_go_proto/object.pb.go1195
-rw-r--r--pkg/state/state_test.go720
-rw-r--r--pkg/state/statefile/BUILD23
-rwxr-xr-xpkg/state/statefile/statefile_state_autogen.go4
-rw-r--r--pkg/state/statefile/statefile_test.go290
-rw-r--r--pkg/syserr/BUILD19
-rwxr-xr-xpkg/syserr/syserr_state_autogen.go4
-rw-r--r--pkg/syserror/BUILD19
-rwxr-xr-xpkg/syserror/syserror_state_autogen.go4
-rw-r--r--pkg/syserror/syserror_test.go136
-rw-r--r--pkg/tcpip/BUILD27
-rw-r--r--pkg/tcpip/adapters/gonet/BUILD38
-rw-r--r--pkg/tcpip/adapters/gonet/gonet.go722
-rw-r--r--pkg/tcpip/adapters/gonet/gonet_test.go684
-rw-r--r--pkg/tcpip/buffer/BUILD22
-rwxr-xr-xpkg/tcpip/buffer/buffer_state_autogen.go24
-rw-r--r--pkg/tcpip/buffer/view_test.go235
-rw-r--r--pkg/tcpip/checker/BUILD16
-rw-r--r--pkg/tcpip/checker/checker.go688
-rw-r--r--pkg/tcpip/hash/jenkins/BUILD22
-rwxr-xr-xpkg/tcpip/hash/jenkins/jenkins_state_autogen.go4
-rw-r--r--pkg/tcpip/hash/jenkins/jenkins_test.go176
-rw-r--r--pkg/tcpip/header/BUILD43
-rwxr-xr-xpkg/tcpip/header/header_state_autogen.go42
-rw-r--r--pkg/tcpip/header/ipversion_test.go67
-rw-r--r--pkg/tcpip/header/tcp_test.go148
-rw-r--r--pkg/tcpip/iptables/BUILD15
-rwxr-xr-xpkg/tcpip/iptables/iptables_state_autogen.go4
-rw-r--r--pkg/tcpip/link/channel/BUILD15
-rw-r--r--pkg/tcpip/link/channel/channel.go135
-rw-r--r--pkg/tcpip/link/fdbased/BUILD42
-rw-r--r--pkg/tcpip/link/fdbased/endpoint_test.go454
-rwxr-xr-xpkg/tcpip/link/fdbased/fdbased_state_autogen.go4
-rw-r--r--pkg/tcpip/link/loopback/BUILD15
-rwxr-xr-xpkg/tcpip/link/loopback/loopback_state_autogen.go4
-rw-r--r--pkg/tcpip/link/muxed/BUILD32
-rw-r--r--pkg/tcpip/link/muxed/injectable.go112
-rw-r--r--pkg/tcpip/link/muxed/injectable_test.go94
-rw-r--r--pkg/tcpip/link/rawfile/BUILD20
-rwxr-xr-xpkg/tcpip/link/rawfile/rawfile_state_autogen.go4
-rw-r--r--pkg/tcpip/link/sharedmem/BUILD43
-rw-r--r--pkg/tcpip/link/sharedmem/pipe/BUILD24
-rw-r--r--pkg/tcpip/link/sharedmem/pipe/pipe.go78
-rw-r--r--pkg/tcpip/link/sharedmem/pipe/pipe_test.go517
-rw-r--r--pkg/tcpip/link/sharedmem/pipe/pipe_unsafe.go35
-rw-r--r--pkg/tcpip/link/sharedmem/pipe/rx.go93
-rw-r--r--pkg/tcpip/link/sharedmem/pipe/tx.go161
-rw-r--r--pkg/tcpip/link/sharedmem/queue/BUILD29
-rw-r--r--pkg/tcpip/link/sharedmem/queue/queue_test.go517
-rw-r--r--pkg/tcpip/link/sharedmem/queue/rx.go221
-rw-r--r--pkg/tcpip/link/sharedmem/queue/tx.go151
-rw-r--r--pkg/tcpip/link/sharedmem/rx.go159
-rw-r--r--pkg/tcpip/link/sharedmem/sharedmem.go264
-rw-r--r--pkg/tcpip/link/sharedmem/sharedmem_test.go776
-rw-r--r--pkg/tcpip/link/sharedmem/sharedmem_unsafe.go25
-rw-r--r--pkg/tcpip/link/sharedmem/tx.go272
-rw-r--r--pkg/tcpip/link/sniffer/BUILD22
-rwxr-xr-xpkg/tcpip/link/sniffer/sniffer_state_autogen.go4
-rw-r--r--pkg/tcpip/link/tun/BUILD12
-rw-r--r--pkg/tcpip/link/tun/tun_unsafe.go63
-rw-r--r--pkg/tcpip/link/waitable/BUILD34
-rw-r--r--pkg/tcpip/link/waitable/waitable.go122
-rw-r--r--pkg/tcpip/link/waitable/waitable_test.go159
-rw-r--r--pkg/tcpip/network/BUILD22
-rw-r--r--pkg/tcpip/network/arp/BUILD36
-rwxr-xr-xpkg/tcpip/network/arp/arp_state_autogen.go4
-rw-r--r--pkg/tcpip/network/arp/arp_test.go140
-rw-r--r--pkg/tcpip/network/fragmentation/BUILD54
-rw-r--r--pkg/tcpip/network/fragmentation/frag_heap_test.go126
-rwxr-xr-xpkg/tcpip/network/fragmentation/fragmentation_state_autogen.go38
-rw-r--r--pkg/tcpip/network/fragmentation/fragmentation_test.go159
-rwxr-xr-xpkg/tcpip/network/fragmentation/reassembler_list.go173
-rw-r--r--pkg/tcpip/network/fragmentation/reassembler_test.go105
-rw-r--r--pkg/tcpip/network/hash/BUILD14
-rwxr-xr-xpkg/tcpip/network/hash/hash_state_autogen.go4
-rw-r--r--pkg/tcpip/network/ip_test.go609
-rw-r--r--pkg/tcpip/network/ipv4/BUILD42
-rwxr-xr-xpkg/tcpip/network/ipv4/ipv4_state_autogen.go4
-rw-r--r--pkg/tcpip/network/ipv4/ipv4_test.go366
-rw-r--r--pkg/tcpip/network/ipv6/BUILD42
-rw-r--r--pkg/tcpip/network/ipv6/icmp_test.go362
-rwxr-xr-xpkg/tcpip/network/ipv6/ipv6_state_autogen.go4
-rw-r--r--pkg/tcpip/network/ipv6/ndp_test.go178
-rw-r--r--pkg/tcpip/ports/BUILD23
-rwxr-xr-xpkg/tcpip/ports/ports_state_autogen.go4
-rw-r--r--pkg/tcpip/ports/ports_test.go177
-rw-r--r--pkg/tcpip/sample/tun_tcp_connect/BUILD21
-rw-r--r--pkg/tcpip/sample/tun_tcp_connect/main.go222
-rw-r--r--pkg/tcpip/sample/tun_tcp_echo/BUILD20
-rw-r--r--pkg/tcpip/sample/tun_tcp_echo/main.go200
-rw-r--r--pkg/tcpip/seqnum/BUILD12
-rwxr-xr-xpkg/tcpip/seqnum/seqnum_state_autogen.go4
-rw-r--r--pkg/tcpip/stack/BUILD88
-rw-r--r--pkg/tcpip/stack/linkaddrcache_test.go277
-rwxr-xr-xpkg/tcpip/stack/linkaddrentry_list.go173
-rwxr-xr-xpkg/tcpip/stack/stack_state_autogen.go87
-rw-r--r--pkg/tcpip/stack/stack_test.go1722
-rw-r--r--pkg/tcpip/stack/transport_test.go579
-rwxr-xr-xpkg/tcpip/tcpip_state_autogen.go44
-rw-r--r--pkg/tcpip/tcpip_test.go197
-rw-r--r--pkg/tcpip/time.s15
-rw-r--r--pkg/tcpip/transport/icmp/BUILD48
-rwxr-xr-xpkg/tcpip/transport/icmp/icmp_packet_list.go173
-rwxr-xr-xpkg/tcpip/transport/icmp/icmp_state_autogen.go98
-rw-r--r--pkg/tcpip/transport/raw/BUILD47
-rwxr-xr-xpkg/tcpip/transport/raw/packet_list.go173
-rwxr-xr-xpkg/tcpip/transport/raw/raw_state_autogen.go98
-rw-r--r--pkg/tcpip/transport/tcp/BUILD100
-rw-r--r--pkg/tcpip/transport/tcp/dual_stack_test.go658
-rw-r--r--pkg/tcpip/transport/tcp/sack_scoreboard_test.go249
-rw-r--r--pkg/tcpip/transport/tcp/tcp_noracedetector_test.go519
-rw-r--r--pkg/tcpip/transport/tcp/tcp_sack_test.go564
-rwxr-xr-xpkg/tcpip/transport/tcp/tcp_segment_list.go173
-rwxr-xr-xpkg/tcpip/transport/tcp/tcp_state_autogen.go469
-rw-r--r--pkg/tcpip/transport/tcp/tcp_test.go4370
-rw-r--r--pkg/tcpip/transport/tcp/tcp_timestamp_test.go295
-rw-r--r--pkg/tcpip/transport/tcp/testing/context/BUILD27
-rw-r--r--pkg/tcpip/transport/tcp/testing/context/context.go1046
-rw-r--r--pkg/tcpip/transport/tcpconntrack/BUILD25
-rw-r--r--pkg/tcpip/transport/tcpconntrack/tcp_conntrack.go349
-rw-r--r--pkg/tcpip/transport/tcpconntrack/tcp_conntrack_test.go511
-rw-r--r--pkg/tcpip/transport/udp/BUILD69
-rwxr-xr-xpkg/tcpip/transport/udp/udp_packet_list.go173
-rwxr-xr-xpkg/tcpip/transport/udp/udp_state_autogen.go128
-rw-r--r--pkg/tcpip/transport/udp/udp_test.go1395
-rw-r--r--pkg/tmutex/BUILD18
-rwxr-xr-xpkg/tmutex/tmutex_state_autogen.go4
-rw-r--r--pkg/tmutex/tmutex_test.go257
-rw-r--r--pkg/unet/BUILD27
-rwxr-xr-xpkg/unet/unet_state_autogen.go4
-rw-r--r--pkg/unet/unet_test.go735
-rw-r--r--pkg/urpc/BUILD24
-rwxr-xr-xpkg/urpc/urpc_state_autogen.go4
-rw-r--r--pkg/urpc/urpc_test.go210
-rw-r--r--pkg/waiter/BUILD45
-rwxr-xr-xpkg/waiter/waiter_list.go173
-rwxr-xr-xpkg/waiter/waiter_state_autogen.go67
-rw-r--r--pkg/waiter/waiter_test.go192
-rw-r--r--runsc/BUILD110
-rw-r--r--runsc/boot/BUILD116
-rw-r--r--runsc/boot/compat_test.go85
-rw-r--r--runsc/boot/filter/BUILD26
-rw-r--r--runsc/boot/fs_test.go193
-rw-r--r--runsc/boot/loader_test.go631
-rw-r--r--runsc/boot/platforms/BUILD16
-rw-r--r--runsc/boot/user_test.go253
-rw-r--r--runsc/cgroup/BUILD24
-rw-r--r--runsc/cgroup/cgroup_test.go67
-rw-r--r--runsc/cmd/BUILD91
-rw-r--r--runsc/cmd/capability_test.go128
-rw-r--r--runsc/cmd/delete_test.go41
-rw-r--r--runsc/cmd/exec_test.go154
-rw-r--r--runsc/cmd/gofer_test.go164
-rw-r--r--runsc/console/BUILD18
-rw-r--r--runsc/container/BUILD65
-rw-r--r--runsc/container/console_test.go483
-rw-r--r--runsc/container/container_test.go2073
-rw-r--r--runsc/container/multi_container_test.go1548
-rw-r--r--runsc/container/shared_volume_test.go277
-rw-r--r--runsc/container/test_app/BUILD19
-rw-r--r--runsc/container/test_app/fds.go185
-rw-r--r--runsc/container/test_app/test_app.go289
-rw-r--r--runsc/criutil/BUILD12
-rw-r--r--runsc/criutil/criutil.go246
-rw-r--r--runsc/debian/description5
-rwxr-xr-xrunsc/debian/postinst.sh24
-rw-r--r--runsc/dockerutil/BUILD15
-rw-r--r--runsc/dockerutil/dockerutil.go452
-rw-r--r--runsc/fsgofer/BUILD35
-rw-r--r--runsc/fsgofer/filter/BUILD25
-rw-r--r--runsc/fsgofer/fsgofer_test.go692
-rw-r--r--runsc/sandbox/BUILD34
-rw-r--r--runsc/specutils/BUILD31
-rw-r--r--runsc/specutils/specutils_test.go265
-rw-r--r--runsc/testutil/BUILD17
-rw-r--r--runsc/testutil/testutil.go440
-rwxr-xr-xrunsc/version_test.sh36
-rwxr-xr-xscripts/build.sh79
-rwxr-xr-xscripts/common.sh80
-rwxr-xr-xscripts/common_bazel.sh92
-rwxr-xr-xscripts/dev.sh72
-rwxr-xr-xscripts/do_tests.sh27
-rwxr-xr-xscripts/docker_tests.sh20
-rwxr-xr-xscripts/go.sh43
-rwxr-xr-xscripts/hostnet_tests.sh21
-rwxr-xr-xscripts/kvm_tests.sh28
-rwxr-xr-xscripts/make_tests.sh24
-rwxr-xr-xscripts/overlay_tests.sh21
-rwxr-xr-xscripts/release.sh38
-rwxr-xr-xscripts/root_tests.sh31
-rwxr-xr-xscripts/simple_tests.sh20
-rwxr-xr-xscripts/syscall_tests.sh20
-rw-r--r--test/BUILD44
-rw-r--r--test/README.md18
-rw-r--r--test/e2e/BUILD31
-rw-r--r--test/e2e/exec_test.go156
-rw-r--r--test/e2e/integration.go16
-rw-r--r--test/e2e/integration_test.go348
-rw-r--r--test/e2e/regression_test.go45
-rw-r--r--test/image/BUILD34
-rw-r--r--test/image/image.go16
-rw-r--r--test/image/image_test.go353
-rw-r--r--test/image/latin10k.txt33
-rw-r--r--test/image/mysql.sql23
-rw-r--r--test/image/ruby.rb23
-rw-r--r--test/image/ruby.sh20
-rw-r--r--test/root/BUILD44
-rw-r--r--test/root/cgroup_test.go232
-rw-r--r--test/root/chroot_test.go142
-rw-r--r--test/root/crictl_test.go242
-rw-r--r--test/root/main_test.go49
-rw-r--r--test/root/oom_score_adj_test.go376
-rw-r--r--test/root/root.go21
-rw-r--r--test/root/testdata/BUILD18
-rw-r--r--test/root/testdata/busybox.go32
-rw-r--r--test/root/testdata/containerd_config.go39
-rw-r--r--test/root/testdata/httpd.go32
-rw-r--r--test/root/testdata/httpd_mount_paths.go53
-rw-r--r--test/root/testdata/sandbox.go30
-rw-r--r--test/runtimes/BUILD25
-rw-r--r--test/runtimes/README.md40
-rw-r--r--test/runtimes/build_defs.bzl19
-rw-r--r--test/runtimes/common/BUILD20
-rw-r--r--test/runtimes/common/common.go114
-rw-r--r--test/runtimes/common/common_test.go128
-rw-r--r--test/runtimes/go/BUILD9
-rw-r--r--test/runtimes/go/Dockerfile35
-rw-r--r--test/runtimes/go/proctor-go.go105
-rw-r--r--test/runtimes/java/BUILD9
-rw-r--r--test/runtimes/java/Dockerfile36
-rw-r--r--test/runtimes/java/proctor-java.go80
-rw-r--r--test/runtimes/nodejs/BUILD9
-rw-r--r--test/runtimes/nodejs/Dockerfile31
-rw-r--r--test/runtimes/nodejs/proctor-nodejs.go60
-rw-r--r--test/runtimes/php/BUILD9
-rw-r--r--test/runtimes/php/Dockerfile31
-rw-r--r--test/runtimes/php/proctor-php.go58
-rw-r--r--test/runtimes/python/BUILD9
-rw-r--r--test/runtimes/python/Dockerfile33
-rw-r--r--test/runtimes/python/proctor-python.go65
-rw-r--r--test/runtimes/runtimes.go20
-rw-r--r--test/runtimes/runtimes_test.go93
-rw-r--r--test/syscalls/BUILD711
-rw-r--r--test/syscalls/README.md107
-rw-r--r--test/syscalls/build_defs.bzl128
-rw-r--r--test/syscalls/gtest/BUILD12
-rw-r--r--test/syscalls/gtest/gtest.go93
-rw-r--r--test/syscalls/linux/32bit.cc226
-rw-r--r--test/syscalls/linux/BUILD3480
-rw-r--r--test/syscalls/linux/accept_bind.cc586
-rw-r--r--test/syscalls/linux/accept_bind_stream.cc91
-rw-r--r--test/syscalls/linux/access.cc170
-rw-r--r--test/syscalls/linux/affinity.cc242
-rw-r--r--test/syscalls/linux/aio.cc424
-rw-r--r--test/syscalls/linux/alarm.cc193
-rw-r--r--test/syscalls/linux/arch_prctl.cc48
-rw-r--r--test/syscalls/linux/bad.cc39
-rw-r--r--test/syscalls/linux/base_poll_test.cc65
-rw-r--r--test/syscalls/linux/base_poll_test.h101
-rw-r--r--test/syscalls/linux/bind.cc146
-rw-r--r--test/syscalls/linux/brk.cc31
-rw-r--r--test/syscalls/linux/chdir.cc64
-rw-r--r--test/syscalls/linux/chmod.cc263
-rw-r--r--test/syscalls/linux/chown.cc200
-rw-r--r--test/syscalls/linux/chroot.cc366
-rw-r--r--test/syscalls/linux/clock_getres.cc37
-rw-r--r--test/syscalls/linux/clock_gettime.cc167
-rw-r--r--test/syscalls/linux/clock_nanosleep.cc153
-rw-r--r--test/syscalls/linux/concurrency.cc123
-rw-r--r--test/syscalls/linux/creat.cc68
-rw-r--r--test/syscalls/linux/dev.cc159
-rw-r--r--test/syscalls/linux/dup.cc133
-rw-r--r--test/syscalls/linux/epoll.cc432
-rw-r--r--test/syscalls/linux/eventfd.cc180
-rw-r--r--test/syscalls/linux/exceptions.cc184
-rw-r--r--test/syscalls/linux/exec.cc630
-rw-r--r--test/syscalls/linux/exec.h34
-rw-r--r--test/syscalls/linux/exec_assert_closed_workload.cc45
-rw-r--r--test/syscalls/linux/exec_basic_workload.cc31
-rw-r--r--test/syscalls/linux/exec_binary.cc1502
-rw-r--r--test/syscalls/linux/exec_proc_exe_workload.cc36
-rw-r--r--test/syscalls/linux/exec_state_workload.cc202
-rw-r--r--test/syscalls/linux/exit.cc78
-rwxr-xr-xtest/syscalls/linux/exit_script.sh22
-rw-r--r--test/syscalls/linux/fadvise64.cc72
-rw-r--r--test/syscalls/linux/fallocate.cc141
-rw-r--r--test/syscalls/linux/fault.cc71
-rw-r--r--test/syscalls/linux/fchdir.cc77
-rw-r--r--test/syscalls/linux/fcntl.cc970
-rw-r--r--test/syscalls/linux/file_base.h206
-rw-r--r--test/syscalls/linux/flock.cc588
-rw-r--r--test/syscalls/linux/fork.cc444
-rw-r--r--test/syscalls/linux/fpsig_fork.cc105
-rw-r--r--test/syscalls/linux/fpsig_nested.cc134
-rw-r--r--test/syscalls/linux/fsync.cc58
-rw-r--r--test/syscalls/linux/futex.cc721
-rw-r--r--test/syscalls/linux/getcpu.cc40
-rw-r--r--test/syscalls/linux/getdents.cc529
-rw-r--r--test/syscalls/linux/getrandom.cc61
-rw-r--r--test/syscalls/linux/getrusage.cc177
-rw-r--r--test/syscalls/linux/inotify.cc1596
-rw-r--r--test/syscalls/linux/ioctl.cc406
-rw-r--r--test/syscalls/linux/ip_socket_test_util.cc197
-rw-r--r--test/syscalls/linux/ip_socket_test_util.h131
-rw-r--r--test/syscalls/linux/iptables.cc204
-rw-r--r--test/syscalls/linux/iptables.h198
-rw-r--r--test/syscalls/linux/itimer.cc346
-rw-r--r--test/syscalls/linux/kill.cc382
-rw-r--r--test/syscalls/linux/link.cc291
-rw-r--r--test/syscalls/linux/lseek.cc202
-rw-r--r--test/syscalls/linux/madvise.cc252
-rw-r--r--test/syscalls/linux/memfd.cc556
-rw-r--r--test/syscalls/linux/memory_accounting.cc99
-rw-r--r--test/syscalls/linux/mempolicy.cc289
-rw-r--r--test/syscalls/linux/mincore.cc96
-rw-r--r--test/syscalls/linux/mkdir.cc96
-rw-r--r--test/syscalls/linux/mknod.cc168
-rw-r--r--test/syscalls/linux/mlock.cc330
-rw-r--r--test/syscalls/linux/mmap.cc1738
-rw-r--r--test/syscalls/linux/mount.cc326
-rw-r--r--test/syscalls/linux/mremap.cc492
-rw-r--r--test/syscalls/linux/msync.cc153
-rw-r--r--test/syscalls/linux/munmap.cc53
-rw-r--r--test/syscalls/linux/open.cc378
-rw-r--r--test/syscalls/linux/open_create.cc130
-rw-r--r--test/syscalls/linux/packet_socket.cc299
-rw-r--r--test/syscalls/linux/packet_socket_raw.cc314
-rw-r--r--test/syscalls/linux/partial_bad_buffer.cc415
-rw-r--r--test/syscalls/linux/pause.cc88
-rw-r--r--test/syscalls/linux/pipe.cc650
-rw-r--r--test/syscalls/linux/poll.cc293
-rw-r--r--test/syscalls/linux/ppoll.cc155
-rw-r--r--test/syscalls/linux/prctl.cc229
-rw-r--r--test/syscalls/linux/prctl_setuid.cc262
-rw-r--r--test/syscalls/linux/pread64.cc152
-rw-r--r--test/syscalls/linux/preadv.cc95
-rw-r--r--test/syscalls/linux/preadv2.cc279
-rw-r--r--test/syscalls/linux/priority.cc216
-rw-r--r--test/syscalls/linux/priority_execve.cc42
-rw-r--r--test/syscalls/linux/proc.cc1996
-rw-r--r--test/syscalls/linux/proc_net.cc62
-rw-r--r--test/syscalls/linux/proc_net_tcp.cc255
-rw-r--r--test/syscalls/linux/proc_net_udp.cc309
-rw-r--r--test/syscalls/linux/proc_net_unix.cc444
-rw-r--r--test/syscalls/linux/proc_pid_smaps.cc468
-rw-r--r--test/syscalls/linux/proc_pid_uid_gid_map.cc311
-rw-r--r--test/syscalls/linux/pselect.cc190
-rw-r--r--test/syscalls/linux/ptrace.cc1214
-rw-r--r--test/syscalls/linux/pty.cc1238
-rw-r--r--test/syscalls/linux/pwrite64.cc79
-rw-r--r--test/syscalls/linux/pwritev2.cc345
-rw-r--r--test/syscalls/linux/raw_socket_hdrincl.cc408
-rw-r--r--test/syscalls/linux/raw_socket_icmp.cc509
-rw-r--r--test/syscalls/linux/raw_socket_ipv4.cc387
-rw-r--r--test/syscalls/linux/read.cc117
-rw-r--r--test/syscalls/linux/readv.cc293
-rw-r--r--test/syscalls/linux/readv_common.cc180
-rw-r--r--test/syscalls/linux/readv_common.h61
-rw-r--r--test/syscalls/linux/readv_socket.cc182
-rw-r--r--test/syscalls/linux/rename.cc394
-rw-r--r--test/syscalls/linux/rlimits.cc75
-rw-r--r--test/syscalls/linux/rtsignal.cc172
-rw-r--r--test/syscalls/linux/sched.cc71
-rw-r--r--test/syscalls/linux/sched_yield.cc33
-rw-r--r--test/syscalls/linux/seccomp.cc406
-rw-r--r--test/syscalls/linux/select.cc168
-rw-r--r--test/syscalls/linux/semaphore.cc492
-rw-r--r--test/syscalls/linux/sendfile.cc517
-rw-r--r--test/syscalls/linux/sendfile_socket.cc242
-rw-r--r--test/syscalls/linux/shm.cc509
-rw-r--r--test/syscalls/linux/sigaction.cc70
-rw-r--r--test/syscalls/linux/sigaltstack.cc275
-rw-r--r--test/syscalls/linux/sigaltstack_check.cc33
-rw-r--r--test/syscalls/linux/sigiret.cc137
-rw-r--r--test/syscalls/linux/sigprocmask.cc269
-rw-r--r--test/syscalls/linux/sigstop.cc150
-rw-r--r--test/syscalls/linux/sigtimedwait.cc324
-rw-r--r--test/syscalls/linux/socket.cc61
-rw-r--r--test/syscalls/linux/socket_abstract.cc47
-rw-r--r--test/syscalls/linux/socket_blocking.cc60
-rw-r--r--test/syscalls/linux/socket_blocking.h29
-rw-r--r--test/syscalls/linux/socket_filesystem.cc47
-rw-r--r--test/syscalls/linux/socket_generic.cc741
-rw-r--r--test/syscalls/linux/socket_generic.h30
-rw-r--r--test/syscalls/linux/socket_inet_loopback.cc1208
-rw-r--r--test/syscalls/linux/socket_ip_loopback_blocking.cc46
-rw-r--r--test/syscalls/linux/socket_ip_tcp_generic.cc701
-rw-r--r--test/syscalls/linux/socket_ip_tcp_generic.h29
-rw-r--r--test/syscalls/linux/socket_ip_tcp_generic_loopback.cc42
-rw-r--r--test/syscalls/linux/socket_ip_tcp_loopback.cc38
-rw-r--r--test/syscalls/linux/socket_ip_tcp_loopback_blocking.cc42
-rw-r--r--test/syscalls/linux/socket_ip_tcp_loopback_nonblock.cc41
-rw-r--r--test/syscalls/linux/socket_ip_tcp_udp_generic.cc78
-rw-r--r--test/syscalls/linux/socket_ip_udp_generic.cc214
-rw-r--r--test/syscalls/linux/socket_ip_udp_generic.h29
-rw-r--r--test/syscalls/linux/socket_ip_udp_loopback.cc48
-rw-r--r--test/syscalls/linux/socket_ip_udp_loopback_blocking.cc37
-rw-r--r--test/syscalls/linux/socket_ip_udp_loopback_nonblock.cc37
-rw-r--r--test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.cc66
-rw-r--r--test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h30
-rw-r--r--test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking_test.cc35
-rw-r--r--test/syscalls/linux/socket_ipv4_udp_unbound.cc1684
-rw-r--r--test/syscalls/linux/socket_ipv4_udp_unbound.h29
-rw-r--r--test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc849
-rw-r--r--test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h48
-rw-r--r--test/syscalls/linux/socket_ipv4_udp_unbound_external_networking_test.cc35
-rw-r--r--test/syscalls/linux/socket_ipv4_udp_unbound_loopback.cc35
-rw-r--r--test/syscalls/linux/socket_netdevice.cc183
-rw-r--r--test/syscalls/linux/socket_netlink_route.cc545
-rw-r--r--test/syscalls/linux/socket_netlink_util.cc106
-rw-r--r--test/syscalls/linux/socket_netlink_util.h43
-rw-r--r--test/syscalls/linux/socket_non_blocking.cc63
-rw-r--r--test/syscalls/linux/socket_non_blocking.h29
-rw-r--r--test/syscalls/linux/socket_non_stream.cc229
-rw-r--r--test/syscalls/linux/socket_non_stream.h29
-rw-r--r--test/syscalls/linux/socket_non_stream_blocking.cc49
-rw-r--r--test/syscalls/linux/socket_non_stream_blocking.h30
-rw-r--r--test/syscalls/linux/socket_stream.cc126
-rw-r--r--test/syscalls/linux/socket_stream.h30
-rw-r--r--test/syscalls/linux/socket_stream_blocking.cc164
-rw-r--r--test/syscalls/linux/socket_stream_blocking.h30
-rw-r--r--test/syscalls/linux/socket_stream_nonblock.cc50
-rw-r--r--test/syscalls/linux/socket_stream_nonblock.h30
-rw-r--r--test/syscalls/linux/socket_test_util.cc815
-rw-r--r--test/syscalls/linux/socket_test_util.h503
-rw-r--r--test/syscalls/linux/socket_test_util_impl.cc28
-rw-r--r--test/syscalls/linux/socket_unix.cc259
-rw-r--r--test/syscalls/linux/socket_unix.h29
-rw-r--r--test/syscalls/linux/socket_unix_abstract_nonblock.cc37
-rw-r--r--test/syscalls/linux/socket_unix_blocking_local.cc44
-rw-r--r--test/syscalls/linux/socket_unix_cmsg.cc1473
-rw-r--r--test/syscalls/linux/socket_unix_cmsg.h30
-rw-r--r--test/syscalls/linux/socket_unix_dgram.cc45
-rw-r--r--test/syscalls/linux/socket_unix_dgram.h29
-rw-r--r--test/syscalls/linux/socket_unix_dgram_local.cc56
-rw-r--r--test/syscalls/linux/socket_unix_dgram_non_blocking.cc57
-rw-r--r--test/syscalls/linux/socket_unix_domain.cc37
-rw-r--r--test/syscalls/linux/socket_unix_filesystem_nonblock.cc37
-rw-r--r--test/syscalls/linux/socket_unix_non_stream.cc247
-rw-r--r--test/syscalls/linux/socket_unix_non_stream.h30
-rw-r--r--test/syscalls/linux/socket_unix_non_stream_blocking_local.cc41
-rw-r--r--test/syscalls/linux/socket_unix_pair.cc42
-rw-r--r--test/syscalls/linux/socket_unix_pair_nonblock.cc37
-rw-r--r--test/syscalls/linux/socket_unix_seqpacket.cc49
-rw-r--r--test/syscalls/linux/socket_unix_seqpacket.h30
-rw-r--r--test/syscalls/linux/socket_unix_seqpacket_local.cc56
-rw-r--r--test/syscalls/linux/socket_unix_stream.cc66
-rw-r--r--test/syscalls/linux/socket_unix_stream_blocking_local.cc39
-rw-r--r--test/syscalls/linux/socket_unix_stream_local.cc46
-rw-r--r--test/syscalls/linux/socket_unix_stream_nonblock_local.cc38
-rw-r--r--test/syscalls/linux/socket_unix_unbound_abstract.cc116
-rw-r--r--test/syscalls/linux/socket_unix_unbound_dgram.cc184
-rw-r--r--test/syscalls/linux/socket_unix_unbound_filesystem.cc84
-rw-r--r--test/syscalls/linux/socket_unix_unbound_seqpacket.cc89
-rw-r--r--test/syscalls/linux/socket_unix_unbound_stream.cc733
-rw-r--r--test/syscalls/linux/splice.cc593
-rw-r--r--test/syscalls/linux/stat.cc658
-rw-r--r--test/syscalls/linux/stat_times.cc303
-rw-r--r--test/syscalls/linux/statfs.cc82
-rw-r--r--test/syscalls/linux/sticky.cc116
-rw-r--r--test/syscalls/linux/symlink.cc377
-rw-r--r--test/syscalls/linux/sync.cc60
-rw-r--r--test/syscalls/linux/sync_file_range.cc112
-rw-r--r--test/syscalls/linux/sysinfo.cc86
-rw-r--r--test/syscalls/linux/syslog.cc51
-rw-r--r--test/syscalls/linux/sysret.cc113
-rw-r--r--test/syscalls/linux/tcp_socket.cc1159
-rw-r--r--test/syscalls/linux/temp_umask.h39
-rw-r--r--test/syscalls/linux/tgkill.cc48
-rw-r--r--test/syscalls/linux/time.cc104
-rw-r--r--test/syscalls/linux/timerfd.cc256
-rw-r--r--test/syscalls/linux/timers.cc645
-rw-r--r--test/syscalls/linux/tkill.cc75
-rw-r--r--test/syscalls/linux/truncate.cc217
-rw-r--r--test/syscalls/linux/udp_bind.cc316
-rw-r--r--test/syscalls/linux/udp_socket.cc1349
-rw-r--r--test/syscalls/linux/uidgid.cc277
-rw-r--r--test/syscalls/linux/uname.cc99
-rw-r--r--test/syscalls/linux/unix_domain_socket_test_util.cc350
-rw-r--r--test/syscalls/linux/unix_domain_socket_test_util.h161
-rw-r--r--test/syscalls/linux/unlink.cc214
-rw-r--r--test/syscalls/linux/unshare.cc50
-rw-r--r--test/syscalls/linux/utimes.cc330
-rw-r--r--test/syscalls/linux/vdso.cc48
-rw-r--r--test/syscalls/linux/vdso_clock_gettime.cc107
-rw-r--r--test/syscalls/linux/vfork.cc194
-rw-r--r--test/syscalls/linux/vsyscall.cc44
-rw-r--r--test/syscalls/linux/wait.cc913
-rw-r--r--test/syscalls/linux/write.cc145
-rw-r--r--test/syscalls/syscall_test_runner.go410
-rwxr-xr-xtest/syscalls/syscall_test_runner.sh34
-rw-r--r--test/util/BUILD314
-rw-r--r--test/util/capability_util.cc81
-rw-r--r--test/util/capability_util.h101
-rw-r--r--test/util/cleanup.h61
-rw-r--r--test/util/epoll_util.cc52
-rw-r--r--test/util/epoll_util.h36
-rw-r--r--test/util/eventfd_util.h43
-rw-r--r--test/util/file_descriptor.h134
-rw-r--r--test/util/fs_util.cc604
-rw-r--r--test/util/fs_util.h185
-rw-r--r--test/util/fs_util_test.cc103
-rw-r--r--test/util/logging.cc97
-rw-r--r--test/util/logging.h73
-rw-r--r--test/util/memory_util.h147
-rw-r--r--test/util/mount_util.h50
-rw-r--r--test/util/multiprocess_util.cc139
-rw-r--r--test/util/multiprocess_util.h114
-rw-r--r--test/util/posix_error.cc98
-rw-r--r--test/util/posix_error.h462
-rw-r--r--test/util/posix_error_test.cc45
-rw-r--r--test/util/proc_util.cc107
-rw-r--r--test/util/proc_util.h150
-rw-r--r--test/util/proc_util_test.cc81
-rw-r--r--test/util/rlimit_util.cc44
-rw-r--r--test/util/rlimit_util.h32
-rw-r--r--test/util/save_util.cc71
-rw-r--r--test/util/save_util.h52
-rw-r--r--test/util/save_util_linux.cc33
-rw-r--r--test/util/save_util_other.cc23
-rw-r--r--test/util/signal_util.cc103
-rw-r--r--test/util/signal_util.h92
-rw-r--r--test/util/temp_path.cc163
-rw-r--r--test/util/temp_path.h134
-rw-r--r--test/util/test_main.cc20
-rw-r--r--test/util/test_util.cc236
-rw-r--r--test/util/test_util.h771
-rw-r--r--test/util/test_util_test.cc250
-rw-r--r--test/util/thread_util.h93
-rw-r--r--test/util/time_util.cc41
-rw-r--r--test/util/time_util.h29
-rw-r--r--test/util/timer_util.cc27
-rw-r--r--test/util/timer_util.h74
-rw-r--r--third_party/gvsync/BUILD54
-rw-r--r--third_party/gvsync/LICENSE27
-rw-r--r--third_party/gvsync/README.md3
-rw-r--r--third_party/gvsync/atomicptrtest/BUILD29
-rw-r--r--third_party/gvsync/atomicptrtest/atomicptr_test.go31
-rw-r--r--third_party/gvsync/downgradable_rwmutex_test.go150
-rw-r--r--third_party/gvsync/seqatomictest/BUILD35
-rw-r--r--third_party/gvsync/seqatomictest/seqatomic_test.go132
-rw-r--r--third_party/gvsync/seqcount_test.go153
-rw-r--r--tools/checkunsafe/BUILD13
-rw-r--r--tools/checkunsafe/check_unsafe.go56
-rwxr-xr-xtools/go_branch.sh82
-rw-r--r--tools/go_generics/BUILD38
-rw-r--r--tools/go_generics/defs.bzl140
-rw-r--r--tools/go_generics/generics.go284
-rw-r--r--tools/go_generics/generics_tests/all_stmts/input.go290
-rw-r--r--tools/go_generics/generics_tests/all_stmts/opts.txt1
-rw-r--r--tools/go_generics/generics_tests/all_stmts/output/output.go288
-rw-r--r--tools/go_generics/generics_tests/all_types/input.go43
-rw-r--r--tools/go_generics/generics_tests/all_types/lib/lib.go17
-rw-r--r--tools/go_generics/generics_tests/all_types/opts.txt1
-rw-r--r--tools/go_generics/generics_tests/all_types/output/output.go41
-rw-r--r--tools/go_generics/generics_tests/anon/input.go46
-rw-r--r--tools/go_generics/generics_tests/anon/opts.txt1
-rw-r--r--tools/go_generics/generics_tests/anon/output/output.go42
-rw-r--r--tools/go_generics/generics_tests/consts/input.go26
-rw-r--r--tools/go_generics/generics_tests/consts/opts.txt1
-rw-r--r--tools/go_generics/generics_tests/consts/output/output.go26
-rw-r--r--tools/go_generics/generics_tests/imports/input.go24
-rw-r--r--tools/go_generics/generics_tests/imports/opts.txt1
-rw-r--r--tools/go_generics/generics_tests/imports/output/output.go27
-rw-r--r--tools/go_generics/generics_tests/remove_typedef/input.go37
-rw-r--r--tools/go_generics/generics_tests/remove_typedef/opts.txt1
-rw-r--r--tools/go_generics/generics_tests/remove_typedef/output/output.go29
-rw-r--r--tools/go_generics/generics_tests/simple/input.go45
-rw-r--r--tools/go_generics/generics_tests/simple/opts.txt1
-rw-r--r--tools/go_generics/generics_tests/simple/output/output.go43
-rw-r--r--tools/go_generics/globals/BUILD13
-rw-r--r--tools/go_generics/globals/globals_visitor.go597
-rw-r--r--tools/go_generics/globals/scope.go80
-rwxr-xr-xtools/go_generics/go_generics_unittest.sh70
-rw-r--r--tools/go_generics/go_merge/BUILD9
-rw-r--r--tools/go_generics/go_merge/main.go139
-rw-r--r--tools/go_generics/imports.go150
-rw-r--r--tools/go_generics/remove.go105
-rw-r--r--tools/go_generics/rules_tests/BUILD44
-rw-r--r--tools/go_generics/rules_tests/template.go42
-rw-r--r--tools/go_generics/rules_tests/template_test.go48
-rw-r--r--tools/go_marshal/BUILD14
-rw-r--r--tools/go_marshal/README.md164
-rw-r--r--tools/go_marshal/analysis/BUILD13
-rw-r--r--tools/go_marshal/analysis/analysis_unsafe.go175
-rw-r--r--tools/go_marshal/defs.bzl152
-rw-r--r--tools/go_marshal/gomarshal/BUILD17
-rw-r--r--tools/go_marshal/gomarshal/generator.go382
-rw-r--r--tools/go_marshal/gomarshal/generator_interfaces.go507
-rw-r--r--tools/go_marshal/gomarshal/generator_tests.go154
-rw-r--r--tools/go_marshal/gomarshal/util.go387
-rw-r--r--tools/go_marshal/main.go73
-rw-r--r--tools/go_marshal/marshal/BUILD14
-rw-r--r--tools/go_marshal/marshal/marshal.go60
-rw-r--r--tools/go_marshal/test/BUILD31
-rw-r--r--tools/go_marshal/test/benchmark_test.go178
-rw-r--r--tools/go_marshal/test/external/BUILD11
-rw-r--r--tools/go_marshal/test/external/external.go23
-rw-r--r--tools/go_marshal/test/test.go105
-rw-r--r--tools/go_stateify/BUILD9
-rw-r--r--tools/go_stateify/defs.bzl136
-rw-r--r--tools/go_stateify/main.go418
-rwxr-xr-xtools/image_build.sh98
-rwxr-xr-xtools/make_repository.sh78
-rw-r--r--tools/nogo.js7
-rwxr-xr-xtools/tag_release.sh68
-rwxr-xr-xtools/workspace_status.sh18
-rw-r--r--vdso/BUILD92
-rw-r--r--vdso/barrier.h49
-rw-r--r--vdso/check_vdso.py204
-rw-r--r--vdso/compiler.h29
-rw-r--r--vdso/cycle_clock.h51
-rw-r--r--vdso/seqlock.h39
-rw-r--r--vdso/syscalls.h99
-rw-r--r--vdso/vdso.cc151
-rw-r--r--vdso/vdso_amd64.lds101
-rw-r--r--vdso/vdso_arm64.lds99
-rw-r--r--vdso/vdso_time.cc159
-rw-r--r--vdso/vdso_time.h27
1158 files changed, 34055 insertions, 166708 deletions
diff --git a/.bazelrc b/.bazelrc
deleted file mode 100644
index 379fc8328..000000000
--- a/.bazelrc
+++ /dev/null
@@ -1,63 +0,0 @@
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Display the current git revision in the info block.
-build --stamp --workspace_status_command tools/workspace_status.sh
-
-# Enable remote execution so actions are performed on the remote systems.
-build:remote --remote_executor=grpcs://remotebuildexecution.googleapis.com
-
-# Add a custom platform and toolchain that builds in a privileged docker
-# container, which is required by our syscall tests.
-build:remote --host_platform=//test:rbe_ubuntu1604
-build:remote --extra_toolchains=//test:cc-toolchain-clang-x86_64-default
-build:remote --extra_execution_platforms=//test:rbe_ubuntu1604
-build:remote --platforms=//test:rbe_ubuntu1604
-
-# Use default image for crosstool toolchain.
-build:remote --crosstool_top=@rbe_default//cc:toolchain
-
-# Default parallelism and timeout for remote jobs.
-build:remote --jobs=50
-build:remote --remote_timeout=3600
-
-# RBE requires a strong hash function, such as SHA256.
-startup --host_jvm_args=-Dbazel.DigestFunction=SHA256
-
-# Enable authentication. This will pick up application default credentials by
-# default. You can use --google_credentials=some_file.json to use a service
-# account credential instead.
-build:remote --google_default_credentials=true
-
-# Auth scope needed for authentication with RBE.
-build:remote --auth_scope="https://www.googleapis.com/auth/cloud-source-tools"
-
-# Set flags for uploading to BES in order to view results in the Bazel Build
-# Results UI.
-build:results --bes_backend="buildeventservice.googleapis.com"
-build:results --bes_timeout=60s
-build:results --tls_enabled
-
-# Output BES results url
-build:results --bes_results_url="https://source.cloud.google.com/results/invocations/"
-
-# Set flags for uploading to BES without Remote Build Execution.
-build:results-local --bes_backend="buildeventservice.googleapis.com"
-build:results-local --bes_timeout=60s
-build:results-local --tls_enabled=true
-build:results-local --auth_enabled=true
-build:results-local --spawn_strategy=local
-build:results-local --remote_cache=remotebuildexecution.googleapis.com
-build:results-local --remote_timeout=3600
-build:results-local --bes_results_url="https://source.cloud.google.com/results/invocations/"
diff --git a/.github/issue_template.md b/.github/issue_template.md
deleted file mode 100644
index 77c401d22..000000000
--- a/.github/issue_template.md
+++ /dev/null
@@ -1,20 +0,0 @@
-Before filling an issue, please consult our FAQ:
-https://gvisor.dev/docs/user_guide/faq/
-
-Also check that the issue hasn't been reported before.
-
-If you have a question, please email gvisor-users@googlegroups.com rather than filing a bug.
-
-If you believe you've found a security issue, please email gvisor-security@googlegroups.com rather than filing a bug.
-
-If this is your first time compiling or running gVisor, please make sure that your system meets the minimum requirements: https://github.com/google/gvisor#requirements
-
-For all other issues, please attach debug logs. To get debug logs, follow the
-instructions here: https://gvisor.dev/docs/user_guide/debugging/
-
-Other useful information to include is:
-
-* `runsc -v`
-* `docker version` or `docker info` if more relevant
-* `uname -a` - `git describe`
-* Detailed reproduction steps
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 13babef4d..000000000
--- a/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-# Generated bazel symlinks.
-/bazel-*
diff --git a/AUTHORS b/AUTHORS
deleted file mode 100644
index 01ba46567..000000000
--- a/AUTHORS
+++ /dev/null
@@ -1,8 +0,0 @@
-# This is the list of gVisor authors for copyright purposes.
-#
-# This does not necessarily list everyone who has contributed code, since in
-# some cases, their employer may be the copyright holder. To see the full list
-# of contributors, see the revision history in source control.
-#
-# Please send a patch if you would like to be included in this list.
-Google LLC
diff --git a/BUILD b/BUILD
deleted file mode 100644
index 60ed992c4..000000000
--- a/BUILD
+++ /dev/null
@@ -1,43 +0,0 @@
-package(licenses = ["notice"]) # Apache 2.0
-
-load("@io_bazel_rules_go//go:def.bzl", "go_path", "nogo")
-load("@bazel_gazelle//:def.bzl", "gazelle")
-
-# The sandbox filegroup is used for sandbox-internal dependencies.
-package_group(
- name = "sandbox",
- packages = [
- "//...",
- ],
-)
-
-# gopath defines a directory that is structured in a way that is compatible
-# with standard Go tools. Things like godoc, editors and refactor tools should
-# work as expected.
-#
-# The files in this tree are symlinks to the true sources.
-go_path(
- name = "gopath",
- mode = "link",
- deps = [
- "//runsc",
- ],
-)
-
-# gazelle is a set of build tools.
-#
-# To update the WORKSPACE from go.mod, use:
-# bazel run //:gazelle -- update-repos -from_file=go.mod
-gazelle(name = "gazelle")
-
-# nogo applies checks to all Go source in this repository, enforcing code
-# guidelines and restrictions. Note that the tool libraries themselves should
-# live in the tools subdirectory (unless they are standard).
-nogo(
- name = "nogo",
- config = "tools/nogo.js",
- visibility = ["//visibility:public"],
- deps = [
- "//tools/checkunsafe",
- ],
-)
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
deleted file mode 100644
index eb6c8edae..000000000
--- a/CODE_OF_CONDUCT.md
+++ /dev/null
@@ -1,92 +0,0 @@
-# Code of Conduct
-
-## Our Pledge
-
-In the interest of fostering an open and welcoming environment, we as
-contributors and maintainers pledge to making participation in our project and
-our community a harassment-free experience for everyone, regardless of age, body
-size, disability, ethnicity, gender identity and expression, level of
-experience, education, socio-economic status, nationality, personal appearance,
-race, religion, or sexual identity and orientation.
-
-## Our Standards
-
-Examples of behavior that contributes to creating a positive environment
-include:
-
-* Using welcoming and inclusive language
-* Being respectful of differing viewpoints and experiences
-* Gracefully accepting constructive criticism
-* Focusing on what is best for the community
-* Showing empathy towards other community members
-
-Examples of unacceptable behavior by participants include:
-
-* The use of sexualized language or imagery and unwelcome sexual attention or
- advances
-* Trolling, insulting/derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or electronic
- address, without explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
- professional setting
-
-## Our Responsibilities
-
-Project maintainers are responsible for clarifying the standards of acceptable
-behavior and are expected to take appropriate and fair corrective action in
-response to any instances of unacceptable behavior.
-
-Project maintainers have the right and responsibility to remove, edit, or reject
-comments, commits, code, wiki edits, issues, and other contributions that are
-not aligned to this Code of Conduct, or to ban temporarily or permanently any
-contributor for other behaviors that they deem inappropriate, threatening,
-offensive, or harmful.
-
-## Scope
-
-This Code of Conduct applies both within project spaces and in public spaces
-when an individual is representing the project or its community. Examples of
-representing a project or community include using an official project e-mail
-address, posting via an official social media account, or acting as an appointed
-representative at an online or offline event. Representation of a project may be
-further defined and clarified by project maintainers.
-
-This Code of Conduct also applies outside the project spaces when the Project
-Steward has a reasonable belief that an individual's behavior may have a
-negative impact on the project or its community.
-
-## Conflict Resolution
-
-We do not believe that all conflict is bad; healthy debate and disagreement
-often yield positive results. However, it is never okay to be disrespectful or
-to engage in behavior that violates the project’s code of conduct.
-
-If you see someone violating the code of conduct, you are encouraged to address
-the behavior directly with those involved. Many issues can be resolved quickly
-and easily, and this gives people more control over the outcome of their
-dispute. If you are unable to resolve the matter for any reason, or if the
-behavior is threatening or harassing, report it. We are dedicated to providing
-an environment where participants feel welcome and safe.
-
-Reports should be directed to Jaice Singer DuMars, jaice at google dot com, the
-Project Steward for gVisor. It is the Project Steward’s duty to receive and
-address reported violations of the code of conduct. They will then work with a
-committee consisting of representatives from the Open Source Programs Office and
-the Google Open Source Strategy team. If for any reason you are uncomfortable
-reaching out the Project Steward, please email opensource@google.com.
-
-We will investigate every complaint, but you may not receive a direct response.
-We will use our discretion in determining when and how to follow up on reported
-incidents, which may range from not taking action to permanent expulsion from
-the project and project-sponsored spaces. We will notify the accused of the
-report and provide them an opportunity to discuss it before any action is taken.
-The identity of the reporter will be omitted from the details of the report
-supplied to the accused. In potentially harmful situations, such as ongoing
-harassment or threats to anyone's safety, we may take action without notice.
-
-## Attribution
-
-This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
-available at
-https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index 5d46168bc..000000000
--- a/CONTRIBUTING.md
+++ /dev/null
@@ -1,147 +0,0 @@
-# Contributing
-
-Want to contribute? Great! First, read this page.
-
-### Contributor License Agreement
-
-Contributions to this project must be accompanied by a Contributor License
-Agreement. You (or your employer) retain the copyright to your contribution;
-this simply gives us permission to use and redistribute your contributions as
-part of the project. Head over to <https://cla.developers.google.com/> to see
-your current agreements on file or to sign a new one.
-
-You generally only need to submit a CLA once, so if you've already submitted one
-(even if it was for a different project), you probably don't need to do it
-again.
-
-### Using GOPATH
-
-Some editors may require the code to be structured in a `GOPATH` directory tree.
-In this case, you may use the `:gopath` target to generate a directory tree with
-symlinks to the original source files.
-
-```
-bazel build :gopath
-```
-
-You can then set the `GOPATH` in your editor to `bazel-bin/gopath`.
-
-If you use this mechanism, keep in mind that the generated tree is not the
-canonical source. You will still need to build and test with `bazel`. New files
-will need to be added to the appropriate `BUILD` files, and the `:gopath` target
-will need to be re-run to generate appropriate symlinks in the `GOPATH`
-directory tree.
-
-### Coding Guidelines
-
-All Go code should conform to the [Go style guidelines][gostyle]. C++ code
-should conform to the [Google C++ Style Guide][cppstyle] and the guidelines
-described for [tests][teststyle].
-
-As a secure runtime, we need to maintain the safety of all of code included in
-gVisor. The following rules help mitigate issues.
-
-Definitions for the rules below:
-
-`core`:
-
-* `//pkg/sentry/...`
-* Transitive dependencies in `//pkg/...`, `//third_party/...`.
-
-`runsc`:
-
-* `//runsc/...`
-
-Rules:
-
-* No cgo in `core` or `runsc`. The final binary must be a statically-linked
- pure Go binary.
-
-* Any files importing "unsafe" must have a name ending in `_unsafe.go`.
-
-* `core` may only depend on the following packages:
-
- * Itself.
- * Go standard library.
- * Except (transitively) package "net" (this will result in a non-cgo
- binary). Use `//pkg/unet` instead.
- * `@org_golang_x_sys//unix:go_default_library` (Go import
- `golang.org/x/sys/unix`).
- * Generated Go protobuf packages.
- * `@com_github_golang_protobuf//proto:go_default_library` (Go import
- `github.com/golang/protobuf/proto`).
- * `@com_github_golang_protobuf//ptypes:go_default_library` (Go import
- `github.com/golang/protobuf/ptypes`).
-
-* `runsc` may only depend on the following packages:
-
- * All packages allowed for `core`.
- * `@com_github_google_subcommands//:go_default_library` (Go import
- `github.com/google/subcommands`).
- * `@com_github_opencontainers_runtime_spec//specs_go:go_default_library`
- (Go import `github.com/opencontainers/runtime-spec/specs_go`).
-
-### Code reviews
-
-Before sending code reviews, run `bazel test ...` to ensure tests are passing.
-
-Code changes are accepted via [pull request][github].
-
-When approved, the change will be submitted by a team member and automatically
-merged into the repository.
-
-### Presubmit checks
-
-Accessing check logs may require membership in the
-[gvisor-dev mailing list][gvisor-dev-list], which is public.
-
-### Bug IDs
-
-Some TODOs and NOTEs sprinkled throughout the code have associated IDs of the
-form `b/1234`. These correspond to bugs in our internal bug tracker. Eventually
-these bugs will be moved to the GitHub Issues, but until then they can simply be
-ignored.
-
-### Build and test with Docker
-
-`scripts/dev.sh` is a convenient script that builds and installs `runsc` as a
-new Docker runtime for you. The scripts tries to extract the runtime name from
-your local environment and will print it at the end. You can also customize it.
-The script creates one regular runtime and another with debug flags enabled.
-Here are a few examples:
-
-```bash
-# Default case (inside branch my-branch)
-$ scripts/dev.sh
-...
-Runtimes my-branch and my-branch-d (debug enabled) setup.
-Use --runtime=my-branch with your Docker command.
- docker run --rm --runtime=my-branch --rm hello-world
-
-If you rebuild, use scripts/dev.sh --refresh.
-Logs are in: /tmp/my-branch/logs
-
-# --refresh just updates the runtime binary and doesn't restart docker.
-$ git/my_branch> scripts/dev.sh --refresh
-
-# Using a custom runtime name
-$ git/my_branch> scripts/dev.sh my-runtime
-...
-Runtimes my-runtime and my-runtime-d (debug enabled) setup.
-Use --runtime=my-runtime with your Docker command.
- docker run --rm --runtime=my-runtime --rm hello-world
-```
-
-### The small print
-
-Contributions made by corporations are covered by a different agreement than the
-one above, the
-[Software Grant and Corporate Contributor License Agreement][gccla].
-
-[cppstyle]: https://google.github.io/styleguide/cppguide.html
-[gcla]: https://cla.developers.google.com/about/google-individual
-[gccla]: https://cla.developers.google.com/about/google-corporate
-[github]: https://github.com/google/gvisor/compare
-[gvisor-dev-list]: https://groups.google.com/forum/#!forum/gvisor-dev
-[gostyle]: https://github.com/golang/go/wiki/CodeReviewComments
-[teststyle]: ./test/
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index 6e9d870db..000000000
--- a/Dockerfile
+++ /dev/null
@@ -1,8 +0,0 @@
-FROM ubuntu:bionic
-
-RUN apt-get update && apt-get install -y curl gnupg2 git python3
-RUN echo "deb [arch=amd64] http://storage.googleapis.com/bazel-apt stable jdk1.8" | tee /etc/apt/sources.list.d/bazel.list && \
- curl https://bazel.build/bazel-release.pub.gpg | apt-key add -
-RUN apt-get update && apt-get install -y bazel && apt-get clean
-
-WORKDIR /gvisor
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index d64569567..000000000
--- a/LICENSE
+++ /dev/null
@@ -1,202 +0,0 @@
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 1735c07df..000000000
--- a/Makefile
+++ /dev/null
@@ -1,47 +0,0 @@
-UID := $(shell id -u ${USER})
-GID := $(shell id -g ${USER})
-GVISOR_BAZEL_CACHE := $(shell readlink -f ~/.cache/bazel/)
-
-all: runsc
-
-docker-build:
- docker build -t gvisor-bazel .
-
-bazel-shutdown:
- docker exec -i gvisor-bazel bazel shutdown && \
- docker kill gvisor-bazel
-
-bazel-server-start: docker-build
- mkdir -p "$(GVISOR_BAZEL_CACHE)" && \
- docker run -d --rm --name gvisor-bazel \
- --user 0:0 \
- -v "$(GVISOR_BAZEL_CACHE):$(HOME)/.cache/bazel/" \
- -v "$(CURDIR):$(CURDIR)" \
- --workdir "$(CURDIR)" \
- --tmpfs /tmp:rw,exec \
- --privileged \
- gvisor-bazel \
- sh -c "while :; do sleep 100; done" && \
- docker exec --user 0:0 -i gvisor-bazel sh -c "groupadd --gid $(GID) --non-unique gvisor && useradd --uid $(UID) --gid $(GID) -d $(HOME) gvisor"
-
-bazel-server:
- docker exec gvisor-bazel true || \
- $(MAKE) bazel-server-start
-
-BAZEL_OPTIONS := build runsc
-bazel: bazel-server
- docker exec -u $(UID):$(GID) -i gvisor-bazel bazel $(BAZEL_OPTIONS)
-
-bazel-alias:
- @echo "alias bazel='docker exec -u $(UID):$(GID) -i gvisor-bazel bazel'"
-
-runsc:
- $(MAKE) BAZEL_OPTIONS="build runsc" bazel
-
-tests:
- $(MAKE) BAZEL_OPTIONS="test --test_tag_filters runsc_ptrace //test/syscalls/..." bazel
-
-unit-tests:
- $(MAKE) BAZEL_OPTIONS="test //pkg/... //runsc/... //tools/..." bazel
-
-.PHONY: docker-build bazel-shutdown bazel-server-start bazel-server bazel runsc tests
diff --git a/README.md b/README.md
index 7ab76d305..d12ba732d 100644
--- a/README.md
+++ b/README.md
@@ -1,158 +1,5 @@
-![gVisor](g3doc/logo.png)
+# gVisor
-[![Status](https://storage.googleapis.com/gvisor-build-badges/build.svg)](https://storage.googleapis.com/gvisor-build-badges/build.html)
-[![gVisor chat](https://badges.gitter.im/gvisor/community.png)](https://gitter.im/gvisor/community)
-
-## What is gVisor?
-
-**gVisor** is a user-space kernel, written in Go, that implements a substantial
-portion of the Linux system surface. It includes an
-[Open Container Initiative (OCI)][oci] runtime called `runsc` that provides an
-isolation boundary between the application and the host kernel. The `runsc`
-runtime integrates with Docker and Kubernetes, making it simple to run sandboxed
-containers.
-
-## Why does gVisor exist?
-
-Containers are not a [**sandbox**][sandbox]. While containers have
-revolutionized how we develop, package, and deploy applications, running
-untrusted or potentially malicious code without additional isolation is not a
-good idea. The efficiency and performance gains from using a single, shared
-kernel also mean that container escape is possible with a single vulnerability.
-
-gVisor is a user-space kernel for containers. It limits the host kernel surface
-accessible to the application while still giving the application access to all
-the features it expects. Unlike most kernels, gVisor does not assume or require
-a fixed set of physical resources; instead, it leverages existing host kernel
-functionality and runs as a normal user-space process. In other words, gVisor
-implements Linux by way of Linux.
-
-gVisor should not be confused with technologies and tools to harden containers
-against external threats, provide additional integrity checks, or limit the
-scope of access for a service. One should always be careful about what data is
-made available to a container.
-
-## Documentation
-
-User documentation and technical architecture, including quick start guides, can
-be found at [gvisor.dev][gvisor-dev].
-
-## Installing from source
-
-gVisor currently requires x86\_64 Linux to build, though support for other
-architectures may become available in the future.
-
-### Requirements
-
-Make sure the following dependencies are installed:
-
-* Linux 4.14.77+ ([older linux][old-linux])
-* [git][git]
-* [Bazel][bazel] 0.28.0+
-* [Python][python]
-* [Docker version 17.09.0 or greater][docker]
-* Gold linker (e.g. `binutils-gold` package on Ubuntu)
-
-### Building
-
-Build and install the `runsc` binary:
-
-```
-bazel build runsc
-sudo cp ./bazel-bin/runsc/linux_amd64_pure_stripped/runsc /usr/local/bin
-```
-
-If you don't want to install bazel on your system, you can build runsc in a
-Docker container:
-
-```
-make runsc
-sudo cp ./bazel-bin/runsc/linux_amd64_pure_stripped/runsc /usr/local/bin
-```
-
-### Testing
-
-The test suite can be run with Bazel:
-
-```
-bazel test //...
-```
-
-or in a Docker container:
-
-```
-make unit-tests
-make tests
-```
-
-### Using remote execution
-
-If you have a [Remote Build Execution][rbe] environment, you can use it to speed
-up build and test cycles.
-
-You must authenticate with the project first:
-
-```
-gcloud auth application-default login --no-launch-browser
-```
-
-Then invoke bazel with the following flags:
-
-```
---config=remote
---project_id=$PROJECT
---remote_instance_name=projects/$PROJECT/instances/default_instance
-```
-
-You can also add those flags to your local ~/.bazelrc to avoid needing to
-specify them each time on the command line.
-
-### Using `go get`
-
-This project uses [bazel][bazel] to build and manage dependencies. A synthetic
-`go` branch is maintained that is compatible with standard `go` tooling for
-convenience.
-
-For example, to build `runsc` directly from this branch:
-
-```
-echo "module runsc" > go.mod
-GO111MODULE=on go get gvisor.dev/gvisor/runsc@go
-CGO_ENABLED=0 GO111MODULE=on go install gvisor.dev/gvisor/runsc
-```
-
-Note that this branch is supported in a best effort capacity, and direct
-development on this branch is not supported. Development should occur on the
-`master` branch, which is then reflected into the `go` branch.
-
-## Community & Governance
-
-The governance model is documented in our [community][community] repository.
-
-The [gvisor-users mailing list][gvisor-users-list] and
-[gvisor-dev mailing list][gvisor-dev-list] are good starting points for
-questions and discussion.
-
-## Security
-
-Sensitive security-related questions, comments and disclosures can be sent to
-the [gvisor-security mailing list][gvisor-security-list]. The full security
-disclosure policy is defined in the [community][community] repository.
-
-## Contributing
-
-See [Contributing.md](CONTRIBUTING.md).
-
-[bazel]: https://bazel.build
-[community]: https://gvisor.googlesource.com/community
-[docker]: https://www.docker.com
-[git]: https://git-scm.com
-[gvisor-security-list]: https://groups.google.com/forum/#!forum/gvisor-security
-[gvisor-users-list]: https://groups.google.com/forum/#!forum/gvisor-users
-[gvisor-dev-list]: https://groups.google.com/forum/#!forum/gvisor-dev
-[oci]: https://www.opencontainers.org
-[old-linux]: https://gvisor.dev/docs/user_guide/networking/#gso
-[python]: https://python.org
-[rbe]: https://blog.bazel.build/2018/10/05/remote-build-execution.html
-[sandbox]: https://en.wikipedia.org/wiki/Sandbox_(computer_security)
-[gvisor-dev]: https://gvisor.dev
+This branch is a synthetic branch, containing only Go sources, that is
+compatible with standard Go tools. See the `master` branch for authoritative
+sources and tests.
diff --git a/WORKSPACE b/WORKSPACE
deleted file mode 100644
index 8ce1d42d4..000000000
--- a/WORKSPACE
+++ /dev/null
@@ -1,252 +0,0 @@
-# Load go bazel rules and gazelle.
-load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
-
-http_archive(
- name = "io_bazel_rules_go",
- sha256 = "ae8c36ff6e565f674c7a3692d6a9ea1096e4c1ade497272c2108a810fb39acd2",
- urls = [
- "https://storage.googleapis.com/bazel-mirror/github.com/bazelbuild/rules_go/releases/download/0.19.4/rules_go-0.19.4.tar.gz",
- "https://github.com/bazelbuild/rules_go/releases/download/0.19.4/rules_go-0.19.4.tar.gz",
- ],
-)
-
-http_archive(
- name = "bazel_gazelle",
- sha256 = "7fc87f4170011201b1690326e8c16c5d802836e3a0d617d8f75c3af2b23180c4",
- urls = [
- "https://storage.googleapis.com/bazel-mirror/github.com/bazelbuild/bazel-gazelle/releases/download/0.18.2/bazel-gazelle-0.18.2.tar.gz",
- "https://github.com/bazelbuild/bazel-gazelle/releases/download/0.18.2/bazel-gazelle-0.18.2.tar.gz",
- ],
-)
-
-load("@io_bazel_rules_go//go:deps.bzl", "go_rules_dependencies", "go_register_toolchains")
-
-go_rules_dependencies()
-
-go_register_toolchains(
- go_version = "1.13",
- nogo = "@//:nogo",
-)
-
-load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository")
-
-gazelle_dependencies()
-
-# Load C++ rules.
-http_archive(
- name = "rules_cc",
- sha256 = "67412176974bfce3f4cf8bdaff39784a72ed709fc58def599d1f68710b58d68b",
- strip_prefix = "rules_cc-b7fe9697c0c76ab2fd431a891dbb9a6a32ed7c3e",
- urls = [
- "https://mirror.bazel.build/github.com/bazelbuild/rules_cc/archive/b7fe9697c0c76ab2fd431a891dbb9a6a32ed7c3e.zip",
- "https://github.com/bazelbuild/rules_cc/archive/b7fe9697c0c76ab2fd431a891dbb9a6a32ed7c3e.zip",
- ],
-)
-
-# Load protobuf dependencies.
-http_archive(
- name = "com_google_protobuf",
- sha256 = "532d2575d8c0992065bb19ec5fba13aa3683499726f6055c11b474f91a00bb0c",
- strip_prefix = "protobuf-7f520092d9050d96fb4b707ad11a51701af4ce49",
- urls = [
- "https://mirror.bazel.build/github.com/protocolbuffers/protobuf/archive/7f520092d9050d96fb4b707ad11a51701af4ce49.zip",
- "https://github.com/protocolbuffers/protobuf/archive/7f520092d9050d96fb4b707ad11a51701af4ce49.zip",
- ],
-)
-
-load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
-
-protobuf_deps()
-
-# Load bazel_toolchain to support Remote Build Execution.
-# See releases at https://releases.bazel.build/bazel-toolchains.html
-http_archive(
- name = "bazel_toolchains",
- sha256 = "a019fbd579ce5aed0239de865b2d8281dbb809efd537bf42e0d366783e8dec65",
- strip_prefix = "bazel-toolchains-0.29.2",
- urls = [
- "https://mirror.bazel.build/github.com/bazelbuild/bazel-toolchains/archive/0.29.2.tar.gz",
- "https://github.com/bazelbuild/bazel-toolchains/archive/0.29.2.tar.gz",
- ],
-)
-
-# Creates a default toolchain config for RBE.
-load("@bazel_toolchains//rules:rbe_repo.bzl", "rbe_autoconfig")
-
-rbe_autoconfig(name = "rbe_default")
-
-# External repositories, in sorted order.
-go_repository(
- name = "com_github_cenkalti_backoff",
- importpath = "github.com/cenkalti/backoff",
- sum = "h1:+FKjzBIdfBHYDvxCv+djmDJdes/AoDtg8gpcxowBlF8=",
- version = "v0.0.0-20190506075156-2146c9339422",
-)
-
-go_repository(
- name = "com_github_gofrs_flock",
- importpath = "github.com/gofrs/flock",
- sum = "h1:JFTFz3HZTGmgMz4E1TabNBNJljROSYgja1b4l50FNVs=",
- version = "v0.6.1-0.20180915234121-886344bea079",
-)
-
-go_repository(
- name = "com_github_golang_mock",
- importpath = "github.com/golang/mock",
- sum = "h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=",
- version = "v1.3.1",
-)
-
-go_repository(
- name = "com_github_google_go-cmp",
- importpath = "github.com/google/go-cmp",
- sum = "h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=",
- version = "v0.2.0",
-)
-
-go_repository(
- name = "com_github_google_subcommands",
- importpath = "github.com/google/subcommands",
- sum = "h1:GZGUPQiZfYrd9uOqyqwbQcHPkz/EZJVkZB1MkaO9UBI=",
- version = "v0.0.0-20190508160503-636abe8753b8",
-)
-
-go_repository(
- name = "com_github_google_uuid",
- importpath = "github.com/google/uuid",
- sum = "h1:rXQlD9GXkjA/PQZhmEaF/8Pj/sJfdZJK7GJG0gkS8I0=",
- version = "v0.0.0-20171129191014-dec09d789f3d",
-)
-
-go_repository(
- name = "com_github_kr_pty",
- importpath = "github.com/kr/pty",
- sum = "h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=",
- version = "v1.1.1",
-)
-
-go_repository(
- name = "com_github_opencontainers_runtime-spec",
- importpath = "github.com/opencontainers/runtime-spec",
- sum = "h1:d9F+LNYwMyi3BDN4GzZdaSiq4otb8duVEWyZjeUtOQI=",
- version = "v0.1.2-0.20171211145439-b2d941ef6a78",
-)
-
-go_repository(
- name = "com_github_syndtr_gocapability",
- importpath = "github.com/syndtr/gocapability",
- sum = "h1:b6uOv7YOFK0TYG7HtkIgExQo+2RdLuwRft63jn2HWj8=",
- version = "v0.0.0-20180916011248-d98352740cb2",
-)
-
-go_repository(
- name = "com_github_vishvananda_netlink",
- importpath = "github.com/vishvananda/netlink",
- sum = "h1:/Tdc23Arz1OtdIsBY2utWepGRQ9fEAJlhkdoLzWMK8Q=",
- version = "v1.0.1-0.20190318003149-adb577d4a45e",
-)
-
-go_repository(
- name = "com_github_vishvananda_netns",
- importpath = "github.com/vishvananda/netns",
- sum = "h1:J9gO8RJCAFlln1jsvRba/CWVUnMHwObklfxxjErl1uk=",
- version = "v0.0.0-20171111001504-be1fbeda1936",
-)
-
-go_repository(
- name = "org_golang_x_crypto",
- importpath = "golang.org/x/crypto",
- sum = "h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=",
- version = "v0.0.0-20190308221718-c2843e01d9a2",
-)
-
-go_repository(
- name = "org_golang_x_net",
- importpath = "golang.org/x/net",
- sum = "h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=",
- version = "v0.0.0-20190311183353-d8887717615a",
-)
-
-go_repository(
- name = "org_golang_x_text",
- importpath = "golang.org/x/text",
- sum = "h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=",
- version = "v0.3.0",
-)
-
-go_repository(
- name = "org_golang_x_tools",
- commit = "36563e24a262",
- importpath = "golang.org/x/tools",
-)
-
-go_repository(
- name = "org_golang_x_sync",
- importpath = "golang.org/x/sync",
- sum = "h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=",
- version = "v0.0.0-20190423024810-112230192c58",
-)
-
-go_repository(
- name = "org_golang_x_sys",
- importpath = "golang.org/x/sys",
- sum = "h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=",
- version = "v0.0.0-20190215142949-d0b11bdaac8a",
-)
-
-go_repository(
- name = "org_golang_x_time",
- commit = "9d24e82272b4f38b78bc8cff74fa936d31ccd8ef",
- importpath = "golang.org/x/time",
-)
-
-go_repository(
- name = "org_golang_x_tools",
- commit = "aa82965741a9fecd12b026fbb3d3c6ed3231b8f8",
- importpath = "golang.org/x/tools",
-)
-
-go_repository(
- name = "com_github_google_btree",
- importpath = "github.com/google/btree",
- sum = "h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=",
- version = "v1.0.0",
-)
-
-go_repository(
- name = "com_github_golang_protobuf",
- importpath = "github.com/golang/protobuf",
- sum = "h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=",
- version = "v1.3.1",
-)
-
-# System Call test dependencies.
-http_archive(
- name = "com_github_gflags_gflags",
- sha256 = "34af2f15cf7367513b352bdcd2493ab14ce43692d2dcd9dfc499492966c64dcf",
- strip_prefix = "gflags-2.2.2",
- urls = [
- "https://mirror.bazel.build/github.com/gflags/gflags/archive/v2.2.2.tar.gz",
- "https://github.com/gflags/gflags/archive/v2.2.2.tar.gz",
- ],
-)
-
-http_archive(
- name = "com_google_absl",
- sha256 = "56775f1283a59e6274c28d99981a9717ff4e0b1161e9129fdb2fcf22531d8d93",
- strip_prefix = "abseil-cpp-a0d1e098c2f99694fa399b175a7ccf920762030e",
- urls = [
- "https://mirror.bazel.build/github.com/abseil/abseil-cpp/archive/a0d1e098c2f99694fa399b175a7ccf920762030e.tar.gz",
- "https://github.com/abseil/abseil-cpp/archive/a0d1e098c2f99694fa399b175a7ccf920762030e.tar.gz",
- ],
-)
-
-http_archive(
- name = "com_google_googletest",
- sha256 = "0a10bea96d8670e5eef948d79d824162b1577bb7889539e49ec786bfc3e48912",
- strip_prefix = "googletest-565f1b848215b77c3732bca345fe76a0431d8b34",
- urls = [
- "https://mirror.bazel.build/github.com/google/googletest/archive/565f1b848215b77c3732bca345fe76a0431d8b34.tar.gz",
- "https://github.com/google/googletest/archive/565f1b848215b77c3732bca345fe76a0431d8b34.tar.gz",
- ],
-)
diff --git a/g3doc/README.md b/g3doc/README.md
deleted file mode 100644
index 49d58cdae..000000000
--- a/g3doc/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-The gVisor logo files are licensed under CC BY-SA 4.0 (Creative Commons
-Attribution-ShareAlike 4.0 International).
diff --git a/g3doc/logo.png b/g3doc/logo.png
deleted file mode 100644
index bd1a1e4b7..000000000
--- a/g3doc/logo.png
+++ /dev/null
Binary files differ
diff --git a/kokoro/build.cfg b/kokoro/build.cfg
deleted file mode 100644
index d462d839c..000000000
--- a/kokoro/build.cfg
+++ /dev/null
@@ -1,23 +0,0 @@
-build_file: "repo/scripts/build.sh"
-
-before_action {
- fetch_keystore {
- keystore_resource {
- keystore_config_id: 73898
- keyname: "kokoro-repo-key"
- }
- }
-}
-
-env_vars {
- key: "KOKORO_REPO_KEY"
- value: "73898_kokoro-repo-key"
-}
-
-action {
- define_artifacts {
- regex: "**/runsc"
- regex: "**/runsc.sha256"
- regex: "**/repo/**"
- }
-}
diff --git a/kokoro/common.cfg b/kokoro/common.cfg
deleted file mode 100644
index 669a2e458..000000000
--- a/kokoro/common.cfg
+++ /dev/null
@@ -1,29 +0,0 @@
-# Give Kokoro access to Remote Build Executor (RBE) service account key.
-before_action {
- fetch_keystore {
- keystore_resource {
- keystore_config_id: 73898
- keyname: "kokoro-rbe-service-account"
- }
- }
-}
-
-# Configure bazel to access RBE.
-bazel_setting {
- # Our GCP project name.
- project_id: "gvisor-rbe"
-
- # Use RBE for execution as well as caching.
- local_execution: false
-
- # This must match the values in the job config.
- auth_credential: {
- keystore_config_id: 73898
- keyname: "kokoro-rbe-service-account"
- }
-
- # Do not change unless you know what you are doing.
- bes_backend_address: "buildeventservice.googleapis.com"
- foundry_backend_address: "remotebuildexecution.googleapis.com"
- upsalite_frontend_address: "https://source.cloud.google.com"
-}
diff --git a/kokoro/do_tests.cfg b/kokoro/do_tests.cfg
deleted file mode 100644
index b45ec0b42..000000000
--- a/kokoro/do_tests.cfg
+++ /dev/null
@@ -1,9 +0,0 @@
-build_file: "repo/scripts/do_tests.sh"
-
-action {
- define_artifacts {
- regex: "**/sponge_log.xml"
- regex: "**/sponge_log.log"
- regex: "**/outputs.zip"
- }
-}
diff --git a/kokoro/docker_tests.cfg b/kokoro/docker_tests.cfg
deleted file mode 100644
index 717d71dd3..000000000
--- a/kokoro/docker_tests.cfg
+++ /dev/null
@@ -1,9 +0,0 @@
-build_file: "repo/scripts/docker_tests.sh"
-
-action {
- define_artifacts {
- regex: "**/sponge_log.xml"
- regex: "**/sponge_log.log"
- regex: "**/outputs.zip"
- }
-}
diff --git a/kokoro/go.cfg b/kokoro/go.cfg
deleted file mode 100644
index b9c1fcb12..000000000
--- a/kokoro/go.cfg
+++ /dev/null
@@ -1,20 +0,0 @@
-build_file: "repo/scripts/go.sh"
-
-before_action {
- fetch_keystore {
- keystore_resource {
- keystore_config_id: 73898
- keyname: "kokoro-github-access-token"
- }
- }
-}
-
-env_vars {
- key: "KOKORO_GITHUB_ACCESS_TOKEN"
- value: "73898_kokoro-github-access-token"
-}
-
-env_vars {
- key: "KOKORO_GO_PUSH"
- value: "true"
-}
diff --git a/kokoro/go_tests.cfg b/kokoro/go_tests.cfg
deleted file mode 100644
index 5eb51041a..000000000
--- a/kokoro/go_tests.cfg
+++ /dev/null
@@ -1 +0,0 @@
-build_file: "repo/scripts/go.sh"
diff --git a/kokoro/hostnet_tests.cfg b/kokoro/hostnet_tests.cfg
deleted file mode 100644
index 532755f4a..000000000
--- a/kokoro/hostnet_tests.cfg
+++ /dev/null
@@ -1,9 +0,0 @@
-build_file: "repo/scripts/hostnet_tests.sh"
-
-action {
- define_artifacts {
- regex: "**/sponge_log.xml"
- regex: "**/sponge_log.log"
- regex: "**/outputs.zip"
- }
-}
diff --git a/kokoro/kvm_tests.cfg b/kokoro/kvm_tests.cfg
deleted file mode 100644
index 54365c2b2..000000000
--- a/kokoro/kvm_tests.cfg
+++ /dev/null
@@ -1,9 +0,0 @@
-build_file: "repo/scripts/kvm_tests.sh"
-
-action {
- define_artifacts {
- regex: "**/sponge_log.xml"
- regex: "**/sponge_log.log"
- regex: "**/outputs.zip"
- }
-}
diff --git a/kokoro/make_tests.cfg b/kokoro/make_tests.cfg
deleted file mode 100644
index d973130ff..000000000
--- a/kokoro/make_tests.cfg
+++ /dev/null
@@ -1,9 +0,0 @@
-build_file: "repo/scripts/make_tests.sh"
-
-action {
- define_artifacts {
- regex: "**/sponge_log.xml"
- regex: "**/sponge_log.log"
- regex: "**/outputs.zip"
- }
-}
diff --git a/kokoro/overlay_tests.cfg b/kokoro/overlay_tests.cfg
deleted file mode 100644
index abd96f60c..000000000
--- a/kokoro/overlay_tests.cfg
+++ /dev/null
@@ -1,9 +0,0 @@
-build_file: "repo/scripts/overlay_tests.sh"
-
-action {
- define_artifacts {
- regex: "**/sponge_log.xml"
- regex: "**/sponge_log.log"
- regex: "**/outputs.zip"
- }
-}
diff --git a/kokoro/release.cfg b/kokoro/release.cfg
deleted file mode 100644
index b9d35bc51..000000000
--- a/kokoro/release.cfg
+++ /dev/null
@@ -1 +0,0 @@
-build_file: "repo/scripts/release.sh"
diff --git a/kokoro/root_tests.cfg b/kokoro/root_tests.cfg
deleted file mode 100644
index 20b97766a..000000000
--- a/kokoro/root_tests.cfg
+++ /dev/null
@@ -1,9 +0,0 @@
-build_file: "repo/scripts/root_tests.sh"
-
-action {
- define_artifacts {
- regex: "**/sponge_log.xml"
- regex: "**/sponge_log.log"
- regex: "**/outputs.zip"
- }
-}
diff --git a/kokoro/simple_tests.cfg b/kokoro/simple_tests.cfg
deleted file mode 100644
index 32e0a9431..000000000
--- a/kokoro/simple_tests.cfg
+++ /dev/null
@@ -1,9 +0,0 @@
-build_file: "repo/scripts/simple_tests.sh"
-
-action {
- define_artifacts {
- regex: "**/sponge_log.xml"
- regex: "**/sponge_log.log"
- regex: "**/outputs.zip"
- }
-}
diff --git a/kokoro/syscall_tests.cfg b/kokoro/syscall_tests.cfg
deleted file mode 100644
index ee6e4a3a4..000000000
--- a/kokoro/syscall_tests.cfg
+++ /dev/null
@@ -1,9 +0,0 @@
-build_file: "repo/scripts/syscall_tests.sh"
-
-action {
- define_artifacts {
- regex: "**/sponge_log.xml"
- regex: "**/sponge_log.log"
- regex: "**/outputs.zip"
- }
-}
diff --git a/kokoro/ubuntu1604/10_core.sh b/kokoro/ubuntu1604/10_core.sh
deleted file mode 100755
index e87a6eee8..000000000
--- a/kokoro/ubuntu1604/10_core.sh
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -xeo pipefail
-
-# Install all essential build tools.
-apt-get update && apt-get -y install make git-core build-essential linux-headers-$(uname -r) pkg-config
-
-# Install a recent go toolchain.
-if ! [[ -d /usr/local/go ]]; then
- wget https://dl.google.com/go/go1.12.linux-amd64.tar.gz
- tar -xvf go1.12.linux-amd64.tar.gz
- mv go /usr/local
-fi
-
-# Link the Go binary from /usr/bin; replacing anything there.
-(cd /usr/bin && rm -f go && sudo ln -fs /usr/local/go/bin/go go)
diff --git a/kokoro/ubuntu1604/20_bazel.sh b/kokoro/ubuntu1604/20_bazel.sh
deleted file mode 100755
index b9a894024..000000000
--- a/kokoro/ubuntu1604/20_bazel.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -xeo pipefail
-
-declare -r BAZEL_VERSION=0.29.1
-
-# Install bazel dependencies.
-apt-get update && apt-get install -y openjdk-8-jdk-headless unzip
-
-# Use the release installer.
-curl -L -o bazel-${BAZEL_VERSION}-installer-linux-x86_64.sh https://github.com/bazelbuild/bazel/releases/download/${BAZEL_VERSION}/bazel-${BAZEL_VERSION}-installer-linux-x86_64.sh
-chmod a+x bazel-${BAZEL_VERSION}-installer-linux-x86_64.sh
-./bazel-${BAZEL_VERSION}-installer-linux-x86_64.sh
-rm -f bazel-${BAZEL_VERSION}-installer-linux-x86_64.sh
diff --git a/kokoro/ubuntu1604/25_docker.sh b/kokoro/ubuntu1604/25_docker.sh
deleted file mode 100755
index 1d3defcd3..000000000
--- a/kokoro/ubuntu1604/25_docker.sh
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Add dependencies.
-apt-get update && apt-get -y install \
- apt-transport-https \
- ca-certificates \
- curl \
- gnupg-agent \
- software-properties-common
-
-# Install the key.
-curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
-
-# Add the repository.
-add-apt-repository \
- "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
- $(lsb_release -cs) \
- stable"
-
-# Install docker.
-apt-get update && apt-get install -y docker-ce docker-ce-cli containerd.io
diff --git a/kokoro/ubuntu1604/30_containerd.sh b/kokoro/ubuntu1604/30_containerd.sh
deleted file mode 100755
index a7472bd1c..000000000
--- a/kokoro/ubuntu1604/30_containerd.sh
+++ /dev/null
@@ -1,76 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -xeo pipefail
-
-# Helper for Go packages below.
-install_helper() {
- PACKAGE="${1}"
- TAG="${2}"
- GOPATH="${3}"
-
- # Clone the repository.
- mkdir -p "${GOPATH}"/src/$(dirname "${PACKAGE}") && \
- git clone https://"${PACKAGE}" "${GOPATH}"/src/"${PACKAGE}"
-
- # Checkout and build the repository.
- (cd "${GOPATH}"/src/"${PACKAGE}" && \
- git checkout "${TAG}" && \
- GOPATH="${GOPATH}" make && \
- GOPATH="${GOPATH}" make install)
-}
-
-# Install dependencies for the crictl tests.
-apt-get install -y btrfs-tools libseccomp-dev
-
-# Install containerd & cri-tools.
-GOPATH=$(mktemp -d --tmpdir gopathXXXXX)
-install_helper github.com/containerd/containerd v1.2.2 "${GOPATH}"
-install_helper github.com/kubernetes-sigs/cri-tools v1.11.0 "${GOPATH}"
-
-# Install gvisor-containerd-shim.
-declare -r base="https://storage.googleapis.com/cri-containerd-staging/gvisor-containerd-shim"
-declare -r latest=$(mktemp --tmpdir gvisor-containerd-shim-latest.XXXXXX)
-declare -r shim_path=$(mktemp --tmpdir gvisor-containerd-shim.XXXXXX)
-wget --no-verbose "${base}"/latest -O ${latest}
-wget --no-verbose "${base}"/gvisor-containerd-shim-$(cat ${latest}) -O ${shim_path}
-chmod +x ${shim_path}
-mv ${shim_path} /usr/local/bin
-
-# Configure containerd-shim.
-declare -r shim_config_path=/etc/containerd
-declare -r shim_config_tmp_path=$(mktemp --tmpdir gvisor-containerd-shim.XXXXXX.toml)
-mkdir -p ${shim_config_path}
-cat > ${shim_config_tmp_path} <<-EOF
- runc_shim = "/usr/local/bin/containerd-shim"
-
-[runsc_config]
- debug = "true"
- debug-log = "/tmp/runsc-logs/"
- strace = "true"
- file-access = "shared"
-EOF
-mv ${shim_config_tmp_path} ${shim_config_path}
-
-# Configure CNI.
-(cd "${GOPATH}" && GOPATH="${GOPATH}" \
- src/github.com/containerd/containerd/script/setup/install-cni)
-
-# Cleanup the above.
-rm -rf "${GOPATH}"
-rm -rf "${latest}"
-rm -rf "${shim_path}"
-rm -rf "${shim_config_tmp_path}"
diff --git a/kokoro/ubuntu1604/40_kokoro.sh b/kokoro/ubuntu1604/40_kokoro.sh
deleted file mode 100755
index 64772d74d..000000000
--- a/kokoro/ubuntu1604/40_kokoro.sh
+++ /dev/null
@@ -1,54 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -xeo pipefail
-
-# Declare kokoro's required public keys.
-declare -r ssh_public_keys=(
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDg7L/ZaEauETWrPklUTky3kvxqQfe2Ax/2CsSqhNIGNMnK/8d79CHlmY9+dE1FFQ/RzKNCaltgy7XcN/fCYiCZr5jm2ZtnLuGNOTzupMNhaYiPL419qmL+5rZXt4/dWTrsHbFRACxT8j51PcRMO5wgbL0Bg2XXimbx8kDFaurL2gqduQYqlu4lxWCaJqOL71WogcimeL63Nq/yeH5PJPWpqE4P9VUQSwAzBWFK/hLeds/AiP3MgVS65qHBnhq0JsHy8JQsqjZbG7Iidt/Ll0+gqzEbi62gDIcczG4KC0iOVzDDP/1BxDtt1lKeA23ll769Fcm3rJyoBMYxjvdw1TDx sabujp@trigger.mtv.corp.google.com"
- "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNgGK/hCdjmulHfRE3hp4rZs38NCR8yAh0eDsztxqGcuXnuSnL7jOlRrbcQpremJ84omD4eKrIpwJUs+YokMdv4= sabujp@trigger.svl.corp.google.com"
-)
-
-# Install dependencies.
-apt-get update && apt-get install -y rsync coreutils python-psutil qemu-kvm
-
-# We need a kbuilder user.
-if useradd -c "kbuilder user" -m -s /bin/bash kbuilder; then
- # User was added successfully; we add the relevant SSH keys here.
- mkdir -p ~kbuilder/.ssh
- (IFS=$'\n'; echo "${ssh_public_keys[*]}") > ~kbuilder/.ssh/authorized_keys
- chmod 0600 ~kbuilder/.ssh/authorized_keys
- chown -R kbuilder ~kbuilder/.ssh
-fi
-
-# Give passwordless sudo access.
-cat > /etc/sudoers.d/kokoro <<EOF
-kbuilder ALL=(ALL) NOPASSWD:ALL
-EOF
-
-# Ensure we can run Docker without sudo.
-usermod -aG docker kbuilder
-
-# Ensure that we can access kvm.
-usermod -aG kvm kbuilder
-
-# Ensure that /tmpfs exists and is writable by kokoro.
-#
-# Note that kokoro will typically attach a second disk (sdb) to the instance
-# that is used for the /tmpfs volume. In the future we could setup an init
-# script that formats and mounts this here; however, we don't expect our build
-# artifacts to be that large.
-mkdir -p /tmpfs && chmod 0777 /tmpfs && touch /tmpfs/READY
diff --git a/kokoro/ubuntu1604/build.sh b/kokoro/ubuntu1604/build.sh
deleted file mode 100755
index d664a3a76..000000000
--- a/kokoro/ubuntu1604/build.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -xeo pipefail
-
-# Run the image_build.sh script with appropriate parameters.
-IMAGE_PROJECT=ubuntu-os-cloud IMAGE_FAMILY=ubuntu-1604-lts $(dirname $0)/../../tools/image_build.sh $(dirname $0)/??_*.sh
diff --git a/kokoro/ubuntu1804/10_core.sh b/kokoro/ubuntu1804/10_core.sh
deleted file mode 120000
index 6facceeee..000000000
--- a/kokoro/ubuntu1804/10_core.sh
+++ /dev/null
@@ -1 +0,0 @@
-../ubuntu1604/10_core.sh \ No newline at end of file
diff --git a/kokoro/ubuntu1804/20_bazel.sh b/kokoro/ubuntu1804/20_bazel.sh
deleted file mode 120000
index 39194c0f5..000000000
--- a/kokoro/ubuntu1804/20_bazel.sh
+++ /dev/null
@@ -1 +0,0 @@
-../ubuntu1604/20_bazel.sh \ No newline at end of file
diff --git a/kokoro/ubuntu1804/25_docker.sh b/kokoro/ubuntu1804/25_docker.sh
deleted file mode 120000
index 63269bd83..000000000
--- a/kokoro/ubuntu1804/25_docker.sh
+++ /dev/null
@@ -1 +0,0 @@
-../ubuntu1604/25_docker.sh \ No newline at end of file
diff --git a/kokoro/ubuntu1804/30_containerd.sh b/kokoro/ubuntu1804/30_containerd.sh
deleted file mode 120000
index 6ac2377ed..000000000
--- a/kokoro/ubuntu1804/30_containerd.sh
+++ /dev/null
@@ -1 +0,0 @@
-../ubuntu1604/30_containerd.sh \ No newline at end of file
diff --git a/kokoro/ubuntu1804/40_kokoro.sh b/kokoro/ubuntu1804/40_kokoro.sh
deleted file mode 120000
index e861fb5e1..000000000
--- a/kokoro/ubuntu1804/40_kokoro.sh
+++ /dev/null
@@ -1 +0,0 @@
-../ubuntu1604/40_kokoro.sh \ No newline at end of file
diff --git a/kokoro/ubuntu1804/build.sh b/kokoro/ubuntu1804/build.sh
deleted file mode 100755
index 2b5c9a6f2..000000000
--- a/kokoro/ubuntu1804/build.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -xeo pipefail
-
-# Run the image_build.sh script with appropriate parameters.
-IMAGE_PROJECT=ubuntu-os-cloud IMAGE_FAMILY=ubuntu-1804-lts $(dirname $0)/../../tools/image_build.sh $(dirname $0)/??_*.sh
diff --git a/pkg/abi/BUILD b/pkg/abi/BUILD
deleted file mode 100644
index 32c601a03..000000000
--- a/pkg/abi/BUILD
+++ /dev/null
@@ -1,14 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "abi",
- srcs = [
- "abi.go",
- "abi_linux.go",
- "flag.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/abi",
- visibility = ["//:sandbox"],
-)
diff --git a/pkg/abi/abi_state_autogen.go b/pkg/abi/abi_state_autogen.go
new file mode 100755
index 000000000..4f94570e5
--- /dev/null
+++ b/pkg/abi/abi_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package abi
+
diff --git a/pkg/abi/linux/BUILD b/pkg/abi/linux/BUILD
deleted file mode 100644
index 39c92bb33..000000000
--- a/pkg/abi/linux/BUILD
+++ /dev/null
@@ -1,76 +0,0 @@
-# Package linux contains the constants and types needed to interface with a
-# Linux kernel. It should be used instead of syscall or golang.org/x/sys/unix
-# when the host OS may not be Linux.
-
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "linux",
- srcs = [
- "aio.go",
- "audit.go",
- "bpf.go",
- "capability.go",
- "clone.go",
- "dev.go",
- "elf.go",
- "epoll.go",
- "errors.go",
- "eventfd.go",
- "exec.go",
- "fcntl.go",
- "file.go",
- "fs.go",
- "futex.go",
- "inotify.go",
- "ioctl.go",
- "ip.go",
- "ipc.go",
- "limits.go",
- "linux.go",
- "mm.go",
- "netdevice.go",
- "netfilter.go",
- "netlink.go",
- "netlink_route.go",
- "poll.go",
- "prctl.go",
- "ptrace.go",
- "rusage.go",
- "sched.go",
- "seccomp.go",
- "sem.go",
- "shm.go",
- "signal.go",
- "socket.go",
- "splice.go",
- "tcp.go",
- "time.go",
- "timer.go",
- "tty.go",
- "uio.go",
- "utsname.go",
- "wait.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/abi/linux",
- visibility = ["//visibility:public"],
- deps = [
- "//pkg/abi",
- "//pkg/binary",
- "//pkg/bits",
- ],
-)
-
-go_test(
- name = "linux_test",
- size = "small",
- srcs = ["netfilter_test.go"],
- embed = [":linux"],
- deps = [
- "//pkg/binary",
- ],
-)
diff --git a/pkg/abi/linux/linux_state_autogen.go b/pkg/abi/linux/linux_state_autogen.go
new file mode 100755
index 000000000..048ee5a25
--- /dev/null
+++ b/pkg/abi/linux/linux_state_autogen.go
@@ -0,0 +1,68 @@
+// automatically generated by stateify.
+
+package linux
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *BPFInstruction) beforeSave() {}
+func (x *BPFInstruction) save(m state.Map) {
+ x.beforeSave()
+ m.Save("OpCode", &x.OpCode)
+ m.Save("JumpIfTrue", &x.JumpIfTrue)
+ m.Save("JumpIfFalse", &x.JumpIfFalse)
+ m.Save("K", &x.K)
+}
+
+func (x *BPFInstruction) afterLoad() {}
+func (x *BPFInstruction) load(m state.Map) {
+ m.Load("OpCode", &x.OpCode)
+ m.Load("JumpIfTrue", &x.JumpIfTrue)
+ m.Load("JumpIfFalse", &x.JumpIfFalse)
+ m.Load("K", &x.K)
+}
+
+func (x *KernelTermios) beforeSave() {}
+func (x *KernelTermios) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InputFlags", &x.InputFlags)
+ m.Save("OutputFlags", &x.OutputFlags)
+ m.Save("ControlFlags", &x.ControlFlags)
+ m.Save("LocalFlags", &x.LocalFlags)
+ m.Save("LineDiscipline", &x.LineDiscipline)
+ m.Save("ControlCharacters", &x.ControlCharacters)
+ m.Save("InputSpeed", &x.InputSpeed)
+ m.Save("OutputSpeed", &x.OutputSpeed)
+}
+
+func (x *KernelTermios) afterLoad() {}
+func (x *KernelTermios) load(m state.Map) {
+ m.Load("InputFlags", &x.InputFlags)
+ m.Load("OutputFlags", &x.OutputFlags)
+ m.Load("ControlFlags", &x.ControlFlags)
+ m.Load("LocalFlags", &x.LocalFlags)
+ m.Load("LineDiscipline", &x.LineDiscipline)
+ m.Load("ControlCharacters", &x.ControlCharacters)
+ m.Load("InputSpeed", &x.InputSpeed)
+ m.Load("OutputSpeed", &x.OutputSpeed)
+}
+
+func (x *WindowSize) beforeSave() {}
+func (x *WindowSize) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Rows", &x.Rows)
+ m.Save("Cols", &x.Cols)
+}
+
+func (x *WindowSize) afterLoad() {}
+func (x *WindowSize) load(m state.Map) {
+ m.Load("Rows", &x.Rows)
+ m.Load("Cols", &x.Cols)
+}
+
+func init() {
+ state.Register("linux.BPFInstruction", (*BPFInstruction)(nil), state.Fns{Save: (*BPFInstruction).save, Load: (*BPFInstruction).load})
+ state.Register("linux.KernelTermios", (*KernelTermios)(nil), state.Fns{Save: (*KernelTermios).save, Load: (*KernelTermios).load})
+ state.Register("linux.WindowSize", (*WindowSize)(nil), state.Fns{Save: (*WindowSize).save, Load: (*WindowSize).load})
+}
diff --git a/pkg/abi/linux/netfilter_test.go b/pkg/abi/linux/netfilter_test.go
deleted file mode 100644
index 21e237f92..000000000
--- a/pkg/abi/linux/netfilter_test.go
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package linux
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/binary"
-)
-
-func TestSizes(t *testing.T) {
- testCases := []struct {
- typ interface{}
- defined uintptr
- }{
- {IPTEntry{}, SizeOfIPTEntry},
- {IPTGetEntries{}, SizeOfIPTGetEntries},
- {IPTGetinfo{}, SizeOfIPTGetinfo},
- {IPTIP{}, SizeOfIPTIP},
- {IPTReplace{}, SizeOfIPTReplace},
- {XTCounters{}, SizeOfXTCounters},
- {XTEntryMatch{}, SizeOfXTEntryMatch},
- {XTEntryTarget{}, SizeOfXTEntryTarget},
- {XTErrorTarget{}, SizeOfXTErrorTarget},
- {XTStandardTarget{}, SizeOfXTStandardTarget},
- }
-
- for _, tc := range testCases {
- if calculated := binary.Size(tc.typ); calculated != tc.defined {
- t.Errorf("%T has a defined size of %d and calculated size of %d", tc.typ, tc.defined, calculated)
- }
- }
-}
diff --git a/pkg/amutex/BUILD b/pkg/amutex/BUILD
deleted file mode 100644
index 6bc486b62..000000000
--- a/pkg/amutex/BUILD
+++ /dev/null
@@ -1,18 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "amutex",
- srcs = ["amutex.go"],
- importpath = "gvisor.dev/gvisor/pkg/amutex",
- visibility = ["//:sandbox"],
-)
-
-go_test(
- name = "amutex_test",
- size = "small",
- srcs = ["amutex_test.go"],
- embed = [":amutex"],
-)
diff --git a/pkg/amutex/amutex_state_autogen.go b/pkg/amutex/amutex_state_autogen.go
new file mode 100755
index 000000000..5651ae4e9
--- /dev/null
+++ b/pkg/amutex/amutex_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package amutex
+
diff --git a/pkg/amutex/amutex_test.go b/pkg/amutex/amutex_test.go
deleted file mode 100644
index 1d7f45641..000000000
--- a/pkg/amutex/amutex_test.go
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package amutex
-
-import (
- "sync"
- "testing"
- "time"
-)
-
-type sleeper struct {
- ch chan struct{}
-}
-
-func (s *sleeper) SleepStart() <-chan struct{} {
- return s.ch
-}
-
-func (*sleeper) SleepFinish(bool) {
-}
-
-func (s *sleeper) Interrupted() bool {
- return len(s.ch) != 0
-}
-
-func TestMutualExclusion(t *testing.T) {
- var m AbortableMutex
- m.Init()
-
- // Test mutual exclusion by running "gr" goroutines concurrently, and
- // have each one increment a counter "iters" times within the critical
- // section established by the mutex.
- //
- // If at the end of the counter is not gr * iters, then we know that
- // goroutines ran concurrently within the critical section.
- //
- // If one of the goroutines doesn't complete, it's likely a bug that
- // causes it to wait forever.
- const gr = 1000
- const iters = 100000
- v := 0
- var wg sync.WaitGroup
- for i := 0; i < gr; i++ {
- wg.Add(1)
- go func() {
- for j := 0; j < iters; j++ {
- m.Lock(nil)
- v++
- m.Unlock()
- }
- wg.Done()
- }()
- }
-
- wg.Wait()
-
- if v != gr*iters {
- t.Fatalf("Bad count: got %v, want %v", v, gr*iters)
- }
-}
-
-func TestAbortWait(t *testing.T) {
- var s sleeper
- var m AbortableMutex
- m.Init()
-
- // Lock the mutex.
- m.Lock(&s)
-
- // Lock again, but this time cancel after 500ms.
- s.ch = make(chan struct{}, 1)
- go func() {
- time.Sleep(500 * time.Millisecond)
- s.ch <- struct{}{}
- }()
- if v := m.Lock(&s); v {
- t.Fatalf("Lock succeeded when it should have failed")
- }
-
- // Lock again, but cancel right away.
- s.ch <- struct{}{}
- if v := m.Lock(&s); v {
- t.Fatalf("Lock succeeded when it should have failed")
- }
-}
diff --git a/pkg/atomicbitops/BUILD b/pkg/atomicbitops/BUILD
deleted file mode 100644
index 5f59866fa..000000000
--- a/pkg/atomicbitops/BUILD
+++ /dev/null
@@ -1,22 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "atomicbitops",
- srcs = [
- "atomic_bitops.go",
- "atomic_bitops_amd64.s",
- "atomic_bitops_common.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/atomicbitops",
- visibility = ["//:sandbox"],
-)
-
-go_test(
- name = "atomicbitops_test",
- size = "small",
- srcs = ["atomic_bitops_test.go"],
- embed = [":atomicbitops"],
-)
diff --git a/pkg/atomicbitops/atomic_bitops_test.go b/pkg/atomicbitops/atomic_bitops_test.go
deleted file mode 100644
index 965e9be79..000000000
--- a/pkg/atomicbitops/atomic_bitops_test.go
+++ /dev/null
@@ -1,261 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package atomicbitops
-
-import (
- "runtime"
- "sync"
- "testing"
-)
-
-const iterations = 100
-
-func detectRaces32(val, target uint32, fn func(*uint32, uint32)) bool {
- runtime.GOMAXPROCS(100)
- for n := 0; n < iterations; n++ {
- x := val
- var wg sync.WaitGroup
- for i := uint32(0); i < 32; i++ {
- wg.Add(1)
- go func(a *uint32, i uint32) {
- defer wg.Done()
- fn(a, uint32(1<<i))
- }(&x, i)
- }
- wg.Wait()
- if x != target {
- return true
- }
- }
- return false
-}
-
-func detectRaces64(val, target uint64, fn func(*uint64, uint64)) bool {
- runtime.GOMAXPROCS(100)
- for n := 0; n < iterations; n++ {
- x := val
- var wg sync.WaitGroup
- for i := uint64(0); i < 64; i++ {
- wg.Add(1)
- go func(a *uint64, i uint64) {
- defer wg.Done()
- fn(a, uint64(1<<i))
- }(&x, i)
- }
- wg.Wait()
- if x != target {
- return true
- }
- }
- return false
-}
-
-func TestOrUint32(t *testing.T) {
- if detectRaces32(0x0, 0xffffffff, OrUint32) {
- t.Error("Data race detected!")
- }
-}
-
-func TestAndUint32(t *testing.T) {
- if detectRaces32(0xf0f0f0f0, 0x00000000, AndUint32) {
- t.Error("Data race detected!")
- }
-}
-
-func TestXorUint32(t *testing.T) {
- if detectRaces32(0xf0f0f0f0, 0x0f0f0f0f, XorUint32) {
- t.Error("Data race detected!")
- }
-}
-
-func TestOrUint64(t *testing.T) {
- if detectRaces64(0x0, 0xffffffffffffffff, OrUint64) {
- t.Error("Data race detected!")
- }
-}
-
-func TestAndUint64(t *testing.T) {
- if detectRaces64(0xf0f0f0f0f0f0f0f0, 0x0, AndUint64) {
- t.Error("Data race detected!")
- }
-}
-
-func TestXorUint64(t *testing.T) {
- if detectRaces64(0xf0f0f0f0f0f0f0f0, 0x0f0f0f0f0f0f0f0f, XorUint64) {
- t.Error("Data race detected!")
- }
-}
-
-func TestCompareAndSwapUint32(t *testing.T) {
- tests := []struct {
- name string
- prev uint32
- old uint32
- new uint32
- next uint32
- }{
- {
- name: "Successful compare-and-swap with prev == new",
- prev: 10,
- old: 10,
- new: 10,
- next: 10,
- },
- {
- name: "Successful compare-and-swap with prev != new",
- prev: 20,
- old: 20,
- new: 22,
- next: 22,
- },
- {
- name: "Failed compare-and-swap with prev == new",
- prev: 31,
- old: 30,
- new: 31,
- next: 31,
- },
- {
- name: "Failed compare-and-swap with prev != new",
- prev: 41,
- old: 40,
- new: 42,
- next: 41,
- },
- }
- for _, test := range tests {
- val := test.prev
- prev := CompareAndSwapUint32(&val, test.old, test.new)
- if got, want := prev, test.prev; got != want {
- t.Errorf("%s: incorrect returned previous value: got %d, expected %d", test.name, got, want)
- }
- if got, want := val, test.next; got != want {
- t.Errorf("%s: incorrect value stored in val: got %d, expected %d", test.name, got, want)
- }
- }
-}
-
-func TestCompareAndSwapUint64(t *testing.T) {
- tests := []struct {
- name string
- prev uint64
- old uint64
- new uint64
- next uint64
- }{
- {
- name: "Successful compare-and-swap with prev == new",
- prev: 0x100000000,
- old: 0x100000000,
- new: 0x100000000,
- next: 0x100000000,
- },
- {
- name: "Successful compare-and-swap with prev != new",
- prev: 0x200000000,
- old: 0x200000000,
- new: 0x200000002,
- next: 0x200000002,
- },
- {
- name: "Failed compare-and-swap with prev == new",
- prev: 0x300000001,
- old: 0x300000000,
- new: 0x300000001,
- next: 0x300000001,
- },
- {
- name: "Failed compare-and-swap with prev != new",
- prev: 0x400000001,
- old: 0x400000000,
- new: 0x400000002,
- next: 0x400000001,
- },
- }
- for _, test := range tests {
- val := test.prev
- prev := CompareAndSwapUint64(&val, test.old, test.new)
- if got, want := prev, test.prev; got != want {
- t.Errorf("%s: incorrect returned previous value: got %d, expected %d", test.name, got, want)
- }
- if got, want := val, test.next; got != want {
- t.Errorf("%s: incorrect value stored in val: got %d, expected %d", test.name, got, want)
- }
- }
-}
-
-func TestIncUnlessZeroInt32(t *testing.T) {
- for _, test := range []struct {
- initial int32
- final int32
- ret bool
- }{
- {
- initial: 0,
- final: 0,
- ret: false,
- },
- {
- initial: 1,
- final: 2,
- ret: true,
- },
- {
- initial: 2,
- final: 3,
- ret: true,
- },
- } {
- val := test.initial
- if got, want := IncUnlessZeroInt32(&val), test.ret; got != want {
- t.Errorf("For initial value of %d: incorrect return value: got %v, wanted %v", test.initial, got, want)
- }
- if got, want := val, test.final; got != want {
- t.Errorf("For initial value of %d: incorrect final value: got %d, wanted %d", test.initial, got, want)
- }
- }
-}
-
-func TestDecUnlessOneInt32(t *testing.T) {
- for _, test := range []struct {
- initial int32
- final int32
- ret bool
- }{
- {
- initial: 0,
- final: -1,
- ret: true,
- },
- {
- initial: 1,
- final: 1,
- ret: false,
- },
- {
- initial: 2,
- final: 1,
- ret: true,
- },
- } {
- val := test.initial
- if got, want := DecUnlessOneInt32(&val), test.ret; got != want {
- t.Errorf("For initial value of %d: incorrect return value: got %v, wanted %v", test.initial, got, want)
- }
- if got, want := val, test.final; got != want {
- t.Errorf("For initial value of %d: incorrect final value: got %d, wanted %d", test.initial, got, want)
- }
- }
-}
diff --git a/pkg/atomicbitops/atomicbitops_state_autogen.go b/pkg/atomicbitops/atomicbitops_state_autogen.go
new file mode 100755
index 000000000..a74ea7d50
--- /dev/null
+++ b/pkg/atomicbitops/atomicbitops_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package atomicbitops
+
diff --git a/pkg/binary/BUILD b/pkg/binary/BUILD
deleted file mode 100644
index 543fb54bf..000000000
--- a/pkg/binary/BUILD
+++ /dev/null
@@ -1,18 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "binary",
- srcs = ["binary.go"],
- importpath = "gvisor.dev/gvisor/pkg/binary",
- visibility = ["//:sandbox"],
-)
-
-go_test(
- name = "binary_test",
- size = "small",
- srcs = ["binary_test.go"],
- embed = [":binary"],
-)
diff --git a/pkg/binary/binary_state_autogen.go b/pkg/binary/binary_state_autogen.go
new file mode 100755
index 000000000..e29aeb344
--- /dev/null
+++ b/pkg/binary/binary_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package binary
+
diff --git a/pkg/binary/binary_test.go b/pkg/binary/binary_test.go
deleted file mode 100644
index 4d609a438..000000000
--- a/pkg/binary/binary_test.go
+++ /dev/null
@@ -1,266 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package binary
-
-import (
- "bytes"
- "encoding/binary"
- "errors"
- "fmt"
- "io"
- "reflect"
- "strings"
- "testing"
-)
-
-func newInt32(i int32) *int32 {
- return &i
-}
-
-func TestSize(t *testing.T) {
- if got, want := Size(uint32(10)), uintptr(4); got != want {
- t.Errorf("Got = %d, want = %d", got, want)
- }
-}
-
-func TestPanic(t *testing.T) {
- tests := []struct {
- name string
- f func([]byte, binary.ByteOrder, interface{})
- data interface{}
- want string
- }{
- {"Unmarshal int", Unmarshal, 5, "invalid type: int"},
- {"Unmarshal []int", Unmarshal, []int{5}, "invalid type: int"},
- {"Marshal int", func(_ []byte, bo binary.ByteOrder, d interface{}) { Marshal(nil, bo, d) }, 5, "invalid type: int"},
- {"Marshal int[]", func(_ []byte, bo binary.ByteOrder, d interface{}) { Marshal(nil, bo, d) }, []int{5}, "invalid type: int"},
- {"Unmarshal short buffer", Unmarshal, newInt32(5), "runtime error: index out of range"},
- {"Unmarshal long buffer", func(_ []byte, bo binary.ByteOrder, d interface{}) { Unmarshal(make([]byte, 50), bo, d) }, newInt32(5), "buffer too long by 46 bytes"},
- {"marshal int", func(_ []byte, bo binary.ByteOrder, d interface{}) { marshal(nil, bo, reflect.ValueOf(d)) }, 5, "invalid type: int"},
- {"Size int", func(_ []byte, _ binary.ByteOrder, d interface{}) { Size(d) }, 5, "invalid type: int"},
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- defer func() {
- r := recover()
- if got := fmt.Sprint(r); !strings.HasPrefix(got, test.want) {
- t.Errorf("Got recover() = %q, want prefix = %q", got, test.want)
- }
- }()
-
- test.f(nil, LittleEndian, test.data)
- })
- }
-}
-
-type inner struct {
- Field int32
-}
-
-type outer struct {
- Int8 int8
- Int16 int16
- Int32 int32
- Int64 int64
- Uint8 uint8
- Uint16 uint16
- Uint32 uint32
- Uint64 uint64
-
- Slice []int32
- Array [5]int32
- Struct inner
-}
-
-func TestMarshalUnmarshal(t *testing.T) {
- want := outer{
- 1, 2, 3, 4, 5, 6, 7, 8,
- []int32{9, 10, 11},
- [5]int32{12, 13, 14, 15, 16},
- inner{17},
- }
- buf := Marshal(nil, LittleEndian, want)
- got := outer{Slice: []int32{0, 0, 0}}
- Unmarshal(buf, LittleEndian, &got)
- if !reflect.DeepEqual(&got, &want) {
- t.Errorf("Got = %#v, want = %#v", got, want)
- }
-}
-
-type outerBenchmark struct {
- Int8 int8
- Int16 int16
- Int32 int32
- Int64 int64
- Uint8 uint8
- Uint16 uint16
- Uint32 uint32
- Uint64 uint64
-
- Array [5]int32
- Struct inner
-}
-
-func BenchmarkMarshalUnmarshal(b *testing.B) {
- b.ReportAllocs()
-
- in := outerBenchmark{
- 1, 2, 3, 4, 5, 6, 7, 8,
- [5]int32{9, 10, 11, 12, 13},
- inner{14},
- }
- buf := make([]byte, Size(&in))
- out := outerBenchmark{}
-
- for i := 0; i < b.N; i++ {
- buf := Marshal(buf[:0], LittleEndian, &in)
- Unmarshal(buf, LittleEndian, &out)
- }
-}
-
-func BenchmarkReadWrite(b *testing.B) {
- b.ReportAllocs()
-
- in := outerBenchmark{
- 1, 2, 3, 4, 5, 6, 7, 8,
- [5]int32{9, 10, 11, 12, 13},
- inner{14},
- }
- buf := bytes.NewBuffer(make([]byte, binary.Size(&in)))
- out := outerBenchmark{}
-
- for i := 0; i < b.N; i++ {
- buf.Reset()
- if err := binary.Write(buf, LittleEndian, &in); err != nil {
- b.Error("Write:", err)
- }
- if err := binary.Read(buf, LittleEndian, &out); err != nil {
- b.Error("Read:", err)
- }
- }
-}
-
-type outerPadding struct {
- _ int8
- _ int16
- _ int32
- _ int64
- _ uint8
- _ uint16
- _ uint32
- _ uint64
-
- _ []int32
- _ [5]int32
- _ inner
-}
-
-func TestMarshalUnmarshalPadding(t *testing.T) {
- var want outerPadding
- buf := Marshal(nil, LittleEndian, want)
- var got outerPadding
- Unmarshal(buf, LittleEndian, &got)
- if !reflect.DeepEqual(&got, &want) {
- t.Errorf("Got = %#v, want = %#v", got, want)
- }
-}
-
-// Numbers with bits in every byte that distinguishable in big and little endian.
-const (
- want16 = 64<<8 | 128
- want32 = 16<<24 | 32<<16 | want16
- want64 = 1<<56 | 2<<48 | 4<<40 | 8<<32 | want32
-)
-
-func TestReadWriteUint16(t *testing.T) {
- const want = uint16(want16)
- var buf bytes.Buffer
- if err := WriteUint16(&buf, LittleEndian, want); err != nil {
- t.Error("WriteUint16:", err)
- }
- got, err := ReadUint16(&buf, LittleEndian)
- if err != nil {
- t.Error("ReadUint16:", err)
- }
- if got != want {
- t.Errorf("got = %d, want = %d", got, want)
- }
-}
-
-func TestReadWriteUint32(t *testing.T) {
- const want = uint32(want32)
- var buf bytes.Buffer
- if err := WriteUint32(&buf, LittleEndian, want); err != nil {
- t.Error("WriteUint32:", err)
- }
- got, err := ReadUint32(&buf, LittleEndian)
- if err != nil {
- t.Error("ReadUint32:", err)
- }
- if got != want {
- t.Errorf("got = %d, want = %d", got, want)
- }
-}
-
-func TestReadWriteUint64(t *testing.T) {
- const want = uint64(want64)
- var buf bytes.Buffer
- if err := WriteUint64(&buf, LittleEndian, want); err != nil {
- t.Error("WriteUint64:", err)
- }
- got, err := ReadUint64(&buf, LittleEndian)
- if err != nil {
- t.Error("ReadUint64:", err)
- }
- if got != want {
- t.Errorf("got = %d, want = %d", got, want)
- }
-}
-
-type readWriter struct {
- err error
-}
-
-func (rw *readWriter) Write([]byte) (int, error) {
- return 0, rw.err
-}
-
-func (rw *readWriter) Read([]byte) (int, error) {
- return 0, rw.err
-}
-
-func TestReadWriteError(t *testing.T) {
- tests := []struct {
- name string
- f func(rw io.ReadWriter) error
- }{
- {"WriteUint16", func(rw io.ReadWriter) error { return WriteUint16(rw, LittleEndian, 0) }},
- {"ReadUint16", func(rw io.ReadWriter) error { _, err := ReadUint16(rw, LittleEndian); return err }},
- {"WriteUint32", func(rw io.ReadWriter) error { return WriteUint32(rw, LittleEndian, 0) }},
- {"ReadUint32", func(rw io.ReadWriter) error { _, err := ReadUint32(rw, LittleEndian); return err }},
- {"WriteUint64", func(rw io.ReadWriter) error { return WriteUint64(rw, LittleEndian, 0) }},
- {"ReadUint64", func(rw io.ReadWriter) error { _, err := ReadUint64(rw, LittleEndian); return err }},
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- want := errors.New("want")
- if got := test.f(&readWriter{want}); got != want {
- t.Errorf("got = %v, want = %v", got, want)
- }
- })
- }
-}
diff --git a/pkg/bits/BUILD b/pkg/bits/BUILD
deleted file mode 100644
index 51967b811..000000000
--- a/pkg/bits/BUILD
+++ /dev/null
@@ -1,57 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template", "go_template_instance")
-
-go_library(
- name = "bits",
- srcs = [
- "bits.go",
- "bits32.go",
- "bits64.go",
- "uint64_arch_amd64.go",
- "uint64_arch_amd64_asm.s",
- "uint64_arch_generic.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/bits",
- visibility = ["//:sandbox"],
-)
-
-go_template(
- name = "bits_template",
- srcs = ["bits_template.go"],
- types = [
- "T",
- ],
-)
-
-go_template_instance(
- name = "bits64",
- out = "bits64.go",
- package = "bits",
- suffix = "64",
- template = ":bits_template",
- types = {
- "T": "uint64",
- },
-)
-
-go_template_instance(
- name = "bits32",
- out = "bits32.go",
- package = "bits",
- suffix = "32",
- template = ":bits_template",
- types = {
- "T": "uint32",
- },
-)
-
-go_test(
- name = "bits_test",
- size = "small",
- srcs = ["uint64_test.go"],
- embed = [":bits"],
-)
diff --git a/pkg/bits/bits32.go b/pkg/bits/bits32.go
new file mode 100755
index 000000000..4e9e45dce
--- /dev/null
+++ b/pkg/bits/bits32.go
@@ -0,0 +1,25 @@
+package bits
+
+// IsOn returns true if *all* bits set in 'bits' are set in 'mask'.
+func IsOn32(mask, bits uint32) bool {
+ return mask&bits == bits
+}
+
+// IsAnyOn returns true if *any* bit set in 'bits' is set in 'mask'.
+func IsAnyOn32(mask, bits uint32) bool {
+ return mask&bits != 0
+}
+
+// Mask returns a T with all of the given bits set.
+func Mask32(is ...int) uint32 {
+ ret := uint32(0)
+ for _, i := range is {
+ ret |= MaskOf32(i)
+ }
+ return ret
+}
+
+// MaskOf is like Mask, but sets only a single bit (more efficiently).
+func MaskOf32(i int) uint32 {
+ return uint32(1) << uint32(i)
+}
diff --git a/pkg/bits/bits64.go b/pkg/bits/bits64.go
new file mode 100755
index 000000000..f49158792
--- /dev/null
+++ b/pkg/bits/bits64.go
@@ -0,0 +1,25 @@
+package bits
+
+// IsOn returns true if *all* bits set in 'bits' are set in 'mask'.
+func IsOn64(mask, bits uint64) bool {
+ return mask&bits == bits
+}
+
+// IsAnyOn returns true if *any* bit set in 'bits' is set in 'mask'.
+func IsAnyOn64(mask, bits uint64) bool {
+ return mask&bits != 0
+}
+
+// Mask returns a T with all of the given bits set.
+func Mask64(is ...int) uint64 {
+ ret := uint64(0)
+ for _, i := range is {
+ ret |= MaskOf64(i)
+ }
+ return ret
+}
+
+// MaskOf is like Mask, but sets only a single bit (more efficiently).
+func MaskOf64(i int) uint64 {
+ return uint64(1) << uint64(i)
+}
diff --git a/pkg/bits/bits_state_autogen.go b/pkg/bits/bits_state_autogen.go
new file mode 100755
index 000000000..2abb1291b
--- /dev/null
+++ b/pkg/bits/bits_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package bits
+
diff --git a/pkg/bits/bits_template.go b/pkg/bits/bits_template.go
deleted file mode 100644
index 93a435b80..000000000
--- a/pkg/bits/bits_template.go
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package bits
-
-// Non-atomic bit operations on a template type T.
-
-// T is a required type parameter that must be an integral type.
-type T uint64
-
-// IsOn returns true if *all* bits set in 'bits' are set in 'mask'.
-func IsOn(mask, bits T) bool {
- return mask&bits == bits
-}
-
-// IsAnyOn returns true if *any* bit set in 'bits' is set in 'mask'.
-func IsAnyOn(mask, bits T) bool {
- return mask&bits != 0
-}
-
-// Mask returns a T with all of the given bits set.
-func Mask(is ...int) T {
- ret := T(0)
- for _, i := range is {
- ret |= MaskOf(i)
- }
- return ret
-}
-
-// MaskOf is like Mask, but sets only a single bit (more efficiently).
-func MaskOf(i int) T {
- return T(1) << T(i)
-}
diff --git a/pkg/bits/uint64_test.go b/pkg/bits/uint64_test.go
deleted file mode 100644
index 1b018d808..000000000
--- a/pkg/bits/uint64_test.go
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package bits
-
-import (
- "reflect"
- "testing"
-)
-
-func TestTrailingZeros64(t *testing.T) {
- for i := 0; i <= 64; i++ {
- n := uint64(1) << uint(i)
- if got, want := TrailingZeros64(n), i; got != want {
- t.Errorf("TrailingZeros64(%#x): got %d, wanted %d", n, got, want)
- }
- }
-
- for i := 0; i < 64; i++ {
- n := ^uint64(0) << uint(i)
- if got, want := TrailingZeros64(n), i; got != want {
- t.Errorf("TrailingZeros64(%#x): got %d, wanted %d", n, got, want)
- }
- }
-
- for i := 0; i < 64; i++ {
- n := ^uint64(0) >> uint(i)
- if got, want := TrailingZeros64(n), 0; got != want {
- t.Errorf("TrailingZeros64(%#x): got %d, wanted %d", n, got, want)
- }
- }
-}
-
-func TestMostSignificantOne64(t *testing.T) {
- for i := 0; i <= 64; i++ {
- n := uint64(1) << uint(i)
- if got, want := MostSignificantOne64(n), i; got != want {
- t.Errorf("MostSignificantOne64(%#x): got %d, wanted %d", n, got, want)
- }
- }
-
- for i := 0; i < 64; i++ {
- n := ^uint64(0) >> uint(i)
- if got, want := MostSignificantOne64(n), 63-i; got != want {
- t.Errorf("MostSignificantOne64(%#x): got %d, wanted %d", n, got, want)
- }
- }
-
- for i := 0; i < 64; i++ {
- n := ^uint64(0) << uint(i)
- if got, want := MostSignificantOne64(n), 63; got != want {
- t.Errorf("MostSignificantOne64(%#x): got %d, wanted %d", n, got, want)
- }
- }
-}
-
-func TestForEachSetBit64(t *testing.T) {
- for _, want := range [][]int{
- {},
- {0},
- {1},
- {63},
- {0, 1},
- {1, 3, 5},
- {0, 63},
- } {
- n := Mask64(want...)
- // "Slice values are deeply equal when ... they are both nil or both
- // non-nil ..."
- got := make([]int, 0)
- ForEachSetBit64(n, func(i int) {
- got = append(got, i)
- })
- if !reflect.DeepEqual(got, want) {
- t.Errorf("ForEachSetBit64(%#x): iterated bits %v, wanted %v", n, got, want)
- }
- }
-}
-
-func TestIsOn(t *testing.T) {
- type spec struct {
- mask uint64
- bits uint64
- any bool
- all bool
- }
- for _, s := range []spec{
- {Mask64(0), Mask64(0), true, true},
- {Mask64(63), Mask64(63), true, true},
- {Mask64(0), Mask64(1), false, false},
- {Mask64(0), Mask64(0, 1), true, false},
-
- {Mask64(1, 63), Mask64(1), true, true},
- {Mask64(1, 63), Mask64(1, 63), true, true},
- {Mask64(1, 63), Mask64(0, 1, 63), true, false},
- {Mask64(1, 63), Mask64(0, 62), false, false},
- } {
- if ok := IsAnyOn64(s.mask, s.bits); ok != s.any {
- t.Errorf("IsAnyOn(%#x, %#x) = %v, wanted: %v", s.mask, s.bits, ok, s.any)
- }
- if ok := IsOn64(s.mask, s.bits); ok != s.all {
- t.Errorf("IsOn(%#x, %#x) = %v, wanted: %v", s.mask, s.bits, ok, s.all)
- }
- }
-}
diff --git a/pkg/bpf/BUILD b/pkg/bpf/BUILD
deleted file mode 100644
index 8d31e068c..000000000
--- a/pkg/bpf/BUILD
+++ /dev/null
@@ -1,34 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "bpf",
- srcs = [
- "bpf.go",
- "decoder.go",
- "input_bytes.go",
- "interpreter.go",
- "program_builder.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/bpf",
- visibility = ["//visibility:public"],
- deps = ["//pkg/abi/linux"],
-)
-
-go_test(
- name = "bpf_test",
- size = "small",
- srcs = [
- "decoder_test.go",
- "interpreter_test.go",
- "program_builder_test.go",
- ],
- embed = [":bpf"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/binary",
- ],
-)
diff --git a/pkg/bpf/bpf_state_autogen.go b/pkg/bpf/bpf_state_autogen.go
new file mode 100755
index 000000000..d10db8e49
--- /dev/null
+++ b/pkg/bpf/bpf_state_autogen.go
@@ -0,0 +1,22 @@
+// automatically generated by stateify.
+
+package bpf
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *Program) beforeSave() {}
+func (x *Program) save(m state.Map) {
+ x.beforeSave()
+ m.Save("instructions", &x.instructions)
+}
+
+func (x *Program) afterLoad() {}
+func (x *Program) load(m state.Map) {
+ m.Load("instructions", &x.instructions)
+}
+
+func init() {
+ state.Register("bpf.Program", (*Program)(nil), state.Fns{Save: (*Program).save, Load: (*Program).load})
+}
diff --git a/pkg/bpf/decoder_test.go b/pkg/bpf/decoder_test.go
deleted file mode 100644
index 6a023f0c0..000000000
--- a/pkg/bpf/decoder_test.go
+++ /dev/null
@@ -1,146 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package bpf
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
-)
-
-func TestDecode(t *testing.T) {
- for _, test := range []struct {
- filter linux.BPFInstruction
- expected string
- fail bool
- }{
- {filter: Stmt(Ld+Imm, 10), expected: "A <- 10"},
- {filter: Stmt(Ld+Abs+W, 10), expected: "A <- P[10:4]"},
- {filter: Stmt(Ld+Ind+H, 10), expected: "A <- P[X+10:2]"},
- {filter: Stmt(Ld+Ind+B, 10), expected: "A <- P[X+10:1]"},
- {filter: Stmt(Ld+Mem, 10), expected: "A <- M[10]"},
- {filter: Stmt(Ld+Len, 0), expected: "A <- len"},
- {filter: Stmt(Ldx+Imm, 10), expected: "X <- 10"},
- {filter: Stmt(Ldx+Mem, 10), expected: "X <- M[10]"},
- {filter: Stmt(Ldx+Len, 0), expected: "X <- len"},
- {filter: Stmt(Ldx+Msh, 10), expected: "X <- 4*(P[10:1]&0xf)"},
- {filter: Stmt(St, 10), expected: "M[10] <- A"},
- {filter: Stmt(Stx, 10), expected: "M[10] <- X"},
- {filter: Stmt(Alu+Add+K, 10), expected: "A <- A + 10"},
- {filter: Stmt(Alu+Sub+K, 10), expected: "A <- A - 10"},
- {filter: Stmt(Alu+Mul+K, 10), expected: "A <- A * 10"},
- {filter: Stmt(Alu+Div+K, 10), expected: "A <- A / 10"},
- {filter: Stmt(Alu+Or+K, 10), expected: "A <- A | 10"},
- {filter: Stmt(Alu+And+K, 10), expected: "A <- A & 10"},
- {filter: Stmt(Alu+Lsh+K, 10), expected: "A <- A << 10"},
- {filter: Stmt(Alu+Rsh+K, 10), expected: "A <- A >> 10"},
- {filter: Stmt(Alu+Mod+K, 10), expected: "A <- A % 10"},
- {filter: Stmt(Alu+Xor+K, 10), expected: "A <- A ^ 10"},
- {filter: Stmt(Alu+Add+X, 0), expected: "A <- A + X"},
- {filter: Stmt(Alu+Sub+X, 0), expected: "A <- A - X"},
- {filter: Stmt(Alu+Mul+X, 0), expected: "A <- A * X"},
- {filter: Stmt(Alu+Div+X, 0), expected: "A <- A / X"},
- {filter: Stmt(Alu+Or+X, 0), expected: "A <- A | X"},
- {filter: Stmt(Alu+And+X, 0), expected: "A <- A & X"},
- {filter: Stmt(Alu+Lsh+X, 0), expected: "A <- A << X"},
- {filter: Stmt(Alu+Rsh+X, 0), expected: "A <- A >> X"},
- {filter: Stmt(Alu+Mod+X, 0), expected: "A <- A % X"},
- {filter: Stmt(Alu+Xor+X, 0), expected: "A <- A ^ X"},
- {filter: Stmt(Alu+Neg, 0), expected: "A <- -A"},
- {filter: Stmt(Jmp+Ja, 10), expected: "pc += 10"},
- {filter: Jump(Jmp+Jeq+K, 10, 2, 5), expected: "pc += (A == 10) ? 2 : 5"},
- {filter: Jump(Jmp+Jgt+K, 10, 2, 5), expected: "pc += (A > 10) ? 2 : 5"},
- {filter: Jump(Jmp+Jge+K, 10, 2, 5), expected: "pc += (A >= 10) ? 2 : 5"},
- {filter: Jump(Jmp+Jset+K, 10, 2, 5), expected: "pc += (A & 10) ? 2 : 5"},
- {filter: Jump(Jmp+Jeq+X, 0, 2, 5), expected: "pc += (A == X) ? 2 : 5"},
- {filter: Jump(Jmp+Jgt+X, 0, 2, 5), expected: "pc += (A > X) ? 2 : 5"},
- {filter: Jump(Jmp+Jge+X, 0, 2, 5), expected: "pc += (A >= X) ? 2 : 5"},
- {filter: Jump(Jmp+Jset+X, 0, 2, 5), expected: "pc += (A & X) ? 2 : 5"},
- {filter: Stmt(Ret+K, 10), expected: "ret 10"},
- {filter: Stmt(Ret+A, 0), expected: "ret A"},
- {filter: Stmt(Misc+Tax, 0), expected: "X <- A"},
- {filter: Stmt(Misc+Txa, 0), expected: "A <- X"},
- {filter: Stmt(Ld+Ind+Msh, 0), fail: true},
- } {
- got, err := Decode(test.filter)
- if test.fail {
- if err == nil {
- t.Errorf("Decode(%v) failed, expected: 'error', got: %q", test.filter, got)
- continue
- }
- } else {
- if err != nil {
- t.Errorf("Decode(%v) failed for test %q, error: %q", test.filter, test.expected, err)
- continue
- }
- if got != test.expected {
- t.Errorf("Decode(%v) failed, expected: %q, got: %q", test.filter, test.expected, got)
- continue
- }
- }
- }
-}
-
-func TestDecodeProgram(t *testing.T) {
- for _, test := range []struct {
- name string
- program []linux.BPFInstruction
- expected string
- fail bool
- }{
- {name: "basic with jump indexes",
- program: []linux.BPFInstruction{
- Stmt(Ld+Abs+W, 10),
- Stmt(Ldx+Mem, 10),
- Stmt(St, 10),
- Stmt(Stx, 10),
- Stmt(Alu+Add+K, 10),
- Stmt(Jmp+Ja, 10),
- Jump(Jmp+Jeq+K, 10, 2, 5),
- Jump(Jmp+Jset+X, 0, 0, 5),
- Stmt(Misc+Tax, 0),
- },
- expected: "0: A <- P[10:4]\n" +
- "1: X <- M[10]\n" +
- "2: M[10] <- A\n" +
- "3: M[10] <- X\n" +
- "4: A <- A + 10\n" +
- "5: pc += 10 [16]\n" +
- "6: pc += (A == 10) ? 2 [9] : 5 [12]\n" +
- "7: pc += (A & X) ? 0 [8] : 5 [13]\n" +
- "8: X <- A\n",
- },
- {name: "invalid instruction",
- program: []linux.BPFInstruction{Stmt(Ld+Abs+W, 10), Stmt(Ld+Len+Mem, 0)},
- fail: true},
- } {
- got, err := DecodeProgram(test.program)
- if test.fail {
- if err == nil {
- t.Errorf("%s: Decode(...) failed, expected: 'error', got: %q", test.name, got)
- continue
- }
- } else {
- if err != nil {
- t.Errorf("%s: Decode failed: %v", test.name, err)
- continue
- }
- if got != test.expected {
- t.Errorf("%s: Decode(...) failed, expected: %q, got: %q", test.name, test.expected, got)
- continue
- }
- }
- }
-}
diff --git a/pkg/bpf/interpreter_test.go b/pkg/bpf/interpreter_test.go
deleted file mode 100644
index 547921d0a..000000000
--- a/pkg/bpf/interpreter_test.go
+++ /dev/null
@@ -1,797 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package bpf
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/binary"
-)
-
-func TestCompilationErrors(t *testing.T) {
- for _, test := range []struct {
- // desc is the test's description.
- desc string
-
- // insns is the BPF instructions to be compiled.
- insns []linux.BPFInstruction
-
- // expectedErr is the expected compilation error.
- expectedErr error
- }{
- {
- desc: "Instructions must not be nil",
- expectedErr: Error{InvalidInstructionCount, 0},
- },
- {
- desc: "Instructions must not be empty",
- insns: []linux.BPFInstruction{},
- expectedErr: Error{InvalidInstructionCount, 0},
- },
- {
- desc: "A program must end with a return",
- insns: make([]linux.BPFInstruction, MaxInstructions),
- expectedErr: Error{InvalidEndOfProgram, MaxInstructions - 1},
- },
- {
- desc: "A program must have MaxInstructions or fewer instructions",
- insns: append(make([]linux.BPFInstruction, MaxInstructions), Stmt(Ret|K, 0)),
- expectedErr: Error{InvalidInstructionCount, MaxInstructions + 1},
- },
- {
- desc: "A load from an invalid M register is a compilation error",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Mem|W, ScratchMemRegisters), // A = M[16]
- Stmt(Ret|K, 0), // return 0
- },
- expectedErr: Error{InvalidRegister, 0},
- },
- {
- desc: "A store to an invalid M register is a compilation error",
- insns: []linux.BPFInstruction{
- Stmt(St, ScratchMemRegisters), // M[16] = A
- Stmt(Ret|K, 0), // return 0
- },
- expectedErr: Error{InvalidRegister, 0},
- },
- {
- desc: "Division by literal zero is a compilation error",
- insns: []linux.BPFInstruction{
- Stmt(Alu|Div|K, 0), // A /= 0
- Stmt(Ret|K, 0), // return 0
- },
- expectedErr: Error{DivisionByZero, 0},
- },
- {
- desc: "An unconditional jump outside of the program is a compilation error",
- insns: []linux.BPFInstruction{
- Jump(Jmp|Ja, 1, 0, 0), // jmp nextpc+1
- Stmt(Ret|K, 0), // return 0
- },
- expectedErr: Error{InvalidJumpTarget, 0},
- },
- {
- desc: "A conditional jump outside of the program in the true case is a compilation error",
- insns: []linux.BPFInstruction{
- Jump(Jmp|Jeq|K, 0, 1, 0), // if (A == K) jmp nextpc+1
- Stmt(Ret|K, 0), // return 0
- },
- expectedErr: Error{InvalidJumpTarget, 0},
- },
- {
- desc: "A conditional jump outside of the program in the false case is a compilation error",
- insns: []linux.BPFInstruction{
- Jump(Jmp|Jeq|K, 0, 0, 1), // if (A != K) jmp nextpc+1
- Stmt(Ret|K, 0), // return 0
- },
- expectedErr: Error{InvalidJumpTarget, 0},
- },
- } {
- _, err := Compile(test.insns)
- if err != test.expectedErr {
- t.Errorf("%s: expected error %q, got error %q", test.desc, test.expectedErr, err)
- }
- }
-}
-
-func TestExecErrors(t *testing.T) {
- for _, test := range []struct {
- // desc is the test's description.
- desc string
-
- // insns is the BPF instructions to be executed.
- insns []linux.BPFInstruction
-
- // expectedErr is the expected execution error.
- expectedErr error
- }{
- {
- desc: "An out-of-bounds load of input data is an execution error",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Abs|B, 0), // A = input[0]
- Stmt(Ret|K, 0), // return 0
- },
- expectedErr: Error{InvalidLoad, 0},
- },
- {
- desc: "Division by zero at runtime is an execution error",
- insns: []linux.BPFInstruction{
- Stmt(Alu|Div|X, 0), // A /= X
- Stmt(Ret|K, 0), // return 0
- },
- expectedErr: Error{DivisionByZero, 0},
- },
- {
- desc: "Modulo zero at runtime is an execution error",
- insns: []linux.BPFInstruction{
- Stmt(Alu|Mod|X, 0), // A %= X
- Stmt(Ret|K, 0), // return 0
- },
- expectedErr: Error{DivisionByZero, 0},
- },
- } {
- p, err := Compile(test.insns)
- if err != nil {
- t.Errorf("%s: unexpected compilation error: %v", test.desc, err)
- continue
- }
- ret, err := Exec(p, InputBytes{nil, binary.BigEndian})
- if err != test.expectedErr {
- t.Errorf("%s: expected execution error %q, got (%d, %v)", test.desc, test.expectedErr, ret, err)
- }
- }
-}
-
-func TestValidInstructions(t *testing.T) {
- for _, test := range []struct {
- // desc is the test's description.
- desc string
-
- // insns is the BPF instructions to be compiled.
- insns []linux.BPFInstruction
-
- // input is the input data. Note that input will be read as big-endian.
- input []byte
-
- // expectedRet is the expected return value of the BPF program.
- expectedRet uint32
- }{
- {
- desc: "Return of immediate",
- insns: []linux.BPFInstruction{
- Stmt(Ret|K, 42), // return 42
- },
- expectedRet: 42,
- },
- {
- desc: "Load of immediate into A",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 42), // A = 42
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 42,
- },
- {
- desc: "Load of immediate into X and copying of X into A",
- insns: []linux.BPFInstruction{
- Stmt(Ldx|Imm|W, 42), // X = 42
- Stmt(Misc|Tax, 0), // A = X
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 42,
- },
- {
- desc: "Copying of A into X and back",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 42), // A = 42
- Stmt(Misc|Txa, 0), // X = A
- Stmt(Ld|Imm|W, 0), // A = 0
- Stmt(Misc|Tax, 0), // A = X
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 42,
- },
- {
- desc: "Load of 32-bit input by absolute offset into A",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Abs|W, 1), // A = input[1..4]
- Stmt(Ret|A, 0), // return A
- },
- input: []byte{0x00, 0x11, 0x22, 0x33, 0x44},
- expectedRet: 0x11223344,
- },
- {
- desc: "Load of 16-bit input by absolute offset into A",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Abs|H, 1), // A = input[1..2]
- Stmt(Ret|A, 0), // return A
- },
- input: []byte{0x00, 0x11, 0x22},
- expectedRet: 0x1122,
- },
- {
- desc: "Load of 8-bit input by absolute offset into A",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Abs|B, 1), // A = input[1]
- Stmt(Ret|A, 0), // return A
- },
- input: []byte{0x00, 0x11},
- expectedRet: 0x11,
- },
- {
- desc: "Load of 32-bit input by relative offset into A",
- insns: []linux.BPFInstruction{
- Stmt(Ldx|Imm|W, 1), // X = 1
- Stmt(Ld|Ind|W, 1), // A = input[X+1..X+4]
- Stmt(Ret|A, 0), // return A
- },
- input: []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55},
- expectedRet: 0x22334455,
- },
- {
- desc: "Load of 16-bit input by relative offset into A",
- insns: []linux.BPFInstruction{
- Stmt(Ldx|Imm|W, 1), // X = 1
- Stmt(Ld|Ind|H, 1), // A = input[X+1..X+2]
- Stmt(Ret|A, 0), // return A
- },
- input: []byte{0x00, 0x11, 0x22, 0x33},
- expectedRet: 0x2233,
- },
- {
- desc: "Load of 8-bit input by relative offset into A",
- insns: []linux.BPFInstruction{
- Stmt(Ldx|Imm|W, 1), // X = 1
- Stmt(Ld|Ind|B, 1), // A = input[X+1]
- Stmt(Ret|A, 0), // return A
- },
- input: []byte{0x00, 0x11, 0x22},
- expectedRet: 0x22,
- },
- {
- desc: "Load/store between A and scratch memory",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 42), // A = 42
- Stmt(St, 2), // M[2] = A
- Stmt(Ld|Imm|W, 0), // A = 0
- Stmt(Ld|Mem|W, 2), // A = M[2]
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 42,
- },
- {
- desc: "Load/store between X and scratch memory",
- insns: []linux.BPFInstruction{
- Stmt(Ldx|Imm|W, 42), // X = 42
- Stmt(Stx, 3), // M[3] = X
- Stmt(Ldx|Imm|W, 0), // X = 0
- Stmt(Ldx|Mem|W, 3), // X = M[3]
- Stmt(Misc|Tax, 0), // A = X
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 42,
- },
- {
- desc: "Load of input length into A",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Len|W, 0), // A = len(input)
- Stmt(Ret|A, 0), // return A
- },
- input: []byte{1, 2, 3},
- expectedRet: 3,
- },
- {
- desc: "Load of input length into X",
- insns: []linux.BPFInstruction{
- Stmt(Ldx|Len|W, 0), // X = len(input)
- Stmt(Misc|Tax, 0), // A = X
- Stmt(Ret|A, 0), // return A
- },
- input: []byte{1, 2, 3},
- expectedRet: 3,
- },
- {
- desc: "Load of MSH (?) into X",
- insns: []linux.BPFInstruction{
- Stmt(Ldx|Msh|B, 0), // X = 4*(input[0]&0xf)
- Stmt(Misc|Tax, 0), // A = X
- Stmt(Ret|A, 0), // return A
- },
- input: []byte{0xf1},
- expectedRet: 4,
- },
- {
- desc: "Addition of immediate",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 10), // A = 10
- Stmt(Alu|Add|K, 20), // A += 20
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 30,
- },
- {
- desc: "Addition of X",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 10), // A = 10
- Stmt(Ldx|Imm|W, 20), // X = 20
- Stmt(Alu|Add|X, 0), // A += X
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 30,
- },
- {
- desc: "Subtraction of immediate",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 30), // A = 30
- Stmt(Alu|Sub|K, 20), // A -= 20
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 10,
- },
- {
- desc: "Subtraction of X",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 30), // A = 30
- Stmt(Ldx|Imm|W, 20), // X = 20
- Stmt(Alu|Sub|X, 0), // A -= X
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 10,
- },
- {
- desc: "Multiplication of immediate",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 2), // A = 2
- Stmt(Alu|Mul|K, 3), // A *= 3
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 6,
- },
- {
- desc: "Multiplication of X",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 2), // A = 2
- Stmt(Ldx|Imm|W, 3), // X = 3
- Stmt(Alu|Mul|X, 0), // A *= X
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 6,
- },
- {
- desc: "Division by immediate",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 6), // A = 6
- Stmt(Alu|Div|K, 3), // A /= 3
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 2,
- },
- {
- desc: "Division by X",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 6), // A = 6
- Stmt(Ldx|Imm|W, 3), // X = 3
- Stmt(Alu|Div|X, 0), // A /= X
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 2,
- },
- {
- desc: "Modulo immediate",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 17), // A = 17
- Stmt(Alu|Mod|K, 7), // A %= 7
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 3,
- },
- {
- desc: "Modulo X",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 17), // A = 17
- Stmt(Ldx|Imm|W, 7), // X = 7
- Stmt(Alu|Mod|X, 0), // A %= X
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 3,
- },
- {
- desc: "Arithmetic negation",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 1), // A = 1
- Stmt(Alu|Neg, 0), // A = -A
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 0xffffffff,
- },
- {
- desc: "Bitwise OR with immediate",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 0xff00aa55), // A = 0xff00aa55
- Stmt(Alu|Or|K, 0xff0055aa), // A |= 0xff0055aa
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 0xff00ffff,
- },
- {
- desc: "Bitwise OR with X",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 0xff00aa55), // A = 0xff00aa55
- Stmt(Ldx|Imm|W, 0xff0055aa), // X = 0xff0055aa
- Stmt(Alu|Or|X, 0), // A |= X
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 0xff00ffff,
- },
- {
- desc: "Bitwise AND with immediate",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 0xff00aa55), // A = 0xff00aa55
- Stmt(Alu|And|K, 0xff0055aa), // A &= 0xff0055aa
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 0xff000000,
- },
- {
- desc: "Bitwise AND with X",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 0xff00aa55), // A = 0xff00aa55
- Stmt(Ldx|Imm|W, 0xff0055aa), // X = 0xff0055aa
- Stmt(Alu|And|X, 0), // A &= X
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 0xff000000,
- },
- {
- desc: "Bitwise XOR with immediate",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 0xff00aa55), // A = 0xff00aa55
- Stmt(Alu|Xor|K, 0xff0055aa), // A ^= 0xff0055aa
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 0x0000ffff,
- },
- {
- desc: "Bitwise XOR with X",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 0xff00aa55), // A = 0xff00aa55
- Stmt(Ldx|Imm|W, 0xff0055aa), // X = 0xff0055aa
- Stmt(Alu|Xor|X, 0), // A ^= X
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 0x0000ffff,
- },
- {
- desc: "Left shift by immediate",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 1), // A = 1
- Stmt(Alu|Lsh|K, 5), // A <<= 5
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 32,
- },
- {
- desc: "Left shift by X",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 1), // A = 1
- Stmt(Ldx|Imm|W, 5), // X = 5
- Stmt(Alu|Lsh|X, 0), // A <<= X
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 32,
- },
- {
- desc: "Right shift by immediate",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 0xffffffff), // A = 0xffffffff
- Stmt(Alu|Rsh|K, 31), // A >>= 31
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 1,
- },
- {
- desc: "Right shift by X",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 0xffffffff), // A = 0xffffffff
- Stmt(Ldx|Imm|W, 31), // X = 31
- Stmt(Alu|Rsh|X, 0), // A >>= X
- Stmt(Ret|A, 0), // return A
- },
- expectedRet: 1,
- },
- {
- desc: "Unconditional jump",
- insns: []linux.BPFInstruction{
- Jump(Jmp|Ja, 1, 0, 0), // jmp nextpc+1
- Stmt(Ret|K, 0), // return 0
- Stmt(Ret|K, 1), // return 1
- },
- expectedRet: 1,
- },
- {
- desc: "Jump when A == immediate",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 42), // A = 42
- Jump(Jmp|Jeq|K, 42, 1, 2), // if (A == 42) jmp nextpc+1 else jmp nextpc+2
- Stmt(Ret|K, 0), // return 0
- Stmt(Ret|K, 1), // return 1
- Stmt(Ret|K, 2), // return 2
- },
- expectedRet: 1,
- },
- {
- desc: "Jump when A != immediate",
- insns: []linux.BPFInstruction{
- Jump(Jmp|Jeq|K, 42, 1, 2), // if (A == 42) jmp nextpc+1 else jmp nextpc+2
- Stmt(Ret|K, 0), // return 0
- Stmt(Ret|K, 1), // return 1
- Stmt(Ret|K, 2), // return 2
- },
- expectedRet: 2,
- },
- {
- desc: "Jump when A == X",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 42), // A = 42
- Stmt(Ldx|Imm|W, 42), // X = 42
- Jump(Jmp|Jeq|X, 0, 1, 2), // if (A == X) jmp nextpc+1 else jmp nextpc+2
- Stmt(Ret|K, 0), // return 0
- Stmt(Ret|K, 1), // return 1
- Stmt(Ret|K, 2), // return 2
- },
- expectedRet: 1,
- },
- {
- desc: "Jump when A != X",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 42), // A = 42
- Jump(Jmp|Jeq|X, 0, 1, 2), // if (A == X) jmp nextpc+1 else jmp nextpc+2
- Stmt(Ret|K, 0), // return 0
- Stmt(Ret|K, 1), // return 1
- Stmt(Ret|K, 2), // return 2
- },
- expectedRet: 2,
- },
- {
- desc: "Jump when A > immediate",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 10), // A = 10
- Jump(Jmp|Jgt|K, 9, 1, 2), // if (A > 9) jmp nextpc+1 else jmp nextpc+2
- Stmt(Ret|K, 0), // return 0
- Stmt(Ret|K, 1), // return 1
- Stmt(Ret|K, 2), // return 2
- },
- expectedRet: 1,
- },
- {
- desc: "Jump when A <= immediate",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 10), // A = 10
- Jump(Jmp|Jgt|K, 10, 1, 2), // if (A > 10) jmp nextpc+1 else jmp nextpc+2
- Stmt(Ret|K, 0), // return 0
- Stmt(Ret|K, 1), // return 1
- Stmt(Ret|K, 2), // return 2
- },
- expectedRet: 2,
- },
- {
- desc: "Jump when A > X",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 10), // A = 10
- Stmt(Ldx|Imm|W, 9), // X = 9
- Jump(Jmp|Jgt|X, 0, 1, 2), // if (A > X) jmp nextpc+1 else jmp nextpc+2
- Stmt(Ret|K, 0), // return 0
- Stmt(Ret|K, 1), // return 1
- Stmt(Ret|K, 2), // return 2
- },
- expectedRet: 1,
- },
- {
- desc: "Jump when A <= X",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 10), // A = 10
- Stmt(Ldx|Imm|W, 10), // X = 10
- Jump(Jmp|Jgt|X, 0, 1, 2), // if (A > X) jmp nextpc+1 else jmp nextpc+2
- Stmt(Ret|K, 0), // return 0
- Stmt(Ret|K, 1), // return 1
- Stmt(Ret|K, 2), // return 2
- },
- expectedRet: 2,
- },
- {
- desc: "Jump when A >= immediate",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 10), // A = 10
- Jump(Jmp|Jge|K, 10, 1, 2), // if (A >= 10) jmp nextpc+1 else jmp nextpc+2
- Stmt(Ret|K, 0), // return 0
- Stmt(Ret|K, 1), // return 1
- Stmt(Ret|K, 2), // return 2
- },
- expectedRet: 1,
- },
- {
- desc: "Jump when A < immediate",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 10), // A = 10
- Jump(Jmp|Jge|K, 11, 1, 2), // if (A >= 11) jmp nextpc+1 else jmp nextpc+2
- Stmt(Ret|K, 0), // return 0
- Stmt(Ret|K, 1), // return 1
- Stmt(Ret|K, 2), // return 2
- },
- expectedRet: 2,
- },
- {
- desc: "Jump when A >= X",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 10), // A = 10
- Stmt(Ldx|Imm|W, 10), // X = 10
- Jump(Jmp|Jge|X, 0, 1, 2), // if (A >= X) jmp nextpc+1 else jmp nextpc+2
- Stmt(Ret|K, 0), // return 0
- Stmt(Ret|K, 1), // return 1
- Stmt(Ret|K, 2), // return 2
- },
- expectedRet: 1,
- },
- {
- desc: "Jump when A < X",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 10), // A = 10
- Stmt(Ldx|Imm|W, 11), // X = 11
- Jump(Jmp|Jge|X, 0, 1, 2), // if (A >= X) jmp nextpc+1 else jmp nextpc+2
- Stmt(Ret|K, 0), // return 0
- Stmt(Ret|K, 1), // return 1
- Stmt(Ret|K, 2), // return 2
- },
- expectedRet: 2,
- },
- {
- desc: "Jump when A & immediate != 0",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 0xff), // A = 0xff
- Jump(Jmp|Jset|K, 0x101, 1, 2), // if (A & 0x101) jmp nextpc+1 else jmp nextpc+2
- Stmt(Ret|K, 0), // return 0
- Stmt(Ret|K, 1), // return 1
- Stmt(Ret|K, 2), // return 2
- },
- expectedRet: 1,
- },
- {
- desc: "Jump when A & immediate == 0",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 0xfe), // A = 0xfe
- Jump(Jmp|Jset|K, 0x101, 1, 2), // if (A & 0x101) jmp nextpc+1 else jmp nextpc+2
- Stmt(Ret|K, 0), // return 0
- Stmt(Ret|K, 1), // return 1
- Stmt(Ret|K, 2), // return 2
- },
- expectedRet: 2,
- },
- {
- desc: "Jump when A & X != 0",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 0xff), // A = 0xff
- Stmt(Ldx|Imm|W, 0x101), // X = 0x101
- Jump(Jmp|Jset|X, 0, 1, 2), // if (A & X) jmp nextpc+1 else jmp nextpc+2
- Stmt(Ret|K, 0), // return 0
- Stmt(Ret|K, 1), // return 1
- Stmt(Ret|K, 2), // return 2
- },
- expectedRet: 1,
- },
- {
- desc: "Jump when A & X == 0",
- insns: []linux.BPFInstruction{
- Stmt(Ld|Imm|W, 0xfe), // A = 0xfe
- Stmt(Ldx|Imm|W, 0x101), // X = 0x101
- Jump(Jmp|Jset|X, 0, 1, 2), // if (A & X) jmp nextpc+1 else jmp nextpc+2
- Stmt(Ret|K, 0), // return 0
- Stmt(Ret|K, 1), // return 1
- Stmt(Ret|K, 2), // return 2
- },
- expectedRet: 2,
- },
- } {
- p, err := Compile(test.insns)
- if err != nil {
- t.Errorf("%s: unexpected compilation error: %v", test.desc, err)
- continue
- }
- ret, err := Exec(p, InputBytes{test.input, binary.BigEndian})
- if err != nil {
- t.Errorf("%s: expected return value of %d, got execution error: %v", test.desc, test.expectedRet, err)
- continue
- }
- if ret != test.expectedRet {
- t.Errorf("%s: expected return value of %d, got value %d", test.desc, test.expectedRet, ret)
- }
- }
-}
-
-func TestSimpleFilter(t *testing.T) {
- // Seccomp filter example given in Linux's
- // Documentation/networking/filter.txt, translated to bytecode using the
- // Linux kernel tree's tools/net/bpf_asm.
- filter := []linux.BPFInstruction{
- {0x20, 0, 0, 0x00000004}, // ld [4] /* offsetof(struct seccomp_data, arch) */
- {0x15, 0, 11, 0xc000003e}, // jne #0xc000003e, bad /* AUDIT_ARCH_X86_64 */
- {0x20, 0, 0, 0000000000}, // ld [0] /* offsetof(struct seccomp_data, nr) */
- {0x15, 10, 0, 0x0000000f}, // jeq #15, good /* __NR_rt_sigreturn */
- {0x15, 9, 0, 0x000000e7}, // jeq #231, good /* __NR_exit_group */
- {0x15, 8, 0, 0x0000003c}, // jeq #60, good /* __NR_exit */
- {0x15, 7, 0, 0000000000}, // jeq #0, good /* __NR_read */
- {0x15, 6, 0, 0x00000001}, // jeq #1, good /* __NR_write */
- {0x15, 5, 0, 0x00000005}, // jeq #5, good /* __NR_fstat */
- {0x15, 4, 0, 0x00000009}, // jeq #9, good /* __NR_mmap */
- {0x15, 3, 0, 0x0000000e}, // jeq #14, good /* __NR_rt_sigprocmask */
- {0x15, 2, 0, 0x0000000d}, // jeq #13, good /* __NR_rt_sigaction */
- {0x15, 1, 0, 0x00000023}, // jeq #35, good /* __NR_nanosleep */
- {0x06, 0, 0, 0000000000}, // bad: ret #0 /* SECCOMP_RET_KILL */
- {0x06, 0, 0, 0x7fff0000}, // good: ret #0x7fff0000 /* SECCOMP_RET_ALLOW */
- }
- p, err := Compile(filter)
- if err != nil {
- t.Fatalf("Unexpected compilation error: %v", err)
- }
-
- for _, test := range []struct {
- // desc is the test's description.
- desc string
-
- // seccompData is the input data.
- seccompData
-
- // expectedRet is the expected return value of the BPF program.
- expectedRet uint32
- }{
- {
- desc: "Invalid arch is rejected",
- seccompData: seccompData{nr: 1 /* x86 exit */, arch: 0x40000003 /* AUDIT_ARCH_I386 */},
- expectedRet: 0,
- },
- {
- desc: "Disallowed syscall is rejected",
- seccompData: seccompData{nr: 105 /* __NR_setuid */, arch: 0xc000003e},
- expectedRet: 0,
- },
- {
- desc: "Whitelisted syscall is allowed",
- seccompData: seccompData{nr: 231 /* __NR_exit_group */, arch: 0xc000003e},
- expectedRet: 0x7fff0000,
- },
- } {
- ret, err := Exec(p, test.seccompData.asInput())
- if err != nil {
- t.Errorf("%s: expected return value of %d, got execution error: %v", test.desc, test.expectedRet, err)
- continue
- }
- if ret != test.expectedRet {
- t.Errorf("%s: expected return value of %d, got value %d", test.desc, test.expectedRet, ret)
- }
- }
-}
-
-// seccompData is equivalent to struct seccomp_data.
-type seccompData struct {
- nr uint32
- arch uint32
- instructionPointer uint64
- args [6]uint64
-}
-
-// asInput converts a seccompData to a bpf.Input.
-func (d *seccompData) asInput() Input {
- return InputBytes{binary.Marshal(nil, binary.LittleEndian, d), binary.LittleEndian}
-}
diff --git a/pkg/bpf/program_builder_test.go b/pkg/bpf/program_builder_test.go
deleted file mode 100644
index 92ca5f4c3..000000000
--- a/pkg/bpf/program_builder_test.go
+++ /dev/null
@@ -1,157 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package bpf
-
-import (
- "fmt"
- "testing"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
-)
-
-func validate(p *ProgramBuilder, expected []linux.BPFInstruction) error {
- instructions, err := p.Instructions()
- if err != nil {
- return fmt.Errorf("Instructions() failed: %v", err)
- }
- got, err := DecodeProgram(instructions)
- if err != nil {
- return fmt.Errorf("DecodeProgram('instructions') failed: %v", err)
- }
- expectedDecoded, err := DecodeProgram(expected)
- if err != nil {
- return fmt.Errorf("DecodeProgram('expected') failed: %v", err)
- }
- if got != expectedDecoded {
- return fmt.Errorf("DecodeProgram() failed, expected: %q, got: %q", expectedDecoded, got)
- }
- return nil
-}
-
-func TestProgramBuilderSimple(t *testing.T) {
- p := NewProgramBuilder()
- p.AddStmt(Ld+Abs+W, 10)
- p.AddJump(Jmp+Ja, 10, 0, 0)
-
- expected := []linux.BPFInstruction{
- Stmt(Ld+Abs+W, 10),
- Jump(Jmp+Ja, 10, 0, 0),
- }
-
- if err := validate(p, expected); err != nil {
- t.Errorf("Validate() failed: %v", err)
- }
-}
-
-func TestProgramBuilderLabels(t *testing.T) {
- p := NewProgramBuilder()
- p.AddJumpTrueLabel(Jmp+Jeq+K, 11, "label_1", 0)
- p.AddJumpFalseLabel(Jmp+Jeq+K, 12, 0, "label_2")
- p.AddJumpLabels(Jmp+Jeq+K, 13, "label_3", "label_4")
- if err := p.AddLabel("label_1"); err != nil {
- t.Errorf("AddLabel(label_1) failed: %v", err)
- }
- p.AddStmt(Ld+Abs+W, 1)
- if err := p.AddLabel("label_3"); err != nil {
- t.Errorf("AddLabel(label_3) failed: %v", err)
- }
- p.AddJumpLabels(Jmp+Jeq+K, 14, "label_4", "label_5")
- if err := p.AddLabel("label_2"); err != nil {
- t.Errorf("AddLabel(label_2) failed: %v", err)
- }
- p.AddJumpLabels(Jmp+Jeq+K, 15, "label_4", "label_6")
- if err := p.AddLabel("label_4"); err != nil {
- t.Errorf("AddLabel(label_4) failed: %v", err)
- }
- p.AddStmt(Ld+Abs+W, 4)
- if err := p.AddLabel("label_5"); err != nil {
- t.Errorf("AddLabel(label_5) failed: %v", err)
- }
- if err := p.AddLabel("label_6"); err != nil {
- t.Errorf("AddLabel(label_6) failed: %v", err)
- }
- p.AddStmt(Ld+Abs+W, 5)
-
- expected := []linux.BPFInstruction{
- Jump(Jmp+Jeq+K, 11, 2, 0),
- Jump(Jmp+Jeq+K, 12, 0, 3),
- Jump(Jmp+Jeq+K, 13, 1, 3),
- Stmt(Ld+Abs+W, 1),
- Jump(Jmp+Jeq+K, 14, 1, 2),
- Jump(Jmp+Jeq+K, 15, 0, 1),
- Stmt(Ld+Abs+W, 4),
- Stmt(Ld+Abs+W, 5),
- }
- if err := validate(p, expected); err != nil {
- t.Errorf("Validate() failed: %v", err)
- }
- // Calling validate()=>p.Instructions() again to make sure
- // Instructions can be called multiple times without ruining
- // the program.
- if err := validate(p, expected); err != nil {
- t.Errorf("Validate() failed: %v", err)
- }
-}
-
-func TestProgramBuilderMissingErrorTarget(t *testing.T) {
- p := NewProgramBuilder()
- p.AddJumpTrueLabel(Jmp+Jeq+K, 10, "label_1", 0)
- if _, err := p.Instructions(); err == nil {
- t.Errorf("Instructions() should have failed")
- }
-}
-
-func TestProgramBuilderLabelWithNoInstruction(t *testing.T) {
- p := NewProgramBuilder()
- p.AddJumpTrueLabel(Jmp+Jeq+K, 10, "label_1", 0)
- if err := p.AddLabel("label_1"); err != nil {
- t.Errorf("AddLabel(label_1) failed: %v", err)
- }
- if _, err := p.Instructions(); err == nil {
- t.Errorf("Instructions() should have failed")
- }
-}
-
-func TestProgramBuilderUnusedLabel(t *testing.T) {
- p := NewProgramBuilder()
- if err := p.AddLabel("unused"); err == nil {
- t.Errorf("AddLabel(unused) should have failed")
- }
-}
-
-func TestProgramBuilderLabelAddedTwice(t *testing.T) {
- p := NewProgramBuilder()
- p.AddJumpTrueLabel(Jmp+Jeq+K, 10, "label_1", 0)
- if err := p.AddLabel("label_1"); err != nil {
- t.Errorf("AddLabel(label_1) failed: %v", err)
- }
- p.AddStmt(Ld+Abs+W, 0)
- if err := p.AddLabel("label_1"); err == nil {
- t.Errorf("AddLabel(label_1) failed: %v", err)
- }
-}
-
-func TestProgramBuilderJumpBackwards(t *testing.T) {
- p := NewProgramBuilder()
- p.AddJumpTrueLabel(Jmp+Jeq+K, 10, "label_1", 0)
- if err := p.AddLabel("label_1"); err != nil {
- t.Errorf("AddLabel(label_1) failed: %v", err)
- }
- p.AddStmt(Ld+Abs+W, 0)
- p.AddJumpTrueLabel(Jmp+Jeq+K, 10, "label_1", 0)
- if _, err := p.Instructions(); err == nil {
- t.Errorf("Instructions() should have failed")
- }
-}
diff --git a/pkg/compressio/BUILD b/pkg/compressio/BUILD
deleted file mode 100644
index a0b21d4bd..000000000
--- a/pkg/compressio/BUILD
+++ /dev/null
@@ -1,19 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "compressio",
- srcs = ["compressio.go"],
- importpath = "gvisor.dev/gvisor/pkg/compressio",
- visibility = ["//:sandbox"],
- deps = ["//pkg/binary"],
-)
-
-go_test(
- name = "compressio_test",
- size = "medium",
- srcs = ["compressio_test.go"],
- embed = [":compressio"],
-)
diff --git a/pkg/compressio/compressio_state_autogen.go b/pkg/compressio/compressio_state_autogen.go
new file mode 100755
index 000000000..cac5ea41c
--- /dev/null
+++ b/pkg/compressio/compressio_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package compressio
+
diff --git a/pkg/compressio/compressio_test.go b/pkg/compressio/compressio_test.go
deleted file mode 100644
index 86dc47e44..000000000
--- a/pkg/compressio/compressio_test.go
+++ /dev/null
@@ -1,290 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package compressio
-
-import (
- "bytes"
- "compress/flate"
- "encoding/base64"
- "fmt"
- "io"
- "math/rand"
- "runtime"
- "testing"
- "time"
-)
-
-type harness interface {
- Errorf(format string, v ...interface{})
- Fatalf(format string, v ...interface{})
- Logf(format string, v ...interface{})
-}
-
-func initTest(t harness, size int) []byte {
- // Set number of processes to number of CPUs.
- runtime.GOMAXPROCS(runtime.NumCPU())
-
- // Construct synthetic data. We do this by encoding random data with
- // base64. This gives a high level of entropy, but still quite a bit of
- // structure, to give reasonable compression ratios (~75%).
- var buf bytes.Buffer
- bufW := base64.NewEncoder(base64.RawStdEncoding, &buf)
- bufR := rand.New(rand.NewSource(0))
- if _, err := io.CopyN(bufW, bufR, int64(size)); err != nil {
- t.Fatalf("unable to seed random data: %v", err)
- }
- return buf.Bytes()
-}
-
-type testOpts struct {
- Name string
- Data []byte
- NewWriter func(*bytes.Buffer) (io.Writer, error)
- NewReader func(*bytes.Buffer) (io.Reader, error)
- PreCompress func()
- PostCompress func()
- PreDecompress func()
- PostDecompress func()
- CompressIters int
- DecompressIters int
- CorruptData bool
-}
-
-func doTest(t harness, opts testOpts) {
- // Compress.
- var compressed bytes.Buffer
- compressionStartTime := time.Now()
- if opts.PreCompress != nil {
- opts.PreCompress()
- }
- if opts.CompressIters <= 0 {
- opts.CompressIters = 1
- }
- for i := 0; i < opts.CompressIters; i++ {
- compressed.Reset()
- w, err := opts.NewWriter(&compressed)
- if err != nil {
- t.Errorf("%s: NewWriter got err %v, expected nil", opts.Name, err)
- }
- if _, err := io.Copy(w, bytes.NewBuffer(opts.Data)); err != nil {
- t.Errorf("%s: compress got err %v, expected nil", opts.Name, err)
- return
- }
- closer, ok := w.(io.Closer)
- if ok {
- if err := closer.Close(); err != nil {
- t.Errorf("%s: got err %v, expected nil", opts.Name, err)
- return
- }
- }
- }
- if opts.PostCompress != nil {
- opts.PostCompress()
- }
- compressionTime := time.Since(compressionStartTime)
- compressionRatio := float32(compressed.Len()) / float32(len(opts.Data))
-
- // Decompress.
- var decompressed bytes.Buffer
- decompressionStartTime := time.Now()
- if opts.PreDecompress != nil {
- opts.PreDecompress()
- }
- if opts.DecompressIters <= 0 {
- opts.DecompressIters = 1
- }
- if opts.CorruptData {
- b := compressed.Bytes()
- b[rand.Intn(len(b))]++
- }
- for i := 0; i < opts.DecompressIters; i++ {
- decompressed.Reset()
- r, err := opts.NewReader(bytes.NewBuffer(compressed.Bytes()))
- if err != nil {
- if opts.CorruptData {
- continue
- }
- t.Errorf("%s: NewReader got err %v, expected nil", opts.Name, err)
- return
- }
- if _, err := io.Copy(&decompressed, r); (err != nil) != opts.CorruptData {
- t.Errorf("%s: decompress got err %v unexpectly", opts.Name, err)
- return
- }
- }
- if opts.PostDecompress != nil {
- opts.PostDecompress()
- }
- decompressionTime := time.Since(decompressionStartTime)
-
- if opts.CorruptData {
- return
- }
-
- // Verify.
- if decompressed.Len() != len(opts.Data) {
- t.Errorf("%s: got %d bytes, expected %d", opts.Name, decompressed.Len(), len(opts.Data))
- }
- if !bytes.Equal(opts.Data, decompressed.Bytes()) {
- t.Errorf("%s: got mismatch, expected match", opts.Name)
- if len(opts.Data) < 32 { // Don't flood the logs.
- t.Errorf("got %v, expected %v", decompressed.Bytes(), opts.Data)
- }
- }
-
- t.Logf("%s: compression time %v, ratio %2.2f, decompression time %v",
- opts.Name, compressionTime, compressionRatio, decompressionTime)
-}
-
-var hashKey = []byte("01234567890123456789012345678901")
-
-func TestCompress(t *testing.T) {
- rand.Seed(time.Now().Unix())
-
- var (
- data = initTest(t, 10*1024*1024)
- data0 = data[:0]
- data1 = data[:1]
- data2 = data[:11]
- data3 = data[:16]
- data4 = data[:]
- )
-
- for _, data := range [][]byte{data0, data1, data2, data3, data4} {
- for _, blockSize := range []uint32{1, 4, 1024, 4 * 1024, 16 * 1024} {
- // Skip annoying tests; they just take too long.
- if blockSize <= 16 && len(data) > 16 {
- continue
- }
-
- for _, key := range [][]byte{nil, hashKey} {
- for _, corruptData := range []bool{false, true} {
- if key == nil && corruptData {
- // No need to test corrupt data
- // case when not doing hashing.
- continue
- }
- // Do the compress test.
- doTest(t, testOpts{
- Name: fmt.Sprintf("len(data)=%d, blockSize=%d, key=%s, corruptData=%v", len(data), blockSize, string(key), corruptData),
- Data: data,
- NewWriter: func(b *bytes.Buffer) (io.Writer, error) {
- return NewWriter(b, key, blockSize, flate.BestSpeed)
- },
- NewReader: func(b *bytes.Buffer) (io.Reader, error) {
- return NewReader(b, key)
- },
- CorruptData: corruptData,
- })
- }
- }
- }
-
- // Do the vanilla test.
- doTest(t, testOpts{
- Name: fmt.Sprintf("len(data)=%d, vanilla flate", len(data)),
- Data: data,
- NewWriter: func(b *bytes.Buffer) (io.Writer, error) {
- return flate.NewWriter(b, flate.BestSpeed)
- },
- NewReader: func(b *bytes.Buffer) (io.Reader, error) {
- return flate.NewReader(b), nil
- },
- })
- }
-}
-
-const (
- benchDataSize = 600 * 1024 * 1024
-)
-
-func benchmark(b *testing.B, compress bool, hash bool, blockSize uint32) {
- b.StopTimer()
- b.SetBytes(benchDataSize)
- data := initTest(b, benchDataSize)
- compIters := b.N
- decompIters := b.N
- if compress {
- decompIters = 0
- } else {
- compIters = 0
- }
- key := hashKey
- if !hash {
- key = nil
- }
- doTest(b, testOpts{
- Name: fmt.Sprintf("compress=%t, hash=%t, len(data)=%d, blockSize=%d", compress, hash, len(data), blockSize),
- Data: data,
- PreCompress: b.StartTimer,
- PostCompress: b.StopTimer,
- NewWriter: func(b *bytes.Buffer) (io.Writer, error) {
- return NewWriter(b, key, blockSize, flate.BestSpeed)
- },
- NewReader: func(b *bytes.Buffer) (io.Reader, error) {
- return NewReader(b, key)
- },
- CompressIters: compIters,
- DecompressIters: decompIters,
- })
-}
-
-func BenchmarkCompressNoHash64K(b *testing.B) {
- benchmark(b, true, false, 64*1024)
-}
-
-func BenchmarkCompressHash64K(b *testing.B) {
- benchmark(b, true, true, 64*1024)
-}
-
-func BenchmarkDecompressNoHash64K(b *testing.B) {
- benchmark(b, false, false, 64*1024)
-}
-
-func BenchmarkDecompressHash64K(b *testing.B) {
- benchmark(b, false, true, 64*1024)
-}
-
-func BenchmarkCompressNoHash1M(b *testing.B) {
- benchmark(b, true, false, 1024*1024)
-}
-
-func BenchmarkCompressHash1M(b *testing.B) {
- benchmark(b, true, true, 1024*1024)
-}
-
-func BenchmarkDecompressNoHash1M(b *testing.B) {
- benchmark(b, false, false, 1024*1024)
-}
-
-func BenchmarkDecompressHash1M(b *testing.B) {
- benchmark(b, false, true, 1024*1024)
-}
-
-func BenchmarkCompressNoHash16M(b *testing.B) {
- benchmark(b, true, false, 16*1024*1024)
-}
-
-func BenchmarkCompressHash16M(b *testing.B) {
- benchmark(b, true, true, 16*1024*1024)
-}
-
-func BenchmarkDecompressNoHash16M(b *testing.B) {
- benchmark(b, false, false, 16*1024*1024)
-}
-
-func BenchmarkDecompressHash16M(b *testing.B) {
- benchmark(b, false, true, 16*1024*1024)
-}
diff --git a/pkg/control/client/BUILD b/pkg/control/client/BUILD
deleted file mode 100644
index 066d7b1a1..000000000
--- a/pkg/control/client/BUILD
+++ /dev/null
@@ -1,16 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "client",
- srcs = [
- "client.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/control/client",
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/unet",
- "//pkg/urpc",
- ],
-)
diff --git a/pkg/control/client/client_state_autogen.go b/pkg/control/client/client_state_autogen.go
new file mode 100755
index 000000000..69ea753a9
--- /dev/null
+++ b/pkg/control/client/client_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package client
+
diff --git a/pkg/control/server/BUILD b/pkg/control/server/BUILD
deleted file mode 100644
index 21adf3adf..000000000
--- a/pkg/control/server/BUILD
+++ /dev/null
@@ -1,15 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "server",
- srcs = ["server.go"],
- importpath = "gvisor.dev/gvisor/pkg/control/server",
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/log",
- "//pkg/unet",
- "//pkg/urpc",
- ],
-)
diff --git a/pkg/control/server/server_state_autogen.go b/pkg/control/server/server_state_autogen.go
new file mode 100755
index 000000000..f2b4725d3
--- /dev/null
+++ b/pkg/control/server/server_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package server
+
diff --git a/pkg/cpuid/BUILD b/pkg/cpuid/BUILD
deleted file mode 100644
index 32422f9e2..000000000
--- a/pkg/cpuid/BUILD
+++ /dev/null
@@ -1,33 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "cpuid",
- srcs = [
- "cpu_amd64.s",
- "cpuid.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/cpuid",
- visibility = ["//:sandbox"],
- deps = ["//pkg/log"],
-)
-
-go_test(
- name = "cpuid_test",
- size = "small",
- srcs = ["cpuid_test.go"],
- embed = [":cpuid"],
-)
-
-go_test(
- name = "cpuid_parse_test",
- size = "small",
- srcs = [
- "cpuid_parse_test.go",
- ],
- embed = [":cpuid"],
- tags = ["manual"],
-)
diff --git a/pkg/cpuid/cpuid_parse_test.go b/pkg/cpuid/cpuid_parse_test.go
deleted file mode 100644
index dd9969db4..000000000
--- a/pkg/cpuid/cpuid_parse_test.go
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package cpuid
-
-import (
- "fmt"
- "io/ioutil"
- "regexp"
- "strconv"
- "strings"
- "syscall"
- "testing"
-)
-
-func kernelVersion() (int, int, error) {
- var u syscall.Utsname
- if err := syscall.Uname(&u); err != nil {
- return 0, 0, err
- }
-
- var r string
- for _, b := range u.Release {
- if b == 0 {
- break
- }
- r += string(b)
- }
-
- s := strings.Split(r, ".")
- if len(s) < 2 {
- return 0, 0, fmt.Errorf("kernel release missing major and minor component: %s", r)
- }
-
- major, err := strconv.Atoi(s[0])
- if err != nil {
- return 0, 0, fmt.Errorf("error parsing major version %q in %q: %v", s[0], r, err)
- }
-
- minor, err := strconv.Atoi(s[1])
- if err != nil {
- return 0, 0, fmt.Errorf("error parsing minor version %q in %q: %v", s[1], r, err)
- }
-
- return major, minor, nil
-}
-
-// TestHostFeatureFlags tests that all features detected by HostFeatureSet are
-// on the host.
-//
-// It does *not* verify that all features reported by the host are detected by
-// HostFeatureSet.
-//
-// i.e., test that HostFeatureSet is a subset of the host features.
-func TestHostFeatureFlags(t *testing.T) {
- cpuinfoBytes, _ := ioutil.ReadFile("/proc/cpuinfo")
- cpuinfo := string(cpuinfoBytes)
- t.Logf("Host cpu info:\n%s", cpuinfo)
-
- major, minor, err := kernelVersion()
- if err != nil {
- t.Fatalf("Unable to parse kernel version: %v", err)
- }
-
- re := regexp.MustCompile(`(?m)^flags\s+: (.*)$`)
- m := re.FindStringSubmatch(cpuinfo)
- if len(m) != 2 {
- t.Fatalf("Unable to extract flags from %q", cpuinfo)
- }
-
- cpuinfoFlags := make(map[string]struct{})
- for _, f := range strings.Split(m[1], " ") {
- cpuinfoFlags[f] = struct{}{}
- }
-
- fs := HostFeatureSet()
-
- // All features have a string and appear in host cpuinfo.
- for f := range fs.Set {
- name := f.flagString(false)
- if name == "" {
- t.Errorf("Non-parsable feature: %v", f)
- }
-
- // Special cases not consistently visible. We don't mind if
- // they are exposed in earlier versions.
- switch {
- // Block 0.
- case f == X86FeatureSDBG && (major < 4 || major == 4 && minor < 3):
- // SDBG only exposed in
- // b1c599b8ff80ea79b9f8277a3f9f36a7b0cfedce (4.3).
- continue
- // Block 2.
- case f == X86FeatureRDT && (major < 4 || major == 4 && minor < 10):
- // RDT only exposed in
- // 4ab1586488cb56ed8728e54c4157cc38646874d9 (4.10).
- continue
- // Block 3.
- case f == X86FeatureAVX512VBMI && (major < 4 || major == 4 && minor < 10):
- // AVX512VBMI only exposed in
- // a8d9df5a509a232a959e4ef2e281f7ecd77810d6 (4.10).
- continue
- case f == X86FeatureUMIP && (major < 4 || major == 4 && minor < 15):
- // UMIP only exposed in
- // 3522c2a6a4f341058b8291326a945e2a2d2aaf55 (4.15).
- continue
- case f == X86FeaturePKU && (major < 4 || major == 4 && minor < 9):
- // PKU only exposed in
- // dfb4a70f20c5b3880da56ee4c9484bdb4e8f1e65 (4.9).
- continue
- // Block 4.
- case f == X86FeatureXSAVES && (major < 4 || major == 4 && minor < 8):
- // XSAVES only exposed in
- // b8be15d588060a03569ac85dc4a0247460988f5b (4.8).
- continue
- // Block 5.
- case f == X86FeaturePERFCTR_LLC && (major < 4 || major == 4 && minor < 14):
- // PERFCTR_LLC renamed in
- // 910448bbed066ab1082b510eef1ae61bb792d854 (4.14).
- continue
- }
-
- hidden := f.flagString(true) == ""
- _, ok := cpuinfoFlags[name]
- if hidden && ok {
- t.Errorf("Unexpectedly hidden flag: %v", f)
- } else if !hidden && !ok {
- t.Errorf("Non-native flag: %v", f)
- }
- }
-}
diff --git a/pkg/cpuid/cpuid_state_autogen.go b/pkg/cpuid/cpuid_state_autogen.go
new file mode 100755
index 000000000..efedfde0f
--- /dev/null
+++ b/pkg/cpuid/cpuid_state_autogen.go
@@ -0,0 +1,68 @@
+// automatically generated by stateify.
+
+package cpuid
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *Cache) beforeSave() {}
+func (x *Cache) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Level", &x.Level)
+ m.Save("Type", &x.Type)
+ m.Save("FullyAssociative", &x.FullyAssociative)
+ m.Save("Partitions", &x.Partitions)
+ m.Save("Ways", &x.Ways)
+ m.Save("Sets", &x.Sets)
+ m.Save("InvalidateHierarchical", &x.InvalidateHierarchical)
+ m.Save("Inclusive", &x.Inclusive)
+ m.Save("DirectMapped", &x.DirectMapped)
+}
+
+func (x *Cache) afterLoad() {}
+func (x *Cache) load(m state.Map) {
+ m.Load("Level", &x.Level)
+ m.Load("Type", &x.Type)
+ m.Load("FullyAssociative", &x.FullyAssociative)
+ m.Load("Partitions", &x.Partitions)
+ m.Load("Ways", &x.Ways)
+ m.Load("Sets", &x.Sets)
+ m.Load("InvalidateHierarchical", &x.InvalidateHierarchical)
+ m.Load("Inclusive", &x.Inclusive)
+ m.Load("DirectMapped", &x.DirectMapped)
+}
+
+func (x *FeatureSet) beforeSave() {}
+func (x *FeatureSet) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Set", &x.Set)
+ m.Save("VendorID", &x.VendorID)
+ m.Save("ExtendedFamily", &x.ExtendedFamily)
+ m.Save("ExtendedModel", &x.ExtendedModel)
+ m.Save("ProcessorType", &x.ProcessorType)
+ m.Save("Family", &x.Family)
+ m.Save("Model", &x.Model)
+ m.Save("SteppingID", &x.SteppingID)
+ m.Save("Caches", &x.Caches)
+ m.Save("CacheLine", &x.CacheLine)
+}
+
+func (x *FeatureSet) afterLoad() {}
+func (x *FeatureSet) load(m state.Map) {
+ m.Load("Set", &x.Set)
+ m.Load("VendorID", &x.VendorID)
+ m.Load("ExtendedFamily", &x.ExtendedFamily)
+ m.Load("ExtendedModel", &x.ExtendedModel)
+ m.Load("ProcessorType", &x.ProcessorType)
+ m.Load("Family", &x.Family)
+ m.Load("Model", &x.Model)
+ m.Load("SteppingID", &x.SteppingID)
+ m.Load("Caches", &x.Caches)
+ m.Load("CacheLine", &x.CacheLine)
+}
+
+func init() {
+ state.Register("cpuid.Cache", (*Cache)(nil), state.Fns{Save: (*Cache).save, Load: (*Cache).load})
+ state.Register("cpuid.FeatureSet", (*FeatureSet)(nil), state.Fns{Save: (*FeatureSet).save, Load: (*FeatureSet).load})
+}
diff --git a/pkg/cpuid/cpuid_test.go b/pkg/cpuid/cpuid_test.go
deleted file mode 100644
index a707ebb55..000000000
--- a/pkg/cpuid/cpuid_test.go
+++ /dev/null
@@ -1,241 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package cpuid
-
-import (
- "testing"
-)
-
-// These are the default values of various FeatureSet fields.
-const (
- defaultVendorID = "GenuineIntel"
-
- // These processor signature defaults are derived from the values
- // listed in Intel Application Note 485 for i7/Xeon processors.
- defaultExtFamily uint8 = 0
- defaultExtModel uint8 = 1
- defaultType uint8 = 0
- defaultFamily uint8 = 0x06
- defaultModel uint8 = 0x0a
- defaultSteppingID uint8 = 0
-)
-
-// newEmptyFeatureSet creates a new FeatureSet with a sensible default model and no features.
-func newEmptyFeatureSet() *FeatureSet {
- return &FeatureSet{
- Set: make(map[Feature]bool),
- VendorID: defaultVendorID,
- ExtendedFamily: defaultExtFamily,
- ExtendedModel: defaultExtModel,
- ProcessorType: defaultType,
- Family: defaultFamily,
- Model: defaultModel,
- SteppingID: defaultSteppingID,
- }
-}
-
-var justFPU = &FeatureSet{
- Set: map[Feature]bool{
- X86FeatureFPU: true,
- }}
-
-var justFPUandPAE = &FeatureSet{
- Set: map[Feature]bool{
- X86FeatureFPU: true,
- X86FeaturePAE: true,
- }}
-
-func TestSubtract(t *testing.T) {
- if diff := justFPU.Subtract(justFPUandPAE); diff != nil {
- t.Errorf("Got %v is not subset of %v, want diff (%v) to be nil", justFPU, justFPUandPAE, diff)
- }
-
- if justFPUandPAE.Subtract(justFPU) == nil {
- t.Errorf("Got %v is a subset of %v, want diff to be nil", justFPU, justFPUandPAE)
- }
-}
-
-// TODO(b/73346484): Run this test on a very old platform, and make sure more
-// bits are enabled than just FPU and PAE. This test currently may not detect
-// if HostFeatureSet gives back junk bits.
-func TestHostFeatureSet(t *testing.T) {
- hostFeatures := HostFeatureSet()
- if justFPUandPAE.Subtract(hostFeatures) != nil {
- t.Errorf("Got invalid feature set %v from HostFeatureSet()", hostFeatures)
- }
-}
-
-func TestHasFeature(t *testing.T) {
- if !justFPU.HasFeature(X86FeatureFPU) {
- t.Errorf("HasFeature failed, %v should contain %v", justFPU, X86FeatureFPU)
- }
-
- if justFPU.HasFeature(X86FeatureAVX) {
- t.Errorf("HasFeature failed, %v should not contain %v", justFPU, X86FeatureAVX)
- }
-}
-
-// Note: these tests are aware of and abuse internal details of FeatureSets.
-// Users of FeatureSets should not depend on this.
-func TestAdd(t *testing.T) {
- // Test a basic insertion into the FeatureSet.
- testFeatures := newEmptyFeatureSet()
- testFeatures.Add(X86FeatureCLFSH)
- if len(testFeatures.Set) != 1 {
- t.Errorf("Got length %v want 1", len(testFeatures.Set))
- }
-
- if !testFeatures.HasFeature(X86FeatureCLFSH) {
- t.Errorf("Add failed, got %v want set with %v", testFeatures, X86FeatureCLFSH)
- }
-
- // Test that duplicates are ignored.
- testFeatures.Add(X86FeatureCLFSH)
- if len(testFeatures.Set) != 1 {
- t.Errorf("Got length %v, want 1", len(testFeatures.Set))
- }
-}
-
-func TestRemove(t *testing.T) {
- // Try removing the last feature.
- testFeatures := newEmptyFeatureSet()
- testFeatures.Add(X86FeatureFPU)
- testFeatures.Add(X86FeaturePAE)
- testFeatures.Remove(X86FeaturePAE)
- if !testFeatures.HasFeature(X86FeatureFPU) || len(testFeatures.Set) != 1 || testFeatures.HasFeature(X86FeaturePAE) {
- t.Errorf("Remove failed, got %v want %v", testFeatures, justFPU)
- }
-
- // Try removing a feature not in the set.
- testFeatures.Remove(X86FeatureRDRAND)
- if !testFeatures.HasFeature(X86FeatureFPU) || len(testFeatures.Set) != 1 {
- t.Errorf("Remove failed, got %v want %v", testFeatures, justFPU)
- }
-}
-
-func TestFeatureFromString(t *testing.T) {
- f, ok := FeatureFromString("avx")
- if f != X86FeatureAVX || !ok {
- t.Errorf("got %v want avx", f)
- }
-
- f, ok = FeatureFromString("bad")
- if ok {
- t.Errorf("got %v want nothing", f)
- }
-}
-
-// This tests function 0 (eax=0), which returns the vendor ID and highest cpuid
-// function reported to be available.
-func TestEmulateIDVendorAndLength(t *testing.T) {
- testFeatures := newEmptyFeatureSet()
-
- ax, bx, cx, dx := testFeatures.EmulateID(0, 0)
- wantEax := uint32(0xd) // Highest supported cpuid function.
-
- // These magical constants are the characters of "GenuineIntel".
- // See Intel AN485 for a reference on why they are laid out like this.
- wantEbx := uint32(0x756e6547)
- wantEcx := uint32(0x6c65746e)
- wantEdx := uint32(0x49656e69)
- if wantEax != ax {
- t.Errorf("highest function failed, got %x want %x", ax, wantEax)
- }
-
- if wantEbx != bx || wantEcx != cx || wantEdx != dx {
- t.Errorf("vendor string emulation failed, bx:cx:dx, got %x:%x:%x want %x:%x:%x", bx, cx, dx, wantEbx, wantEcx, wantEdx)
- }
-}
-
-func TestEmulateIDBasicFeatures(t *testing.T) {
- // Make a minimal test feature set.
- testFeatures := newEmptyFeatureSet()
- testFeatures.Add(X86FeatureCLFSH)
- testFeatures.Add(X86FeatureAVX)
- testFeatures.CacheLine = 64
-
- ax, bx, cx, dx := testFeatures.EmulateID(1, 0)
- ECXAVXBit := uint32(1 << uint(X86FeatureAVX))
- EDXCLFlushBit := uint32(1 << uint(X86FeatureCLFSH-32)) // We adjust by 32 since it's in block 1.
-
- if EDXCLFlushBit&dx == 0 || dx&^EDXCLFlushBit != 0 {
- t.Errorf("EmulateID failed, got feature bits %x want %x", dx, testFeatures.blockMask(1))
- }
-
- if ECXAVXBit&cx == 0 || cx&^ECXAVXBit != 0 {
- t.Errorf("EmulateID failed, got feature bits %x want %x", cx, testFeatures.blockMask(0))
- }
-
- // Default signature bits, based on values for i7/Xeon.
- // See Intel AN485 for information on stepping/model bits.
- defaultSignature := uint32(0x000106a0)
- if defaultSignature != ax {
- t.Errorf("EmulateID stepping emulation failed, got %x want %x", ax, defaultSignature)
- }
-
- clflushSizeInfo := uint32(8 << 8)
- if clflushSizeInfo != bx {
- t.Errorf("EmulateID bx emulation failed, got %x want %x", bx, clflushSizeInfo)
- }
-}
-
-func TestEmulateIDExtendedFeatures(t *testing.T) {
- // Make a minimal test feature set, one bit in each extended feature word.
- testFeatures := newEmptyFeatureSet()
- testFeatures.Add(X86FeatureSMEP)
- testFeatures.Add(X86FeatureAVX512VBMI)
-
- ax, bx, cx, dx := testFeatures.EmulateID(7, 0)
- EBXSMEPBit := uint32(1 << uint(X86FeatureSMEP-2*32)) // Adjust by 2*32 since SMEP is a block 2 feature.
- ECXAVXBit := uint32(1 << uint(X86FeatureAVX512VBMI-3*32)) // We adjust by 3*32 since it's a block 3 feature.
-
- // Test that the desired bit is set and no other bits are set.
- if EBXSMEPBit&bx == 0 || bx&^EBXSMEPBit != 0 {
- t.Errorf("extended feature emulation failed, got feature bits %x want %x", bx, testFeatures.blockMask(2))
- }
-
- if ECXAVXBit&cx == 0 || cx&^ECXAVXBit != 0 {
- t.Errorf("extended feature emulation failed, got feature bits %x want %x", cx, testFeatures.blockMask(3))
- }
-
- if ax != 0 || dx != 0 {
- t.Errorf("extended feature emulation failed, ax:dx, got %x:%x want 0:0", ax, dx)
- }
-
- // Check that no subleaves other than 0 do anything.
- ax, bx, cx, dx = testFeatures.EmulateID(7, 1)
- if ax != 0 || bx != 0 || cx != 0 || dx != 0 {
- t.Errorf("extended feature emulation failed, got %x:%x:%x:%x want 0:0", ax, bx, cx, dx)
- }
-
-}
-
-// Checks that the expected extended features are available via cpuid functions
-// 0x80000000 and up.
-func TestEmulateIDExtended(t *testing.T) {
- testFeatures := newEmptyFeatureSet()
- testFeatures.Add(X86FeatureSYSCALL)
- EDXSYSCALLBit := uint32(1 << uint(X86FeatureSYSCALL-6*32)) // Adjust by 6*32 since SYSCALL is a block 6 feature.
-
- ax, bx, cx, dx := testFeatures.EmulateID(0x80000000, 0)
- if ax != 0x80000001 || bx != 0 || cx != 0 || dx != 0 {
- t.Errorf("EmulateID extended emulation failed, ax:bx:cx:dx, got %x:%x:%x:%x want 0x80000001:0:0:0", ax, bx, cx, dx)
- }
-
- _, _, _, dx = testFeatures.EmulateID(0x80000001, 0)
- if EDXSYSCALLBit&dx == 0 || dx&^EDXSYSCALLBit != 0 {
- t.Errorf("extended feature emulation failed, got feature bits %x want %x", dx, testFeatures.blockMask(6))
- }
-}
diff --git a/pkg/eventchannel/BUILD b/pkg/eventchannel/BUILD
deleted file mode 100644
index 71f2abc83..000000000
--- a/pkg/eventchannel/BUILD
+++ /dev/null
@@ -1,44 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "eventchannel",
- srcs = [
- "event.go",
- "rate.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/eventchannel",
- visibility = ["//:sandbox"],
- deps = [
- ":eventchannel_go_proto",
- "//pkg/log",
- "//pkg/unet",
- "@com_github_golang_protobuf//proto:go_default_library",
- "@com_github_golang_protobuf//ptypes:go_default_library_gen",
- "@org_golang_x_time//rate:go_default_library",
- ],
-)
-
-proto_library(
- name = "eventchannel_proto",
- srcs = ["event.proto"],
-)
-
-go_proto_library(
- name = "eventchannel_go_proto",
- importpath = "gvisor.dev/gvisor/pkg/eventchannel/eventchannel_go_proto",
- proto = ":eventchannel_proto",
- visibility = ["//:sandbox"],
-)
-
-go_test(
- name = "eventchannel_test",
- srcs = ["event_test.go"],
- embed = [":eventchannel"],
- deps = [
- "@com_github_golang_protobuf//proto:go_default_library",
- ],
-)
diff --git a/pkg/eventchannel/event.proto b/pkg/eventchannel/event.proto
deleted file mode 100644
index 34468f072..000000000
--- a/pkg/eventchannel/event.proto
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto3";
-
-package gvisor;
-
-// A debug event encapsulates any other event protobuf in text format. This is
-// useful because clients reading events emitted this way do not need to link
-// the event protobufs to display them in a human-readable format.
-message DebugEvent {
- // Name of the inner message.
- string name = 1;
- // Text representation of the inner message content.
- string text = 2;
-}
diff --git a/pkg/eventchannel/event_test.go b/pkg/eventchannel/event_test.go
deleted file mode 100644
index 3649097d6..000000000
--- a/pkg/eventchannel/event_test.go
+++ /dev/null
@@ -1,146 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package eventchannel
-
-import (
- "fmt"
- "sync"
- "testing"
- "time"
-
- "github.com/golang/protobuf/proto"
-)
-
-// testEmitter is an emitter that can be used in tests. It records all events
-// emitted, and whether it has been closed.
-type testEmitter struct {
- // mu protects all fields below.
- mu sync.Mutex
-
- // events contains all emitted events.
- events []proto.Message
-
- // closed records whether Close() was called.
- closed bool
-}
-
-// Emit implements Emitter.Emit.
-func (te *testEmitter) Emit(msg proto.Message) (bool, error) {
- te.mu.Lock()
- defer te.mu.Unlock()
- te.events = append(te.events, msg)
- return false, nil
-}
-
-// Close implements Emitter.Close.
-func (te *testEmitter) Close() error {
- te.mu.Lock()
- defer te.mu.Unlock()
- if te.closed {
- return fmt.Errorf("closed called twice")
- }
- te.closed = true
- return nil
-}
-
-// testMessage implements proto.Message for testing.
-type testMessage struct {
- proto.Message
-
- // name is the name of the message, used by tests to compare messages.
- name string
-}
-
-func TestMultiEmitter(t *testing.T) {
- // Create three testEmitters, tied together in a multiEmitter.
- me := &multiEmitter{}
- var emitters []*testEmitter
- for i := 0; i < 3; i++ {
- te := &testEmitter{}
- emitters = append(emitters, te)
- me.AddEmitter(te)
- }
-
- // Emit three messages to multiEmitter.
- names := []string{"foo", "bar", "baz"}
- for _, name := range names {
- m := testMessage{name: name}
- if _, err := me.Emit(m); err != nil {
- t.Fatal("me.Emit(%v) failed: %v", m, err)
- }
- }
-
- // All three emitters should have all three events.
- for _, te := range emitters {
- if got, want := len(te.events), len(names); got != want {
- t.Fatalf("emitter got %d events, want %d", got, want)
- }
- for i, name := range names {
- if got := te.events[i].(testMessage).name; got != name {
- t.Errorf("emitter got message with name %q, want %q", got, name)
- }
- }
- }
-
- // Close multiEmitter.
- if err := me.Close(); err != nil {
- t.Fatal("me.Close() failed: %v", err)
- }
-
- // All testEmitters should be closed.
- for _, te := range emitters {
- if !te.closed {
- t.Errorf("te.closed got false, want true")
- }
- }
-}
-
-func TestRateLimitedEmitter(t *testing.T) {
- // Create a RateLimittedEmitter that wraps a testEmitter.
- te := &testEmitter{}
- max := float64(5) // events per second
- burst := 10 // events
- rle := RateLimitedEmitterFrom(te, max, burst)
-
- // Send 50 messages in one shot.
- for i := 0; i < 50; i++ {
- if _, err := rle.Emit(testMessage{}); err != nil {
- t.Fatalf("rle.Emit failed: %v", err)
- }
- }
-
- // We should have received only 10 messages.
- if got, want := len(te.events), 10; got != want {
- t.Errorf("got %d events, want %d", got, want)
- }
-
- // Sleep for a second and then send another 50.
- time.Sleep(1 * time.Second)
- for i := 0; i < 50; i++ {
- if _, err := rle.Emit(testMessage{}); err != nil {
- t.Fatalf("rle.Emit failed: %v", err)
- }
- }
-
- // We should have at least 5 more message, plus maybe a few more if the
- // test ran slowly.
- got, wantAtLeast, wantAtMost := len(te.events), 15, 20
- if got < wantAtLeast {
- t.Errorf("got %d events, want at least %d", got, wantAtLeast)
- }
- if got > wantAtMost {
- t.Errorf("got %d events, want at most %d", got, wantAtMost)
- }
-}
diff --git a/pkg/eventchannel/eventchannel_go_proto/event.pb.go b/pkg/eventchannel/eventchannel_go_proto/event.pb.go
new file mode 100755
index 000000000..bb71ed3e6
--- /dev/null
+++ b/pkg/eventchannel/eventchannel_go_proto/event.pb.go
@@ -0,0 +1,85 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: pkg/eventchannel/event.proto
+
+package gvisor
+
+import (
+ fmt "fmt"
+ proto "github.com/golang/protobuf/proto"
+ math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+type DebugEvent struct {
+ Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+ Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *DebugEvent) Reset() { *m = DebugEvent{} }
+func (m *DebugEvent) String() string { return proto.CompactTextString(m) }
+func (*DebugEvent) ProtoMessage() {}
+func (*DebugEvent) Descriptor() ([]byte, []int) {
+ return fileDescriptor_fcfbd51abd9de962, []int{0}
+}
+
+func (m *DebugEvent) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_DebugEvent.Unmarshal(m, b)
+}
+func (m *DebugEvent) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_DebugEvent.Marshal(b, m, deterministic)
+}
+func (m *DebugEvent) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_DebugEvent.Merge(m, src)
+}
+func (m *DebugEvent) XXX_Size() int {
+ return xxx_messageInfo_DebugEvent.Size(m)
+}
+func (m *DebugEvent) XXX_DiscardUnknown() {
+ xxx_messageInfo_DebugEvent.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_DebugEvent proto.InternalMessageInfo
+
+func (m *DebugEvent) GetName() string {
+ if m != nil {
+ return m.Name
+ }
+ return ""
+}
+
+func (m *DebugEvent) GetText() string {
+ if m != nil {
+ return m.Text
+ }
+ return ""
+}
+
+func init() {
+ proto.RegisterType((*DebugEvent)(nil), "gvisor.DebugEvent")
+}
+
+func init() { proto.RegisterFile("pkg/eventchannel/event.proto", fileDescriptor_fcfbd51abd9de962) }
+
+var fileDescriptor_fcfbd51abd9de962 = []byte{
+ // 103 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x29, 0xc8, 0x4e, 0xd7,
+ 0x4f, 0x2d, 0x4b, 0xcd, 0x2b, 0x49, 0xce, 0x48, 0xcc, 0xcb, 0x4b, 0xcd, 0x81, 0x70, 0xf4, 0x0a,
+ 0x8a, 0xf2, 0x4b, 0xf2, 0x85, 0xd8, 0xd2, 0xcb, 0x32, 0x8b, 0xf3, 0x8b, 0x94, 0x4c, 0xb8, 0xb8,
+ 0x5c, 0x52, 0x93, 0x4a, 0xd3, 0x5d, 0x41, 0x72, 0x42, 0x42, 0x5c, 0x2c, 0x79, 0x89, 0xb9, 0xa9,
+ 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x60, 0x36, 0x48, 0xac, 0x24, 0xb5, 0xa2, 0x44, 0x82,
+ 0x09, 0x22, 0x06, 0x62, 0x27, 0xb1, 0x81, 0x0d, 0x31, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x17,
+ 0xee, 0x7f, 0xef, 0x64, 0x00, 0x00, 0x00,
+}
diff --git a/pkg/eventchannel/eventchannel_state_autogen.go b/pkg/eventchannel/eventchannel_state_autogen.go
new file mode 100755
index 000000000..cfd3a5e43
--- /dev/null
+++ b/pkg/eventchannel/eventchannel_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package eventchannel
+
diff --git a/pkg/fd/BUILD b/pkg/fd/BUILD
deleted file mode 100644
index afa8f7659..000000000
--- a/pkg/fd/BUILD
+++ /dev/null
@@ -1,18 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "fd",
- srcs = ["fd.go"],
- importpath = "gvisor.dev/gvisor/pkg/fd",
- visibility = ["//visibility:public"],
-)
-
-go_test(
- name = "fd_test",
- size = "small",
- srcs = ["fd_test.go"],
- embed = [":fd"],
-)
diff --git a/pkg/fd/fd_state_autogen.go b/pkg/fd/fd_state_autogen.go
new file mode 100755
index 000000000..0320140b0
--- /dev/null
+++ b/pkg/fd/fd_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package fd
+
diff --git a/pkg/fd/fd_test.go b/pkg/fd/fd_test.go
deleted file mode 100644
index 5fb0ad47d..000000000
--- a/pkg/fd/fd_test.go
+++ /dev/null
@@ -1,136 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fd
-
-import (
- "math"
- "os"
- "syscall"
- "testing"
-)
-
-func TestSetNegOne(t *testing.T) {
- type entry struct {
- name string
- file *FD
- fn func() error
- }
- var tests []entry
-
- fd, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
- if err != nil {
- t.Fatal("syscall.Socket:", err)
- }
- f1 := New(fd)
- tests = append(tests, entry{
- "Release",
- f1,
- func() error {
- return syscall.Close(f1.Release())
- },
- })
-
- fd, err = syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
- if err != nil {
- t.Fatal("syscall.Socket:", err)
- }
- f2 := New(fd)
- tests = append(tests, entry{
- "Close",
- f2,
- f2.Close,
- })
-
- for _, test := range tests {
- if err := test.fn(); err != nil {
- t.Errorf("%s: %v", test.name, err)
- continue
- }
- if fd := test.file.FD(); fd != -1 {
- t.Errorf("%s: got FD() = %d, want = -1", test.name, fd)
- }
- }
-}
-
-func TestStartsNegOne(t *testing.T) {
- type entry struct {
- name string
- file *FD
- }
-
- tests := []entry{
- {"-1", New(-1)},
- {"-2", New(-2)},
- {"MinInt32", New(math.MinInt32)},
- {"MinInt64", New(math.MinInt64)},
- }
-
- for _, test := range tests {
- if fd := test.file.FD(); fd != -1 {
- t.Errorf("%s: got FD() = %d, want = -1", test.name, fd)
- }
- }
-}
-
-func TestFileDotFile(t *testing.T) {
- fd, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
- if err != nil {
- t.Fatal("syscall.Socket:", err)
- }
-
- f := New(fd)
- of, err := f.File()
- if err != nil {
- t.Fatalf("File got err %v want nil", err)
- }
-
- if ofd, nfd := int(of.Fd()), f.FD(); ofd == nfd || ofd == -1 {
- // Try not to double close the FD.
- f.Release()
-
- t.Fatalf("got %#v.File().Fd() = %d, want new FD", f, ofd)
- }
-
- f.Close()
- of.Close()
-}
-
-func TestFileDotFileError(t *testing.T) {
- f := &FD{ReadWriter{-2}}
-
- if of, err := f.File(); err == nil {
- t.Errorf("File %v got nil err want non-nil", of)
- of.Close()
- }
-}
-
-func TestNewFromFile(t *testing.T) {
- f, err := NewFromFile(os.Stdin)
- if err != nil {
- t.Fatalf("NewFromFile got err %v want nil", err)
- }
- if nfd, ofd := f.FD(), int(os.Stdin.Fd()); nfd == -1 || nfd == ofd {
- t.Errorf("got FD() = %d, want = new FD (old FD was %d)", nfd, ofd)
- }
- f.Close()
-}
-
-func TestNewFromFileError(t *testing.T) {
- f, err := NewFromFile(nil)
- if err == nil {
- t.Errorf("NewFromFile got %v with nil err want non-nil", f)
- f.Close()
- }
-}
diff --git a/pkg/fdchannel/BUILD b/pkg/fdchannel/BUILD
deleted file mode 100644
index 56495cbd9..000000000
--- a/pkg/fdchannel/BUILD
+++ /dev/null
@@ -1,18 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "fdchannel",
- srcs = ["fdchannel_unsafe.go"],
- importpath = "gvisor.dev/gvisor/pkg/fdchannel",
- visibility = ["//visibility:public"],
-)
-
-go_test(
- name = "fdchannel_test",
- size = "small",
- srcs = ["fdchannel_test.go"],
- embed = [":fdchannel"],
-)
diff --git a/pkg/fdchannel/fdchannel_state_autogen.go b/pkg/fdchannel/fdchannel_state_autogen.go
new file mode 100755
index 000000000..8dbf80cba
--- /dev/null
+++ b/pkg/fdchannel/fdchannel_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package fdchannel
+
diff --git a/pkg/fdchannel/fdchannel_test.go b/pkg/fdchannel/fdchannel_test.go
deleted file mode 100644
index 5d01dc636..000000000
--- a/pkg/fdchannel/fdchannel_test.go
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fdchannel
-
-import (
- "io/ioutil"
- "os"
- "sync"
- "syscall"
- "testing"
- "time"
-)
-
-func TestSendRecvFD(t *testing.T) {
- sendFile, err := ioutil.TempFile("", "fdchannel_test_")
- if err != nil {
- t.Fatalf("failed to create temporary file: %v", err)
- }
- defer sendFile.Close()
-
- chanFDs, err := NewConnectedSockets()
- if err != nil {
- t.Fatalf("failed to create fdchannel sockets: %v", err)
- }
- sendEP := NewEndpoint(chanFDs[0])
- defer sendEP.Destroy()
- recvEP := NewEndpoint(chanFDs[1])
- defer recvEP.Destroy()
-
- recvFD, err := recvEP.RecvFDNonblock()
- if err != syscall.EAGAIN && err != syscall.EWOULDBLOCK {
- t.Errorf("RecvFDNonblock before SendFD: got (%d, %v), wanted (<unspecified>, EAGAIN or EWOULDBLOCK", recvFD, err)
- }
-
- if err := sendEP.SendFD(int(sendFile.Fd())); err != nil {
- t.Fatalf("SendFD failed: %v", err)
- }
- recvFD, err = recvEP.RecvFD()
- if err != nil {
- t.Fatalf("RecvFD failed: %v", err)
- }
- recvFile := os.NewFile(uintptr(recvFD), "received file")
- defer recvFile.Close()
-
- sendInfo, err := sendFile.Stat()
- if err != nil {
- t.Fatalf("failed to stat sent file: %v", err)
- }
- sendInfoSys := sendInfo.Sys()
- sendStat, ok := sendInfoSys.(*syscall.Stat_t)
- if !ok {
- t.Fatalf("sent file's FileInfo is backed by unknown type %T", sendInfoSys)
- }
-
- recvInfo, err := recvFile.Stat()
- if err != nil {
- t.Fatalf("failed to stat received file: %v", err)
- }
- recvInfoSys := recvInfo.Sys()
- recvStat, ok := recvInfoSys.(*syscall.Stat_t)
- if !ok {
- t.Fatalf("received file's FileInfo is backed by unknown type %T", recvInfoSys)
- }
-
- if sendStat.Dev != recvStat.Dev || sendStat.Ino != recvStat.Ino {
- t.Errorf("sent file (dev=%d, ino=%d) does not match received file (dev=%d, ino=%d)", sendStat.Dev, sendStat.Ino, recvStat.Dev, recvStat.Ino)
- }
-}
-
-func TestShutdownThenRecvFD(t *testing.T) {
- sendFile, err := ioutil.TempFile("", "fdchannel_test_")
- if err != nil {
- t.Fatalf("failed to create temporary file: %v", err)
- }
- defer sendFile.Close()
-
- chanFDs, err := NewConnectedSockets()
- if err != nil {
- t.Fatalf("failed to create fdchannel sockets: %v", err)
- }
- sendEP := NewEndpoint(chanFDs[0])
- defer sendEP.Destroy()
- recvEP := NewEndpoint(chanFDs[1])
- defer recvEP.Destroy()
-
- recvEP.Shutdown()
- if _, err := recvEP.RecvFD(); err == nil {
- t.Error("RecvFD succeeded unexpectedly")
- }
-}
-
-func TestRecvFDThenShutdown(t *testing.T) {
- sendFile, err := ioutil.TempFile("", "fdchannel_test_")
- if err != nil {
- t.Fatalf("failed to create temporary file: %v", err)
- }
- defer sendFile.Close()
-
- chanFDs, err := NewConnectedSockets()
- if err != nil {
- t.Fatalf("failed to create fdchannel sockets: %v", err)
- }
- sendEP := NewEndpoint(chanFDs[0])
- defer sendEP.Destroy()
- recvEP := NewEndpoint(chanFDs[1])
- defer recvEP.Destroy()
-
- var receiverWG sync.WaitGroup
- receiverWG.Add(1)
- go func() {
- defer receiverWG.Done()
- if _, err := recvEP.RecvFD(); err == nil {
- t.Error("RecvFD succeeded unexpectedly")
- }
- }()
- defer receiverWG.Wait()
- time.Sleep(time.Second) // to ensure recvEP.RecvFD() has blocked
- recvEP.Shutdown()
-}
diff --git a/pkg/fdchannel/fdchannel_unsafe.go b/pkg/fdchannel/fdchannel_unsafe.go
index 367235be5..367235be5 100644..100755
--- a/pkg/fdchannel/fdchannel_unsafe.go
+++ b/pkg/fdchannel/fdchannel_unsafe.go
diff --git a/pkg/fdnotifier/BUILD b/pkg/fdnotifier/BUILD
deleted file mode 100644
index aca2d8a82..000000000
--- a/pkg/fdnotifier/BUILD
+++ /dev/null
@@ -1,17 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "fdnotifier",
- srcs = [
- "fdnotifier.go",
- "poll_unsafe.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/fdnotifier",
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/waiter",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
diff --git a/pkg/fdnotifier/fdnotifier_state_autogen.go b/pkg/fdnotifier/fdnotifier_state_autogen.go
new file mode 100755
index 000000000..6f6076b7b
--- /dev/null
+++ b/pkg/fdnotifier/fdnotifier_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package fdnotifier
+
diff --git a/pkg/flipcall/BUILD b/pkg/flipcall/BUILD
deleted file mode 100644
index 5643d5f26..000000000
--- a/pkg/flipcall/BUILD
+++ /dev/null
@@ -1,34 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "flipcall",
- srcs = [
- "ctrl_futex.go",
- "flipcall.go",
- "flipcall_unsafe.go",
- "futex_linux.go",
- "io.go",
- "packet_window_allocator.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/flipcall",
- visibility = ["//visibility:public"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/log",
- "//pkg/memutil",
- "//third_party/gvsync",
- ],
-)
-
-go_test(
- name = "flipcall_test",
- size = "small",
- srcs = [
- "flipcall_example_test.go",
- "flipcall_test.go",
- ],
- embed = [":flipcall"],
-)
diff --git a/pkg/flipcall/ctrl_futex.go b/pkg/flipcall/ctrl_futex.go
index 8390915a2..8390915a2 100644..100755
--- a/pkg/flipcall/ctrl_futex.go
+++ b/pkg/flipcall/ctrl_futex.go
diff --git a/pkg/flipcall/flipcall.go b/pkg/flipcall/flipcall.go
index 386cee42c..386cee42c 100644..100755
--- a/pkg/flipcall/flipcall.go
+++ b/pkg/flipcall/flipcall.go
diff --git a/pkg/flipcall/flipcall_example_test.go b/pkg/flipcall/flipcall_example_test.go
deleted file mode 100644
index 8d88b845d..000000000
--- a/pkg/flipcall/flipcall_example_test.go
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package flipcall
-
-import (
- "bytes"
- "fmt"
- "sync"
-)
-
-func Example() {
- const (
- reqPrefix = "request "
- respPrefix = "response "
- count = 3
- maxMessageLen = len(respPrefix) + 1 // 1 digit
- )
-
- pwa, err := NewPacketWindowAllocator()
- if err != nil {
- panic(err)
- }
- defer pwa.Destroy()
- pwd, err := pwa.Allocate(PacketWindowLengthForDataCap(uint32(maxMessageLen)))
- if err != nil {
- panic(err)
- }
- var clientEP Endpoint
- if err := clientEP.Init(ClientSide, pwd); err != nil {
- panic(err)
- }
- defer clientEP.Destroy()
- var serverEP Endpoint
- if err := serverEP.Init(ServerSide, pwd); err != nil {
- panic(err)
- }
- defer serverEP.Destroy()
-
- var serverRun sync.WaitGroup
- serverRun.Add(1)
- go func() {
- defer serverRun.Done()
- i := 0
- var buf bytes.Buffer
- // wait for first request
- n, err := serverEP.RecvFirst()
- if err != nil {
- return
- }
- for {
- // read request
- buf.Reset()
- buf.Write(serverEP.Data()[:n])
- fmt.Println(buf.String())
- // write response
- buf.Reset()
- fmt.Fprintf(&buf, "%s%d", respPrefix, i)
- copy(serverEP.Data(), buf.Bytes())
- // send response and wait for next request
- n, err = serverEP.SendRecv(uint32(buf.Len()))
- if err != nil {
- return
- }
- i++
- }
- }()
- defer func() {
- serverEP.Shutdown()
- serverRun.Wait()
- }()
-
- // establish connection as client
- if err := clientEP.Connect(); err != nil {
- panic(err)
- }
- var buf bytes.Buffer
- for i := 0; i < count; i++ {
- // write request
- buf.Reset()
- fmt.Fprintf(&buf, "%s%d", reqPrefix, i)
- copy(clientEP.Data(), buf.Bytes())
- // send request and wait for response
- n, err := clientEP.SendRecv(uint32(buf.Len()))
- if err != nil {
- panic(err)
- }
- // read response
- buf.Reset()
- buf.Write(clientEP.Data()[:n])
- fmt.Println(buf.String())
- }
-
- // Output:
- // request 0
- // response 0
- // request 1
- // response 1
- // request 2
- // response 2
-}
diff --git a/pkg/flipcall/flipcall_state_autogen.go b/pkg/flipcall/flipcall_state_autogen.go
new file mode 100755
index 000000000..e9371b536
--- /dev/null
+++ b/pkg/flipcall/flipcall_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package flipcall
+
diff --git a/pkg/flipcall/flipcall_test.go b/pkg/flipcall/flipcall_test.go
deleted file mode 100644
index 168a487ec..000000000
--- a/pkg/flipcall/flipcall_test.go
+++ /dev/null
@@ -1,404 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package flipcall
-
-import (
- "runtime"
- "sync"
- "testing"
- "time"
-)
-
-var testPacketWindowSize = pageSize
-
-type testConnection struct {
- pwa PacketWindowAllocator
- clientEP Endpoint
- serverEP Endpoint
-}
-
-func newTestConnectionWithOptions(tb testing.TB, clientOpts, serverOpts []EndpointOption) *testConnection {
- c := &testConnection{}
- if err := c.pwa.Init(); err != nil {
- tb.Fatalf("failed to create PacketWindowAllocator: %v", err)
- }
- pwd, err := c.pwa.Allocate(testPacketWindowSize)
- if err != nil {
- c.pwa.Destroy()
- tb.Fatalf("PacketWindowAllocator.Allocate() failed: %v", err)
- }
- if err := c.clientEP.Init(ClientSide, pwd, clientOpts...); err != nil {
- c.pwa.Destroy()
- tb.Fatalf("failed to create client Endpoint: %v", err)
- }
- if err := c.serverEP.Init(ServerSide, pwd, serverOpts...); err != nil {
- c.pwa.Destroy()
- c.clientEP.Destroy()
- tb.Fatalf("failed to create server Endpoint: %v", err)
- }
- return c
-}
-
-func newTestConnection(tb testing.TB) *testConnection {
- return newTestConnectionWithOptions(tb, nil, nil)
-}
-
-func (c *testConnection) destroy() {
- c.pwa.Destroy()
- c.clientEP.Destroy()
- c.serverEP.Destroy()
-}
-
-func testSendRecv(t *testing.T, c *testConnection) {
- // This shared variable is used to confirm that synchronization between
- // flipcall endpoints is visible to the Go race detector.
- state := 0
- var serverRun sync.WaitGroup
- serverRun.Add(1)
- go func() {
- defer serverRun.Done()
- t.Logf("server Endpoint waiting for packet 1")
- if _, err := c.serverEP.RecvFirst(); err != nil {
- t.Errorf("server Endpoint.RecvFirst() failed: %v", err)
- return
- }
- state++
- if state != 2 {
- t.Errorf("shared state counter: got %d, wanted 2", state)
- }
- t.Logf("server Endpoint got packet 1, sending packet 2 and waiting for packet 3")
- if _, err := c.serverEP.SendRecv(0); err != nil {
- t.Errorf("server Endpoint.SendRecv() failed: %v", err)
- return
- }
- state++
- if state != 4 {
- t.Errorf("shared state counter: got %d, wanted 4", state)
- }
- t.Logf("server Endpoint got packet 3")
- }()
- defer func() {
- // Ensure that the server goroutine is cleaned up before
- // c.serverEP.Destroy(), even if the test fails.
- c.serverEP.Shutdown()
- serverRun.Wait()
- }()
-
- t.Logf("client Endpoint establishing connection")
- if err := c.clientEP.Connect(); err != nil {
- t.Fatalf("client Endpoint.Connect() failed: %v", err)
- }
- state++
- if state != 1 {
- t.Errorf("shared state counter: got %d, wanted 1", state)
- }
- t.Logf("client Endpoint sending packet 1 and waiting for packet 2")
- if _, err := c.clientEP.SendRecv(0); err != nil {
- t.Fatalf("client Endpoint.SendRecv() failed: %v", err)
- }
- state++
- if state != 3 {
- t.Errorf("shared state counter: got %d, wanted 3", state)
- }
- t.Logf("client Endpoint got packet 2, sending packet 3")
- if err := c.clientEP.SendLast(0); err != nil {
- t.Fatalf("client Endpoint.SendLast() failed: %v", err)
- }
- t.Logf("waiting for server goroutine to complete")
- serverRun.Wait()
-}
-
-func TestSendRecv(t *testing.T) {
- c := newTestConnection(t)
- defer c.destroy()
- testSendRecv(t, c)
-}
-
-func testShutdownBeforeConnect(t *testing.T, c *testConnection, remoteShutdown bool) {
- if remoteShutdown {
- c.serverEP.Shutdown()
- } else {
- c.clientEP.Shutdown()
- }
- if err := c.clientEP.Connect(); err == nil {
- t.Errorf("client Endpoint.Connect() succeeded unexpectedly")
- }
-}
-
-func TestShutdownBeforeConnectLocal(t *testing.T) {
- c := newTestConnection(t)
- defer c.destroy()
- testShutdownBeforeConnect(t, c, false)
-}
-
-func TestShutdownBeforeConnectRemote(t *testing.T) {
- c := newTestConnection(t)
- defer c.destroy()
- testShutdownBeforeConnect(t, c, true)
-}
-
-func testShutdownDuringConnect(t *testing.T, c *testConnection, remoteShutdown bool) {
- var clientRun sync.WaitGroup
- clientRun.Add(1)
- go func() {
- defer clientRun.Done()
- if err := c.clientEP.Connect(); err == nil {
- t.Errorf("client Endpoint.Connect() succeeded unexpectedly")
- }
- }()
- time.Sleep(time.Second) // to allow c.clientEP.Connect() to block
- if remoteShutdown {
- c.serverEP.Shutdown()
- } else {
- c.clientEP.Shutdown()
- }
- clientRun.Wait()
-}
-
-func TestShutdownDuringConnectLocal(t *testing.T) {
- c := newTestConnection(t)
- defer c.destroy()
- testShutdownDuringConnect(t, c, false)
-}
-
-func TestShutdownDuringConnectRemote(t *testing.T) {
- c := newTestConnection(t)
- defer c.destroy()
- testShutdownDuringConnect(t, c, true)
-}
-
-func testShutdownBeforeRecvFirst(t *testing.T, c *testConnection, remoteShutdown bool) {
- if remoteShutdown {
- c.clientEP.Shutdown()
- } else {
- c.serverEP.Shutdown()
- }
- if _, err := c.serverEP.RecvFirst(); err == nil {
- t.Errorf("server Endpoint.RecvFirst() succeeded unexpectedly")
- }
-}
-
-func TestShutdownBeforeRecvFirstLocal(t *testing.T) {
- c := newTestConnection(t)
- defer c.destroy()
- testShutdownBeforeRecvFirst(t, c, false)
-}
-
-func TestShutdownBeforeRecvFirstRemote(t *testing.T) {
- c := newTestConnection(t)
- defer c.destroy()
- testShutdownBeforeRecvFirst(t, c, true)
-}
-
-func testShutdownDuringRecvFirstBeforeConnect(t *testing.T, c *testConnection, remoteShutdown bool) {
- var serverRun sync.WaitGroup
- serverRun.Add(1)
- go func() {
- defer serverRun.Done()
- if _, err := c.serverEP.RecvFirst(); err == nil {
- t.Errorf("server Endpoint.RecvFirst() succeeded unexpectedly")
- }
- }()
- time.Sleep(time.Second) // to allow c.serverEP.RecvFirst() to block
- if remoteShutdown {
- c.clientEP.Shutdown()
- } else {
- c.serverEP.Shutdown()
- }
- serverRun.Wait()
-}
-
-func TestShutdownDuringRecvFirstBeforeConnectLocal(t *testing.T) {
- c := newTestConnection(t)
- defer c.destroy()
- testShutdownDuringRecvFirstBeforeConnect(t, c, false)
-}
-
-func TestShutdownDuringRecvFirstBeforeConnectRemote(t *testing.T) {
- c := newTestConnection(t)
- defer c.destroy()
- testShutdownDuringRecvFirstBeforeConnect(t, c, true)
-}
-
-func testShutdownDuringRecvFirstAfterConnect(t *testing.T, c *testConnection, remoteShutdown bool) {
- var serverRun sync.WaitGroup
- serverRun.Add(1)
- go func() {
- defer serverRun.Done()
- if _, err := c.serverEP.RecvFirst(); err == nil {
- t.Errorf("server Endpoint.RecvFirst() succeeded unexpectedly")
- }
- }()
- defer func() {
- // Ensure that the server goroutine is cleaned up before
- // c.serverEP.Destroy(), even if the test fails.
- c.serverEP.Shutdown()
- serverRun.Wait()
- }()
- if err := c.clientEP.Connect(); err != nil {
- t.Fatalf("client Endpoint.Connect() failed: %v", err)
- }
- if remoteShutdown {
- c.clientEP.Shutdown()
- } else {
- c.serverEP.Shutdown()
- }
- serverRun.Wait()
-}
-
-func TestShutdownDuringRecvFirstAfterConnectLocal(t *testing.T) {
- c := newTestConnection(t)
- defer c.destroy()
- testShutdownDuringRecvFirstAfterConnect(t, c, false)
-}
-
-func TestShutdownDuringRecvFirstAfterConnectRemote(t *testing.T) {
- c := newTestConnection(t)
- defer c.destroy()
- testShutdownDuringRecvFirstAfterConnect(t, c, true)
-}
-
-func testShutdownDuringClientSendRecv(t *testing.T, c *testConnection, remoteShutdown bool) {
- var serverRun sync.WaitGroup
- serverRun.Add(1)
- go func() {
- defer serverRun.Done()
- if _, err := c.serverEP.RecvFirst(); err != nil {
- t.Errorf("server Endpoint.RecvFirst() failed: %v", err)
- }
- // At this point, the client must be blocked in c.clientEP.SendRecv().
- if remoteShutdown {
- c.serverEP.Shutdown()
- } else {
- c.clientEP.Shutdown()
- }
- }()
- defer func() {
- // Ensure that the server goroutine is cleaned up before
- // c.serverEP.Destroy(), even if the test fails.
- c.serverEP.Shutdown()
- serverRun.Wait()
- }()
- if err := c.clientEP.Connect(); err != nil {
- t.Fatalf("client Endpoint.Connect() failed: %v", err)
- }
- if _, err := c.clientEP.SendRecv(0); err == nil {
- t.Errorf("client Endpoint.SendRecv() succeeded unexpectedly")
- }
-}
-
-func TestShutdownDuringClientSendRecvLocal(t *testing.T) {
- c := newTestConnection(t)
- defer c.destroy()
- testShutdownDuringClientSendRecv(t, c, false)
-}
-
-func TestShutdownDuringClientSendRecvRemote(t *testing.T) {
- c := newTestConnection(t)
- defer c.destroy()
- testShutdownDuringClientSendRecv(t, c, true)
-}
-
-func testShutdownDuringServerSendRecv(t *testing.T, c *testConnection, remoteShutdown bool) {
- var serverRun sync.WaitGroup
- serverRun.Add(1)
- go func() {
- defer serverRun.Done()
- if _, err := c.serverEP.RecvFirst(); err != nil {
- t.Errorf("server Endpoint.RecvFirst() failed: %v", err)
- return
- }
- if _, err := c.serverEP.SendRecv(0); err == nil {
- t.Errorf("server Endpoint.SendRecv() succeeded unexpectedly")
- }
- }()
- defer func() {
- // Ensure that the server goroutine is cleaned up before
- // c.serverEP.Destroy(), even if the test fails.
- c.serverEP.Shutdown()
- serverRun.Wait()
- }()
- if err := c.clientEP.Connect(); err != nil {
- t.Fatalf("client Endpoint.Connect() failed: %v", err)
- }
- if _, err := c.clientEP.SendRecv(0); err != nil {
- t.Fatalf("client Endpoint.SendRecv() failed: %v", err)
- }
- time.Sleep(time.Second) // to allow serverEP.SendRecv() to block
- if remoteShutdown {
- c.clientEP.Shutdown()
- } else {
- c.serverEP.Shutdown()
- }
- serverRun.Wait()
-}
-
-func TestShutdownDuringServerSendRecvLocal(t *testing.T) {
- c := newTestConnection(t)
- defer c.destroy()
- testShutdownDuringServerSendRecv(t, c, false)
-}
-
-func TestShutdownDuringServerSendRecvRemote(t *testing.T) {
- c := newTestConnection(t)
- defer c.destroy()
- testShutdownDuringServerSendRecv(t, c, true)
-}
-
-func benchmarkSendRecv(b *testing.B, c *testConnection) {
- var serverRun sync.WaitGroup
- serverRun.Add(1)
- go func() {
- defer serverRun.Done()
- if b.N == 0 {
- return
- }
- if _, err := c.serverEP.RecvFirst(); err != nil {
- b.Errorf("server Endpoint.RecvFirst() failed: %v", err)
- return
- }
- for i := 1; i < b.N; i++ {
- if _, err := c.serverEP.SendRecv(0); err != nil {
- b.Errorf("server Endpoint.SendRecv() failed: %v", err)
- return
- }
- }
- if err := c.serverEP.SendLast(0); err != nil {
- b.Errorf("server Endpoint.SendLast() failed: %v", err)
- }
- }()
- defer func() {
- c.serverEP.Shutdown()
- serverRun.Wait()
- }()
-
- if err := c.clientEP.Connect(); err != nil {
- b.Fatalf("client Endpoint.Connect() failed: %v", err)
- }
- runtime.GC()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- if _, err := c.clientEP.SendRecv(0); err != nil {
- b.Fatalf("client Endpoint.SendRecv() failed: %v", err)
- }
- }
- b.StopTimer()
-}
-
-func BenchmarkSendRecv(b *testing.B) {
- c := newTestConnection(b)
- defer c.destroy()
- benchmarkSendRecv(b, c)
-}
diff --git a/pkg/flipcall/flipcall_unsafe.go b/pkg/flipcall/flipcall_unsafe.go
index a37952637..a37952637 100644..100755
--- a/pkg/flipcall/flipcall_unsafe.go
+++ b/pkg/flipcall/flipcall_unsafe.go
diff --git a/pkg/flipcall/futex_linux.go b/pkg/flipcall/futex_linux.go
index b127a2bbb..b127a2bbb 100644..100755
--- a/pkg/flipcall/futex_linux.go
+++ b/pkg/flipcall/futex_linux.go
diff --git a/pkg/flipcall/io.go b/pkg/flipcall/io.go
index 85e40b932..85e40b932 100644..100755
--- a/pkg/flipcall/io.go
+++ b/pkg/flipcall/io.go
diff --git a/pkg/flipcall/packet_window_allocator.go b/pkg/flipcall/packet_window_allocator.go
index ccb918fab..ccb918fab 100644..100755
--- a/pkg/flipcall/packet_window_allocator.go
+++ b/pkg/flipcall/packet_window_allocator.go
diff --git a/pkg/fspath/BUILD b/pkg/fspath/BUILD
deleted file mode 100644
index 0c5f50397..000000000
--- a/pkg/fspath/BUILD
+++ /dev/null
@@ -1,29 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(
- default_visibility = ["//visibility:public"],
- licenses = ["notice"],
-)
-
-go_library(
- name = "fspath",
- srcs = [
- "builder.go",
- "builder_unsafe.go",
- "fspath.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/fspath",
- deps = ["//pkg/syserror"],
-)
-
-go_test(
- name = "fspath_test",
- size = "small",
- srcs = [
- "builder_test.go",
- "fspath_test.go",
- ],
- embed = [":fspath"],
- deps = ["//pkg/syserror"],
-)
diff --git a/pkg/fspath/builder.go b/pkg/fspath/builder.go
deleted file mode 100644
index 7ddb36826..000000000
--- a/pkg/fspath/builder.go
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fspath
-
-import (
- "fmt"
-)
-
-// Builder is similar to strings.Builder, but is used to produce pathnames
-// given path components in reverse order (from leaf to root). This is useful
-// in the common case where a filesystem is represented by a tree of named
-// nodes, and the path to a given node must be produced by walking upward from
-// that node to a given root.
-type Builder struct {
- buf []byte
- start int
- needSep bool
-}
-
-// Reset resets the Builder to be empty.
-func (b *Builder) Reset() {
- b.start = len(b.buf)
- b.needSep = false
-}
-
-// Len returns the number of accumulated bytes.
-func (b *Builder) Len() int {
- return len(b.buf) - b.start
-}
-
-func (b *Builder) needToGrow(n int) bool {
- return b.start < n
-}
-
-func (b *Builder) grow(n int) {
- newLen := b.Len() + n
- var newCap int
- if len(b.buf) == 0 {
- newCap = 64 // arbitrary
- } else {
- newCap = 2 * len(b.buf)
- }
- for newCap < newLen {
- newCap *= 2
- if newCap == 0 {
- panic(fmt.Sprintf("required length (%d) causes buffer size to overflow", newLen))
- }
- }
- newBuf := make([]byte, newCap)
- copy(newBuf[newCap-b.Len():], b.buf[b.start:])
- b.start += newCap - len(b.buf)
- b.buf = newBuf
-}
-
-// PrependComponent prepends the given path component to b's buffer. A path
-// separator is automatically inserted if appropriate.
-func (b *Builder) PrependComponent(pc string) {
- if b.needSep {
- b.PrependByte('/')
- }
- b.PrependString(pc)
- b.needSep = true
-}
-
-// PrependString prepends the given string to b's buffer.
-func (b *Builder) PrependString(str string) {
- if b.needToGrow(len(str)) {
- b.grow(len(str))
- }
- b.start -= len(str)
- copy(b.buf[b.start:], str)
-}
-
-// PrependByte prepends the given byte to b's buffer.
-func (b *Builder) PrependByte(c byte) {
- if b.needToGrow(1) {
- b.grow(1)
- }
- b.start--
- b.buf[b.start] = c
-}
-
-// AppendString appends the given string to b's buffer.
-func (b *Builder) AppendString(str string) {
- if b.needToGrow(len(str)) {
- b.grow(len(str))
- }
- oldStart := b.start
- b.start -= len(str)
- copy(b.buf[b.start:], b.buf[oldStart:])
- copy(b.buf[len(b.buf)-len(str):], str)
-}
diff --git a/pkg/fspath/builder_test.go b/pkg/fspath/builder_test.go
deleted file mode 100644
index 22f890273..000000000
--- a/pkg/fspath/builder_test.go
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fspath
-
-import (
- "testing"
-)
-
-func TestBuilder(t *testing.T) {
- type testCase struct {
- pcs []string // path components in reverse order
- after string
- want string
- }
- tests := []testCase{
- {
- // Empty case.
- },
- {
- pcs: []string{"foo"},
- want: "foo",
- },
- {
- pcs: []string{"foo", "bar", "baz"},
- want: "baz/bar/foo",
- },
- {
- pcs: []string{"foo", "bar"},
- after: " (deleted)",
- want: "bar/foo (deleted)",
- },
- }
-
- for _, test := range tests {
- t.Run(test.want, func(t *testing.T) {
- var b Builder
- for _, pc := range test.pcs {
- b.PrependComponent(pc)
- }
- b.AppendString(test.after)
- if got := b.String(); got != test.want {
- t.Errorf("got %q, wanted %q", got, test.want)
- }
- })
- }
-}
diff --git a/pkg/fspath/builder_unsafe.go b/pkg/fspath/builder_unsafe.go
deleted file mode 100644
index 75606808d..000000000
--- a/pkg/fspath/builder_unsafe.go
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fspath
-
-import (
- "unsafe"
-)
-
-// String returns the accumulated string. No other methods should be called
-// after String.
-func (b *Builder) String() string {
- bs := b.buf[b.start:]
- // Compare strings.Builder.String().
- return *(*string)(unsafe.Pointer(&bs))
-}
diff --git a/pkg/fspath/fspath.go b/pkg/fspath/fspath.go
deleted file mode 100644
index f68752560..000000000
--- a/pkg/fspath/fspath.go
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package fspath provides efficient tools for working with file paths in
-// Linux-compatible filesystem implementations.
-package fspath
-
-import (
- "strings"
-
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-const pathSep = '/'
-
-// Parse parses a pathname as described by path_resolution(7).
-func Parse(pathname string) (Path, error) {
- if len(pathname) == 0 {
- // "... POSIX decrees that an empty pathname must not be resolved
- // successfully. Linux returns ENOENT in this case." -
- // path_resolution(7)
- return Path{}, syserror.ENOENT
- }
- // Skip leading path separators.
- i := 0
- for pathname[i] == pathSep {
- i++
- if i == len(pathname) {
- // pathname consists entirely of path separators.
- return Path{
- Absolute: true,
- Dir: true,
- }, nil
- }
- }
- // Skip trailing path separators. This is required by Iterator.Next. This
- // loop is guaranteed to terminate with j >= 0 because otherwise the
- // pathname would consist entirely of path separators, so we would have
- // returned above.
- j := len(pathname) - 1
- for pathname[j] == pathSep {
- j--
- }
- // Find the end of the first path component.
- firstEnd := i + 1
- for firstEnd != len(pathname) && pathname[firstEnd] != pathSep {
- firstEnd++
- }
- return Path{
- Begin: Iterator{
- partialPathname: pathname[i : j+1],
- end: firstEnd - i,
- },
- Absolute: i != 0,
- Dir: j != len(pathname)-1,
- }, nil
-}
-
-// Path contains the information contained in a pathname string.
-//
-// Path is copyable by value.
-type Path struct {
- // Begin is an iterator to the first path component in the relative part of
- // the path.
- //
- // Path doesn't store information about path components after the first
- // since this would require allocation.
- Begin Iterator
-
- // If true, the path is absolute, such that lookup should begin at the
- // filesystem root. If false, the path is relative, such that where lookup
- // begins is unspecified.
- Absolute bool
-
- // If true, the pathname contains trailing path separators, so the last
- // path component must exist and resolve to a directory.
- Dir bool
-}
-
-// String returns a pathname string equivalent to p. Note that the returned
-// string is not necessarily equal to the string p was parsed from; in
-// particular, redundant path separators will not be present.
-func (p Path) String() string {
- var b strings.Builder
- if p.Absolute {
- b.WriteByte(pathSep)
- }
- sep := false
- for pit := p.Begin; pit.Ok(); pit = pit.Next() {
- if sep {
- b.WriteByte(pathSep)
- }
- b.WriteString(pit.String())
- sep = true
- }
- // Don't return "//" for Parse("/").
- if p.Dir && p.Begin.Ok() {
- b.WriteByte(pathSep)
- }
- return b.String()
-}
-
-// An Iterator represents either a path component in a Path or a terminal
-// iterator indicating that the end of the path has been reached.
-//
-// Iterator is immutable and copyable by value. The zero value of Iterator is
-// valid, and represents a terminal iterator.
-type Iterator struct {
- // partialPathname is a substring of the original pathname beginning at the
- // start of the represented path component and ending immediately after the
- // end of the last path component in the pathname. If partialPathname is
- // empty, the PathnameIterator is terminal.
- //
- // See TestParseIteratorPartialPathnames in fspath_test.go for a worked
- // example.
- partialPathname string
-
- // end is the offset into partialPathname of the first byte after the end
- // of the represented path component.
- end int
-}
-
-// Ok returns true if it is not terminal.
-func (it Iterator) Ok() bool {
- return len(it.partialPathname) != 0
-}
-
-// String returns the path component represented by it.
-//
-// Preconditions: it.Ok().
-func (it Iterator) String() string {
- return it.partialPathname[:it.end]
-}
-
-// Next returns an iterator to the path component after it. If it is the last
-// component in the path, Next returns a terminal iterator.
-//
-// Preconditions: it.Ok().
-func (it Iterator) Next() Iterator {
- if it.end == len(it.partialPathname) {
- // End of the path.
- return Iterator{}
- }
- // Skip path separators. Since Parse trims trailing path separators, if we
- // aren't at the end of the path, there is definitely another path
- // component.
- i := it.end + 1
- for {
- if it.partialPathname[i] != pathSep {
- break
- }
- i++
- }
- nextPartialPathname := it.partialPathname[i:]
- // Find the end of this path component.
- nextEnd := 1
- for nextEnd < len(nextPartialPathname) && nextPartialPathname[nextEnd] != pathSep {
- nextEnd++
- }
- return Iterator{
- partialPathname: nextPartialPathname,
- end: nextEnd,
- }
-}
-
-// NextOk is equivalent to it.Next().Ok(), but is faster.
-//
-// Preconditions: it.Ok().
-func (it Iterator) NextOk() bool {
- return it.end != len(it.partialPathname)
-}
diff --git a/pkg/fspath/fspath_test.go b/pkg/fspath/fspath_test.go
deleted file mode 100644
index 215b35622..000000000
--- a/pkg/fspath/fspath_test.go
+++ /dev/null
@@ -1,143 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fspath
-
-import (
- "reflect"
- "strings"
- "testing"
-
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-func TestParseIteratorPartialPathnames(t *testing.T) {
- path, err := Parse("/foo//bar///baz////")
- if err != nil {
- t.Fatalf("Parse failed: %v", err)
- }
- // Parse strips leading slashes, and records their presence as
- // Path.Absolute.
- if !path.Absolute {
- t.Errorf("Path.Absolute: got false, wanted true")
- }
- // Parse strips trailing slashes, and records their presence as Path.Dir.
- if !path.Dir {
- t.Errorf("Path.Dir: got false, wanted true")
- }
- // The first Iterator.partialPathname is the input pathname, with leading
- // and trailing slashes stripped.
- it := path.Begin
- if want := "foo//bar///baz"; it.partialPathname != want {
- t.Errorf("first Iterator.partialPathname: got %q, wanted %q", it.partialPathname, want)
- }
- // Successive Iterator.partialPathnames remove the leading path component
- // and following slashes, until we run out of path components and get a
- // terminal Iterator.
- it = it.Next()
- if want := "bar///baz"; it.partialPathname != want {
- t.Errorf("second Iterator.partialPathname: got %q, wanted %q", it.partialPathname, want)
- }
- it = it.Next()
- if want := "baz"; it.partialPathname != want {
- t.Errorf("third Iterator.partialPathname: got %q, wanted %q", it.partialPathname, want)
- }
- it = it.Next()
- if want := ""; it.partialPathname != want {
- t.Errorf("fourth Iterator.partialPathname: got %q, wanted %q", it.partialPathname, want)
- }
- if it.Ok() {
- t.Errorf("fourth Iterator.Ok(): got true, wanted false")
- }
-}
-
-func TestParse(t *testing.T) {
- type testCase struct {
- pathname string
- relpath []string
- abs bool
- dir bool
- }
- tests := []testCase{
- {
- pathname: "/",
- relpath: []string{},
- abs: true,
- dir: true,
- },
- {
- pathname: "//",
- relpath: []string{},
- abs: true,
- dir: true,
- },
- }
- for _, sep := range []string{"/", "//"} {
- for _, abs := range []bool{false, true} {
- for _, dir := range []bool{false, true} {
- for _, pcs := range [][]string{
- // single path component
- {"foo"},
- // multiple path components, including non-UTF-8
- {".", "foo", "..", "\xe6", "bar"},
- } {
- prefix := ""
- if abs {
- prefix = sep
- }
- suffix := ""
- if dir {
- suffix = sep
- }
- tests = append(tests, testCase{
- pathname: prefix + strings.Join(pcs, sep) + suffix,
- relpath: pcs,
- abs: abs,
- dir: dir,
- })
- }
- }
- }
- }
-
- for _, test := range tests {
- t.Run(test.pathname, func(t *testing.T) {
- p, err := Parse(test.pathname)
- if err != nil {
- t.Fatalf("failed to parse pathname %q: %v", test.pathname, err)
- }
- t.Logf("pathname %q => path %q", test.pathname, p)
- if p.Absolute != test.abs {
- t.Errorf("path absoluteness: got %v, wanted %v", p.Absolute, test.abs)
- }
- if p.Dir != test.dir {
- t.Errorf("path must resolve to a directory: got %v, wanted %v", p.Dir, test.dir)
- }
- pcs := []string{}
- for pit := p.Begin; pit.Ok(); pit = pit.Next() {
- pcs = append(pcs, pit.String())
- }
- if !reflect.DeepEqual(pcs, test.relpath) {
- t.Errorf("relative path: got %v, wanted %v", pcs, test.relpath)
- }
- })
- }
-}
-
-func TestParseEmptyPathname(t *testing.T) {
- p, err := Parse("")
- if err != syserror.ENOENT {
- t.Errorf("parsing empty pathname: got (%v, %v), wanted (<unspecified>, ENOENT)", p, err)
- }
-}
diff --git a/pkg/gate/BUILD b/pkg/gate/BUILD
deleted file mode 100644
index 4b9321711..000000000
--- a/pkg/gate/BUILD
+++ /dev/null
@@ -1,23 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "gate",
- srcs = [
- "gate.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/gate",
- visibility = ["//visibility:public"],
-)
-
-go_test(
- name = "gate_test",
- srcs = [
- "gate_test.go",
- ],
- deps = [
- ":gate",
- ],
-)
diff --git a/pkg/gate/gate_state_autogen.go b/pkg/gate/gate_state_autogen.go
new file mode 100755
index 000000000..a81fca776
--- /dev/null
+++ b/pkg/gate/gate_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package gate
+
diff --git a/pkg/gate/gate_test.go b/pkg/gate/gate_test.go
deleted file mode 100644
index 5dbd8d712..000000000
--- a/pkg/gate/gate_test.go
+++ /dev/null
@@ -1,189 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package gate_test
-
-import (
- "sync"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/gate"
-)
-
-func TestBasicEnter(t *testing.T) {
- var g gate.Gate
-
- if !g.Enter() {
- t.Fatalf("Failed to enter when it should be allowed")
- }
-
- g.Leave()
-
- g.Close()
-
- if g.Enter() {
- t.Fatalf("Allowed to enter when it should fail")
- }
-}
-
-func enterFunc(t *testing.T, g *gate.Gate, enter, leave, reenter chan struct{}, done1, done2, done3 *sync.WaitGroup) {
- // Wait until instructed to enter.
- <-enter
- if !g.Enter() {
- t.Errorf("Failed to enter when it should be allowed")
- }
-
- done1.Done()
-
- // Wait until instructed to leave.
- <-leave
- g.Leave()
-
- done2.Done()
-
- // Wait until instructed to reenter.
- <-reenter
- if g.Enter() {
- t.Errorf("Allowed to enter when it should fail")
- }
- done3.Done()
-}
-
-func TestConcurrentEnter(t *testing.T) {
- var g gate.Gate
- var done1, done2, done3 sync.WaitGroup
-
- // Create 1000 worker goroutines.
- enter := make(chan struct{})
- leave := make(chan struct{})
- reenter := make(chan struct{})
- done1.Add(1000)
- done2.Add(1000)
- done3.Add(1000)
- for i := 0; i < 1000; i++ {
- go enterFunc(t, &g, enter, leave, reenter, &done1, &done2, &done3)
- }
-
- // Tell them all to enter, then leave.
- close(enter)
- done1.Wait()
-
- close(leave)
- done2.Wait()
-
- // Close the gate, then have the workers try to enter again.
- g.Close()
- close(reenter)
- done3.Wait()
-}
-
-func closeFunc(g *gate.Gate, done chan struct{}) {
- g.Close()
- close(done)
-}
-
-func TestCloseWaits(t *testing.T) {
- var g gate.Gate
-
- // Enter 10 times.
- for i := 0; i < 10; i++ {
- if !g.Enter() {
- t.Fatalf("Failed to enter when it should be allowed")
- }
- }
-
- // Launch closer. Check that it doesn't complete.
- done := make(chan struct{})
- go closeFunc(&g, done)
-
- for i := 0; i < 10; i++ {
- select {
- case <-done:
- t.Fatalf("Close function completed too soon")
- case <-time.After(100 * time.Millisecond):
- }
-
- g.Leave()
- }
-
- // Now the closer must complete.
- <-done
-}
-
-func TestMultipleSerialCloses(t *testing.T) {
- var g gate.Gate
-
- // Enter 10 times.
- for i := 0; i < 10; i++ {
- if !g.Enter() {
- t.Fatalf("Failed to enter when it should be allowed")
- }
- }
-
- // Launch closer. Check that it doesn't complete.
- done := make(chan struct{})
- go closeFunc(&g, done)
-
- for i := 0; i < 10; i++ {
- select {
- case <-done:
- t.Fatalf("Close function completed too soon")
- case <-time.After(100 * time.Millisecond):
- }
-
- g.Leave()
- }
-
- // Now the closer must complete.
- <-done
-
- // Close again should not block.
- done = make(chan struct{})
- go closeFunc(&g, done)
-
- select {
- case <-done:
- case <-time.After(2 * time.Second):
- t.Fatalf("Second Close is blocking")
- }
-}
-
-func worker(g *gate.Gate, done *sync.WaitGroup) {
- for {
- if !g.Enter() {
- break
- }
- g.Leave()
- }
- done.Done()
-}
-
-func TestConcurrentAll(t *testing.T) {
- var g gate.Gate
- var done sync.WaitGroup
-
- // Launch 1000 goroutines to concurrently enter/leave.
- done.Add(1000)
- for i := 0; i < 1000; i++ {
- go worker(&g, &done)
- }
-
- // Wait for the goroutines to do some work, then close the gate.
- time.Sleep(2 * time.Second)
- g.Close()
-
- // Wait for all of them to complete.
- done.Wait()
-}
diff --git a/pkg/ilist/BUILD b/pkg/ilist/BUILD
deleted file mode 100644
index 34d2673ef..000000000
--- a/pkg/ilist/BUILD
+++ /dev/null
@@ -1,58 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-load("//tools/go_generics:defs.bzl", "go_template", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "ilist",
- srcs = [
- "interface_list.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/ilist",
- visibility = ["//visibility:public"],
-)
-
-go_template_instance(
- name = "interface_list",
- out = "interface_list.go",
- package = "ilist",
- template = ":generic_list",
- types = {},
-)
-
-# This list is used for benchmarking.
-go_template_instance(
- name = "test_list",
- out = "test_list.go",
- package = "ilist",
- prefix = "direct",
- template = ":generic_list",
- types = {
- "Element": "*direct",
- "Linker": "*direct",
- },
-)
-
-go_test(
- name = "list_test",
- size = "small",
- srcs = [
- "list_test.go",
- "test_list.go",
- ],
- embed = [":ilist"],
-)
-
-go_template(
- name = "generic_list",
- srcs = [
- "list.go",
- ],
- opt_types = [
- "Element",
- "ElementMapper",
- "Linker",
- ],
- visibility = ["//visibility:public"],
-)
diff --git a/pkg/ilist/ilist_state_autogen.go b/pkg/ilist/ilist_state_autogen.go
new file mode 100755
index 000000000..03d7a9564
--- /dev/null
+++ b/pkg/ilist/ilist_state_autogen.go
@@ -0,0 +1,38 @@
+// automatically generated by stateify.
+
+package ilist
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *List) beforeSave() {}
+func (x *List) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *List) afterLoad() {}
+func (x *List) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *Entry) beforeSave() {}
+func (x *Entry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *Entry) afterLoad() {}
+func (x *Entry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func init() {
+ state.Register("ilist.List", (*List)(nil), state.Fns{Save: (*List).save, Load: (*List).load})
+ state.Register("ilist.Entry", (*Entry)(nil), state.Fns{Save: (*Entry).save, Load: (*Entry).load})
+}
diff --git a/pkg/ilist/list.go b/pkg/ilist/interface_list.go
index 019caadca..940c2d3f6 100644..100755
--- a/pkg/ilist/list.go
+++ b/pkg/ilist/interface_list.go
@@ -1,18 +1,3 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package ilist provides the implementation of intrusive linked lists.
package ilist
// Linker is the interface that objects must implement if they want to be added
diff --git a/pkg/ilist/list_test.go b/pkg/ilist/list_test.go
deleted file mode 100644
index 3f9abfb56..000000000
--- a/pkg/ilist/list_test.go
+++ /dev/null
@@ -1,240 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ilist
-
-import (
- "testing"
-)
-
-type testEntry struct {
- Entry
- value int
-}
-
-type direct struct {
- directEntry
- value int
-}
-
-func verifyEquality(t *testing.T, entries []testEntry, l *List) {
- t.Helper()
-
- i := 0
- for it := l.Front(); it != nil; it = it.Next() {
- e := it.(*testEntry)
- if e != &entries[i] {
- t.Errorf("Wrong entry at index %d", i)
- return
- }
- i++
- }
-
- if i != len(entries) {
- t.Errorf("Wrong number of entries; want = %d, got = %d", len(entries), i)
- return
- }
-
- i = 0
- for it := l.Back(); it != nil; it = it.Prev() {
- e := it.(*testEntry)
- if e != &entries[len(entries)-1-i] {
- t.Errorf("Wrong entry at index %d", i)
- return
- }
- i++
- }
-
- if i != len(entries) {
- t.Errorf("Wrong number of entries; want = %d, got = %d", len(entries), i)
- return
- }
-}
-
-func TestZeroEmpty(t *testing.T) {
- var l List
- if l.Front() != nil {
- t.Error("Front is non-nil")
- }
- if l.Back() != nil {
- t.Error("Back is non-nil")
- }
-}
-
-func TestPushBack(t *testing.T) {
- var l List
-
- // Test single entry insertion.
- var entry testEntry
- l.PushBack(&entry)
-
- e := l.Front().(*testEntry)
- if e != &entry {
- t.Error("Wrong entry returned")
- }
-
- // Test inserting 100 entries.
- l.Reset()
- var entries [100]testEntry
- for i := range entries {
- l.PushBack(&entries[i])
- }
-
- verifyEquality(t, entries[:], &l)
-}
-
-func TestPushFront(t *testing.T) {
- var l List
-
- // Test single entry insertion.
- var entry testEntry
- l.PushFront(&entry)
-
- e := l.Front().(*testEntry)
- if e != &entry {
- t.Error("Wrong entry returned")
- }
-
- // Test inserting 100 entries.
- l.Reset()
- var entries [100]testEntry
- for i := range entries {
- l.PushFront(&entries[len(entries)-1-i])
- }
-
- verifyEquality(t, entries[:], &l)
-}
-
-func TestRemove(t *testing.T) {
- // Remove entry from single-element list.
- var l List
- var entry testEntry
- l.PushBack(&entry)
- l.Remove(&entry)
- if l.Front() != nil {
- t.Error("List is empty")
- }
-
- var entries [100]testEntry
-
- // Remove single element from lists of lengths 2 to 101.
- for n := 1; n <= len(entries); n++ {
- for extra := 0; extra <= n; extra++ {
- l.Reset()
- for i := 0; i < n; i++ {
- if extra == i {
- l.PushBack(&entry)
- }
- l.PushBack(&entries[i])
- }
- if extra == n {
- l.PushBack(&entry)
- }
-
- l.Remove(&entry)
- verifyEquality(t, entries[:n], &l)
- }
- }
-}
-
-func TestReset(t *testing.T) {
- var l List
-
- // Resetting list of one element.
- l.PushBack(&testEntry{})
- if l.Front() == nil {
- t.Error("List is empty")
- }
-
- l.Reset()
- if l.Front() != nil {
- t.Error("List is not empty")
- }
-
- // Resetting list of 10 elements.
- for i := 0; i < 10; i++ {
- l.PushBack(&testEntry{})
- }
-
- if l.Front() == nil {
- t.Error("List is empty")
- }
-
- l.Reset()
- if l.Front() != nil {
- t.Error("List is not empty")
- }
-
- // Resetting empty list.
- l.Reset()
- if l.Front() != nil {
- t.Error("List is not empty")
- }
-}
-
-func BenchmarkIterateForward(b *testing.B) {
- var l List
- for i := 0; i < 1000000; i++ {
- l.PushBack(&testEntry{value: i})
- }
-
- for i := b.N; i > 0; i-- {
- tmp := 0
- for e := l.Front(); e != nil; e = e.Next() {
- tmp += e.(*testEntry).value
- }
- }
-}
-
-func BenchmarkIterateBackward(b *testing.B) {
- var l List
- for i := 0; i < 1000000; i++ {
- l.PushBack(&testEntry{value: i})
- }
-
- for i := b.N; i > 0; i-- {
- tmp := 0
- for e := l.Back(); e != nil; e = e.Prev() {
- tmp += e.(*testEntry).value
- }
- }
-}
-
-func BenchmarkDirectIterateForward(b *testing.B) {
- var l directList
- for i := 0; i < 1000000; i++ {
- l.PushBack(&direct{value: i})
- }
-
- for i := b.N; i > 0; i-- {
- tmp := 0
- for e := l.Front(); e != nil; e = e.Next() {
- tmp += e.value
- }
- }
-}
-
-func BenchmarkDirectIterateBackward(b *testing.B) {
- var l directList
- for i := 0; i < 1000000; i++ {
- l.PushBack(&direct{value: i})
- }
-
- for i := b.N; i > 0; i-- {
- tmp := 0
- for e := l.Back(); e != nil; e = e.Prev() {
- tmp += e.value
- }
- }
-}
diff --git a/pkg/linewriter/BUILD b/pkg/linewriter/BUILD
deleted file mode 100644
index a5d980d14..000000000
--- a/pkg/linewriter/BUILD
+++ /dev/null
@@ -1,17 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "linewriter",
- srcs = ["linewriter.go"],
- importpath = "gvisor.dev/gvisor/pkg/linewriter",
- visibility = ["//visibility:public"],
-)
-
-go_test(
- name = "linewriter_test",
- srcs = ["linewriter_test.go"],
- embed = [":linewriter"],
-)
diff --git a/pkg/linewriter/linewriter_state_autogen.go b/pkg/linewriter/linewriter_state_autogen.go
new file mode 100755
index 000000000..194088d76
--- /dev/null
+++ b/pkg/linewriter/linewriter_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package linewriter
+
diff --git a/pkg/linewriter/linewriter_test.go b/pkg/linewriter/linewriter_test.go
deleted file mode 100644
index 96dc7e6e0..000000000
--- a/pkg/linewriter/linewriter_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package linewriter
-
-import (
- "bytes"
- "testing"
-)
-
-func TestWriter(t *testing.T) {
- testCases := []struct {
- input []string
- want []string
- }{
- {
- input: []string{"1\n", "2\n"},
- want: []string{"1", "2"},
- },
- {
- input: []string{"1\n", "\n", "2\n"},
- want: []string{"1", "", "2"},
- },
- {
- input: []string{"1\n2\n", "3\n"},
- want: []string{"1", "2", "3"},
- },
- {
- input: []string{"1", "2\n"},
- want: []string{"12"},
- },
- {
- // Data with no newline yet is omitted.
- input: []string{"1\n", "2\n", "3"},
- want: []string{"1", "2"},
- },
- }
-
- for _, c := range testCases {
- var lines [][]byte
-
- w := NewWriter(func(p []byte) {
- // We must not retain p, so we must make a copy.
- b := make([]byte, len(p))
- copy(b, p)
-
- lines = append(lines, b)
- })
-
- for _, in := range c.input {
- n, err := w.Write([]byte(in))
- if err != nil {
- t.Errorf("Write(%q) err got %v want nil (case %+v)", in, err, c)
- }
- if n != len(in) {
- t.Errorf("Write(%q) b got %d want %d (case %+v)", in, n, len(in), c)
- }
- }
-
- if len(lines) != len(c.want) {
- t.Errorf("len(lines) got %d want %d (case %+v)", len(lines), len(c.want), c)
- }
-
- for i := range lines {
- if !bytes.Equal(lines[i], []byte(c.want[i])) {
- t.Errorf("item %d got %q want %q (case %+v)", i, lines[i], c.want[i], c)
- }
- }
- }
-}
diff --git a/pkg/log/BUILD b/pkg/log/BUILD
deleted file mode 100644
index fc5f5779b..000000000
--- a/pkg/log/BUILD
+++ /dev/null
@@ -1,30 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "log",
- srcs = [
- "glog.go",
- "glog_unsafe.go",
- "json.go",
- "json_k8s.go",
- "log.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/log",
- visibility = [
- "//visibility:public",
- ],
- deps = ["//pkg/linewriter"],
-)
-
-go_test(
- name = "log_test",
- size = "small",
- srcs = [
- "json_test.go",
- "log_test.go",
- ],
- embed = [":log"],
-)
diff --git a/pkg/log/json_test.go b/pkg/log/json_test.go
deleted file mode 100644
index f25224fe1..000000000
--- a/pkg/log/json_test.go
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package log
-
-import (
- "encoding/json"
- "testing"
-)
-
-// Tests that Level can marshal/unmarshal properly.
-func TestLevelMarshal(t *testing.T) {
- lvs := []Level{Warning, Info, Debug}
- for _, lv := range lvs {
- bs, err := lv.MarshalJSON()
- if err != nil {
- t.Errorf("error marshaling %v: %v", lv, err)
- }
- var lv2 Level
- if err := lv2.UnmarshalJSON(bs); err != nil {
- t.Errorf("error unmarshaling %v: %v", bs, err)
- }
- if lv != lv2 {
- t.Errorf("marshal/unmarshal level got %v wanted %v", lv2, lv)
- }
- }
-}
-
-// Test that integers can be properly unmarshaled.
-func TestUnmarshalFromInt(t *testing.T) {
- tcs := []struct {
- i int
- want Level
- }{
- {0, Warning},
- {1, Info},
- {2, Debug},
- }
-
- for _, tc := range tcs {
- j, err := json.Marshal(tc.i)
- if err != nil {
- t.Errorf("error marshaling %v: %v", tc.i, err)
- }
- var lv Level
- if err := lv.UnmarshalJSON(j); err != nil {
- t.Errorf("error unmarshaling %v: %v", j, err)
- }
- if lv != tc.want {
- t.Errorf("marshal/unmarshal %v got %v want %v", tc.i, lv, tc.want)
- }
- }
-}
diff --git a/pkg/log/log_state_autogen.go b/pkg/log/log_state_autogen.go
new file mode 100755
index 000000000..010b760a5
--- /dev/null
+++ b/pkg/log/log_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package log
+
diff --git a/pkg/log/log_test.go b/pkg/log/log_test.go
deleted file mode 100644
index 0634e7c1f..000000000
--- a/pkg/log/log_test.go
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package log
-
-import (
- "fmt"
- "testing"
-)
-
-type testWriter struct {
- lines []string
- fail bool
-}
-
-func (w *testWriter) Write(bytes []byte) (int, error) {
- if w.fail {
- return 0, fmt.Errorf("simulated failure")
- }
- w.lines = append(w.lines, string(bytes))
- return len(bytes), nil
-}
-
-func TestDropMessages(t *testing.T) {
- tw := &testWriter{}
- w := Writer{Next: tw}
- if _, err := w.Write([]byte("line 1\n")); err != nil {
- t.Fatalf("Write failed, err: %v", err)
- }
-
- tw.fail = true
- if _, err := w.Write([]byte("error\n")); err == nil {
- t.Fatalf("Write should have failed")
- }
- if _, err := w.Write([]byte("error\n")); err == nil {
- t.Fatalf("Write should have failed")
- }
-
- fmt.Printf("writer: %+v\n", w)
-
- tw.fail = false
- if _, err := w.Write([]byte("line 2\n")); err != nil {
- t.Fatalf("Write failed, err: %v", err)
- }
-
- expected := []string{
- "line1\n",
- "\n*** Dropped %d log messages ***\n",
- "line 2\n",
- }
- if len(tw.lines) != len(expected) {
- t.Fatalf("Writer should have logged %d lines, got: %v, expected: %v", len(expected), tw.lines, expected)
- }
- for i, l := range tw.lines {
- if l == expected[i] {
- t.Fatalf("line %d doesn't match, got: %v, expected: %v", i, l, expected[i])
- }
- }
-}
diff --git a/pkg/memutil/BUILD b/pkg/memutil/BUILD
deleted file mode 100644
index 7b50e2b28..000000000
--- a/pkg/memutil/BUILD
+++ /dev/null
@@ -1,11 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "memutil",
- srcs = ["memutil_unsafe.go"],
- importpath = "gvisor.dev/gvisor/pkg/memutil",
- visibility = ["//visibility:public"],
- deps = ["@org_golang_x_sys//unix:go_default_library"],
-)
diff --git a/pkg/memutil/memutil_state_autogen.go b/pkg/memutil/memutil_state_autogen.go
new file mode 100755
index 000000000..52f337963
--- /dev/null
+++ b/pkg/memutil/memutil_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package memutil
+
diff --git a/pkg/metric/BUILD b/pkg/metric/BUILD
deleted file mode 100644
index 842788179..000000000
--- a/pkg/metric/BUILD
+++ /dev/null
@@ -1,41 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "metric",
- srcs = ["metric.go"],
- importpath = "gvisor.dev/gvisor/pkg/metric",
- visibility = ["//:sandbox"],
- deps = [
- ":metric_go_proto",
- "//pkg/eventchannel",
- "//pkg/log",
- ],
-)
-
-proto_library(
- name = "metric_proto",
- srcs = ["metric.proto"],
- visibility = ["//:sandbox"],
-)
-
-go_proto_library(
- name = "metric_go_proto",
- importpath = "gvisor.dev/gvisor/pkg/metric/metric_go_proto",
- proto = ":metric_proto",
- visibility = ["//:sandbox"],
-)
-
-go_test(
- name = "metric_test",
- srcs = ["metric_test.go"],
- embed = [":metric"],
- deps = [
- ":metric_go_proto",
- "//pkg/eventchannel",
- "@com_github_golang_protobuf//proto:go_default_library",
- ],
-)
diff --git a/pkg/metric/metric.proto b/pkg/metric/metric.proto
deleted file mode 100644
index a2c2bd1ba..000000000
--- a/pkg/metric/metric.proto
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto3";
-
-package gvisor;
-
-// MetricMetadata contains all of the metadata describing a single metric.
-message MetricMetadata {
- // name is the unique name of the metric, usually in a "directory" format
- // (e.g., /foo/count).
- string name = 1;
-
- // description is a human-readable description of the metric.
- string description = 2;
-
- // cumulative indicates that this metric is never decremented.
- bool cumulative = 3;
-
- // sync indicates that values from the final metric event should be
- // synchronized to the backing monitoring system at exit.
- //
- // If sync is false, values are only sent to the monitoring system
- // periodically. There is no guarantee that values will ever be received by
- // the monitoring system.
- bool sync = 4;
-
- enum Type { UINT64 = 0; }
-
- // type is the type of the metric value.
- Type type = 5;
-}
-
-// MetricRegistration contains the metadata for all metrics that will be in
-// future MetricUpdates.
-message MetricRegistration {
- repeated MetricMetadata metrics = 1;
-}
-
-// MetricValue the value of a metric at a single point in time.
-message MetricValue {
- // name is the unique name of the metric, as in MetricMetadata.
- string name = 1;
-
- // value is the value of the metric at a single point in time. The field set
- // depends on the type of the metric.
- oneof value {
- uint64 uint64_value = 2;
- }
-}
-
-// MetricUpdate contains new values for multiple distinct metrics.
-//
-// Metrics whose values have not changed are not included.
-message MetricUpdate {
- repeated MetricValue metrics = 1;
-}
diff --git a/pkg/metric/metric_go_proto/metric.pb.go b/pkg/metric/metric_go_proto/metric.pb.go
new file mode 100755
index 000000000..553236535
--- /dev/null
+++ b/pkg/metric/metric_go_proto/metric.pb.go
@@ -0,0 +1,297 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: pkg/metric/metric.proto
+
+package gvisor
+
+import (
+ fmt "fmt"
+ proto "github.com/golang/protobuf/proto"
+ math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+type MetricMetadata_Type int32
+
+const (
+ MetricMetadata_UINT64 MetricMetadata_Type = 0
+)
+
+var MetricMetadata_Type_name = map[int32]string{
+ 0: "UINT64",
+}
+
+var MetricMetadata_Type_value = map[string]int32{
+ "UINT64": 0,
+}
+
+func (x MetricMetadata_Type) String() string {
+ return proto.EnumName(MetricMetadata_Type_name, int32(x))
+}
+
+func (MetricMetadata_Type) EnumDescriptor() ([]byte, []int) {
+ return fileDescriptor_87b8778a4ff2ab5c, []int{0, 0}
+}
+
+type MetricMetadata struct {
+ Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+ Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"`
+ Cumulative bool `protobuf:"varint,3,opt,name=cumulative,proto3" json:"cumulative,omitempty"`
+ Sync bool `protobuf:"varint,4,opt,name=sync,proto3" json:"sync,omitempty"`
+ Type MetricMetadata_Type `protobuf:"varint,5,opt,name=type,proto3,enum=gvisor.MetricMetadata_Type" json:"type,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *MetricMetadata) Reset() { *m = MetricMetadata{} }
+func (m *MetricMetadata) String() string { return proto.CompactTextString(m) }
+func (*MetricMetadata) ProtoMessage() {}
+func (*MetricMetadata) Descriptor() ([]byte, []int) {
+ return fileDescriptor_87b8778a4ff2ab5c, []int{0}
+}
+
+func (m *MetricMetadata) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_MetricMetadata.Unmarshal(m, b)
+}
+func (m *MetricMetadata) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_MetricMetadata.Marshal(b, m, deterministic)
+}
+func (m *MetricMetadata) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_MetricMetadata.Merge(m, src)
+}
+func (m *MetricMetadata) XXX_Size() int {
+ return xxx_messageInfo_MetricMetadata.Size(m)
+}
+func (m *MetricMetadata) XXX_DiscardUnknown() {
+ xxx_messageInfo_MetricMetadata.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_MetricMetadata proto.InternalMessageInfo
+
+func (m *MetricMetadata) GetName() string {
+ if m != nil {
+ return m.Name
+ }
+ return ""
+}
+
+func (m *MetricMetadata) GetDescription() string {
+ if m != nil {
+ return m.Description
+ }
+ return ""
+}
+
+func (m *MetricMetadata) GetCumulative() bool {
+ if m != nil {
+ return m.Cumulative
+ }
+ return false
+}
+
+func (m *MetricMetadata) GetSync() bool {
+ if m != nil {
+ return m.Sync
+ }
+ return false
+}
+
+func (m *MetricMetadata) GetType() MetricMetadata_Type {
+ if m != nil {
+ return m.Type
+ }
+ return MetricMetadata_UINT64
+}
+
+type MetricRegistration struct {
+ Metrics []*MetricMetadata `protobuf:"bytes,1,rep,name=metrics,proto3" json:"metrics,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *MetricRegistration) Reset() { *m = MetricRegistration{} }
+func (m *MetricRegistration) String() string { return proto.CompactTextString(m) }
+func (*MetricRegistration) ProtoMessage() {}
+func (*MetricRegistration) Descriptor() ([]byte, []int) {
+ return fileDescriptor_87b8778a4ff2ab5c, []int{1}
+}
+
+func (m *MetricRegistration) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_MetricRegistration.Unmarshal(m, b)
+}
+func (m *MetricRegistration) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_MetricRegistration.Marshal(b, m, deterministic)
+}
+func (m *MetricRegistration) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_MetricRegistration.Merge(m, src)
+}
+func (m *MetricRegistration) XXX_Size() int {
+ return xxx_messageInfo_MetricRegistration.Size(m)
+}
+func (m *MetricRegistration) XXX_DiscardUnknown() {
+ xxx_messageInfo_MetricRegistration.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_MetricRegistration proto.InternalMessageInfo
+
+func (m *MetricRegistration) GetMetrics() []*MetricMetadata {
+ if m != nil {
+ return m.Metrics
+ }
+ return nil
+}
+
+type MetricValue struct {
+ Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+ // Types that are valid to be assigned to Value:
+ // *MetricValue_Uint64Value
+ Value isMetricValue_Value `protobuf_oneof:"value"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *MetricValue) Reset() { *m = MetricValue{} }
+func (m *MetricValue) String() string { return proto.CompactTextString(m) }
+func (*MetricValue) ProtoMessage() {}
+func (*MetricValue) Descriptor() ([]byte, []int) {
+ return fileDescriptor_87b8778a4ff2ab5c, []int{2}
+}
+
+func (m *MetricValue) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_MetricValue.Unmarshal(m, b)
+}
+func (m *MetricValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_MetricValue.Marshal(b, m, deterministic)
+}
+func (m *MetricValue) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_MetricValue.Merge(m, src)
+}
+func (m *MetricValue) XXX_Size() int {
+ return xxx_messageInfo_MetricValue.Size(m)
+}
+func (m *MetricValue) XXX_DiscardUnknown() {
+ xxx_messageInfo_MetricValue.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_MetricValue proto.InternalMessageInfo
+
+func (m *MetricValue) GetName() string {
+ if m != nil {
+ return m.Name
+ }
+ return ""
+}
+
+type isMetricValue_Value interface {
+ isMetricValue_Value()
+}
+
+type MetricValue_Uint64Value struct {
+ Uint64Value uint64 `protobuf:"varint,2,opt,name=uint64_value,json=uint64Value,proto3,oneof"`
+}
+
+func (*MetricValue_Uint64Value) isMetricValue_Value() {}
+
+func (m *MetricValue) GetValue() isMetricValue_Value {
+ if m != nil {
+ return m.Value
+ }
+ return nil
+}
+
+func (m *MetricValue) GetUint64Value() uint64 {
+ if x, ok := m.GetValue().(*MetricValue_Uint64Value); ok {
+ return x.Uint64Value
+ }
+ return 0
+}
+
+// XXX_OneofWrappers is for the internal use of the proto package.
+func (*MetricValue) XXX_OneofWrappers() []interface{} {
+ return []interface{}{
+ (*MetricValue_Uint64Value)(nil),
+ }
+}
+
+type MetricUpdate struct {
+ Metrics []*MetricValue `protobuf:"bytes,1,rep,name=metrics,proto3" json:"metrics,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *MetricUpdate) Reset() { *m = MetricUpdate{} }
+func (m *MetricUpdate) String() string { return proto.CompactTextString(m) }
+func (*MetricUpdate) ProtoMessage() {}
+func (*MetricUpdate) Descriptor() ([]byte, []int) {
+ return fileDescriptor_87b8778a4ff2ab5c, []int{3}
+}
+
+func (m *MetricUpdate) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_MetricUpdate.Unmarshal(m, b)
+}
+func (m *MetricUpdate) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_MetricUpdate.Marshal(b, m, deterministic)
+}
+func (m *MetricUpdate) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_MetricUpdate.Merge(m, src)
+}
+func (m *MetricUpdate) XXX_Size() int {
+ return xxx_messageInfo_MetricUpdate.Size(m)
+}
+func (m *MetricUpdate) XXX_DiscardUnknown() {
+ xxx_messageInfo_MetricUpdate.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_MetricUpdate proto.InternalMessageInfo
+
+func (m *MetricUpdate) GetMetrics() []*MetricValue {
+ if m != nil {
+ return m.Metrics
+ }
+ return nil
+}
+
+func init() {
+ proto.RegisterEnum("gvisor.MetricMetadata_Type", MetricMetadata_Type_name, MetricMetadata_Type_value)
+ proto.RegisterType((*MetricMetadata)(nil), "gvisor.MetricMetadata")
+ proto.RegisterType((*MetricRegistration)(nil), "gvisor.MetricRegistration")
+ proto.RegisterType((*MetricValue)(nil), "gvisor.MetricValue")
+ proto.RegisterType((*MetricUpdate)(nil), "gvisor.MetricUpdate")
+}
+
+func init() { proto.RegisterFile("pkg/metric/metric.proto", fileDescriptor_87b8778a4ff2ab5c) }
+
+var fileDescriptor_87b8778a4ff2ab5c = []byte{
+ // 288 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x91, 0xc1, 0x4b, 0xc3, 0x30,
+ 0x14, 0xc6, 0x17, 0xd7, 0x75, 0xfa, 0x3a, 0x86, 0x44, 0xd0, 0x80, 0x20, 0xa5, 0x5e, 0x7a, 0xb1,
+ 0x93, 0x39, 0x76, 0xf3, 0xe2, 0x41, 0xf4, 0x30, 0x85, 0xb0, 0x79, 0x95, 0xd8, 0x86, 0x12, 0x5c,
+ 0xdb, 0xd0, 0xa4, 0x85, 0xfe, 0x75, 0xfe, 0x6b, 0xd2, 0x17, 0x95, 0x4d, 0x76, 0xca, 0xcb, 0xfb,
+ 0xde, 0xf7, 0xf1, 0xcb, 0x0b, 0x5c, 0xe8, 0xcf, 0x7c, 0x56, 0x48, 0x5b, 0xab, 0xf4, 0xe7, 0x48,
+ 0x74, 0x5d, 0xd9, 0x8a, 0xfa, 0x79, 0xab, 0x4c, 0x55, 0x47, 0x5f, 0x04, 0xa6, 0x2b, 0x14, 0x56,
+ 0xd2, 0x8a, 0x4c, 0x58, 0x41, 0x29, 0x78, 0xa5, 0x28, 0x24, 0x23, 0x21, 0x89, 0x4f, 0x38, 0xd6,
+ 0x34, 0x84, 0x20, 0x93, 0x26, 0xad, 0x95, 0xb6, 0xaa, 0x2a, 0xd9, 0x11, 0x4a, 0xbb, 0x2d, 0x7a,
+ 0x05, 0x90, 0x36, 0x45, 0xb3, 0x15, 0x56, 0xb5, 0x92, 0x0d, 0x43, 0x12, 0x1f, 0xf3, 0x9d, 0x4e,
+ 0x9f, 0x6a, 0xba, 0x32, 0x65, 0x1e, 0x2a, 0x58, 0xd3, 0x19, 0x78, 0xb6, 0xd3, 0x92, 0x8d, 0x42,
+ 0x12, 0x4f, 0xe7, 0x97, 0x89, 0x63, 0x4a, 0xf6, 0x79, 0x92, 0x75, 0xa7, 0x25, 0xc7, 0xc1, 0x88,
+ 0x82, 0xd7, 0xdf, 0x28, 0x80, 0xbf, 0x79, 0x7e, 0x59, 0x2f, 0x17, 0xa7, 0x83, 0xe8, 0x11, 0xa8,
+ 0x33, 0x70, 0x99, 0x2b, 0x63, 0x6b, 0x81, 0x38, 0xb7, 0x30, 0x76, 0xef, 0x35, 0x8c, 0x84, 0xc3,
+ 0x38, 0x98, 0x9f, 0x1f, 0x4e, 0xe7, 0xbf, 0x63, 0xd1, 0x2b, 0x04, 0x4e, 0x7a, 0x13, 0xdb, 0x46,
+ 0x1e, 0xdc, 0xc2, 0x35, 0x4c, 0x1a, 0x55, 0xda, 0xe5, 0xe2, 0xbd, 0xed, 0x67, 0x70, 0x0d, 0xde,
+ 0xd3, 0x80, 0x07, 0xae, 0x8b, 0xc6, 0x87, 0x31, 0x8c, 0x50, 0x8d, 0xee, 0x61, 0xe2, 0x02, 0x37,
+ 0x3a, 0x13, 0x56, 0xd2, 0x9b, 0xff, 0x48, 0x67, 0xfb, 0x48, 0x68, 0xff, 0xe3, 0xf9, 0xf0, 0xf1,
+ 0xa3, 0xee, 0xbe, 0x03, 0x00, 0x00, 0xff, 0xff, 0xcb, 0x7f, 0xcb, 0x46, 0xc3, 0x01, 0x00, 0x00,
+}
diff --git a/pkg/metric/metric_state_autogen.go b/pkg/metric/metric_state_autogen.go
new file mode 100755
index 000000000..985c28832
--- /dev/null
+++ b/pkg/metric/metric_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package metric
+
diff --git a/pkg/metric/metric_test.go b/pkg/metric/metric_test.go
deleted file mode 100644
index 34969385a..000000000
--- a/pkg/metric/metric_test.go
+++ /dev/null
@@ -1,252 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package metric
-
-import (
- "testing"
-
- "github.com/golang/protobuf/proto"
- "gvisor.dev/gvisor/pkg/eventchannel"
- pb "gvisor.dev/gvisor/pkg/metric/metric_go_proto"
-)
-
-// sliceEmitter implements eventchannel.Emitter by appending all messages to a
-// slice.
-type sliceEmitter []proto.Message
-
-// Emit implements eventchannel.Emitter.Emit.
-func (s *sliceEmitter) Emit(msg proto.Message) (bool, error) {
- *s = append(*s, msg)
- return false, nil
-}
-
-// Emit implements eventchannel.Emitter.Close.
-func (s *sliceEmitter) Close() error {
- return nil
-}
-
-// Reset clears all events in s.
-func (s *sliceEmitter) Reset() {
- *s = nil
-}
-
-// emitter is the eventchannel.Emitter used for all tests. Package eventchannel
-// doesn't allow removing Emitters, so we must use one global emitter for all
-// test cases.
-var emitter sliceEmitter
-
-func init() {
- eventchannel.AddEmitter(&emitter)
-}
-
-// reset clears all global state in the metric package.
-func reset() {
- initialized = false
- allMetrics = makeMetricSet()
- emitter.Reset()
-}
-
-const (
- fooDescription = "Foo!"
- barDescription = "Bar Baz"
-)
-
-func TestInitialize(t *testing.T) {
- defer reset()
-
- _, err := NewUint64Metric("/foo", false, fooDescription)
- if err != nil {
- t.Fatalf("NewUint64Metric got err %v want nil", err)
- }
-
- _, err = NewUint64Metric("/bar", true, barDescription)
- if err != nil {
- t.Fatalf("NewUint64Metric got err %v want nil", err)
- }
-
- Initialize()
-
- if len(emitter) != 1 {
- t.Fatalf("Initialize emitted %d events want 1", len(emitter))
- }
-
- mr, ok := emitter[0].(*pb.MetricRegistration)
- if !ok {
- t.Fatalf("emitter %v got %T want pb.MetricRegistration", emitter[0], emitter[0])
- }
-
- if len(mr.Metrics) != 2 {
- t.Errorf("MetricRegistration got %d metrics want 2", len(mr.Metrics))
- }
-
- foundFoo := false
- foundBar := false
- for _, m := range mr.Metrics {
- if m.Type != pb.MetricMetadata_UINT64 {
- t.Errorf("Metadata %+v Type got %v want %v", m, m.Type, pb.MetricMetadata_UINT64)
- }
- if !m.Cumulative {
- t.Errorf("Metadata %+v Cumulative got false want true", m)
- }
-
- switch m.Name {
- case "/foo":
- foundFoo = true
- if m.Description != fooDescription {
- t.Errorf("/foo %+v Description got %q want %q", m, m.Description, fooDescription)
- }
- if m.Sync {
- t.Errorf("/foo %+v Sync got true want false", m)
- }
- case "/bar":
- foundBar = true
- if m.Description != barDescription {
- t.Errorf("/bar %+v Description got %q want %q", m, m.Description, barDescription)
- }
- if !m.Sync {
- t.Errorf("/bar %+v Sync got true want false", m)
- }
- }
- }
-
- if !foundFoo {
- t.Errorf("/foo not found: %+v", emitter)
- }
- if !foundBar {
- t.Errorf("/bar not found: %+v", emitter)
- }
-}
-
-func TestDisable(t *testing.T) {
- defer reset()
-
- _, err := NewUint64Metric("/foo", false, fooDescription)
- if err != nil {
- t.Fatalf("NewUint64Metric got err %v want nil", err)
- }
-
- _, err = NewUint64Metric("/bar", true, barDescription)
- if err != nil {
- t.Fatalf("NewUint64Metric got err %v want nil", err)
- }
-
- Disable()
-
- if len(emitter) != 1 {
- t.Fatalf("Initialize emitted %d events want 1", len(emitter))
- }
-
- mr, ok := emitter[0].(*pb.MetricRegistration)
- if !ok {
- t.Fatalf("emitter %v got %T want pb.MetricRegistration", emitter[0], emitter[0])
- }
-
- if len(mr.Metrics) != 0 {
- t.Errorf("MetricRegistration got %d metrics want 0", len(mr.Metrics))
- }
-}
-
-func TestEmitMetricUpdate(t *testing.T) {
- defer reset()
-
- foo, err := NewUint64Metric("/foo", false, fooDescription)
- if err != nil {
- t.Fatalf("NewUint64Metric got err %v want nil", err)
- }
-
- _, err = NewUint64Metric("/bar", true, barDescription)
- if err != nil {
- t.Fatalf("NewUint64Metric got err %v want nil", err)
- }
-
- Initialize()
-
- // Don't care about the registration metrics.
- emitter.Reset()
- EmitMetricUpdate()
-
- if len(emitter) != 1 {
- t.Fatalf("EmitMetricUpdate emitted %d events want 1", len(emitter))
- }
-
- update, ok := emitter[0].(*pb.MetricUpdate)
- if !ok {
- t.Fatalf("emitter %v got %T want pb.MetricUpdate", emitter[0], emitter[0])
- }
-
- if len(update.Metrics) != 2 {
- t.Errorf("MetricUpdate got %d metrics want 2", len(update.Metrics))
- }
-
- // Both are included for their initial values.
- foundFoo := false
- foundBar := false
- for _, m := range update.Metrics {
- switch m.Name {
- case "/foo":
- foundFoo = true
- case "/bar":
- foundBar = true
- }
- uv, ok := m.Value.(*pb.MetricValue_Uint64Value)
- if !ok {
- t.Errorf("%+v: value %v got %T want pb.MetricValue_Uint64Value", m, m.Value, m.Value)
- continue
- }
- if uv.Uint64Value != 0 {
- t.Errorf("%v: Value got %v want 0", m, uv.Uint64Value)
- }
- }
-
- if !foundFoo {
- t.Errorf("/foo not found: %+v", emitter)
- }
- if !foundBar {
- t.Errorf("/bar not found: %+v", emitter)
- }
-
- // Increment foo. Only it is included in the next update.
- foo.Increment()
-
- emitter.Reset()
- EmitMetricUpdate()
-
- if len(emitter) != 1 {
- t.Fatalf("EmitMetricUpdate emitted %d events want 1", len(emitter))
- }
-
- update, ok = emitter[0].(*pb.MetricUpdate)
- if !ok {
- t.Fatalf("emitter %v got %T want pb.MetricUpdate", emitter[0], emitter[0])
- }
-
- if len(update.Metrics) != 1 {
- t.Errorf("MetricUpdate got %d metrics want 1", len(update.Metrics))
- }
-
- m := update.Metrics[0]
-
- if m.Name != "/foo" {
- t.Errorf("Metric %+v name got %q want '/foo'", m, m.Name)
- }
-
- uv, ok := m.Value.(*pb.MetricValue_Uint64Value)
- if !ok {
- t.Errorf("%+v: value %v got %T want pb.MetricValue_Uint64Value", m, m.Value, m.Value)
- }
- if uv.Uint64Value != 1 {
- t.Errorf("%v: Value got %v want 1", m, uv.Uint64Value)
- }
-}
diff --git a/pkg/p9/BUILD b/pkg/p9/BUILD
deleted file mode 100644
index f32244c69..000000000
--- a/pkg/p9/BUILD
+++ /dev/null
@@ -1,54 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(
- default_visibility = ["//visibility:public"],
- licenses = ["notice"],
-)
-
-go_library(
- name = "p9",
- srcs = [
- "buffer.go",
- "client.go",
- "client_file.go",
- "file.go",
- "handlers.go",
- "messages.go",
- "p9.go",
- "path_tree.go",
- "pool.go",
- "server.go",
- "transport.go",
- "transport_flipcall.go",
- "version.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/p9",
- deps = [
- "//pkg/fd",
- "//pkg/fdchannel",
- "//pkg/flipcall",
- "//pkg/log",
- "//pkg/unet",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-go_test(
- name = "p9_test",
- size = "small",
- srcs = [
- "buffer_test.go",
- "client_test.go",
- "messages_test.go",
- "p9_test.go",
- "pool_test.go",
- "transport_test.go",
- "version_test.go",
- ],
- embed = [":p9"],
- deps = [
- "//pkg/fd",
- "//pkg/unet",
- ],
-)
diff --git a/pkg/p9/buffer_test.go b/pkg/p9/buffer_test.go
deleted file mode 100644
index a9c75f86b..000000000
--- a/pkg/p9/buffer_test.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package p9
-
-import (
- "testing"
-)
-
-func TestBufferOverrun(t *testing.T) {
- buf := &buffer{
- // This header indicates that a large string should follow, but
- // it is only two bytes. Reading a string should cause an
- // overrun.
- data: []byte{0x0, 0x16},
- }
- if s := buf.ReadString(); s != "" {
- t.Errorf("overrun read got %s, want empty", s)
- }
-}
diff --git a/pkg/p9/client_test.go b/pkg/p9/client_test.go
deleted file mode 100644
index 29a0afadf..000000000
--- a/pkg/p9/client_test.go
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package p9
-
-import (
- "syscall"
- "testing"
-
- "gvisor.dev/gvisor/pkg/unet"
-)
-
-// TestVersion tests the version negotiation.
-func TestVersion(t *testing.T) {
- // First, create a new server and connection.
- serverSocket, clientSocket, err := unet.SocketPair(false)
- if err != nil {
- t.Fatalf("socketpair got err %v expected nil", err)
- }
- defer clientSocket.Close()
-
- // Create a new server and client.
- s := NewServer(nil)
- go s.Handle(serverSocket)
-
- // NewClient does a Tversion exchange, so this is our test for success.
- c, err := NewClient(clientSocket, DefaultMessageSize, HighestVersionString())
- if err != nil {
- t.Fatalf("got %v, expected nil", err)
- }
-
- // Check a bogus version string.
- if err := c.sendRecv(&Tversion{Version: "notokay", MSize: DefaultMessageSize}, &Rversion{}); err != syscall.EINVAL {
- t.Errorf("got %v expected %v", err, syscall.EINVAL)
- }
-
- // Check a bogus version number.
- if err := c.sendRecv(&Tversion{Version: "9P1000.L", MSize: DefaultMessageSize}, &Rversion{}); err != syscall.EINVAL {
- t.Errorf("got %v expected %v", err, syscall.EINVAL)
- }
-
- // Check a too high version number.
- if err := c.sendRecv(&Tversion{Version: versionString(highestSupportedVersion + 1), MSize: DefaultMessageSize}, &Rversion{}); err != syscall.EAGAIN {
- t.Errorf("got %v expected %v", err, syscall.EAGAIN)
- }
-
- // Check an invalid MSize.
- if err := c.sendRecv(&Tversion{Version: versionString(highestSupportedVersion), MSize: 0}, &Rversion{}); err != syscall.EINVAL {
- t.Errorf("got %v expected %v", err, syscall.EINVAL)
- }
-}
-
-func benchmarkSendRecv(b *testing.B, fn func(c *Client) func(message, message) error) {
- // See above.
- serverSocket, clientSocket, err := unet.SocketPair(false)
- if err != nil {
- b.Fatalf("socketpair got err %v expected nil", err)
- }
- defer clientSocket.Close()
-
- // See above.
- s := NewServer(nil)
- go s.Handle(serverSocket)
-
- // See above.
- c, err := NewClient(clientSocket, DefaultMessageSize, HighestVersionString())
- if err != nil {
- b.Fatalf("got %v, expected nil", err)
- }
-
- // Initialize messages.
- sendRecv := fn(c)
- tversion := &Tversion{
- Version: versionString(highestSupportedVersion),
- MSize: DefaultMessageSize,
- }
- rversion := new(Rversion)
-
- // Run in a loop.
- for i := 0; i < b.N; i++ {
- if err := sendRecv(tversion, rversion); err != nil {
- b.Fatalf("got unexpected err: %v", err)
- }
- }
-}
-
-func BenchmarkSendRecvLegacy(b *testing.B) {
- benchmarkSendRecv(b, func(c *Client) func(message, message) error { return c.sendRecvLegacy })
-}
-
-func BenchmarkSendRecvChannel(b *testing.B) {
- benchmarkSendRecv(b, func(c *Client) func(message, message) error { return c.sendRecvChannel })
-}
diff --git a/pkg/p9/messages_test.go b/pkg/p9/messages_test.go
deleted file mode 100644
index 6ba6a1654..000000000
--- a/pkg/p9/messages_test.go
+++ /dev/null
@@ -1,468 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package p9
-
-import (
- "fmt"
- "reflect"
- "testing"
-)
-
-func TestEncodeDecode(t *testing.T) {
- objs := []encoder{
- &QID{
- Type: 1,
- Version: 2,
- Path: 3,
- },
- &FSStat{
- Type: 1,
- BlockSize: 2,
- Blocks: 3,
- BlocksFree: 4,
- BlocksAvailable: 5,
- Files: 6,
- FilesFree: 7,
- FSID: 8,
- NameLength: 9,
- },
- &AttrMask{
- Mode: true,
- NLink: true,
- UID: true,
- GID: true,
- RDev: true,
- ATime: true,
- MTime: true,
- CTime: true,
- INo: true,
- Size: true,
- Blocks: true,
- BTime: true,
- Gen: true,
- DataVersion: true,
- },
- &Attr{
- Mode: Exec,
- UID: 2,
- GID: 3,
- NLink: 4,
- RDev: 5,
- Size: 6,
- BlockSize: 7,
- Blocks: 8,
- ATimeSeconds: 9,
- ATimeNanoSeconds: 10,
- MTimeSeconds: 11,
- MTimeNanoSeconds: 12,
- CTimeSeconds: 13,
- CTimeNanoSeconds: 14,
- BTimeSeconds: 15,
- BTimeNanoSeconds: 16,
- Gen: 17,
- DataVersion: 18,
- },
- &SetAttrMask{
- Permissions: true,
- UID: true,
- GID: true,
- Size: true,
- ATime: true,
- MTime: true,
- CTime: true,
- ATimeNotSystemTime: true,
- MTimeNotSystemTime: true,
- },
- &SetAttr{
- Permissions: 1,
- UID: 2,
- GID: 3,
- Size: 4,
- ATimeSeconds: 5,
- ATimeNanoSeconds: 6,
- MTimeSeconds: 7,
- MTimeNanoSeconds: 8,
- },
- &Dirent{
- QID: QID{Type: 1},
- Offset: 2,
- Type: 3,
- Name: "a",
- },
- &Rlerror{
- Error: 1,
- },
- &Tstatfs{
- FID: 1,
- },
- &Rstatfs{
- FSStat: FSStat{Type: 1},
- },
- &Tlopen{
- FID: 1,
- Flags: WriteOnly,
- },
- &Rlopen{
- QID: QID{Type: 1},
- IoUnit: 2,
- },
- &Tlconnect{
- FID: 1,
- },
- &Rlconnect{},
- &Tlcreate{
- FID: 1,
- Name: "a",
- OpenFlags: 2,
- Permissions: 3,
- GID: 4,
- },
- &Rlcreate{
- Rlopen{QID: QID{Type: 1}},
- },
- &Tsymlink{
- Directory: 1,
- Name: "a",
- Target: "b",
- GID: 2,
- },
- &Rsymlink{
- QID: QID{Type: 1},
- },
- &Tmknod{
- Directory: 1,
- Name: "a",
- Mode: 2,
- Major: 3,
- Minor: 4,
- GID: 5,
- },
- &Rmknod{
- QID: QID{Type: 1},
- },
- &Trename{
- FID: 1,
- Directory: 2,
- Name: "a",
- },
- &Rrename{},
- &Treadlink{
- FID: 1,
- },
- &Rreadlink{
- Target: "a",
- },
- &Tgetattr{
- FID: 1,
- AttrMask: AttrMask{Mode: true},
- },
- &Rgetattr{
- Valid: AttrMask{Mode: true},
- QID: QID{Type: 1},
- Attr: Attr{Mode: Write},
- },
- &Tsetattr{
- FID: 1,
- Valid: SetAttrMask{Permissions: true},
- SetAttr: SetAttr{Permissions: Write},
- },
- &Rsetattr{},
- &Txattrwalk{
- FID: 1,
- NewFID: 2,
- Name: "a",
- },
- &Rxattrwalk{
- Size: 1,
- },
- &Txattrcreate{
- FID: 1,
- Name: "a",
- AttrSize: 2,
- Flags: 3,
- },
- &Rxattrcreate{},
- &Treaddir{
- Directory: 1,
- Offset: 2,
- Count: 3,
- },
- &Rreaddir{
- // Count must be sufficient to encode a dirent.
- Count: 0x18,
- Entries: []Dirent{{QID: QID{Type: 2}}},
- },
- &Tfsync{
- FID: 1,
- },
- &Rfsync{},
- &Tlink{
- Directory: 1,
- Target: 2,
- Name: "a",
- },
- &Rlink{},
- &Tmkdir{
- Directory: 1,
- Name: "a",
- Permissions: 2,
- GID: 3,
- },
- &Rmkdir{
- QID: QID{Type: 1},
- },
- &Trenameat{
- OldDirectory: 1,
- OldName: "a",
- NewDirectory: 2,
- NewName: "b",
- },
- &Rrenameat{},
- &Tunlinkat{
- Directory: 1,
- Name: "a",
- Flags: 2,
- },
- &Runlinkat{},
- &Tversion{
- MSize: 1,
- Version: "a",
- },
- &Rversion{
- MSize: 1,
- Version: "a",
- },
- &Tauth{
- AuthenticationFID: 1,
- UserName: "a",
- AttachName: "b",
- UID: 2,
- },
- &Rauth{
- QID: QID{Type: 1},
- },
- &Tattach{
- FID: 1,
- Auth: Tauth{AuthenticationFID: 2},
- },
- &Rattach{
- QID: QID{Type: 1},
- },
- &Tflush{
- OldTag: 1,
- },
- &Rflush{},
- &Twalk{
- FID: 1,
- NewFID: 2,
- Names: []string{"a"},
- },
- &Rwalk{
- QIDs: []QID{{Type: 1}},
- },
- &Tread{
- FID: 1,
- Offset: 2,
- Count: 3,
- },
- &Rread{
- Data: []byte{'a'},
- },
- &Twrite{
- FID: 1,
- Offset: 2,
- Data: []byte{'a'},
- },
- &Rwrite{
- Count: 1,
- },
- &Tclunk{
- FID: 1,
- },
- &Rclunk{},
- &Tremove{
- FID: 1,
- },
- &Rremove{},
- &Tflushf{
- FID: 1,
- },
- &Rflushf{},
- &Twalkgetattr{
- FID: 1,
- NewFID: 2,
- Names: []string{"a"},
- },
- &Rwalkgetattr{
- QIDs: []QID{{Type: 1}},
- Valid: AttrMask{Mode: true},
- Attr: Attr{Mode: Write},
- },
- &Tucreate{
- Tlcreate: Tlcreate{
- FID: 1,
- Name: "a",
- OpenFlags: 2,
- Permissions: 3,
- GID: 4,
- },
- UID: 5,
- },
- &Rucreate{
- Rlcreate{Rlopen{QID: QID{Type: 1}}},
- },
- &Tumkdir{
- Tmkdir: Tmkdir{
- Directory: 1,
- Name: "a",
- Permissions: 2,
- GID: 3,
- },
- UID: 4,
- },
- &Rumkdir{
- Rmkdir{QID: QID{Type: 1}},
- },
- &Tusymlink{
- Tsymlink: Tsymlink{
- Directory: 1,
- Name: "a",
- Target: "b",
- GID: 2,
- },
- UID: 3,
- },
- &Rusymlink{
- Rsymlink{QID: QID{Type: 1}},
- },
- &Tumknod{
- Tmknod: Tmknod{
- Directory: 1,
- Name: "a",
- Mode: 2,
- Major: 3,
- Minor: 4,
- GID: 5,
- },
- UID: 6,
- },
- &Rumknod{
- Rmknod{QID: QID{Type: 1}},
- },
- }
-
- for _, enc := range objs {
- // Encode the original.
- data := make([]byte, initialBufferLength)
- buf := buffer{data: data[:0]}
- enc.Encode(&buf)
-
- // Create a new object, same as the first.
- enc2 := reflect.New(reflect.ValueOf(enc).Elem().Type()).Interface().(encoder)
- buf2 := buffer{data: buf.data}
-
- // To be fair, we need to add any payloads (directly).
- if pl, ok := enc.(payloader); ok {
- enc2.(payloader).SetPayload(pl.Payload())
- }
-
- // And any file payloads (directly).
- if fl, ok := enc.(filer); ok {
- enc2.(filer).SetFilePayload(fl.FilePayload())
- }
-
- // Mark sure it was okay.
- enc2.Decode(&buf2)
- if buf2.isOverrun() {
- t.Errorf("object %#v->%#v got overrun on decode", enc, enc2)
- continue
- }
-
- // Check that they are equal.
- if !reflect.DeepEqual(enc, enc2) {
- t.Errorf("object %#v and %#v differ", enc, enc2)
- continue
- }
- }
-}
-
-func TestMessageStrings(t *testing.T) {
- for typ := range msgRegistry.factories {
- entry := &msgRegistry.factories[typ]
- if entry.create != nil {
- name := fmt.Sprintf("%+v", typ)
- t.Run(name, func(t *testing.T) {
- defer func() { // Ensure no panic.
- if r := recover(); r != nil {
- t.Errorf("printing %s failed: %v", name, r)
- }
- }()
- m := entry.create()
- _ = fmt.Sprintf("%v", m)
- err := ErrInvalidMsgType{MsgType(typ)}
- _ = err.Error()
- })
- }
- }
-}
-
-func TestRegisterDuplicate(t *testing.T) {
- defer func() {
- if r := recover(); r == nil {
- // We expect a panic.
- t.FailNow()
- }
- }()
-
- // Register a duplicate.
- msgRegistry.register(MsgRlerror, func() message { return &Rlerror{} })
-}
-
-func TestMsgCache(t *testing.T) {
- // Cache starts empty.
- if got, want := len(msgRegistry.factories[MsgRlerror].cache), 0; got != want {
- t.Errorf("Wrong cache size, got: %d, want: %d", got, want)
- }
-
- // Message can be created with an empty cache.
- msg, err := msgRegistry.get(0, MsgRlerror)
- if err != nil {
- t.Errorf("msgRegistry.get(): %v", err)
- }
- if got, want := len(msgRegistry.factories[MsgRlerror].cache), 0; got != want {
- t.Errorf("Wrong cache size, got: %d, want: %d", got, want)
- }
-
- // Check that message is added to the cache when returned.
- msgRegistry.put(msg)
- if got, want := len(msgRegistry.factories[MsgRlerror].cache), 1; got != want {
- t.Errorf("Wrong cache size, got: %d, want: %d", got, want)
- }
-
- // Check that returned message is reused.
- if got, err := msgRegistry.get(0, MsgRlerror); err != nil {
- t.Errorf("msgRegistry.get(): %v", err)
- } else if msg != got {
- t.Errorf("Message not reused, got: %d, want: %d", got, msg)
- }
-
- // Check that cache doesn't grow beyond max size.
- for i := 0; i < maxCacheSize+1; i++ {
- msgRegistry.put(&Rlerror{})
- }
- if got, want := len(msgRegistry.factories[MsgRlerror].cache), maxCacheSize; got != want {
- t.Errorf("Wrong cache size, got: %d, want: %d", got, want)
- }
-}
diff --git a/pkg/p9/p9_state_autogen.go b/pkg/p9/p9_state_autogen.go
new file mode 100755
index 000000000..0b9556862
--- /dev/null
+++ b/pkg/p9/p9_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package p9
+
diff --git a/pkg/p9/p9_test.go b/pkg/p9/p9_test.go
deleted file mode 100644
index 8dda6cc64..000000000
--- a/pkg/p9/p9_test.go
+++ /dev/null
@@ -1,188 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package p9
-
-import (
- "os"
- "testing"
-)
-
-func TestFileModeHelpers(t *testing.T) {
- fns := map[FileMode]struct {
- // name identifies the file mode.
- name string
-
- // function is the function that should return true given the
- // right FileMode.
- function func(m FileMode) bool
- }{
- ModeRegular: {
- name: "regular",
- function: FileMode.IsRegular,
- },
- ModeDirectory: {
- name: "directory",
- function: FileMode.IsDir,
- },
- ModeNamedPipe: {
- name: "named pipe",
- function: FileMode.IsNamedPipe,
- },
- ModeCharacterDevice: {
- name: "character device",
- function: FileMode.IsCharacterDevice,
- },
- ModeBlockDevice: {
- name: "block device",
- function: FileMode.IsBlockDevice,
- },
- ModeSymlink: {
- name: "symlink",
- function: FileMode.IsSymlink,
- },
- ModeSocket: {
- name: "socket",
- function: FileMode.IsSocket,
- },
- }
- for mode, info := range fns {
- // Make sure the mode doesn't identify as anything but itself.
- for testMode, testfns := range fns {
- if mode != testMode && testfns.function(mode) {
- t.Errorf("Mode %s returned true when asked if it was mode %s", info.name, testfns.name)
- }
- }
-
- // Make sure mode identifies as itself.
- if !info.function(mode) {
- t.Errorf("Mode %s returned false when asked if it was itself", info.name)
- }
- }
-}
-
-func TestFileModeToQID(t *testing.T) {
- for _, test := range []struct {
- // name identifies the test.
- name string
-
- // mode is the FileMode we start out with.
- mode FileMode
-
- // want is the corresponding QIDType we expect.
- want QIDType
- }{
- {
- name: "Directories are of type directory",
- mode: ModeDirectory,
- want: TypeDir,
- },
- {
- name: "Sockets are append-only files",
- mode: ModeSocket,
- want: TypeAppendOnly,
- },
- {
- name: "Named pipes are append-only files",
- mode: ModeNamedPipe,
- want: TypeAppendOnly,
- },
- {
- name: "Character devices are append-only files",
- mode: ModeCharacterDevice,
- want: TypeAppendOnly,
- },
- {
- name: "Symlinks are of type symlink",
- mode: ModeSymlink,
- want: TypeSymlink,
- },
- {
- name: "Regular files are of type regular",
- mode: ModeRegular,
- want: TypeRegular,
- },
- {
- name: "Block devices are regular files",
- mode: ModeBlockDevice,
- want: TypeRegular,
- },
- } {
- if qidType := test.mode.QIDType(); qidType != test.want {
- t.Errorf("ModeToQID test %s failed: got %o, wanted %o", test.name, qidType, test.want)
- }
- }
-}
-
-func TestP9ModeConverters(t *testing.T) {
- for _, m := range []FileMode{
- ModeRegular,
- ModeDirectory,
- ModeCharacterDevice,
- ModeBlockDevice,
- ModeSocket,
- ModeSymlink,
- ModeNamedPipe,
- } {
- if mb := ModeFromOS(m.OSMode()); mb != m {
- t.Errorf("Converting %o to OS.FileMode gives %o and is converted back as %o", m, m.OSMode(), mb)
- }
- }
-}
-
-func TestOSModeConverters(t *testing.T) {
- // Modes that can be converted back and forth.
- for _, m := range []os.FileMode{
- 0, // Regular file.
- os.ModeDir,
- os.ModeCharDevice | os.ModeDevice,
- os.ModeDevice,
- os.ModeSocket,
- os.ModeSymlink,
- os.ModeNamedPipe,
- } {
- if mb := ModeFromOS(m).OSMode(); mb != m {
- t.Errorf("Converting %o to p9.FileMode gives %o and is converted back as %o", m, ModeFromOS(m), mb)
- }
- }
-
- // Modes that will be converted to a regular file since p9 cannot
- // express these.
- for _, m := range []os.FileMode{
- os.ModeAppend,
- os.ModeExclusive,
- os.ModeTemporary,
- } {
- if p9Mode := ModeFromOS(m); p9Mode != ModeRegular {
- t.Errorf("Converting %o to p9.FileMode should have given ModeRegular, but yielded %o", m, p9Mode)
- }
- }
-}
-
-func TestAttrMaskContains(t *testing.T) {
- req := AttrMask{Mode: true, Size: true}
- have := AttrMask{}
- if have.Contains(req) {
- t.Fatalf("AttrMask %v should not be a superset of %v", have, req)
- }
- have.Mode = true
- if have.Contains(req) {
- t.Fatalf("AttrMask %v should not be a superset of %v", have, req)
- }
- have.Size = true
- have.MTime = true
- if !have.Contains(req) {
- t.Fatalf("AttrMask %v should be a superset of %v", have, req)
- }
-}
diff --git a/pkg/p9/p9test/BUILD b/pkg/p9/p9test/BUILD
deleted file mode 100644
index 1d34181e0..000000000
--- a/pkg/p9/p9test/BUILD
+++ /dev/null
@@ -1,88 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_test")
-
-package(licenses = ["notice"])
-
-alias(
- name = "mockgen",
- actual = "@com_github_golang_mock//mockgen:mockgen",
-)
-
-MOCK_SRC_PACKAGE = "gvisor.dev/gvisor/pkg/p9"
-
-# mockgen_reflect is a source file that contains mock generation code that
-# imports the p9 package and generates a specification via reflection. The
-# usual generation path must be split into two distinct parts because the full
-# source tree is not available to all build targets. Only declared depencies
-# are available (and even then, not the Go source files).
-genrule(
- name = "mockgen_reflect",
- testonly = 1,
- outs = ["mockgen_reflect.go"],
- cmd = (
- "$(location :mockgen) " +
- "-package p9test " +
- "-prog_only " + MOCK_SRC_PACKAGE + " " +
- "Attacher,File > $@"
- ),
- tools = [":mockgen"],
-)
-
-# mockgen_exec is the binary that includes the above reflection generator.
-# Running this binary will emit an encoded version of the p9 Attacher and File
-# structures. This is consumed by the mocks genrule, below.
-go_binary(
- name = "mockgen_exec",
- testonly = 1,
- srcs = ["mockgen_reflect.go"],
- deps = [
- "//pkg/p9",
- "@com_github_golang_mock//mockgen/model:go_default_library",
- ],
-)
-
-# mocks consumes the encoded output above, and generates the full source for a
-# set of mocks. These are included directly in the p9test library.
-genrule(
- name = "mocks",
- testonly = 1,
- outs = ["mocks.go"],
- cmd = (
- "$(location :mockgen) " +
- "-package p9test " +
- "-exec_only $(location :mockgen_exec) " + MOCK_SRC_PACKAGE + " File > $@"
- ),
- tools = [
- ":mockgen",
- ":mockgen_exec",
- ],
-)
-
-go_library(
- name = "p9test",
- srcs = [
- "mocks.go",
- "p9test.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/p9/p9test",
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/fd",
- "//pkg/log",
- "//pkg/p9",
- "//pkg/unet",
- "@com_github_golang_mock//gomock:go_default_library",
- ],
-)
-
-go_test(
- name = "client_test",
- size = "small",
- srcs = ["client_test.go"],
- embed = [":p9test"],
- deps = [
- "//pkg/fd",
- "//pkg/p9",
- "@com_github_golang_mock//gomock:go_default_library",
- ],
-)
diff --git a/pkg/p9/p9test/client_test.go b/pkg/p9/p9test/client_test.go
deleted file mode 100644
index fe649c2e8..000000000
--- a/pkg/p9/p9test/client_test.go
+++ /dev/null
@@ -1,2129 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package p9test
-
-import (
- "bytes"
- "fmt"
- "io"
- "math/rand"
- "os"
- "reflect"
- "strings"
- "sync"
- "syscall"
- "testing"
- "time"
-
- "github.com/golang/mock/gomock"
- "gvisor.dev/gvisor/pkg/fd"
- "gvisor.dev/gvisor/pkg/p9"
-)
-
-func TestPanic(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- // Create a new root.
- d := h.NewDirectory(nil)(nil)
- defer d.Close() // Needed manually.
- h.Attacher.EXPECT().Attach().Return(d, nil).Do(func() {
- // Panic here, and ensure that we get back EFAULT.
- panic("handler")
- })
-
- // Attach to the client.
- if _, err := c.Attach("/"); err != syscall.EFAULT {
- t.Fatalf("got attach err %v, want EFAULT", err)
- }
-}
-
-func TestAttachNoLeak(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- // Create a new root.
- d := h.NewDirectory(nil)(nil)
- h.Attacher.EXPECT().Attach().Return(d, nil).Times(1)
-
- // Attach to the client.
- f, err := c.Attach("/")
- if err != nil {
- t.Fatalf("got attach err %v, want nil", err)
- }
-
- // Don't close the file. This should be closed automatically when the
- // client disconnects. The mock asserts that everything is closed
- // exactly once. This statement just removes the unused variable error.
- _ = f
-}
-
-func TestBadAttach(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- // Return an error on attach.
- h.Attacher.EXPECT().Attach().Return(nil, syscall.EINVAL).Times(1)
-
- // Attach to the client.
- if _, err := c.Attach("/"); err != syscall.EINVAL {
- t.Fatalf("got attach err %v, want syscall.EINVAL", err)
- }
-}
-
-func TestWalkAttach(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- // Create a new root.
- d := h.NewDirectory(map[string]Generator{
- "a": h.NewDirectory(map[string]Generator{
- "b": h.NewFile(),
- }),
- })(nil)
- h.Attacher.EXPECT().Attach().Return(d, nil).Times(1)
-
- // Attach to the client as a non-root, and ensure that the walk above
- // occurs as expected. We should get back b, and all references should
- // be dropped when the file is closed.
- f, err := c.Attach("/a/b")
- if err != nil {
- t.Fatalf("got attach err %v, want nil", err)
- }
- defer f.Close()
-
- // Check that's a regular file.
- if _, _, attr, err := f.GetAttr(p9.AttrMaskAll()); err != nil {
- t.Errorf("got err %v, want nil", err)
- } else if !attr.Mode.IsRegular() {
- t.Errorf("got mode %v, want regular file", err)
- }
-}
-
-// newTypeMap returns a new type map dictionary.
-func newTypeMap(h *Harness) map[string]Generator {
- return map[string]Generator{
- "directory": h.NewDirectory(map[string]Generator{}),
- "file": h.NewFile(),
- "symlink": h.NewSymlink(),
- "block-device": h.NewBlockDevice(),
- "character-device": h.NewCharacterDevice(),
- "named-pipe": h.NewNamedPipe(),
- "socket": h.NewSocket(),
- }
-}
-
-// newRoot returns a new root filesystem.
-//
-// This is set up in a deterministic way for testing most operations.
-//
-// The represented file system looks like:
-// - file
-// - symlink
-// - directory
-// ...
-// + one
-// - file
-// - symlink
-// - directory
-// ...
-// + two
-// - file
-// - symlink
-// - directory
-// ...
-// + three
-// - file
-// - symlink
-// - directory
-// ...
-func newRoot(h *Harness, c *p9.Client) (*Mock, p9.File) {
- root := newTypeMap(h)
- one := newTypeMap(h)
- two := newTypeMap(h)
- three := newTypeMap(h)
- one["two"] = h.NewDirectory(two) // Will be nested in one.
- root["one"] = h.NewDirectory(one) // Top level.
- root["three"] = h.NewDirectory(three) // Alternate top-level.
-
- // Create a new root.
- rootBackend := h.NewDirectory(root)(nil)
- h.Attacher.EXPECT().Attach().Return(rootBackend, nil)
-
- // Attach to the client.
- r, err := c.Attach("/")
- if err != nil {
- h.t.Fatalf("got attach err %v, want nil", err)
- }
-
- return rootBackend, r
-}
-
-func allInvalidNames(from string) []string {
- return []string{
- from + "/other",
- from + "/..",
- from + "/.",
- from + "/",
- "other/" + from,
- "/" + from,
- "./" + from,
- "../" + from,
- ".",
- "..",
- "/",
- "",
- }
-}
-
-func TestWalkInvalid(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- // Run relevant tests.
- for name := range newTypeMap(h) {
- // These are all the various ways that one might attempt to
- // construct compound paths. They should all be rejected, as
- // any compound that contains a / is not allowed, as well as
- // the singular paths of '.' and '..'.
- if _, _, err := root.Walk([]string{".", name}); err != syscall.EINVAL {
- t.Errorf("Walk through . %s wanted EINVAL, got %v", name, err)
- }
- if _, _, err := root.Walk([]string{"..", name}); err != syscall.EINVAL {
- t.Errorf("Walk through . %s wanted EINVAL, got %v", name, err)
- }
- if _, _, err := root.Walk([]string{name, "."}); err != syscall.EINVAL {
- t.Errorf("Walk through %s . wanted EINVAL, got %v", name, err)
- }
- if _, _, err := root.Walk([]string{name, ".."}); err != syscall.EINVAL {
- t.Errorf("Walk through %s .. wanted EINVAL, got %v", name, err)
- }
- for _, invalidName := range allInvalidNames(name) {
- if _, _, err := root.Walk([]string{invalidName}); err != syscall.EINVAL {
- t.Errorf("Walk through %s wanted EINVAL, got %v", invalidName, err)
- }
- }
- wantErr := syscall.EINVAL
- if name == "directory" {
- // We can attempt a walk through a directory. However,
- // we should never see a file named "other", so we
- // expect this to return ENOENT.
- wantErr = syscall.ENOENT
- }
- if _, _, err := root.Walk([]string{name, "other"}); err != wantErr {
- t.Errorf("Walk through %s/other wanted %v, got %v", name, wantErr, err)
- }
-
- // Do a successful walk.
- _, f, err := root.Walk([]string{name})
- if err != nil {
- t.Errorf("Walk to %s wanted nil, got %v", name, err)
- }
- defer f.Close()
- local := h.Pop(f)
-
- // Check that the file matches.
- _, localMask, localAttr, localErr := local.GetAttr(p9.AttrMaskAll())
- if _, mask, attr, err := f.GetAttr(p9.AttrMaskAll()); mask != localMask || attr != localAttr || err != localErr {
- t.Errorf("GetAttr got (%v, %v, %v), wanted (%v, %v, %v)",
- mask, attr, err, localMask, localAttr, localErr)
- }
-
- // Ensure we can't walk backwards.
- if _, _, err := f.Walk([]string{"."}); err != syscall.EINVAL {
- t.Errorf("Walk through %s/. wanted EINVAL, got %v", name, err)
- }
- if _, _, err := f.Walk([]string{".."}); err != syscall.EINVAL {
- t.Errorf("Walk through %s/.. wanted EINVAL, got %v", name, err)
- }
- }
-}
-
-// fileGenerator is a function to generate files via walk or create.
-//
-// Examples are:
-// - walkHelper
-// - walkAndOpenHelper
-// - createHelper
-type fileGenerator func(*Harness, string, p9.File) (*Mock, *Mock, p9.File)
-
-// walkHelper walks to the given file.
-//
-// The backends of the parent and walked file are returned, as well as the
-// walked client file.
-func walkHelper(h *Harness, name string, dir p9.File) (parentBackend *Mock, walkedBackend *Mock, walked p9.File) {
- _, parent, err := dir.Walk(nil)
- if err != nil {
- h.t.Fatalf("Walk(nil) got err %v, want nil", err)
- }
- defer parent.Close()
- parentBackend = h.Pop(parent)
-
- _, walked, err = parent.Walk([]string{name})
- if err != nil {
- h.t.Fatalf("Walk(%s) got err %v, want nil", name, err)
- }
- walkedBackend = h.Pop(walked)
-
- return parentBackend, walkedBackend, walked
-}
-
-// walkAndOpenHelper additionally opens the walked file, if possible.
-func walkAndOpenHelper(h *Harness, name string, dir p9.File) (*Mock, *Mock, p9.File) {
- parentBackend, walkedBackend, walked := walkHelper(h, name, dir)
- if p9.CanOpen(walkedBackend.Attr.Mode) {
- // Open for all file types that we can. We stick to a read-only
- // open here because directories may not be opened otherwise.
- walkedBackend.EXPECT().Open(p9.ReadOnly).Times(1)
- if _, _, _, err := walked.Open(p9.ReadOnly); err != nil {
- h.t.Errorf("got open err %v, want nil", err)
- }
- } else {
- // ... or assert an error for others.
- if _, _, _, err := walked.Open(p9.ReadOnly); err != syscall.EINVAL {
- h.t.Errorf("got open err %v, want EINVAL", err)
- }
- }
- return parentBackend, walkedBackend, walked
-}
-
-// createHelper creates the given file and returns the parent directory,
-// created file and client file, which must be closed when done.
-func createHelper(h *Harness, name string, dir p9.File) (*Mock, *Mock, p9.File) {
- // Clone the directory first, since Create replaces the existing file.
- // We change the type after calling create.
- _, dirThenFile, err := dir.Walk(nil)
- if err != nil {
- h.t.Fatalf("got walk err %v, want nil", err)
- }
-
- // Create a new server-side file. On the server-side, the a new file is
- // returned from a create call. The client will reuse the same file,
- // but we still expect the normal chain of closes. This complicates
- // things a bit because the "parent" will always chain to the cloned
- // dir above.
- dirBackend := h.Pop(dirThenFile) // New backend directory.
- newFile := h.NewFile()(dirBackend) // New file with backend parent.
- dirBackend.EXPECT().Create(name, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, newFile, newFile.QID, uint32(0), nil)
-
- // Create via the client.
- _, dirThenFile, _, _, err = dirThenFile.Create(name, p9.ReadOnly, 0, 0, 0)
- if err != nil {
- h.t.Fatalf("got create err %v, want nil", err)
- }
-
- // Ensure subsequent walks succeed.
- dirBackend.AddChild(name, h.NewFile())
- return dirBackend, newFile, dirThenFile
-}
-
-// deprecatedRemover allows us to access the deprecated Remove operation within
-// the p9.File client object.
-type deprecatedRemover interface {
- Remove() error
-}
-
-// checkDeleted asserts that relevant methods fail for an unlinked file.
-//
-// This function will close the file at the end.
-func checkDeleted(h *Harness, file p9.File) {
- defer file.Close() // See doc.
-
- if _, _, _, err := file.Open(p9.ReadOnly); err != syscall.EINVAL {
- h.t.Errorf("open while deleted, got %v, want EINVAL", err)
- }
- if _, _, _, _, err := file.Create("created", p9.ReadOnly, 0, 0, 0); err != syscall.EINVAL {
- h.t.Errorf("create while deleted, got %v, want EINVAL", err)
- }
- if _, err := file.Symlink("old", "new", 0, 0); err != syscall.EINVAL {
- h.t.Errorf("symlink while deleted, got %v, want EINVAL", err)
- }
- // N.B. This link is technically invalid, but if a call to link is
- // actually made in the backend then the mock will panic.
- if err := file.Link(file, "new"); err != syscall.EINVAL {
- h.t.Errorf("link while deleted, got %v, want EINVAL", err)
- }
- if err := file.RenameAt("src", file, "dst"); err != syscall.EINVAL {
- h.t.Errorf("renameAt while deleted, got %v, want EINVAL", err)
- }
- if err := file.UnlinkAt("file", 0); err != syscall.EINVAL {
- h.t.Errorf("unlinkAt while deleted, got %v, want EINVAL", err)
- }
- if err := file.Rename(file, "dst"); err != syscall.EINVAL {
- h.t.Errorf("rename while deleted, got %v, want EINVAL", err)
- }
- if _, err := file.Readlink(); err != syscall.EINVAL {
- h.t.Errorf("readlink while deleted, got %v, want EINVAL", err)
- }
- if _, err := file.Mkdir("dir", p9.ModeDirectory, 0, 0); err != syscall.EINVAL {
- h.t.Errorf("mkdir while deleted, got %v, want EINVAL", err)
- }
- if _, err := file.Mknod("dir", p9.ModeDirectory, 0, 0, 0, 0); err != syscall.EINVAL {
- h.t.Errorf("mknod while deleted, got %v, want EINVAL", err)
- }
- if _, err := file.Readdir(0, 1); err != syscall.EINVAL {
- h.t.Errorf("readdir while deleted, got %v, want EINVAL", err)
- }
- if _, err := file.Connect(p9.ConnectFlags(0)); err != syscall.EINVAL {
- h.t.Errorf("connect while deleted, got %v, want EINVAL", err)
- }
-
- // The remove method is technically deprecated, but we want to ensure
- // that it still checks for deleted appropriately. We must first clone
- // the file because remove is equivalent to close.
- _, newFile, err := file.Walk(nil)
- if err == syscall.EBUSY {
- // We can't walk from here because this reference is open
- // already. Okay, we will also have unopened cases through
- // TestUnlink, just skip the remove operation for now.
- return
- } else if err != nil {
- h.t.Fatalf("clone failed, got %v, want nil", err)
- }
- if err := newFile.(deprecatedRemover).Remove(); err != syscall.EINVAL {
- h.t.Errorf("remove while deleted, got %v, want EINVAL", err)
- }
-}
-
-// deleter is a function to remove a file.
-type deleter func(parent p9.File, name string) error
-
-// unlinkAt is a deleter.
-func unlinkAt(parent p9.File, name string) error {
- // Call unlink. Note that a filesystem may normally impose additional
- // constaints on unlinkat success, such as ensuring that a directory is
- // empty, requiring AT_REMOVEDIR in flags to remove a directory, etc.
- // None of that is required internally (entire trees can be marked
- // deleted when this operation succeeds), so the mock will succeed.
- return parent.UnlinkAt(name, 0)
-}
-
-// remove is a deleter.
-func remove(parent p9.File, name string) error {
- // See notes above re: remove.
- _, newFile, err := parent.Walk([]string{name})
- if err != nil {
- // Should not be expected.
- return err
- }
-
- // Do the actual remove.
- if err := newFile.(deprecatedRemover).Remove(); err != nil {
- return err
- }
-
- // Ensure that the remove closed the file.
- if err := newFile.(deprecatedRemover).Remove(); err != syscall.EBADF {
- return syscall.EBADF // Propagate this code.
- }
-
- return nil
-}
-
-// unlinkHelper unlinks the noted path, and ensures that all relevant
-// operations on that path, acquired from multiple paths, start failing.
-func unlinkHelper(h *Harness, root p9.File, targetNames []string, targetGen fileGenerator, deleteFn deleter) {
- // name is the file to be unlinked.
- name := targetNames[len(targetNames)-1]
-
- // Walk to the directory containing the target.
- _, parent, err := root.Walk(targetNames[:len(targetNames)-1])
- if err != nil {
- h.t.Fatalf("got walk err %v, want nil", err)
- }
- defer parent.Close()
- parentBackend := h.Pop(parent)
-
- // Walk to or generate the target file.
- _, _, target := targetGen(h, name, parent)
- defer checkDeleted(h, target)
-
- // Walk to a second reference.
- _, second, err := parent.Walk([]string{name})
- if err != nil {
- h.t.Fatalf("got walk err %v, want nil", err)
- }
- defer checkDeleted(h, second)
-
- // Walk to a third reference, from the start.
- _, third, err := root.Walk(targetNames)
- if err != nil {
- h.t.Fatalf("got walk err %v, want nil", err)
- }
- defer checkDeleted(h, third)
-
- // This will be translated in the backend to an unlinkat.
- parentBackend.EXPECT().UnlinkAt(name, uint32(0)).Return(nil)
-
- // Actually perform the deletion.
- if err := deleteFn(parent, name); err != nil {
- h.t.Fatalf("got delete err %v, want nil", err)
- }
-}
-
-func unlinkTest(t *testing.T, targetNames []string, targetGen fileGenerator) {
- t.Run(fmt.Sprintf("unlinkAt(%s)", strings.Join(targetNames, "/")), func(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- unlinkHelper(h, root, targetNames, targetGen, unlinkAt)
- })
- t.Run(fmt.Sprintf("remove(%s)", strings.Join(targetNames, "/")), func(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- unlinkHelper(h, root, targetNames, targetGen, remove)
- })
-}
-
-func TestUnlink(t *testing.T) {
- // Unlink all files.
- for name := range newTypeMap(nil) {
- unlinkTest(t, []string{name}, walkHelper)
- unlinkTest(t, []string{name}, walkAndOpenHelper)
- unlinkTest(t, []string{"one", name}, walkHelper)
- unlinkTest(t, []string{"one", name}, walkAndOpenHelper)
- unlinkTest(t, []string{"one", "two", name}, walkHelper)
- unlinkTest(t, []string{"one", "two", name}, walkAndOpenHelper)
- }
-
- // Unlink a directory.
- unlinkTest(t, []string{"one"}, walkHelper)
- unlinkTest(t, []string{"one"}, walkAndOpenHelper)
- unlinkTest(t, []string{"one", "two"}, walkHelper)
- unlinkTest(t, []string{"one", "two"}, walkAndOpenHelper)
-
- // Unlink created files.
- unlinkTest(t, []string{"created"}, createHelper)
- unlinkTest(t, []string{"one", "created"}, createHelper)
- unlinkTest(t, []string{"one", "two", "created"}, createHelper)
-}
-
-func TestUnlinkAtInvalid(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- for name := range newTypeMap(nil) {
- for _, invalidName := range allInvalidNames(name) {
- if err := root.UnlinkAt(invalidName, 0); err != syscall.EINVAL {
- t.Errorf("got %v for name %q, want EINVAL", err, invalidName)
- }
- }
- }
-}
-
-// expectRenamed asserts an ordered sequence of rename calls, based on all the
-// elements in elements being the source, and the first element therein
-// changing to dstName, parented at dstParent.
-func expectRenamed(file *Mock, elements []string, dstParent *Mock, dstName string) *gomock.Call {
- if len(elements) > 0 {
- // Recurse to the parent, if necessary.
- call := expectRenamed(file.parent, elements[:len(elements)-1], dstParent, dstName)
-
- // Recursive case: this element is unchanged, but should have
- // it's hook called after the parent.
- return file.EXPECT().Renamed(file.parent, elements[len(elements)-1]).Do(func(p p9.File, _ string) {
- file.parent = p.(*Mock)
- }).After(call)
- }
-
- // Base case: this is the changed element.
- return file.EXPECT().Renamed(dstParent, dstName).Do(func(p p9.File, name string) {
- file.parent = p.(*Mock)
- })
-}
-
-// renamer is a rename function.
-type renamer func(h *Harness, srcParent, dstParent p9.File, origName, newName string, selfRename bool) error
-
-// renameAt is a renamer.
-func renameAt(_ *Harness, srcParent, dstParent p9.File, srcName, dstName string, selfRename bool) error {
- return srcParent.RenameAt(srcName, dstParent, dstName)
-}
-
-// rename is a renamer.
-func rename(h *Harness, srcParent, dstParent p9.File, srcName, dstName string, selfRename bool) error {
- _, f, err := srcParent.Walk([]string{srcName})
- if err != nil {
- return err
- }
- defer f.Close()
- if !selfRename {
- backend := h.Pop(f)
- backend.EXPECT().Renamed(gomock.Any(), dstName).Do(func(p p9.File, name string) {
- backend.parent = p.(*Mock) // Required for close ordering.
- })
- }
- return f.Rename(dstParent, dstName)
-}
-
-// renameHelper executes a rename, and asserts that all relevant elements
-// receive expected notifications. If overwriting a file, this includes
-// ensuring that the target has been appropriately marked as unlinked.
-func renameHelper(h *Harness, root p9.File, srcNames []string, dstNames []string, target fileGenerator, renameFn renamer) {
- // Walk to the directory containing the target.
- srcQID, targetParent, err := root.Walk(srcNames[:len(srcNames)-1])
- if err != nil {
- h.t.Fatalf("got walk err %v, want nil", err)
- }
- defer targetParent.Close()
- targetParentBackend := h.Pop(targetParent)
-
- // Walk to or generate the target file.
- _, targetBackend, src := target(h, srcNames[len(srcNames)-1], targetParent)
- defer src.Close()
-
- // Walk to a second reference.
- _, second, err := targetParent.Walk([]string{srcNames[len(srcNames)-1]})
- if err != nil {
- h.t.Fatalf("got walk err %v, want nil", err)
- }
- defer second.Close()
- secondBackend := h.Pop(second)
-
- // Walk to a third reference, from the start.
- _, third, err := root.Walk(srcNames)
- if err != nil {
- h.t.Fatalf("got walk err %v, want nil", err)
- }
- defer third.Close()
- thirdBackend := h.Pop(third)
-
- // Find the common suffix to identify the rename parent.
- var (
- renameDestPath []string
- renameSrcPath []string
- selfRename bool
- )
- for i := 1; i <= len(srcNames) && i <= len(dstNames); i++ {
- if srcNames[len(srcNames)-i] != dstNames[len(dstNames)-i] {
- // Take the full prefix of dstNames up until this
- // point, including the first mismatched name. The
- // first mismatch must be the renamed entry.
- renameDestPath = dstNames[:len(dstNames)-i+1]
- renameSrcPath = srcNames[:len(srcNames)-i+1]
-
- // Does the renameDestPath fully contain the
- // renameSrcPath here? If yes, then this is a mismatch.
- // We can't rename the src to some subpath of itself.
- if len(renameDestPath) > len(renameSrcPath) &&
- reflect.DeepEqual(renameDestPath[:len(renameSrcPath)], renameSrcPath) {
- renameDestPath = nil
- renameSrcPath = nil
- continue
- }
- break
- }
- }
- if len(renameSrcPath) == 0 || len(renameDestPath) == 0 {
- // This must be a rename to self, or a tricky look-alike. This
- // happens iff we fail to find a suitable divergence in the two
- // paths. It's a true self move if the path length is the same.
- renameDestPath = dstNames
- renameSrcPath = srcNames
- selfRename = len(srcNames) == len(dstNames)
- }
-
- // Walk to the source parent.
- _, srcParent, err := root.Walk(renameSrcPath[:len(renameSrcPath)-1])
- if err != nil {
- h.t.Fatalf("got walk err %v, want nil", err)
- }
- defer srcParent.Close()
- srcParentBackend := h.Pop(srcParent)
-
- // Walk to the destination parent.
- _, dstParent, err := root.Walk(renameDestPath[:len(renameDestPath)-1])
- if err != nil {
- h.t.Fatalf("got walk err %v, want nil", err)
- }
- defer dstParent.Close()
- dstParentBackend := h.Pop(dstParent)
-
- // expectedErr is the result of the rename operation.
- var expectedErr error
-
- // Walk to the target file, if one exists.
- dstQID, dst, err := root.Walk(renameDestPath)
- if err == nil {
- if !selfRename && srcQID[0].Type == dstQID[0].Type {
- // If there is a destination file, and is it of the
- // same type as the source file, then we expect the
- // rename to succeed. We expect the destination file to
- // be deleted, so we run a deletion test on it in this
- // case.
- defer checkDeleted(h, dst)
- } else {
- if !selfRename {
- // If the type is different than the
- // destination, then we expect the rename to
- // fail. We expect ensure that this is
- // returned.
- expectedErr = syscall.EINVAL
- } else {
- // This is the file being renamed to itself.
- // This is technically allowed and a no-op, but
- // all the triggers will fire.
- }
- dst.Close()
- }
- }
- dstName := renameDestPath[len(renameDestPath)-1] // Renamed element.
- srcName := renameSrcPath[len(renameSrcPath)-1] // Renamed element.
- if expectedErr == nil && !selfRename {
- // Expect all to be renamed appropriately. Note that if this is
- // a final file being renamed, then we expect the file to be
- // called with the new parent. If not, then we expect the
- // rename hook to be called, but the parent will remain
- // unchanged.
- elements := srcNames[len(renameSrcPath):]
- expectRenamed(targetBackend, elements, dstParentBackend, dstName)
- expectRenamed(secondBackend, elements, dstParentBackend, dstName)
- expectRenamed(thirdBackend, elements, dstParentBackend, dstName)
-
- // The target parent has also been opened, and may be moved
- // directly or indirectly.
- if len(elements) > 1 {
- expectRenamed(targetParentBackend, elements[:len(elements)-1], dstParentBackend, dstName)
- }
- }
-
- // Expect the rename if it's not the same file. Note that like unlink,
- // renames are always translated to the at variant in the backend.
- if !selfRename {
- srcParentBackend.EXPECT().RenameAt(srcName, dstParentBackend, dstName).Return(expectedErr)
- }
-
- // Perform the actual rename; everything has been lined up.
- if err := renameFn(h, srcParent, dstParent, srcName, dstName, selfRename); err != expectedErr {
- h.t.Fatalf("got rename err %v, want %v", err, expectedErr)
- }
-}
-
-func renameTest(t *testing.T, srcNames []string, dstNames []string, target fileGenerator) {
- t.Run(fmt.Sprintf("renameAt(%s->%s)", strings.Join(srcNames, "/"), strings.Join(dstNames, "/")), func(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- renameHelper(h, root, srcNames, dstNames, target, renameAt)
- })
- t.Run(fmt.Sprintf("rename(%s->%s)", strings.Join(srcNames, "/"), strings.Join(dstNames, "/")), func(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- renameHelper(h, root, srcNames, dstNames, target, rename)
- })
-}
-
-func TestRename(t *testing.T) {
- // In-directory rename, simple case.
- for name := range newTypeMap(nil) {
- // Within the root.
- renameTest(t, []string{name}, []string{"renamed"}, walkHelper)
- renameTest(t, []string{name}, []string{"renamed"}, walkAndOpenHelper)
-
- // Within a subdirectory.
- renameTest(t, []string{"one", name}, []string{"one", "renamed"}, walkHelper)
- renameTest(t, []string{"one", name}, []string{"one", "renamed"}, walkAndOpenHelper)
- }
-
- // ... with created files.
- renameTest(t, []string{"created"}, []string{"renamed"}, createHelper)
- renameTest(t, []string{"one", "created"}, []string{"one", "renamed"}, createHelper)
-
- // Across directories.
- for name := range newTypeMap(nil) {
- // Down one level.
- renameTest(t, []string{"one", name}, []string{"one", "two", "renamed"}, walkHelper)
- renameTest(t, []string{"one", name}, []string{"one", "two", "renamed"}, walkAndOpenHelper)
-
- // Up one level.
- renameTest(t, []string{"one", "two", name}, []string{"one", "renamed"}, walkHelper)
- renameTest(t, []string{"one", "two", name}, []string{"one", "renamed"}, walkAndOpenHelper)
-
- // Across at the same level.
- renameTest(t, []string{"one", name}, []string{"three", "renamed"}, walkHelper)
- renameTest(t, []string{"one", name}, []string{"three", "renamed"}, walkAndOpenHelper)
- }
-
- // ... with created files.
- renameTest(t, []string{"one", "created"}, []string{"one", "two", "renamed"}, createHelper)
- renameTest(t, []string{"one", "two", "created"}, []string{"one", "renamed"}, createHelper)
- renameTest(t, []string{"one", "created"}, []string{"three", "renamed"}, createHelper)
-
- // Renaming parents.
- for name := range newTypeMap(nil) {
- // Rename a parent.
- renameTest(t, []string{"one", name}, []string{"renamed", name}, walkHelper)
- renameTest(t, []string{"one", name}, []string{"renamed", name}, walkAndOpenHelper)
-
- // Rename a super parent.
- renameTest(t, []string{"one", "two", name}, []string{"renamed", name}, walkHelper)
- renameTest(t, []string{"one", "two", name}, []string{"renamed", name}, walkAndOpenHelper)
- }
-
- // ... with created files.
- renameTest(t, []string{"one", "created"}, []string{"renamed", "created"}, createHelper)
- renameTest(t, []string{"one", "two", "created"}, []string{"renamed", "created"}, createHelper)
-
- // Over existing files, including itself.
- for name := range newTypeMap(nil) {
- for other := range newTypeMap(nil) {
- // Overwrite the noted file (may be itself).
- renameTest(t, []string{"one", name}, []string{"one", other}, walkHelper)
- renameTest(t, []string{"one", name}, []string{"one", other}, walkAndOpenHelper)
-
- // Overwrite other files in another directory.
- renameTest(t, []string{"one", name}, []string{"one", "two", other}, walkHelper)
- renameTest(t, []string{"one", name}, []string{"one", "two", other}, walkAndOpenHelper)
- }
-
- // Overwrite by moving the parent.
- renameTest(t, []string{"three", name}, []string{"one", name}, walkHelper)
- renameTest(t, []string{"three", name}, []string{"one", name}, walkAndOpenHelper)
-
- // Create over the types.
- renameTest(t, []string{"one", "created"}, []string{"one", name}, createHelper)
- renameTest(t, []string{"one", "created"}, []string{"one", "two", name}, createHelper)
- renameTest(t, []string{"three", "created"}, []string{"one", name}, createHelper)
- }
-}
-
-func TestRenameInvalid(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- for name := range newTypeMap(nil) {
- for _, invalidName := range allInvalidNames(name) {
- if err := root.Rename(root, invalidName); err != syscall.EINVAL {
- t.Errorf("got %v for name %q, want EINVAL", err, invalidName)
- }
- }
- }
-}
-
-func TestRenameAtInvalid(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- for name := range newTypeMap(nil) {
- for _, invalidName := range allInvalidNames(name) {
- if err := root.RenameAt(invalidName, root, "okay"); err != syscall.EINVAL {
- t.Errorf("got %v for name %q, want EINVAL", err, invalidName)
- }
- if err := root.RenameAt("okay", root, invalidName); err != syscall.EINVAL {
- t.Errorf("got %v for name %q, want EINVAL", err, invalidName)
- }
- }
- }
-}
-
-// TestRenameSecondOrder tests that indirect rename targets continue to receive
-// Renamed calls after a rename of its renamed parent. i.e.,
-//
-// 1. Create /one/file
-// 2. Create /directory
-// 3. Rename /one -> /directory/one
-// 4. Rename /directory -> /three/foo
-// 5. file from (1) should still receive Renamed.
-//
-// This is a regression test for b/135219260.
-func TestRenameSecondOrder(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- rootBackend, root := newRoot(h, c)
- defer root.Close()
-
- // Walk to /one.
- _, oneBackend, oneFile := walkHelper(h, "one", root)
- defer oneFile.Close()
-
- // Walk to and generate /one/file.
- //
- // walkHelper re-walks to oneFile, so we need the second backend,
- // which will also receive Renamed calls.
- oneSecondBackend, fileBackend, fileFile := walkHelper(h, "file", oneFile)
- defer fileFile.Close()
-
- // Walk to and generate /directory.
- _, directoryBackend, directoryFile := walkHelper(h, "directory", root)
- defer directoryFile.Close()
-
- // Rename /one to /directory/one.
- rootBackend.EXPECT().RenameAt("one", directoryBackend, "one").Return(nil)
- expectRenamed(oneBackend, []string{}, directoryBackend, "one")
- expectRenamed(oneSecondBackend, []string{}, directoryBackend, "one")
- expectRenamed(fileBackend, []string{}, oneBackend, "file")
- if err := renameAt(h, root, directoryFile, "one", "one", false); err != nil {
- h.t.Fatalf("got rename err %v, want nil", err)
- }
-
- // Walk to /three.
- _, threeBackend, threeFile := walkHelper(h, "three", root)
- defer threeFile.Close()
-
- // Rename /directory to /three/foo.
- rootBackend.EXPECT().RenameAt("directory", threeBackend, "foo").Return(nil)
- expectRenamed(directoryBackend, []string{}, threeBackend, "foo")
- expectRenamed(oneBackend, []string{}, directoryBackend, "one")
- expectRenamed(oneSecondBackend, []string{}, directoryBackend, "one")
- expectRenamed(fileBackend, []string{}, oneBackend, "file")
- if err := renameAt(h, root, threeFile, "directory", "foo", false); err != nil {
- h.t.Fatalf("got rename err %v, want nil", err)
- }
-}
-
-func TestReadlink(t *testing.T) {
- for name := range newTypeMap(nil) {
- t.Run(name, func(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- // Walk to the file normally.
- _, f, err := root.Walk([]string{name})
- if err != nil {
- t.Fatalf("walk failed: got %v, wanted nil", err)
- }
- defer f.Close()
- backend := h.Pop(f)
-
- const symlinkTarget = "symlink-target"
-
- if backend.Attr.Mode.IsSymlink() {
- // This should only go through on symlinks.
- backend.EXPECT().Readlink().Return(symlinkTarget, nil)
- }
-
- // Attempt a Readlink operation.
- target, err := f.Readlink()
- if err != nil && err != syscall.EINVAL {
- t.Errorf("readlink got %v, wanted EINVAL", err)
- } else if err == nil && target != symlinkTarget {
- t.Errorf("readlink got %v, wanted %v", target, symlinkTarget)
- }
- })
- }
-}
-
-// fdTest is a wrapper around operations that may send file descriptors. This
-// asserts that the file descriptors are working as intended.
-func fdTest(t *testing.T, sendFn func(*fd.FD) *fd.FD) {
- // Create a pipe that we can read from.
- r, w, err := os.Pipe()
- if err != nil {
- t.Fatalf("unable to create pipe: %v", err)
- }
- defer r.Close()
- defer w.Close()
-
- // Attempt to send the write end.
- wFD, err := fd.NewFromFile(w)
- if err != nil {
- t.Fatalf("unable to convert file: %v", err)
- }
- defer wFD.Close() // This is a copy.
-
- // Send wFD and receive newFD.
- newFD := sendFn(wFD)
- defer newFD.Close()
-
- // Attempt to write.
- const message = "hello"
- if _, err := newFD.Write([]byte(message)); err != nil {
- t.Fatalf("write got %v, wanted nil", err)
- }
-
- // Should see the message on our end.
- buffer := []byte(message)
- if _, err := io.ReadFull(r, buffer); err != nil {
- t.Fatalf("read got %v, wanted nil", err)
- }
- if string(buffer) != message {
- t.Errorf("got message %v, wanted %v", string(buffer), message)
- }
-}
-
-func TestConnect(t *testing.T) {
- for name := range newTypeMap(nil) {
- t.Run(name, func(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- // Walk to the file normally.
- _, backend, f := walkHelper(h, name, root)
- defer f.Close()
-
- // Catch all the non-socket cases.
- if !backend.Attr.Mode.IsSocket() {
- // This has been set up to fail if Connect is called.
- if _, err := f.Connect(p9.ConnectFlags(0)); err != syscall.EINVAL {
- t.Errorf("connect got %v, wanted EINVAL", err)
- }
- return
- }
-
- // Ensure the fd exchange works.
- fdTest(t, func(send *fd.FD) *fd.FD {
- backend.EXPECT().Connect(p9.ConnectFlags(0)).Return(send, nil)
- recv, err := backend.Connect(p9.ConnectFlags(0))
- if err != nil {
- t.Fatalf("connect got %v, wanted nil", err)
- }
- return recv
- })
- })
- }
-}
-
-func TestReaddir(t *testing.T) {
- for name := range newTypeMap(nil) {
- t.Run(name, func(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- // Walk to the file normally.
- _, backend, f := walkHelper(h, name, root)
- defer f.Close()
-
- // Catch all the non-directory cases.
- if !backend.Attr.Mode.IsDir() {
- // This has also been set up to fail if Readdir is called.
- if _, err := f.Readdir(0, 1); err != syscall.EINVAL {
- t.Errorf("readdir got %v, wanted EINVAL", err)
- }
- return
- }
-
- // Ensure that readdir works for directories.
- if _, err := f.Readdir(0, 1); err != syscall.EINVAL {
- t.Errorf("readdir got %v, wanted EINVAL", err)
- }
- if _, _, _, err := f.Open(p9.ReadWrite); err != syscall.EINVAL {
- t.Errorf("readdir got %v, wanted EINVAL", err)
- }
- if _, _, _, err := f.Open(p9.WriteOnly); err != syscall.EINVAL {
- t.Errorf("readdir got %v, wanted EINVAL", err)
- }
- backend.EXPECT().Open(p9.ReadOnly).Times(1)
- if _, _, _, err := f.Open(p9.ReadOnly); err != nil {
- t.Errorf("readdir got %v, wanted nil", err)
- }
- backend.EXPECT().Readdir(uint64(0), uint32(1)).Times(1)
- if _, err := f.Readdir(0, 1); err != nil {
- t.Errorf("readdir got %v, wanted nil", err)
- }
- })
- }
-}
-
-func TestOpen(t *testing.T) {
- type openTest struct {
- name string
- mode p9.OpenFlags
- err error
- match func(p9.FileMode) bool
- }
-
- cases := []openTest{
- {
- name: "invalid",
- mode: ^p9.OpenFlagsModeMask,
- err: syscall.EINVAL,
- match: func(p9.FileMode) bool { return true },
- },
- {
- name: "not-openable-read-only",
- mode: p9.ReadOnly,
- err: syscall.EINVAL,
- match: func(mode p9.FileMode) bool { return !p9.CanOpen(mode) },
- },
- {
- name: "not-openable-write-only",
- mode: p9.WriteOnly,
- err: syscall.EINVAL,
- match: func(mode p9.FileMode) bool { return !p9.CanOpen(mode) },
- },
- {
- name: "not-openable-read-write",
- mode: p9.ReadWrite,
- err: syscall.EINVAL,
- match: func(mode p9.FileMode) bool { return !p9.CanOpen(mode) },
- },
- {
- name: "directory-read-only",
- mode: p9.ReadOnly,
- err: nil,
- match: func(mode p9.FileMode) bool { return mode.IsDir() },
- },
- {
- name: "directory-read-write",
- mode: p9.ReadWrite,
- err: syscall.EINVAL,
- match: func(mode p9.FileMode) bool { return mode.IsDir() },
- },
- {
- name: "directory-write-only",
- mode: p9.WriteOnly,
- err: syscall.EINVAL,
- match: func(mode p9.FileMode) bool { return mode.IsDir() },
- },
- {
- name: "read-only",
- mode: p9.ReadOnly,
- err: nil,
- match: func(mode p9.FileMode) bool { return p9.CanOpen(mode) },
- },
- {
- name: "write-only",
- mode: p9.WriteOnly,
- err: nil,
- match: func(mode p9.FileMode) bool { return p9.CanOpen(mode) && !mode.IsDir() },
- },
- {
- name: "read-write",
- mode: p9.ReadWrite,
- err: nil,
- match: func(mode p9.FileMode) bool { return p9.CanOpen(mode) && !mode.IsDir() },
- },
- }
-
- // Open(mode OpenFlags) (*fd.FD, QID, uint32, error)
- // - only works on Regular, NamedPipe, BLockDevice, CharacterDevice
- // - returning a file works as expected
- for name := range newTypeMap(nil) {
- for _, tc := range cases {
- t.Run(fmt.Sprintf("%s-%s", tc.name, name), func(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- // Walk to the file normally.
- _, backend, f := walkHelper(h, name, root)
- defer f.Close()
-
- // Does this match the case?
- if !tc.match(backend.Attr.Mode) {
- t.SkipNow()
- }
-
- // Ensure open-required operations fail.
- if _, err := f.ReadAt([]byte("hello"), 0); err != syscall.EINVAL {
- t.Errorf("readAt got %v, wanted EINVAL", err)
- }
- if _, err := f.WriteAt(make([]byte, 6), 0); err != syscall.EINVAL {
- t.Errorf("writeAt got %v, wanted EINVAL", err)
- }
- if err := f.FSync(); err != syscall.EINVAL {
- t.Errorf("fsync got %v, wanted EINVAL", err)
- }
- if _, err := f.Readdir(0, 1); err != syscall.EINVAL {
- t.Errorf("readdir got %v, wanted EINVAL", err)
- }
-
- // Attempt the given open.
- if tc.err != nil {
- // We expect an error, just test and return.
- if _, _, _, err := f.Open(tc.mode); err != tc.err {
- t.Fatalf("open with mode %v got %v, want %v", tc.mode, err, tc.err)
- }
- return
- }
-
- // Run an FD test, since we expect success.
- fdTest(t, func(send *fd.FD) *fd.FD {
- backend.EXPECT().Open(tc.mode).Return(send, p9.QID{}, uint32(0), nil).Times(1)
- recv, _, _, err := f.Open(tc.mode)
- if err != tc.err {
- t.Fatalf("open with mode %v got %v, want %v", tc.mode, err, tc.err)
- }
- return recv
- })
-
- // If the open was successful, attempt another one.
- if _, _, _, err := f.Open(tc.mode); err != syscall.EINVAL {
- t.Errorf("second open with mode %v got %v, want EINVAL", tc.mode, err)
- }
-
- // Ensure that all illegal operations fail.
- if _, _, err := f.Walk(nil); err != syscall.EINVAL && err != syscall.EBUSY {
- t.Errorf("walk got %v, wanted EINVAL or EBUSY", err)
- }
- if _, _, _, _, err := f.WalkGetAttr(nil); err != syscall.EINVAL && err != syscall.EBUSY {
- t.Errorf("walkgetattr got %v, wanted EINVAL or EBUSY", err)
- }
- })
- }
- }
-}
-
-func TestClose(t *testing.T) {
- type closeTest struct {
- name string
- closeFn func(backend *Mock, f p9.File)
- }
-
- cases := []closeTest{
- {
- name: "close",
- closeFn: func(_ *Mock, f p9.File) {
- f.Close()
- },
- },
- {
- name: "remove",
- closeFn: func(backend *Mock, f p9.File) {
- // Allow the rename call in the parent, automatically translated.
- backend.parent.EXPECT().UnlinkAt(gomock.Any(), gomock.Any()).Times(1)
- f.(deprecatedRemover).Remove()
- },
- },
- }
-
- for name := range newTypeMap(nil) {
- for _, tc := range cases {
- t.Run(fmt.Sprintf("%s(%s)", tc.name, name), func(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- // Walk to the file normally.
- _, backend, f := walkHelper(h, name, root)
-
- // Close via the prescribed method.
- tc.closeFn(backend, f)
-
- // Everything should fail with EBADF.
- if _, _, err := f.Walk(nil); err != syscall.EBADF {
- t.Errorf("walk got %v, wanted EBADF", err)
- }
- if _, err := f.StatFS(); err != syscall.EBADF {
- t.Errorf("statfs got %v, wanted EBADF", err)
- }
- if _, _, _, err := f.GetAttr(p9.AttrMaskAll()); err != syscall.EBADF {
- t.Errorf("getattr got %v, wanted EBADF", err)
- }
- if err := f.SetAttr(p9.SetAttrMask{}, p9.SetAttr{}); err != syscall.EBADF {
- t.Errorf("setattrk got %v, wanted EBADF", err)
- }
- if err := f.Rename(root, "new-name"); err != syscall.EBADF {
- t.Errorf("rename got %v, wanted EBADF", err)
- }
- if err := f.Close(); err != syscall.EBADF {
- t.Errorf("close got %v, wanted EBADF", err)
- }
- if _, _, _, err := f.Open(p9.ReadOnly); err != syscall.EBADF {
- t.Errorf("open got %v, wanted EBADF", err)
- }
- if _, err := f.ReadAt([]byte("hello"), 0); err != syscall.EBADF {
- t.Errorf("readAt got %v, wanted EBADF", err)
- }
- if _, err := f.WriteAt(make([]byte, 6), 0); err != syscall.EBADF {
- t.Errorf("writeAt got %v, wanted EBADF", err)
- }
- if err := f.FSync(); err != syscall.EBADF {
- t.Errorf("fsync got %v, wanted EBADF", err)
- }
- if _, _, _, _, err := f.Create("new-file", p9.ReadWrite, 0, 0, 0); err != syscall.EBADF {
- t.Errorf("create got %v, wanted EBADF", err)
- }
- if _, err := f.Mkdir("new-directory", 0, 0, 0); err != syscall.EBADF {
- t.Errorf("mkdir got %v, wanted EBADF", err)
- }
- if _, err := f.Symlink("old-name", "new-name", 0, 0); err != syscall.EBADF {
- t.Errorf("symlink got %v, wanted EBADF", err)
- }
- if err := f.Link(root, "new-name"); err != syscall.EBADF {
- t.Errorf("link got %v, wanted EBADF", err)
- }
- if _, err := f.Mknod("new-block-device", 0, 0, 0, 0, 0); err != syscall.EBADF {
- t.Errorf("mknod got %v, wanted EBADF", err)
- }
- if err := f.RenameAt("old-name", root, "new-name"); err != syscall.EBADF {
- t.Errorf("renameAt got %v, wanted EBADF", err)
- }
- if err := f.UnlinkAt("name", 0); err != syscall.EBADF {
- t.Errorf("unlinkAt got %v, wanted EBADF", err)
- }
- if _, err := f.Readdir(0, 1); err != syscall.EBADF {
- t.Errorf("readdir got %v, wanted EBADF", err)
- }
- if _, err := f.Readlink(); err != syscall.EBADF {
- t.Errorf("readlink got %v, wanted EBADF", err)
- }
- if err := f.Flush(); err != syscall.EBADF {
- t.Errorf("flush got %v, wanted EBADF", err)
- }
- if _, _, _, _, err := f.WalkGetAttr(nil); err != syscall.EBADF {
- t.Errorf("walkgetattr got %v, wanted EBADF", err)
- }
- if _, err := f.Connect(p9.ConnectFlags(0)); err != syscall.EBADF {
- t.Errorf("connect got %v, wanted EBADF", err)
- }
- })
- }
- }
-}
-
-// onlyWorksOnOpenThings is a helper test method for operations that should
-// only work on files that have been explicitly opened.
-func onlyWorksOnOpenThings(h *Harness, t *testing.T, name string, root p9.File, mode p9.OpenFlags, expectedErr error, fn func(backend *Mock, f p9.File, shouldSucceed bool) error) {
- // Walk to the file normally.
- _, backend, f := walkHelper(h, name, root)
- defer f.Close()
-
- // Does it work before opening?
- if err := fn(backend, f, false); err != syscall.EINVAL {
- t.Errorf("operation got %v, wanted EINVAL", err)
- }
-
- // Is this openable?
- if !p9.CanOpen(backend.Attr.Mode) {
- return // Nothing to do.
- }
-
- // If this is a directory, we can't handle writing.
- if backend.Attr.Mode.IsDir() && (mode == p9.ReadWrite || mode == p9.WriteOnly) {
- return // Skip.
- }
-
- // Open the file.
- backend.EXPECT().Open(mode)
- if _, _, _, err := f.Open(mode); err != nil {
- t.Fatalf("open got %v, wanted nil", err)
- }
-
- // Attempt the operation.
- if err := fn(backend, f, expectedErr == nil); err != expectedErr {
- t.Fatalf("operation got %v, wanted %v", err, expectedErr)
- }
-}
-
-func TestRead(t *testing.T) {
- type readTest struct {
- name string
- mode p9.OpenFlags
- err error
- }
-
- cases := []readTest{
- {
- name: "read-only",
- mode: p9.ReadOnly,
- err: nil,
- },
- {
- name: "read-write",
- mode: p9.ReadWrite,
- err: nil,
- },
- {
- name: "write-only",
- mode: p9.WriteOnly,
- err: syscall.EPERM,
- },
- }
-
- for name := range newTypeMap(nil) {
- for _, tc := range cases {
- t.Run(fmt.Sprintf("%s-%s", tc.name, name), func(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- const message = "hello"
-
- onlyWorksOnOpenThings(h, t, name, root, tc.mode, tc.err, func(backend *Mock, f p9.File, shouldSucceed bool) error {
- if !shouldSucceed {
- _, err := f.ReadAt([]byte(message), 0)
- return err
- }
-
- // Prepare for the call to readAt in the backend.
- backend.EXPECT().ReadAt(gomock.Any(), uint64(0)).Do(func(p []byte, offset uint64) {
- copy(p, message)
- }).Return(len(message), nil)
-
- // Make the client call.
- p := make([]byte, 2*len(message)) // Double size.
- n, err := f.ReadAt(p, 0)
-
- // Sanity check result.
- if err != nil {
- return err
- }
- if n != len(message) {
- t.Fatalf("message length incorrect, got %d, want %d", n, len(message))
- }
- if !bytes.Equal(p[:n], []byte(message)) {
- t.Fatalf("message incorrect, got %v, want %v", p, []byte(message))
- }
- return nil // Success.
- })
- })
- }
- }
-}
-
-func TestWrite(t *testing.T) {
- type writeTest struct {
- name string
- mode p9.OpenFlags
- err error
- }
-
- cases := []writeTest{
- {
- name: "read-only",
- mode: p9.ReadOnly,
- err: syscall.EPERM,
- },
- {
- name: "read-write",
- mode: p9.ReadWrite,
- err: nil,
- },
- {
- name: "write-only",
- mode: p9.WriteOnly,
- err: nil,
- },
- }
-
- for name := range newTypeMap(nil) {
- for _, tc := range cases {
- t.Run(fmt.Sprintf("%s-%s", tc.name, name), func(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- const message = "hello"
-
- onlyWorksOnOpenThings(h, t, name, root, tc.mode, tc.err, func(backend *Mock, f p9.File, shouldSucceed bool) error {
- if !shouldSucceed {
- _, err := f.WriteAt([]byte(message), 0)
- return err
- }
-
- // Prepare for the call to readAt in the backend.
- var output []byte // Saved by Do below.
- backend.EXPECT().WriteAt(gomock.Any(), uint64(0)).Do(func(p []byte, offset uint64) {
- output = p
- }).Return(len(message), nil)
-
- // Make the client call.
- n, err := f.WriteAt([]byte(message), 0)
-
- // Sanity check result.
- if err != nil {
- return err
- }
- if n != len(message) {
- t.Fatalf("message length incorrect, got %d, want %d", n, len(message))
- }
- if !bytes.Equal(output, []byte(message)) {
- t.Fatalf("message incorrect, got %v, want %v", output, []byte(message))
- }
- return nil // Success.
- })
- })
- }
- }
-}
-
-func TestFSync(t *testing.T) {
- for name := range newTypeMap(nil) {
- for _, mode := range []p9.OpenFlags{p9.ReadOnly, p9.WriteOnly, p9.ReadWrite} {
- t.Run(fmt.Sprintf("%s-%s", mode, name), func(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- onlyWorksOnOpenThings(h, t, name, root, mode, nil, func(backend *Mock, f p9.File, shouldSucceed bool) error {
- if shouldSucceed {
- backend.EXPECT().FSync().Times(1)
- }
- return f.FSync()
- })
- })
- }
- }
-}
-
-func TestFlush(t *testing.T) {
- for name := range newTypeMap(nil) {
- t.Run(name, func(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- _, backend, f := walkHelper(h, name, root)
- defer f.Close()
-
- backend.EXPECT().Flush()
- f.Flush()
- })
- }
-}
-
-// onlyWorksOnDirectories is a helper test method for operations that should
-// only work on unopened directories, such as create, mkdir and symlink.
-func onlyWorksOnDirectories(h *Harness, t *testing.T, name string, root p9.File, fn func(backend *Mock, f p9.File, shouldSucceed bool) error) {
- // Walk to the file normally.
- _, backend, f := walkHelper(h, name, root)
- defer f.Close()
-
- // Only directories support mknod.
- if !backend.Attr.Mode.IsDir() {
- if err := fn(backend, f, false); err != syscall.EINVAL {
- t.Errorf("operation got %v, wanted EINVAL", err)
- }
- return // Nothing else to do.
- }
-
- // Should succeed.
- if err := fn(backend, f, true); err != nil {
- t.Fatalf("operation got %v, wanted nil", err)
- }
-
- // Open the directory.
- backend.EXPECT().Open(p9.ReadOnly).Times(1)
- if _, _, _, err := f.Open(p9.ReadOnly); err != nil {
- t.Fatalf("open got %v, wanted nil", err)
- }
-
- // Should not work again.
- if err := fn(backend, f, false); err != syscall.EINVAL {
- t.Fatalf("operation got %v, wanted EINVAL", err)
- }
-}
-
-func TestCreate(t *testing.T) {
- for name := range newTypeMap(nil) {
- t.Run(name, func(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- onlyWorksOnDirectories(h, t, name, root, func(backend *Mock, f p9.File, shouldSucceed bool) error {
- if !shouldSucceed {
- _, _, _, _, err := f.Create("new-file", p9.ReadWrite, 0, 1, 2)
- return err
- }
-
- // If the create is going to succeed, then we
- // need to create a new backend file, and we
- // clone to ensure that we don't close the
- // original.
- _, newF, err := f.Walk(nil)
- if err != nil {
- t.Fatalf("clone got %v, wanted nil", err)
- }
- defer newF.Close()
- newBackend := h.Pop(newF)
-
- // Run a regular FD test to validate that path.
- fdTest(t, func(send *fd.FD) *fd.FD {
- // Return the send FD on success.
- newFile := h.NewFile()(backend) // New file with the parent backend.
- newBackend.EXPECT().Create("new-file", p9.ReadWrite, p9.FileMode(0), p9.UID(1), p9.GID(2)).Return(send, newFile, p9.QID{}, uint32(0), nil)
-
- // Receive the fd back.
- recv, _, _, _, err := newF.Create("new-file", p9.ReadWrite, 0, 1, 2)
- if err != nil {
- t.Fatalf("create got %v, wanted nil", err)
- }
- return recv
- })
-
- // The above will fail via normal test flow, so
- // we can assume that it passed.
- return nil
- })
- })
- }
-}
-
-func TestCreateInvalid(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- for name := range newTypeMap(nil) {
- for _, invalidName := range allInvalidNames(name) {
- if _, _, _, _, err := root.Create(invalidName, p9.ReadWrite, 0, 0, 0); err != syscall.EINVAL {
- t.Errorf("got %v for name %q, want EINVAL", err, invalidName)
- }
- }
- }
-}
-
-func TestMkdir(t *testing.T) {
- for name := range newTypeMap(nil) {
- t.Run(name, func(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- onlyWorksOnDirectories(h, t, name, root, func(backend *Mock, f p9.File, shouldSucceed bool) error {
- if shouldSucceed {
- backend.EXPECT().Mkdir("new-directory", p9.FileMode(0), p9.UID(1), p9.GID(2))
- }
- _, err := f.Mkdir("new-directory", 0, 1, 2)
- return err
- })
- })
- }
-}
-
-func TestMkdirInvalid(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- for name := range newTypeMap(nil) {
- for _, invalidName := range allInvalidNames(name) {
- if _, err := root.Mkdir(invalidName, 0, 0, 0); err != syscall.EINVAL {
- t.Errorf("got %v for name %q, want EINVAL", err, invalidName)
- }
- }
- }
-}
-
-func TestSymlink(t *testing.T) {
- for name := range newTypeMap(nil) {
- t.Run(name, func(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- onlyWorksOnDirectories(h, t, name, root, func(backend *Mock, f p9.File, shouldSucceed bool) error {
- if shouldSucceed {
- backend.EXPECT().Symlink("old-name", "new-name", p9.UID(1), p9.GID(2))
- }
- _, err := f.Symlink("old-name", "new-name", 1, 2)
- return err
- })
- })
- }
-}
-
-func TestSyminkInvalid(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- for name := range newTypeMap(nil) {
- for _, invalidName := range allInvalidNames(name) {
- // We need only test for invalid names in the new name,
- // the target can be an arbitrary string and we don't
- // need to sanity check it.
- if _, err := root.Symlink("old-name", invalidName, 0, 0); err != syscall.EINVAL {
- t.Errorf("got %v for name %q, want EINVAL", err, invalidName)
- }
- }
- }
-}
-
-func TestLink(t *testing.T) {
- for name := range newTypeMap(nil) {
- t.Run(name, func(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- onlyWorksOnDirectories(h, t, name, root, func(backend *Mock, f p9.File, shouldSucceed bool) error {
- if shouldSucceed {
- backend.EXPECT().Link(gomock.Any(), "new-link")
- }
- return f.Link(f, "new-link")
- })
- })
- }
-}
-
-func TestLinkInvalid(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- for name := range newTypeMap(nil) {
- for _, invalidName := range allInvalidNames(name) {
- if err := root.Link(root, invalidName); err != syscall.EINVAL {
- t.Errorf("got %v for name %q, want EINVAL", err, invalidName)
- }
- }
- }
-}
-
-func TestMknod(t *testing.T) {
- for name := range newTypeMap(nil) {
- t.Run(name, func(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- onlyWorksOnDirectories(h, t, name, root, func(backend *Mock, f p9.File, shouldSucceed bool) error {
- if shouldSucceed {
- backend.EXPECT().Mknod("new-block-device", p9.FileMode(0), uint32(1), uint32(2), p9.UID(3), p9.GID(4)).Times(1)
- }
- _, err := f.Mknod("new-block-device", 0, 1, 2, 3, 4)
- return err
- })
- })
- }
-}
-
-// concurrentFn is a specification of a concurrent operation. This is used to
-// drive the concurrency tests below.
-type concurrentFn struct {
- name string
- match func(p9.FileMode) bool
- op func(h *Harness, backend *Mock, f p9.File, callback func())
-}
-
-func concurrentTest(t *testing.T, name string, fn1, fn2 concurrentFn, sameDir, expectedOkay bool) {
- var (
- names1 []string
- names2 []string
- )
- if sameDir {
- // Use the same file one directory up.
- names1, names2 = []string{"one", name}, []string{"one", name}
- } else {
- // For different directories, just use siblings.
- names1, names2 = []string{"one", name}, []string{"three", name}
- }
-
- t.Run(fmt.Sprintf("%s(%v)+%s(%v)", fn1.name, names1, fn2.name, names2), func(t *testing.T) {
- h, c := NewHarness(t)
- defer h.Finish()
-
- _, root := newRoot(h, c)
- defer root.Close()
-
- // Walk to both files as given.
- _, f1, err := root.Walk(names1)
- if err != nil {
- t.Fatalf("error walking, got %v, want nil", err)
- }
- defer f1.Close()
- b1 := h.Pop(f1)
- _, f2, err := root.Walk(names2)
- if err != nil {
- t.Fatalf("error walking, got %v, want nil", err)
- }
- defer f2.Close()
- b2 := h.Pop(f2)
-
- // Are these a good match for the current test case?
- if !fn1.match(b1.Attr.Mode) {
- t.SkipNow()
- }
- if !fn2.match(b2.Attr.Mode) {
- t.SkipNow()
- }
-
- // Construct our "concurrency creator".
- in1 := make(chan struct{}, 1)
- in2 := make(chan struct{}, 1)
- var top sync.WaitGroup
- var fns sync.WaitGroup
- defer top.Wait()
- top.Add(2) // Accounting for below.
- defer fns.Done()
- fns.Add(1) // See line above; released before top.Wait.
- go func() {
- defer top.Done()
- fn1.op(h, b1, f1, func() {
- in1 <- struct{}{}
- fns.Wait()
- })
- }()
- go func() {
- defer top.Done()
- fn2.op(h, b2, f2, func() {
- in2 <- struct{}{}
- fns.Wait()
- })
- }()
-
- // Compute a reasonable timeout. If we expect the operation to hang,
- // give it 10 milliseconds before we assert that it's fine. After all,
- // there will be a lot of these tests. If we don't expect it to hang,
- // give it a full minute, since the machine could be slow.
- timeout := 10 * time.Millisecond
- if expectedOkay {
- timeout = 1 * time.Minute
- }
-
- // Read the first channel.
- var second chan struct{}
- select {
- case <-in1:
- second = in2
- case <-in2:
- second = in1
- }
-
- // Catch concurrency.
- select {
- case <-second:
- // We finished successful. Is this good? Depends on the
- // expected result.
- if !expectedOkay {
- t.Errorf("%q and %q proceeded concurrently!", fn1.name, fn2.name)
- }
- case <-time.After(timeout):
- // Great, things did not proceed concurrently. Is that what we
- // expected?
- if expectedOkay {
- t.Errorf("%q and %q hung concurrently!", fn1.name, fn2.name)
- }
- }
- })
-}
-
-func randomFileName() string {
- return fmt.Sprintf("%x", rand.Int63())
-}
-
-func TestConcurrency(t *testing.T) {
- readExclusive := []concurrentFn{
- {
- // N.B. We can't explicitly check WalkGetAttr behavior,
- // but we rely on the fact that the internal code paths
- // are the same.
- name: "walk",
- match: func(mode p9.FileMode) bool { return mode.IsDir() },
- op: func(h *Harness, backend *Mock, f p9.File, callback func()) {
- // See the documentation of WalkCallback.
- // Because walk is actually implemented by the
- // mock, we need a special place for this
- // callback.
- //
- // Note that a clone actually locks the parent
- // node. So we walk from this node to test
- // concurrent operations appropriately.
- backend.WalkCallback = func() error {
- callback()
- return nil
- }
- f.Walk([]string{randomFileName()}) // Won't exist.
- },
- },
- {
- name: "fsync",
- match: func(mode p9.FileMode) bool { return p9.CanOpen(mode) },
- op: func(h *Harness, backend *Mock, f p9.File, callback func()) {
- backend.EXPECT().Open(gomock.Any())
- backend.EXPECT().FSync().Do(func() {
- callback()
- })
- f.Open(p9.ReadOnly) // Required.
- f.FSync()
- },
- },
- {
- name: "readdir",
- match: func(mode p9.FileMode) bool { return mode.IsDir() },
- op: func(h *Harness, backend *Mock, f p9.File, callback func()) {
- backend.EXPECT().Open(gomock.Any())
- backend.EXPECT().Readdir(gomock.Any(), gomock.Any()).Do(func(uint64, uint32) {
- callback()
- })
- f.Open(p9.ReadOnly) // Required.
- f.Readdir(0, 1)
- },
- },
- {
- name: "readlink",
- match: func(mode p9.FileMode) bool { return mode.IsSymlink() },
- op: func(h *Harness, backend *Mock, f p9.File, callback func()) {
- backend.EXPECT().Readlink().Do(func() {
- callback()
- })
- f.Readlink()
- },
- },
- {
- name: "connect",
- match: func(mode p9.FileMode) bool { return mode.IsSocket() },
- op: func(h *Harness, backend *Mock, f p9.File, callback func()) {
- backend.EXPECT().Connect(gomock.Any()).Do(func(p9.ConnectFlags) {
- callback()
- })
- f.Connect(0)
- },
- },
- {
- name: "open",
- match: func(mode p9.FileMode) bool { return p9.CanOpen(mode) },
- op: func(h *Harness, backend *Mock, f p9.File, callback func()) {
- backend.EXPECT().Open(gomock.Any()).Do(func(p9.OpenFlags) {
- callback()
- })
- f.Open(p9.ReadOnly)
- },
- },
- {
- name: "flush",
- match: func(mode p9.FileMode) bool { return true },
- op: func(h *Harness, backend *Mock, f p9.File, callback func()) {
- backend.EXPECT().Flush().Do(func() {
- callback()
- })
- f.Flush()
- },
- },
- }
- writeExclusive := []concurrentFn{
- {
- // N.B. We can't really check getattr. But this is an
- // extremely low-risk function, it seems likely that
- // this check is paranoid anyways.
- name: "setattr",
- match: func(mode p9.FileMode) bool { return true },
- op: func(h *Harness, backend *Mock, f p9.File, callback func()) {
- backend.EXPECT().SetAttr(gomock.Any(), gomock.Any()).Do(func(p9.SetAttrMask, p9.SetAttr) {
- callback()
- })
- f.SetAttr(p9.SetAttrMask{}, p9.SetAttr{})
- },
- },
- {
- name: "unlinkAt",
- match: func(mode p9.FileMode) bool { return mode.IsDir() },
- op: func(h *Harness, backend *Mock, f p9.File, callback func()) {
- backend.EXPECT().UnlinkAt(gomock.Any(), gomock.Any()).Do(func(string, uint32) {
- callback()
- })
- f.UnlinkAt(randomFileName(), 0)
- },
- },
- {
- name: "mknod",
- match: func(mode p9.FileMode) bool { return mode.IsDir() },
- op: func(h *Harness, backend *Mock, f p9.File, callback func()) {
- backend.EXPECT().Mknod(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Do(func(string, p9.FileMode, uint32, uint32, p9.UID, p9.GID) {
- callback()
- })
- f.Mknod(randomFileName(), 0, 0, 0, 0, 0)
- },
- },
- {
- name: "link",
- match: func(mode p9.FileMode) bool { return mode.IsDir() },
- op: func(h *Harness, backend *Mock, f p9.File, callback func()) {
- backend.EXPECT().Link(gomock.Any(), gomock.Any()).Do(func(p9.File, string) {
- callback()
- })
- f.Link(f, randomFileName())
- },
- },
- {
- name: "symlink",
- match: func(mode p9.FileMode) bool { return mode.IsDir() },
- op: func(h *Harness, backend *Mock, f p9.File, callback func()) {
- backend.EXPECT().Symlink(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Do(func(string, string, p9.UID, p9.GID) {
- callback()
- })
- f.Symlink(randomFileName(), randomFileName(), 0, 0)
- },
- },
- {
- name: "mkdir",
- match: func(mode p9.FileMode) bool { return mode.IsDir() },
- op: func(h *Harness, backend *Mock, f p9.File, callback func()) {
- backend.EXPECT().Mkdir(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Do(func(string, p9.FileMode, p9.UID, p9.GID) {
- callback()
- })
- f.Mkdir(randomFileName(), 0, 0, 0)
- },
- },
- {
- name: "create",
- match: func(mode p9.FileMode) bool { return mode.IsDir() },
- op: func(h *Harness, backend *Mock, f p9.File, callback func()) {
- // Return an error for the creation operation, as this is the simplest.
- backend.EXPECT().Create(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil, p9.QID{}, uint32(0), syscall.EINVAL).Do(func(string, p9.OpenFlags, p9.FileMode, p9.UID, p9.GID) {
- callback()
- })
- f.Create(randomFileName(), p9.ReadOnly, 0, 0, 0)
- },
- },
- }
- globalExclusive := []concurrentFn{
- {
- name: "remove",
- match: func(mode p9.FileMode) bool { return mode.IsDir() },
- op: func(h *Harness, backend *Mock, f p9.File, callback func()) {
- // Remove operates on a locked parent. So we
- // add a child, walk to it and call remove.
- // Note that because this operation can operate
- // concurrently with itself, we need to
- // generate a random file name.
- randomFile := randomFileName()
- backend.AddChild(randomFile, h.NewFile())
- defer backend.RemoveChild(randomFile)
- _, file, err := f.Walk([]string{randomFile})
- if err != nil {
- h.t.Fatalf("walk got %v, want nil", err)
- }
-
- // Remove is automatically translated to the parent.
- backend.EXPECT().UnlinkAt(gomock.Any(), gomock.Any()).Do(func(string, uint32) {
- callback()
- })
-
- // Remove is also a close.
- file.(deprecatedRemover).Remove()
- },
- },
- {
- name: "rename",
- match: func(mode p9.FileMode) bool { return mode.IsDir() },
- op: func(h *Harness, backend *Mock, f p9.File, callback func()) {
- // Similarly to remove, because we need to
- // operate on a child, we allow a walk.
- randomFile := randomFileName()
- backend.AddChild(randomFile, h.NewFile())
- defer backend.RemoveChild(randomFile)
- _, file, err := f.Walk([]string{randomFile})
- if err != nil {
- h.t.Fatalf("walk got %v, want nil", err)
- }
- defer file.Close()
- fileBackend := h.Pop(file)
-
- // Rename is automatically translated to the parent.
- backend.EXPECT().RenameAt(gomock.Any(), gomock.Any(), gomock.Any()).Do(func(string, p9.File, string) {
- callback()
- })
-
- // Attempt the rename.
- fileBackend.EXPECT().Renamed(gomock.Any(), gomock.Any())
- file.Rename(f, randomFileName())
- },
- },
- {
- name: "renameAt",
- match: func(mode p9.FileMode) bool { return mode.IsDir() },
- op: func(h *Harness, backend *Mock, f p9.File, callback func()) {
- backend.EXPECT().RenameAt(gomock.Any(), gomock.Any(), gomock.Any()).Do(func(string, p9.File, string) {
- callback()
- })
-
- // Attempt the rename. There are no active fids
- // with this name, so we don't need to expect
- // Renamed hooks on anything.
- f.RenameAt(randomFileName(), f, randomFileName())
- },
- },
- }
-
- for _, fn1 := range readExclusive {
- for _, fn2 := range readExclusive {
- for name := range newTypeMap(nil) {
- // Everything should be able to proceed in parallel.
- concurrentTest(t, name, fn1, fn2, true, true)
- concurrentTest(t, name, fn1, fn2, false, true)
- }
- }
- }
-
- for _, fn1 := range append(readExclusive, writeExclusive...) {
- for _, fn2 := range writeExclusive {
- for name := range newTypeMap(nil) {
- // Only cross-directory functions should proceed in parallel.
- concurrentTest(t, name, fn1, fn2, true, false)
- concurrentTest(t, name, fn1, fn2, false, true)
- }
- }
- }
-
- for _, fn1 := range append(append(readExclusive, writeExclusive...), globalExclusive...) {
- for _, fn2 := range globalExclusive {
- for name := range newTypeMap(nil) {
- // Nothing should be able to run in parallel.
- concurrentTest(t, name, fn1, fn2, true, false)
- concurrentTest(t, name, fn1, fn2, false, false)
- }
- }
- }
-}
diff --git a/pkg/p9/p9test/p9test.go b/pkg/p9/p9test/p9test.go
deleted file mode 100644
index 9d74638bb..000000000
--- a/pkg/p9/p9test/p9test.go
+++ /dev/null
@@ -1,329 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package p9test provides standard mocks for p9.
-package p9test
-
-import (
- "fmt"
- "sync"
- "sync/atomic"
- "syscall"
- "testing"
-
- "github.com/golang/mock/gomock"
- "gvisor.dev/gvisor/pkg/p9"
- "gvisor.dev/gvisor/pkg/unet"
-)
-
-// Harness is an attacher mock.
-type Harness struct {
- t *testing.T
- mockCtrl *gomock.Controller
- Attacher *MockAttacher
- wg sync.WaitGroup
- clientSocket *unet.Socket
- mu sync.Mutex
- created []*Mock
-}
-
-// globalPath is a QID.Path Generator.
-var globalPath uint64
-
-// MakePath returns a globally unique path.
-func MakePath() uint64 {
- return atomic.AddUint64(&globalPath, 1)
-}
-
-// Generator is a function that generates a new file.
-type Generator func(parent *Mock) *Mock
-
-// Mock is a common mock element.
-type Mock struct {
- p9.DefaultWalkGetAttr
- *MockFile
- parent *Mock
- closed bool
- harness *Harness
- QID p9.QID
- Attr p9.Attr
- children map[string]Generator
-
- // WalkCallback is a special function that will be called from within
- // the walk context. This is needed for the concurrent tests within
- // this package.
- WalkCallback func() error
-}
-
-// globalMu protects the children maps in all mocks. Note that this is not a
-// particularly elegant solution, but because the test has walks from the root
-// through to final nodes, we must share maps below, and it's easiest to simply
-// protect against concurrent access globally.
-var globalMu sync.RWMutex
-
-// AddChild adds a new child to the Mock.
-func (m *Mock) AddChild(name string, generator Generator) {
- globalMu.Lock()
- defer globalMu.Unlock()
- m.children[name] = generator
-}
-
-// RemoveChild removes the child with the given name.
-func (m *Mock) RemoveChild(name string) {
- globalMu.Lock()
- defer globalMu.Unlock()
- delete(m.children, name)
-}
-
-// Matches implements gomock.Matcher.Matches.
-func (m *Mock) Matches(x interface{}) bool {
- if om, ok := x.(*Mock); ok {
- return m.QID.Path == om.QID.Path
- }
- return false
-}
-
-// String implements gomock.Matcher.String.
-func (m *Mock) String() string {
- return fmt.Sprintf("Mock{Mode: 0x%x, QID.Path: %d}", m.Attr.Mode, m.QID.Path)
-}
-
-// GetAttr returns the current attributes.
-func (m *Mock) GetAttr(mask p9.AttrMask) (p9.QID, p9.AttrMask, p9.Attr, error) {
- return m.QID, p9.AttrMaskAll(), m.Attr, nil
-}
-
-// Walk supports clone and walking in directories.
-func (m *Mock) Walk(names []string) ([]p9.QID, p9.File, error) {
- if m.WalkCallback != nil {
- if err := m.WalkCallback(); err != nil {
- return nil, nil, err
- }
- }
- if len(names) == 0 {
- // Clone the file appropriately.
- nm := m.harness.NewMock(m.parent, m.QID.Path, m.Attr)
- nm.children = m.children // Inherit children.
- return []p9.QID{nm.QID}, nm, nil
- } else if len(names) != 1 {
- m.harness.t.Fail() // Should not happen.
- return nil, nil, syscall.EINVAL
- }
-
- if m.Attr.Mode.IsDir() {
- globalMu.RLock()
- defer globalMu.RUnlock()
- if fn, ok := m.children[names[0]]; ok {
- // Generate the child.
- nm := fn(m)
- return []p9.QID{nm.QID}, nm, nil
- }
- // No child found.
- return nil, nil, syscall.ENOENT
- }
-
- // Call the underlying mock.
- return m.MockFile.Walk(names)
-}
-
-// WalkGetAttr calls the default implementation; this is a client-side optimization.
-func (m *Mock) WalkGetAttr(names []string) ([]p9.QID, p9.File, p9.AttrMask, p9.Attr, error) {
- return m.DefaultWalkGetAttr.WalkGetAttr(names)
-}
-
-// Pop pops off the most recently created Mock and assert that this mock
-// represents the same file passed in. If nil is passed in, no check is
-// performed.
-//
-// Precondition: there must be at least one Mock or this will panic.
-func (h *Harness) Pop(clientFile p9.File) *Mock {
- h.mu.Lock()
- defer h.mu.Unlock()
-
- if clientFile == nil {
- // If no clientFile is provided, then we always return the last
- // created file. The caller can safely use this as long as
- // there is no concurrency.
- m := h.created[len(h.created)-1]
- h.created = h.created[:len(h.created)-1]
- return m
- }
-
- qid, _, _, err := clientFile.GetAttr(p9.AttrMaskAll())
- if err != nil {
- // We do not expect this to happen.
- panic(fmt.Sprintf("err during Pop: %v", err))
- }
-
- // Find the relevant file in our created list. We must scan the last
- // from back to front to ensure that we favor the most recently
- // generated file.
- for i := len(h.created) - 1; i >= 0; i-- {
- m := h.created[i]
- if qid.Path == m.QID.Path {
- // Copy and truncate.
- copy(h.created[i:], h.created[i+1:])
- h.created = h.created[:len(h.created)-1]
- return m
- }
- }
-
- // Unable to find relevant file.
- panic(fmt.Sprintf("unable to locate file with QID %+v", qid.Path))
-}
-
-// NewMock returns a new base file.
-func (h *Harness) NewMock(parent *Mock, path uint64, attr p9.Attr) *Mock {
- m := &Mock{
- MockFile: NewMockFile(h.mockCtrl),
- parent: parent,
- harness: h,
- QID: p9.QID{
- Type: p9.QIDType((attr.Mode & p9.FileModeMask) >> 12),
- Path: path,
- },
- Attr: attr,
- }
-
- // Always ensure Close is after the parent's close. Note that this
- // can't be done via a straight-forward After call, because the parent
- // might change after initial creation. We ensure that this is true at
- // close time.
- m.EXPECT().Close().Return(nil).Times(1).Do(func() {
- if m.parent != nil && m.parent.closed {
- h.t.FailNow()
- }
- // Note that this should not be racy, as this operation should
- // be protected by the Times(1) above first.
- m.closed = true
- })
-
- // Remember what was created.
- h.mu.Lock()
- defer h.mu.Unlock()
- h.created = append(h.created, m)
-
- return m
-}
-
-// NewFile returns a new file mock.
-//
-// Note that ReadAt and WriteAt must be mocked separately.
-func (h *Harness) NewFile() Generator {
- return func(parent *Mock) *Mock {
- return h.NewMock(parent, MakePath(), p9.Attr{Mode: p9.ModeRegular})
- }
-}
-
-// NewDirectory returns a new mock directory.
-//
-// Note that Mkdir, Link, Mknod, RenameAt, UnlinkAt and Readdir must be mocked
-// separately. Walk is provided and children may be manipulated via AddChild
-// and RemoveChild. After calling Walk remotely, one can use Pop to find the
-// corresponding backend mock on the server side.
-func (h *Harness) NewDirectory(contents map[string]Generator) Generator {
- return func(parent *Mock) *Mock {
- m := h.NewMock(parent, MakePath(), p9.Attr{Mode: p9.ModeDirectory})
- m.children = contents // Save contents.
- return m
- }
-}
-
-// NewSymlink returns a new mock directory.
-//
-// Note that Readlink must be mocked separately.
-func (h *Harness) NewSymlink() Generator {
- return func(parent *Mock) *Mock {
- return h.NewMock(parent, MakePath(), p9.Attr{Mode: p9.ModeSymlink})
- }
-}
-
-// NewBlockDevice returns a new mock block device.
-func (h *Harness) NewBlockDevice() Generator {
- return func(parent *Mock) *Mock {
- return h.NewMock(parent, MakePath(), p9.Attr{Mode: p9.ModeBlockDevice})
- }
-}
-
-// NewCharacterDevice returns a new mock character device.
-func (h *Harness) NewCharacterDevice() Generator {
- return func(parent *Mock) *Mock {
- return h.NewMock(parent, MakePath(), p9.Attr{Mode: p9.ModeCharacterDevice})
- }
-}
-
-// NewNamedPipe returns a new mock named pipe.
-func (h *Harness) NewNamedPipe() Generator {
- return func(parent *Mock) *Mock {
- return h.NewMock(parent, MakePath(), p9.Attr{Mode: p9.ModeNamedPipe})
- }
-}
-
-// NewSocket returns a new mock socket.
-func (h *Harness) NewSocket() Generator {
- return func(parent *Mock) *Mock {
- return h.NewMock(parent, MakePath(), p9.Attr{Mode: p9.ModeSocket})
- }
-}
-
-// Finish completes all checks and shuts down the server.
-func (h *Harness) Finish() {
- h.clientSocket.Close()
- h.wg.Wait()
- h.mockCtrl.Finish()
-}
-
-// NewHarness creates and returns a new test server.
-//
-// It should always be used as:
-//
-// h, c := NewHarness(t)
-// defer h.Finish()
-//
-func NewHarness(t *testing.T) (*Harness, *p9.Client) {
- // Create the mock.
- mockCtrl := gomock.NewController(t)
- h := &Harness{
- t: t,
- mockCtrl: mockCtrl,
- Attacher: NewMockAttacher(mockCtrl),
- }
-
- // Make socket pair.
- serverSocket, clientSocket, err := unet.SocketPair(false)
- if err != nil {
- t.Fatalf("socketpair got err %v wanted nil", err)
- }
-
- // Start the server, synchronized on exit.
- server := p9.NewServer(h.Attacher)
- h.wg.Add(1)
- go func() {
- defer h.wg.Done()
- server.Handle(serverSocket)
- }()
-
- // Create the client.
- client, err := p9.NewClient(clientSocket, p9.DefaultMessageSize, p9.HighestVersionString())
- if err != nil {
- serverSocket.Close()
- clientSocket.Close()
- t.Fatalf("new client got %v, expected nil", err)
- return nil, nil // Never hit.
- }
-
- // Capture the client socket.
- h.clientSocket = clientSocket
- return h, client
-}
diff --git a/pkg/p9/pool_test.go b/pkg/p9/pool_test.go
deleted file mode 100644
index e4746b8da..000000000
--- a/pkg/p9/pool_test.go
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package p9
-
-import (
- "testing"
-)
-
-func TestPoolUnique(t *testing.T) {
- p := pool{start: 1, limit: 3}
- got := make(map[uint64]bool)
-
- for {
- n, ok := p.Get()
- if !ok {
- break
- }
-
- // Check unique.
- if _, ok := got[n]; ok {
- t.Errorf("pool spit out %v multiple times", n)
- }
-
- // Record.
- got[n] = true
- }
-}
-
-func TestExausted(t *testing.T) {
- p := pool{start: 1, limit: 500}
- for i := 0; i < 499; i++ {
- _, ok := p.Get()
- if !ok {
- t.Fatalf("pool exhausted before 499 items")
- }
- }
-
- _, ok := p.Get()
- if ok {
- t.Errorf("pool not exhausted when it should be")
- }
-}
-
-func TestPoolRecycle(t *testing.T) {
- p := pool{start: 1, limit: 500}
- n1, _ := p.Get()
- p.Put(n1)
- n2, _ := p.Get()
- if n1 != n2 {
- t.Errorf("pool not recycling items")
- }
-}
diff --git a/pkg/p9/transport_flipcall.go b/pkg/p9/transport_flipcall.go
index aebb54959..aebb54959 100644..100755
--- a/pkg/p9/transport_flipcall.go
+++ b/pkg/p9/transport_flipcall.go
diff --git a/pkg/p9/transport_test.go b/pkg/p9/transport_test.go
deleted file mode 100644
index 2f50ff3ea..000000000
--- a/pkg/p9/transport_test.go
+++ /dev/null
@@ -1,231 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package p9
-
-import (
- "io/ioutil"
- "os"
- "testing"
-
- "gvisor.dev/gvisor/pkg/fd"
- "gvisor.dev/gvisor/pkg/unet"
-)
-
-const (
- MsgTypeBadEncode = iota + 252
- MsgTypeBadDecode
- MsgTypeUnregistered
-)
-
-func TestSendRecv(t *testing.T) {
- server, client, err := unet.SocketPair(false)
- if err != nil {
- t.Fatalf("socketpair got err %v expected nil", err)
- }
- defer server.Close()
- defer client.Close()
-
- if err := send(client, Tag(1), &Tlopen{}); err != nil {
- t.Fatalf("send got err %v expected nil", err)
- }
-
- tag, m, err := recv(server, maximumLength, msgRegistry.get)
- if err != nil {
- t.Fatalf("recv got err %v expected nil", err)
- }
- if tag != Tag(1) {
- t.Fatalf("got tag %v expected 1", tag)
- }
- if _, ok := m.(*Tlopen); !ok {
- t.Fatalf("got message %v expected *Tlopen", m)
- }
-}
-
-// badDecode overruns on decode.
-type badDecode struct{}
-
-func (*badDecode) Decode(b *buffer) { b.markOverrun() }
-func (*badDecode) Encode(b *buffer) {}
-func (*badDecode) Type() MsgType { return MsgTypeBadDecode }
-func (*badDecode) String() string { return "badDecode{}" }
-
-func TestRecvOverrun(t *testing.T) {
- server, client, err := unet.SocketPair(false)
- if err != nil {
- t.Fatalf("socketpair got err %v expected nil", err)
- }
- defer server.Close()
- defer client.Close()
-
- if err := send(client, Tag(1), &badDecode{}); err != nil {
- t.Fatalf("send got err %v expected nil", err)
- }
-
- if _, _, err := recv(server, maximumLength, msgRegistry.get); err == nil {
- t.Fatalf("recv got err %v expected ErrSocket{ErrNoValidMessage}", err)
- }
-}
-
-// unregistered is not registered on decode.
-type unregistered struct{}
-
-func (*unregistered) Decode(b *buffer) {}
-func (*unregistered) Encode(b *buffer) {}
-func (*unregistered) Type() MsgType { return MsgTypeUnregistered }
-func (*unregistered) String() string { return "unregistered{}" }
-
-func TestRecvInvalidType(t *testing.T) {
- server, client, err := unet.SocketPair(false)
- if err != nil {
- t.Fatalf("socketpair got err %v expected nil", err)
- }
- defer server.Close()
- defer client.Close()
-
- if err := send(client, Tag(1), &unregistered{}); err != nil {
- t.Fatalf("send got err %v expected nil", err)
- }
-
- _, _, err = recv(server, maximumLength, msgRegistry.get)
- if _, ok := err.(*ErrInvalidMsgType); !ok {
- t.Fatalf("recv got err %v expected ErrInvalidMsgType", err)
- }
-}
-
-func TestSendRecvWithFile(t *testing.T) {
- server, client, err := unet.SocketPair(false)
- if err != nil {
- t.Fatalf("socketpair got err %v expected nil", err)
- }
- defer server.Close()
- defer client.Close()
-
- // Create a tempfile.
- osf, err := ioutil.TempFile("", "p9")
- if err != nil {
- t.Fatalf("tempfile got err %v expected nil", err)
- }
- os.Remove(osf.Name())
- f, err := fd.NewFromFile(osf)
- osf.Close()
- if err != nil {
- t.Fatalf("unable to create file: %v", err)
- }
-
- rlopen := &Rlopen{}
- rlopen.SetFilePayload(f)
- if err := send(client, Tag(1), rlopen); err != nil {
- t.Fatalf("send got err %v expected nil", err)
- }
-
- // Enable withFile.
- tag, m, err := recv(server, maximumLength, msgRegistry.get)
- if err != nil {
- t.Fatalf("recv got err %v expected nil", err)
- }
- if tag != Tag(1) {
- t.Fatalf("got tag %v expected 1", tag)
- }
- rlopen, ok := m.(*Rlopen)
- if !ok {
- t.Fatalf("got m %v expected *Rlopen", m)
- }
- if rlopen.File == nil {
- t.Fatalf("got nil file expected non-nil")
- }
-}
-
-func TestRecvClosed(t *testing.T) {
- server, client, err := unet.SocketPair(false)
- if err != nil {
- t.Fatalf("socketpair got err %v expected nil", err)
- }
- defer server.Close()
- client.Close()
-
- _, _, err = recv(server, maximumLength, msgRegistry.get)
- if err == nil {
- t.Fatalf("got err nil expected non-nil")
- }
- if _, ok := err.(ErrSocket); !ok {
- t.Fatalf("got err %v expected ErrSocket", err)
- }
-}
-
-func TestSendClosed(t *testing.T) {
- server, client, err := unet.SocketPair(false)
- if err != nil {
- t.Fatalf("socketpair got err %v expected nil", err)
- }
- server.Close()
- defer client.Close()
-
- err = send(client, Tag(1), &Tlopen{})
- if err == nil {
- t.Fatalf("send got err nil expected non-nil")
- }
- if _, ok := err.(ErrSocket); !ok {
- t.Fatalf("got err %v expected ErrSocket", err)
- }
-}
-
-func BenchmarkSendRecv(b *testing.B) {
- server, client, err := unet.SocketPair(false)
- if err != nil {
- b.Fatalf("socketpair got err %v expected nil", err)
- }
- defer server.Close()
- defer client.Close()
-
- // Exchange Rflush messages since these contain no data and therefore incur
- // no additional marshaling overhead.
- go func() {
- for i := 0; i < b.N; i++ {
- tag, m, err := recv(server, maximumLength, msgRegistry.get)
- if err != nil {
- b.Fatalf("recv got err %v expected nil", err)
- }
- if tag != Tag(1) {
- b.Fatalf("got tag %v expected 1", tag)
- }
- if _, ok := m.(*Rflush); !ok {
- b.Fatalf("got message %T expected *Rflush", m)
- }
- if err := send(server, Tag(2), &Rflush{}); err != nil {
- b.Fatalf("send got err %v expected nil", err)
- }
- }
- }()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- if err := send(client, Tag(1), &Rflush{}); err != nil {
- b.Fatalf("send got err %v expected nil", err)
- }
- tag, m, err := recv(client, maximumLength, msgRegistry.get)
- if err != nil {
- b.Fatalf("recv got err %v expected nil", err)
- }
- if tag != Tag(2) {
- b.Fatalf("got tag %v expected 2", tag)
- }
- if _, ok := m.(*Rflush); !ok {
- b.Fatalf("got message %v expected *Rflush", m)
- }
- }
-}
-
-func init() {
- msgRegistry.register(MsgTypeBadDecode, func() message { return &badDecode{} })
-}
diff --git a/pkg/p9/version_test.go b/pkg/p9/version_test.go
deleted file mode 100644
index 291e8580e..000000000
--- a/pkg/p9/version_test.go
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package p9
-
-import (
- "testing"
-)
-
-func TestVersionNumberEquivalent(t *testing.T) {
- for i := uint32(0); i < 1024; i++ {
- str := versionString(i)
- version, ok := parseVersion(str)
- if !ok {
- t.Errorf("#%d: parseVersion(%q) failed, want success", i, str)
- continue
- }
- if i != version {
- t.Errorf("#%d: got version %d, want %d", i, i, version)
- }
- }
-}
-
-func TestVersionStringEquivalent(t *testing.T) {
- // There is one case where the version is not equivalent on purpose,
- // that is 9P2000.L.Google.0. It is not equivalent because versionString
- // must always return the more generic 9P2000.L for legacy servers that
- // check for it. See net/9p/client.c.
- str := "9P2000.L.Google.0"
- version, ok := parseVersion(str)
- if !ok {
- t.Errorf("parseVersion(%q) failed, want success", str)
- }
- if got := versionString(version); got != "9P2000.L" {
- t.Errorf("versionString(%d) got %q, want %q", version, got, "9P2000.L")
- }
-
- for _, test := range []struct {
- versionString string
- }{
- {
- versionString: "9P2000.L",
- },
- {
- versionString: "9P2000.L.Google.1",
- },
- {
- versionString: "9P2000.L.Google.347823894",
- },
- } {
- version, ok := parseVersion(test.versionString)
- if !ok {
- t.Errorf("parseVersion(%q) failed, want success", test.versionString)
- continue
- }
- if got := versionString(version); got != test.versionString {
- t.Errorf("versionString(%d) got %q, want %q", version, got, test.versionString)
- }
- }
-}
-
-func TestParseVersion(t *testing.T) {
- for _, test := range []struct {
- versionString string
- expectSuccess bool
- expectedVersion uint32
- }{
- {
- versionString: "9P",
- expectSuccess: false,
- },
- {
- versionString: "9P.L",
- expectSuccess: false,
- },
- {
- versionString: "9P200.L",
- expectSuccess: false,
- },
- {
- versionString: "9P2000",
- expectSuccess: false,
- },
- {
- versionString: "9P2000.L.Google.-1",
- expectSuccess: false,
- },
- {
- versionString: "9P2000.L.Google.",
- expectSuccess: false,
- },
- {
- versionString: "9P2000.L.Google.3546343826724305832",
- expectSuccess: false,
- },
- {
- versionString: "9P2001.L",
- expectSuccess: false,
- },
- {
- versionString: "9P2000.L",
- expectSuccess: true,
- expectedVersion: 0,
- },
- {
- versionString: "9P2000.L.Google.0",
- expectSuccess: true,
- expectedVersion: 0,
- },
- {
- versionString: "9P2000.L.Google.1",
- expectSuccess: true,
- expectedVersion: 1,
- },
- } {
- version, ok := parseVersion(test.versionString)
- if ok != test.expectSuccess {
- t.Errorf("parseVersion(%q) got (_, %v), want (_, %v)", test.versionString, ok, test.expectSuccess)
- continue
- }
- if !test.expectSuccess {
- continue
- }
- if version != test.expectedVersion {
- t.Errorf("parseVersion(%q) got (%d, _), want (%d, _)", test.versionString, version, test.expectedVersion)
- }
- }
-}
-
-func BenchmarkParseVersion(b *testing.B) {
- for n := 0; n < b.N; n++ {
- parseVersion("9P2000.L.Google.1")
- }
-}
diff --git a/pkg/procid/BUILD b/pkg/procid/BUILD
deleted file mode 100644
index 078f084b2..000000000
--- a/pkg/procid/BUILD
+++ /dev/null
@@ -1,34 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "procid",
- srcs = [
- "procid.go",
- "procid_amd64.s",
- "procid_arm64.s",
- ],
- importpath = "gvisor.dev/gvisor/pkg/procid",
- visibility = ["//visibility:public"],
-)
-
-go_test(
- name = "procid_test",
- size = "small",
- srcs = [
- "procid_test.go",
- ],
- embed = [":procid"],
-)
-
-go_test(
- name = "procid_net_test",
- size = "small",
- srcs = [
- "procid_net_test.go",
- "procid_test.go",
- ],
- embed = [":procid"],
-)
diff --git a/pkg/procid/procid_net_test.go b/pkg/procid/procid_net_test.go
deleted file mode 100644
index b628e2285..000000000
--- a/pkg/procid/procid_net_test.go
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package procid
-
-// This file is just to force the inclusion of the "net" package, which will
-// make the test binary a cgo one.
-import (
- _ "net"
-)
diff --git a/pkg/procid/procid_state_autogen.go b/pkg/procid/procid_state_autogen.go
new file mode 100755
index 000000000..f27a7c510
--- /dev/null
+++ b/pkg/procid/procid_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package procid
+
diff --git a/pkg/procid/procid_test.go b/pkg/procid/procid_test.go
deleted file mode 100644
index 88dd0b3ae..000000000
--- a/pkg/procid/procid_test.go
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package procid
-
-import (
- "os"
- "runtime"
- "sync"
- "syscall"
- "testing"
-)
-
-// runOnMain is used to send functions to run on the main (initial) thread.
-var runOnMain = make(chan func(), 10)
-
-func checkProcid(t *testing.T, start *sync.WaitGroup, done *sync.WaitGroup) {
- defer done.Done()
-
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
-
- start.Done()
- start.Wait()
-
- procID := Current()
- tid := syscall.Gettid()
-
- if procID != uint64(tid) {
- t.Logf("Bad procid: expected %v, got %v", tid, procID)
- t.Fail()
- }
-}
-
-func TestProcidInitialized(t *testing.T) {
- var start sync.WaitGroup
- var done sync.WaitGroup
-
- count := 100
- start.Add(count + 1)
- done.Add(count + 1)
-
- // Run the check on the main thread.
- //
- // When cgo is not included, the only case when procid isn't initialized
- // is in the main (initial) thread, so we have to test this case
- // specifically.
- runOnMain <- func() {
- checkProcid(t, &start, &done)
- }
-
- // Run the check on a number of different threads.
- for i := 0; i < count; i++ {
- go checkProcid(t, &start, &done)
- }
-
- done.Wait()
-}
-
-func TestMain(m *testing.M) {
- // Make sure we remain at the main (initial) thread.
- runtime.LockOSThread()
-
- // Start running tests in a different goroutine.
- go func() {
- os.Exit(m.Run())
- }()
-
- // Execute any functions that have been sent for execution in the main
- // thread.
- for f := range runOnMain {
- f()
- }
-}
diff --git a/pkg/rand/BUILD b/pkg/rand/BUILD
deleted file mode 100644
index f4f2001f3..000000000
--- a/pkg/rand/BUILD
+++ /dev/null
@@ -1,14 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "rand",
- srcs = [
- "rand.go",
- "rand_linux.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/rand",
- visibility = ["//:sandbox"],
- deps = ["@org_golang_x_sys//unix:go_default_library"],
-)
diff --git a/pkg/rand/rand_state_autogen.go b/pkg/rand/rand_state_autogen.go
new file mode 100755
index 000000000..e46e9ec7e
--- /dev/null
+++ b/pkg/rand/rand_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package rand
+
diff --git a/pkg/refs/BUILD b/pkg/refs/BUILD
deleted file mode 100644
index 827385139..000000000
--- a/pkg/refs/BUILD
+++ /dev/null
@@ -1,39 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_template_instance(
- name = "weak_ref_list",
- out = "weak_ref_list.go",
- package = "refs",
- prefix = "weakRef",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*WeakRef",
- "Linker": "*WeakRef",
- },
-)
-
-go_library(
- name = "refs",
- srcs = [
- "refcounter.go",
- "refcounter_state.go",
- "weak_ref_list.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/refs",
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/log",
- ],
-)
-
-go_test(
- name = "refs_test",
- size = "small",
- srcs = ["refcounter_test.go"],
- embed = [":refs"],
-)
diff --git a/pkg/refs/refcounter_test.go b/pkg/refs/refcounter_test.go
deleted file mode 100644
index ffd3d3f07..000000000
--- a/pkg/refs/refcounter_test.go
+++ /dev/null
@@ -1,172 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package refs
-
-import (
- "reflect"
- "sync"
- "testing"
-)
-
-type testCounter struct {
- AtomicRefCount
-
- // mu protects the boolean below.
- mu sync.Mutex
-
- // destroyed indicates whether this was destroyed.
- destroyed bool
-}
-
-func (t *testCounter) DecRef() {
- t.AtomicRefCount.DecRefWithDestructor(t.destroy)
-}
-
-func (t *testCounter) destroy() {
- t.mu.Lock()
- defer t.mu.Unlock()
- t.destroyed = true
-}
-
-func (t *testCounter) IsDestroyed() bool {
- t.mu.Lock()
- defer t.mu.Unlock()
- return t.destroyed
-}
-
-func newTestCounter() *testCounter {
- return &testCounter{destroyed: false}
-}
-
-func TestOneRef(t *testing.T) {
- tc := newTestCounter()
- tc.DecRef()
-
- if !tc.IsDestroyed() {
- t.Errorf("object should have been destroyed")
- }
-}
-
-func TestTwoRefs(t *testing.T) {
- tc := newTestCounter()
- tc.IncRef()
- tc.DecRef()
- tc.DecRef()
-
- if !tc.IsDestroyed() {
- t.Errorf("object should have been destroyed")
- }
-}
-
-func TestMultiRefs(t *testing.T) {
- tc := newTestCounter()
- tc.IncRef()
- tc.DecRef()
-
- tc.IncRef()
- tc.DecRef()
-
- tc.DecRef()
-
- if !tc.IsDestroyed() {
- t.Errorf("object should have been destroyed")
- }
-}
-
-func TestWeakRef(t *testing.T) {
- tc := newTestCounter()
- w := NewWeakRef(tc, nil)
-
- // Try resolving.
- if x := w.Get(); x == nil {
- t.Errorf("weak reference didn't resolve: expected %v, got nil", tc)
- } else {
- x.DecRef()
- }
-
- // Try resolving again.
- if x := w.Get(); x == nil {
- t.Errorf("weak reference didn't resolve: expected %v, got nil", tc)
- } else {
- x.DecRef()
- }
-
- // Shouldn't be destroyed yet. (Can't continue if this fails.)
- if tc.IsDestroyed() {
- t.Fatalf("original object destroyed earlier than expected")
- }
-
- // Drop the original reference.
- tc.DecRef()
-
- // Assert destroyed.
- if !tc.IsDestroyed() {
- t.Errorf("original object not destroyed as expected")
- }
-
- // Shouldn't be anything.
- if x := w.Get(); x != nil {
- t.Errorf("weak reference resolved: expected nil, got %v", x)
- }
-}
-
-func TestWeakRefDrop(t *testing.T) {
- tc := newTestCounter()
- w := NewWeakRef(tc, nil)
- w.Drop()
-
- // Just assert the list is empty.
- if !tc.weakRefs.Empty() {
- t.Errorf("weak reference not dropped")
- }
-
- // Drop the original reference.
- tc.DecRef()
-}
-
-type testWeakRefUser struct {
- weakRefGone func()
-}
-
-func (u *testWeakRefUser) WeakRefGone() {
- u.weakRefGone()
-}
-
-func TestCallback(t *testing.T) {
- called := false
- tc := newTestCounter()
- var w *WeakRef
- w = NewWeakRef(tc, &testWeakRefUser{func() {
- called = true
-
- // Check that the weak ref has been zapped.
- rc := w.obj.Load().(RefCounter)
- if v := reflect.ValueOf(rc); v != reflect.Zero(v.Type()) {
- t.Fatalf("Callback called with non-nil ptr")
- }
-
- // Check that we're not holding the mutex by acquiring and
- // releasing it.
- tc.mu.Lock()
- tc.mu.Unlock()
- }})
-
- // Drop the original reference, this must trigger the callback.
- tc.DecRef()
-
- if !called {
- t.Fatalf("Callback not called")
- }
-}
diff --git a/pkg/refs/refs_state_autogen.go b/pkg/refs/refs_state_autogen.go
new file mode 100755
index 000000000..1f69f0c0c
--- /dev/null
+++ b/pkg/refs/refs_state_autogen.go
@@ -0,0 +1,81 @@
+// automatically generated by stateify.
+
+package refs
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *WeakRef) beforeSave() {}
+func (x *WeakRef) save(m state.Map) {
+ x.beforeSave()
+ var obj savedReference = x.saveObj()
+ m.SaveValue("obj", obj)
+ m.Save("user", &x.user)
+}
+
+func (x *WeakRef) afterLoad() {}
+func (x *WeakRef) load(m state.Map) {
+ m.Load("user", &x.user)
+ m.LoadValue("obj", new(savedReference), func(y interface{}) { x.loadObj(y.(savedReference)) })
+}
+
+func (x *AtomicRefCount) beforeSave() {}
+func (x *AtomicRefCount) save(m state.Map) {
+ x.beforeSave()
+ m.Save("refCount", &x.refCount)
+ m.Save("name", &x.name)
+ m.Save("stack", &x.stack)
+}
+
+func (x *AtomicRefCount) afterLoad() {}
+func (x *AtomicRefCount) load(m state.Map) {
+ m.Load("refCount", &x.refCount)
+ m.Load("name", &x.name)
+ m.Load("stack", &x.stack)
+}
+
+func (x *savedReference) beforeSave() {}
+func (x *savedReference) save(m state.Map) {
+ x.beforeSave()
+ m.Save("obj", &x.obj)
+}
+
+func (x *savedReference) afterLoad() {}
+func (x *savedReference) load(m state.Map) {
+ m.Load("obj", &x.obj)
+}
+
+func (x *weakRefList) beforeSave() {}
+func (x *weakRefList) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *weakRefList) afterLoad() {}
+func (x *weakRefList) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *weakRefEntry) beforeSave() {}
+func (x *weakRefEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *weakRefEntry) afterLoad() {}
+func (x *weakRefEntry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func init() {
+ state.Register("refs.WeakRef", (*WeakRef)(nil), state.Fns{Save: (*WeakRef).save, Load: (*WeakRef).load})
+ state.Register("refs.AtomicRefCount", (*AtomicRefCount)(nil), state.Fns{Save: (*AtomicRefCount).save, Load: (*AtomicRefCount).load})
+ state.Register("refs.savedReference", (*savedReference)(nil), state.Fns{Save: (*savedReference).save, Load: (*savedReference).load})
+ state.Register("refs.weakRefList", (*weakRefList)(nil), state.Fns{Save: (*weakRefList).save, Load: (*weakRefList).load})
+ state.Register("refs.weakRefEntry", (*weakRefEntry)(nil), state.Fns{Save: (*weakRefEntry).save, Load: (*weakRefEntry).load})
+}
diff --git a/pkg/refs/weak_ref_list.go b/pkg/refs/weak_ref_list.go
new file mode 100755
index 000000000..df8e98bf5
--- /dev/null
+++ b/pkg/refs/weak_ref_list.go
@@ -0,0 +1,173 @@
+package refs
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type weakRefElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (weakRefElementMapper) linkerFor(elem *WeakRef) *WeakRef { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type weakRefList struct {
+ head *WeakRef
+ tail *WeakRef
+}
+
+// Reset resets list l to the empty state.
+func (l *weakRefList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *weakRefList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *weakRefList) Front() *WeakRef {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *weakRefList) Back() *WeakRef {
+ return l.tail
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *weakRefList) PushFront(e *WeakRef) {
+ weakRefElementMapper{}.linkerFor(e).SetNext(l.head)
+ weakRefElementMapper{}.linkerFor(e).SetPrev(nil)
+
+ if l.head != nil {
+ weakRefElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *weakRefList) PushBack(e *WeakRef) {
+ weakRefElementMapper{}.linkerFor(e).SetNext(nil)
+ weakRefElementMapper{}.linkerFor(e).SetPrev(l.tail)
+
+ if l.tail != nil {
+ weakRefElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *weakRefList) PushBackList(m *weakRefList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ weakRefElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ weakRefElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *weakRefList) InsertAfter(b, e *WeakRef) {
+ a := weakRefElementMapper{}.linkerFor(b).Next()
+ weakRefElementMapper{}.linkerFor(e).SetNext(a)
+ weakRefElementMapper{}.linkerFor(e).SetPrev(b)
+ weakRefElementMapper{}.linkerFor(b).SetNext(e)
+
+ if a != nil {
+ weakRefElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *weakRefList) InsertBefore(a, e *WeakRef) {
+ b := weakRefElementMapper{}.linkerFor(a).Prev()
+ weakRefElementMapper{}.linkerFor(e).SetNext(a)
+ weakRefElementMapper{}.linkerFor(e).SetPrev(b)
+ weakRefElementMapper{}.linkerFor(a).SetPrev(e)
+
+ if b != nil {
+ weakRefElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *weakRefList) Remove(e *WeakRef) {
+ prev := weakRefElementMapper{}.linkerFor(e).Prev()
+ next := weakRefElementMapper{}.linkerFor(e).Next()
+
+ if prev != nil {
+ weakRefElementMapper{}.linkerFor(prev).SetNext(next)
+ } else {
+ l.head = next
+ }
+
+ if next != nil {
+ weakRefElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else {
+ l.tail = prev
+ }
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type weakRefEntry struct {
+ next *WeakRef
+ prev *WeakRef
+}
+
+// Next returns the entry that follows e in the list.
+func (e *weakRefEntry) Next() *WeakRef {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *weakRefEntry) Prev() *WeakRef {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *weakRefEntry) SetNext(elem *WeakRef) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *weakRefEntry) SetPrev(elem *WeakRef) {
+ e.prev = elem
+}
diff --git a/pkg/seccomp/BUILD b/pkg/seccomp/BUILD
deleted file mode 100644
index af94e944d..000000000
--- a/pkg/seccomp/BUILD
+++ /dev/null
@@ -1,52 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_embed_data", "go_test")
-
-package(licenses = ["notice"])
-
-go_binary(
- name = "victim",
- testonly = 1,
- srcs = ["seccomp_test_victim.go"],
- deps = [":seccomp"],
-)
-
-go_embed_data(
- name = "victim_data",
- testonly = 1,
- src = "victim",
- package = "seccomp",
- var = "victimData",
-)
-
-go_library(
- name = "seccomp",
- srcs = [
- "seccomp.go",
- "seccomp_amd64.go",
- "seccomp_arm64.go",
- "seccomp_rules.go",
- "seccomp_unsafe.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/seccomp",
- visibility = ["//visibility:public"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/bpf",
- "//pkg/log",
- ],
-)
-
-go_test(
- name = "seccomp_test",
- size = "small",
- srcs = [
- "seccomp_test.go",
- ":victim_data",
- ],
- embed = [":seccomp"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/binary",
- "//pkg/bpf",
- ],
-)
diff --git a/pkg/seccomp/seccomp_state_autogen.go b/pkg/seccomp/seccomp_state_autogen.go
new file mode 100755
index 000000000..0fc23d1a8
--- /dev/null
+++ b/pkg/seccomp/seccomp_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package seccomp
+
diff --git a/pkg/seccomp/seccomp_test.go b/pkg/seccomp/seccomp_test.go
deleted file mode 100644
index 353686ed3..000000000
--- a/pkg/seccomp/seccomp_test.go
+++ /dev/null
@@ -1,505 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package seccomp
-
-import (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "math"
- "math/rand"
- "os"
- "os/exec"
- "strings"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/binary"
- "gvisor.dev/gvisor/pkg/bpf"
-)
-
-type seccompData struct {
- nr uint32
- arch uint32
- instructionPointer uint64
- args [6]uint64
-}
-
-// newVictim makes a victim binary.
-func newVictim() (string, error) {
- f, err := ioutil.TempFile("", "victim")
- if err != nil {
- return "", err
- }
- defer f.Close()
- path := f.Name()
- if _, err := io.Copy(f, bytes.NewBuffer(victimData)); err != nil {
- os.Remove(path)
- return "", err
- }
- if err := os.Chmod(path, 0755); err != nil {
- os.Remove(path)
- return "", err
- }
- return path, nil
-}
-
-// asInput converts a seccompData to a bpf.Input.
-func (d *seccompData) asInput() bpf.Input {
- return bpf.InputBytes{binary.Marshal(nil, binary.LittleEndian, d), binary.LittleEndian}
-}
-
-func TestBasic(t *testing.T) {
- type spec struct {
- // desc is the test's description.
- desc string
-
- // data is the input data.
- data seccompData
-
- // want is the expected return value of the BPF program.
- want linux.BPFAction
- }
-
- for _, test := range []struct {
- ruleSets []RuleSet
- defaultAction linux.BPFAction
- specs []spec
- }{
- {
- ruleSets: []RuleSet{
- {
- Rules: SyscallRules{1: {}},
- Action: linux.SECCOMP_RET_ALLOW,
- },
- },
- defaultAction: linux.SECCOMP_RET_TRAP,
- specs: []spec{
- {
- desc: "Single syscall allowed",
- data: seccompData{nr: 1, arch: linux.AUDIT_ARCH_X86_64},
- want: linux.SECCOMP_RET_ALLOW,
- },
- {
- desc: "Single syscall disallowed",
- data: seccompData{nr: 2, arch: linux.AUDIT_ARCH_X86_64},
- want: linux.SECCOMP_RET_TRAP,
- },
- },
- },
- {
- ruleSets: []RuleSet{
- {
- Rules: SyscallRules{
- 1: []Rule{
- {
- AllowValue(0x1),
- },
- },
- },
- Action: linux.SECCOMP_RET_ALLOW,
- },
- {
- Rules: SyscallRules{
- 1: {},
- 2: {},
- },
- Action: linux.SECCOMP_RET_TRAP,
- },
- },
- defaultAction: linux.SECCOMP_RET_KILL_THREAD,
- specs: []spec{
- {
- desc: "Multiple rulesets allowed (1a)",
- data: seccompData{nr: 1, arch: linux.AUDIT_ARCH_X86_64, args: [6]uint64{0x1}},
- want: linux.SECCOMP_RET_ALLOW,
- },
- {
- desc: "Multiple rulesets allowed (1b)",
- data: seccompData{nr: 1, arch: linux.AUDIT_ARCH_X86_64},
- want: linux.SECCOMP_RET_TRAP,
- },
- {
- desc: "Multiple rulesets allowed (2)",
- data: seccompData{nr: 1, arch: linux.AUDIT_ARCH_X86_64},
- want: linux.SECCOMP_RET_TRAP,
- },
- {
- desc: "Multiple rulesets allowed (2)",
- data: seccompData{nr: 0, arch: linux.AUDIT_ARCH_X86_64},
- want: linux.SECCOMP_RET_KILL_THREAD,
- },
- },
- },
- {
- ruleSets: []RuleSet{
- {
- Rules: SyscallRules{
- 1: {},
- 3: {},
- 5: {},
- },
- Action: linux.SECCOMP_RET_ALLOW,
- },
- },
- defaultAction: linux.SECCOMP_RET_TRAP,
- specs: []spec{
- {
- desc: "Multiple syscalls allowed (1)",
- data: seccompData{nr: 1, arch: linux.AUDIT_ARCH_X86_64},
- want: linux.SECCOMP_RET_ALLOW,
- },
- {
- desc: "Multiple syscalls allowed (3)",
- data: seccompData{nr: 3, arch: linux.AUDIT_ARCH_X86_64},
- want: linux.SECCOMP_RET_ALLOW,
- },
- {
- desc: "Multiple syscalls allowed (5)",
- data: seccompData{nr: 5, arch: linux.AUDIT_ARCH_X86_64},
- want: linux.SECCOMP_RET_ALLOW,
- },
- {
- desc: "Multiple syscalls disallowed (0)",
- data: seccompData{nr: 0, arch: linux.AUDIT_ARCH_X86_64},
- want: linux.SECCOMP_RET_TRAP,
- },
- {
- desc: "Multiple syscalls disallowed (2)",
- data: seccompData{nr: 2, arch: linux.AUDIT_ARCH_X86_64},
- want: linux.SECCOMP_RET_TRAP,
- },
- {
- desc: "Multiple syscalls disallowed (4)",
- data: seccompData{nr: 4, arch: linux.AUDIT_ARCH_X86_64},
- want: linux.SECCOMP_RET_TRAP,
- },
- {
- desc: "Multiple syscalls disallowed (6)",
- data: seccompData{nr: 6, arch: linux.AUDIT_ARCH_X86_64},
- want: linux.SECCOMP_RET_TRAP,
- },
- {
- desc: "Multiple syscalls disallowed (100)",
- data: seccompData{nr: 100, arch: linux.AUDIT_ARCH_X86_64},
- want: linux.SECCOMP_RET_TRAP,
- },
- },
- },
- {
- ruleSets: []RuleSet{
- {
- Rules: SyscallRules{
- 1: {},
- },
- Action: linux.SECCOMP_RET_ALLOW,
- },
- },
- defaultAction: linux.SECCOMP_RET_TRAP,
- specs: []spec{
- {
- desc: "Wrong architecture",
- data: seccompData{nr: 1, arch: 123},
- want: linux.SECCOMP_RET_TRAP,
- },
- },
- },
- {
- ruleSets: []RuleSet{
- {
- Rules: SyscallRules{
- 1: {},
- },
- Action: linux.SECCOMP_RET_ALLOW,
- },
- },
- defaultAction: linux.SECCOMP_RET_TRAP,
- specs: []spec{
- {
- desc: "Syscall disallowed, action trap",
- data: seccompData{nr: 2, arch: linux.AUDIT_ARCH_X86_64},
- want: linux.SECCOMP_RET_TRAP,
- },
- },
- },
- {
- ruleSets: []RuleSet{
- {
- Rules: SyscallRules{
- 1: []Rule{
- {
- AllowAny{},
- AllowValue(0xf),
- },
- },
- },
- Action: linux.SECCOMP_RET_ALLOW,
- },
- },
- defaultAction: linux.SECCOMP_RET_TRAP,
- specs: []spec{
- {
- desc: "Syscall argument allowed",
- data: seccompData{nr: 1, arch: linux.AUDIT_ARCH_X86_64, args: [6]uint64{0xf, 0xf}},
- want: linux.SECCOMP_RET_ALLOW,
- },
- {
- desc: "Syscall argument disallowed",
- data: seccompData{nr: 1, arch: linux.AUDIT_ARCH_X86_64, args: [6]uint64{0xf, 0xe}},
- want: linux.SECCOMP_RET_TRAP,
- },
- },
- },
- {
- ruleSets: []RuleSet{
- {
- Rules: SyscallRules{
- 1: []Rule{
- {
- AllowValue(0xf),
- },
- {
- AllowValue(0xe),
- },
- },
- },
- Action: linux.SECCOMP_RET_ALLOW,
- },
- },
- defaultAction: linux.SECCOMP_RET_TRAP,
- specs: []spec{
- {
- desc: "Syscall argument allowed, two rules",
- data: seccompData{nr: 1, arch: linux.AUDIT_ARCH_X86_64, args: [6]uint64{0xf}},
- want: linux.SECCOMP_RET_ALLOW,
- },
- {
- desc: "Syscall argument allowed, two rules",
- data: seccompData{nr: 1, arch: linux.AUDIT_ARCH_X86_64, args: [6]uint64{0xe}},
- want: linux.SECCOMP_RET_ALLOW,
- },
- },
- },
- {
- ruleSets: []RuleSet{
- {
- Rules: SyscallRules{
- 1: []Rule{
- {
- AllowValue(0),
- AllowValue(math.MaxUint64 - 1),
- AllowValue(math.MaxUint32),
- },
- },
- },
- Action: linux.SECCOMP_RET_ALLOW,
- },
- },
- defaultAction: linux.SECCOMP_RET_TRAP,
- specs: []spec{
- {
- desc: "64bit syscall argument allowed",
- data: seccompData{
- nr: 1,
- arch: linux.AUDIT_ARCH_X86_64,
- args: [6]uint64{0, math.MaxUint64 - 1, math.MaxUint32},
- },
- want: linux.SECCOMP_RET_ALLOW,
- },
- {
- desc: "64bit syscall argument disallowed",
- data: seccompData{
- nr: 1,
- arch: linux.AUDIT_ARCH_X86_64,
- args: [6]uint64{0, math.MaxUint64, math.MaxUint32},
- },
- want: linux.SECCOMP_RET_TRAP,
- },
- {
- desc: "64bit syscall argument disallowed",
- data: seccompData{
- nr: 1,
- arch: linux.AUDIT_ARCH_X86_64,
- args: [6]uint64{0, math.MaxUint64, math.MaxUint32 - 1},
- },
- want: linux.SECCOMP_RET_TRAP,
- },
- },
- },
- } {
- instrs, err := BuildProgram(test.ruleSets, test.defaultAction)
- if err != nil {
- t.Errorf("%s: buildProgram() got error: %v", test.specs[0].desc, err)
- continue
- }
- p, err := bpf.Compile(instrs)
- if err != nil {
- t.Errorf("%s: bpf.Compile() got error: %v", test.specs[0].desc, err)
- continue
- }
- for _, spec := range test.specs {
- got, err := bpf.Exec(p, spec.data.asInput())
- if err != nil {
- t.Errorf("%s: bpf.Exec() got error: %v", spec.desc, err)
- continue
- }
- if got != uint32(spec.want) {
- t.Errorf("%s: bpd.Exec() = %d, want: %d", spec.desc, got, spec.want)
- }
- }
- }
-}
-
-// TestRandom tests that randomly generated rules are encoded correctly.
-func TestRandom(t *testing.T) {
- rand.Seed(time.Now().UnixNano())
- size := rand.Intn(50) + 1
- syscallRules := make(map[uintptr][]Rule)
- for len(syscallRules) < size {
- n := uintptr(rand.Intn(200))
- if _, ok := syscallRules[n]; !ok {
- syscallRules[n] = []Rule{}
- }
- }
-
- fmt.Printf("Testing filters: %v", syscallRules)
- instrs, err := BuildProgram([]RuleSet{
- RuleSet{
- Rules: syscallRules,
- Action: linux.SECCOMP_RET_ALLOW,
- },
- }, linux.SECCOMP_RET_TRAP)
- if err != nil {
- t.Fatalf("buildProgram() got error: %v", err)
- }
- p, err := bpf.Compile(instrs)
- if err != nil {
- t.Fatalf("bpf.Compile() got error: %v", err)
- }
- for i := uint32(0); i < 200; i++ {
- data := seccompData{nr: i, arch: linux.AUDIT_ARCH_X86_64}
- got, err := bpf.Exec(p, data.asInput())
- if err != nil {
- t.Errorf("bpf.Exec() got error: %v, for syscall %d", err, i)
- continue
- }
- want := linux.SECCOMP_RET_TRAP
- if _, ok := syscallRules[uintptr(i)]; ok {
- want = linux.SECCOMP_RET_ALLOW
- }
- if got != uint32(want) {
- t.Errorf("bpf.Exec() = %d, want: %d, for syscall %d", got, want, i)
- }
- }
-}
-
-// TestReadDeal checks that a process dies when it trips over the filter and
-// that it doesn't die when the filter is not triggered.
-func TestRealDeal(t *testing.T) {
- for _, test := range []struct {
- die bool
- want string
- }{
- {die: true, want: "bad system call"},
- {die: false, want: "Syscall was allowed!!!"},
- } {
- victim, err := newVictim()
- if err != nil {
- t.Fatalf("unable to get victim: %v", err)
- }
- defer os.Remove(victim)
- dieFlag := fmt.Sprintf("-die=%v", test.die)
- cmd := exec.Command(victim, dieFlag)
-
- out, err := cmd.CombinedOutput()
- if test.die {
- if err == nil {
- t.Errorf("victim was not killed as expected, output: %s", out)
- continue
- }
- // Depending on kernel version, either RET_TRAP or RET_KILL_PROCESS is
- // used. RET_TRAP dumps reason for exit in output, while RET_KILL_PROCESS
- // returns SIGSYS as exit status.
- if !strings.Contains(string(out), test.want) &&
- !strings.Contains(err.Error(), test.want) {
- t.Errorf("Victim error is wrong, got: %v, err: %v, want: %v", string(out), err, test.want)
- continue
- }
- } else {
- if err != nil {
- t.Errorf("victim failed to execute, err: %v", err)
- continue
- }
- if !strings.Contains(string(out), test.want) {
- t.Errorf("Victim output is wrong, got: %v, want: %v", string(out), test.want)
- continue
- }
- }
- }
-}
-
-// TestMerge ensures that empty rules are not erased when rules are merged.
-func TestMerge(t *testing.T) {
- for _, tst := range []struct {
- name string
- main []Rule
- merge []Rule
- want []Rule
- }{
- {
- name: "empty both",
- main: nil,
- merge: nil,
- want: []Rule{{}, {}},
- },
- {
- name: "empty main",
- main: nil,
- merge: []Rule{{}},
- want: []Rule{{}, {}},
- },
- {
- name: "empty merge",
- main: []Rule{{}},
- merge: nil,
- want: []Rule{{}, {}},
- },
- } {
- t.Run(tst.name, func(t *testing.T) {
- mainRules := SyscallRules{1: tst.main}
- mergeRules := SyscallRules{1: tst.merge}
- mainRules.Merge(mergeRules)
- if got, want := len(mainRules[1]), len(tst.want); got != want {
- t.Errorf("wrong length, got: %d, want: %d", got, want)
- }
- for i, r := range mainRules[1] {
- if r != tst.want[i] {
- t.Errorf("result, got: %v, want: %v", r, tst.want[i])
- }
- }
- })
- }
-}
-
-// TestAddRule ensures that empty rules are not erased when rules are added.
-func TestAddRule(t *testing.T) {
- rules := SyscallRules{1: {}}
- rules.AddRule(1, Rule{})
- if got, want := len(rules[1]), 2; got != want {
- t.Errorf("len(rules[1]), got: %d, want: %d", got, want)
- }
-}
diff --git a/pkg/seccomp/seccomp_test_victim.go b/pkg/seccomp/seccomp_test_victim.go
deleted file mode 100644
index 48413f1fb..000000000
--- a/pkg/seccomp/seccomp_test_victim.go
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Test binary used to test that seccomp filters are properly constructed and
-// indeed kill the process on violation.
-package main
-
-import (
- "flag"
- "fmt"
- "os"
- "syscall"
-
- "gvisor.dev/gvisor/pkg/seccomp"
-)
-
-func main() {
- dieFlag := flag.Bool("die", false, "trips over the filter if true")
- flag.Parse()
-
- syscalls := seccomp.SyscallRules{
- syscall.SYS_ACCEPT: {},
- syscall.SYS_ARCH_PRCTL: {},
- syscall.SYS_BIND: {},
- syscall.SYS_BRK: {},
- syscall.SYS_CLOCK_GETTIME: {},
- syscall.SYS_CLONE: {},
- syscall.SYS_CLOSE: {},
- syscall.SYS_DUP: {},
- syscall.SYS_DUP2: {},
- syscall.SYS_EPOLL_CREATE1: {},
- syscall.SYS_EPOLL_CTL: {},
- syscall.SYS_EPOLL_WAIT: {},
- syscall.SYS_EPOLL_PWAIT: {},
- syscall.SYS_EXIT: {},
- syscall.SYS_EXIT_GROUP: {},
- syscall.SYS_FALLOCATE: {},
- syscall.SYS_FCHMOD: {},
- syscall.SYS_FCNTL: {},
- syscall.SYS_FSTAT: {},
- syscall.SYS_FSYNC: {},
- syscall.SYS_FTRUNCATE: {},
- syscall.SYS_FUTEX: {},
- syscall.SYS_GETDENTS64: {},
- syscall.SYS_GETPEERNAME: {},
- syscall.SYS_GETPID: {},
- syscall.SYS_GETSOCKNAME: {},
- syscall.SYS_GETSOCKOPT: {},
- syscall.SYS_GETTID: {},
- syscall.SYS_GETTIMEOFDAY: {},
- syscall.SYS_LISTEN: {},
- syscall.SYS_LSEEK: {},
- syscall.SYS_MADVISE: {},
- syscall.SYS_MINCORE: {},
- syscall.SYS_MMAP: {},
- syscall.SYS_MPROTECT: {},
- syscall.SYS_MUNLOCK: {},
- syscall.SYS_MUNMAP: {},
- syscall.SYS_NANOSLEEP: {},
- syscall.SYS_NEWFSTATAT: {},
- syscall.SYS_OPEN: {},
- syscall.SYS_PPOLL: {},
- syscall.SYS_PREAD64: {},
- syscall.SYS_PSELECT6: {},
- syscall.SYS_PWRITE64: {},
- syscall.SYS_READ: {},
- syscall.SYS_READLINKAT: {},
- syscall.SYS_READV: {},
- syscall.SYS_RECVMSG: {},
- syscall.SYS_RENAMEAT: {},
- syscall.SYS_RESTART_SYSCALL: {},
- syscall.SYS_RT_SIGACTION: {},
- syscall.SYS_RT_SIGPROCMASK: {},
- syscall.SYS_RT_SIGRETURN: {},
- syscall.SYS_SCHED_YIELD: {},
- syscall.SYS_SENDMSG: {},
- syscall.SYS_SETITIMER: {},
- syscall.SYS_SET_ROBUST_LIST: {},
- syscall.SYS_SETSOCKOPT: {},
- syscall.SYS_SHUTDOWN: {},
- syscall.SYS_SIGALTSTACK: {},
- syscall.SYS_SOCKET: {},
- syscall.SYS_SYNC_FILE_RANGE: {},
- syscall.SYS_TGKILL: {},
- syscall.SYS_UTIMENSAT: {},
- syscall.SYS_WRITE: {},
- syscall.SYS_WRITEV: {},
- }
- die := *dieFlag
- if !die {
- syscalls[syscall.SYS_OPENAT] = []seccomp.Rule{
- {
- seccomp.AllowValue(10),
- },
- }
- }
-
- if err := seccomp.Install(syscalls); err != nil {
- fmt.Printf("Failed to install seccomp: %v", err)
- os.Exit(1)
- }
- fmt.Printf("Filters installed\n")
-
- syscall.RawSyscall(syscall.SYS_OPENAT, 10, 0, 0)
- fmt.Printf("Syscall was allowed!!!\n")
-}
diff --git a/pkg/secio/BUILD b/pkg/secio/BUILD
deleted file mode 100644
index 22abdc69f..000000000
--- a/pkg/secio/BUILD
+++ /dev/null
@@ -1,21 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "secio",
- srcs = [
- "full_reader.go",
- "secio.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/secio",
- visibility = ["//pkg/sentry:internal"],
-)
-
-go_test(
- name = "secio_test",
- size = "small",
- srcs = ["secio_test.go"],
- embed = [":secio"],
-)
diff --git a/pkg/secio/secio_state_autogen.go b/pkg/secio/secio_state_autogen.go
new file mode 100755
index 000000000..ec559f264
--- /dev/null
+++ b/pkg/secio/secio_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package secio
+
diff --git a/pkg/secio/secio_test.go b/pkg/secio/secio_test.go
deleted file mode 100644
index d1d905187..000000000
--- a/pkg/secio/secio_test.go
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package secio
-
-import (
- "bytes"
- "errors"
- "io"
- "io/ioutil"
- "math"
- "testing"
-)
-
-var errEndOfBuffer = errors.New("write beyond end of buffer")
-
-// buffer resembles bytes.Buffer, but implements io.ReaderAt and io.WriterAt.
-// Reads beyond the end of the buffer return io.EOF. Writes beyond the end of
-// the buffer return errEndOfBuffer.
-type buffer struct {
- Bytes []byte
-}
-
-// ReadAt implements io.ReaderAt.ReadAt.
-func (b *buffer) ReadAt(dst []byte, off int64) (int, error) {
- if off >= int64(len(b.Bytes)) {
- return 0, io.EOF
- }
- n := copy(dst, b.Bytes[off:])
- if n < len(dst) {
- return n, io.EOF
- }
- return n, nil
-}
-
-// WriteAt implements io.WriterAt.WriteAt.
-func (b *buffer) WriteAt(src []byte, off int64) (int, error) {
- if off >= int64(len(b.Bytes)) {
- return 0, errEndOfBuffer
- }
- n := copy(b.Bytes[off:], src)
- if n < len(src) {
- return n, errEndOfBuffer
- }
- return n, nil
-}
-
-func newBufferString(s string) *buffer {
- return &buffer{[]byte(s)}
-}
-
-func TestOffsetReader(t *testing.T) {
- buf := newBufferString("foobar")
- r := NewOffsetReader(buf, 3)
- dst, err := ioutil.ReadAll(r)
- if want := []byte("bar"); !bytes.Equal(dst, want) || err != nil {
- t.Errorf("ReadAll: got (%q, %v), wanted (%q, nil)", dst, err, want)
- }
-}
-
-func TestSectionReader(t *testing.T) {
- buf := newBufferString("foobarbaz")
- r := NewSectionReader(buf, 3, 3)
- dst, err := ioutil.ReadAll(r)
- if want, wantErr := []byte("bar"), ErrReachedLimit; !bytes.Equal(dst, want) || err != wantErr {
- t.Errorf("ReadAll: got (%q, %v), wanted (%q, %v)", dst, err, want, wantErr)
- }
-}
-
-func TestSectionReaderLimitOverflow(t *testing.T) {
- // SectionReader behaves like OffsetReader when limit overflows int64.
- buf := newBufferString("foobar")
- r := NewSectionReader(buf, 3, math.MaxInt64)
- dst, err := ioutil.ReadAll(r)
- if want := []byte("bar"); !bytes.Equal(dst, want) || err != nil {
- t.Errorf("ReadAll: got (%q, %v), wanted (%q, nil)", dst, err, want)
- }
-}
-
-func TestOffsetWriter(t *testing.T) {
- buf := newBufferString("ABCDEF")
- w := NewOffsetWriter(buf, 3)
- n, err := w.Write([]byte("foobar"))
- if wantN, wantErr := 3, errEndOfBuffer; n != wantN || err != wantErr {
- t.Errorf("WriteAt: got (%v, %v), wanted (%v, %v)", n, err, wantN, wantErr)
- }
- if got, want := buf.Bytes, []byte("ABCfoo"); !bytes.Equal(got, want) {
- t.Errorf("buf.Bytes: got %q, wanted %q", got, want)
- }
-}
-
-func TestSectionWriter(t *testing.T) {
- buf := newBufferString("ABCDEFGHI")
- w := NewSectionWriter(buf, 3, 3)
- n, err := w.Write([]byte("foobar"))
- if wantN, wantErr := 3, ErrReachedLimit; n != wantN || err != wantErr {
- t.Errorf("WriteAt: got (%v, %v), wanted (%v, %v)", n, err, wantN, wantErr)
- }
- if got, want := buf.Bytes, []byte("ABCfooGHI"); !bytes.Equal(got, want) {
- t.Errorf("buf.Bytes: got %q, wanted %q", got, want)
- }
-}
-
-func TestSectionWriterLimitOverflow(t *testing.T) {
- // SectionWriter behaves like OffsetWriter when limit overflows int64.
- buf := newBufferString("ABCDEF")
- w := NewSectionWriter(buf, 3, math.MaxInt64)
- n, err := w.Write([]byte("foobar"))
- if wantN, wantErr := 3, errEndOfBuffer; n != wantN || err != wantErr {
- t.Errorf("WriteAt: got (%v, %v), wanted (%v, %v)", n, err, wantN, wantErr)
- }
- if got, want := buf.Bytes, []byte("ABCfoo"); !bytes.Equal(got, want) {
- t.Errorf("buf.Bytes: got %q, wanted %q", got, want)
- }
-}
diff --git a/pkg/segment/BUILD b/pkg/segment/BUILD
deleted file mode 100644
index 700385907..000000000
--- a/pkg/segment/BUILD
+++ /dev/null
@@ -1,31 +0,0 @@
-package(
- default_visibility = ["//:sandbox"],
- licenses = ["notice"],
-)
-
-load("//tools/go_generics:defs.bzl", "go_template")
-
-go_template(
- name = "generic_range",
- srcs = ["range.go"],
- types = [
- "T",
- ],
-)
-
-go_template(
- name = "generic_set",
- srcs = [
- "set.go",
- "set_state.go",
- ],
- opt_consts = [
- "minDegree",
- ],
- types = [
- "Key",
- "Range",
- "Value",
- "Functions",
- ],
-)
diff --git a/pkg/segment/set_state.go b/pkg/segment/set_state.go
deleted file mode 100644
index 76de92591..000000000
--- a/pkg/segment/set_state.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package segment
-
-func (s *Set) saveRoot() *SegmentDataSlices {
- return s.ExportSortedSlices()
-}
-
-func (s *Set) loadRoot(sds *SegmentDataSlices) {
- if err := s.ImportSortedSlices(sds); err != nil {
- panic(err)
- }
-}
diff --git a/pkg/segment/test/BUILD b/pkg/segment/test/BUILD
deleted file mode 100644
index 12d7c77d2..000000000
--- a/pkg/segment/test/BUILD
+++ /dev/null
@@ -1,53 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(
- default_visibility = ["//visibility:private"],
- licenses = ["notice"],
-)
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-
-go_template_instance(
- name = "int_range",
- out = "int_range.go",
- package = "segment",
- template = "//pkg/segment:generic_range",
- types = {
- "T": "int",
- },
-)
-
-go_template_instance(
- name = "int_set",
- out = "int_set.go",
- package = "segment",
- template = "//pkg/segment:generic_set",
- types = {
- "Key": "int",
- "Range": "Range",
- "Value": "int",
- "Functions": "setFunctions",
- },
-)
-
-go_library(
- name = "segment",
- testonly = 1,
- srcs = [
- "int_range.go",
- "int_set.go",
- "set_functions.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/segment/segment",
- deps = [
- "//pkg/state",
- ],
-)
-
-go_test(
- name = "segment_test",
- size = "small",
- srcs = ["segment_test.go"],
- embed = [":segment"],
-)
diff --git a/pkg/segment/test/segment_test.go b/pkg/segment/test/segment_test.go
deleted file mode 100644
index f19a005f3..000000000
--- a/pkg/segment/test/segment_test.go
+++ /dev/null
@@ -1,564 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package segment
-
-import (
- "fmt"
- "math/rand"
- "testing"
-)
-
-const (
- // testSize is the baseline number of elements inserted into sets under
- // test, and is chosen to be large enough to ensure interesting amounts of
- // tree rebalancing.
- //
- // Note that because checkSet is called between each insertion/removal in
- // some tests that use it, tests may be quadratic in testSize.
- testSize = 8000
-
- // valueOffset is the difference between the value and start of test
- // segments.
- valueOffset = 100000
-)
-
-func shuffle(xs []int) {
- for i := range xs {
- j := rand.Intn(i + 1)
- xs[i], xs[j] = xs[j], xs[i]
- }
-}
-
-func randPermutation(size int) []int {
- p := make([]int, size)
- for i := range p {
- p[i] = i
- }
- shuffle(p)
- return p
-}
-
-// checkSet returns an error if s is incorrectly sorted, does not contain
-// exactly expectedSegments segments, or contains a segment for which val !=
-// key + valueOffset.
-func checkSet(s *Set, expectedSegments int) error {
- havePrev := false
- prev := 0
- nrSegments := 0
- for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
- next := seg.Start()
- if havePrev && prev >= next {
- return fmt.Errorf("incorrect order: key %d (segment %d) >= key %d (segment %d)", prev, nrSegments-1, next, nrSegments)
- }
- if got, want := seg.Value(), seg.Start()+valueOffset; got != want {
- return fmt.Errorf("segment %d has key %d, value %d (expected %d)", nrSegments, seg.Start, got, want)
- }
- prev = next
- havePrev = true
- nrSegments++
- }
- if nrSegments != expectedSegments {
- return fmt.Errorf("incorrect number of segments: got %d, wanted %d", nrSegments, expectedSegments)
- }
- return nil
-}
-
-// countSegmentsIn returns the number of segments in s.
-func countSegmentsIn(s *Set) int {
- var count int
- for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
- count++
- }
- return count
-}
-
-func TestAddRandom(t *testing.T) {
- var s Set
- order := randPermutation(testSize)
- var nrInsertions int
- for i, j := range order {
- if !s.AddWithoutMerging(Range{j, j + 1}, j+valueOffset) {
- t.Errorf("Iteration %d: failed to insert segment with key %d", i, j)
- break
- }
- nrInsertions++
- if err := checkSet(&s, nrInsertions); err != nil {
- t.Errorf("Iteration %d: %v", i, err)
- break
- }
- }
- if got, want := countSegmentsIn(&s), nrInsertions; got != want {
- t.Errorf("Wrong final number of segments: got %d, wanted %d", got, want)
- }
- if t.Failed() {
- t.Logf("Insertion order: %v", order[:nrInsertions])
- t.Logf("Set contents:\n%v", &s)
- }
-}
-
-func TestRemoveRandom(t *testing.T) {
- var s Set
- for i := 0; i < testSize; i++ {
- if !s.AddWithoutMerging(Range{i, i + 1}, i+valueOffset) {
- t.Fatalf("Failed to insert segment %d", i)
- }
- }
- order := randPermutation(testSize)
- var nrRemovals int
- for i, j := range order {
- seg := s.FindSegment(j)
- if !seg.Ok() {
- t.Errorf("Iteration %d: failed to find segment with key %d", i, j)
- break
- }
- s.Remove(seg)
- nrRemovals++
- if err := checkSet(&s, testSize-nrRemovals); err != nil {
- t.Errorf("Iteration %d: %v", i, err)
- break
- }
- }
- if got, want := countSegmentsIn(&s), testSize-nrRemovals; got != want {
- t.Errorf("Wrong final number of segments: got %d, wanted %d", got, want)
- }
- if t.Failed() {
- t.Logf("Removal order: %v", order[:nrRemovals])
- t.Logf("Set contents:\n%v", &s)
- t.FailNow()
- }
-}
-
-func TestAddSequentialAdjacent(t *testing.T) {
- var s Set
- var nrInsertions int
- for i := 0; i < testSize; i++ {
- if !s.AddWithoutMerging(Range{i, i + 1}, i+valueOffset) {
- t.Fatalf("Failed to insert segment %d", i)
- }
- nrInsertions++
- if err := checkSet(&s, nrInsertions); err != nil {
- t.Errorf("Iteration %d: %v", i, err)
- break
- }
- }
- if got, want := countSegmentsIn(&s), nrInsertions; got != want {
- t.Errorf("Wrong final number of segments: got %d, wanted %d", got, want)
- }
- if t.Failed() {
- t.Logf("Set contents:\n%v", &s)
- }
-
- first := s.FirstSegment()
- gotSeg, gotGap := first.PrevNonEmpty()
- if wantGap := s.FirstGap(); gotSeg.Ok() || gotGap != wantGap {
- t.Errorf("FirstSegment().PrevNonEmpty(): got (%v, %v), wanted (<terminal iterator>, %v)", gotSeg, gotGap, wantGap)
- }
- gotSeg, gotGap = first.NextNonEmpty()
- if wantSeg := first.NextSegment(); gotSeg != wantSeg || gotGap.Ok() {
- t.Errorf("FirstSegment().NextNonEmpty(): got (%v, %v), wanted (%v, <terminal iterator>)", gotSeg, gotGap, wantSeg)
- }
-
- last := s.LastSegment()
- gotSeg, gotGap = last.PrevNonEmpty()
- if wantSeg := last.PrevSegment(); gotSeg != wantSeg || gotGap.Ok() {
- t.Errorf("LastSegment().PrevNonEmpty(): got (%v, %v), wanted (%v, <terminal iterator>)", gotSeg, gotGap, wantSeg)
- }
- gotSeg, gotGap = last.NextNonEmpty()
- if wantGap := s.LastGap(); gotSeg.Ok() || gotGap != wantGap {
- t.Errorf("LastSegment().NextNonEmpty(): got (%v, %v), wanted (<terminal iterator>, %v)", gotSeg, gotGap, wantGap)
- }
-
- for seg := first.NextSegment(); seg != last; seg = seg.NextSegment() {
- gotSeg, gotGap = seg.PrevNonEmpty()
- if wantSeg := seg.PrevSegment(); gotSeg != wantSeg || gotGap.Ok() {
- t.Errorf("%v.PrevNonEmpty(): got (%v, %v), wanted (%v, <terminal iterator>)", seg, gotSeg, gotGap, wantSeg)
- }
- gotSeg, gotGap = seg.NextNonEmpty()
- if wantSeg := seg.NextSegment(); gotSeg != wantSeg || gotGap.Ok() {
- t.Errorf("%v.NextNonEmpty(): got (%v, %v), wanted (%v, <terminal iterator>)", seg, gotSeg, gotGap, wantSeg)
- }
- }
-}
-
-func TestAddSequentialNonAdjacent(t *testing.T) {
- var s Set
- var nrInsertions int
- for i := 0; i < testSize; i++ {
- // The range here differs from TestAddSequentialAdjacent so that
- // consecutive segments are not adjacent.
- if !s.AddWithoutMerging(Range{2 * i, 2*i + 1}, 2*i+valueOffset) {
- t.Fatalf("Failed to insert segment %d", i)
- }
- nrInsertions++
- if err := checkSet(&s, nrInsertions); err != nil {
- t.Errorf("Iteration %d: %v", i, err)
- break
- }
- }
- if got, want := countSegmentsIn(&s), nrInsertions; got != want {
- t.Errorf("Wrong final number of segments: got %d, wanted %d", got, want)
- }
- if t.Failed() {
- t.Logf("Set contents:\n%v", &s)
- }
-
- for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
- gotSeg, gotGap := seg.PrevNonEmpty()
- if wantGap := seg.PrevGap(); gotSeg.Ok() || gotGap != wantGap {
- t.Errorf("%v.PrevNonEmpty(): got (%v, %v), wanted (<terminal iterator>, %v)", seg, gotSeg, gotGap, wantGap)
- }
- gotSeg, gotGap = seg.NextNonEmpty()
- if wantGap := seg.NextGap(); gotSeg.Ok() || gotGap != wantGap {
- t.Errorf("%v.NextNonEmpty(): got (%v, %v), wanted (<terminal iterator>, %v)", seg, gotSeg, gotGap, wantGap)
- }
- }
-}
-
-func TestMergeSplit(t *testing.T) {
- tests := []struct {
- name string
- initial []Range
- split bool
- splitAddr int
- final []Range
- }{
- {
- name: "Add merges after existing segment",
- initial: []Range{{1000, 1100}, {1100, 1200}},
- final: []Range{{1000, 1200}},
- },
- {
- name: "Add merges before existing segment",
- initial: []Range{{1100, 1200}, {1000, 1100}},
- final: []Range{{1000, 1200}},
- },
- {
- name: "Add merges between existing segments",
- initial: []Range{{1000, 1100}, {1200, 1300}, {1100, 1200}},
- final: []Range{{1000, 1300}},
- },
- {
- name: "SplitAt does nothing at a free address",
- initial: []Range{{100, 200}},
- split: true,
- splitAddr: 300,
- final: []Range{{100, 200}},
- },
- {
- name: "SplitAt does nothing at the beginning of a segment",
- initial: []Range{{100, 200}},
- split: true,
- splitAddr: 100,
- final: []Range{{100, 200}},
- },
- {
- name: "SplitAt does nothing at the end of a segment",
- initial: []Range{{100, 200}},
- split: true,
- splitAddr: 200,
- final: []Range{{100, 200}},
- },
- {
- name: "SplitAt splits in the middle of a segment",
- initial: []Range{{100, 200}},
- split: true,
- splitAddr: 150,
- final: []Range{{100, 150}, {150, 200}},
- },
- }
-Tests:
- for _, test := range tests {
- var s Set
- for _, r := range test.initial {
- if !s.Add(r, 0) {
- t.Errorf("%s: Add(%v) failed; set contents:\n%v", test.name, r, &s)
- continue Tests
- }
- }
- if test.split {
- s.SplitAt(test.splitAddr)
- }
- var i int
- for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
- if i > len(test.final) {
- t.Errorf("%s: Incorrect number of segments: got %d, wanted %d; set contents:\n%v", test.name, countSegmentsIn(&s), len(test.final), &s)
- continue Tests
- }
- if got, want := seg.Range(), test.final[i]; got != want {
- t.Errorf("%s: Segment %d mismatch: got %v, wanted %v; set contents:\n%v", test.name, i, got, want, &s)
- continue Tests
- }
- i++
- }
- if i < len(test.final) {
- t.Errorf("%s: Incorrect number of segments: got %d, wanted %d; set contents:\n%v", test.name, i, len(test.final), &s)
- }
- }
-}
-
-func TestIsolate(t *testing.T) {
- tests := []struct {
- name string
- initial Range
- bounds Range
- final []Range
- }{
- {
- name: "Isolate does not split a segment that falls inside bounds",
- initial: Range{100, 200},
- bounds: Range{100, 200},
- final: []Range{{100, 200}},
- },
- {
- name: "Isolate splits at beginning of segment",
- initial: Range{50, 200},
- bounds: Range{100, 200},
- final: []Range{{50, 100}, {100, 200}},
- },
- {
- name: "Isolate splits at end of segment",
- initial: Range{100, 250},
- bounds: Range{100, 200},
- final: []Range{{100, 200}, {200, 250}},
- },
- {
- name: "Isolate splits at beginning and end of segment",
- initial: Range{50, 250},
- bounds: Range{100, 200},
- final: []Range{{50, 100}, {100, 200}, {200, 250}},
- },
- }
-Tests:
- for _, test := range tests {
- var s Set
- seg := s.Insert(s.FirstGap(), test.initial, 0)
- seg = s.Isolate(seg, test.bounds)
- if !test.bounds.IsSupersetOf(seg.Range()) {
- t.Errorf("%s: Isolated segment %v lies outside bounds %v; set contents:\n%v", test.name, seg.Range(), test.bounds, &s)
- }
- var i int
- for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
- if i > len(test.final) {
- t.Errorf("%s: Incorrect number of segments: got %d, wanted %d; set contents:\n%v", test.name, countSegmentsIn(&s), len(test.final), &s)
- continue Tests
- }
- if got, want := seg.Range(), test.final[i]; got != want {
- t.Errorf("%s: Segment %d mismatch: got %v, wanted %v; set contents:\n%v", test.name, i, got, want, &s)
- continue Tests
- }
- i++
- }
- if i < len(test.final) {
- t.Errorf("%s: Incorrect number of segments: got %d, wanted %d; set contents:\n%v", test.name, i, len(test.final), &s)
- }
- }
-}
-
-func benchmarkAddSequential(b *testing.B, size int) {
- for n := 0; n < b.N; n++ {
- var s Set
- for i := 0; i < size; i++ {
- if !s.AddWithoutMerging(Range{i, i + 1}, i) {
- b.Fatalf("Failed to insert segment %d", i)
- }
- }
- }
-}
-
-func benchmarkAddRandom(b *testing.B, size int) {
- order := randPermutation(size)
-
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- var s Set
- for _, i := range order {
- if !s.AddWithoutMerging(Range{i, i + 1}, i) {
- b.Fatalf("Failed to insert segment %d", i)
- }
- }
- }
-}
-
-func benchmarkFindSequential(b *testing.B, size int) {
- var s Set
- for i := 0; i < size; i++ {
- if !s.AddWithoutMerging(Range{i, i + 1}, i) {
- b.Fatalf("Failed to insert segment %d", i)
- }
- }
-
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- for i := 0; i < size; i++ {
- if seg := s.FindSegment(i); !seg.Ok() {
- b.Fatalf("Failed to find segment %d", i)
- }
- }
- }
-}
-
-func benchmarkFindRandom(b *testing.B, size int) {
- var s Set
- for i := 0; i < size; i++ {
- if !s.AddWithoutMerging(Range{i, i + 1}, i) {
- b.Fatalf("Failed to insert segment %d", i)
- }
- }
- order := randPermutation(size)
-
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- for _, i := range order {
- if si := s.FindSegment(i); !si.Ok() {
- b.Fatalf("Failed to find segment %d", i)
- }
- }
- }
-}
-
-func benchmarkIteration(b *testing.B, size int) {
- var s Set
- for i := 0; i < size; i++ {
- if !s.AddWithoutMerging(Range{i, i + 1}, i) {
- b.Fatalf("Failed to insert segment %d", i)
- }
- }
-
- b.ResetTimer()
- var count uint64
- for n := 0; n < b.N; n++ {
- for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
- count++
- }
- }
- if got, want := count, uint64(size)*uint64(b.N); got != want {
- b.Fatalf("Iterated wrong number of segments: got %d, wanted %d", got, want)
- }
-}
-
-func benchmarkAddFindRemoveSequential(b *testing.B, size int) {
- for n := 0; n < b.N; n++ {
- var s Set
- for i := 0; i < size; i++ {
- if !s.AddWithoutMerging(Range{i, i + 1}, i) {
- b.Fatalf("Failed to insert segment %d", i)
- }
- }
- for i := 0; i < size; i++ {
- seg := s.FindSegment(i)
- if !seg.Ok() {
- b.Fatalf("Failed to find segment %d", i)
- }
- s.Remove(seg)
- }
- if !s.IsEmpty() {
- b.Fatalf("Set not empty after all removals:\n%v", &s)
- }
- }
-}
-
-func benchmarkAddFindRemoveRandom(b *testing.B, size int) {
- order := randPermutation(size)
-
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- var s Set
- for _, i := range order {
- if !s.AddWithoutMerging(Range{i, i + 1}, i) {
- b.Fatalf("Failed to insert segment %d", i)
- }
- }
- for _, i := range order {
- seg := s.FindSegment(i)
- if !seg.Ok() {
- b.Fatalf("Failed to find segment %d", i)
- }
- s.Remove(seg)
- }
- if !s.IsEmpty() {
- b.Fatalf("Set not empty after all removals:\n%v", &s)
- }
- }
-}
-
-// Although we don't generally expect our segment sets to get this big, they're
-// useful for emulating the effect of cache pressure.
-var testSizes = []struct {
- desc string
- size int
-}{
- {"64", 1 << 6},
- {"256", 1 << 8},
- {"1K", 1 << 10},
- {"4K", 1 << 12},
- {"16K", 1 << 14},
- {"64K", 1 << 16},
-}
-
-func BenchmarkAddSequential(b *testing.B) {
- for _, test := range testSizes {
- b.Run(test.desc, func(b *testing.B) {
- benchmarkAddSequential(b, test.size)
- })
- }
-}
-
-func BenchmarkAddRandom(b *testing.B) {
- for _, test := range testSizes {
- b.Run(test.desc, func(b *testing.B) {
- benchmarkAddRandom(b, test.size)
- })
- }
-}
-
-func BenchmarkFindSequential(b *testing.B) {
- for _, test := range testSizes {
- b.Run(test.desc, func(b *testing.B) {
- benchmarkFindSequential(b, test.size)
- })
- }
-}
-
-func BenchmarkFindRandom(b *testing.B) {
- for _, test := range testSizes {
- b.Run(test.desc, func(b *testing.B) {
- benchmarkFindRandom(b, test.size)
- })
- }
-}
-
-func BenchmarkIteration(b *testing.B) {
- for _, test := range testSizes {
- b.Run(test.desc, func(b *testing.B) {
- benchmarkIteration(b, test.size)
- })
- }
-}
-
-func BenchmarkAddFindRemoveSequential(b *testing.B) {
- for _, test := range testSizes {
- b.Run(test.desc, func(b *testing.B) {
- benchmarkAddFindRemoveSequential(b, test.size)
- })
- }
-}
-
-func BenchmarkAddFindRemoveRandom(b *testing.B) {
- for _, test := range testSizes {
- b.Run(test.desc, func(b *testing.B) {
- benchmarkAddFindRemoveRandom(b, test.size)
- })
- }
-}
diff --git a/pkg/segment/test/set_functions.go b/pkg/segment/test/set_functions.go
deleted file mode 100644
index bcddb39bb..000000000
--- a/pkg/segment/test/set_functions.go
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package segment
-
-// Basic numeric constants that we define because the math package doesn't.
-// TODO(nlacasse): These should be Math.MaxInt64/MinInt64?
-const (
- maxInt = int(^uint(0) >> 1)
- minInt = -maxInt - 1
-)
-
-type setFunctions struct{}
-
-func (setFunctions) MinKey() int {
- return minInt
-}
-
-func (setFunctions) MaxKey() int {
- return maxInt
-}
-
-func (setFunctions) ClearValue(*int) {}
-
-func (setFunctions) Merge(_ Range, val1 int, _ Range, _ int) (int, bool) {
- return val1, true
-}
-
-func (setFunctions) Split(_ Range, val int, _ int) (int, int) {
- return val, val
-}
diff --git a/pkg/sentry/BUILD b/pkg/sentry/BUILD
deleted file mode 100644
index 2d6379c86..000000000
--- a/pkg/sentry/BUILD
+++ /dev/null
@@ -1,14 +0,0 @@
-# This BUILD file defines a package_group that allows for interdependencies for
-# sentry-internal packages.
-
-package(licenses = ["notice"])
-
-package_group(
- name = "internal",
- packages = [
- "//pkg/sentry/...",
- "//runsc/...",
- # Code generated by go_marshal relies on go_marshal libraries.
- "//tools/go_marshal/...",
- ],
-)
diff --git a/pkg/sentry/arch/BUILD b/pkg/sentry/arch/BUILD
deleted file mode 100644
index 7aace2d7b..000000000
--- a/pkg/sentry/arch/BUILD
+++ /dev/null
@@ -1,50 +0,0 @@
-load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
-
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "arch",
- srcs = [
- "aligned.go",
- "arch.go",
- "arch_amd64.go",
- "arch_amd64.s",
- "arch_state_x86.go",
- "arch_x86.go",
- "auxv.go",
- "signal_act.go",
- "signal_amd64.go",
- "signal_info.go",
- "signal_stack.go",
- "stack.go",
- "syscalls_amd64.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/arch",
- visibility = ["//:sandbox"],
- deps = [
- ":registers_go_proto",
- "//pkg/abi/linux",
- "//pkg/binary",
- "//pkg/cpuid",
- "//pkg/log",
- "//pkg/sentry/context",
- "//pkg/sentry/limits",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- ],
-)
-
-proto_library(
- name = "registers_proto",
- srcs = ["registers.proto"],
- visibility = ["//visibility:public"],
-)
-
-go_proto_library(
- name = "registers_go_proto",
- importpath = "gvisor.dev/gvisor/pkg/sentry/arch/registers_go_proto",
- proto = ":registers_proto",
- visibility = ["//visibility:public"],
-)
diff --git a/pkg/sentry/arch/arch_state_autogen.go b/pkg/sentry/arch/arch_state_autogen.go
new file mode 100755
index 000000000..9b1205531
--- /dev/null
+++ b/pkg/sentry/arch/arch_state_autogen.go
@@ -0,0 +1,193 @@
+// automatically generated by stateify.
+
+package arch
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *MmapLayout) beforeSave() {}
+func (x *MmapLayout) save(m state.Map) {
+ x.beforeSave()
+ m.Save("MinAddr", &x.MinAddr)
+ m.Save("MaxAddr", &x.MaxAddr)
+ m.Save("BottomUpBase", &x.BottomUpBase)
+ m.Save("TopDownBase", &x.TopDownBase)
+ m.Save("DefaultDirection", &x.DefaultDirection)
+ m.Save("MaxStackRand", &x.MaxStackRand)
+}
+
+func (x *MmapLayout) afterLoad() {}
+func (x *MmapLayout) load(m state.Map) {
+ m.Load("MinAddr", &x.MinAddr)
+ m.Load("MaxAddr", &x.MaxAddr)
+ m.Load("BottomUpBase", &x.BottomUpBase)
+ m.Load("TopDownBase", &x.TopDownBase)
+ m.Load("DefaultDirection", &x.DefaultDirection)
+ m.Load("MaxStackRand", &x.MaxStackRand)
+}
+
+func (x *context64) beforeSave() {}
+func (x *context64) save(m state.Map) {
+ x.beforeSave()
+ m.Save("State", &x.State)
+ m.Save("sigFPState", &x.sigFPState)
+}
+
+func (x *context64) afterLoad() {}
+func (x *context64) load(m state.Map) {
+ m.Load("State", &x.State)
+ m.Load("sigFPState", &x.sigFPState)
+}
+
+func (x *syscallPtraceRegs) beforeSave() {}
+func (x *syscallPtraceRegs) save(m state.Map) {
+ x.beforeSave()
+ m.Save("R15", &x.R15)
+ m.Save("R14", &x.R14)
+ m.Save("R13", &x.R13)
+ m.Save("R12", &x.R12)
+ m.Save("Rbp", &x.Rbp)
+ m.Save("Rbx", &x.Rbx)
+ m.Save("R11", &x.R11)
+ m.Save("R10", &x.R10)
+ m.Save("R9", &x.R9)
+ m.Save("R8", &x.R8)
+ m.Save("Rax", &x.Rax)
+ m.Save("Rcx", &x.Rcx)
+ m.Save("Rdx", &x.Rdx)
+ m.Save("Rsi", &x.Rsi)
+ m.Save("Rdi", &x.Rdi)
+ m.Save("Orig_rax", &x.Orig_rax)
+ m.Save("Rip", &x.Rip)
+ m.Save("Cs", &x.Cs)
+ m.Save("Eflags", &x.Eflags)
+ m.Save("Rsp", &x.Rsp)
+ m.Save("Ss", &x.Ss)
+ m.Save("Fs_base", &x.Fs_base)
+ m.Save("Gs_base", &x.Gs_base)
+ m.Save("Ds", &x.Ds)
+ m.Save("Es", &x.Es)
+ m.Save("Fs", &x.Fs)
+ m.Save("Gs", &x.Gs)
+}
+
+func (x *syscallPtraceRegs) afterLoad() {}
+func (x *syscallPtraceRegs) load(m state.Map) {
+ m.Load("R15", &x.R15)
+ m.Load("R14", &x.R14)
+ m.Load("R13", &x.R13)
+ m.Load("R12", &x.R12)
+ m.Load("Rbp", &x.Rbp)
+ m.Load("Rbx", &x.Rbx)
+ m.Load("R11", &x.R11)
+ m.Load("R10", &x.R10)
+ m.Load("R9", &x.R9)
+ m.Load("R8", &x.R8)
+ m.Load("Rax", &x.Rax)
+ m.Load("Rcx", &x.Rcx)
+ m.Load("Rdx", &x.Rdx)
+ m.Load("Rsi", &x.Rsi)
+ m.Load("Rdi", &x.Rdi)
+ m.Load("Orig_rax", &x.Orig_rax)
+ m.Load("Rip", &x.Rip)
+ m.Load("Cs", &x.Cs)
+ m.Load("Eflags", &x.Eflags)
+ m.Load("Rsp", &x.Rsp)
+ m.Load("Ss", &x.Ss)
+ m.Load("Fs_base", &x.Fs_base)
+ m.Load("Gs_base", &x.Gs_base)
+ m.Load("Ds", &x.Ds)
+ m.Load("Es", &x.Es)
+ m.Load("Fs", &x.Fs)
+ m.Load("Gs", &x.Gs)
+}
+
+func (x *State) beforeSave() {}
+func (x *State) save(m state.Map) {
+ x.beforeSave()
+ var Regs syscallPtraceRegs = x.saveRegs()
+ m.SaveValue("Regs", Regs)
+ m.Save("x86FPState", &x.x86FPState)
+ m.Save("FeatureSet", &x.FeatureSet)
+}
+
+func (x *State) load(m state.Map) {
+ m.LoadWait("x86FPState", &x.x86FPState)
+ m.Load("FeatureSet", &x.FeatureSet)
+ m.LoadValue("Regs", new(syscallPtraceRegs), func(y interface{}) { x.loadRegs(y.(syscallPtraceRegs)) })
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *AuxEntry) beforeSave() {}
+func (x *AuxEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Key", &x.Key)
+ m.Save("Value", &x.Value)
+}
+
+func (x *AuxEntry) afterLoad() {}
+func (x *AuxEntry) load(m state.Map) {
+ m.Load("Key", &x.Key)
+ m.Load("Value", &x.Value)
+}
+
+func (x *SignalAct) beforeSave() {}
+func (x *SignalAct) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Handler", &x.Handler)
+ m.Save("Flags", &x.Flags)
+ m.Save("Restorer", &x.Restorer)
+ m.Save("Mask", &x.Mask)
+}
+
+func (x *SignalAct) afterLoad() {}
+func (x *SignalAct) load(m state.Map) {
+ m.Load("Handler", &x.Handler)
+ m.Load("Flags", &x.Flags)
+ m.Load("Restorer", &x.Restorer)
+ m.Load("Mask", &x.Mask)
+}
+
+func (x *SignalStack) beforeSave() {}
+func (x *SignalStack) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Addr", &x.Addr)
+ m.Save("Flags", &x.Flags)
+ m.Save("Size", &x.Size)
+}
+
+func (x *SignalStack) afterLoad() {}
+func (x *SignalStack) load(m state.Map) {
+ m.Load("Addr", &x.Addr)
+ m.Load("Flags", &x.Flags)
+ m.Load("Size", &x.Size)
+}
+
+func (x *SignalInfo) beforeSave() {}
+func (x *SignalInfo) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Signo", &x.Signo)
+ m.Save("Errno", &x.Errno)
+ m.Save("Code", &x.Code)
+ m.Save("Fields", &x.Fields)
+}
+
+func (x *SignalInfo) afterLoad() {}
+func (x *SignalInfo) load(m state.Map) {
+ m.Load("Signo", &x.Signo)
+ m.Load("Errno", &x.Errno)
+ m.Load("Code", &x.Code)
+ m.Load("Fields", &x.Fields)
+}
+
+func init() {
+ state.Register("arch.MmapLayout", (*MmapLayout)(nil), state.Fns{Save: (*MmapLayout).save, Load: (*MmapLayout).load})
+ state.Register("arch.context64", (*context64)(nil), state.Fns{Save: (*context64).save, Load: (*context64).load})
+ state.Register("arch.syscallPtraceRegs", (*syscallPtraceRegs)(nil), state.Fns{Save: (*syscallPtraceRegs).save, Load: (*syscallPtraceRegs).load})
+ state.Register("arch.State", (*State)(nil), state.Fns{Save: (*State).save, Load: (*State).load})
+ state.Register("arch.AuxEntry", (*AuxEntry)(nil), state.Fns{Save: (*AuxEntry).save, Load: (*AuxEntry).load})
+ state.Register("arch.SignalAct", (*SignalAct)(nil), state.Fns{Save: (*SignalAct).save, Load: (*SignalAct).load})
+ state.Register("arch.SignalStack", (*SignalStack)(nil), state.Fns{Save: (*SignalStack).save, Load: (*SignalStack).load})
+ state.Register("arch.SignalInfo", (*SignalInfo)(nil), state.Fns{Save: (*SignalInfo).save, Load: (*SignalInfo).load})
+}
diff --git a/pkg/sentry/arch/registers.proto b/pkg/sentry/arch/registers.proto
deleted file mode 100644
index 9dc83e241..000000000
--- a/pkg/sentry/arch/registers.proto
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto3";
-
-package gvisor;
-
-message AMD64Registers {
- uint64 rax = 1;
- uint64 rbx = 2;
- uint64 rcx = 3;
- uint64 rdx = 4;
- uint64 rsi = 5;
- uint64 rdi = 6;
- uint64 rsp = 7;
- uint64 rbp = 8;
-
- uint64 r8 = 9;
- uint64 r9 = 10;
- uint64 r10 = 11;
- uint64 r11 = 12;
- uint64 r12 = 13;
- uint64 r13 = 14;
- uint64 r14 = 15;
- uint64 r15 = 16;
-
- uint64 rip = 17;
- uint64 rflags = 18;
- uint64 orig_rax = 19;
- uint64 cs = 20;
- uint64 ds = 21;
- uint64 es = 22;
- uint64 fs = 23;
- uint64 gs = 24;
- uint64 ss = 25;
- uint64 fs_base = 26;
- uint64 gs_base = 27;
-}
-
-message Registers {
- oneof arch {
- AMD64Registers amd64 = 1;
- }
-}
diff --git a/pkg/sentry/arch/registers_go_proto/registers.pb.go b/pkg/sentry/arch/registers_go_proto/registers.pb.go
new file mode 100755
index 000000000..088209be7
--- /dev/null
+++ b/pkg/sentry/arch/registers_go_proto/registers.pb.go
@@ -0,0 +1,367 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: pkg/sentry/arch/registers.proto
+
+package gvisor
+
+import (
+ fmt "fmt"
+ proto "github.com/golang/protobuf/proto"
+ math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+type AMD64Registers struct {
+ Rax uint64 `protobuf:"varint,1,opt,name=rax,proto3" json:"rax,omitempty"`
+ Rbx uint64 `protobuf:"varint,2,opt,name=rbx,proto3" json:"rbx,omitempty"`
+ Rcx uint64 `protobuf:"varint,3,opt,name=rcx,proto3" json:"rcx,omitempty"`
+ Rdx uint64 `protobuf:"varint,4,opt,name=rdx,proto3" json:"rdx,omitempty"`
+ Rsi uint64 `protobuf:"varint,5,opt,name=rsi,proto3" json:"rsi,omitempty"`
+ Rdi uint64 `protobuf:"varint,6,opt,name=rdi,proto3" json:"rdi,omitempty"`
+ Rsp uint64 `protobuf:"varint,7,opt,name=rsp,proto3" json:"rsp,omitempty"`
+ Rbp uint64 `protobuf:"varint,8,opt,name=rbp,proto3" json:"rbp,omitempty"`
+ R8 uint64 `protobuf:"varint,9,opt,name=r8,proto3" json:"r8,omitempty"`
+ R9 uint64 `protobuf:"varint,10,opt,name=r9,proto3" json:"r9,omitempty"`
+ R10 uint64 `protobuf:"varint,11,opt,name=r10,proto3" json:"r10,omitempty"`
+ R11 uint64 `protobuf:"varint,12,opt,name=r11,proto3" json:"r11,omitempty"`
+ R12 uint64 `protobuf:"varint,13,opt,name=r12,proto3" json:"r12,omitempty"`
+ R13 uint64 `protobuf:"varint,14,opt,name=r13,proto3" json:"r13,omitempty"`
+ R14 uint64 `protobuf:"varint,15,opt,name=r14,proto3" json:"r14,omitempty"`
+ R15 uint64 `protobuf:"varint,16,opt,name=r15,proto3" json:"r15,omitempty"`
+ Rip uint64 `protobuf:"varint,17,opt,name=rip,proto3" json:"rip,omitempty"`
+ Rflags uint64 `protobuf:"varint,18,opt,name=rflags,proto3" json:"rflags,omitempty"`
+ OrigRax uint64 `protobuf:"varint,19,opt,name=orig_rax,json=origRax,proto3" json:"orig_rax,omitempty"`
+ Cs uint64 `protobuf:"varint,20,opt,name=cs,proto3" json:"cs,omitempty"`
+ Ds uint64 `protobuf:"varint,21,opt,name=ds,proto3" json:"ds,omitempty"`
+ Es uint64 `protobuf:"varint,22,opt,name=es,proto3" json:"es,omitempty"`
+ Fs uint64 `protobuf:"varint,23,opt,name=fs,proto3" json:"fs,omitempty"`
+ Gs uint64 `protobuf:"varint,24,opt,name=gs,proto3" json:"gs,omitempty"`
+ Ss uint64 `protobuf:"varint,25,opt,name=ss,proto3" json:"ss,omitempty"`
+ FsBase uint64 `protobuf:"varint,26,opt,name=fs_base,json=fsBase,proto3" json:"fs_base,omitempty"`
+ GsBase uint64 `protobuf:"varint,27,opt,name=gs_base,json=gsBase,proto3" json:"gs_base,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *AMD64Registers) Reset() { *m = AMD64Registers{} }
+func (m *AMD64Registers) String() string { return proto.CompactTextString(m) }
+func (*AMD64Registers) ProtoMessage() {}
+func (*AMD64Registers) Descriptor() ([]byte, []int) {
+ return fileDescriptor_082b7510610e0457, []int{0}
+}
+
+func (m *AMD64Registers) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_AMD64Registers.Unmarshal(m, b)
+}
+func (m *AMD64Registers) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_AMD64Registers.Marshal(b, m, deterministic)
+}
+func (m *AMD64Registers) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_AMD64Registers.Merge(m, src)
+}
+func (m *AMD64Registers) XXX_Size() int {
+ return xxx_messageInfo_AMD64Registers.Size(m)
+}
+func (m *AMD64Registers) XXX_DiscardUnknown() {
+ xxx_messageInfo_AMD64Registers.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_AMD64Registers proto.InternalMessageInfo
+
+func (m *AMD64Registers) GetRax() uint64 {
+ if m != nil {
+ return m.Rax
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetRbx() uint64 {
+ if m != nil {
+ return m.Rbx
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetRcx() uint64 {
+ if m != nil {
+ return m.Rcx
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetRdx() uint64 {
+ if m != nil {
+ return m.Rdx
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetRsi() uint64 {
+ if m != nil {
+ return m.Rsi
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetRdi() uint64 {
+ if m != nil {
+ return m.Rdi
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetRsp() uint64 {
+ if m != nil {
+ return m.Rsp
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetRbp() uint64 {
+ if m != nil {
+ return m.Rbp
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetR8() uint64 {
+ if m != nil {
+ return m.R8
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetR9() uint64 {
+ if m != nil {
+ return m.R9
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetR10() uint64 {
+ if m != nil {
+ return m.R10
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetR11() uint64 {
+ if m != nil {
+ return m.R11
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetR12() uint64 {
+ if m != nil {
+ return m.R12
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetR13() uint64 {
+ if m != nil {
+ return m.R13
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetR14() uint64 {
+ if m != nil {
+ return m.R14
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetR15() uint64 {
+ if m != nil {
+ return m.R15
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetRip() uint64 {
+ if m != nil {
+ return m.Rip
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetRflags() uint64 {
+ if m != nil {
+ return m.Rflags
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetOrigRax() uint64 {
+ if m != nil {
+ return m.OrigRax
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetCs() uint64 {
+ if m != nil {
+ return m.Cs
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetDs() uint64 {
+ if m != nil {
+ return m.Ds
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetEs() uint64 {
+ if m != nil {
+ return m.Es
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetFs() uint64 {
+ if m != nil {
+ return m.Fs
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetGs() uint64 {
+ if m != nil {
+ return m.Gs
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetSs() uint64 {
+ if m != nil {
+ return m.Ss
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetFsBase() uint64 {
+ if m != nil {
+ return m.FsBase
+ }
+ return 0
+}
+
+func (m *AMD64Registers) GetGsBase() uint64 {
+ if m != nil {
+ return m.GsBase
+ }
+ return 0
+}
+
+type Registers struct {
+ // Types that are valid to be assigned to Arch:
+ // *Registers_Amd64
+ Arch isRegisters_Arch `protobuf_oneof:"arch"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Registers) Reset() { *m = Registers{} }
+func (m *Registers) String() string { return proto.CompactTextString(m) }
+func (*Registers) ProtoMessage() {}
+func (*Registers) Descriptor() ([]byte, []int) {
+ return fileDescriptor_082b7510610e0457, []int{1}
+}
+
+func (m *Registers) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Registers.Unmarshal(m, b)
+}
+func (m *Registers) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Registers.Marshal(b, m, deterministic)
+}
+func (m *Registers) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Registers.Merge(m, src)
+}
+func (m *Registers) XXX_Size() int {
+ return xxx_messageInfo_Registers.Size(m)
+}
+func (m *Registers) XXX_DiscardUnknown() {
+ xxx_messageInfo_Registers.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Registers proto.InternalMessageInfo
+
+type isRegisters_Arch interface {
+ isRegisters_Arch()
+}
+
+type Registers_Amd64 struct {
+ Amd64 *AMD64Registers `protobuf:"bytes,1,opt,name=amd64,proto3,oneof"`
+}
+
+func (*Registers_Amd64) isRegisters_Arch() {}
+
+func (m *Registers) GetArch() isRegisters_Arch {
+ if m != nil {
+ return m.Arch
+ }
+ return nil
+}
+
+func (m *Registers) GetAmd64() *AMD64Registers {
+ if x, ok := m.GetArch().(*Registers_Amd64); ok {
+ return x.Amd64
+ }
+ return nil
+}
+
+// XXX_OneofWrappers is for the internal use of the proto package.
+func (*Registers) XXX_OneofWrappers() []interface{} {
+ return []interface{}{
+ (*Registers_Amd64)(nil),
+ }
+}
+
+func init() {
+ proto.RegisterType((*AMD64Registers)(nil), "gvisor.AMD64Registers")
+ proto.RegisterType((*Registers)(nil), "gvisor.Registers")
+}
+
+func init() { proto.RegisterFile("pkg/sentry/arch/registers.proto", fileDescriptor_082b7510610e0457) }
+
+var fileDescriptor_082b7510610e0457 = []byte{
+ // 354 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x92, 0x4d, 0x6f, 0xe2, 0x30,
+ 0x10, 0x86, 0x17, 0x08, 0x01, 0xcc, 0x2e, 0xcb, 0x66, 0x5b, 0x18, 0xda, 0x43, 0x2b, 0x4e, 0x3d,
+ 0x05, 0x02, 0x01, 0xc1, 0xb1, 0xb4, 0x87, 0x5e, 0x7a, 0xc9, 0x1f, 0x40, 0xf9, 0x74, 0xad, 0x7e,
+ 0xc4, 0xf2, 0xa0, 0x2a, 0x3d, 0xf7, 0x8f, 0x57, 0xf6, 0xd8, 0xaa, 0x7a, 0xcb, 0xf3, 0xcc, 0x6b,
+ 0x39, 0x93, 0xbc, 0xec, 0x4a, 0x3e, 0xf3, 0x05, 0x96, 0x6f, 0x27, 0xf5, 0xb1, 0x48, 0x55, 0xfe,
+ 0xb4, 0x50, 0x25, 0x17, 0x78, 0x2a, 0x15, 0x86, 0x52, 0xd5, 0xa7, 0x3a, 0xf0, 0xf9, 0xbb, 0xc0,
+ 0x5a, 0xcd, 0x3f, 0x3d, 0x36, 0xba, 0x7d, 0xbc, 0xdf, 0xc6, 0x89, 0x0b, 0x04, 0x63, 0xd6, 0x51,
+ 0x69, 0x03, 0xad, 0xeb, 0xd6, 0x8d, 0x97, 0xe8, 0x47, 0x63, 0xb2, 0x06, 0xda, 0xd6, 0x64, 0x64,
+ 0xf2, 0x06, 0x3a, 0xd6, 0xe4, 0x64, 0x8a, 0x06, 0x3c, 0x6b, 0x0a, 0x32, 0x28, 0xa0, 0x6b, 0x0d,
+ 0x0a, 0xca, 0x08, 0xf0, 0x5d, 0x86, 0x0c, 0x4a, 0xe8, 0xb9, 0x8c, 0xa4, 0xbb, 0x24, 0xf4, 0xdd,
+ 0x5d, 0x32, 0x18, 0xb1, 0xb6, 0xda, 0xc1, 0xc0, 0x88, 0xb6, 0xda, 0x19, 0xde, 0x03, 0xb3, 0xbc,
+ 0x37, 0x27, 0xa2, 0x25, 0x0c, 0xed, 0x89, 0x68, 0x49, 0x26, 0x82, 0xdf, 0xce, 0x44, 0x64, 0x56,
+ 0xf0, 0xc7, 0x99, 0x15, 0x99, 0x35, 0x8c, 0x9c, 0x59, 0x93, 0x89, 0xe1, 0xaf, 0x33, 0x31, 0x99,
+ 0x0d, 0x8c, 0x9d, 0xd9, 0x18, 0x23, 0x24, 0xfc, 0xb3, 0x46, 0xc8, 0x60, 0xc2, 0x7c, 0x55, 0xbd,
+ 0xa4, 0x1c, 0x21, 0x30, 0xd2, 0x52, 0x30, 0x63, 0xfd, 0x5a, 0x09, 0x7e, 0xd4, 0x9f, 0xf2, 0xbf,
+ 0x99, 0xf4, 0x34, 0x27, 0x69, 0xa3, 0x17, 0xc8, 0x11, 0xce, 0x68, 0x81, 0x1c, 0x35, 0x17, 0x08,
+ 0xe7, 0xc4, 0x85, 0xe1, 0x12, 0x61, 0x42, 0x5c, 0x1a, 0xae, 0x10, 0xa6, 0xc4, 0x95, 0x61, 0x8e,
+ 0x00, 0xc4, 0xdc, 0x30, 0x22, 0xcc, 0x88, 0x11, 0x83, 0x29, 0xeb, 0x55, 0x78, 0xcc, 0x52, 0x2c,
+ 0xe1, 0x82, 0xde, 0xa9, 0xc2, 0x43, 0x8a, 0xa5, 0x1e, 0x70, 0x3b, 0xb8, 0xa4, 0x01, 0x37, 0x83,
+ 0xf9, 0x1d, 0x1b, 0x7c, 0xff, 0xff, 0x90, 0x75, 0xd3, 0xd7, 0x62, 0x1b, 0x9b, 0x06, 0x0c, 0x57,
+ 0x93, 0x90, 0xaa, 0x12, 0xfe, 0xac, 0xc9, 0xc3, 0xaf, 0x84, 0x62, 0x07, 0x9f, 0x79, 0xba, 0x62,
+ 0x99, 0x6f, 0x9a, 0xb5, 0xfe, 0x0a, 0x00, 0x00, 0xff, 0xff, 0xb4, 0xcc, 0x03, 0x27, 0x7c, 0x02,
+ 0x00, 0x00,
+}
diff --git a/pkg/sentry/context/BUILD b/pkg/sentry/context/BUILD
deleted file mode 100644
index 8dc1a77b1..000000000
--- a/pkg/sentry/context/BUILD
+++ /dev/null
@@ -1,14 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "context",
- srcs = ["context.go"],
- importpath = "gvisor.dev/gvisor/pkg/sentry/context",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/amutex",
- "//pkg/log",
- ],
-)
diff --git a/pkg/sentry/context/context_state_autogen.go b/pkg/sentry/context/context_state_autogen.go
new file mode 100755
index 000000000..7dd55bfea
--- /dev/null
+++ b/pkg/sentry/context/context_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package context
+
diff --git a/pkg/sentry/context/contexttest/BUILD b/pkg/sentry/context/contexttest/BUILD
deleted file mode 100644
index 3b6841b7e..000000000
--- a/pkg/sentry/context/contexttest/BUILD
+++ /dev/null
@@ -1,22 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "contexttest",
- testonly = 1,
- srcs = ["contexttest.go"],
- importpath = "gvisor.dev/gvisor/pkg/sentry/context/contexttest",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/memutil",
- "//pkg/sentry/context",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/limits",
- "//pkg/sentry/pgalloc",
- "//pkg/sentry/platform",
- "//pkg/sentry/platform/ptrace",
- "//pkg/sentry/uniqueid",
- ],
-)
diff --git a/pkg/sentry/context/contexttest/contexttest.go b/pkg/sentry/context/contexttest/contexttest.go
deleted file mode 100644
index 15cf086a9..000000000
--- a/pkg/sentry/context/contexttest/contexttest.go
+++ /dev/null
@@ -1,188 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package contexttest builds a test context.Context.
-package contexttest
-
-import (
- "os"
- "sync/atomic"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/memutil"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time"
- "gvisor.dev/gvisor/pkg/sentry/limits"
- "gvisor.dev/gvisor/pkg/sentry/pgalloc"
- "gvisor.dev/gvisor/pkg/sentry/platform"
- "gvisor.dev/gvisor/pkg/sentry/platform/ptrace"
- "gvisor.dev/gvisor/pkg/sentry/uniqueid"
-)
-
-// Context returns a Context that may be used in tests. Uses ptrace as the
-// platform.Platform.
-//
-// Note that some filesystems may require a minimal kernel for testing, which
-// this test context does not provide. For such tests, see kernel/contexttest.
-func Context(tb testing.TB) context.Context {
- const memfileName = "contexttest-memory"
- memfd, err := memutil.CreateMemFD(memfileName, 0)
- if err != nil {
- tb.Fatalf("error creating application memory file: %v", err)
- }
- memfile := os.NewFile(uintptr(memfd), memfileName)
- mf, err := pgalloc.NewMemoryFile(memfile, pgalloc.MemoryFileOpts{})
- if err != nil {
- memfile.Close()
- tb.Fatalf("error creating pgalloc.MemoryFile: %v", err)
- }
- p, err := ptrace.New()
- if err != nil {
- tb.Fatal(err)
- }
- // Test usage of context.Background is fine.
- return &TestContext{
- Context: context.Background(),
- l: limits.NewLimitSet(),
- mf: mf,
- platform: p,
- creds: auth.NewAnonymousCredentials(),
- otherValues: make(map[interface{}]interface{}),
- }
-}
-
-// TestContext represents a context with minimal functionality suitable for
-// running tests.
-type TestContext struct {
- context.Context
- l *limits.LimitSet
- mf *pgalloc.MemoryFile
- platform platform.Platform
- creds *auth.Credentials
- otherValues map[interface{}]interface{}
-}
-
-// globalUniqueID tracks incremental unique identifiers for tests.
-var globalUniqueID uint64
-
-// globalUniqueIDProvider implements unix.UniqueIDProvider.
-type globalUniqueIDProvider struct{}
-
-// UniqueID implements unix.UniqueIDProvider.UniqueID.
-func (*globalUniqueIDProvider) UniqueID() uint64 {
- return atomic.AddUint64(&globalUniqueID, 1)
-}
-
-// lastInotifyCookie is a monotonically increasing counter for generating unique
-// inotify cookies. Must be accessed using atomic ops.
-var lastInotifyCookie uint32
-
-// hostClock implements ktime.Clock.
-type hostClock struct {
- ktime.WallRateClock
- ktime.NoClockEvents
-}
-
-// Now implements ktime.Clock.Now.
-func (hostClock) Now() ktime.Time {
- return ktime.FromNanoseconds(time.Now().UnixNano())
-}
-
-// RegisterValue registers additional values with this test context. Useful for
-// providing values from external packages that contexttest can't depend on.
-func (t *TestContext) RegisterValue(key, value interface{}) {
- t.otherValues[key] = value
-}
-
-// Value implements context.Context.
-func (t *TestContext) Value(key interface{}) interface{} {
- switch key {
- case auth.CtxCredentials:
- return t.creds
- case limits.CtxLimits:
- return t.l
- case pgalloc.CtxMemoryFile:
- return t.mf
- case pgalloc.CtxMemoryFileProvider:
- return t
- case platform.CtxPlatform:
- return t.platform
- case uniqueid.CtxGlobalUniqueID:
- return (*globalUniqueIDProvider).UniqueID(nil)
- case uniqueid.CtxGlobalUniqueIDProvider:
- return &globalUniqueIDProvider{}
- case uniqueid.CtxInotifyCookie:
- return atomic.AddUint32(&lastInotifyCookie, 1)
- case ktime.CtxRealtimeClock:
- return hostClock{}
- default:
- if val, ok := t.otherValues[key]; ok {
- return val
- }
- return t.Context.Value(key)
- }
-}
-
-// MemoryFile implements pgalloc.MemoryFileProvider.MemoryFile.
-func (t *TestContext) MemoryFile() *pgalloc.MemoryFile {
- return t.mf
-}
-
-// RootContext returns a Context that may be used in tests that need root
-// credentials. Uses ptrace as the platform.Platform.
-func RootContext(tb testing.TB) context.Context {
- return WithCreds(Context(tb), auth.NewRootCredentials(auth.NewRootUserNamespace()))
-}
-
-// WithCreds returns a copy of ctx carrying creds.
-func WithCreds(ctx context.Context, creds *auth.Credentials) context.Context {
- return &authContext{ctx, creds}
-}
-
-type authContext struct {
- context.Context
- creds *auth.Credentials
-}
-
-// Value implements context.Context.
-func (ac *authContext) Value(key interface{}) interface{} {
- switch key {
- case auth.CtxCredentials:
- return ac.creds
- default:
- return ac.Context.Value(key)
- }
-}
-
-// WithLimitSet returns a copy of ctx carrying l.
-func WithLimitSet(ctx context.Context, l *limits.LimitSet) context.Context {
- return limitContext{ctx, l}
-}
-
-type limitContext struct {
- context.Context
- l *limits.LimitSet
-}
-
-// Value implements context.Context.
-func (lc limitContext) Value(key interface{}) interface{} {
- switch key {
- case limits.CtxLimits:
- return lc.l
- default:
- return lc.Context.Value(key)
- }
-}
diff --git a/pkg/sentry/control/BUILD b/pkg/sentry/control/BUILD
deleted file mode 100644
index 5522cecd0..000000000
--- a/pkg/sentry/control/BUILD
+++ /dev/null
@@ -1,48 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "control",
- srcs = [
- "control.go",
- "logging.go",
- "pprof.go",
- "proc.go",
- "state.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/control",
- visibility = [
- "//pkg/sentry:internal",
- ],
- deps = [
- "//pkg/abi/linux",
- "//pkg/fd",
- "//pkg/log",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/host",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/limits",
- "//pkg/sentry/state",
- "//pkg/sentry/strace",
- "//pkg/sentry/usage",
- "//pkg/sentry/watchdog",
- "//pkg/tcpip/link/sniffer",
- "//pkg/urpc",
- ],
-)
-
-go_test(
- name = "control_test",
- size = "small",
- srcs = ["proc_test.go"],
- embed = [":control"],
- deps = [
- "//pkg/log",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/usage",
- ],
-)
diff --git a/pkg/sentry/control/control_state_autogen.go b/pkg/sentry/control/control_state_autogen.go
new file mode 100755
index 000000000..a1de4bc6d
--- /dev/null
+++ b/pkg/sentry/control/control_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package control
+
diff --git a/pkg/sentry/control/proc_test.go b/pkg/sentry/control/proc_test.go
deleted file mode 100644
index d8ada2694..000000000
--- a/pkg/sentry/control/proc_test.go
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package control
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/log"
- ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time"
- "gvisor.dev/gvisor/pkg/sentry/usage"
-)
-
-func init() {
- log.SetLevel(log.Debug)
-}
-
-// Tests that ProcessData.Table() prints with the correct format.
-func TestProcessListTable(t *testing.T) {
- testCases := []struct {
- pl []*Process
- expected string
- }{
- {
- pl: []*Process{},
- expected: "UID PID PPID C STIME TIME CMD",
- },
- {
- pl: []*Process{
- {
- UID: 0,
- PID: 0,
- PPID: 0,
- C: 0,
- STime: "0",
- Time: "0",
- Cmd: "zero",
- },
- {
- UID: 1,
- PID: 1,
- PPID: 1,
- C: 1,
- STime: "1",
- Time: "1",
- Cmd: "one",
- },
- },
- expected: `UID PID PPID C STIME TIME CMD
-0 0 0 0 0 0 zero
-1 1 1 1 1 1 one`,
- },
- }
-
- for _, tc := range testCases {
- output := ProcessListToTable(tc.pl)
-
- if tc.expected != output {
- t.Errorf("PrintTable(%v): got:\n%s\nwant:\n%s", tc.pl, output, tc.expected)
- }
- }
-}
-
-func TestProcessListJSON(t *testing.T) {
- testCases := []struct {
- pl []*Process
- expected string
- }{
- {
- pl: []*Process{},
- expected: "[]",
- },
- {
- pl: []*Process{
- {
- UID: 0,
- PID: 0,
- PPID: 0,
- C: 0,
- STime: "0",
- Time: "0",
- Cmd: "zero",
- },
- {
- UID: 1,
- PID: 1,
- PPID: 1,
- C: 1,
- STime: "1",
- Time: "1",
- Cmd: "one",
- },
- },
- expected: "[0,1]",
- },
- }
-
- for _, tc := range testCases {
- output, err := PrintPIDsJSON(tc.pl)
- if err != nil {
- t.Errorf("failed to generate JSON: %v", err)
- }
-
- if tc.expected != output {
- t.Errorf("PrintJSON(%v): got:\n%s\nwant:\n%s", tc.pl, output, tc.expected)
- }
- }
-}
-
-func TestPercentCPU(t *testing.T) {
- testCases := []struct {
- stats usage.CPUStats
- startTime ktime.Time
- now ktime.Time
- expected int32
- }{
- {
- // Verify that 100% use is capped at 99.
- stats: usage.CPUStats{UserTime: 1e9, SysTime: 1e9},
- startTime: ktime.FromNanoseconds(7e9),
- now: ktime.FromNanoseconds(9e9),
- expected: 99,
- },
- {
- // Verify that if usage > lifetime, we get at most 99%
- // usage.
- stats: usage.CPUStats{UserTime: 2e9, SysTime: 2e9},
- startTime: ktime.FromNanoseconds(7e9),
- now: ktime.FromNanoseconds(9e9),
- expected: 99,
- },
- {
- // Verify that 50% usage is reported correctly.
- stats: usage.CPUStats{UserTime: 1e9, SysTime: 1e9},
- startTime: ktime.FromNanoseconds(12e9),
- now: ktime.FromNanoseconds(16e9),
- expected: 50,
- },
- {
- // Verify that 0% usage is reported correctly.
- stats: usage.CPUStats{UserTime: 0, SysTime: 0},
- startTime: ktime.FromNanoseconds(12e9),
- now: ktime.FromNanoseconds(14e9),
- expected: 0,
- },
- }
-
- for _, tc := range testCases {
- if pcpu := percentCPU(tc.stats, tc.startTime, tc.now); pcpu != tc.expected {
- t.Errorf("percentCPU(%v, %v, %v): got %d, want %d", tc.stats, tc.startTime, tc.now, pcpu, tc.expected)
- }
- }
-}
diff --git a/pkg/sentry/device/BUILD b/pkg/sentry/device/BUILD
deleted file mode 100644
index 0c86197f7..000000000
--- a/pkg/sentry/device/BUILD
+++ /dev/null
@@ -1,20 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "device",
- srcs = ["device.go"],
- importpath = "gvisor.dev/gvisor/pkg/sentry/device",
- visibility = ["//pkg/sentry:internal"],
- deps = ["//pkg/abi/linux"],
-)
-
-go_test(
- name = "device_test",
- size = "small",
- srcs = ["device_test.go"],
- embed = [":device"],
-)
diff --git a/pkg/sentry/device/device_state_autogen.go b/pkg/sentry/device/device_state_autogen.go
new file mode 100755
index 000000000..be40f6c77
--- /dev/null
+++ b/pkg/sentry/device/device_state_autogen.go
@@ -0,0 +1,52 @@
+// automatically generated by stateify.
+
+package device
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *Registry) beforeSave() {}
+func (x *Registry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("lastAnonDeviceMinor", &x.lastAnonDeviceMinor)
+ m.Save("devices", &x.devices)
+}
+
+func (x *Registry) afterLoad() {}
+func (x *Registry) load(m state.Map) {
+ m.Load("lastAnonDeviceMinor", &x.lastAnonDeviceMinor)
+ m.Load("devices", &x.devices)
+}
+
+func (x *ID) beforeSave() {}
+func (x *ID) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Major", &x.Major)
+ m.Save("Minor", &x.Minor)
+}
+
+func (x *ID) afterLoad() {}
+func (x *ID) load(m state.Map) {
+ m.Load("Major", &x.Major)
+ m.Load("Minor", &x.Minor)
+}
+
+func (x *Device) beforeSave() {}
+func (x *Device) save(m state.Map) {
+ x.beforeSave()
+ m.Save("ID", &x.ID)
+ m.Save("last", &x.last)
+}
+
+func (x *Device) afterLoad() {}
+func (x *Device) load(m state.Map) {
+ m.Load("ID", &x.ID)
+ m.Load("last", &x.last)
+}
+
+func init() {
+ state.Register("device.Registry", (*Registry)(nil), state.Fns{Save: (*Registry).save, Load: (*Registry).load})
+ state.Register("device.ID", (*ID)(nil), state.Fns{Save: (*ID).save, Load: (*ID).load})
+ state.Register("device.Device", (*Device)(nil), state.Fns{Save: (*Device).save, Load: (*Device).load})
+}
diff --git a/pkg/sentry/device/device_test.go b/pkg/sentry/device/device_test.go
deleted file mode 100644
index e3f51ce4f..000000000
--- a/pkg/sentry/device/device_test.go
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package device
-
-import (
- "testing"
-)
-
-func TestMultiDevice(t *testing.T) {
- device := &MultiDevice{}
-
- // Check that Load fails to install virtual inodes that are
- // uninitialized.
- if device.Load(MultiDeviceKey{}, 0) {
- t.Fatalf("got load of invalid virtual inode 0, want unsuccessful")
- }
-
- inode := device.Map(MultiDeviceKey{})
-
- // Assert that the same raw device and inode map to
- // a consistent virtual inode.
- if i := device.Map(MultiDeviceKey{}); i != inode {
- t.Fatalf("got inode %d, want %d in %s", i, inode, device)
- }
-
- // Assert that a new inode or new device does not conflict.
- if i := device.Map(MultiDeviceKey{Device: 0, Inode: 1}); i == inode {
- t.Fatalf("got reused inode %d, want new distinct inode in %s", i, device)
- }
- last := device.Map(MultiDeviceKey{Device: 1, Inode: 0})
- if last == inode {
- t.Fatalf("got reused inode %d, want new distinct inode in %s", last, device)
- }
-
- // Virtual is the virtual inode we want to load.
- virtual := last + 1
-
- // Assert that we can load a virtual inode at a new place.
- if !device.Load(MultiDeviceKey{Device: 0, Inode: 2}, virtual) {
- t.Fatalf("got load of virtual inode %d failed, want success in %s", virtual, device)
- }
-
- // Assert that the next inode skips over the loaded one.
- if i := device.Map(MultiDeviceKey{Device: 0, Inode: 3}); i != virtual+1 {
- t.Fatalf("got inode %d, want %d in %s", i, virtual+1, device)
- }
-}
diff --git a/pkg/sentry/fs/BUILD b/pkg/sentry/fs/BUILD
deleted file mode 100644
index 3119a61b6..000000000
--- a/pkg/sentry/fs/BUILD
+++ /dev/null
@@ -1,137 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "fs",
- srcs = [
- "attr.go",
- "context.go",
- "copy_up.go",
- "dentry.go",
- "dirent.go",
- "dirent_cache.go",
- "dirent_cache_limiter.go",
- "dirent_list.go",
- "dirent_state.go",
- "event_list.go",
- "file.go",
- "file_operations.go",
- "file_overlay.go",
- "file_state.go",
- "filesystems.go",
- "flags.go",
- "fs.go",
- "inode.go",
- "inode_inotify.go",
- "inode_operations.go",
- "inode_overlay.go",
- "inotify.go",
- "inotify_event.go",
- "inotify_watch.go",
- "mock.go",
- "mount.go",
- "mount_overlay.go",
- "mounts.go",
- "offset.go",
- "overlay.go",
- "path.go",
- "restore.go",
- "save.go",
- "seek.go",
- "splice.go",
- "sync.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/amutex",
- "//pkg/log",
- "//pkg/metric",
- "//pkg/p9",
- "//pkg/refs",
- "//pkg/secio",
- "//pkg/sentry/arch",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs/lock",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/limits",
- "//pkg/sentry/memmap",
- "//pkg/sentry/platform",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/uniqueid",
- "//pkg/sentry/usage",
- "//pkg/sentry/usermem",
- "//pkg/state",
- "//pkg/syserror",
- "//pkg/waiter",
- "//third_party/gvsync",
- ],
-)
-
-go_template_instance(
- name = "dirent_list",
- out = "dirent_list.go",
- package = "fs",
- prefix = "dirent",
- template = "//pkg/ilist:generic_list",
- types = {
- "Linker": "*Dirent",
- "Element": "*Dirent",
- },
-)
-
-go_template_instance(
- name = "event_list",
- out = "event_list.go",
- package = "fs",
- prefix = "event",
- template = "//pkg/ilist:generic_list",
- types = {
- "Linker": "*Event",
- "Element": "*Event",
- },
-)
-
-go_test(
- name = "fs_x_test",
- size = "small",
- srcs = [
- "copy_up_test.go",
- "file_overlay_test.go",
- "inode_overlay_test.go",
- "mounts_test.go",
- ],
- deps = [
- ":fs",
- "//pkg/sentry/context",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/fs/ramfs",
- "//pkg/sentry/fs/tmpfs",
- "//pkg/sentry/kernel/contexttest",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- ],
-)
-
-go_test(
- name = "fs_test",
- size = "small",
- srcs = [
- "dirent_cache_test.go",
- "dirent_refs_test.go",
- "mount_test.go",
- "path_test.go",
- ],
- embed = [":fs"],
- deps = [
- "//pkg/sentry/context",
- "//pkg/sentry/context/contexttest",
- ],
-)
diff --git a/pkg/sentry/fs/README.md b/pkg/sentry/fs/README.md
deleted file mode 100644
index db4a1b730..000000000
--- a/pkg/sentry/fs/README.md
+++ /dev/null
@@ -1,229 +0,0 @@
-This package provides an implementation of the Linux virtual filesystem.
-
-[TOC]
-
-## Overview
-
-- An `fs.Dirent` caches an `fs.Inode` in memory at a path in the VFS, giving
- the `fs.Inode` a relative position with respect to other `fs.Inode`s.
-
-- If an `fs.Dirent` is referenced by two file descriptors, then those file
- descriptors are coherent with each other: they depend on the same
- `fs.Inode`.
-
-- A mount point is an `fs.Dirent` for which `fs.Dirent.mounted` is true. It
- exposes the root of a mounted filesystem.
-
-- The `fs.Inode` produced by a registered filesystem on mount(2) owns an
- `fs.MountedFilesystem` from which other `fs.Inode`s will be looked up. For a
- remote filesystem, the `fs.MountedFilesystem` owns the connection to that
- remote filesystem.
-
-- In general:
-
-```
-fs.Inode <------------------------------
-| |
-| |
-produced by |
-exactly one |
-| responsible for the
-| virtual identity of
-v |
-fs.MountedFilesystem -------------------
-```
-
-Glossary:
-
-- VFS: virtual filesystem.
-
-- inode: a virtual file object holding a cached view of a file on a backing
- filesystem (includes metadata and page caches).
-
-- superblock: the virtual state of a mounted filesystem (e.g. the virtual
- inode number set).
-
-- mount namespace: a view of the mounts under a root (during path traversal,
- the VFS makes visible/follows the mount point that is in the current task's
- mount namespace).
-
-## Save and restore
-
-An application's hard dependencies on filesystem state can be broken down into
-two categories:
-
-- The state necessary to execute a traversal on or view the *virtual*
- filesystem hierarchy, regardless of what files an application has open.
-
-- The state necessary to represent open files.
-
-The first is always necessary to save and restore. An application may never have
-any open file descriptors, but across save and restore it should see a coherent
-view of any mount namespace. NOTE(b/63601033): Currently only one "initial"
-mount namespace is supported.
-
-The second is so that system calls across save and restore are coherent with
-each other (e.g. so that unintended re-reads or overwrites do not occur).
-
-Specifically this state is:
-
-- An `fs.MountManager` containing mount points.
-
-- A `kernel.FDTable` containing pointers to open files.
-
-Anything else managed by the VFS that can be easily loaded into memory from a
-filesystem is synced back to those filesystems and is not saved. Examples are
-pages in page caches used for optimizations (i.e. readahead and writeback), and
-directory entries used to accelerate path lookups.
-
-### Mount points
-
-Saving and restoring a mount point means saving and restoring:
-
-- The root of the mounted filesystem.
-
-- Mount flags, which control how the VFS interacts with the mounted
- filesystem.
-
-- Any relevant metadata about the mounted filesystem.
-
-- All `fs.Inode`s referenced by the application that reside under the mount
- point.
-
-`fs.MountedFilesystem` is metadata about a filesystem that is mounted. It is
-referenced by every `fs.Inode` loaded into memory under the mount point
-including the `fs.Inode` of the mount point itself. The `fs.MountedFilesystem`
-maps file objects on the filesystem to a virtualized `fs.Inode` number and vice
-versa.
-
-To restore all `fs.Inode`s under a given mount point, each `fs.Inode` leverages
-its dependency on an `fs.MountedFilesystem`. Since the `fs.MountedFilesystem`
-knows how an `fs.Inode` maps to a file object on a backing filesystem, this
-mapping can be trivially consulted by each `fs.Inode` when the `fs.Inode` is
-restored.
-
-In detail, a mount point is saved in two steps:
-
-- First, after the kernel is paused but before state.Save, we walk all mount
- namespaces and install a mapping from `fs.Inode` numbers to file paths
- relative to the root of the mounted filesystem in each
- `fs.MountedFilesystem`. This is subsequently called the set of `fs.Inode`
- mappings.
-
-- Second, during state.Save, each `fs.MountedFilesystem` decides whether to
- save the set of `fs.Inode` mappings. In-memory filesystems, like tmpfs, have
- no need to save a set of `fs.Inode` mappings, since the `fs.Inode`s can be
- entirely encoded in state file. Each `fs.MountedFilesystem` also optionally
- saves the device name from when the filesystem was originally mounted. Each
- `fs.Inode` saves its virtual identifier and a reference to a
- `fs.MountedFilesystem`.
-
-A mount point is restored in two steps:
-
-- First, before state.Load, all mount configurations are stored in a global
- `fs.RestoreEnvironment`. This tells us what mount points the user wants to
- restore and how to re-establish pointers to backing filesystems.
-
-- Second, during state.Load, each `fs.MountedFilesystem` optionally searches
- for a mount in the `fs.RestoreEnvironment` that matches its saved device
- name. The `fs.MountedFilesystem` then reestablishes a pointer to the root of
- the mounted filesystem. For example, the mount specification provides the
- network connection for a mounted remote filesystem client to communicate
- with its remote file server. The `fs.MountedFilesystem` also trivially loads
- its set of `fs.Inode` mappings. When an `fs.Inode` is encountered, the
- `fs.Inode` loads its virtual identifier and its reference a
- `fs.MountedFilesystem`. It uses the `fs.MountedFilesystem` to obtain the
- root of the mounted filesystem and the `fs.Inode` mappings to obtain the
- relative file path to its data. With these, the `fs.Inode` re-establishes a
- pointer to its file object.
-
-A mount point can trivially restore its `fs.Inode`s in parallel since
-`fs.Inode`s have a restore dependency on their `fs.MountedFilesystem` and not on
-each other.
-
-### Open files
-
-An `fs.File` references the following filesystem objects:
-
-```go
-fs.File -> fs.Dirent -> fs.Inode -> fs.MountedFilesystem
-```
-
-The `fs.Inode` is restored using its `fs.MountedFilesystem`. The
-[Mount points](#mount-points) section above describes how this happens in
-detail. The `fs.Dirent` restores its pointer to an `fs.Inode`, pointers to
-parent and children `fs.Dirents`, and the basename of the file.
-
-Otherwise an `fs.File` restores flags, an offset, and a unique identifier (only
-used internally).
-
-It may use the `fs.Inode`, which it indirectly holds a reference on through the
-`fs.Dirent`, to reestablish an open file handle on the backing filesystem (e.g.
-to continue reading and writing).
-
-## Overlay
-
-The overlay implementation in the fs package takes Linux overlayfs as a frame of
-reference but corrects for several POSIX consistency errors.
-
-In Linux overlayfs, the `struct inode` used for reading and writing to the same
-file may be different. This is because the `struct inode` is dissociated with
-the process of copying up the file from the upper to the lower directory. Since
-flock(2) and fcntl(2) locks, inotify(7) watches, page caches, and a file's
-identity are all stored directly or indirectly off the `struct inode`, these
-properties of the `struct inode` may be stale after the first modification. This
-can lead to file locking bugs, missed inotify events, and inconsistent data in
-shared memory mappings of files, to name a few problems.
-
-The fs package maintains a single `fs.Inode` to represent a directory entry in
-an overlay and defines operations on this `fs.Inode` which synchronize with the
-copy up process. This achieves several things:
-
-+ File locks, inotify watches, and the identity of the file need not be copied
- at all.
-
-+ Memory mappings of files coordinate with the copy up process so that if a
- file in the lower directory is memory mapped, all references to it are
- invalidated, forcing the application to re-fault on memory mappings of the
- file under the upper directory.
-
-The `fs.Inode` holds metadata about files in the upper and/or lower directories
-via an `fs.overlayEntry`. The `fs.overlayEntry` implements the `fs.Mappable`
-interface. It multiplexes between upper and lower directory memory mappings and
-stores a copy of memory references so they can be transferred to the upper
-directory `fs.Mappable` when the file is copied up.
-
-The lower filesystem in an overlay may contain another (nested) overlay, but the
-upper filesystem may not contain another overlay. In other words, nested
-overlays form a tree structure that only allows branching in the lower
-filesystem.
-
-Caching decisions in the overlay are delegated to the upper filesystem, meaning
-that the Keep and Revalidate methods on the overlay return the same values as
-the upper filesystem. A small wrinkle is that the lower filesystem is not
-allowed to return `true` from Revalidate, as the overlay can not reload inodes
-from the lower filesystem. A lower filesystem that does return `true` from
-Revalidate will trigger a panic.
-
-The `fs.Inode` also holds a reference to a `fs.MountedFilesystem` that
-normalizes across the mounted filesystem state of the upper and lower
-directories.
-
-When a file is copied from the lower to the upper directory, attempts to
-interact with the file block until the copy completes. All copying synchronizes
-with rename(2).
-
-## Future Work
-
-### Overlay
-
-When a file is copied from a lower directory to an upper directory, several
-locks are taken: the global renamuMu and the copyMu of the `fs.Inode` being
-copied. This blocks operations on the file, including fault handling of memory
-mappings. Performance could be improved by copying files into a temporary
-directory that resides on the same filesystem as the upper directory and doing
-an atomic rename, holding locks only during the rename operation.
-
-Additionally files are copied up synchronously. For large files, this causes a
-noticeable latency. Performance could be improved by pipelining copies at
-non-overlapping file offsets.
diff --git a/pkg/sentry/fs/anon/BUILD b/pkg/sentry/fs/anon/BUILD
deleted file mode 100644
index ae1c9cf76..000000000
--- a/pkg/sentry/fs/anon/BUILD
+++ /dev/null
@@ -1,21 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "anon",
- srcs = [
- "anon.go",
- "device.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/anon",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/usermem",
- ],
-)
diff --git a/pkg/sentry/fs/anon/anon_state_autogen.go b/pkg/sentry/fs/anon/anon_state_autogen.go
new file mode 100755
index 000000000..fcb914212
--- /dev/null
+++ b/pkg/sentry/fs/anon/anon_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package anon
+
diff --git a/pkg/sentry/fs/copy_up_test.go b/pkg/sentry/fs/copy_up_test.go
deleted file mode 100644
index 1d80bf15a..000000000
--- a/pkg/sentry/fs/copy_up_test.go
+++ /dev/null
@@ -1,183 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fs_test
-
-import (
- "bytes"
- "crypto/rand"
- "fmt"
- "io"
- "sync"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/fs"
- _ "gvisor.dev/gvisor/pkg/sentry/fs/tmpfs"
- "gvisor.dev/gvisor/pkg/sentry/kernel/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
-)
-
-const (
- // origFileSize is the original file size. This many bytes should be
- // copied up before the test file is modified.
- origFileSize = 4096
-
- // truncatedFileSize is the size to truncate all test files.
- truncateFileSize = 10
-)
-
-// TestConcurrentCopyUp is a copy up stress test for an overlay.
-//
-// It creates a 64-level deep directory tree in the lower filesystem and
-// populates the last subdirectory with 64 files containing random content:
-//
-// /lower
-// /sudir0/.../subdir63/
-// /file0
-// ...
-// /file63
-//
-// The files are truncated concurrently by 4 goroutines per file.
-// These goroutines contend with copying up all parent 64 subdirectories
-// as well as the final file content.
-//
-// At the end of the test, we assert that the files respect the new truncated
-// size and contain the content we expect.
-func TestConcurrentCopyUp(t *testing.T) {
- ctx := contexttest.Context(t)
- files := makeOverlayTestFiles(t)
-
- var wg sync.WaitGroup
- for _, file := range files {
- for i := 0; i < 4; i++ {
- wg.Add(1)
- go func(o *overlayTestFile) {
- if err := o.File.Dirent.Inode.Truncate(ctx, o.File.Dirent, truncateFileSize); err != nil {
- t.Fatalf("failed to copy up: %v", err)
- }
- wg.Done()
- }(file)
- }
- }
- wg.Wait()
-
- for _, file := range files {
- got := make([]byte, origFileSize)
- n, err := file.File.Readv(ctx, usermem.BytesIOSequence(got))
- if int(n) != truncateFileSize {
- t.Fatalf("read %d bytes from file, want %d", n, truncateFileSize)
- }
- if err != nil && err != io.EOF {
- t.Fatalf("read got error %v, want nil", err)
- }
- if !bytes.Equal(got[:n], file.content[:truncateFileSize]) {
- t.Fatalf("file content is %v, want %v", got[:n], file.content[:truncateFileSize])
- }
- }
-}
-
-type overlayTestFile struct {
- File *fs.File
- name string
- content []byte
-}
-
-func makeOverlayTestFiles(t *testing.T) []*overlayTestFile {
- ctx := contexttest.Context(t)
-
- // Create a lower tmpfs mount.
- fsys, _ := fs.FindFilesystem("tmpfs")
- lower, err := fsys.Mount(contexttest.Context(t), "", fs.MountSourceFlags{}, "", nil)
- if err != nil {
- t.Fatalf("failed to mount tmpfs: %v", err)
- }
- lowerRoot := fs.NewDirent(ctx, lower, "")
-
- // Make a deep set of subdirectories that everyone shares.
- next := lowerRoot
- for i := 0; i < 64; i++ {
- name := fmt.Sprintf("subdir%d", i)
- err := next.CreateDirectory(ctx, lowerRoot, name, fs.FilePermsFromMode(0777))
- if err != nil {
- t.Fatalf("failed to create dir %q: %v", name, err)
- }
- next, err = next.Walk(ctx, lowerRoot, name)
- if err != nil {
- t.Fatalf("failed to walk to %q: %v", name, err)
- }
- }
-
- // Make a bunch of files in the last directory.
- var files []*overlayTestFile
- for i := 0; i < 64; i++ {
- name := fmt.Sprintf("file%d", i)
- f, err := next.Create(ctx, next, name, fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0666))
- if err != nil {
- t.Fatalf("failed to create file %q: %v", name, err)
- }
- defer f.DecRef()
-
- relname, _ := f.Dirent.FullName(lowerRoot)
-
- o := &overlayTestFile{
- name: relname,
- content: make([]byte, origFileSize),
- }
-
- if _, err := rand.Read(o.content); err != nil {
- t.Fatalf("failed to read from /dev/urandom: %v", err)
- }
-
- if _, err := f.Writev(ctx, usermem.BytesIOSequence(o.content)); err != nil {
- t.Fatalf("failed to write content to file %q: %v", name, err)
- }
-
- files = append(files, o)
- }
-
- // Create an empty upper tmpfs mount which we will copy up into.
- upper, err := fsys.Mount(ctx, "", fs.MountSourceFlags{}, "", nil)
- if err != nil {
- t.Fatalf("failed to mount tmpfs: %v", err)
- }
-
- // Construct an overlay root.
- overlay, err := fs.NewOverlayRoot(ctx, upper, lower, fs.MountSourceFlags{})
- if err != nil {
- t.Fatalf("failed to construct overlay root: %v", err)
- }
-
- // Create a MountNamespace to traverse the file system.
- mns, err := fs.NewMountNamespace(ctx, overlay)
- if err != nil {
- t.Fatalf("failed to construct mount manager: %v", err)
- }
-
- // Walk to all of the files in the overlay, open them readable.
- for _, f := range files {
- maxTraversals := uint(0)
- d, err := mns.FindInode(ctx, mns.Root(), mns.Root(), f.name, &maxTraversals)
- if err != nil {
- t.Fatalf("failed to find %q: %v", f.name, err)
- }
- defer d.DecRef()
-
- f.File, err = d.Inode.GetFile(ctx, d, fs.FileFlags{Read: true})
- if err != nil {
- t.Fatalf("failed to open file %q readable: %v", f.name, err)
- }
- }
-
- return files
-}
diff --git a/pkg/sentry/fs/dev/BUILD b/pkg/sentry/fs/dev/BUILD
deleted file mode 100644
index 80e106e6f..000000000
--- a/pkg/sentry/fs/dev/BUILD
+++ /dev/null
@@ -1,35 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "dev",
- srcs = [
- "dev.go",
- "device.go",
- "fs.go",
- "full.go",
- "null.go",
- "random.go",
- "tty.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/dev",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/rand",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/fs/ramfs",
- "//pkg/sentry/fs/tmpfs",
- "//pkg/sentry/memmap",
- "//pkg/sentry/mm",
- "//pkg/sentry/pgalloc",
- "//pkg/sentry/safemem",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/fs/dev/dev_state_autogen.go b/pkg/sentry/fs/dev/dev_state_autogen.go
new file mode 100755
index 000000000..a997f3ecf
--- /dev/null
+++ b/pkg/sentry/fs/dev/dev_state_autogen.go
@@ -0,0 +1,130 @@
+// automatically generated by stateify.
+
+package dev
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *filesystem) beforeSave() {}
+func (x *filesystem) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *filesystem) afterLoad() {}
+func (x *filesystem) load(m state.Map) {
+}
+
+func (x *fullDevice) beforeSave() {}
+func (x *fullDevice) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+}
+
+func (x *fullDevice) afterLoad() {}
+func (x *fullDevice) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+}
+
+func (x *fullFileOperations) beforeSave() {}
+func (x *fullFileOperations) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *fullFileOperations) afterLoad() {}
+func (x *fullFileOperations) load(m state.Map) {
+}
+
+func (x *nullDevice) beforeSave() {}
+func (x *nullDevice) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+}
+
+func (x *nullDevice) afterLoad() {}
+func (x *nullDevice) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+}
+
+func (x *nullFileOperations) beforeSave() {}
+func (x *nullFileOperations) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *nullFileOperations) afterLoad() {}
+func (x *nullFileOperations) load(m state.Map) {
+}
+
+func (x *zeroDevice) beforeSave() {}
+func (x *zeroDevice) save(m state.Map) {
+ x.beforeSave()
+ m.Save("nullDevice", &x.nullDevice)
+}
+
+func (x *zeroDevice) afterLoad() {}
+func (x *zeroDevice) load(m state.Map) {
+ m.Load("nullDevice", &x.nullDevice)
+}
+
+func (x *zeroFileOperations) beforeSave() {}
+func (x *zeroFileOperations) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *zeroFileOperations) afterLoad() {}
+func (x *zeroFileOperations) load(m state.Map) {
+}
+
+func (x *randomDevice) beforeSave() {}
+func (x *randomDevice) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+}
+
+func (x *randomDevice) afterLoad() {}
+func (x *randomDevice) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+}
+
+func (x *randomFileOperations) beforeSave() {}
+func (x *randomFileOperations) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *randomFileOperations) afterLoad() {}
+func (x *randomFileOperations) load(m state.Map) {
+}
+
+func (x *ttyInodeOperations) beforeSave() {}
+func (x *ttyInodeOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+}
+
+func (x *ttyInodeOperations) afterLoad() {}
+func (x *ttyInodeOperations) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+}
+
+func (x *ttyFileOperations) beforeSave() {}
+func (x *ttyFileOperations) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *ttyFileOperations) afterLoad() {}
+func (x *ttyFileOperations) load(m state.Map) {
+}
+
+func init() {
+ state.Register("dev.filesystem", (*filesystem)(nil), state.Fns{Save: (*filesystem).save, Load: (*filesystem).load})
+ state.Register("dev.fullDevice", (*fullDevice)(nil), state.Fns{Save: (*fullDevice).save, Load: (*fullDevice).load})
+ state.Register("dev.fullFileOperations", (*fullFileOperations)(nil), state.Fns{Save: (*fullFileOperations).save, Load: (*fullFileOperations).load})
+ state.Register("dev.nullDevice", (*nullDevice)(nil), state.Fns{Save: (*nullDevice).save, Load: (*nullDevice).load})
+ state.Register("dev.nullFileOperations", (*nullFileOperations)(nil), state.Fns{Save: (*nullFileOperations).save, Load: (*nullFileOperations).load})
+ state.Register("dev.zeroDevice", (*zeroDevice)(nil), state.Fns{Save: (*zeroDevice).save, Load: (*zeroDevice).load})
+ state.Register("dev.zeroFileOperations", (*zeroFileOperations)(nil), state.Fns{Save: (*zeroFileOperations).save, Load: (*zeroFileOperations).load})
+ state.Register("dev.randomDevice", (*randomDevice)(nil), state.Fns{Save: (*randomDevice).save, Load: (*randomDevice).load})
+ state.Register("dev.randomFileOperations", (*randomFileOperations)(nil), state.Fns{Save: (*randomFileOperations).save, Load: (*randomFileOperations).load})
+ state.Register("dev.ttyInodeOperations", (*ttyInodeOperations)(nil), state.Fns{Save: (*ttyInodeOperations).save, Load: (*ttyInodeOperations).load})
+ state.Register("dev.ttyFileOperations", (*ttyFileOperations)(nil), state.Fns{Save: (*ttyFileOperations).save, Load: (*ttyFileOperations).load})
+}
diff --git a/pkg/sentry/fs/dirent_cache_test.go b/pkg/sentry/fs/dirent_cache_test.go
deleted file mode 100644
index 395c879f5..000000000
--- a/pkg/sentry/fs/dirent_cache_test.go
+++ /dev/null
@@ -1,247 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fs
-
-import (
- "testing"
-)
-
-func TestDirentCache(t *testing.T) {
- const maxSize = 5
-
- c := NewDirentCache(maxSize)
-
- // Size starts at 0.
- if got, want := c.Size(), uint64(0); got != want {
- t.Errorf("c.Size() got %v, want %v", got, want)
- }
-
- // Create a Dirent d.
- d := NewNegativeDirent("")
-
- // c does not contain d.
- if got, want := c.contains(d), false; got != want {
- t.Errorf("c.contains(d) got %v want %v", got, want)
- }
-
- // Add d to the cache.
- c.Add(d)
-
- // Size is now 1.
- if got, want := c.Size(), uint64(1); got != want {
- t.Errorf("c.Size() got %v, want %v", got, want)
- }
-
- // c contains d.
- if got, want := c.contains(d), true; got != want {
- t.Errorf("c.contains(d) got %v want %v", got, want)
- }
-
- // Add maxSize-1 more elements. d should be oldest element.
- for i := 0; i < maxSize-1; i++ {
- c.Add(NewNegativeDirent(""))
- }
-
- // Size is maxSize.
- if got, want := c.Size(), uint64(maxSize); got != want {
- t.Errorf("c.Size() got %v, want %v", got, want)
- }
-
- // c contains d.
- if got, want := c.contains(d), true; got != want {
- t.Errorf("c.contains(d) got %v want %v", got, want)
- }
-
- // "Bump" d to the front by re-adding it.
- c.Add(d)
-
- // Size is maxSize.
- if got, want := c.Size(), uint64(maxSize); got != want {
- t.Errorf("c.Size() got %v, want %v", got, want)
- }
-
- // c contains d.
- if got, want := c.contains(d), true; got != want {
- t.Errorf("c.contains(d) got %v want %v", got, want)
- }
-
- // Add maxSize-1 more elements. d should again be oldest element.
- for i := 0; i < maxSize-1; i++ {
- c.Add(NewNegativeDirent(""))
- }
-
- // Size is maxSize.
- if got, want := c.Size(), uint64(maxSize); got != want {
- t.Errorf("c.Size() got %v, want %v", got, want)
- }
-
- // c contains d.
- if got, want := c.contains(d), true; got != want {
- t.Errorf("c.contains(d) got %v want %v", got, want)
- }
-
- // Add one more element, which will bump d from the cache.
- c.Add(NewNegativeDirent(""))
-
- // Size is maxSize.
- if got, want := c.Size(), uint64(maxSize); got != want {
- t.Errorf("c.Size() got %v, want %v", got, want)
- }
-
- // c does not contain d.
- if got, want := c.contains(d), false; got != want {
- t.Errorf("c.contains(d) got %v want %v", got, want)
- }
-
- // Invalidating causes size to be 0 and list to be empty.
- c.Invalidate()
- if got, want := c.Size(), uint64(0); got != want {
- t.Errorf("c.Size() got %v, want %v", got, want)
- }
- if got, want := c.list.Empty(), true; got != want {
- t.Errorf("c.list.Empty() got %v, want %v", got, want)
- }
-
- // Fill cache with maxSize dirents.
- for i := 0; i < maxSize; i++ {
- c.Add(NewNegativeDirent(""))
- }
-}
-
-func TestDirentCacheLimiter(t *testing.T) {
- const (
- globalMaxSize = 5
- maxSize = 3
- )
-
- limit := NewDirentCacheLimiter(globalMaxSize)
- c1 := NewDirentCache(maxSize)
- c1.limit = limit
- c2 := NewDirentCache(maxSize)
- c2.limit = limit
-
- // Create a Dirent d.
- d := NewNegativeDirent("")
-
- // Add d to the cache.
- c1.Add(d)
- if got, want := c1.Size(), uint64(1); got != want {
- t.Errorf("c1.Size() got %v, want %v", got, want)
- }
-
- // Add maxSize-1 more elements. d should be oldest element.
- for i := 0; i < maxSize-1; i++ {
- c1.Add(NewNegativeDirent(""))
- }
- if got, want := c1.Size(), uint64(maxSize); got != want {
- t.Errorf("c1.Size() got %v, want %v", got, want)
- }
-
- // Check that d is still there.
- if got, want := c1.contains(d), true; got != want {
- t.Errorf("c1.contains(d) got %v want %v", got, want)
- }
-
- // Fill up the other cache, it will start dropping old entries from the cache
- // when the global limit is reached.
- for i := 0; i < maxSize; i++ {
- c2.Add(NewNegativeDirent(""))
- }
-
- // Check is what's remaining from global max.
- if got, want := c2.Size(), globalMaxSize-maxSize; int(got) != want {
- t.Errorf("c2.Size() got %v, want %v", got, want)
- }
-
- // Check that d was not dropped.
- if got, want := c1.contains(d), true; got != want {
- t.Errorf("c1.contains(d) got %v want %v", got, want)
- }
-
- // Add an entry that will eventually be dropped. Check is done later...
- drop := NewNegativeDirent("")
- c1.Add(drop)
-
- // Check that d is bumped to front even when global limit is reached.
- c1.Add(d)
- if got, want := c1.contains(d), true; got != want {
- t.Errorf("c1.contains(d) got %v want %v", got, want)
- }
-
- // Add 2 more element and check that:
- // - d is still in the list: to verify that d was bumped
- // - d2/d3 are in the list: older entries are dropped when global limit is
- // reached.
- // - drop is not in the list: indeed older elements are dropped.
- d2 := NewNegativeDirent("")
- c1.Add(d2)
- d3 := NewNegativeDirent("")
- c1.Add(d3)
- if got, want := c1.contains(d), true; got != want {
- t.Errorf("c1.contains(d) got %v want %v", got, want)
- }
- if got, want := c1.contains(d2), true; got != want {
- t.Errorf("c1.contains(d2) got %v want %v", got, want)
- }
- if got, want := c1.contains(d3), true; got != want {
- t.Errorf("c1.contains(d3) got %v want %v", got, want)
- }
- if got, want := c1.contains(drop), false; got != want {
- t.Errorf("c1.contains(drop) got %v want %v", got, want)
- }
-
- // Drop all entries from one cache. The other will be allowed to grow.
- c1.Invalidate()
- c2.Add(NewNegativeDirent(""))
- if got, want := c2.Size(), uint64(maxSize); got != want {
- t.Errorf("c2.Size() got %v, want %v", got, want)
- }
-}
-
-// TestNilDirentCache tests that a nil cache supports all cache operations, but
-// treats them as noop.
-func TestNilDirentCache(t *testing.T) {
- // Create a nil cache.
- var c *DirentCache
-
- // Size is zero.
- if got, want := c.Size(), uint64(0); got != want {
- t.Errorf("c.Size() got %v, want %v", got, want)
- }
-
- // Call Add.
- c.Add(NewNegativeDirent(""))
-
- // Size is zero.
- if got, want := c.Size(), uint64(0); got != want {
- t.Errorf("c.Size() got %v, want %v", got, want)
- }
-
- // Call Remove.
- c.Remove(NewNegativeDirent(""))
-
- // Size is zero.
- if got, want := c.Size(), uint64(0); got != want {
- t.Errorf("c.Size() got %v, want %v", got, want)
- }
-
- // Call Invalidate.
- c.Invalidate()
-
- // Size is zero.
- if got, want := c.Size(), uint64(0); got != want {
- t.Errorf("c.Size() got %v, want %v", got, want)
- }
-}
diff --git a/pkg/sentry/fs/dirent_list.go b/pkg/sentry/fs/dirent_list.go
new file mode 100755
index 000000000..750961a48
--- /dev/null
+++ b/pkg/sentry/fs/dirent_list.go
@@ -0,0 +1,173 @@
+package fs
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type direntElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (direntElementMapper) linkerFor(elem *Dirent) *Dirent { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type direntList struct {
+ head *Dirent
+ tail *Dirent
+}
+
+// Reset resets list l to the empty state.
+func (l *direntList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *direntList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *direntList) Front() *Dirent {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *direntList) Back() *Dirent {
+ return l.tail
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *direntList) PushFront(e *Dirent) {
+ direntElementMapper{}.linkerFor(e).SetNext(l.head)
+ direntElementMapper{}.linkerFor(e).SetPrev(nil)
+
+ if l.head != nil {
+ direntElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *direntList) PushBack(e *Dirent) {
+ direntElementMapper{}.linkerFor(e).SetNext(nil)
+ direntElementMapper{}.linkerFor(e).SetPrev(l.tail)
+
+ if l.tail != nil {
+ direntElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *direntList) PushBackList(m *direntList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ direntElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ direntElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *direntList) InsertAfter(b, e *Dirent) {
+ a := direntElementMapper{}.linkerFor(b).Next()
+ direntElementMapper{}.linkerFor(e).SetNext(a)
+ direntElementMapper{}.linkerFor(e).SetPrev(b)
+ direntElementMapper{}.linkerFor(b).SetNext(e)
+
+ if a != nil {
+ direntElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *direntList) InsertBefore(a, e *Dirent) {
+ b := direntElementMapper{}.linkerFor(a).Prev()
+ direntElementMapper{}.linkerFor(e).SetNext(a)
+ direntElementMapper{}.linkerFor(e).SetPrev(b)
+ direntElementMapper{}.linkerFor(a).SetPrev(e)
+
+ if b != nil {
+ direntElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *direntList) Remove(e *Dirent) {
+ prev := direntElementMapper{}.linkerFor(e).Prev()
+ next := direntElementMapper{}.linkerFor(e).Next()
+
+ if prev != nil {
+ direntElementMapper{}.linkerFor(prev).SetNext(next)
+ } else {
+ l.head = next
+ }
+
+ if next != nil {
+ direntElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else {
+ l.tail = prev
+ }
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type direntEntry struct {
+ next *Dirent
+ prev *Dirent
+}
+
+// Next returns the entry that follows e in the list.
+func (e *direntEntry) Next() *Dirent {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *direntEntry) Prev() *Dirent {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *direntEntry) SetNext(elem *Dirent) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *direntEntry) SetPrev(elem *Dirent) {
+ e.prev = elem
+}
diff --git a/pkg/sentry/fs/dirent_refs_test.go b/pkg/sentry/fs/dirent_refs_test.go
deleted file mode 100644
index 47bc72a88..000000000
--- a/pkg/sentry/fs/dirent_refs_test.go
+++ /dev/null
@@ -1,418 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fs
-
-import (
- "syscall"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
-)
-
-func newMockDirInode(ctx context.Context, cache *DirentCache) *Inode {
- return NewMockInode(ctx, NewMockMountSource(cache), StableAttr{Type: Directory})
-}
-
-func TestWalkPositive(t *testing.T) {
- // refs == 0 -> one reference.
- // refs == -1 -> has been destroyed.
-
- ctx := contexttest.Context(t)
- root := NewDirent(ctx, newMockDirInode(ctx, nil), "root")
-
- if got := root.ReadRefs(); got != 1 {
- t.Fatalf("root has a ref count of %d, want %d", got, 1)
- }
-
- name := "d"
- d, err := root.walk(ctx, root, name, false)
- if err != nil {
- t.Fatalf("root.walk(root, %q) got %v, want nil", name, err)
- }
-
- if got := root.ReadRefs(); got != 2 {
- t.Fatalf("root has a ref count of %d, want %d", got, 2)
- }
-
- if got := d.ReadRefs(); got != 1 {
- t.Fatalf("child name = %q has a ref count of %d, want %d", d.name, got, 1)
- }
-
- d.DecRef()
-
- if got := root.ReadRefs(); got != 1 {
- t.Fatalf("root has a ref count of %d, want %d", got, 1)
- }
-
- if got := d.ReadRefs(); got != 0 {
- t.Fatalf("child name = %q has a ref count of %d, want %d", d.name, got, 0)
- }
-
- root.flush()
-
- if got := len(root.children); got != 0 {
- t.Fatalf("root has %d children, want %d", got, 0)
- }
-}
-
-func TestWalkNegative(t *testing.T) {
- // refs == 0 -> one reference.
- // refs == -1 -> has been destroyed.
-
- ctx := contexttest.Context(t)
- root := NewDirent(ctx, NewEmptyDir(ctx, nil), "root")
- mn := root.Inode.InodeOperations.(*mockInodeOperationsLookupNegative)
-
- if got := root.ReadRefs(); got != 1 {
- t.Fatalf("root has a ref count of %d, want %d", got, 1)
- }
-
- name := "d"
- for i := 0; i < 100; i++ {
- _, err := root.walk(ctx, root, name, false)
- if err != syscall.ENOENT {
- t.Fatalf("root.walk(root, %q) got %v, want %v", name, err, syscall.ENOENT)
- }
- }
-
- if got := root.ReadRefs(); got != 1 {
- t.Fatalf("root has a ref count of %d, want %d", got, 1)
- }
-
- if got := len(root.children); got != 1 {
- t.Fatalf("root has %d children, want %d", got, 1)
- }
-
- w, ok := root.children[name]
- if !ok {
- t.Fatalf("root wants child at %q", name)
- }
-
- child := w.Get()
- if child == nil {
- t.Fatalf("root wants to resolve weak reference")
- }
-
- if !child.(*Dirent).IsNegative() {
- t.Fatalf("root found positive child at %q, want negative", name)
- }
-
- if got := child.(*Dirent).ReadRefs(); got != 2 {
- t.Fatalf("child has a ref count of %d, want %d", got, 2)
- }
-
- child.DecRef()
-
- if got := child.(*Dirent).ReadRefs(); got != 1 {
- t.Fatalf("child has a ref count of %d, want %d", got, 1)
- }
-
- if got := len(root.children); got != 1 {
- t.Fatalf("root has %d children, want %d", got, 1)
- }
-
- root.DecRef()
-
- if got := root.ReadRefs(); got != 0 {
- t.Fatalf("root has a ref count of %d, want %d", got, 0)
- }
-
- AsyncBarrier()
-
- if got := mn.releaseCalled; got != true {
- t.Fatalf("root.Close was called %v, want true", got)
- }
-}
-
-type mockInodeOperationsLookupNegative struct {
- *MockInodeOperations
- releaseCalled bool
-}
-
-func NewEmptyDir(ctx context.Context, cache *DirentCache) *Inode {
- m := NewMockMountSource(cache)
- return NewInode(ctx, &mockInodeOperationsLookupNegative{
- MockInodeOperations: NewMockInodeOperations(ctx),
- }, m, StableAttr{Type: Directory})
-}
-
-func (m *mockInodeOperationsLookupNegative) Lookup(ctx context.Context, dir *Inode, p string) (*Dirent, error) {
- return NewNegativeDirent(p), nil
-}
-
-func (m *mockInodeOperationsLookupNegative) Release(context.Context) {
- m.releaseCalled = true
-}
-
-func TestHashNegativeToPositive(t *testing.T) {
- // refs == 0 -> one reference.
- // refs == -1 -> has been destroyed.
-
- ctx := contexttest.Context(t)
- root := NewDirent(ctx, NewEmptyDir(ctx, nil), "root")
-
- name := "d"
- _, err := root.walk(ctx, root, name, false)
- if err != syscall.ENOENT {
- t.Fatalf("root.walk(root, %q) got %v, want %v", name, err, syscall.ENOENT)
- }
-
- if got := root.exists(ctx, root, name); got != false {
- t.Fatalf("got %q exists, want does not exist", name)
- }
-
- f, err := root.Create(ctx, root, name, FileFlags{}, FilePermissions{})
- if err != nil {
- t.Fatalf("root.Create(%q, _), got error %v, want nil", name, err)
- }
- d := f.Dirent
-
- if d.IsNegative() {
- t.Fatalf("got negative Dirent, want positive")
- }
-
- if got := d.ReadRefs(); got != 1 {
- t.Fatalf("child %q has a ref count of %d, want %d", name, got, 1)
- }
-
- if got := root.ReadRefs(); got != 2 {
- t.Fatalf("root has a ref count of %d, want %d", got, 2)
- }
-
- if got := len(root.children); got != 1 {
- t.Fatalf("got %d children, want %d", got, 1)
- }
-
- w, ok := root.children[name]
- if !ok {
- t.Fatalf("failed to find weak reference to %q", name)
- }
-
- child := w.Get()
- if child == nil {
- t.Fatalf("want to resolve weak reference")
- }
-
- if child.(*Dirent) != d {
- t.Fatalf("got foreign child")
- }
-}
-
-func TestRevalidate(t *testing.T) {
- // refs == 0 -> one reference.
- // refs == -1 -> has been destroyed.
-
- for _, test := range []struct {
- // desc is the test's description.
- desc string
-
- // Whether to make negative Dirents.
- makeNegative bool
- }{
- {
- desc: "Revalidate negative Dirent",
- makeNegative: true,
- },
- {
- desc: "Revalidate positive Dirent",
- makeNegative: false,
- },
- } {
- t.Run(test.desc, func(t *testing.T) {
- ctx := contexttest.Context(t)
- root := NewDirent(ctx, NewMockInodeRevalidate(ctx, test.makeNegative), "root")
-
- name := "d"
- d1, err := root.walk(ctx, root, name, false)
- if !test.makeNegative && err != nil {
- t.Fatalf("root.walk(root, %q) got %v, want nil", name, err)
- }
- d2, err := root.walk(ctx, root, name, false)
- if !test.makeNegative && err != nil {
- t.Fatalf("root.walk(root, %q) got %v, want nil", name, err)
- }
- if !test.makeNegative && d1 == d2 {
- t.Fatalf("revalidating walk got same *Dirent, want different")
- }
- if got := len(root.children); got != 1 {
- t.Errorf("revalidating walk got %d children, want %d", got, 1)
- }
- })
- }
-}
-
-type MockInodeOperationsRevalidate struct {
- *MockInodeOperations
- makeNegative bool
-}
-
-func NewMockInodeRevalidate(ctx context.Context, makeNegative bool) *Inode {
- mn := NewMockInodeOperations(ctx)
- m := NewMockMountSource(nil)
- m.MountSourceOperations.(*MockMountSourceOps).revalidate = true
- return NewInode(ctx, &MockInodeOperationsRevalidate{MockInodeOperations: mn, makeNegative: makeNegative}, m, StableAttr{Type: Directory})
-}
-
-func (m *MockInodeOperationsRevalidate) Lookup(ctx context.Context, dir *Inode, p string) (*Dirent, error) {
- if !m.makeNegative {
- return m.MockInodeOperations.Lookup(ctx, dir, p)
- }
- return NewNegativeDirent(p), nil
-}
-
-func TestCreateExtraRefs(t *testing.T) {
- // refs == 0 -> one reference.
- // refs == -1 -> has been destroyed.
-
- ctx := contexttest.Context(t)
- for _, test := range []struct {
- // desc is the test's description.
- desc string
-
- // root is the Dirent to create from.
- root *Dirent
-
- // expected references on walked Dirent.
- refs int64
- }{
- {
- desc: "Create caching",
- root: NewDirent(ctx, NewEmptyDir(ctx, NewDirentCache(1)), "root"),
- refs: 2,
- },
- {
- desc: "Create not caching",
- root: NewDirent(ctx, NewEmptyDir(ctx, nil), "root"),
- refs: 1,
- },
- } {
- t.Run(test.desc, func(t *testing.T) {
- name := "d"
- f, err := test.root.Create(ctx, test.root, name, FileFlags{}, FilePermissions{})
- if err != nil {
- t.Fatalf("root.Create(root, %q) failed: %v", name, err)
- }
- d := f.Dirent
-
- if got := d.ReadRefs(); got != test.refs {
- t.Errorf("dirent has a ref count of %d, want %d", got, test.refs)
- }
- })
- }
-}
-
-func TestRemoveExtraRefs(t *testing.T) {
- // refs == 0 -> one reference.
- // refs == -1 -> has been destroyed.
-
- ctx := contexttest.Context(t)
- for _, test := range []struct {
- // desc is the test's description.
- desc string
-
- // root is the Dirent to make and remove from.
- root *Dirent
- }{
- {
- desc: "Remove caching",
- root: NewDirent(ctx, NewEmptyDir(ctx, NewDirentCache(1)), "root"),
- },
- {
- desc: "Remove not caching",
- root: NewDirent(ctx, NewEmptyDir(ctx, nil), "root"),
- },
- } {
- t.Run(test.desc, func(t *testing.T) {
- name := "d"
- f, err := test.root.Create(ctx, test.root, name, FileFlags{}, FilePermissions{})
- if err != nil {
- t.Fatalf("root.Create(%q, _) failed: %v", name, err)
- }
- d := f.Dirent
-
- if err := test.root.Remove(contexttest.Context(t), test.root, name, false /* dirPath */); err != nil {
- t.Fatalf("root.Remove(root, %q) failed: %v", name, err)
- }
-
- if got := d.ReadRefs(); got != 1 {
- t.Fatalf("dirent has a ref count of %d, want %d", got, 1)
- }
-
- d.DecRef()
-
- test.root.flush()
-
- if got := len(test.root.children); got != 0 {
- t.Errorf("root has %d children, want %d", got, 0)
- }
- })
- }
-}
-
-func TestRenameExtraRefs(t *testing.T) {
- // refs == 0 -> one reference.
- // refs == -1 -> has been destroyed.
-
- for _, test := range []struct {
- // desc is the test's description.
- desc string
-
- // cache of extra Dirent references, may be nil.
- cache *DirentCache
- }{
- {
- desc: "Rename no caching",
- cache: nil,
- },
- {
- desc: "Rename caching",
- cache: NewDirentCache(5),
- },
- } {
- t.Run(test.desc, func(t *testing.T) {
- ctx := contexttest.Context(t)
-
- dirAttr := StableAttr{Type: Directory}
-
- oldParent := NewDirent(ctx, NewMockInode(ctx, NewMockMountSource(test.cache), dirAttr), "old_parent")
- newParent := NewDirent(ctx, NewMockInode(ctx, NewMockMountSource(test.cache), dirAttr), "new_parent")
-
- renamed, err := oldParent.Walk(ctx, oldParent, "old_child")
- if err != nil {
- t.Fatalf("Walk(oldParent, %q) got error %v, want nil", "old_child", err)
- }
- replaced, err := newParent.Walk(ctx, oldParent, "new_child")
- if err != nil {
- t.Fatalf("Walk(newParent, %q) got error %v, want nil", "new_child", err)
- }
-
- if err := Rename(contexttest.RootContext(t), oldParent /*root */, oldParent, "old_child", newParent, "new_child"); err != nil {
- t.Fatalf("Rename got error %v, want nil", err)
- }
-
- oldParent.flush()
- newParent.flush()
-
- // Expect to have only active references.
- if got := renamed.ReadRefs(); got != 1 {
- t.Errorf("renamed has ref count %d, want only active references %d", got, 1)
- }
- if got := replaced.ReadRefs(); got != 1 {
- t.Errorf("replaced has ref count %d, want only active references %d", got, 1)
- }
- })
- }
-}
diff --git a/pkg/sentry/fs/event_list.go b/pkg/sentry/fs/event_list.go
new file mode 100755
index 000000000..c94cb03e1
--- /dev/null
+++ b/pkg/sentry/fs/event_list.go
@@ -0,0 +1,173 @@
+package fs
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type eventElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (eventElementMapper) linkerFor(elem *Event) *Event { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type eventList struct {
+ head *Event
+ tail *Event
+}
+
+// Reset resets list l to the empty state.
+func (l *eventList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *eventList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *eventList) Front() *Event {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *eventList) Back() *Event {
+ return l.tail
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *eventList) PushFront(e *Event) {
+ eventElementMapper{}.linkerFor(e).SetNext(l.head)
+ eventElementMapper{}.linkerFor(e).SetPrev(nil)
+
+ if l.head != nil {
+ eventElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *eventList) PushBack(e *Event) {
+ eventElementMapper{}.linkerFor(e).SetNext(nil)
+ eventElementMapper{}.linkerFor(e).SetPrev(l.tail)
+
+ if l.tail != nil {
+ eventElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *eventList) PushBackList(m *eventList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ eventElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ eventElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *eventList) InsertAfter(b, e *Event) {
+ a := eventElementMapper{}.linkerFor(b).Next()
+ eventElementMapper{}.linkerFor(e).SetNext(a)
+ eventElementMapper{}.linkerFor(e).SetPrev(b)
+ eventElementMapper{}.linkerFor(b).SetNext(e)
+
+ if a != nil {
+ eventElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *eventList) InsertBefore(a, e *Event) {
+ b := eventElementMapper{}.linkerFor(a).Prev()
+ eventElementMapper{}.linkerFor(e).SetNext(a)
+ eventElementMapper{}.linkerFor(e).SetPrev(b)
+ eventElementMapper{}.linkerFor(a).SetPrev(e)
+
+ if b != nil {
+ eventElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *eventList) Remove(e *Event) {
+ prev := eventElementMapper{}.linkerFor(e).Prev()
+ next := eventElementMapper{}.linkerFor(e).Next()
+
+ if prev != nil {
+ eventElementMapper{}.linkerFor(prev).SetNext(next)
+ } else {
+ l.head = next
+ }
+
+ if next != nil {
+ eventElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else {
+ l.tail = prev
+ }
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type eventEntry struct {
+ next *Event
+ prev *Event
+}
+
+// Next returns the entry that follows e in the list.
+func (e *eventEntry) Next() *Event {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *eventEntry) Prev() *Event {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *eventEntry) SetNext(elem *Event) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *eventEntry) SetPrev(elem *Event) {
+ e.prev = elem
+}
diff --git a/pkg/sentry/fs/fdpipe/BUILD b/pkg/sentry/fs/fdpipe/BUILD
deleted file mode 100644
index b9bd9ed17..000000000
--- a/pkg/sentry/fs/fdpipe/BUILD
+++ /dev/null
@@ -1,50 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "fdpipe",
- srcs = [
- "pipe.go",
- "pipe_opener.go",
- "pipe_state.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/fdpipe",
- imports = ["gvisor.dev/gvisor/pkg/sentry/fs"],
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/fd",
- "//pkg/fdnotifier",
- "//pkg/log",
- "//pkg/secio",
- "//pkg/sentry/context",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/safemem",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "fdpipe_test",
- size = "small",
- srcs = [
- "pipe_opener_test.go",
- "pipe_test.go",
- ],
- embed = [":fdpipe"],
- deps = [
- "//pkg/fd",
- "//pkg/fdnotifier",
- "//pkg/sentry/context",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/fs",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- "@com_github_google_uuid//:go_default_library",
- ],
-)
diff --git a/pkg/sentry/fs/fdpipe/fdpipe_state_autogen.go b/pkg/sentry/fs/fdpipe/fdpipe_state_autogen.go
new file mode 100755
index 000000000..38c1ed916
--- /dev/null
+++ b/pkg/sentry/fs/fdpipe/fdpipe_state_autogen.go
@@ -0,0 +1,27 @@
+// automatically generated by stateify.
+
+package fdpipe
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+ "gvisor.dev/gvisor/pkg/sentry/fs"
+)
+
+func (x *pipeOperations) save(m state.Map) {
+ x.beforeSave()
+ var flags fs.FileFlags = x.saveFlags()
+ m.SaveValue("flags", flags)
+ m.Save("opener", &x.opener)
+ m.Save("readAheadBuffer", &x.readAheadBuffer)
+}
+
+func (x *pipeOperations) load(m state.Map) {
+ m.LoadWait("opener", &x.opener)
+ m.Load("readAheadBuffer", &x.readAheadBuffer)
+ m.LoadValue("flags", new(fs.FileFlags), func(y interface{}) { x.loadFlags(y.(fs.FileFlags)) })
+ m.AfterLoad(x.afterLoad)
+}
+
+func init() {
+ state.Register("fdpipe.pipeOperations", (*pipeOperations)(nil), state.Fns{Save: (*pipeOperations).save, Load: (*pipeOperations).load})
+}
diff --git a/pkg/sentry/fs/fdpipe/pipe_opener_test.go b/pkg/sentry/fs/fdpipe/pipe_opener_test.go
deleted file mode 100644
index 8e4d839e1..000000000
--- a/pkg/sentry/fs/fdpipe/pipe_opener_test.go
+++ /dev/null
@@ -1,522 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fdpipe
-
-import (
- "bytes"
- "fmt"
- "io"
- "os"
- "path"
- "syscall"
- "testing"
- "time"
-
- "github.com/google/uuid"
- "gvisor.dev/gvisor/pkg/fd"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-type hostOpener struct {
- name string
-}
-
-func (h *hostOpener) NonBlockingOpen(_ context.Context, p fs.PermMask) (*fd.FD, error) {
- var flags int
- switch {
- case p.Read && p.Write:
- flags = syscall.O_RDWR
- case p.Write:
- flags = syscall.O_WRONLY
- case p.Read:
- flags = syscall.O_RDONLY
- default:
- return nil, syscall.EINVAL
- }
- f, err := syscall.Open(h.name, flags|syscall.O_NONBLOCK, 0666)
- if err != nil {
- return nil, err
- }
- return fd.New(f), nil
-}
-
-func pipename() string {
- return fmt.Sprintf(path.Join(os.TempDir(), "test-named-pipe-%s"), uuid.New())
-}
-
-func mkpipe(name string) error {
- return syscall.Mknod(name, syscall.S_IFIFO|0666, 0)
-}
-
-func TestTryOpen(t *testing.T) {
- for _, test := range []struct {
- // desc is the test's description.
- desc string
-
- // makePipe is true if the test case should create the pipe.
- makePipe bool
-
- // flags are the fs.FileFlags used to open the pipe.
- flags fs.FileFlags
-
- // expectFile is true if a fs.File is expected.
- expectFile bool
-
- // err is the expected error
- err error
- }{
- {
- desc: "FileFlags lacking Read and Write are invalid",
- makePipe: false,
- flags: fs.FileFlags{}, /* bogus */
- expectFile: false,
- err: syscall.EINVAL,
- },
- {
- desc: "NonBlocking Read only error returns immediately",
- makePipe: false, /* causes the error */
- flags: fs.FileFlags{Read: true, NonBlocking: true},
- expectFile: false,
- err: syscall.ENOENT,
- },
- {
- desc: "NonBlocking Read only success returns immediately",
- makePipe: true,
- flags: fs.FileFlags{Read: true, NonBlocking: true},
- expectFile: true,
- err: nil,
- },
- {
- desc: "NonBlocking Write only error returns immediately",
- makePipe: false, /* causes the error */
- flags: fs.FileFlags{Write: true, NonBlocking: true},
- expectFile: false,
- err: syscall.ENOENT,
- },
- {
- desc: "NonBlocking Write only no reader error returns immediately",
- makePipe: true,
- flags: fs.FileFlags{Write: true, NonBlocking: true},
- expectFile: false,
- err: syscall.ENXIO,
- },
- {
- desc: "ReadWrite error returns immediately",
- makePipe: false, /* causes the error */
- flags: fs.FileFlags{Read: true, Write: true},
- expectFile: false,
- err: syscall.ENOENT,
- },
- {
- desc: "ReadWrite returns immediately",
- makePipe: true,
- flags: fs.FileFlags{Read: true, Write: true},
- expectFile: true,
- err: nil,
- },
- {
- desc: "Blocking Write only returns open error",
- makePipe: false, /* causes the error */
- flags: fs.FileFlags{Write: true},
- expectFile: false,
- err: syscall.ENOENT, /* from bogus perms */
- },
- {
- desc: "Blocking Read only returns open error",
- makePipe: false, /* causes the error */
- flags: fs.FileFlags{Read: true},
- expectFile: false,
- err: syscall.ENOENT,
- },
- {
- desc: "Blocking Write only returns with syserror.ErrWouldBlock",
- makePipe: true,
- flags: fs.FileFlags{Write: true},
- expectFile: false,
- err: syserror.ErrWouldBlock,
- },
- {
- desc: "Blocking Read only returns with syserror.ErrWouldBlock",
- makePipe: true,
- flags: fs.FileFlags{Read: true},
- expectFile: false,
- err: syserror.ErrWouldBlock,
- },
- } {
- name := pipename()
- if test.makePipe {
- // Create the pipe. We do this per-test case to keep tests independent.
- if err := mkpipe(name); err != nil {
- t.Errorf("%s: failed to make host pipe: %v", test.desc, err)
- continue
- }
- defer syscall.Unlink(name)
- }
-
- // Use a host opener to keep things simple.
- opener := &hostOpener{name: name}
-
- pipeOpenState := &pipeOpenState{}
- ctx := contexttest.Context(t)
- pipeOps, err := pipeOpenState.TryOpen(ctx, opener, test.flags)
- if unwrapError(err) != test.err {
- t.Errorf("%s: got error %v, want %v", test.desc, err, test.err)
- if pipeOps != nil {
- // Cleanup the state of the pipe, and remove the fd from the
- // fdnotifier. Sadly this needed to maintain the correctness
- // of other tests because the fdnotifier is global.
- pipeOps.Release()
- }
- continue
- }
- if (pipeOps != nil) != test.expectFile {
- t.Errorf("%s: got non-nil file %v, want %v", test.desc, pipeOps != nil, test.expectFile)
- }
- if pipeOps != nil {
- // Same as above.
- pipeOps.Release()
- }
- }
-}
-
-func TestPipeOpenUnblocksEventually(t *testing.T) {
- for _, test := range []struct {
- // desc is the test's description.
- desc string
-
- // partnerIsReader is true if the goroutine opening the same pipe as the test case
- // should open the pipe read only. Otherwise write only. This also means that the
- // test case will open the pipe in the opposite way.
- partnerIsReader bool
-
- // partnerIsBlocking is true if the goroutine opening the same pipe as the test case
- // should do so without the O_NONBLOCK flag, otherwise opens the pipe with O_NONBLOCK
- // until ENXIO is not returned.
- partnerIsBlocking bool
- }{
- {
- desc: "Blocking Read with blocking writer partner opens eventually",
- partnerIsReader: false,
- partnerIsBlocking: true,
- },
- {
- desc: "Blocking Write with blocking reader partner opens eventually",
- partnerIsReader: true,
- partnerIsBlocking: true,
- },
- {
- desc: "Blocking Read with non-blocking writer partner opens eventually",
- partnerIsReader: false,
- partnerIsBlocking: false,
- },
- {
- desc: "Blocking Write with non-blocking reader partner opens eventually",
- partnerIsReader: true,
- partnerIsBlocking: false,
- },
- } {
- // Create the pipe. We do this per-test case to keep tests independent.
- name := pipename()
- if err := mkpipe(name); err != nil {
- t.Errorf("%s: failed to make host pipe: %v", test.desc, err)
- continue
- }
- defer syscall.Unlink(name)
-
- // Spawn the partner.
- type fderr struct {
- fd int
- err error
- }
- errch := make(chan fderr, 1)
- go func() {
- var flags int
- if test.partnerIsReader {
- flags = syscall.O_RDONLY
- } else {
- flags = syscall.O_WRONLY
- }
- if test.partnerIsBlocking {
- fd, err := syscall.Open(name, flags, 0666)
- errch <- fderr{fd: fd, err: err}
- } else {
- var fd int
- err := error(syscall.ENXIO)
- for err == syscall.ENXIO {
- fd, err = syscall.Open(name, flags|syscall.O_NONBLOCK, 0666)
- time.Sleep(1 * time.Second)
- }
- errch <- fderr{fd: fd, err: err}
- }
- }()
-
- // Setup file flags for either a read only or write only open.
- flags := fs.FileFlags{
- Read: !test.partnerIsReader,
- Write: test.partnerIsReader,
- }
-
- // Open the pipe in a blocking way, which should succeed eventually.
- opener := &hostOpener{name: name}
- ctx := contexttest.Context(t)
- pipeOps, err := Open(ctx, opener, flags)
- if pipeOps != nil {
- // Same as TestTryOpen.
- pipeOps.Release()
- }
-
- // Check that the partner opened the file successfully.
- e := <-errch
- if e.err != nil {
- t.Errorf("%s: partner got error %v, wanted nil", test.desc, e.err)
- continue
- }
- // If so, then close the partner fd to avoid leaking an fd.
- syscall.Close(e.fd)
-
- // Check that our blocking open was successful.
- if err != nil {
- t.Errorf("%s: blocking open got error %v, wanted nil", test.desc, err)
- continue
- }
- if pipeOps == nil {
- t.Errorf("%s: blocking open got nil file, wanted non-nil", test.desc)
- continue
- }
- }
-}
-
-func TestCopiedReadAheadBuffer(t *testing.T) {
- // Create the pipe.
- name := pipename()
- if err := mkpipe(name); err != nil {
- t.Fatalf("failed to make host pipe: %v", err)
- }
- defer syscall.Unlink(name)
-
- // We're taking advantage of the fact that pipes opened read only always return
- // success, but internally they are not deemed "opened" until we're sure that
- // another writer comes along. This means we can open the same pipe write only
- // with no problems + write to it, given that opener.Open already tried to open
- // the pipe RDONLY and succeeded, which we know happened if TryOpen returns
- // syserror.ErrwouldBlock.
- //
- // This simulates the open(RDONLY) <-> open(WRONLY)+write race we care about, but
- // does not cause our test to be racy (which would be terrible).
- opener := &hostOpener{name: name}
- pipeOpenState := &pipeOpenState{}
- ctx := contexttest.Context(t)
- pipeOps, err := pipeOpenState.TryOpen(ctx, opener, fs.FileFlags{Read: true})
- if pipeOps != nil {
- pipeOps.Release()
- t.Fatalf("open(%s, %o) got file, want nil", name, syscall.O_RDONLY)
- }
- if err != syserror.ErrWouldBlock {
- t.Fatalf("open(%s, %o) got error %v, want %v", name, syscall.O_RDONLY, err, syserror.ErrWouldBlock)
- }
-
- // Then open the same pipe write only and write some bytes to it. The next
- // time we try to open the pipe read only again via the pipeOpenState, we should
- // succeed and buffer some of the bytes written.
- fd, err := syscall.Open(name, syscall.O_WRONLY, 0666)
- if err != nil {
- t.Fatalf("open(%s, %o) got error %v, want nil", name, syscall.O_WRONLY, err)
- }
- defer syscall.Close(fd)
-
- data := []byte("hello")
- if n, err := syscall.Write(fd, data); n != len(data) || err != nil {
- t.Fatalf("write(%v) got (%d, %v), want (%d, nil)", data, n, err, len(data))
- }
-
- // Try the read again, knowing that it should succeed this time.
- pipeOps, err = pipeOpenState.TryOpen(ctx, opener, fs.FileFlags{Read: true})
- if pipeOps == nil {
- t.Fatalf("open(%s, %o) got nil file, want not nil", name, syscall.O_RDONLY)
- }
- defer pipeOps.Release()
-
- if err != nil {
- t.Fatalf("open(%s, %o) got error %v, want nil", name, syscall.O_RDONLY, err)
- }
-
- inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{
- Type: fs.Pipe,
- })
- file := fs.NewFile(ctx, fs.NewDirent(ctx, inode, "pipe"), fs.FileFlags{Read: true}, pipeOps)
-
- // Check that the file we opened points to a pipe with a non-empty read ahead buffer.
- bufsize := len(pipeOps.readAheadBuffer)
- if bufsize != 1 {
- t.Fatalf("read ahead buffer got %d bytes, want %d", bufsize, 1)
- }
-
- // Now for the final test, try to read everything in, expecting to get back all of
- // the bytes that were written at once. Note that in the wild there is no atomic
- // read size so expecting to get all bytes from a single writer when there are
- // multiple readers is a bad expectation.
- buf := make([]byte, len(data))
- ioseq := usermem.BytesIOSequence(buf)
- n, err := pipeOps.Read(ctx, file, ioseq, 0)
- if err != nil {
- t.Fatalf("read request got error %v, want nil", err)
- }
- if n != int64(len(data)) {
- t.Fatalf("read request got %d bytes, want %d", n, len(data))
- }
- if !bytes.Equal(buf, data) {
- t.Errorf("read request got bytes [%v], want [%v]", buf, data)
- }
-}
-
-func TestPipeHangup(t *testing.T) {
- for _, test := range []struct {
- // desc is the test's description.
- desc string
-
- // flags control how we open our end of the pipe and must be read
- // only or write only. They also dicate how a coordinating partner
- // fd is opened, which is their inverse (read only -> write only, etc).
- flags fs.FileFlags
-
- // hangupSelf if true causes the test case to close our end of the pipe
- // and causes hangup errors to be asserted on our coordinating partner's
- // fd. If hangupSelf is false, then our partner's fd is closed and the
- // hangup errors are expected on our end of the pipe.
- hangupSelf bool
- }{
- {
- desc: "Read only gets hangup error",
- flags: fs.FileFlags{Read: true},
- },
- {
- desc: "Write only gets hangup error",
- flags: fs.FileFlags{Write: true},
- },
- {
- desc: "Read only generates hangup error",
- flags: fs.FileFlags{Read: true},
- hangupSelf: true,
- },
- {
- desc: "Write only generates hangup error",
- flags: fs.FileFlags{Write: true},
- hangupSelf: true,
- },
- } {
- if test.flags.Read == test.flags.Write {
- t.Errorf("%s: test requires a single reader or writer", test.desc)
- continue
- }
-
- // Create the pipe. We do this per-test case to keep tests independent.
- name := pipename()
- if err := mkpipe(name); err != nil {
- t.Errorf("%s: failed to make host pipe: %v", test.desc, err)
- continue
- }
- defer syscall.Unlink(name)
-
- // Fire off a partner routine which tries to open the same pipe blocking,
- // which will synchronize with us. The channel allows us to get back the
- // fd once we expect this partner routine to succeed, so we can manifest
- // hangup events more directly.
- fdchan := make(chan int, 1)
- go func() {
- // Be explicit about the flags to protect the test from
- // misconfiguration.
- var flags int
- if test.flags.Read {
- flags = syscall.O_WRONLY
- } else {
- flags = syscall.O_RDONLY
- }
- fd, err := syscall.Open(name, flags, 0666)
- if err != nil {
- t.Logf("Open(%q, %o, 0666) partner failed: %v", name, flags, err)
- }
- fdchan <- fd
- }()
-
- // Open our end in a blocking way to ensure that we coordinate.
- opener := &hostOpener{name: name}
- ctx := contexttest.Context(t)
- pipeOps, err := Open(ctx, opener, test.flags)
- if err != nil {
- t.Errorf("%s: Open got error %v, want nil", test.desc, err)
- continue
- }
- // Don't defer file.DecRef here because that causes the hangup we're
- // trying to test for.
-
- // Expect the partner routine to have coordinated with us and get back
- // its open fd.
- f := <-fdchan
- if f < 0 {
- t.Errorf("%s: partner routine got fd %d, want > 0", test.desc, f)
- pipeOps.Release()
- continue
- }
-
- if test.hangupSelf {
- // Hangup self and assert that our partner got the expected hangup
- // error.
- pipeOps.Release()
-
- if test.flags.Read {
- // Partner is writer.
- assertWriterHungup(t, test.desc, fd.NewReadWriter(f))
- } else {
- // Partner is reader.
- assertReaderHungup(t, test.desc, fd.NewReadWriter(f))
- }
- } else {
- // Hangup our partner and expect us to get the hangup error.
- syscall.Close(f)
- defer pipeOps.Release()
-
- if test.flags.Read {
- assertReaderHungup(t, test.desc, pipeOps.(*pipeOperations).file)
- } else {
- assertWriterHungup(t, test.desc, pipeOps.(*pipeOperations).file)
- }
- }
- }
-}
-
-func assertReaderHungup(t *testing.T, desc string, reader io.Reader) bool {
- // Drain the pipe completely, it might have crap in it, but expect EOF eventually.
- var err error
- for err == nil {
- _, err = reader.Read(make([]byte, 10))
- }
- if err != io.EOF {
- t.Errorf("%s: read from self after hangup got error %v, want %v", desc, err, io.EOF)
- return false
- }
- return true
-}
-
-func assertWriterHungup(t *testing.T, desc string, writer io.Writer) bool {
- if _, err := writer.Write([]byte("hello")); unwrapError(err) != syscall.EPIPE {
- t.Errorf("%s: write to self after hangup got error %v, want %v", desc, err, syscall.EPIPE)
- return false
- }
- return true
-}
diff --git a/pkg/sentry/fs/fdpipe/pipe_test.go b/pkg/sentry/fs/fdpipe/pipe_test.go
deleted file mode 100644
index 69abc1e71..000000000
--- a/pkg/sentry/fs/fdpipe/pipe_test.go
+++ /dev/null
@@ -1,505 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fdpipe
-
-import (
- "bytes"
- "io"
- "os"
- "syscall"
- "testing"
-
- "gvisor.dev/gvisor/pkg/fd"
- "gvisor.dev/gvisor/pkg/fdnotifier"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-func singlePipeFD() (int, error) {
- fds := make([]int, 2)
- if err := syscall.Pipe(fds); err != nil {
- return -1, err
- }
- syscall.Close(fds[1])
- return fds[0], nil
-}
-
-func singleDirFD() (int, error) {
- return syscall.Open(os.TempDir(), syscall.O_RDONLY, 0666)
-}
-
-func mockPipeDirent(t *testing.T) *fs.Dirent {
- ctx := contexttest.Context(t)
- node := fs.NewMockInodeOperations(ctx)
- node.UAttr = fs.UnstableAttr{
- Perms: fs.FilePermissions{
- User: fs.PermMask{Read: true, Write: true},
- },
- }
- inode := fs.NewInode(ctx, node, fs.NewMockMountSource(nil), fs.StableAttr{
- Type: fs.Pipe,
- BlockSize: usermem.PageSize,
- })
- return fs.NewDirent(ctx, inode, "")
-}
-
-func TestNewPipe(t *testing.T) {
- for _, test := range []struct {
- // desc is the test's description.
- desc string
-
- // getfd generates the fd to pass to newPipeOperations.
- getfd func() (int, error)
-
- // flags are the fs.FileFlags passed to newPipeOperations.
- flags fs.FileFlags
-
- // readAheadBuffer is the buffer passed to newPipeOperations.
- readAheadBuffer []byte
-
- // err is the expected error.
- err error
- }{
- {
- desc: "Cannot make new pipe from bad fd",
- getfd: func() (int, error) { return -1, nil },
- err: syscall.EINVAL,
- },
- {
- desc: "Cannot make new pipe from non-pipe fd",
- getfd: singleDirFD,
- err: syscall.EINVAL,
- },
- {
- desc: "Can make new pipe from pipe fd",
- getfd: singlePipeFD,
- flags: fs.FileFlags{Read: true},
- readAheadBuffer: []byte("hello"),
- },
- } {
- gfd, err := test.getfd()
- if err != nil {
- t.Errorf("%s: getfd got (%d, %v), want (fd, nil)", test.desc, gfd, err)
- continue
- }
- f := fd.New(gfd)
-
- p, err := newPipeOperations(contexttest.Context(t), nil, test.flags, f, test.readAheadBuffer)
- if p != nil {
- // This is necessary to remove the fd from the global fd notifier.
- defer p.Release()
- } else {
- // If there is no p to DecRef on, because newPipeOperations failed, then the
- // file still needs to be closed.
- defer f.Close()
- }
-
- if err != test.err {
- t.Errorf("%s: got error %v, want %v", test.desc, err, test.err)
- continue
- }
- // Check the state of the pipe given that it was successfully opened.
- if err == nil {
- if p == nil {
- t.Errorf("%s: got nil pipe and nil error, want (pipe, nil)", test.desc)
- continue
- }
- if flags := p.flags; test.flags != flags {
- t.Errorf("%s: got file flags %s, want %s", test.desc, flags, test.flags)
- continue
- }
- if len(test.readAheadBuffer) != len(p.readAheadBuffer) {
- t.Errorf("%s: got read ahead buffer length %d, want %d", test.desc, len(p.readAheadBuffer), len(test.readAheadBuffer))
- continue
- }
- fileFlags, _, errno := syscall.Syscall(syscall.SYS_FCNTL, uintptr(p.file.FD()), syscall.F_GETFL, 0)
- if errno != 0 {
- t.Errorf("%s: failed to get file flags for fd %d, got %v, want 0", test.desc, p.file.FD(), errno)
- continue
- }
- if fileFlags&syscall.O_NONBLOCK == 0 {
- t.Errorf("%s: pipe is blocking, expected non-blocking", test.desc)
- continue
- }
- if !fdnotifier.HasFD(int32(f.FD())) {
- t.Errorf("%s: pipe fd %d is not registered for events", test.desc, f.FD)
- }
- }
- }
-}
-
-func TestPipeDestruction(t *testing.T) {
- fds := make([]int, 2)
- if err := syscall.Pipe(fds); err != nil {
- t.Fatalf("failed to create pipes: got %v, want nil", err)
- }
- f := fd.New(fds[0])
-
- // We don't care about the other end, just use the read end.
- syscall.Close(fds[1])
-
- // Test the read end, but it doesn't really matter which.
- p, err := newPipeOperations(contexttest.Context(t), nil, fs.FileFlags{Read: true}, f, nil)
- if err != nil {
- f.Close()
- t.Fatalf("newPipeOperations got error %v, want nil", err)
- }
- // Drop our only reference, which should trigger the destructor.
- p.Release()
-
- if fdnotifier.HasFD(int32(fds[0])) {
- t.Fatalf("after DecRef fdnotifier has fd %d, want no longer registered", fds[0])
- }
- if p.file != nil {
- t.Errorf("after DecRef got file, want nil")
- }
-}
-
-type Seek struct{}
-
-type ReadDir struct{}
-
-type Writev struct {
- Src usermem.IOSequence
-}
-
-type Readv struct {
- Dst usermem.IOSequence
-}
-
-type Fsync struct{}
-
-func TestPipeRequest(t *testing.T) {
- for _, test := range []struct {
- // desc is the test's description.
- desc string
-
- // request to execute.
- context interface{}
-
- // flags determines whether to use the read or write end
- // of the pipe, for this test it can only be Read or Write.
- flags fs.FileFlags
-
- // keepOpenPartner if false closes the other end of the pipe,
- // otherwise this is delayed until the end of the test.
- keepOpenPartner bool
-
- // expected error
- err error
- }{
- {
- desc: "ReadDir on pipe returns ENOTDIR",
- context: &ReadDir{},
- err: syscall.ENOTDIR,
- },
- {
- desc: "Fsync on pipe returns EINVAL",
- context: &Fsync{},
- err: syscall.EINVAL,
- },
- {
- desc: "Seek on pipe returns ESPIPE",
- context: &Seek{},
- err: syscall.ESPIPE,
- },
- {
- desc: "Readv on pipe from empty buffer returns nil",
- context: &Readv{Dst: usermem.BytesIOSequence(nil)},
- flags: fs.FileFlags{Read: true},
- },
- {
- desc: "Readv on pipe from non-empty buffer and closed partner returns EOF",
- context: &Readv{Dst: usermem.BytesIOSequence(make([]byte, 10))},
- flags: fs.FileFlags{Read: true},
- err: io.EOF,
- },
- {
- desc: "Readv on pipe from non-empty buffer and open partner returns EWOULDBLOCK",
- context: &Readv{Dst: usermem.BytesIOSequence(make([]byte, 10))},
- flags: fs.FileFlags{Read: true},
- keepOpenPartner: true,
- err: syserror.ErrWouldBlock,
- },
- {
- desc: "Writev on pipe from empty buffer returns nil",
- context: &Writev{Src: usermem.BytesIOSequence(nil)},
- flags: fs.FileFlags{Write: true},
- },
- {
- desc: "Writev on pipe from non-empty buffer and closed partner returns EPIPE",
- context: &Writev{Src: usermem.BytesIOSequence([]byte("hello"))},
- flags: fs.FileFlags{Write: true},
- err: syscall.EPIPE,
- },
- {
- desc: "Writev on pipe from non-empty buffer and open partner succeeds",
- context: &Writev{Src: usermem.BytesIOSequence([]byte("hello"))},
- flags: fs.FileFlags{Write: true},
- keepOpenPartner: true,
- },
- } {
- if test.flags.Read && test.flags.Write {
- panic("both read and write not supported for this test")
- }
-
- fds := make([]int, 2)
- if err := syscall.Pipe(fds); err != nil {
- t.Errorf("%s: failed to create pipes: got %v, want nil", test.desc, err)
- continue
- }
-
- // Configure the fd and partner fd based on the file flags.
- testFd, partnerFd := fds[0], fds[1]
- if test.flags.Write {
- testFd, partnerFd = fds[1], fds[0]
- }
-
- // Configure closing the fds.
- if test.keepOpenPartner {
- defer syscall.Close(partnerFd)
- } else {
- syscall.Close(partnerFd)
- }
-
- // Create the pipe.
- ctx := contexttest.Context(t)
- p, err := newPipeOperations(ctx, nil, test.flags, fd.New(testFd), nil)
- if err != nil {
- t.Fatalf("%s: newPipeOperations got error %v, want nil", test.desc, err)
- }
- defer p.Release()
-
- inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{Type: fs.Pipe})
- file := fs.NewFile(ctx, fs.NewDirent(ctx, inode, "pipe"), fs.FileFlags{Read: true}, p)
-
- // Issue request via the appropriate function.
- switch c := test.context.(type) {
- case *Seek:
- _, err = p.Seek(ctx, file, 0, 0)
- case *ReadDir:
- _, err = p.Readdir(ctx, file, nil)
- case *Readv:
- _, err = p.Read(ctx, file, c.Dst, 0)
- case *Writev:
- _, err = p.Write(ctx, file, c.Src, 0)
- case *Fsync:
- err = p.Fsync(ctx, file, 0, fs.FileMaxOffset, fs.SyncAll)
- default:
- t.Errorf("%s: unknown request type %T", test.desc, test.context)
- }
-
- if unwrapError(err) != test.err {
- t.Errorf("%s: got error %v, want %v", test.desc, err, test.err)
- }
- }
-}
-
-func TestPipeReadAheadBuffer(t *testing.T) {
- fds := make([]int, 2)
- if err := syscall.Pipe(fds); err != nil {
- t.Fatalf("failed to create pipes: got %v, want nil", err)
- }
- rfile := fd.New(fds[0])
-
- // Eventually close the write end, which is not wrapped in a pipe object.
- defer syscall.Close(fds[1])
-
- // Write some bytes to this end.
- data := []byte("world")
- if n, err := syscall.Write(fds[1], data); n != len(data) || err != nil {
- rfile.Close()
- t.Fatalf("write to pipe got (%d, %v), want (%d, nil)", n, err, len(data))
- }
- // Close the write end immediately, we don't care about it.
-
- buffered := []byte("hello ")
- ctx := contexttest.Context(t)
- p, err := newPipeOperations(ctx, nil, fs.FileFlags{Read: true}, rfile, buffered)
- if err != nil {
- rfile.Close()
- t.Fatalf("newPipeOperations got error %v, want nil", err)
- }
- defer p.Release()
-
- inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{
- Type: fs.Pipe,
- })
- file := fs.NewFile(ctx, fs.NewDirent(ctx, inode, "pipe"), fs.FileFlags{Read: true}, p)
-
- // In total we expect to read data + buffered.
- total := append(buffered, data...)
-
- buf := make([]byte, len(total))
- iov := usermem.BytesIOSequence(buf)
- n, err := p.Read(contexttest.Context(t), file, iov, 0)
- if err != nil {
- t.Fatalf("read request got error %v, want nil", err)
- }
- if n != int64(len(total)) {
- t.Fatalf("read request got %d bytes, want %d", n, len(total))
- }
- if !bytes.Equal(buf, total) {
- t.Errorf("read request got bytes [%v], want [%v]", buf, total)
- }
-}
-
-// This is very important for pipes in general because they can return
-// EWOULDBLOCK and for those that block they must continue until they have read
-// all of the data (and report it as such).
-func TestPipeReadsAccumulate(t *testing.T) {
- fds := make([]int, 2)
- if err := syscall.Pipe(fds); err != nil {
- t.Fatalf("failed to create pipes: got %v, want nil", err)
- }
- rfile := fd.New(fds[0])
-
- // Eventually close the write end, it doesn't depend on a pipe object.
- defer syscall.Close(fds[1])
-
- // Get a new read only pipe reference.
- ctx := contexttest.Context(t)
- p, err := newPipeOperations(ctx, nil, fs.FileFlags{Read: true}, rfile, nil)
- if err != nil {
- rfile.Close()
- t.Fatalf("newPipeOperations got error %v, want nil", err)
- }
- // Don't forget to remove the fd from the fd notifier. Otherwise other tests will
- // likely be borked, because it's global :(
- defer p.Release()
-
- inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{
- Type: fs.Pipe,
- })
- file := fs.NewFile(ctx, fs.NewDirent(ctx, inode, "pipe"), fs.FileFlags{Read: true}, p)
-
- // Write some some bytes to the pipe.
- data := []byte("some message")
- if n, err := syscall.Write(fds[1], data); n != len(data) || err != nil {
- t.Fatalf("write to pipe got (%d, %v), want (%d, nil)", n, err, len(data))
- }
-
- // Construct a segment vec that is a bit more than we have written so we
- // trigger an EWOULDBLOCK.
- wantBytes := len(data) + 1
- readBuffer := make([]byte, wantBytes)
- iov := usermem.BytesIOSequence(readBuffer)
- n, err := p.Read(ctx, file, iov, 0)
- total := n
- iov = iov.DropFirst64(n)
- if err != syserror.ErrWouldBlock {
- t.Fatalf("Readv got error %v, want %v", err, syserror.ErrWouldBlock)
- }
-
- // Write a few more bytes to allow us to read more/accumulate.
- extra := []byte("extra")
- if n, err := syscall.Write(fds[1], extra); n != len(extra) || err != nil {
- t.Fatalf("write to pipe got (%d, %v), want (%d, nil)", n, err, len(extra))
- }
-
- // This time, using the same request, we should not block.
- n, err = p.Read(ctx, file, iov, 0)
- total += n
- if err != nil {
- t.Fatalf("Readv got error %v, want nil", err)
- }
-
- // Assert that the result we got back is cumulative.
- if total != int64(wantBytes) {
- t.Fatalf("Readv sequence got %d bytes, want %d", total, wantBytes)
- }
-
- if want := append(data, extra[0]); !bytes.Equal(readBuffer, want) {
- t.Errorf("Readv sequence got %v, want %v", readBuffer, want)
- }
-}
-
-// Same as TestReadsAccumulate.
-func TestPipeWritesAccumulate(t *testing.T) {
- fds := make([]int, 2)
- if err := syscall.Pipe(fds); err != nil {
- t.Fatalf("failed to create pipes: got %v, want nil", err)
- }
- wfile := fd.New(fds[1])
-
- // Eventually close the read end, it doesn't depend on a pipe object.
- defer syscall.Close(fds[0])
-
- // Get a new write only pipe reference.
- ctx := contexttest.Context(t)
- p, err := newPipeOperations(ctx, nil, fs.FileFlags{Write: true}, wfile, nil)
- if err != nil {
- wfile.Close()
- t.Fatalf("newPipeOperations got error %v, want nil", err)
- }
- // Don't forget to remove the fd from the fd notifier. Otherwise other tests
- // will likely be borked, because it's global :(
- defer p.Release()
-
- inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{
- Type: fs.Pipe,
- })
- file := fs.NewFile(ctx, fs.NewDirent(ctx, inode, "pipe"), fs.FileFlags{Read: true}, p)
-
- pipeSize, _, errno := syscall.Syscall(syscall.SYS_FCNTL, uintptr(wfile.FD()), syscall.F_GETPIPE_SZ, 0)
- if errno != 0 {
- t.Fatalf("fcntl(F_GETPIPE_SZ) failed: %v", errno)
- }
- t.Logf("Pipe buffer size: %d", pipeSize)
-
- // Construct a segment vec that is larger than the pipe size to trigger an
- // EWOULDBLOCK.
- wantBytes := int(pipeSize) * 2
- writeBuffer := make([]byte, wantBytes)
- for i := 0; i < wantBytes; i++ {
- writeBuffer[i] = 'a'
- }
- iov := usermem.BytesIOSequence(writeBuffer)
- n, err := p.Write(ctx, file, iov, 0)
- if err != syserror.ErrWouldBlock {
- t.Fatalf("Writev got error %v, want %v", err, syserror.ErrWouldBlock)
- }
- if n != int64(pipeSize) {
- t.Fatalf("Writev partial write, got: %v, want %v", n, pipeSize)
- }
- total := n
- iov = iov.DropFirst64(n)
-
- // Read the entire pipe buf size to make space for the second half.
- readBuffer := make([]byte, n)
- if n, err := syscall.Read(fds[0], readBuffer); n != len(readBuffer) || err != nil {
- t.Fatalf("write to pipe got (%d, %v), want (%d, nil)", n, err, len(readBuffer))
- }
- if !bytes.Equal(readBuffer, writeBuffer[:len(readBuffer)]) {
- t.Fatalf("wrong data read from pipe, got: %v, want: %v", readBuffer, writeBuffer)
- }
-
- // This time we should not block.
- n, err = p.Write(ctx, file, iov, 0)
- if err != nil {
- t.Fatalf("Writev got error %v, want nil", err)
- }
- if n != int64(pipeSize) {
- t.Fatalf("Writev partial write, got: %v, want %v", n, pipeSize)
- }
- total += n
-
- // Assert that the result we got back is cumulative.
- if total != int64(wantBytes) {
- t.Fatalf("Writev sequence got %d bytes, want %d", total, wantBytes)
- }
-}
diff --git a/pkg/sentry/fs/file_overlay_test.go b/pkg/sentry/fs/file_overlay_test.go
deleted file mode 100644
index 2fb824d5c..000000000
--- a/pkg/sentry/fs/file_overlay_test.go
+++ /dev/null
@@ -1,275 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fs_test
-
-import (
- "reflect"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- "gvisor.dev/gvisor/pkg/sentry/fs/fsutil"
- "gvisor.dev/gvisor/pkg/sentry/fs/ramfs"
- "gvisor.dev/gvisor/pkg/sentry/kernel/contexttest"
-)
-
-func TestReaddir(t *testing.T) {
- ctx := contexttest.Context(t)
- ctx = &rootContext{
- Context: ctx,
- root: fs.NewDirent(ctx, newTestRamfsDir(ctx, nil, nil), "root"),
- }
- for _, test := range []struct {
- // Test description.
- desc string
-
- // Lookup parameters.
- dir *fs.Inode
-
- // Want from lookup.
- err error
- names []string
- }{
- {
- desc: "no upper, lower has entries",
- dir: fs.NewTestOverlayDir(ctx,
- nil, /* upper */
- newTestRamfsDir(ctx, []dirContent{
- {name: "a"},
- {name: "b"},
- }, nil), /* lower */
- false /* revalidate */),
- names: []string{".", "..", "a", "b"},
- },
- {
- desc: "upper has entries, no lower",
- dir: fs.NewTestOverlayDir(ctx,
- newTestRamfsDir(ctx, []dirContent{
- {name: "a"},
- {name: "b"},
- }, nil), /* upper */
- nil, /* lower */
- false /* revalidate */),
- names: []string{".", "..", "a", "b"},
- },
- {
- desc: "upper and lower, entries combine",
- dir: fs.NewTestOverlayDir(ctx,
- newTestRamfsDir(ctx, []dirContent{
- {name: "a"},
- }, nil), /* upper */
- newTestRamfsDir(ctx, []dirContent{
- {name: "b"},
- }, nil), /* lower */
- false /* revalidate */),
- names: []string{".", "..", "a", "b"},
- },
- {
- desc: "upper and lower, entries combine, none are masked",
- dir: fs.NewTestOverlayDir(ctx,
- newTestRamfsDir(ctx, []dirContent{
- {name: "a"},
- }, []string{"b"}), /* upper */
- newTestRamfsDir(ctx, []dirContent{
- {name: "c"},
- }, nil), /* lower */
- false /* revalidate */),
- names: []string{".", "..", "a", "c"},
- },
- {
- desc: "upper and lower, entries combine, upper masks some of lower",
- dir: fs.NewTestOverlayDir(ctx,
- newTestRamfsDir(ctx, []dirContent{
- {name: "a"},
- }, []string{"b"}), /* upper */
- newTestRamfsDir(ctx, []dirContent{
- {name: "b"}, /* will be masked */
- {name: "c"},
- }, nil), /* lower */
- false /* revalidate */),
- names: []string{".", "..", "a", "c"},
- },
- } {
- t.Run(test.desc, func(t *testing.T) {
- openDir, err := test.dir.GetFile(ctx, fs.NewDirent(ctx, test.dir, "stub"), fs.FileFlags{Read: true})
- if err != nil {
- t.Fatalf("GetFile got error %v, want nil", err)
- }
- stubSerializer := &fs.CollectEntriesSerializer{}
- err = openDir.Readdir(ctx, stubSerializer)
- if err != test.err {
- t.Fatalf("Readdir got error %v, want nil", err)
- }
- if err != nil {
- return
- }
- if !reflect.DeepEqual(stubSerializer.Order, test.names) {
- t.Errorf("Readdir got names %v, want %v", stubSerializer.Order, test.names)
- }
- })
- }
-}
-
-func TestReaddirRevalidation(t *testing.T) {
- ctx := contexttest.Context(t)
- ctx = &rootContext{
- Context: ctx,
- root: fs.NewDirent(ctx, newTestRamfsDir(ctx, nil, nil), "root"),
- }
-
- // Create an overlay with two directories, each with one file.
- upper := newTestRamfsDir(ctx, []dirContent{{name: "a"}}, nil)
- lower := newTestRamfsDir(ctx, []dirContent{{name: "b"}}, nil)
- overlay := fs.NewTestOverlayDir(ctx, upper, lower, true /* revalidate */)
-
- // Get a handle to the dirent in the upper filesystem so that we can
- // modify it without going through the dirent.
- upperDir := upper.InodeOperations.(*dir).InodeOperations.(*ramfs.Dir)
-
- // Check that overlay returns the files from both upper and lower.
- openDir, err := overlay.GetFile(ctx, fs.NewDirent(ctx, overlay, "stub"), fs.FileFlags{Read: true})
- if err != nil {
- t.Fatalf("GetFile got error %v, want nil", err)
- }
- ser := &fs.CollectEntriesSerializer{}
- if err := openDir.Readdir(ctx, ser); err != nil {
- t.Fatalf("Readdir got error %v, want nil", err)
- }
- got, want := ser.Order, []string{".", "..", "a", "b"}
- if !reflect.DeepEqual(got, want) {
- t.Errorf("Readdir got names %v, want %v", got, want)
- }
-
- // Remove "a" from the upper and add "c".
- if err := upperDir.Remove(ctx, upper, "a"); err != nil {
- t.Fatalf("error removing child: %v", err)
- }
- upperDir.AddChild(ctx, "c", fs.NewInode(ctx, fsutil.NewSimpleFileInode(ctx, fs.RootOwner, fs.FilePermissions{}, 0),
- upper.MountSource, fs.StableAttr{Type: fs.RegularFile}))
-
- // Seek to beginning of the directory and do the readdir again.
- if _, err := openDir.Seek(ctx, fs.SeekSet, 0); err != nil {
- t.Fatalf("error seeking to beginning of dir: %v", err)
- }
- ser = &fs.CollectEntriesSerializer{}
- if err := openDir.Readdir(ctx, ser); err != nil {
- t.Fatalf("Readdir got error %v, want nil", err)
- }
-
- // Readdir should return the updated children.
- got, want = ser.Order, []string{".", "..", "b", "c"}
- if !reflect.DeepEqual(got, want) {
- t.Errorf("Readdir got names %v, want %v", got, want)
- }
-}
-
-// TestReaddirOverlayFrozen tests that calling Readdir on an overlay file with
-// a frozen dirent tree does not make Readdir calls to the underlying files.
-func TestReaddirOverlayFrozen(t *testing.T) {
- ctx := contexttest.Context(t)
-
- // Create an overlay with two directories, each with two files.
- upper := newTestRamfsDir(ctx, []dirContent{{name: "upper-file1"}, {name: "upper-file2"}}, nil)
- lower := newTestRamfsDir(ctx, []dirContent{{name: "lower-file1"}, {name: "lower-file2"}}, nil)
- overlayInode := fs.NewTestOverlayDir(ctx, upper, lower, false)
-
- // Set that overlay as the root.
- root := fs.NewDirent(ctx, overlayInode, "root")
- ctx = &rootContext{
- Context: ctx,
- root: root,
- }
-
- // Check that calling Readdir on the root now returns all 4 files (2
- // from each layer in the overlay).
- rootFile, err := root.Inode.GetFile(ctx, root, fs.FileFlags{Read: true})
- if err != nil {
- t.Fatalf("root.Inode.GetFile failed: %v", err)
- }
- defer rootFile.DecRef()
- ser := &fs.CollectEntriesSerializer{}
- if err := rootFile.Readdir(ctx, ser); err != nil {
- t.Fatalf("rootFile.Readdir failed: %v", err)
- }
- if got, want := ser.Order, []string{".", "..", "lower-file1", "lower-file2", "upper-file1", "upper-file2"}; !reflect.DeepEqual(got, want) {
- t.Errorf("Readdir got names %v, want %v", got, want)
- }
-
- // Readdir should have been called on upper and lower.
- upperDir := upper.InodeOperations.(*dir)
- lowerDir := lower.InodeOperations.(*dir)
- if !upperDir.ReaddirCalled {
- t.Errorf("upperDir.ReaddirCalled got %v, want true", upperDir.ReaddirCalled)
- }
- if !lowerDir.ReaddirCalled {
- t.Errorf("lowerDir.ReaddirCalled got %v, want true", lowerDir.ReaddirCalled)
- }
-
- // Reset.
- upperDir.ReaddirCalled = false
- lowerDir.ReaddirCalled = false
-
- // Take references on "upper-file1" and "lower-file1", pinning them in
- // the dirent tree.
- for _, name := range []string{"upper-file1", "lower-file1"} {
- if _, err := root.Walk(ctx, root, name); err != nil {
- t.Fatalf("root.Walk(%q) failed: %v", name, err)
- }
- // Don't drop a reference on the returned dirent so that it
- // will stay in the tree.
- }
-
- // Freeze the dirent tree.
- root.Freeze()
-
- // Seek back to the beginning of the file.
- if _, err := rootFile.Seek(ctx, fs.SeekSet, 0); err != nil {
- t.Fatalf("error seeking to beginning of directory: %v", err)
- }
-
- // Calling Readdir on the root now will return only the pinned
- // children.
- ser = &fs.CollectEntriesSerializer{}
- if err := rootFile.Readdir(ctx, ser); err != nil {
- t.Fatalf("rootFile.Readdir failed: %v", err)
- }
- if got, want := ser.Order, []string{".", "..", "lower-file1", "upper-file1"}; !reflect.DeepEqual(got, want) {
- t.Errorf("Readdir got names %v, want %v", got, want)
- }
-
- // Readdir should NOT have been called on upper or lower.
- if upperDir.ReaddirCalled {
- t.Errorf("upperDir.ReaddirCalled got %v, want false", upperDir.ReaddirCalled)
- }
- if lowerDir.ReaddirCalled {
- t.Errorf("lowerDir.ReaddirCalled got %v, want false", lowerDir.ReaddirCalled)
- }
-}
-
-type rootContext struct {
- context.Context
- root *fs.Dirent
-}
-
-// Value implements context.Context.
-func (r *rootContext) Value(key interface{}) interface{} {
- switch key {
- case fs.CtxRoot:
- r.root.IncRef()
- return r.root
- default:
- return r.Context.Value(key)
- }
-}
diff --git a/pkg/sentry/fs/filetest/BUILD b/pkg/sentry/fs/filetest/BUILD
deleted file mode 100644
index a9d6d9301..000000000
--- a/pkg/sentry/fs/filetest/BUILD
+++ /dev/null
@@ -1,20 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "filetest",
- testonly = 1,
- srcs = ["filetest.go"],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/filetest",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/sentry/context",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/anon",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/usermem",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/fs/filetest/filetest.go b/pkg/sentry/fs/filetest/filetest.go
deleted file mode 100644
index 22270a494..000000000
--- a/pkg/sentry/fs/filetest/filetest.go
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package filetest provides a test implementation of an fs.File.
-package filetest
-
-import (
- "fmt"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- "gvisor.dev/gvisor/pkg/sentry/fs/anon"
- "gvisor.dev/gvisor/pkg/sentry/fs/fsutil"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-// TestFileOperations is an implementation of the File interface. It provides all
-// required methods.
-type TestFileOperations struct {
- fsutil.FileNoopRelease `state:"nosave"`
- fsutil.FilePipeSeek `state:"nosave"`
- fsutil.FileNotDirReaddir `state:"nosave"`
- fsutil.FileNoFsync `state:"nosave"`
- fsutil.FileNoopFlush `state:"nosave"`
- fsutil.FileNoMMap `state:"nosave"`
- fsutil.FileNoIoctl `state:"nosave"`
- fsutil.FileNoSplice `state:"nosave"`
- fsutil.FileUseInodeUnstableAttr `state:"nosave"`
- waiter.AlwaysReady `state:"nosave"`
-}
-
-// NewTestFile creates and initializes a new test file.
-func NewTestFile(tb testing.TB) *fs.File {
- ctx := contexttest.Context(tb)
- dirent := fs.NewDirent(ctx, anon.NewInode(ctx), "test")
- return fs.NewFile(ctx, dirent, fs.FileFlags{}, &TestFileOperations{})
-}
-
-// Read just fails the request.
-func (*TestFileOperations) Read(context.Context, *fs.File, usermem.IOSequence, int64) (int64, error) {
- return 0, fmt.Errorf("Readv not implemented")
-}
-
-// Write just fails the request.
-func (*TestFileOperations) Write(context.Context, *fs.File, usermem.IOSequence, int64) (int64, error) {
- return 0, fmt.Errorf("Writev not implemented")
-}
diff --git a/pkg/sentry/fs/fs_state_autogen.go b/pkg/sentry/fs/fs_state_autogen.go
new file mode 100755
index 000000000..5ea2669e6
--- /dev/null
+++ b/pkg/sentry/fs/fs_state_autogen.go
@@ -0,0 +1,632 @@
+// automatically generated by stateify.
+
+package fs
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *StableAttr) beforeSave() {}
+func (x *StableAttr) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Type", &x.Type)
+ m.Save("DeviceID", &x.DeviceID)
+ m.Save("InodeID", &x.InodeID)
+ m.Save("BlockSize", &x.BlockSize)
+ m.Save("DeviceFileMajor", &x.DeviceFileMajor)
+ m.Save("DeviceFileMinor", &x.DeviceFileMinor)
+}
+
+func (x *StableAttr) afterLoad() {}
+func (x *StableAttr) load(m state.Map) {
+ m.Load("Type", &x.Type)
+ m.Load("DeviceID", &x.DeviceID)
+ m.Load("InodeID", &x.InodeID)
+ m.Load("BlockSize", &x.BlockSize)
+ m.Load("DeviceFileMajor", &x.DeviceFileMajor)
+ m.Load("DeviceFileMinor", &x.DeviceFileMinor)
+}
+
+func (x *UnstableAttr) beforeSave() {}
+func (x *UnstableAttr) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Size", &x.Size)
+ m.Save("Usage", &x.Usage)
+ m.Save("Perms", &x.Perms)
+ m.Save("Owner", &x.Owner)
+ m.Save("AccessTime", &x.AccessTime)
+ m.Save("ModificationTime", &x.ModificationTime)
+ m.Save("StatusChangeTime", &x.StatusChangeTime)
+ m.Save("Links", &x.Links)
+}
+
+func (x *UnstableAttr) afterLoad() {}
+func (x *UnstableAttr) load(m state.Map) {
+ m.Load("Size", &x.Size)
+ m.Load("Usage", &x.Usage)
+ m.Load("Perms", &x.Perms)
+ m.Load("Owner", &x.Owner)
+ m.Load("AccessTime", &x.AccessTime)
+ m.Load("ModificationTime", &x.ModificationTime)
+ m.Load("StatusChangeTime", &x.StatusChangeTime)
+ m.Load("Links", &x.Links)
+}
+
+func (x *AttrMask) beforeSave() {}
+func (x *AttrMask) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Type", &x.Type)
+ m.Save("DeviceID", &x.DeviceID)
+ m.Save("InodeID", &x.InodeID)
+ m.Save("BlockSize", &x.BlockSize)
+ m.Save("Size", &x.Size)
+ m.Save("Usage", &x.Usage)
+ m.Save("Perms", &x.Perms)
+ m.Save("UID", &x.UID)
+ m.Save("GID", &x.GID)
+ m.Save("AccessTime", &x.AccessTime)
+ m.Save("ModificationTime", &x.ModificationTime)
+ m.Save("StatusChangeTime", &x.StatusChangeTime)
+ m.Save("Links", &x.Links)
+}
+
+func (x *AttrMask) afterLoad() {}
+func (x *AttrMask) load(m state.Map) {
+ m.Load("Type", &x.Type)
+ m.Load("DeviceID", &x.DeviceID)
+ m.Load("InodeID", &x.InodeID)
+ m.Load("BlockSize", &x.BlockSize)
+ m.Load("Size", &x.Size)
+ m.Load("Usage", &x.Usage)
+ m.Load("Perms", &x.Perms)
+ m.Load("UID", &x.UID)
+ m.Load("GID", &x.GID)
+ m.Load("AccessTime", &x.AccessTime)
+ m.Load("ModificationTime", &x.ModificationTime)
+ m.Load("StatusChangeTime", &x.StatusChangeTime)
+ m.Load("Links", &x.Links)
+}
+
+func (x *PermMask) beforeSave() {}
+func (x *PermMask) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Read", &x.Read)
+ m.Save("Write", &x.Write)
+ m.Save("Execute", &x.Execute)
+}
+
+func (x *PermMask) afterLoad() {}
+func (x *PermMask) load(m state.Map) {
+ m.Load("Read", &x.Read)
+ m.Load("Write", &x.Write)
+ m.Load("Execute", &x.Execute)
+}
+
+func (x *FilePermissions) beforeSave() {}
+func (x *FilePermissions) save(m state.Map) {
+ x.beforeSave()
+ m.Save("User", &x.User)
+ m.Save("Group", &x.Group)
+ m.Save("Other", &x.Other)
+ m.Save("Sticky", &x.Sticky)
+ m.Save("SetUID", &x.SetUID)
+ m.Save("SetGID", &x.SetGID)
+}
+
+func (x *FilePermissions) afterLoad() {}
+func (x *FilePermissions) load(m state.Map) {
+ m.Load("User", &x.User)
+ m.Load("Group", &x.Group)
+ m.Load("Other", &x.Other)
+ m.Load("Sticky", &x.Sticky)
+ m.Load("SetUID", &x.SetUID)
+ m.Load("SetGID", &x.SetGID)
+}
+
+func (x *FileOwner) beforeSave() {}
+func (x *FileOwner) save(m state.Map) {
+ x.beforeSave()
+ m.Save("UID", &x.UID)
+ m.Save("GID", &x.GID)
+}
+
+func (x *FileOwner) afterLoad() {}
+func (x *FileOwner) load(m state.Map) {
+ m.Load("UID", &x.UID)
+ m.Load("GID", &x.GID)
+}
+
+func (x *DentAttr) beforeSave() {}
+func (x *DentAttr) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Type", &x.Type)
+ m.Save("InodeID", &x.InodeID)
+}
+
+func (x *DentAttr) afterLoad() {}
+func (x *DentAttr) load(m state.Map) {
+ m.Load("Type", &x.Type)
+ m.Load("InodeID", &x.InodeID)
+}
+
+func (x *SortedDentryMap) beforeSave() {}
+func (x *SortedDentryMap) save(m state.Map) {
+ x.beforeSave()
+ m.Save("names", &x.names)
+ m.Save("entries", &x.entries)
+}
+
+func (x *SortedDentryMap) afterLoad() {}
+func (x *SortedDentryMap) load(m state.Map) {
+ m.Load("names", &x.names)
+ m.Load("entries", &x.entries)
+}
+
+func (x *Dirent) save(m state.Map) {
+ x.beforeSave()
+ var children map[string]*Dirent = x.saveChildren()
+ m.SaveValue("children", children)
+ m.Save("AtomicRefCount", &x.AtomicRefCount)
+ m.Save("userVisible", &x.userVisible)
+ m.Save("Inode", &x.Inode)
+ m.Save("name", &x.name)
+ m.Save("parent", &x.parent)
+ m.Save("deleted", &x.deleted)
+ m.Save("frozen", &x.frozen)
+ m.Save("mounted", &x.mounted)
+}
+
+func (x *Dirent) load(m state.Map) {
+ m.Load("AtomicRefCount", &x.AtomicRefCount)
+ m.Load("userVisible", &x.userVisible)
+ m.Load("Inode", &x.Inode)
+ m.Load("name", &x.name)
+ m.Load("parent", &x.parent)
+ m.Load("deleted", &x.deleted)
+ m.Load("frozen", &x.frozen)
+ m.Load("mounted", &x.mounted)
+ m.LoadValue("children", new(map[string]*Dirent), func(y interface{}) { x.loadChildren(y.(map[string]*Dirent)) })
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *DirentCache) beforeSave() {}
+func (x *DirentCache) save(m state.Map) {
+ x.beforeSave()
+ if !state.IsZeroValue(x.currentSize) { m.Failf("currentSize is %v, expected zero", x.currentSize) }
+ if !state.IsZeroValue(x.list) { m.Failf("list is %v, expected zero", x.list) }
+ m.Save("maxSize", &x.maxSize)
+ m.Save("limit", &x.limit)
+}
+
+func (x *DirentCache) afterLoad() {}
+func (x *DirentCache) load(m state.Map) {
+ m.Load("maxSize", &x.maxSize)
+ m.Load("limit", &x.limit)
+}
+
+func (x *DirentCacheLimiter) beforeSave() {}
+func (x *DirentCacheLimiter) save(m state.Map) {
+ x.beforeSave()
+ if !state.IsZeroValue(x.count) { m.Failf("count is %v, expected zero", x.count) }
+ m.Save("max", &x.max)
+}
+
+func (x *DirentCacheLimiter) afterLoad() {}
+func (x *DirentCacheLimiter) load(m state.Map) {
+ m.Load("max", &x.max)
+}
+
+func (x *direntList) beforeSave() {}
+func (x *direntList) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *direntList) afterLoad() {}
+func (x *direntList) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *direntEntry) beforeSave() {}
+func (x *direntEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *direntEntry) afterLoad() {}
+func (x *direntEntry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func (x *eventList) beforeSave() {}
+func (x *eventList) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *eventList) afterLoad() {}
+func (x *eventList) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *eventEntry) beforeSave() {}
+func (x *eventEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *eventEntry) afterLoad() {}
+func (x *eventEntry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func (x *File) save(m state.Map) {
+ x.beforeSave()
+ m.Save("AtomicRefCount", &x.AtomicRefCount)
+ m.Save("UniqueID", &x.UniqueID)
+ m.Save("Dirent", &x.Dirent)
+ m.Save("flags", &x.flags)
+ m.Save("async", &x.async)
+ m.Save("FileOperations", &x.FileOperations)
+ m.Save("offset", &x.offset)
+}
+
+func (x *File) load(m state.Map) {
+ m.Load("AtomicRefCount", &x.AtomicRefCount)
+ m.Load("UniqueID", &x.UniqueID)
+ m.Load("Dirent", &x.Dirent)
+ m.Load("flags", &x.flags)
+ m.Load("async", &x.async)
+ m.LoadWait("FileOperations", &x.FileOperations)
+ m.Load("offset", &x.offset)
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *overlayFileOperations) beforeSave() {}
+func (x *overlayFileOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("upper", &x.upper)
+ m.Save("lower", &x.lower)
+ m.Save("dirCursor", &x.dirCursor)
+}
+
+func (x *overlayFileOperations) afterLoad() {}
+func (x *overlayFileOperations) load(m state.Map) {
+ m.Load("upper", &x.upper)
+ m.Load("lower", &x.lower)
+ m.Load("dirCursor", &x.dirCursor)
+}
+
+func (x *overlayMappingIdentity) beforeSave() {}
+func (x *overlayMappingIdentity) save(m state.Map) {
+ x.beforeSave()
+ m.Save("AtomicRefCount", &x.AtomicRefCount)
+ m.Save("id", &x.id)
+ m.Save("overlayFile", &x.overlayFile)
+}
+
+func (x *overlayMappingIdentity) afterLoad() {}
+func (x *overlayMappingIdentity) load(m state.Map) {
+ m.Load("AtomicRefCount", &x.AtomicRefCount)
+ m.Load("id", &x.id)
+ m.Load("overlayFile", &x.overlayFile)
+}
+
+func (x *MountSourceFlags) beforeSave() {}
+func (x *MountSourceFlags) save(m state.Map) {
+ x.beforeSave()
+ m.Save("ReadOnly", &x.ReadOnly)
+ m.Save("NoAtime", &x.NoAtime)
+ m.Save("ForcePageCache", &x.ForcePageCache)
+ m.Save("NoExec", &x.NoExec)
+}
+
+func (x *MountSourceFlags) afterLoad() {}
+func (x *MountSourceFlags) load(m state.Map) {
+ m.Load("ReadOnly", &x.ReadOnly)
+ m.Load("NoAtime", &x.NoAtime)
+ m.Load("ForcePageCache", &x.ForcePageCache)
+ m.Load("NoExec", &x.NoExec)
+}
+
+func (x *FileFlags) beforeSave() {}
+func (x *FileFlags) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Direct", &x.Direct)
+ m.Save("NonBlocking", &x.NonBlocking)
+ m.Save("DSync", &x.DSync)
+ m.Save("Sync", &x.Sync)
+ m.Save("Append", &x.Append)
+ m.Save("Read", &x.Read)
+ m.Save("Write", &x.Write)
+ m.Save("Pread", &x.Pread)
+ m.Save("Pwrite", &x.Pwrite)
+ m.Save("Directory", &x.Directory)
+ m.Save("Async", &x.Async)
+ m.Save("LargeFile", &x.LargeFile)
+ m.Save("NonSeekable", &x.NonSeekable)
+}
+
+func (x *FileFlags) afterLoad() {}
+func (x *FileFlags) load(m state.Map) {
+ m.Load("Direct", &x.Direct)
+ m.Load("NonBlocking", &x.NonBlocking)
+ m.Load("DSync", &x.DSync)
+ m.Load("Sync", &x.Sync)
+ m.Load("Append", &x.Append)
+ m.Load("Read", &x.Read)
+ m.Load("Write", &x.Write)
+ m.Load("Pread", &x.Pread)
+ m.Load("Pwrite", &x.Pwrite)
+ m.Load("Directory", &x.Directory)
+ m.Load("Async", &x.Async)
+ m.Load("LargeFile", &x.LargeFile)
+ m.Load("NonSeekable", &x.NonSeekable)
+}
+
+func (x *Inode) beforeSave() {}
+func (x *Inode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("AtomicRefCount", &x.AtomicRefCount)
+ m.Save("InodeOperations", &x.InodeOperations)
+ m.Save("StableAttr", &x.StableAttr)
+ m.Save("LockCtx", &x.LockCtx)
+ m.Save("Watches", &x.Watches)
+ m.Save("MountSource", &x.MountSource)
+ m.Save("overlay", &x.overlay)
+}
+
+func (x *Inode) afterLoad() {}
+func (x *Inode) load(m state.Map) {
+ m.Load("AtomicRefCount", &x.AtomicRefCount)
+ m.Load("InodeOperations", &x.InodeOperations)
+ m.Load("StableAttr", &x.StableAttr)
+ m.Load("LockCtx", &x.LockCtx)
+ m.Load("Watches", &x.Watches)
+ m.Load("MountSource", &x.MountSource)
+ m.Load("overlay", &x.overlay)
+}
+
+func (x *LockCtx) beforeSave() {}
+func (x *LockCtx) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Posix", &x.Posix)
+ m.Save("BSD", &x.BSD)
+}
+
+func (x *LockCtx) afterLoad() {}
+func (x *LockCtx) load(m state.Map) {
+ m.Load("Posix", &x.Posix)
+ m.Load("BSD", &x.BSD)
+}
+
+func (x *Watches) beforeSave() {}
+func (x *Watches) save(m state.Map) {
+ x.beforeSave()
+ m.Save("ws", &x.ws)
+ m.Save("unlinked", &x.unlinked)
+}
+
+func (x *Watches) afterLoad() {}
+func (x *Watches) load(m state.Map) {
+ m.Load("ws", &x.ws)
+ m.Load("unlinked", &x.unlinked)
+}
+
+func (x *Inotify) beforeSave() {}
+func (x *Inotify) save(m state.Map) {
+ x.beforeSave()
+ m.Save("id", &x.id)
+ m.Save("events", &x.events)
+ m.Save("scratch", &x.scratch)
+ m.Save("nextWatch", &x.nextWatch)
+ m.Save("watches", &x.watches)
+}
+
+func (x *Inotify) afterLoad() {}
+func (x *Inotify) load(m state.Map) {
+ m.Load("id", &x.id)
+ m.Load("events", &x.events)
+ m.Load("scratch", &x.scratch)
+ m.Load("nextWatch", &x.nextWatch)
+ m.Load("watches", &x.watches)
+}
+
+func (x *Event) beforeSave() {}
+func (x *Event) save(m state.Map) {
+ x.beforeSave()
+ m.Save("eventEntry", &x.eventEntry)
+ m.Save("wd", &x.wd)
+ m.Save("mask", &x.mask)
+ m.Save("cookie", &x.cookie)
+ m.Save("len", &x.len)
+ m.Save("name", &x.name)
+}
+
+func (x *Event) afterLoad() {}
+func (x *Event) load(m state.Map) {
+ m.Load("eventEntry", &x.eventEntry)
+ m.Load("wd", &x.wd)
+ m.Load("mask", &x.mask)
+ m.Load("cookie", &x.cookie)
+ m.Load("len", &x.len)
+ m.Load("name", &x.name)
+}
+
+func (x *Watch) beforeSave() {}
+func (x *Watch) save(m state.Map) {
+ x.beforeSave()
+ m.Save("owner", &x.owner)
+ m.Save("wd", &x.wd)
+ m.Save("target", &x.target)
+ m.Save("unpinned", &x.unpinned)
+ m.Save("mask", &x.mask)
+ m.Save("pins", &x.pins)
+}
+
+func (x *Watch) afterLoad() {}
+func (x *Watch) load(m state.Map) {
+ m.Load("owner", &x.owner)
+ m.Load("wd", &x.wd)
+ m.Load("target", &x.target)
+ m.Load("unpinned", &x.unpinned)
+ m.Load("mask", &x.mask)
+ m.Load("pins", &x.pins)
+}
+
+func (x *MountSource) beforeSave() {}
+func (x *MountSource) save(m state.Map) {
+ x.beforeSave()
+ m.Save("AtomicRefCount", &x.AtomicRefCount)
+ m.Save("MountSourceOperations", &x.MountSourceOperations)
+ m.Save("FilesystemType", &x.FilesystemType)
+ m.Save("Flags", &x.Flags)
+ m.Save("fscache", &x.fscache)
+ m.Save("direntRefs", &x.direntRefs)
+}
+
+func (x *MountSource) afterLoad() {}
+func (x *MountSource) load(m state.Map) {
+ m.Load("AtomicRefCount", &x.AtomicRefCount)
+ m.Load("MountSourceOperations", &x.MountSourceOperations)
+ m.Load("FilesystemType", &x.FilesystemType)
+ m.Load("Flags", &x.Flags)
+ m.Load("fscache", &x.fscache)
+ m.Load("direntRefs", &x.direntRefs)
+}
+
+func (x *SimpleMountSourceOperations) beforeSave() {}
+func (x *SimpleMountSourceOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("keep", &x.keep)
+ m.Save("revalidate", &x.revalidate)
+ m.Save("cacheReaddir", &x.cacheReaddir)
+}
+
+func (x *SimpleMountSourceOperations) afterLoad() {}
+func (x *SimpleMountSourceOperations) load(m state.Map) {
+ m.Load("keep", &x.keep)
+ m.Load("revalidate", &x.revalidate)
+ m.Load("cacheReaddir", &x.cacheReaddir)
+}
+
+func (x *overlayMountSourceOperations) beforeSave() {}
+func (x *overlayMountSourceOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("upper", &x.upper)
+ m.Save("lower", &x.lower)
+}
+
+func (x *overlayMountSourceOperations) afterLoad() {}
+func (x *overlayMountSourceOperations) load(m state.Map) {
+ m.Load("upper", &x.upper)
+ m.Load("lower", &x.lower)
+}
+
+func (x *overlayFilesystem) beforeSave() {}
+func (x *overlayFilesystem) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *overlayFilesystem) afterLoad() {}
+func (x *overlayFilesystem) load(m state.Map) {
+}
+
+func (x *Mount) beforeSave() {}
+func (x *Mount) save(m state.Map) {
+ x.beforeSave()
+ m.Save("ID", &x.ID)
+ m.Save("ParentID", &x.ParentID)
+ m.Save("root", &x.root)
+ m.Save("previous", &x.previous)
+}
+
+func (x *Mount) afterLoad() {}
+func (x *Mount) load(m state.Map) {
+ m.Load("ID", &x.ID)
+ m.Load("ParentID", &x.ParentID)
+ m.Load("root", &x.root)
+ m.Load("previous", &x.previous)
+}
+
+func (x *MountNamespace) beforeSave() {}
+func (x *MountNamespace) save(m state.Map) {
+ x.beforeSave()
+ m.Save("AtomicRefCount", &x.AtomicRefCount)
+ m.Save("userns", &x.userns)
+ m.Save("root", &x.root)
+ m.Save("mounts", &x.mounts)
+ m.Save("mountID", &x.mountID)
+}
+
+func (x *MountNamespace) afterLoad() {}
+func (x *MountNamespace) load(m state.Map) {
+ m.Load("AtomicRefCount", &x.AtomicRefCount)
+ m.Load("userns", &x.userns)
+ m.Load("root", &x.root)
+ m.Load("mounts", &x.mounts)
+ m.Load("mountID", &x.mountID)
+}
+
+func (x *overlayEntry) beforeSave() {}
+func (x *overlayEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("lowerExists", &x.lowerExists)
+ m.Save("lower", &x.lower)
+ m.Save("mappings", &x.mappings)
+ m.Save("upper", &x.upper)
+ m.Save("dirCache", &x.dirCache)
+}
+
+func (x *overlayEntry) afterLoad() {}
+func (x *overlayEntry) load(m state.Map) {
+ m.Load("lowerExists", &x.lowerExists)
+ m.Load("lower", &x.lower)
+ m.Load("mappings", &x.mappings)
+ m.Load("upper", &x.upper)
+ m.Load("dirCache", &x.dirCache)
+}
+
+func init() {
+ state.Register("fs.StableAttr", (*StableAttr)(nil), state.Fns{Save: (*StableAttr).save, Load: (*StableAttr).load})
+ state.Register("fs.UnstableAttr", (*UnstableAttr)(nil), state.Fns{Save: (*UnstableAttr).save, Load: (*UnstableAttr).load})
+ state.Register("fs.AttrMask", (*AttrMask)(nil), state.Fns{Save: (*AttrMask).save, Load: (*AttrMask).load})
+ state.Register("fs.PermMask", (*PermMask)(nil), state.Fns{Save: (*PermMask).save, Load: (*PermMask).load})
+ state.Register("fs.FilePermissions", (*FilePermissions)(nil), state.Fns{Save: (*FilePermissions).save, Load: (*FilePermissions).load})
+ state.Register("fs.FileOwner", (*FileOwner)(nil), state.Fns{Save: (*FileOwner).save, Load: (*FileOwner).load})
+ state.Register("fs.DentAttr", (*DentAttr)(nil), state.Fns{Save: (*DentAttr).save, Load: (*DentAttr).load})
+ state.Register("fs.SortedDentryMap", (*SortedDentryMap)(nil), state.Fns{Save: (*SortedDentryMap).save, Load: (*SortedDentryMap).load})
+ state.Register("fs.Dirent", (*Dirent)(nil), state.Fns{Save: (*Dirent).save, Load: (*Dirent).load})
+ state.Register("fs.DirentCache", (*DirentCache)(nil), state.Fns{Save: (*DirentCache).save, Load: (*DirentCache).load})
+ state.Register("fs.DirentCacheLimiter", (*DirentCacheLimiter)(nil), state.Fns{Save: (*DirentCacheLimiter).save, Load: (*DirentCacheLimiter).load})
+ state.Register("fs.direntList", (*direntList)(nil), state.Fns{Save: (*direntList).save, Load: (*direntList).load})
+ state.Register("fs.direntEntry", (*direntEntry)(nil), state.Fns{Save: (*direntEntry).save, Load: (*direntEntry).load})
+ state.Register("fs.eventList", (*eventList)(nil), state.Fns{Save: (*eventList).save, Load: (*eventList).load})
+ state.Register("fs.eventEntry", (*eventEntry)(nil), state.Fns{Save: (*eventEntry).save, Load: (*eventEntry).load})
+ state.Register("fs.File", (*File)(nil), state.Fns{Save: (*File).save, Load: (*File).load})
+ state.Register("fs.overlayFileOperations", (*overlayFileOperations)(nil), state.Fns{Save: (*overlayFileOperations).save, Load: (*overlayFileOperations).load})
+ state.Register("fs.overlayMappingIdentity", (*overlayMappingIdentity)(nil), state.Fns{Save: (*overlayMappingIdentity).save, Load: (*overlayMappingIdentity).load})
+ state.Register("fs.MountSourceFlags", (*MountSourceFlags)(nil), state.Fns{Save: (*MountSourceFlags).save, Load: (*MountSourceFlags).load})
+ state.Register("fs.FileFlags", (*FileFlags)(nil), state.Fns{Save: (*FileFlags).save, Load: (*FileFlags).load})
+ state.Register("fs.Inode", (*Inode)(nil), state.Fns{Save: (*Inode).save, Load: (*Inode).load})
+ state.Register("fs.LockCtx", (*LockCtx)(nil), state.Fns{Save: (*LockCtx).save, Load: (*LockCtx).load})
+ state.Register("fs.Watches", (*Watches)(nil), state.Fns{Save: (*Watches).save, Load: (*Watches).load})
+ state.Register("fs.Inotify", (*Inotify)(nil), state.Fns{Save: (*Inotify).save, Load: (*Inotify).load})
+ state.Register("fs.Event", (*Event)(nil), state.Fns{Save: (*Event).save, Load: (*Event).load})
+ state.Register("fs.Watch", (*Watch)(nil), state.Fns{Save: (*Watch).save, Load: (*Watch).load})
+ state.Register("fs.MountSource", (*MountSource)(nil), state.Fns{Save: (*MountSource).save, Load: (*MountSource).load})
+ state.Register("fs.SimpleMountSourceOperations", (*SimpleMountSourceOperations)(nil), state.Fns{Save: (*SimpleMountSourceOperations).save, Load: (*SimpleMountSourceOperations).load})
+ state.Register("fs.overlayMountSourceOperations", (*overlayMountSourceOperations)(nil), state.Fns{Save: (*overlayMountSourceOperations).save, Load: (*overlayMountSourceOperations).load})
+ state.Register("fs.overlayFilesystem", (*overlayFilesystem)(nil), state.Fns{Save: (*overlayFilesystem).save, Load: (*overlayFilesystem).load})
+ state.Register("fs.Mount", (*Mount)(nil), state.Fns{Save: (*Mount).save, Load: (*Mount).load})
+ state.Register("fs.MountNamespace", (*MountNamespace)(nil), state.Fns{Save: (*MountNamespace).save, Load: (*MountNamespace).load})
+ state.Register("fs.overlayEntry", (*overlayEntry)(nil), state.Fns{Save: (*overlayEntry).save, Load: (*overlayEntry).load})
+}
diff --git a/pkg/sentry/fs/fsutil/BUILD b/pkg/sentry/fs/fsutil/BUILD
deleted file mode 100644
index b4ac83dc4..000000000
--- a/pkg/sentry/fs/fsutil/BUILD
+++ /dev/null
@@ -1,120 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_template_instance(
- name = "dirty_set_impl",
- out = "dirty_set_impl.go",
- imports = {
- "memmap": "gvisor.dev/gvisor/pkg/sentry/memmap",
- "platform": "gvisor.dev/gvisor/pkg/sentry/platform",
- },
- package = "fsutil",
- prefix = "Dirty",
- template = "//pkg/segment:generic_set",
- types = {
- "Key": "uint64",
- "Range": "memmap.MappableRange",
- "Value": "DirtyInfo",
- "Functions": "dirtySetFunctions",
- },
-)
-
-go_template_instance(
- name = "frame_ref_set_impl",
- out = "frame_ref_set_impl.go",
- imports = {
- "platform": "gvisor.dev/gvisor/pkg/sentry/platform",
- },
- package = "fsutil",
- prefix = "frameRef",
- template = "//pkg/segment:generic_set",
- types = {
- "Key": "uint64",
- "Range": "platform.FileRange",
- "Value": "uint64",
- "Functions": "frameRefSetFunctions",
- },
-)
-
-go_template_instance(
- name = "file_range_set_impl",
- out = "file_range_set_impl.go",
- imports = {
- "memmap": "gvisor.dev/gvisor/pkg/sentry/memmap",
- "platform": "gvisor.dev/gvisor/pkg/sentry/platform",
- },
- package = "fsutil",
- prefix = "FileRange",
- template = "//pkg/segment:generic_set",
- types = {
- "Key": "uint64",
- "Range": "memmap.MappableRange",
- "Value": "uint64",
- "Functions": "fileRangeSetFunctions",
- },
-)
-
-go_library(
- name = "fsutil",
- srcs = [
- "dirty_set.go",
- "dirty_set_impl.go",
- "file.go",
- "file_range_set.go",
- "file_range_set_impl.go",
- "frame_ref_set.go",
- "frame_ref_set_impl.go",
- "fsutil.go",
- "host_file_mapper.go",
- "host_file_mapper_state.go",
- "host_file_mapper_unsafe.go",
- "host_mappable.go",
- "inode.go",
- "inode_cached.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/fsutil",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/log",
- "//pkg/sentry/arch",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/memmap",
- "//pkg/sentry/pgalloc",
- "//pkg/sentry/platform",
- "//pkg/sentry/safemem",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/usage",
- "//pkg/sentry/usermem",
- "//pkg/state",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "fsutil_test",
- size = "small",
- srcs = [
- "dirty_set_test.go",
- "inode_cached_test.go",
- ],
- embed = [":fsutil"],
- deps = [
- "//pkg/sentry/context",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/fs",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/memmap",
- "//pkg/sentry/safemem",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- ],
-)
diff --git a/pkg/sentry/fs/fsutil/README.md b/pkg/sentry/fs/fsutil/README.md
deleted file mode 100644
index 8be367334..000000000
--- a/pkg/sentry/fs/fsutil/README.md
+++ /dev/null
@@ -1,207 +0,0 @@
-This package provides utilities for implementing virtual filesystem objects.
-
-[TOC]
-
-## Page cache
-
-`CachingInodeOperations` implements a page cache for files that cannot use the
-host page cache. Normally these are files that store their data in a remote
-filesystem. This also applies to files that are accessed on a platform that does
-not support directly memory mapping host file descriptors (e.g. the ptrace
-platform).
-
-An `CachingInodeOperations` buffers regions of a single file into memory. It is
-owned by an `fs.Inode`, the in-memory representation of a file (all open file
-descriptors are backed by an `fs.Inode`). The `fs.Inode` provides operations for
-reading memory into an `CachingInodeOperations`, to represent the contents of
-the file in-memory, and for writing memory out, to relieve memory pressure on
-the kernel and to synchronize in-memory changes to filesystems.
-
-An `CachingInodeOperations` enables readable and/or writable memory access to
-file content. Files can be mapped shared or private, see mmap(2). When a file is
-mapped shared, changes to the file via write(2) and truncate(2) are reflected in
-the shared memory region. Conversely, when the shared memory region is modified,
-changes to the file are visible via read(2). Multiple shared mappings of the
-same file are coherent with each other. This is consistent with Linux.
-
-When a file is mapped private, updates to the mapped memory are not visible to
-other memory mappings. Updates to the mapped memory are also not reflected in
-the file content as seen by read(2). If the file is changed after a private
-mapping is created, for instance by write(2), the change to the file may or may
-not be reflected in the private mapping. This is consistent with Linux.
-
-An `CachingInodeOperations` keeps track of ranges of memory that were modified
-(or "dirtied"). When the file is explicitly synced via fsync(2), only the dirty
-ranges are written out to the filesystem. Any error returned indicates a failure
-to write all dirty memory of an `CachingInodeOperations` to the filesystem. In
-this case the filesystem may be in an inconsistent state. The same operation can
-be performed on the shared memory itself using msync(2). If neither fsync(2) nor
-msync(2) is performed, then the dirty memory is written out in accordance with
-the `CachingInodeOperations` eviction strategy (see below) and there is no
-guarantee that memory will be written out successfully in full.
-
-### Memory allocation and eviction
-
-An `CachingInodeOperations` implements the following allocation and eviction
-strategy:
-
-- Memory is allocated and brought up to date with the contents of a file when
- a region of mapped memory is accessed (or "faulted on").
-
-- Dirty memory is written out to filesystems when an fsync(2) or msync(2)
- operation is performed on a memory mapped file, for all memory mapped files
- when saved, and/or when there are no longer any memory mappings of a range
- of a file, see munmap(2). As the latter implies, in the absence of a panic
- or SIGKILL, dirty memory is written out for all memory mapped files when an
- application exits.
-
-- Memory is freed when there are no longer any memory mappings of a range of a
- file (e.g. when an application exits). This behavior is consistent with
- Linux for shared memory that has been locked via mlock(2).
-
-Notably, memory is not allocated for read(2) or write(2) operations. This means
-that reads and writes to the file are only accelerated by an
-`CachingInodeOperations` if the file being read or written has been memory
-mapped *and* if the shared memory has been accessed at the region being read or
-written. This diverges from Linux which buffers memory into a page cache on
-read(2) proactively (i.e. readahead) and delays writing it out to filesystems on
-write(2) (i.e. writeback). The absence of these optimizations is not visible to
-applications beyond less than optimal performance when repeatedly reading and/or
-writing to same region of a file. See [Future Work](#future-work) for plans to
-implement these optimizations.
-
-Additionally, memory held by `CachingInodeOperationss` is currently unbounded in
-size. An `CachingInodeOperations` does not write out dirty memory and free it
-under system memory pressure. This can cause pathological memory usage.
-
-When memory is written back, an `CachingInodeOperations` may write regions of
-shared memory that were never modified. This is due to the strategy of
-minimizing page faults (see below) and handling only a subset of memory write
-faults. In the absence of an application or sentry crash, it is guaranteed that
-if a region of shared memory was written to, it is written back to a filesystem.
-
-### Life of a shared memory mapping
-
-A file is memory mapped via mmap(2). For example, if `A` is an address, an
-application may execute:
-
-```
-mmap(A, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
-```
-
-This creates a shared mapping of fd that reflects 4k of the contents of fd
-starting at offset 0, accessible at address `A`. This in turn creates a virtual
-memory area region ("vma") which indicates that [`A`, `A`+0x1000) is now a valid
-address range for this application to access.
-
-At this point, memory has not been allocated in the file's
-`CachingInodeOperations`. It is also the case that the address range [`A`,
-`A`+0x1000) has not been mapped on the host on behalf of the application. If the
-application then tries to modify 8 bytes of the shared memory:
-
-```
-char buffer[] = "aaaaaaaa";
-memcpy(A, buffer, 8);
-```
-
-The host then sends a `SIGSEGV` to the sentry because the address range [`A`,
-`A`+8) is not mapped on the host. The `SIGSEGV` indicates that the memory was
-accessed writable. The sentry looks up the vma associated with [`A`, `A`+8),
-finds the file that was mapped and its `CachingInodeOperations`. It then calls
-`CachingInodeOperations.Translate` which allocates memory to back [`A`, `A`+8).
-It may choose to allocate more memory (i.e. do "readahead") to minimize
-subsequent faults.
-
-Memory that is allocated comes from a host tmpfs file (see
-`pgalloc.MemoryFile`). The host tmpfs file memory is brought up to date with the
-contents of the mapped file on its filesystem. The region of the host tmpfs file
-that reflects the mapped file is then mapped into the host address space of the
-application so that subsequent memory accesses do not repeatedly generate a
-`SIGSEGV`.
-
-The range that was allocated, including any extra memory allocation to minimize
-faults, is marked dirty due to the write fault. This overcounts dirty memory if
-the extra memory allocated is never modified.
-
-To make the scenario more interesting, imagine that this application spawns
-another process and maps the same file in the exact same way:
-
-```
-mmap(A, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
-```
-
-Imagine that this process then tries to modify the file again but with only 4
-bytes:
-
-```
-char buffer[] = "bbbb";
-memcpy(A, buffer, 4);
-```
-
-Since the first process has already mapped and accessed the same region of the
-file writable, `CachingInodeOperations.Translate` is called but returns the
-memory that has already been allocated rather than allocating new memory. The
-address range [`A`, `A`+0x1000) reflects the same cached view of the file as the
-first process sees. For example, reading 8 bytes from the file from either
-process via read(2) starting at offset 0 returns a consistent "bbbbaaaa".
-
-When this process no longer needs the shared memory, it may do:
-
-```
-munmap(A, 0x1000);
-```
-
-At this point, the modified memory cached by the `CachingInodeOperations` is not
-written back to the file because it is still in use by the first process that
-mapped it. When the first process also does:
-
-```
-munmap(A, 0x1000);
-```
-
-Then the last memory mapping of the file at the range [0, 0x1000) is gone. The
-file's `CachingInodeOperations` then starts writing back memory marked dirty to
-the file on its filesystem. Once writing completes, regardless of whether it was
-successful, the `CachingInodeOperations` frees the memory cached at the range
-[0, 0x1000).
-
-Subsequent read(2) or write(2) operations on the file go directly to the
-filesystem since there no longer exists memory for it in its
-`CachingInodeOperations`.
-
-## Future Work
-
-### Page cache
-
-The sentry does not yet implement the readahead and writeback optimizations for
-read(2) and write(2) respectively. To do so, on read(2) and/or write(2) the
-sentry must ensure that memory is allocated in a page cache to read or write
-into. However, the sentry cannot boundlessly allocate memory. If it did, the
-host would eventually OOM-kill the sentry+application process. This means that
-the sentry must implement a page cache memory allocation strategy that is
-bounded by a global user or container imposed limit. When this limit is
-approached, the sentry must decide from which page cache memory should be freed
-so that it can allocate more memory. If it makes a poor decision, the sentry may
-end up freeing and re-allocating memory to back regions of files that are
-frequently used, nullifying the optimization (and in some cases causing worse
-performance due to the overhead of memory allocation and general management).
-This is a form of "cache thrashing".
-
-In Linux, much research has been done to select and implement a lightweight but
-optimal page cache eviction algorithm. Linux makes use of hardware page bits to
-keep track of whether memory has been accessed. The sentry does not have direct
-access to hardware. Implementing a similarly lightweight and optimal page cache
-eviction algorithm will need to either introduce a kernel interface to obtain
-these page bits or find a suitable alternative proxy for access events.
-
-In Linux, readahead happens by default but is not always ideal. For instance,
-for files that are not read sequentially, it would be more ideal to simply read
-from only those regions of the file rather than to optimistically cache some
-number of bytes ahead of the read (up to 2MB in Linux) if the bytes cached won't
-be accessed. Linux implements the fadvise64(2) system call for applications to
-specify that a range of a file will not be accessed sequentially. The advice bit
-FADV_RANDOM turns off the readahead optimization for the given range in the
-given file. However fadvise64 is rarely used by applications so Linux implements
-a readahead backoff strategy if reads are not sequential. To ensure that
-application performance is not degraded, the sentry must implement a similar
-backoff strategy.
diff --git a/pkg/sentry/fs/fsutil/dirty_set_impl.go b/pkg/sentry/fs/fsutil/dirty_set_impl.go
new file mode 100755
index 000000000..2510b81b3
--- /dev/null
+++ b/pkg/sentry/fs/fsutil/dirty_set_impl.go
@@ -0,0 +1,1274 @@
+package fsutil
+
+import (
+ __generics_imported0 "gvisor.dev/gvisor/pkg/sentry/memmap"
+)
+
+import (
+ "bytes"
+ "fmt"
+)
+
+const (
+ // minDegree is the minimum degree of an internal node in a Set B-tree.
+ //
+ // - Any non-root node has at least minDegree-1 segments.
+ //
+ // - Any non-root internal (non-leaf) node has at least minDegree children.
+ //
+ // - The root node may have fewer than minDegree-1 segments, but it may
+ // only have 0 segments if the tree is empty.
+ //
+ // Our implementation requires minDegree >= 3. Higher values of minDegree
+ // usually improve performance, but increase memory usage for small sets.
+ DirtyminDegree = 3
+
+ DirtymaxDegree = 2 * DirtyminDegree
+)
+
+// A Set is a mapping of segments with non-overlapping Range keys. The zero
+// value for a Set is an empty set. Set values are not safely movable nor
+// copyable. Set is thread-compatible.
+//
+// +stateify savable
+type DirtySet struct {
+ root Dirtynode `state:".(*DirtySegmentDataSlices)"`
+}
+
+// IsEmpty returns true if the set contains no segments.
+func (s *DirtySet) IsEmpty() bool {
+ return s.root.nrSegments == 0
+}
+
+// IsEmptyRange returns true iff no segments in the set overlap the given
+// range. This is semantically equivalent to s.SpanRange(r) == 0, but may be
+// more efficient.
+func (s *DirtySet) IsEmptyRange(r __generics_imported0.MappableRange) bool {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return true
+ }
+ _, gap := s.Find(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ return r.End <= gap.End()
+}
+
+// Span returns the total size of all segments in the set.
+func (s *DirtySet) Span() uint64 {
+ var sz uint64
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sz += seg.Range().Length()
+ }
+ return sz
+}
+
+// SpanRange returns the total size of the intersection of segments in the set
+// with the given range.
+func (s *DirtySet) SpanRange(r __generics_imported0.MappableRange) uint64 {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return 0
+ }
+ var sz uint64
+ for seg := s.LowerBoundSegment(r.Start); seg.Ok() && seg.Start() < r.End; seg = seg.NextSegment() {
+ sz += seg.Range().Intersect(r).Length()
+ }
+ return sz
+}
+
+// FirstSegment returns the first segment in the set. If the set is empty,
+// FirstSegment returns a terminal iterator.
+func (s *DirtySet) FirstSegment() DirtyIterator {
+ if s.root.nrSegments == 0 {
+ return DirtyIterator{}
+ }
+ return s.root.firstSegment()
+}
+
+// LastSegment returns the last segment in the set. If the set is empty,
+// LastSegment returns a terminal iterator.
+func (s *DirtySet) LastSegment() DirtyIterator {
+ if s.root.nrSegments == 0 {
+ return DirtyIterator{}
+ }
+ return s.root.lastSegment()
+}
+
+// FirstGap returns the first gap in the set.
+func (s *DirtySet) FirstGap() DirtyGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return DirtyGapIterator{n, 0}
+}
+
+// LastGap returns the last gap in the set.
+func (s *DirtySet) LastGap() DirtyGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return DirtyGapIterator{n, n.nrSegments}
+}
+
+// Find returns the segment or gap whose range contains the given key. If a
+// segment is found, the returned Iterator is non-terminal and the
+// returned GapIterator is terminal. Otherwise, the returned Iterator is
+// terminal and the returned GapIterator is non-terminal.
+func (s *DirtySet) Find(key uint64) (DirtyIterator, DirtyGapIterator) {
+ n := &s.root
+ for {
+
+ lower := 0
+ upper := n.nrSegments
+ for lower < upper {
+ i := lower + (upper-lower)/2
+ if r := n.keys[i]; key < r.End {
+ if key >= r.Start {
+ return DirtyIterator{n, i}, DirtyGapIterator{}
+ }
+ upper = i
+ } else {
+ lower = i + 1
+ }
+ }
+ i := lower
+ if !n.hasChildren {
+ return DirtyIterator{}, DirtyGapIterator{n, i}
+ }
+ n = n.children[i]
+ }
+}
+
+// FindSegment returns the segment whose range contains the given key. If no
+// such segment exists, FindSegment returns a terminal iterator.
+func (s *DirtySet) FindSegment(key uint64) DirtyIterator {
+ seg, _ := s.Find(key)
+ return seg
+}
+
+// LowerBoundSegment returns the segment with the lowest range that contains a
+// key greater than or equal to min. If no such segment exists,
+// LowerBoundSegment returns a terminal iterator.
+func (s *DirtySet) LowerBoundSegment(min uint64) DirtyIterator {
+ seg, gap := s.Find(min)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.NextSegment()
+}
+
+// UpperBoundSegment returns the segment with the highest range that contains a
+// key less than or equal to max. If no such segment exists, UpperBoundSegment
+// returns a terminal iterator.
+func (s *DirtySet) UpperBoundSegment(max uint64) DirtyIterator {
+ seg, gap := s.Find(max)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.PrevSegment()
+}
+
+// FindGap returns the gap containing the given key. If no such gap exists
+// (i.e. the set contains a segment containing that key), FindGap returns a
+// terminal iterator.
+func (s *DirtySet) FindGap(key uint64) DirtyGapIterator {
+ _, gap := s.Find(key)
+ return gap
+}
+
+// LowerBoundGap returns the gap with the lowest range that is greater than or
+// equal to min.
+func (s *DirtySet) LowerBoundGap(min uint64) DirtyGapIterator {
+ seg, gap := s.Find(min)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.NextGap()
+}
+
+// UpperBoundGap returns the gap with the highest range that is less than or
+// equal to max.
+func (s *DirtySet) UpperBoundGap(max uint64) DirtyGapIterator {
+ seg, gap := s.Find(max)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.PrevGap()
+}
+
+// Add inserts the given segment into the set and returns true. If the new
+// segment can be merged with adjacent segments, Add will do so. If the new
+// segment would overlap an existing segment, Add returns false. If Add
+// succeeds, all existing iterators are invalidated.
+func (s *DirtySet) Add(r __generics_imported0.MappableRange, val DirtyInfo) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.Insert(gap, r, val)
+ return true
+}
+
+// AddWithoutMerging inserts the given segment into the set and returns true.
+// If it would overlap an existing segment, AddWithoutMerging does nothing and
+// returns false. If AddWithoutMerging succeeds, all existing iterators are
+// invalidated.
+func (s *DirtySet) AddWithoutMerging(r __generics_imported0.MappableRange, val DirtyInfo) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.InsertWithoutMergingUnchecked(gap, r, val)
+ return true
+}
+
+// Insert inserts the given segment into the given gap. If the new segment can
+// be merged with adjacent segments, Insert will do so. Insert returns an
+// iterator to the segment containing the inserted value (which may have been
+// merged with other values). All existing iterators (including gap, but not
+// including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid, Insert panics.
+//
+// Insert is semantically equivalent to a InsertWithoutMerging followed by a
+// Merge, but may be more efficient. Note that there is no unchecked variant of
+// Insert since Insert must retrieve and inspect gap's predecessor and
+// successor segments regardless.
+func (s *DirtySet) Insert(gap DirtyGapIterator, r __generics_imported0.MappableRange, val DirtyInfo) DirtyIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ prev, next := gap.PrevSegment(), gap.NextSegment()
+ if prev.Ok() && prev.End() > r.Start {
+ panic(fmt.Sprintf("new segment %v overlaps predecessor %v", r, prev.Range()))
+ }
+ if next.Ok() && next.Start() < r.End {
+ panic(fmt.Sprintf("new segment %v overlaps successor %v", r, next.Range()))
+ }
+ if prev.Ok() && prev.End() == r.Start {
+ if mval, ok := (dirtySetFunctions{}).Merge(prev.Range(), prev.Value(), r, val); ok {
+ prev.SetEndUnchecked(r.End)
+ prev.SetValue(mval)
+ if next.Ok() && next.Start() == r.End {
+ val = mval
+ if mval, ok := (dirtySetFunctions{}).Merge(prev.Range(), val, next.Range(), next.Value()); ok {
+ prev.SetEndUnchecked(next.End())
+ prev.SetValue(mval)
+ return s.Remove(next).PrevSegment()
+ }
+ }
+ return prev
+ }
+ }
+ if next.Ok() && next.Start() == r.End {
+ if mval, ok := (dirtySetFunctions{}).Merge(r, val, next.Range(), next.Value()); ok {
+ next.SetStartUnchecked(r.Start)
+ next.SetValue(mval)
+ return next
+ }
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMerging inserts the given segment into the given gap and
+// returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid,
+// InsertWithoutMerging panics.
+func (s *DirtySet) InsertWithoutMerging(gap DirtyGapIterator, r __generics_imported0.MappableRange, val DirtyInfo) DirtyIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if gr := gap.Range(); !gr.IsSupersetOf(r) {
+ panic(fmt.Sprintf("cannot insert segment range %v into gap range %v", r, gr))
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMergingUnchecked inserts the given segment into the given gap
+// and returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// Preconditions: r.Start >= gap.Start(); r.End <= gap.End().
+func (s *DirtySet) InsertWithoutMergingUnchecked(gap DirtyGapIterator, r __generics_imported0.MappableRange, val DirtyInfo) DirtyIterator {
+ gap = gap.node.rebalanceBeforeInsert(gap)
+ copy(gap.node.keys[gap.index+1:], gap.node.keys[gap.index:gap.node.nrSegments])
+ copy(gap.node.values[gap.index+1:], gap.node.values[gap.index:gap.node.nrSegments])
+ gap.node.keys[gap.index] = r
+ gap.node.values[gap.index] = val
+ gap.node.nrSegments++
+ return DirtyIterator{gap.node, gap.index}
+}
+
+// Remove removes the given segment and returns an iterator to the vacated gap.
+// All existing iterators (including seg, but not including the returned
+// iterator) are invalidated.
+func (s *DirtySet) Remove(seg DirtyIterator) DirtyGapIterator {
+
+ if seg.node.hasChildren {
+
+ victim := seg.PrevSegment()
+
+ seg.SetRangeUnchecked(victim.Range())
+ seg.SetValue(victim.Value())
+ return s.Remove(victim).NextGap()
+ }
+ copy(seg.node.keys[seg.index:], seg.node.keys[seg.index+1:seg.node.nrSegments])
+ copy(seg.node.values[seg.index:], seg.node.values[seg.index+1:seg.node.nrSegments])
+ dirtySetFunctions{}.ClearValue(&seg.node.values[seg.node.nrSegments-1])
+ seg.node.nrSegments--
+ return seg.node.rebalanceAfterRemove(DirtyGapIterator{seg.node, seg.index})
+}
+
+// RemoveAll removes all segments from the set. All existing iterators are
+// invalidated.
+func (s *DirtySet) RemoveAll() {
+ s.root = Dirtynode{}
+}
+
+// RemoveRange removes all segments in the given range. An iterator to the
+// newly formed gap is returned, and all existing iterators are invalidated.
+func (s *DirtySet) RemoveRange(r __generics_imported0.MappableRange) DirtyGapIterator {
+ seg, gap := s.Find(r.Start)
+ if seg.Ok() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ for seg = gap.NextSegment(); seg.Ok() && seg.Start() < r.End; seg = gap.NextSegment() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ return gap
+}
+
+// Merge attempts to merge two neighboring segments. If successful, Merge
+// returns an iterator to the merged segment, and all existing iterators are
+// invalidated. Otherwise, Merge returns a terminal iterator.
+//
+// If first is not the predecessor of second, Merge panics.
+func (s *DirtySet) Merge(first, second DirtyIterator) DirtyIterator {
+ if first.NextSegment() != second {
+ panic(fmt.Sprintf("attempt to merge non-neighboring segments %v, %v", first.Range(), second.Range()))
+ }
+ return s.MergeUnchecked(first, second)
+}
+
+// MergeUnchecked attempts to merge two neighboring segments. If successful,
+// MergeUnchecked returns an iterator to the merged segment, and all existing
+// iterators are invalidated. Otherwise, MergeUnchecked returns a terminal
+// iterator.
+//
+// Precondition: first is the predecessor of second: first.NextSegment() ==
+// second, first == second.PrevSegment().
+func (s *DirtySet) MergeUnchecked(first, second DirtyIterator) DirtyIterator {
+ if first.End() == second.Start() {
+ if mval, ok := (dirtySetFunctions{}).Merge(first.Range(), first.Value(), second.Range(), second.Value()); ok {
+
+ first.SetEndUnchecked(second.End())
+ first.SetValue(mval)
+ return s.Remove(second).PrevSegment()
+ }
+ }
+ return DirtyIterator{}
+}
+
+// MergeAll attempts to merge all adjacent segments in the set. All existing
+// iterators are invalidated.
+func (s *DirtySet) MergeAll() {
+ seg := s.FirstSegment()
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeRange attempts to merge all adjacent segments that contain a key in the
+// specific range. All existing iterators are invalidated.
+func (s *DirtySet) MergeRange(r __generics_imported0.MappableRange) {
+ seg := s.LowerBoundSegment(r.Start)
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() && next.Range().Start < r.End {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeAdjacent attempts to merge the segment containing r.Start with its
+// predecessor, and the segment containing r.End-1 with its successor.
+func (s *DirtySet) MergeAdjacent(r __generics_imported0.MappableRange) {
+ first := s.FindSegment(r.Start)
+ if first.Ok() {
+ if prev := first.PrevSegment(); prev.Ok() {
+ s.Merge(prev, first)
+ }
+ }
+ last := s.FindSegment(r.End - 1)
+ if last.Ok() {
+ if next := last.NextSegment(); next.Ok() {
+ s.Merge(last, next)
+ }
+ }
+}
+
+// Split splits the given segment at the given key and returns iterators to the
+// two resulting segments. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+//
+// If the segment cannot be split at split (because split is at the start or
+// end of the segment's range, so splitting would produce a segment with zero
+// length, or because split falls outside the segment's range altogether),
+// Split panics.
+func (s *DirtySet) Split(seg DirtyIterator, split uint64) (DirtyIterator, DirtyIterator) {
+ if !seg.Range().CanSplitAt(split) {
+ panic(fmt.Sprintf("can't split %v at %v", seg.Range(), split))
+ }
+ return s.SplitUnchecked(seg, split)
+}
+
+// SplitUnchecked splits the given segment at the given key and returns
+// iterators to the two resulting segments. All existing iterators (including
+// seg, but not including the returned iterators) are invalidated.
+//
+// Preconditions: seg.Start() < key < seg.End().
+func (s *DirtySet) SplitUnchecked(seg DirtyIterator, split uint64) (DirtyIterator, DirtyIterator) {
+ val1, val2 := (dirtySetFunctions{}).Split(seg.Range(), seg.Value(), split)
+ end2 := seg.End()
+ seg.SetEndUnchecked(split)
+ seg.SetValue(val1)
+ seg2 := s.InsertWithoutMergingUnchecked(seg.NextGap(), __generics_imported0.MappableRange{split, end2}, val2)
+
+ return seg2.PrevSegment(), seg2
+}
+
+// SplitAt splits the segment straddling split, if one exists. SplitAt returns
+// true if a segment was split and false otherwise. If SplitAt splits a
+// segment, all existing iterators are invalidated.
+func (s *DirtySet) SplitAt(split uint64) bool {
+ if seg := s.FindSegment(split); seg.Ok() && seg.Range().CanSplitAt(split) {
+ s.SplitUnchecked(seg, split)
+ return true
+ }
+ return false
+}
+
+// Isolate ensures that the given segment's range does not escape r by
+// splitting at r.Start and r.End if necessary, and returns an updated iterator
+// to the bounded segment. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+func (s *DirtySet) Isolate(seg DirtyIterator, r __generics_imported0.MappableRange) DirtyIterator {
+ if seg.Range().CanSplitAt(r.Start) {
+ _, seg = s.SplitUnchecked(seg, r.Start)
+ }
+ if seg.Range().CanSplitAt(r.End) {
+ seg, _ = s.SplitUnchecked(seg, r.End)
+ }
+ return seg
+}
+
+// ApplyContiguous applies a function to a contiguous range of segments,
+// splitting if necessary. The function is applied until the first gap is
+// encountered, at which point the gap is returned. If the function is applied
+// across the entire range, a terminal gap is returned. All existing iterators
+// are invalidated.
+//
+// N.B. The Iterator must not be invalidated by the function.
+func (s *DirtySet) ApplyContiguous(r __generics_imported0.MappableRange, fn func(seg DirtyIterator)) DirtyGapIterator {
+ seg, gap := s.Find(r.Start)
+ if !seg.Ok() {
+ return gap
+ }
+ for {
+ seg = s.Isolate(seg, r)
+ fn(seg)
+ if seg.End() >= r.End {
+ return DirtyGapIterator{}
+ }
+ gap = seg.NextGap()
+ if !gap.IsEmpty() {
+ return gap
+ }
+ seg = gap.NextSegment()
+ if !seg.Ok() {
+
+ return DirtyGapIterator{}
+ }
+ }
+}
+
+// +stateify savable
+type Dirtynode struct {
+ // An internal binary tree node looks like:
+ //
+ // K
+ // / \
+ // Cl Cr
+ //
+ // where all keys in the subtree rooted by Cl (the left subtree) are less
+ // than K (the key of the parent node), and all keys in the subtree rooted
+ // by Cr (the right subtree) are greater than K.
+ //
+ // An internal B-tree node's indexes work out to look like:
+ //
+ // K0 K1 K2 ... Kn-1
+ // / \/ \/ \ ... / \
+ // C0 C1 C2 C3 ... Cn-1 Cn
+ //
+ // where n is nrSegments.
+ nrSegments int
+
+ // parent is a pointer to this node's parent. If this node is root, parent
+ // is nil.
+ parent *Dirtynode
+
+ // parentIndex is the index of this node in parent.children.
+ parentIndex int
+
+ // Flag for internal nodes that is technically redundant with "children[0]
+ // != nil", but is stored in the first cache line. "hasChildren" rather
+ // than "isLeaf" because false must be the correct value for an empty root.
+ hasChildren bool
+
+ // Nodes store keys and values in separate arrays to maximize locality in
+ // the common case (scanning keys for lookup).
+ keys [DirtymaxDegree - 1]__generics_imported0.MappableRange
+ values [DirtymaxDegree - 1]DirtyInfo
+ children [DirtymaxDegree]*Dirtynode
+}
+
+// firstSegment returns the first segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *Dirtynode) firstSegment() DirtyIterator {
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return DirtyIterator{n, 0}
+}
+
+// lastSegment returns the last segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *Dirtynode) lastSegment() DirtyIterator {
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return DirtyIterator{n, n.nrSegments - 1}
+}
+
+func (n *Dirtynode) prevSibling() *Dirtynode {
+ if n.parent == nil || n.parentIndex == 0 {
+ return nil
+ }
+ return n.parent.children[n.parentIndex-1]
+}
+
+func (n *Dirtynode) nextSibling() *Dirtynode {
+ if n.parent == nil || n.parentIndex == n.parent.nrSegments {
+ return nil
+ }
+ return n.parent.children[n.parentIndex+1]
+}
+
+// rebalanceBeforeInsert splits n and its ancestors if they are full, as
+// required for insertion, and returns an updated iterator to the position
+// represented by gap.
+func (n *Dirtynode) rebalanceBeforeInsert(gap DirtyGapIterator) DirtyGapIterator {
+ if n.parent != nil {
+ gap = n.parent.rebalanceBeforeInsert(gap)
+ }
+ if n.nrSegments < DirtymaxDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ left := &Dirtynode{
+ nrSegments: DirtyminDegree - 1,
+ parent: n,
+ parentIndex: 0,
+ hasChildren: n.hasChildren,
+ }
+ right := &Dirtynode{
+ nrSegments: DirtyminDegree - 1,
+ parent: n,
+ parentIndex: 1,
+ hasChildren: n.hasChildren,
+ }
+ copy(left.keys[:DirtyminDegree-1], n.keys[:DirtyminDegree-1])
+ copy(left.values[:DirtyminDegree-1], n.values[:DirtyminDegree-1])
+ copy(right.keys[:DirtyminDegree-1], n.keys[DirtyminDegree:])
+ copy(right.values[:DirtyminDegree-1], n.values[DirtyminDegree:])
+ n.keys[0], n.values[0] = n.keys[DirtyminDegree-1], n.values[DirtyminDegree-1]
+ DirtyzeroValueSlice(n.values[1:])
+ if n.hasChildren {
+ copy(left.children[:DirtyminDegree], n.children[:DirtyminDegree])
+ copy(right.children[:DirtyminDegree], n.children[DirtyminDegree:])
+ DirtyzeroNodeSlice(n.children[2:])
+ for i := 0; i < DirtyminDegree; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ right.children[i].parent = right
+ right.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = 1
+ n.hasChildren = true
+ n.children[0] = left
+ n.children[1] = right
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < DirtyminDegree {
+ return DirtyGapIterator{left, gap.index}
+ }
+ return DirtyGapIterator{right, gap.index - DirtyminDegree}
+ }
+
+ copy(n.parent.keys[n.parentIndex+1:], n.parent.keys[n.parentIndex:n.parent.nrSegments])
+ copy(n.parent.values[n.parentIndex+1:], n.parent.values[n.parentIndex:n.parent.nrSegments])
+ n.parent.keys[n.parentIndex], n.parent.values[n.parentIndex] = n.keys[DirtyminDegree-1], n.values[DirtyminDegree-1]
+ copy(n.parent.children[n.parentIndex+2:], n.parent.children[n.parentIndex+1:n.parent.nrSegments+1])
+ for i := n.parentIndex + 2; i < n.parent.nrSegments+2; i++ {
+ n.parent.children[i].parentIndex = i
+ }
+ sibling := &Dirtynode{
+ nrSegments: DirtyminDegree - 1,
+ parent: n.parent,
+ parentIndex: n.parentIndex + 1,
+ hasChildren: n.hasChildren,
+ }
+ n.parent.children[n.parentIndex+1] = sibling
+ n.parent.nrSegments++
+ copy(sibling.keys[:DirtyminDegree-1], n.keys[DirtyminDegree:])
+ copy(sibling.values[:DirtyminDegree-1], n.values[DirtyminDegree:])
+ DirtyzeroValueSlice(n.values[DirtyminDegree-1:])
+ if n.hasChildren {
+ copy(sibling.children[:DirtyminDegree], n.children[DirtyminDegree:])
+ DirtyzeroNodeSlice(n.children[DirtyminDegree:])
+ for i := 0; i < DirtyminDegree; i++ {
+ sibling.children[i].parent = sibling
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = DirtyminDegree - 1
+
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < DirtyminDegree {
+ return gap
+ }
+ return DirtyGapIterator{sibling, gap.index - DirtyminDegree}
+}
+
+// rebalanceAfterRemove "unsplits" n and its ancestors if they are deficient
+// (contain fewer segments than required by B-tree invariants), as required for
+// removal, and returns an updated iterator to the position represented by gap.
+//
+// Precondition: n is the only node in the tree that may currently violate a
+// B-tree invariant.
+func (n *Dirtynode) rebalanceAfterRemove(gap DirtyGapIterator) DirtyGapIterator {
+ for {
+ if n.nrSegments >= DirtyminDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ return gap
+ }
+
+ if sibling := n.prevSibling(); sibling != nil && sibling.nrSegments >= DirtyminDegree {
+ copy(n.keys[1:], n.keys[:n.nrSegments])
+ copy(n.values[1:], n.values[:n.nrSegments])
+ n.keys[0] = n.parent.keys[n.parentIndex-1]
+ n.values[0] = n.parent.values[n.parentIndex-1]
+ n.parent.keys[n.parentIndex-1] = sibling.keys[sibling.nrSegments-1]
+ n.parent.values[n.parentIndex-1] = sibling.values[sibling.nrSegments-1]
+ dirtySetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ copy(n.children[1:], n.children[:n.nrSegments+1])
+ n.children[0] = sibling.children[sibling.nrSegments]
+ sibling.children[sibling.nrSegments] = nil
+ n.children[0].parent = n
+ n.children[0].parentIndex = 0
+ for i := 1; i < n.nrSegments+2; i++ {
+ n.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling && gap.index == sibling.nrSegments {
+ return DirtyGapIterator{n, 0}
+ }
+ if gap.node == n {
+ return DirtyGapIterator{n, gap.index + 1}
+ }
+ return gap
+ }
+ if sibling := n.nextSibling(); sibling != nil && sibling.nrSegments >= DirtyminDegree {
+ n.keys[n.nrSegments] = n.parent.keys[n.parentIndex]
+ n.values[n.nrSegments] = n.parent.values[n.parentIndex]
+ n.parent.keys[n.parentIndex] = sibling.keys[0]
+ n.parent.values[n.parentIndex] = sibling.values[0]
+ copy(sibling.keys[:sibling.nrSegments-1], sibling.keys[1:])
+ copy(sibling.values[:sibling.nrSegments-1], sibling.values[1:])
+ dirtySetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ n.children[n.nrSegments+1] = sibling.children[0]
+ copy(sibling.children[:sibling.nrSegments], sibling.children[1:])
+ sibling.children[sibling.nrSegments] = nil
+ n.children[n.nrSegments+1].parent = n
+ n.children[n.nrSegments+1].parentIndex = n.nrSegments + 1
+ for i := 0; i < sibling.nrSegments; i++ {
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling {
+ if gap.index == 0 {
+ return DirtyGapIterator{n, n.nrSegments}
+ }
+ return DirtyGapIterator{sibling, gap.index - 1}
+ }
+ return gap
+ }
+
+ p := n.parent
+ if p.nrSegments == 1 {
+
+ left, right := p.children[0], p.children[1]
+ p.nrSegments = left.nrSegments + right.nrSegments + 1
+ p.hasChildren = left.hasChildren
+ p.keys[left.nrSegments] = p.keys[0]
+ p.values[left.nrSegments] = p.values[0]
+ copy(p.keys[:left.nrSegments], left.keys[:left.nrSegments])
+ copy(p.values[:left.nrSegments], left.values[:left.nrSegments])
+ copy(p.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(p.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(p.children[:left.nrSegments+1], left.children[:left.nrSegments+1])
+ copy(p.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := 0; i < p.nrSegments+1; i++ {
+ p.children[i].parent = p
+ p.children[i].parentIndex = i
+ }
+ } else {
+ p.children[0] = nil
+ p.children[1] = nil
+ }
+ if gap.node == left {
+ return DirtyGapIterator{p, gap.index}
+ }
+ if gap.node == right {
+ return DirtyGapIterator{p, gap.index + left.nrSegments + 1}
+ }
+ return gap
+ }
+ // Merge n and either sibling, along with the segment separating the
+ // two, into whichever of the two nodes comes first. This is the
+ // reverse of the non-root splitting case in
+ // node.rebalanceBeforeInsert.
+ var left, right *Dirtynode
+ if n.parentIndex > 0 {
+ left = n.prevSibling()
+ right = n
+ } else {
+ left = n
+ right = n.nextSibling()
+ }
+
+ if gap.node == right {
+ gap = DirtyGapIterator{left, gap.index + left.nrSegments + 1}
+ }
+ left.keys[left.nrSegments] = p.keys[left.parentIndex]
+ left.values[left.nrSegments] = p.values[left.parentIndex]
+ copy(left.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(left.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(left.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := left.nrSegments + 1; i < left.nrSegments+right.nrSegments+2; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ }
+ }
+ left.nrSegments += right.nrSegments + 1
+ copy(p.keys[left.parentIndex:], p.keys[left.parentIndex+1:p.nrSegments])
+ copy(p.values[left.parentIndex:], p.values[left.parentIndex+1:p.nrSegments])
+ dirtySetFunctions{}.ClearValue(&p.values[p.nrSegments-1])
+ copy(p.children[left.parentIndex+1:], p.children[left.parentIndex+2:p.nrSegments+1])
+ for i := 0; i < p.nrSegments; i++ {
+ p.children[i].parentIndex = i
+ }
+ p.children[p.nrSegments] = nil
+ p.nrSegments--
+
+ n = p
+ }
+}
+
+// A Iterator is conceptually one of:
+//
+// - A pointer to a segment in a set; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Iterators are copyable values and are meaningfully equality-comparable. The
+// zero value of Iterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type DirtyIterator struct {
+ // node is the node containing the iterated segment. If the iterator is
+ // terminal, node is nil.
+ node *Dirtynode
+
+ // index is the index of the segment in node.keys/values.
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (seg DirtyIterator) Ok() bool {
+ return seg.node != nil
+}
+
+// Range returns the iterated segment's range key.
+func (seg DirtyIterator) Range() __generics_imported0.MappableRange {
+ return seg.node.keys[seg.index]
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (seg DirtyIterator) Start() uint64 {
+ return seg.node.keys[seg.index].Start
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (seg DirtyIterator) End() uint64 {
+ return seg.node.keys[seg.index].End
+}
+
+// SetRangeUnchecked mutates the iterated segment's range key. This operation
+// does not invalidate any iterators.
+//
+// Preconditions:
+//
+// - r.Length() > 0.
+//
+// - The new range must not overlap an existing one: If seg.NextSegment().Ok(),
+// then r.end <= seg.NextSegment().Start(); if seg.PrevSegment().Ok(), then
+// r.start >= seg.PrevSegment().End().
+func (seg DirtyIterator) SetRangeUnchecked(r __generics_imported0.MappableRange) {
+ seg.node.keys[seg.index] = r
+}
+
+// SetRange mutates the iterated segment's range key. If the new range would
+// cause the iterated segment to overlap another segment, or if the new range
+// is invalid, SetRange panics. This operation does not invalidate any
+// iterators.
+func (seg DirtyIterator) SetRange(r __generics_imported0.MappableRange) {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && r.Start < prev.End() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, prev.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && r.End > next.Start() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, next.Range()))
+ }
+ seg.SetRangeUnchecked(r)
+}
+
+// SetStartUnchecked mutates the iterated segment's start. This operation does
+// not invalidate any iterators.
+//
+// Preconditions: The new start must be valid: start < seg.End(); if
+// seg.PrevSegment().Ok(), then start >= seg.PrevSegment().End().
+func (seg DirtyIterator) SetStartUnchecked(start uint64) {
+ seg.node.keys[seg.index].Start = start
+}
+
+// SetStart mutates the iterated segment's start. If the new start value would
+// cause the iterated segment to overlap another segment, or would result in an
+// invalid range, SetStart panics. This operation does not invalidate any
+// iterators.
+func (seg DirtyIterator) SetStart(start uint64) {
+ if start >= seg.End() {
+ panic(fmt.Sprintf("new start %v would invalidate segment range %v", start, seg.Range()))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && start < prev.End() {
+ panic(fmt.Sprintf("new start %v would cause segment range %v to overlap segment range %v", start, seg.Range(), prev.Range()))
+ }
+ seg.SetStartUnchecked(start)
+}
+
+// SetEndUnchecked mutates the iterated segment's end. This operation does not
+// invalidate any iterators.
+//
+// Preconditions: The new end must be valid: end > seg.Start(); if
+// seg.NextSegment().Ok(), then end <= seg.NextSegment().Start().
+func (seg DirtyIterator) SetEndUnchecked(end uint64) {
+ seg.node.keys[seg.index].End = end
+}
+
+// SetEnd mutates the iterated segment's end. If the new end value would cause
+// the iterated segment to overlap another segment, or would result in an
+// invalid range, SetEnd panics. This operation does not invalidate any
+// iterators.
+func (seg DirtyIterator) SetEnd(end uint64) {
+ if end <= seg.Start() {
+ panic(fmt.Sprintf("new end %v would invalidate segment range %v", end, seg.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && end > next.Start() {
+ panic(fmt.Sprintf("new end %v would cause segment range %v to overlap segment range %v", end, seg.Range(), next.Range()))
+ }
+ seg.SetEndUnchecked(end)
+}
+
+// Value returns a copy of the iterated segment's value.
+func (seg DirtyIterator) Value() DirtyInfo {
+ return seg.node.values[seg.index]
+}
+
+// ValuePtr returns a pointer to the iterated segment's value. The pointer is
+// invalidated if the iterator is invalidated. This operation does not
+// invalidate any iterators.
+func (seg DirtyIterator) ValuePtr() *DirtyInfo {
+ return &seg.node.values[seg.index]
+}
+
+// SetValue mutates the iterated segment's value. This operation does not
+// invalidate any iterators.
+func (seg DirtyIterator) SetValue(val DirtyInfo) {
+ seg.node.values[seg.index] = val
+}
+
+// PrevSegment returns the iterated segment's predecessor. If there is no
+// preceding segment, PrevSegment returns a terminal iterator.
+func (seg DirtyIterator) PrevSegment() DirtyIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index].lastSegment()
+ }
+ if seg.index > 0 {
+ return DirtyIterator{seg.node, seg.index - 1}
+ }
+ if seg.node.parent == nil {
+ return DirtyIterator{}
+ }
+ return DirtysegmentBeforePosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// NextSegment returns the iterated segment's successor. If there is no
+// succeeding segment, NextSegment returns a terminal iterator.
+func (seg DirtyIterator) NextSegment() DirtyIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment()
+ }
+ if seg.index < seg.node.nrSegments-1 {
+ return DirtyIterator{seg.node, seg.index + 1}
+ }
+ if seg.node.parent == nil {
+ return DirtyIterator{}
+ }
+ return DirtysegmentAfterPosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// PrevGap returns the gap immediately before the iterated segment.
+func (seg DirtyIterator) PrevGap() DirtyGapIterator {
+ if seg.node.hasChildren {
+
+ return seg.node.children[seg.index].lastSegment().NextGap()
+ }
+ return DirtyGapIterator{seg.node, seg.index}
+}
+
+// NextGap returns the gap immediately after the iterated segment.
+func (seg DirtyIterator) NextGap() DirtyGapIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment().PrevGap()
+ }
+ return DirtyGapIterator{seg.node, seg.index + 1}
+}
+
+// PrevNonEmpty returns the iterated segment's predecessor if it is adjacent,
+// or the gap before the iterated segment otherwise. If seg.Start() ==
+// Functions.MinKey(), PrevNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by PrevNonEmpty will be
+// non-terminal.
+func (seg DirtyIterator) PrevNonEmpty() (DirtyIterator, DirtyGapIterator) {
+ gap := seg.PrevGap()
+ if gap.Range().Length() != 0 {
+ return DirtyIterator{}, gap
+ }
+ return gap.PrevSegment(), DirtyGapIterator{}
+}
+
+// NextNonEmpty returns the iterated segment's successor if it is adjacent, or
+// the gap after the iterated segment otherwise. If seg.End() ==
+// Functions.MaxKey(), NextNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by NextNonEmpty will be
+// non-terminal.
+func (seg DirtyIterator) NextNonEmpty() (DirtyIterator, DirtyGapIterator) {
+ gap := seg.NextGap()
+ if gap.Range().Length() != 0 {
+ return DirtyIterator{}, gap
+ }
+ return gap.NextSegment(), DirtyGapIterator{}
+}
+
+// A GapIterator is conceptually one of:
+//
+// - A pointer to a position between two segments, before the first segment, or
+// after the last segment in a set, called a *gap*; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Note that the gap between two adjacent segments exists (iterators to it are
+// non-terminal), but has a length of zero. GapIterator.IsEmpty returns true
+// for such gaps. An empty set contains a single gap, spanning the entire range
+// of the set's keys.
+//
+// GapIterators are copyable values and are meaningfully equality-comparable.
+// The zero value of GapIterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type DirtyGapIterator struct {
+ // The representation of a GapIterator is identical to that of an Iterator,
+ // except that index corresponds to positions between segments in the same
+ // way as for node.children (see comment for node.nrSegments).
+ node *Dirtynode
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (gap DirtyGapIterator) Ok() bool {
+ return gap.node != nil
+}
+
+// Range returns the range spanned by the iterated gap.
+func (gap DirtyGapIterator) Range() __generics_imported0.MappableRange {
+ return __generics_imported0.MappableRange{gap.Start(), gap.End()}
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (gap DirtyGapIterator) Start() uint64 {
+ if ps := gap.PrevSegment(); ps.Ok() {
+ return ps.End()
+ }
+ return dirtySetFunctions{}.MinKey()
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (gap DirtyGapIterator) End() uint64 {
+ if ns := gap.NextSegment(); ns.Ok() {
+ return ns.Start()
+ }
+ return dirtySetFunctions{}.MaxKey()
+}
+
+// IsEmpty returns true if the iterated gap is empty (that is, the "gap" is
+// between two adjacent segments.)
+func (gap DirtyGapIterator) IsEmpty() bool {
+ return gap.Range().Length() == 0
+}
+
+// PrevSegment returns the segment immediately before the iterated gap. If no
+// such segment exists, PrevSegment returns a terminal iterator.
+func (gap DirtyGapIterator) PrevSegment() DirtyIterator {
+ return DirtysegmentBeforePosition(gap.node, gap.index)
+}
+
+// NextSegment returns the segment immediately after the iterated gap. If no
+// such segment exists, NextSegment returns a terminal iterator.
+func (gap DirtyGapIterator) NextSegment() DirtyIterator {
+ return DirtysegmentAfterPosition(gap.node, gap.index)
+}
+
+// PrevGap returns the iterated gap's predecessor. If no such gap exists,
+// PrevGap returns a terminal iterator.
+func (gap DirtyGapIterator) PrevGap() DirtyGapIterator {
+ seg := gap.PrevSegment()
+ if !seg.Ok() {
+ return DirtyGapIterator{}
+ }
+ return seg.PrevGap()
+}
+
+// NextGap returns the iterated gap's successor. If no such gap exists, NextGap
+// returns a terminal iterator.
+func (gap DirtyGapIterator) NextGap() DirtyGapIterator {
+ seg := gap.NextSegment()
+ if !seg.Ok() {
+ return DirtyGapIterator{}
+ }
+ return seg.NextGap()
+}
+
+// segmentBeforePosition returns the predecessor segment of the position given
+// by n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentBeforePosition returns a terminal iterator.
+func DirtysegmentBeforePosition(n *Dirtynode, i int) DirtyIterator {
+ for i == 0 {
+ if n.parent == nil {
+ return DirtyIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return DirtyIterator{n, i - 1}
+}
+
+// segmentAfterPosition returns the successor segment of the position given by
+// n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentAfterPosition returns a terminal iterator.
+func DirtysegmentAfterPosition(n *Dirtynode, i int) DirtyIterator {
+ for i == n.nrSegments {
+ if n.parent == nil {
+ return DirtyIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return DirtyIterator{n, i}
+}
+
+func DirtyzeroValueSlice(slice []DirtyInfo) {
+
+ for i := range slice {
+ dirtySetFunctions{}.ClearValue(&slice[i])
+ }
+}
+
+func DirtyzeroNodeSlice(slice []*Dirtynode) {
+ for i := range slice {
+ slice[i] = nil
+ }
+}
+
+// String stringifies a Set for debugging.
+func (s *DirtySet) String() string {
+ return s.root.String()
+}
+
+// String stringifies a node (and all of its children) for debugging.
+func (n *Dirtynode) String() string {
+ var buf bytes.Buffer
+ n.writeDebugString(&buf, "")
+ return buf.String()
+}
+
+func (n *Dirtynode) writeDebugString(buf *bytes.Buffer, prefix string) {
+ if n.hasChildren != (n.nrSegments > 0 && n.children[0] != nil) {
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent value of hasChildren: got %v, want %v\n", n.hasChildren, !n.hasChildren))
+ }
+ for i := 0; i < n.nrSegments; i++ {
+ if child := n.children[i]; child != nil {
+ cprefix := fmt.Sprintf("%s- % 3d ", prefix, i)
+ if child.parent != n || child.parentIndex != i {
+ buf.WriteString(cprefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent linkage to parent: got (%p, %d), want (%p, %d)\n", child.parent, child.parentIndex, n, i))
+ }
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, i))
+ }
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("- % 3d: %v => %v\n", i, n.keys[i], n.values[i]))
+ }
+ if child := n.children[n.nrSegments]; child != nil {
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, n.nrSegments))
+ }
+}
+
+// SegmentDataSlices represents segments from a set as slices of start, end, and
+// values. SegmentDataSlices is primarily used as an intermediate representation
+// for save/restore and the layout here is optimized for that.
+//
+// +stateify savable
+type DirtySegmentDataSlices struct {
+ Start []uint64
+ End []uint64
+ Values []DirtyInfo
+}
+
+// ExportSortedSlice returns a copy of all segments in the given set, in ascending
+// key order.
+func (s *DirtySet) ExportSortedSlices() *DirtySegmentDataSlices {
+ var sds DirtySegmentDataSlices
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sds.Start = append(sds.Start, seg.Start())
+ sds.End = append(sds.End, seg.End())
+ sds.Values = append(sds.Values, seg.Value())
+ }
+ sds.Start = sds.Start[:len(sds.Start):len(sds.Start)]
+ sds.End = sds.End[:len(sds.End):len(sds.End)]
+ sds.Values = sds.Values[:len(sds.Values):len(sds.Values)]
+ return &sds
+}
+
+// ImportSortedSlice initializes the given set from the given slice.
+//
+// Preconditions: s must be empty. sds must represent a valid set (the segments
+// in sds must have valid lengths that do not overlap). The segments in sds
+// must be sorted in ascending key order.
+func (s *DirtySet) ImportSortedSlices(sds *DirtySegmentDataSlices) error {
+ if !s.IsEmpty() {
+ return fmt.Errorf("cannot import into non-empty set %v", s)
+ }
+ gap := s.FirstGap()
+ for i := range sds.Start {
+ r := __generics_imported0.MappableRange{sds.Start[i], sds.End[i]}
+ if !gap.Range().IsSupersetOf(r) {
+ return fmt.Errorf("segment overlaps a preceding segment or is incorrectly sorted: [%d, %d) => %v", sds.Start[i], sds.End[i], sds.Values[i])
+ }
+ gap = s.InsertWithoutMerging(gap, r, sds.Values[i]).NextGap()
+ }
+ return nil
+}
+func (s *DirtySet) saveRoot() *DirtySegmentDataSlices {
+ return s.ExportSortedSlices()
+}
+
+func (s *DirtySet) loadRoot(sds *DirtySegmentDataSlices) {
+ if err := s.ImportSortedSlices(sds); err != nil {
+ panic(err)
+ }
+}
diff --git a/pkg/sentry/fs/fsutil/dirty_set_test.go b/pkg/sentry/fs/fsutil/dirty_set_test.go
deleted file mode 100644
index 75575d994..000000000
--- a/pkg/sentry/fs/fsutil/dirty_set_test.go
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fsutil
-
-import (
- "reflect"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/memmap"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
-)
-
-func TestDirtySet(t *testing.T) {
- var set DirtySet
- set.MarkDirty(memmap.MappableRange{0, 2 * usermem.PageSize})
- set.KeepDirty(memmap.MappableRange{usermem.PageSize, 2 * usermem.PageSize})
- set.MarkClean(memmap.MappableRange{0, 2 * usermem.PageSize})
- want := &DirtySegmentDataSlices{
- Start: []uint64{usermem.PageSize},
- End: []uint64{2 * usermem.PageSize},
- Values: []DirtyInfo{{Keep: true}},
- }
- if got := set.ExportSortedSlices(); !reflect.DeepEqual(got, want) {
- t.Errorf("set:\n\tgot %v,\n\twant %v", got, want)
- }
-}
diff --git a/pkg/segment/set.go b/pkg/sentry/fs/fsutil/file_range_set_impl.go
index 03e4f258f..0548bba08 100644..100755
--- a/pkg/segment/set.go
+++ b/pkg/sentry/fs/fsutil/file_range_set_impl.go
@@ -1,73 +1,14 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package segment provides tools for working with collections of segments. A
-// segment is a key-value mapping, where the key is a non-empty contiguous
-// range of values of type Key, and the value is a single value of type Value.
-//
-// Clients using this package must use the go_template_instance rule in
-// tools/go_generics/defs.bzl to create an instantiation of this
-// template package, providing types to use in place of Key, Range, Value, and
-// Functions. See pkg/segment/test/BUILD for a usage example.
-package segment
+package fsutil
+
+import (
+ __generics_imported0 "gvisor.dev/gvisor/pkg/sentry/memmap"
+)
import (
"bytes"
"fmt"
)
-// Key is a required type parameter that must be an integral type.
-type Key uint64
-
-// Range is a required type parameter equivalent to Range<Key>.
-type Range interface{}
-
-// Value is a required type parameter.
-type Value interface{}
-
-// Functions is a required type parameter that must be a struct implementing
-// the methods defined by Functions.
-type Functions interface {
- // MinKey returns the minimum allowed key.
- MinKey() Key
-
- // MaxKey returns the maximum allowed key + 1.
- MaxKey() Key
-
- // ClearValue deinitializes the given value. (For example, if Value is a
- // pointer or interface type, ClearValue should set it to nil.)
- ClearValue(*Value)
-
- // Merge attempts to merge the values corresponding to two consecutive
- // segments. If successful, Merge returns (merged value, true). Otherwise,
- // it returns (unspecified, false).
- //
- // Preconditions: r1.End == r2.Start.
- //
- // Postconditions: If merging succeeds, val1 and val2 are invalidated.
- Merge(r1 Range, val1 Value, r2 Range, val2 Value) (Value, bool)
-
- // Split splits a segment's value at a key within its range, such that the
- // first returned value corresponds to the range [r.Start, split) and the
- // second returned value corresponds to the range [split, r.End).
- //
- // Preconditions: r.Start < split < r.End.
- //
- // Postconditions: The original value val is invalidated.
- Split(r Range, val Value, split Key) (Value, Value)
-}
-
const (
// minDegree is the minimum degree of an internal node in a Set B-tree.
//
@@ -80,9 +21,9 @@ const (
//
// Our implementation requires minDegree >= 3. Higher values of minDegree
// usually improve performance, but increase memory usage for small sets.
- minDegree = 3
+ FileRangeminDegree = 3
- maxDegree = 2 * minDegree
+ FileRangemaxDegree = 2 * FileRangeminDegree
)
// A Set is a mapping of segments with non-overlapping Range keys. The zero
@@ -90,19 +31,19 @@ const (
// copyable. Set is thread-compatible.
//
// +stateify savable
-type Set struct {
- root node `state:".(*SegmentDataSlices)"`
+type FileRangeSet struct {
+ root FileRangenode `state:".(*FileRangeSegmentDataSlices)"`
}
// IsEmpty returns true if the set contains no segments.
-func (s *Set) IsEmpty() bool {
+func (s *FileRangeSet) IsEmpty() bool {
return s.root.nrSegments == 0
}
// IsEmptyRange returns true iff no segments in the set overlap the given
// range. This is semantically equivalent to s.SpanRange(r) == 0, but may be
// more efficient.
-func (s *Set) IsEmptyRange(r Range) bool {
+func (s *FileRangeSet) IsEmptyRange(r __generics_imported0.MappableRange) bool {
switch {
case r.Length() < 0:
panic(fmt.Sprintf("invalid range %v", r))
@@ -117,8 +58,8 @@ func (s *Set) IsEmptyRange(r Range) bool {
}
// Span returns the total size of all segments in the set.
-func (s *Set) Span() Key {
- var sz Key
+func (s *FileRangeSet) Span() uint64 {
+ var sz uint64
for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
sz += seg.Range().Length()
}
@@ -127,14 +68,14 @@ func (s *Set) Span() Key {
// SpanRange returns the total size of the intersection of segments in the set
// with the given range.
-func (s *Set) SpanRange(r Range) Key {
+func (s *FileRangeSet) SpanRange(r __generics_imported0.MappableRange) uint64 {
switch {
case r.Length() < 0:
panic(fmt.Sprintf("invalid range %v", r))
case r.Length() == 0:
return 0
}
- var sz Key
+ var sz uint64
for seg := s.LowerBoundSegment(r.Start); seg.Ok() && seg.Start() < r.End; seg = seg.NextSegment() {
sz += seg.Range().Intersect(r).Length()
}
@@ -143,56 +84,55 @@ func (s *Set) SpanRange(r Range) Key {
// FirstSegment returns the first segment in the set. If the set is empty,
// FirstSegment returns a terminal iterator.
-func (s *Set) FirstSegment() Iterator {
+func (s *FileRangeSet) FirstSegment() FileRangeIterator {
if s.root.nrSegments == 0 {
- return Iterator{}
+ return FileRangeIterator{}
}
return s.root.firstSegment()
}
// LastSegment returns the last segment in the set. If the set is empty,
// LastSegment returns a terminal iterator.
-func (s *Set) LastSegment() Iterator {
+func (s *FileRangeSet) LastSegment() FileRangeIterator {
if s.root.nrSegments == 0 {
- return Iterator{}
+ return FileRangeIterator{}
}
return s.root.lastSegment()
}
// FirstGap returns the first gap in the set.
-func (s *Set) FirstGap() GapIterator {
+func (s *FileRangeSet) FirstGap() FileRangeGapIterator {
n := &s.root
for n.hasChildren {
n = n.children[0]
}
- return GapIterator{n, 0}
+ return FileRangeGapIterator{n, 0}
}
// LastGap returns the last gap in the set.
-func (s *Set) LastGap() GapIterator {
+func (s *FileRangeSet) LastGap() FileRangeGapIterator {
n := &s.root
for n.hasChildren {
n = n.children[n.nrSegments]
}
- return GapIterator{n, n.nrSegments}
+ return FileRangeGapIterator{n, n.nrSegments}
}
// Find returns the segment or gap whose range contains the given key. If a
// segment is found, the returned Iterator is non-terminal and the
// returned GapIterator is terminal. Otherwise, the returned Iterator is
// terminal and the returned GapIterator is non-terminal.
-func (s *Set) Find(key Key) (Iterator, GapIterator) {
+func (s *FileRangeSet) Find(key uint64) (FileRangeIterator, FileRangeGapIterator) {
n := &s.root
for {
- // Binary search invariant: the correct value of i lies within [lower,
- // upper].
+
lower := 0
upper := n.nrSegments
for lower < upper {
i := lower + (upper-lower)/2
if r := n.keys[i]; key < r.End {
if key >= r.Start {
- return Iterator{n, i}, GapIterator{}
+ return FileRangeIterator{n, i}, FileRangeGapIterator{}
}
upper = i
} else {
@@ -201,7 +141,7 @@ func (s *Set) Find(key Key) (Iterator, GapIterator) {
}
i := lower
if !n.hasChildren {
- return Iterator{}, GapIterator{n, i}
+ return FileRangeIterator{}, FileRangeGapIterator{n, i}
}
n = n.children[i]
}
@@ -209,7 +149,7 @@ func (s *Set) Find(key Key) (Iterator, GapIterator) {
// FindSegment returns the segment whose range contains the given key. If no
// such segment exists, FindSegment returns a terminal iterator.
-func (s *Set) FindSegment(key Key) Iterator {
+func (s *FileRangeSet) FindSegment(key uint64) FileRangeIterator {
seg, _ := s.Find(key)
return seg
}
@@ -217,7 +157,7 @@ func (s *Set) FindSegment(key Key) Iterator {
// LowerBoundSegment returns the segment with the lowest range that contains a
// key greater than or equal to min. If no such segment exists,
// LowerBoundSegment returns a terminal iterator.
-func (s *Set) LowerBoundSegment(min Key) Iterator {
+func (s *FileRangeSet) LowerBoundSegment(min uint64) FileRangeIterator {
seg, gap := s.Find(min)
if seg.Ok() {
return seg
@@ -228,7 +168,7 @@ func (s *Set) LowerBoundSegment(min Key) Iterator {
// UpperBoundSegment returns the segment with the highest range that contains a
// key less than or equal to max. If no such segment exists, UpperBoundSegment
// returns a terminal iterator.
-func (s *Set) UpperBoundSegment(max Key) Iterator {
+func (s *FileRangeSet) UpperBoundSegment(max uint64) FileRangeIterator {
seg, gap := s.Find(max)
if seg.Ok() {
return seg
@@ -239,14 +179,14 @@ func (s *Set) UpperBoundSegment(max Key) Iterator {
// FindGap returns the gap containing the given key. If no such gap exists
// (i.e. the set contains a segment containing that key), FindGap returns a
// terminal iterator.
-func (s *Set) FindGap(key Key) GapIterator {
+func (s *FileRangeSet) FindGap(key uint64) FileRangeGapIterator {
_, gap := s.Find(key)
return gap
}
// LowerBoundGap returns the gap with the lowest range that is greater than or
// equal to min.
-func (s *Set) LowerBoundGap(min Key) GapIterator {
+func (s *FileRangeSet) LowerBoundGap(min uint64) FileRangeGapIterator {
seg, gap := s.Find(min)
if gap.Ok() {
return gap
@@ -256,7 +196,7 @@ func (s *Set) LowerBoundGap(min Key) GapIterator {
// UpperBoundGap returns the gap with the highest range that is less than or
// equal to max.
-func (s *Set) UpperBoundGap(max Key) GapIterator {
+func (s *FileRangeSet) UpperBoundGap(max uint64) FileRangeGapIterator {
seg, gap := s.Find(max)
if gap.Ok() {
return gap
@@ -268,7 +208,7 @@ func (s *Set) UpperBoundGap(max Key) GapIterator {
// segment can be merged with adjacent segments, Add will do so. If the new
// segment would overlap an existing segment, Add returns false. If Add
// succeeds, all existing iterators are invalidated.
-func (s *Set) Add(r Range, val Value) bool {
+func (s *FileRangeSet) Add(r __generics_imported0.MappableRange, val uint64) bool {
if r.Length() <= 0 {
panic(fmt.Sprintf("invalid segment range %v", r))
}
@@ -287,7 +227,7 @@ func (s *Set) Add(r Range, val Value) bool {
// If it would overlap an existing segment, AddWithoutMerging does nothing and
// returns false. If AddWithoutMerging succeeds, all existing iterators are
// invalidated.
-func (s *Set) AddWithoutMerging(r Range, val Value) bool {
+func (s *FileRangeSet) AddWithoutMerging(r __generics_imported0.MappableRange, val uint64) bool {
if r.Length() <= 0 {
panic(fmt.Sprintf("invalid segment range %v", r))
}
@@ -314,7 +254,7 @@ func (s *Set) AddWithoutMerging(r Range, val Value) bool {
// Merge, but may be more efficient. Note that there is no unchecked variant of
// Insert since Insert must retrieve and inspect gap's predecessor and
// successor segments regardless.
-func (s *Set) Insert(gap GapIterator, r Range, val Value) Iterator {
+func (s *FileRangeSet) Insert(gap FileRangeGapIterator, r __generics_imported0.MappableRange, val uint64) FileRangeIterator {
if r.Length() <= 0 {
panic(fmt.Sprintf("invalid segment range %v", r))
}
@@ -326,12 +266,12 @@ func (s *Set) Insert(gap GapIterator, r Range, val Value) Iterator {
panic(fmt.Sprintf("new segment %v overlaps successor %v", r, next.Range()))
}
if prev.Ok() && prev.End() == r.Start {
- if mval, ok := (Functions{}).Merge(prev.Range(), prev.Value(), r, val); ok {
+ if mval, ok := (fileRangeSetFunctions{}).Merge(prev.Range(), prev.Value(), r, val); ok {
prev.SetEndUnchecked(r.End)
prev.SetValue(mval)
if next.Ok() && next.Start() == r.End {
val = mval
- if mval, ok := (Functions{}).Merge(prev.Range(), val, next.Range(), next.Value()); ok {
+ if mval, ok := (fileRangeSetFunctions{}).Merge(prev.Range(), val, next.Range(), next.Value()); ok {
prev.SetEndUnchecked(next.End())
prev.SetValue(mval)
return s.Remove(next).PrevSegment()
@@ -341,7 +281,7 @@ func (s *Set) Insert(gap GapIterator, r Range, val Value) Iterator {
}
}
if next.Ok() && next.Start() == r.End {
- if mval, ok := (Functions{}).Merge(r, val, next.Range(), next.Value()); ok {
+ if mval, ok := (fileRangeSetFunctions{}).Merge(r, val, next.Range(), next.Value()); ok {
next.SetStartUnchecked(r.Start)
next.SetValue(mval)
return next
@@ -356,7 +296,7 @@ func (s *Set) Insert(gap GapIterator, r Range, val Value) Iterator {
//
// If the gap cannot accommodate the segment, or if r is invalid,
// InsertWithoutMerging panics.
-func (s *Set) InsertWithoutMerging(gap GapIterator, r Range, val Value) Iterator {
+func (s *FileRangeSet) InsertWithoutMerging(gap FileRangeGapIterator, r __generics_imported0.MappableRange, val uint64) FileRangeIterator {
if r.Length() <= 0 {
panic(fmt.Sprintf("invalid segment range %v", r))
}
@@ -371,52 +311,45 @@ func (s *Set) InsertWithoutMerging(gap GapIterator, r Range, val Value) Iterator
// (including gap, but not including the returned iterator) are invalidated.
//
// Preconditions: r.Start >= gap.Start(); r.End <= gap.End().
-func (s *Set) InsertWithoutMergingUnchecked(gap GapIterator, r Range, val Value) Iterator {
+func (s *FileRangeSet) InsertWithoutMergingUnchecked(gap FileRangeGapIterator, r __generics_imported0.MappableRange, val uint64) FileRangeIterator {
gap = gap.node.rebalanceBeforeInsert(gap)
copy(gap.node.keys[gap.index+1:], gap.node.keys[gap.index:gap.node.nrSegments])
copy(gap.node.values[gap.index+1:], gap.node.values[gap.index:gap.node.nrSegments])
gap.node.keys[gap.index] = r
gap.node.values[gap.index] = val
gap.node.nrSegments++
- return Iterator{gap.node, gap.index}
+ return FileRangeIterator{gap.node, gap.index}
}
// Remove removes the given segment and returns an iterator to the vacated gap.
// All existing iterators (including seg, but not including the returned
// iterator) are invalidated.
-func (s *Set) Remove(seg Iterator) GapIterator {
- // We only want to remove directly from a leaf node.
+func (s *FileRangeSet) Remove(seg FileRangeIterator) FileRangeGapIterator {
+
if seg.node.hasChildren {
- // Since seg.node has children, the removed segment must have a
- // predecessor (at the end of the rightmost leaf of its left child
- // subtree). Move the contents of that predecessor into the removed
- // segment's position, and remove that predecessor instead. (We choose
- // to steal the predecessor rather than the successor because removing
- // from the end of a leaf node doesn't involve any copying unless
- // merging is required.)
+
victim := seg.PrevSegment()
- // This must be unchecked since until victim is removed, seg and victim
- // overlap.
+
seg.SetRangeUnchecked(victim.Range())
seg.SetValue(victim.Value())
return s.Remove(victim).NextGap()
}
copy(seg.node.keys[seg.index:], seg.node.keys[seg.index+1:seg.node.nrSegments])
copy(seg.node.values[seg.index:], seg.node.values[seg.index+1:seg.node.nrSegments])
- Functions{}.ClearValue(&seg.node.values[seg.node.nrSegments-1])
+ fileRangeSetFunctions{}.ClearValue(&seg.node.values[seg.node.nrSegments-1])
seg.node.nrSegments--
- return seg.node.rebalanceAfterRemove(GapIterator{seg.node, seg.index})
+ return seg.node.rebalanceAfterRemove(FileRangeGapIterator{seg.node, seg.index})
}
// RemoveAll removes all segments from the set. All existing iterators are
// invalidated.
-func (s *Set) RemoveAll() {
- s.root = node{}
+func (s *FileRangeSet) RemoveAll() {
+ s.root = FileRangenode{}
}
// RemoveRange removes all segments in the given range. An iterator to the
// newly formed gap is returned, and all existing iterators are invalidated.
-func (s *Set) RemoveRange(r Range) GapIterator {
+func (s *FileRangeSet) RemoveRange(r __generics_imported0.MappableRange) FileRangeGapIterator {
seg, gap := s.Find(r.Start)
if seg.Ok() {
seg = s.Isolate(seg, r)
@@ -434,7 +367,7 @@ func (s *Set) RemoveRange(r Range) GapIterator {
// invalidated. Otherwise, Merge returns a terminal iterator.
//
// If first is not the predecessor of second, Merge panics.
-func (s *Set) Merge(first, second Iterator) Iterator {
+func (s *FileRangeSet) Merge(first, second FileRangeIterator) FileRangeIterator {
if first.NextSegment() != second {
panic(fmt.Sprintf("attempt to merge non-neighboring segments %v, %v", first.Range(), second.Range()))
}
@@ -448,22 +381,21 @@ func (s *Set) Merge(first, second Iterator) Iterator {
//
// Precondition: first is the predecessor of second: first.NextSegment() ==
// second, first == second.PrevSegment().
-func (s *Set) MergeUnchecked(first, second Iterator) Iterator {
+func (s *FileRangeSet) MergeUnchecked(first, second FileRangeIterator) FileRangeIterator {
if first.End() == second.Start() {
- if mval, ok := (Functions{}).Merge(first.Range(), first.Value(), second.Range(), second.Value()); ok {
- // N.B. This must be unchecked because until s.Remove(second), first
- // overlaps second.
+ if mval, ok := (fileRangeSetFunctions{}).Merge(first.Range(), first.Value(), second.Range(), second.Value()); ok {
+
first.SetEndUnchecked(second.End())
first.SetValue(mval)
return s.Remove(second).PrevSegment()
}
}
- return Iterator{}
+ return FileRangeIterator{}
}
// MergeAll attempts to merge all adjacent segments in the set. All existing
// iterators are invalidated.
-func (s *Set) MergeAll() {
+func (s *FileRangeSet) MergeAll() {
seg := s.FirstSegment()
if !seg.Ok() {
return
@@ -480,7 +412,7 @@ func (s *Set) MergeAll() {
// MergeRange attempts to merge all adjacent segments that contain a key in the
// specific range. All existing iterators are invalidated.
-func (s *Set) MergeRange(r Range) {
+func (s *FileRangeSet) MergeRange(r __generics_imported0.MappableRange) {
seg := s.LowerBoundSegment(r.Start)
if !seg.Ok() {
return
@@ -497,7 +429,7 @@ func (s *Set) MergeRange(r Range) {
// MergeAdjacent attempts to merge the segment containing r.Start with its
// predecessor, and the segment containing r.End-1 with its successor.
-func (s *Set) MergeAdjacent(r Range) {
+func (s *FileRangeSet) MergeAdjacent(r __generics_imported0.MappableRange) {
first := s.FindSegment(r.Start)
if first.Ok() {
if prev := first.PrevSegment(); prev.Ok() {
@@ -520,7 +452,7 @@ func (s *Set) MergeAdjacent(r Range) {
// end of the segment's range, so splitting would produce a segment with zero
// length, or because split falls outside the segment's range altogether),
// Split panics.
-func (s *Set) Split(seg Iterator, split Key) (Iterator, Iterator) {
+func (s *FileRangeSet) Split(seg FileRangeIterator, split uint64) (FileRangeIterator, FileRangeIterator) {
if !seg.Range().CanSplitAt(split) {
panic(fmt.Sprintf("can't split %v at %v", seg.Range(), split))
}
@@ -532,20 +464,20 @@ func (s *Set) Split(seg Iterator, split Key) (Iterator, Iterator) {
// seg, but not including the returned iterators) are invalidated.
//
// Preconditions: seg.Start() < key < seg.End().
-func (s *Set) SplitUnchecked(seg Iterator, split Key) (Iterator, Iterator) {
- val1, val2 := (Functions{}).Split(seg.Range(), seg.Value(), split)
+func (s *FileRangeSet) SplitUnchecked(seg FileRangeIterator, split uint64) (FileRangeIterator, FileRangeIterator) {
+ val1, val2 := (fileRangeSetFunctions{}).Split(seg.Range(), seg.Value(), split)
end2 := seg.End()
seg.SetEndUnchecked(split)
seg.SetValue(val1)
- seg2 := s.InsertWithoutMergingUnchecked(seg.NextGap(), Range{split, end2}, val2)
- // seg may now be invalid due to the Insert.
+ seg2 := s.InsertWithoutMergingUnchecked(seg.NextGap(), __generics_imported0.MappableRange{split, end2}, val2)
+
return seg2.PrevSegment(), seg2
}
// SplitAt splits the segment straddling split, if one exists. SplitAt returns
// true if a segment was split and false otherwise. If SplitAt splits a
// segment, all existing iterators are invalidated.
-func (s *Set) SplitAt(split Key) bool {
+func (s *FileRangeSet) SplitAt(split uint64) bool {
if seg := s.FindSegment(split); seg.Ok() && seg.Range().CanSplitAt(split) {
s.SplitUnchecked(seg, split)
return true
@@ -557,7 +489,7 @@ func (s *Set) SplitAt(split Key) bool {
// splitting at r.Start and r.End if necessary, and returns an updated iterator
// to the bounded segment. All existing iterators (including seg, but not
// including the returned iterators) are invalidated.
-func (s *Set) Isolate(seg Iterator, r Range) Iterator {
+func (s *FileRangeSet) Isolate(seg FileRangeIterator, r __generics_imported0.MappableRange) FileRangeIterator {
if seg.Range().CanSplitAt(r.Start) {
_, seg = s.SplitUnchecked(seg, r.Start)
}
@@ -574,7 +506,7 @@ func (s *Set) Isolate(seg Iterator, r Range) Iterator {
// are invalidated.
//
// N.B. The Iterator must not be invalidated by the function.
-func (s *Set) ApplyContiguous(r Range, fn func(seg Iterator)) GapIterator {
+func (s *FileRangeSet) ApplyContiguous(r __generics_imported0.MappableRange, fn func(seg FileRangeIterator)) FileRangeGapIterator {
seg, gap := s.Find(r.Start)
if !seg.Ok() {
return gap
@@ -583,7 +515,7 @@ func (s *Set) ApplyContiguous(r Range, fn func(seg Iterator)) GapIterator {
seg = s.Isolate(seg, r)
fn(seg)
if seg.End() >= r.End {
- return GapIterator{}
+ return FileRangeGapIterator{}
}
gap = seg.NextGap()
if !gap.IsEmpty() {
@@ -591,15 +523,14 @@ func (s *Set) ApplyContiguous(r Range, fn func(seg Iterator)) GapIterator {
}
seg = gap.NextSegment()
if !seg.Ok() {
- // This implies that the last segment extended all the
- // way to the maximum value, since the gap was empty.
- return GapIterator{}
+
+ return FileRangeGapIterator{}
}
}
}
// +stateify savable
-type node struct {
+type FileRangenode struct {
// An internal binary tree node looks like:
//
// K
@@ -621,7 +552,7 @@ type node struct {
// parent is a pointer to this node's parent. If this node is root, parent
// is nil.
- parent *node
+ parent *FileRangenode
// parentIndex is the index of this node in parent.children.
parentIndex int
@@ -633,39 +564,39 @@ type node struct {
// Nodes store keys and values in separate arrays to maximize locality in
// the common case (scanning keys for lookup).
- keys [maxDegree - 1]Range
- values [maxDegree - 1]Value
- children [maxDegree]*node
+ keys [FileRangemaxDegree - 1]__generics_imported0.MappableRange
+ values [FileRangemaxDegree - 1]uint64
+ children [FileRangemaxDegree]*FileRangenode
}
// firstSegment returns the first segment in the subtree rooted by n.
//
// Preconditions: n.nrSegments != 0.
-func (n *node) firstSegment() Iterator {
+func (n *FileRangenode) firstSegment() FileRangeIterator {
for n.hasChildren {
n = n.children[0]
}
- return Iterator{n, 0}
+ return FileRangeIterator{n, 0}
}
// lastSegment returns the last segment in the subtree rooted by n.
//
// Preconditions: n.nrSegments != 0.
-func (n *node) lastSegment() Iterator {
+func (n *FileRangenode) lastSegment() FileRangeIterator {
for n.hasChildren {
n = n.children[n.nrSegments]
}
- return Iterator{n, n.nrSegments - 1}
+ return FileRangeIterator{n, n.nrSegments - 1}
}
-func (n *node) prevSibling() *node {
+func (n *FileRangenode) prevSibling() *FileRangenode {
if n.parent == nil || n.parentIndex == 0 {
return nil
}
return n.parent.children[n.parentIndex-1]
}
-func (n *node) nextSibling() *node {
+func (n *FileRangenode) nextSibling() *FileRangenode {
if n.parent == nil || n.parentIndex == n.parent.nrSegments {
return nil
}
@@ -675,40 +606,38 @@ func (n *node) nextSibling() *node {
// rebalanceBeforeInsert splits n and its ancestors if they are full, as
// required for insertion, and returns an updated iterator to the position
// represented by gap.
-func (n *node) rebalanceBeforeInsert(gap GapIterator) GapIterator {
+func (n *FileRangenode) rebalanceBeforeInsert(gap FileRangeGapIterator) FileRangeGapIterator {
if n.parent != nil {
gap = n.parent.rebalanceBeforeInsert(gap)
}
- if n.nrSegments < maxDegree-1 {
+ if n.nrSegments < FileRangemaxDegree-1 {
return gap
}
if n.parent == nil {
- // n is root. Move all segments before and after n's median segment
- // into new child nodes adjacent to the median segment, which is now
- // the only segment in root.
- left := &node{
- nrSegments: minDegree - 1,
+
+ left := &FileRangenode{
+ nrSegments: FileRangeminDegree - 1,
parent: n,
parentIndex: 0,
hasChildren: n.hasChildren,
}
- right := &node{
- nrSegments: minDegree - 1,
+ right := &FileRangenode{
+ nrSegments: FileRangeminDegree - 1,
parent: n,
parentIndex: 1,
hasChildren: n.hasChildren,
}
- copy(left.keys[:minDegree-1], n.keys[:minDegree-1])
- copy(left.values[:minDegree-1], n.values[:minDegree-1])
- copy(right.keys[:minDegree-1], n.keys[minDegree:])
- copy(right.values[:minDegree-1], n.values[minDegree:])
- n.keys[0], n.values[0] = n.keys[minDegree-1], n.values[minDegree-1]
- zeroValueSlice(n.values[1:])
+ copy(left.keys[:FileRangeminDegree-1], n.keys[:FileRangeminDegree-1])
+ copy(left.values[:FileRangeminDegree-1], n.values[:FileRangeminDegree-1])
+ copy(right.keys[:FileRangeminDegree-1], n.keys[FileRangeminDegree:])
+ copy(right.values[:FileRangeminDegree-1], n.values[FileRangeminDegree:])
+ n.keys[0], n.values[0] = n.keys[FileRangeminDegree-1], n.values[FileRangeminDegree-1]
+ FileRangezeroValueSlice(n.values[1:])
if n.hasChildren {
- copy(left.children[:minDegree], n.children[:minDegree])
- copy(right.children[:minDegree], n.children[minDegree:])
- zeroNodeSlice(n.children[2:])
- for i := 0; i < minDegree; i++ {
+ copy(left.children[:FileRangeminDegree], n.children[:FileRangeminDegree])
+ copy(right.children[:FileRangeminDegree], n.children[FileRangeminDegree:])
+ FileRangezeroNodeSlice(n.children[2:])
+ for i := 0; i < FileRangeminDegree; i++ {
left.children[i].parent = left
left.children[i].parentIndex = i
right.children[i].parent = right
@@ -722,50 +651,47 @@ func (n *node) rebalanceBeforeInsert(gap GapIterator) GapIterator {
if gap.node != n {
return gap
}
- if gap.index < minDegree {
- return GapIterator{left, gap.index}
+ if gap.index < FileRangeminDegree {
+ return FileRangeGapIterator{left, gap.index}
}
- return GapIterator{right, gap.index - minDegree}
+ return FileRangeGapIterator{right, gap.index - FileRangeminDegree}
}
- // n is non-root. Move n's median segment into its parent node (which can't
- // be full because we've already invoked n.parent.rebalanceBeforeInsert)
- // and move all segments after n's median into a new sibling node (the
- // median segment's right child subtree).
+
copy(n.parent.keys[n.parentIndex+1:], n.parent.keys[n.parentIndex:n.parent.nrSegments])
copy(n.parent.values[n.parentIndex+1:], n.parent.values[n.parentIndex:n.parent.nrSegments])
- n.parent.keys[n.parentIndex], n.parent.values[n.parentIndex] = n.keys[minDegree-1], n.values[minDegree-1]
+ n.parent.keys[n.parentIndex], n.parent.values[n.parentIndex] = n.keys[FileRangeminDegree-1], n.values[FileRangeminDegree-1]
copy(n.parent.children[n.parentIndex+2:], n.parent.children[n.parentIndex+1:n.parent.nrSegments+1])
for i := n.parentIndex + 2; i < n.parent.nrSegments+2; i++ {
n.parent.children[i].parentIndex = i
}
- sibling := &node{
- nrSegments: minDegree - 1,
+ sibling := &FileRangenode{
+ nrSegments: FileRangeminDegree - 1,
parent: n.parent,
parentIndex: n.parentIndex + 1,
hasChildren: n.hasChildren,
}
n.parent.children[n.parentIndex+1] = sibling
n.parent.nrSegments++
- copy(sibling.keys[:minDegree-1], n.keys[minDegree:])
- copy(sibling.values[:minDegree-1], n.values[minDegree:])
- zeroValueSlice(n.values[minDegree-1:])
+ copy(sibling.keys[:FileRangeminDegree-1], n.keys[FileRangeminDegree:])
+ copy(sibling.values[:FileRangeminDegree-1], n.values[FileRangeminDegree:])
+ FileRangezeroValueSlice(n.values[FileRangeminDegree-1:])
if n.hasChildren {
- copy(sibling.children[:minDegree], n.children[minDegree:])
- zeroNodeSlice(n.children[minDegree:])
- for i := 0; i < minDegree; i++ {
+ copy(sibling.children[:FileRangeminDegree], n.children[FileRangeminDegree:])
+ FileRangezeroNodeSlice(n.children[FileRangeminDegree:])
+ for i := 0; i < FileRangeminDegree; i++ {
sibling.children[i].parent = sibling
sibling.children[i].parentIndex = i
}
}
- n.nrSegments = minDegree - 1
- // gap.node can't be n.parent because gaps are always in leaf nodes.
+ n.nrSegments = FileRangeminDegree - 1
+
if gap.node != n {
return gap
}
- if gap.index < minDegree {
+ if gap.index < FileRangeminDegree {
return gap
}
- return GapIterator{sibling, gap.index - minDegree}
+ return FileRangeGapIterator{sibling, gap.index - FileRangeminDegree}
}
// rebalanceAfterRemove "unsplits" n and its ancestors if they are deficient
@@ -774,41 +700,24 @@ func (n *node) rebalanceBeforeInsert(gap GapIterator) GapIterator {
//
// Precondition: n is the only node in the tree that may currently violate a
// B-tree invariant.
-func (n *node) rebalanceAfterRemove(gap GapIterator) GapIterator {
+func (n *FileRangenode) rebalanceAfterRemove(gap FileRangeGapIterator) FileRangeGapIterator {
for {
- if n.nrSegments >= minDegree-1 {
+ if n.nrSegments >= FileRangeminDegree-1 {
return gap
}
if n.parent == nil {
- // Root is allowed to be deficient.
+
return gap
}
- // There's one other thing we can do before resorting to unsplitting.
- // If either sibling node has at least minDegree segments, rotate that
- // sibling's closest segment through the segment in the parent that
- // separates us. That is, given:
- //
- // ... D ...
- // / \
- // ... B C] [E ...
- //
- // where the node containing E is deficient, end up with:
- //
- // ... C ...
- // / \
- // ... B] [D E ...
- //
- // As in Set.Remove, prefer rotating from the end of the sibling to the
- // left: by precondition, n.node has fewer segments (to memcpy) than
- // the sibling does.
- if sibling := n.prevSibling(); sibling != nil && sibling.nrSegments >= minDegree {
+
+ if sibling := n.prevSibling(); sibling != nil && sibling.nrSegments >= FileRangeminDegree {
copy(n.keys[1:], n.keys[:n.nrSegments])
copy(n.values[1:], n.values[:n.nrSegments])
n.keys[0] = n.parent.keys[n.parentIndex-1]
n.values[0] = n.parent.values[n.parentIndex-1]
n.parent.keys[n.parentIndex-1] = sibling.keys[sibling.nrSegments-1]
n.parent.values[n.parentIndex-1] = sibling.values[sibling.nrSegments-1]
- Functions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ fileRangeSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
if n.hasChildren {
copy(n.children[1:], n.children[:n.nrSegments+1])
n.children[0] = sibling.children[sibling.nrSegments]
@@ -822,21 +731,21 @@ func (n *node) rebalanceAfterRemove(gap GapIterator) GapIterator {
n.nrSegments++
sibling.nrSegments--
if gap.node == sibling && gap.index == sibling.nrSegments {
- return GapIterator{n, 0}
+ return FileRangeGapIterator{n, 0}
}
if gap.node == n {
- return GapIterator{n, gap.index + 1}
+ return FileRangeGapIterator{n, gap.index + 1}
}
return gap
}
- if sibling := n.nextSibling(); sibling != nil && sibling.nrSegments >= minDegree {
+ if sibling := n.nextSibling(); sibling != nil && sibling.nrSegments >= FileRangeminDegree {
n.keys[n.nrSegments] = n.parent.keys[n.parentIndex]
n.values[n.nrSegments] = n.parent.values[n.parentIndex]
n.parent.keys[n.parentIndex] = sibling.keys[0]
n.parent.values[n.parentIndex] = sibling.values[0]
copy(sibling.keys[:sibling.nrSegments-1], sibling.keys[1:])
copy(sibling.values[:sibling.nrSegments-1], sibling.values[1:])
- Functions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ fileRangeSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
if n.hasChildren {
n.children[n.nrSegments+1] = sibling.children[0]
copy(sibling.children[:sibling.nrSegments], sibling.children[1:])
@@ -851,21 +760,16 @@ func (n *node) rebalanceAfterRemove(gap GapIterator) GapIterator {
sibling.nrSegments--
if gap.node == sibling {
if gap.index == 0 {
- return GapIterator{n, n.nrSegments}
+ return FileRangeGapIterator{n, n.nrSegments}
}
- return GapIterator{sibling, gap.index - 1}
+ return FileRangeGapIterator{sibling, gap.index - 1}
}
return gap
}
- // Otherwise, we must unsplit.
+
p := n.parent
if p.nrSegments == 1 {
- // Merge all segments in both n and its sibling back into n.parent.
- // This is the reverse of the root splitting case in
- // node.rebalanceBeforeInsert. (Because we require minDegree >= 3,
- // only root can have 1 segment in this path, so this reduces the
- // height of the tree by 1, without violating the constraint that
- // all leaf nodes remain at the same depth.)
+
left, right := p.children[0], p.children[1]
p.nrSegments = left.nrSegments + right.nrSegments + 1
p.hasChildren = left.hasChildren
@@ -887,10 +791,10 @@ func (n *node) rebalanceAfterRemove(gap GapIterator) GapIterator {
p.children[1] = nil
}
if gap.node == left {
- return GapIterator{p, gap.index}
+ return FileRangeGapIterator{p, gap.index}
}
if gap.node == right {
- return GapIterator{p, gap.index + left.nrSegments + 1}
+ return FileRangeGapIterator{p, gap.index + left.nrSegments + 1}
}
return gap
}
@@ -898,7 +802,7 @@ func (n *node) rebalanceAfterRemove(gap GapIterator) GapIterator {
// two, into whichever of the two nodes comes first. This is the
// reverse of the non-root splitting case in
// node.rebalanceBeforeInsert.
- var left, right *node
+ var left, right *FileRangenode
if n.parentIndex > 0 {
left = n.prevSibling()
right = n
@@ -906,10 +810,9 @@ func (n *node) rebalanceAfterRemove(gap GapIterator) GapIterator {
left = n
right = n.nextSibling()
}
- // Fix up gap first since we need the old left.nrSegments, which
- // merging will change.
+
if gap.node == right {
- gap = GapIterator{left, gap.index + left.nrSegments + 1}
+ gap = FileRangeGapIterator{left, gap.index + left.nrSegments + 1}
}
left.keys[left.nrSegments] = p.keys[left.parentIndex]
left.values[left.nrSegments] = p.values[left.parentIndex]
@@ -925,14 +828,14 @@ func (n *node) rebalanceAfterRemove(gap GapIterator) GapIterator {
left.nrSegments += right.nrSegments + 1
copy(p.keys[left.parentIndex:], p.keys[left.parentIndex+1:p.nrSegments])
copy(p.values[left.parentIndex:], p.values[left.parentIndex+1:p.nrSegments])
- Functions{}.ClearValue(&p.values[p.nrSegments-1])
+ fileRangeSetFunctions{}.ClearValue(&p.values[p.nrSegments-1])
copy(p.children[left.parentIndex+1:], p.children[left.parentIndex+2:p.nrSegments+1])
for i := 0; i < p.nrSegments; i++ {
p.children[i].parentIndex = i
}
p.children[p.nrSegments] = nil
p.nrSegments--
- // This process robs p of one segment, so recurse into rebalancing p.
+
n = p
}
}
@@ -949,10 +852,10 @@ func (n *node) rebalanceAfterRemove(gap GapIterator) GapIterator {
//
// Unless otherwise specified, any mutation of a set invalidates all existing
// iterators into the set.
-type Iterator struct {
+type FileRangeIterator struct {
// node is the node containing the iterated segment. If the iterator is
// terminal, node is nil.
- node *node
+ node *FileRangenode
// index is the index of the segment in node.keys/values.
index int
@@ -960,24 +863,24 @@ type Iterator struct {
// Ok returns true if the iterator is not terminal. All other methods are only
// valid for non-terminal iterators.
-func (seg Iterator) Ok() bool {
+func (seg FileRangeIterator) Ok() bool {
return seg.node != nil
}
// Range returns the iterated segment's range key.
-func (seg Iterator) Range() Range {
+func (seg FileRangeIterator) Range() __generics_imported0.MappableRange {
return seg.node.keys[seg.index]
}
// Start is equivalent to Range().Start, but should be preferred if only the
// start of the range is needed.
-func (seg Iterator) Start() Key {
+func (seg FileRangeIterator) Start() uint64 {
return seg.node.keys[seg.index].Start
}
// End is equivalent to Range().End, but should be preferred if only the end of
// the range is needed.
-func (seg Iterator) End() Key {
+func (seg FileRangeIterator) End() uint64 {
return seg.node.keys[seg.index].End
}
@@ -991,7 +894,7 @@ func (seg Iterator) End() Key {
// - The new range must not overlap an existing one: If seg.NextSegment().Ok(),
// then r.end <= seg.NextSegment().Start(); if seg.PrevSegment().Ok(), then
// r.start >= seg.PrevSegment().End().
-func (seg Iterator) SetRangeUnchecked(r Range) {
+func (seg FileRangeIterator) SetRangeUnchecked(r __generics_imported0.MappableRange) {
seg.node.keys[seg.index] = r
}
@@ -999,7 +902,7 @@ func (seg Iterator) SetRangeUnchecked(r Range) {
// cause the iterated segment to overlap another segment, or if the new range
// is invalid, SetRange panics. This operation does not invalidate any
// iterators.
-func (seg Iterator) SetRange(r Range) {
+func (seg FileRangeIterator) SetRange(r __generics_imported0.MappableRange) {
if r.Length() <= 0 {
panic(fmt.Sprintf("invalid segment range %v", r))
}
@@ -1017,7 +920,7 @@ func (seg Iterator) SetRange(r Range) {
//
// Preconditions: The new start must be valid: start < seg.End(); if
// seg.PrevSegment().Ok(), then start >= seg.PrevSegment().End().
-func (seg Iterator) SetStartUnchecked(start Key) {
+func (seg FileRangeIterator) SetStartUnchecked(start uint64) {
seg.node.keys[seg.index].Start = start
}
@@ -1025,7 +928,7 @@ func (seg Iterator) SetStartUnchecked(start Key) {
// cause the iterated segment to overlap another segment, or would result in an
// invalid range, SetStart panics. This operation does not invalidate any
// iterators.
-func (seg Iterator) SetStart(start Key) {
+func (seg FileRangeIterator) SetStart(start uint64) {
if start >= seg.End() {
panic(fmt.Sprintf("new start %v would invalidate segment range %v", start, seg.Range()))
}
@@ -1040,7 +943,7 @@ func (seg Iterator) SetStart(start Key) {
//
// Preconditions: The new end must be valid: end > seg.Start(); if
// seg.NextSegment().Ok(), then end <= seg.NextSegment().Start().
-func (seg Iterator) SetEndUnchecked(end Key) {
+func (seg FileRangeIterator) SetEndUnchecked(end uint64) {
seg.node.keys[seg.index].End = end
}
@@ -1048,7 +951,7 @@ func (seg Iterator) SetEndUnchecked(end Key) {
// the iterated segment to overlap another segment, or would result in an
// invalid range, SetEnd panics. This operation does not invalidate any
// iterators.
-func (seg Iterator) SetEnd(end Key) {
+func (seg FileRangeIterator) SetEnd(end uint64) {
if end <= seg.Start() {
panic(fmt.Sprintf("new end %v would invalidate segment range %v", end, seg.Range()))
}
@@ -1059,69 +962,68 @@ func (seg Iterator) SetEnd(end Key) {
}
// Value returns a copy of the iterated segment's value.
-func (seg Iterator) Value() Value {
+func (seg FileRangeIterator) Value() uint64 {
return seg.node.values[seg.index]
}
// ValuePtr returns a pointer to the iterated segment's value. The pointer is
// invalidated if the iterator is invalidated. This operation does not
// invalidate any iterators.
-func (seg Iterator) ValuePtr() *Value {
+func (seg FileRangeIterator) ValuePtr() *uint64 {
return &seg.node.values[seg.index]
}
// SetValue mutates the iterated segment's value. This operation does not
// invalidate any iterators.
-func (seg Iterator) SetValue(val Value) {
+func (seg FileRangeIterator) SetValue(val uint64) {
seg.node.values[seg.index] = val
}
// PrevSegment returns the iterated segment's predecessor. If there is no
// preceding segment, PrevSegment returns a terminal iterator.
-func (seg Iterator) PrevSegment() Iterator {
+func (seg FileRangeIterator) PrevSegment() FileRangeIterator {
if seg.node.hasChildren {
return seg.node.children[seg.index].lastSegment()
}
if seg.index > 0 {
- return Iterator{seg.node, seg.index - 1}
+ return FileRangeIterator{seg.node, seg.index - 1}
}
if seg.node.parent == nil {
- return Iterator{}
+ return FileRangeIterator{}
}
- return segmentBeforePosition(seg.node.parent, seg.node.parentIndex)
+ return FileRangesegmentBeforePosition(seg.node.parent, seg.node.parentIndex)
}
// NextSegment returns the iterated segment's successor. If there is no
// succeeding segment, NextSegment returns a terminal iterator.
-func (seg Iterator) NextSegment() Iterator {
+func (seg FileRangeIterator) NextSegment() FileRangeIterator {
if seg.node.hasChildren {
return seg.node.children[seg.index+1].firstSegment()
}
if seg.index < seg.node.nrSegments-1 {
- return Iterator{seg.node, seg.index + 1}
+ return FileRangeIterator{seg.node, seg.index + 1}
}
if seg.node.parent == nil {
- return Iterator{}
+ return FileRangeIterator{}
}
- return segmentAfterPosition(seg.node.parent, seg.node.parentIndex)
+ return FileRangesegmentAfterPosition(seg.node.parent, seg.node.parentIndex)
}
// PrevGap returns the gap immediately before the iterated segment.
-func (seg Iterator) PrevGap() GapIterator {
+func (seg FileRangeIterator) PrevGap() FileRangeGapIterator {
if seg.node.hasChildren {
- // Note that this isn't recursive because the last segment in a subtree
- // must be in a leaf node.
+
return seg.node.children[seg.index].lastSegment().NextGap()
}
- return GapIterator{seg.node, seg.index}
+ return FileRangeGapIterator{seg.node, seg.index}
}
// NextGap returns the gap immediately after the iterated segment.
-func (seg Iterator) NextGap() GapIterator {
+func (seg FileRangeIterator) NextGap() FileRangeGapIterator {
if seg.node.hasChildren {
return seg.node.children[seg.index+1].firstSegment().PrevGap()
}
- return GapIterator{seg.node, seg.index + 1}
+ return FileRangeGapIterator{seg.node, seg.index + 1}
}
// PrevNonEmpty returns the iterated segment's predecessor if it is adjacent,
@@ -1129,12 +1031,12 @@ func (seg Iterator) NextGap() GapIterator {
// Functions.MinKey(), PrevNonEmpty will return two terminal iterators.
// Otherwise, exactly one of the iterators returned by PrevNonEmpty will be
// non-terminal.
-func (seg Iterator) PrevNonEmpty() (Iterator, GapIterator) {
+func (seg FileRangeIterator) PrevNonEmpty() (FileRangeIterator, FileRangeGapIterator) {
gap := seg.PrevGap()
if gap.Range().Length() != 0 {
- return Iterator{}, gap
+ return FileRangeIterator{}, gap
}
- return gap.PrevSegment(), GapIterator{}
+ return gap.PrevSegment(), FileRangeGapIterator{}
}
// NextNonEmpty returns the iterated segment's successor if it is adjacent, or
@@ -1142,12 +1044,12 @@ func (seg Iterator) PrevNonEmpty() (Iterator, GapIterator) {
// Functions.MaxKey(), NextNonEmpty will return two terminal iterators.
// Otherwise, exactly one of the iterators returned by NextNonEmpty will be
// non-terminal.
-func (seg Iterator) NextNonEmpty() (Iterator, GapIterator) {
+func (seg FileRangeIterator) NextNonEmpty() (FileRangeIterator, FileRangeGapIterator) {
gap := seg.NextGap()
if gap.Range().Length() != 0 {
- return Iterator{}, gap
+ return FileRangeIterator{}, gap
}
- return gap.NextSegment(), GapIterator{}
+ return gap.NextSegment(), FileRangeGapIterator{}
}
// A GapIterator is conceptually one of:
@@ -1168,77 +1070,77 @@ func (seg Iterator) NextNonEmpty() (Iterator, GapIterator) {
//
// Unless otherwise specified, any mutation of a set invalidates all existing
// iterators into the set.
-type GapIterator struct {
+type FileRangeGapIterator struct {
// The representation of a GapIterator is identical to that of an Iterator,
// except that index corresponds to positions between segments in the same
// way as for node.children (see comment for node.nrSegments).
- node *node
+ node *FileRangenode
index int
}
// Ok returns true if the iterator is not terminal. All other methods are only
// valid for non-terminal iterators.
-func (gap GapIterator) Ok() bool {
+func (gap FileRangeGapIterator) Ok() bool {
return gap.node != nil
}
// Range returns the range spanned by the iterated gap.
-func (gap GapIterator) Range() Range {
- return Range{gap.Start(), gap.End()}
+func (gap FileRangeGapIterator) Range() __generics_imported0.MappableRange {
+ return __generics_imported0.MappableRange{gap.Start(), gap.End()}
}
// Start is equivalent to Range().Start, but should be preferred if only the
// start of the range is needed.
-func (gap GapIterator) Start() Key {
+func (gap FileRangeGapIterator) Start() uint64 {
if ps := gap.PrevSegment(); ps.Ok() {
return ps.End()
}
- return Functions{}.MinKey()
+ return fileRangeSetFunctions{}.MinKey()
}
// End is equivalent to Range().End, but should be preferred if only the end of
// the range is needed.
-func (gap GapIterator) End() Key {
+func (gap FileRangeGapIterator) End() uint64 {
if ns := gap.NextSegment(); ns.Ok() {
return ns.Start()
}
- return Functions{}.MaxKey()
+ return fileRangeSetFunctions{}.MaxKey()
}
// IsEmpty returns true if the iterated gap is empty (that is, the "gap" is
// between two adjacent segments.)
-func (gap GapIterator) IsEmpty() bool {
+func (gap FileRangeGapIterator) IsEmpty() bool {
return gap.Range().Length() == 0
}
// PrevSegment returns the segment immediately before the iterated gap. If no
// such segment exists, PrevSegment returns a terminal iterator.
-func (gap GapIterator) PrevSegment() Iterator {
- return segmentBeforePosition(gap.node, gap.index)
+func (gap FileRangeGapIterator) PrevSegment() FileRangeIterator {
+ return FileRangesegmentBeforePosition(gap.node, gap.index)
}
// NextSegment returns the segment immediately after the iterated gap. If no
// such segment exists, NextSegment returns a terminal iterator.
-func (gap GapIterator) NextSegment() Iterator {
- return segmentAfterPosition(gap.node, gap.index)
+func (gap FileRangeGapIterator) NextSegment() FileRangeIterator {
+ return FileRangesegmentAfterPosition(gap.node, gap.index)
}
// PrevGap returns the iterated gap's predecessor. If no such gap exists,
// PrevGap returns a terminal iterator.
-func (gap GapIterator) PrevGap() GapIterator {
+func (gap FileRangeGapIterator) PrevGap() FileRangeGapIterator {
seg := gap.PrevSegment()
if !seg.Ok() {
- return GapIterator{}
+ return FileRangeGapIterator{}
}
return seg.PrevGap()
}
// NextGap returns the iterated gap's successor. If no such gap exists, NextGap
// returns a terminal iterator.
-func (gap GapIterator) NextGap() GapIterator {
+func (gap FileRangeGapIterator) NextGap() FileRangeGapIterator {
seg := gap.NextSegment()
if !seg.Ok() {
- return GapIterator{}
+ return FileRangeGapIterator{}
}
return seg.NextGap()
}
@@ -1246,56 +1148,55 @@ func (gap GapIterator) NextGap() GapIterator {
// segmentBeforePosition returns the predecessor segment of the position given
// by n.children[i], which may or may not contain a child. If no such segment
// exists, segmentBeforePosition returns a terminal iterator.
-func segmentBeforePosition(n *node, i int) Iterator {
+func FileRangesegmentBeforePosition(n *FileRangenode, i int) FileRangeIterator {
for i == 0 {
if n.parent == nil {
- return Iterator{}
+ return FileRangeIterator{}
}
n, i = n.parent, n.parentIndex
}
- return Iterator{n, i - 1}
+ return FileRangeIterator{n, i - 1}
}
// segmentAfterPosition returns the successor segment of the position given by
// n.children[i], which may or may not contain a child. If no such segment
// exists, segmentAfterPosition returns a terminal iterator.
-func segmentAfterPosition(n *node, i int) Iterator {
+func FileRangesegmentAfterPosition(n *FileRangenode, i int) FileRangeIterator {
for i == n.nrSegments {
if n.parent == nil {
- return Iterator{}
+ return FileRangeIterator{}
}
n, i = n.parent, n.parentIndex
}
- return Iterator{n, i}
+ return FileRangeIterator{n, i}
}
-func zeroValueSlice(slice []Value) {
- // TODO(jamieliu): check if Go is actually smart enough to optimize a
- // ClearValue that assigns nil to a memset here
+func FileRangezeroValueSlice(slice []uint64) {
+
for i := range slice {
- Functions{}.ClearValue(&slice[i])
+ fileRangeSetFunctions{}.ClearValue(&slice[i])
}
}
-func zeroNodeSlice(slice []*node) {
+func FileRangezeroNodeSlice(slice []*FileRangenode) {
for i := range slice {
slice[i] = nil
}
}
// String stringifies a Set for debugging.
-func (s *Set) String() string {
+func (s *FileRangeSet) String() string {
return s.root.String()
}
// String stringifies a node (and all of its children) for debugging.
-func (n *node) String() string {
+func (n *FileRangenode) String() string {
var buf bytes.Buffer
n.writeDebugString(&buf, "")
return buf.String()
}
-func (n *node) writeDebugString(buf *bytes.Buffer, prefix string) {
+func (n *FileRangenode) writeDebugString(buf *bytes.Buffer, prefix string) {
if n.hasChildren != (n.nrSegments > 0 && n.children[0] != nil) {
buf.WriteString(prefix)
buf.WriteString(fmt.Sprintf("WARNING: inconsistent value of hasChildren: got %v, want %v\n", n.hasChildren, !n.hasChildren))
@@ -1322,16 +1223,16 @@ func (n *node) writeDebugString(buf *bytes.Buffer, prefix string) {
// for save/restore and the layout here is optimized for that.
//
// +stateify savable
-type SegmentDataSlices struct {
- Start []Key
- End []Key
- Values []Value
+type FileRangeSegmentDataSlices struct {
+ Start []uint64
+ End []uint64
+ Values []uint64
}
// ExportSortedSlice returns a copy of all segments in the given set, in ascending
// key order.
-func (s *Set) ExportSortedSlices() *SegmentDataSlices {
- var sds SegmentDataSlices
+func (s *FileRangeSet) ExportSortedSlices() *FileRangeSegmentDataSlices {
+ var sds FileRangeSegmentDataSlices
for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
sds.Start = append(sds.Start, seg.Start())
sds.End = append(sds.End, seg.End())
@@ -1348,13 +1249,13 @@ func (s *Set) ExportSortedSlices() *SegmentDataSlices {
// Preconditions: s must be empty. sds must represent a valid set (the segments
// in sds must have valid lengths that do not overlap). The segments in sds
// must be sorted in ascending key order.
-func (s *Set) ImportSortedSlices(sds *SegmentDataSlices) error {
+func (s *FileRangeSet) ImportSortedSlices(sds *FileRangeSegmentDataSlices) error {
if !s.IsEmpty() {
return fmt.Errorf("cannot import into non-empty set %v", s)
}
gap := s.FirstGap()
for i := range sds.Start {
- r := Range{sds.Start[i], sds.End[i]}
+ r := __generics_imported0.MappableRange{sds.Start[i], sds.End[i]}
if !gap.Range().IsSupersetOf(r) {
return fmt.Errorf("segment overlaps a preceding segment or is incorrectly sorted: [%d, %d) => %v", sds.Start[i], sds.End[i], sds.Values[i])
}
@@ -1362,3 +1263,12 @@ func (s *Set) ImportSortedSlices(sds *SegmentDataSlices) error {
}
return nil
}
+func (s *FileRangeSet) saveRoot() *FileRangeSegmentDataSlices {
+ return s.ExportSortedSlices()
+}
+
+func (s *FileRangeSet) loadRoot(sds *FileRangeSegmentDataSlices) {
+ if err := s.ImportSortedSlices(sds); err != nil {
+ panic(err)
+ }
+}
diff --git a/pkg/sentry/fs/fsutil/frame_ref_set_impl.go b/pkg/sentry/fs/fsutil/frame_ref_set_impl.go
new file mode 100755
index 000000000..d4601bffa
--- /dev/null
+++ b/pkg/sentry/fs/fsutil/frame_ref_set_impl.go
@@ -0,0 +1,1274 @@
+package fsutil
+
+import (
+ __generics_imported0 "gvisor.dev/gvisor/pkg/sentry/platform"
+)
+
+import (
+ "bytes"
+ "fmt"
+)
+
+const (
+ // minDegree is the minimum degree of an internal node in a Set B-tree.
+ //
+ // - Any non-root node has at least minDegree-1 segments.
+ //
+ // - Any non-root internal (non-leaf) node has at least minDegree children.
+ //
+ // - The root node may have fewer than minDegree-1 segments, but it may
+ // only have 0 segments if the tree is empty.
+ //
+ // Our implementation requires minDegree >= 3. Higher values of minDegree
+ // usually improve performance, but increase memory usage for small sets.
+ frameRefminDegree = 3
+
+ frameRefmaxDegree = 2 * frameRefminDegree
+)
+
+// A Set is a mapping of segments with non-overlapping Range keys. The zero
+// value for a Set is an empty set. Set values are not safely movable nor
+// copyable. Set is thread-compatible.
+//
+// +stateify savable
+type frameRefSet struct {
+ root frameRefnode `state:".(*frameRefSegmentDataSlices)"`
+}
+
+// IsEmpty returns true if the set contains no segments.
+func (s *frameRefSet) IsEmpty() bool {
+ return s.root.nrSegments == 0
+}
+
+// IsEmptyRange returns true iff no segments in the set overlap the given
+// range. This is semantically equivalent to s.SpanRange(r) == 0, but may be
+// more efficient.
+func (s *frameRefSet) IsEmptyRange(r __generics_imported0.FileRange) bool {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return true
+ }
+ _, gap := s.Find(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ return r.End <= gap.End()
+}
+
+// Span returns the total size of all segments in the set.
+func (s *frameRefSet) Span() uint64 {
+ var sz uint64
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sz += seg.Range().Length()
+ }
+ return sz
+}
+
+// SpanRange returns the total size of the intersection of segments in the set
+// with the given range.
+func (s *frameRefSet) SpanRange(r __generics_imported0.FileRange) uint64 {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return 0
+ }
+ var sz uint64
+ for seg := s.LowerBoundSegment(r.Start); seg.Ok() && seg.Start() < r.End; seg = seg.NextSegment() {
+ sz += seg.Range().Intersect(r).Length()
+ }
+ return sz
+}
+
+// FirstSegment returns the first segment in the set. If the set is empty,
+// FirstSegment returns a terminal iterator.
+func (s *frameRefSet) FirstSegment() frameRefIterator {
+ if s.root.nrSegments == 0 {
+ return frameRefIterator{}
+ }
+ return s.root.firstSegment()
+}
+
+// LastSegment returns the last segment in the set. If the set is empty,
+// LastSegment returns a terminal iterator.
+func (s *frameRefSet) LastSegment() frameRefIterator {
+ if s.root.nrSegments == 0 {
+ return frameRefIterator{}
+ }
+ return s.root.lastSegment()
+}
+
+// FirstGap returns the first gap in the set.
+func (s *frameRefSet) FirstGap() frameRefGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return frameRefGapIterator{n, 0}
+}
+
+// LastGap returns the last gap in the set.
+func (s *frameRefSet) LastGap() frameRefGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return frameRefGapIterator{n, n.nrSegments}
+}
+
+// Find returns the segment or gap whose range contains the given key. If a
+// segment is found, the returned Iterator is non-terminal and the
+// returned GapIterator is terminal. Otherwise, the returned Iterator is
+// terminal and the returned GapIterator is non-terminal.
+func (s *frameRefSet) Find(key uint64) (frameRefIterator, frameRefGapIterator) {
+ n := &s.root
+ for {
+
+ lower := 0
+ upper := n.nrSegments
+ for lower < upper {
+ i := lower + (upper-lower)/2
+ if r := n.keys[i]; key < r.End {
+ if key >= r.Start {
+ return frameRefIterator{n, i}, frameRefGapIterator{}
+ }
+ upper = i
+ } else {
+ lower = i + 1
+ }
+ }
+ i := lower
+ if !n.hasChildren {
+ return frameRefIterator{}, frameRefGapIterator{n, i}
+ }
+ n = n.children[i]
+ }
+}
+
+// FindSegment returns the segment whose range contains the given key. If no
+// such segment exists, FindSegment returns a terminal iterator.
+func (s *frameRefSet) FindSegment(key uint64) frameRefIterator {
+ seg, _ := s.Find(key)
+ return seg
+}
+
+// LowerBoundSegment returns the segment with the lowest range that contains a
+// key greater than or equal to min. If no such segment exists,
+// LowerBoundSegment returns a terminal iterator.
+func (s *frameRefSet) LowerBoundSegment(min uint64) frameRefIterator {
+ seg, gap := s.Find(min)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.NextSegment()
+}
+
+// UpperBoundSegment returns the segment with the highest range that contains a
+// key less than or equal to max. If no such segment exists, UpperBoundSegment
+// returns a terminal iterator.
+func (s *frameRefSet) UpperBoundSegment(max uint64) frameRefIterator {
+ seg, gap := s.Find(max)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.PrevSegment()
+}
+
+// FindGap returns the gap containing the given key. If no such gap exists
+// (i.e. the set contains a segment containing that key), FindGap returns a
+// terminal iterator.
+func (s *frameRefSet) FindGap(key uint64) frameRefGapIterator {
+ _, gap := s.Find(key)
+ return gap
+}
+
+// LowerBoundGap returns the gap with the lowest range that is greater than or
+// equal to min.
+func (s *frameRefSet) LowerBoundGap(min uint64) frameRefGapIterator {
+ seg, gap := s.Find(min)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.NextGap()
+}
+
+// UpperBoundGap returns the gap with the highest range that is less than or
+// equal to max.
+func (s *frameRefSet) UpperBoundGap(max uint64) frameRefGapIterator {
+ seg, gap := s.Find(max)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.PrevGap()
+}
+
+// Add inserts the given segment into the set and returns true. If the new
+// segment can be merged with adjacent segments, Add will do so. If the new
+// segment would overlap an existing segment, Add returns false. If Add
+// succeeds, all existing iterators are invalidated.
+func (s *frameRefSet) Add(r __generics_imported0.FileRange, val uint64) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.Insert(gap, r, val)
+ return true
+}
+
+// AddWithoutMerging inserts the given segment into the set and returns true.
+// If it would overlap an existing segment, AddWithoutMerging does nothing and
+// returns false. If AddWithoutMerging succeeds, all existing iterators are
+// invalidated.
+func (s *frameRefSet) AddWithoutMerging(r __generics_imported0.FileRange, val uint64) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.InsertWithoutMergingUnchecked(gap, r, val)
+ return true
+}
+
+// Insert inserts the given segment into the given gap. If the new segment can
+// be merged with adjacent segments, Insert will do so. Insert returns an
+// iterator to the segment containing the inserted value (which may have been
+// merged with other values). All existing iterators (including gap, but not
+// including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid, Insert panics.
+//
+// Insert is semantically equivalent to a InsertWithoutMerging followed by a
+// Merge, but may be more efficient. Note that there is no unchecked variant of
+// Insert since Insert must retrieve and inspect gap's predecessor and
+// successor segments regardless.
+func (s *frameRefSet) Insert(gap frameRefGapIterator, r __generics_imported0.FileRange, val uint64) frameRefIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ prev, next := gap.PrevSegment(), gap.NextSegment()
+ if prev.Ok() && prev.End() > r.Start {
+ panic(fmt.Sprintf("new segment %v overlaps predecessor %v", r, prev.Range()))
+ }
+ if next.Ok() && next.Start() < r.End {
+ panic(fmt.Sprintf("new segment %v overlaps successor %v", r, next.Range()))
+ }
+ if prev.Ok() && prev.End() == r.Start {
+ if mval, ok := (frameRefSetFunctions{}).Merge(prev.Range(), prev.Value(), r, val); ok {
+ prev.SetEndUnchecked(r.End)
+ prev.SetValue(mval)
+ if next.Ok() && next.Start() == r.End {
+ val = mval
+ if mval, ok := (frameRefSetFunctions{}).Merge(prev.Range(), val, next.Range(), next.Value()); ok {
+ prev.SetEndUnchecked(next.End())
+ prev.SetValue(mval)
+ return s.Remove(next).PrevSegment()
+ }
+ }
+ return prev
+ }
+ }
+ if next.Ok() && next.Start() == r.End {
+ if mval, ok := (frameRefSetFunctions{}).Merge(r, val, next.Range(), next.Value()); ok {
+ next.SetStartUnchecked(r.Start)
+ next.SetValue(mval)
+ return next
+ }
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMerging inserts the given segment into the given gap and
+// returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid,
+// InsertWithoutMerging panics.
+func (s *frameRefSet) InsertWithoutMerging(gap frameRefGapIterator, r __generics_imported0.FileRange, val uint64) frameRefIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if gr := gap.Range(); !gr.IsSupersetOf(r) {
+ panic(fmt.Sprintf("cannot insert segment range %v into gap range %v", r, gr))
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMergingUnchecked inserts the given segment into the given gap
+// and returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// Preconditions: r.Start >= gap.Start(); r.End <= gap.End().
+func (s *frameRefSet) InsertWithoutMergingUnchecked(gap frameRefGapIterator, r __generics_imported0.FileRange, val uint64) frameRefIterator {
+ gap = gap.node.rebalanceBeforeInsert(gap)
+ copy(gap.node.keys[gap.index+1:], gap.node.keys[gap.index:gap.node.nrSegments])
+ copy(gap.node.values[gap.index+1:], gap.node.values[gap.index:gap.node.nrSegments])
+ gap.node.keys[gap.index] = r
+ gap.node.values[gap.index] = val
+ gap.node.nrSegments++
+ return frameRefIterator{gap.node, gap.index}
+}
+
+// Remove removes the given segment and returns an iterator to the vacated gap.
+// All existing iterators (including seg, but not including the returned
+// iterator) are invalidated.
+func (s *frameRefSet) Remove(seg frameRefIterator) frameRefGapIterator {
+
+ if seg.node.hasChildren {
+
+ victim := seg.PrevSegment()
+
+ seg.SetRangeUnchecked(victim.Range())
+ seg.SetValue(victim.Value())
+ return s.Remove(victim).NextGap()
+ }
+ copy(seg.node.keys[seg.index:], seg.node.keys[seg.index+1:seg.node.nrSegments])
+ copy(seg.node.values[seg.index:], seg.node.values[seg.index+1:seg.node.nrSegments])
+ frameRefSetFunctions{}.ClearValue(&seg.node.values[seg.node.nrSegments-1])
+ seg.node.nrSegments--
+ return seg.node.rebalanceAfterRemove(frameRefGapIterator{seg.node, seg.index})
+}
+
+// RemoveAll removes all segments from the set. All existing iterators are
+// invalidated.
+func (s *frameRefSet) RemoveAll() {
+ s.root = frameRefnode{}
+}
+
+// RemoveRange removes all segments in the given range. An iterator to the
+// newly formed gap is returned, and all existing iterators are invalidated.
+func (s *frameRefSet) RemoveRange(r __generics_imported0.FileRange) frameRefGapIterator {
+ seg, gap := s.Find(r.Start)
+ if seg.Ok() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ for seg = gap.NextSegment(); seg.Ok() && seg.Start() < r.End; seg = gap.NextSegment() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ return gap
+}
+
+// Merge attempts to merge two neighboring segments. If successful, Merge
+// returns an iterator to the merged segment, and all existing iterators are
+// invalidated. Otherwise, Merge returns a terminal iterator.
+//
+// If first is not the predecessor of second, Merge panics.
+func (s *frameRefSet) Merge(first, second frameRefIterator) frameRefIterator {
+ if first.NextSegment() != second {
+ panic(fmt.Sprintf("attempt to merge non-neighboring segments %v, %v", first.Range(), second.Range()))
+ }
+ return s.MergeUnchecked(first, second)
+}
+
+// MergeUnchecked attempts to merge two neighboring segments. If successful,
+// MergeUnchecked returns an iterator to the merged segment, and all existing
+// iterators are invalidated. Otherwise, MergeUnchecked returns a terminal
+// iterator.
+//
+// Precondition: first is the predecessor of second: first.NextSegment() ==
+// second, first == second.PrevSegment().
+func (s *frameRefSet) MergeUnchecked(first, second frameRefIterator) frameRefIterator {
+ if first.End() == second.Start() {
+ if mval, ok := (frameRefSetFunctions{}).Merge(first.Range(), first.Value(), second.Range(), second.Value()); ok {
+
+ first.SetEndUnchecked(second.End())
+ first.SetValue(mval)
+ return s.Remove(second).PrevSegment()
+ }
+ }
+ return frameRefIterator{}
+}
+
+// MergeAll attempts to merge all adjacent segments in the set. All existing
+// iterators are invalidated.
+func (s *frameRefSet) MergeAll() {
+ seg := s.FirstSegment()
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeRange attempts to merge all adjacent segments that contain a key in the
+// specific range. All existing iterators are invalidated.
+func (s *frameRefSet) MergeRange(r __generics_imported0.FileRange) {
+ seg := s.LowerBoundSegment(r.Start)
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() && next.Range().Start < r.End {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeAdjacent attempts to merge the segment containing r.Start with its
+// predecessor, and the segment containing r.End-1 with its successor.
+func (s *frameRefSet) MergeAdjacent(r __generics_imported0.FileRange) {
+ first := s.FindSegment(r.Start)
+ if first.Ok() {
+ if prev := first.PrevSegment(); prev.Ok() {
+ s.Merge(prev, first)
+ }
+ }
+ last := s.FindSegment(r.End - 1)
+ if last.Ok() {
+ if next := last.NextSegment(); next.Ok() {
+ s.Merge(last, next)
+ }
+ }
+}
+
+// Split splits the given segment at the given key and returns iterators to the
+// two resulting segments. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+//
+// If the segment cannot be split at split (because split is at the start or
+// end of the segment's range, so splitting would produce a segment with zero
+// length, or because split falls outside the segment's range altogether),
+// Split panics.
+func (s *frameRefSet) Split(seg frameRefIterator, split uint64) (frameRefIterator, frameRefIterator) {
+ if !seg.Range().CanSplitAt(split) {
+ panic(fmt.Sprintf("can't split %v at %v", seg.Range(), split))
+ }
+ return s.SplitUnchecked(seg, split)
+}
+
+// SplitUnchecked splits the given segment at the given key and returns
+// iterators to the two resulting segments. All existing iterators (including
+// seg, but not including the returned iterators) are invalidated.
+//
+// Preconditions: seg.Start() < key < seg.End().
+func (s *frameRefSet) SplitUnchecked(seg frameRefIterator, split uint64) (frameRefIterator, frameRefIterator) {
+ val1, val2 := (frameRefSetFunctions{}).Split(seg.Range(), seg.Value(), split)
+ end2 := seg.End()
+ seg.SetEndUnchecked(split)
+ seg.SetValue(val1)
+ seg2 := s.InsertWithoutMergingUnchecked(seg.NextGap(), __generics_imported0.FileRange{split, end2}, val2)
+
+ return seg2.PrevSegment(), seg2
+}
+
+// SplitAt splits the segment straddling split, if one exists. SplitAt returns
+// true if a segment was split and false otherwise. If SplitAt splits a
+// segment, all existing iterators are invalidated.
+func (s *frameRefSet) SplitAt(split uint64) bool {
+ if seg := s.FindSegment(split); seg.Ok() && seg.Range().CanSplitAt(split) {
+ s.SplitUnchecked(seg, split)
+ return true
+ }
+ return false
+}
+
+// Isolate ensures that the given segment's range does not escape r by
+// splitting at r.Start and r.End if necessary, and returns an updated iterator
+// to the bounded segment. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+func (s *frameRefSet) Isolate(seg frameRefIterator, r __generics_imported0.FileRange) frameRefIterator {
+ if seg.Range().CanSplitAt(r.Start) {
+ _, seg = s.SplitUnchecked(seg, r.Start)
+ }
+ if seg.Range().CanSplitAt(r.End) {
+ seg, _ = s.SplitUnchecked(seg, r.End)
+ }
+ return seg
+}
+
+// ApplyContiguous applies a function to a contiguous range of segments,
+// splitting if necessary. The function is applied until the first gap is
+// encountered, at which point the gap is returned. If the function is applied
+// across the entire range, a terminal gap is returned. All existing iterators
+// are invalidated.
+//
+// N.B. The Iterator must not be invalidated by the function.
+func (s *frameRefSet) ApplyContiguous(r __generics_imported0.FileRange, fn func(seg frameRefIterator)) frameRefGapIterator {
+ seg, gap := s.Find(r.Start)
+ if !seg.Ok() {
+ return gap
+ }
+ for {
+ seg = s.Isolate(seg, r)
+ fn(seg)
+ if seg.End() >= r.End {
+ return frameRefGapIterator{}
+ }
+ gap = seg.NextGap()
+ if !gap.IsEmpty() {
+ return gap
+ }
+ seg = gap.NextSegment()
+ if !seg.Ok() {
+
+ return frameRefGapIterator{}
+ }
+ }
+}
+
+// +stateify savable
+type frameRefnode struct {
+ // An internal binary tree node looks like:
+ //
+ // K
+ // / \
+ // Cl Cr
+ //
+ // where all keys in the subtree rooted by Cl (the left subtree) are less
+ // than K (the key of the parent node), and all keys in the subtree rooted
+ // by Cr (the right subtree) are greater than K.
+ //
+ // An internal B-tree node's indexes work out to look like:
+ //
+ // K0 K1 K2 ... Kn-1
+ // / \/ \/ \ ... / \
+ // C0 C1 C2 C3 ... Cn-1 Cn
+ //
+ // where n is nrSegments.
+ nrSegments int
+
+ // parent is a pointer to this node's parent. If this node is root, parent
+ // is nil.
+ parent *frameRefnode
+
+ // parentIndex is the index of this node in parent.children.
+ parentIndex int
+
+ // Flag for internal nodes that is technically redundant with "children[0]
+ // != nil", but is stored in the first cache line. "hasChildren" rather
+ // than "isLeaf" because false must be the correct value for an empty root.
+ hasChildren bool
+
+ // Nodes store keys and values in separate arrays to maximize locality in
+ // the common case (scanning keys for lookup).
+ keys [frameRefmaxDegree - 1]__generics_imported0.FileRange
+ values [frameRefmaxDegree - 1]uint64
+ children [frameRefmaxDegree]*frameRefnode
+}
+
+// firstSegment returns the first segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *frameRefnode) firstSegment() frameRefIterator {
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return frameRefIterator{n, 0}
+}
+
+// lastSegment returns the last segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *frameRefnode) lastSegment() frameRefIterator {
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return frameRefIterator{n, n.nrSegments - 1}
+}
+
+func (n *frameRefnode) prevSibling() *frameRefnode {
+ if n.parent == nil || n.parentIndex == 0 {
+ return nil
+ }
+ return n.parent.children[n.parentIndex-1]
+}
+
+func (n *frameRefnode) nextSibling() *frameRefnode {
+ if n.parent == nil || n.parentIndex == n.parent.nrSegments {
+ return nil
+ }
+ return n.parent.children[n.parentIndex+1]
+}
+
+// rebalanceBeforeInsert splits n and its ancestors if they are full, as
+// required for insertion, and returns an updated iterator to the position
+// represented by gap.
+func (n *frameRefnode) rebalanceBeforeInsert(gap frameRefGapIterator) frameRefGapIterator {
+ if n.parent != nil {
+ gap = n.parent.rebalanceBeforeInsert(gap)
+ }
+ if n.nrSegments < frameRefmaxDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ left := &frameRefnode{
+ nrSegments: frameRefminDegree - 1,
+ parent: n,
+ parentIndex: 0,
+ hasChildren: n.hasChildren,
+ }
+ right := &frameRefnode{
+ nrSegments: frameRefminDegree - 1,
+ parent: n,
+ parentIndex: 1,
+ hasChildren: n.hasChildren,
+ }
+ copy(left.keys[:frameRefminDegree-1], n.keys[:frameRefminDegree-1])
+ copy(left.values[:frameRefminDegree-1], n.values[:frameRefminDegree-1])
+ copy(right.keys[:frameRefminDegree-1], n.keys[frameRefminDegree:])
+ copy(right.values[:frameRefminDegree-1], n.values[frameRefminDegree:])
+ n.keys[0], n.values[0] = n.keys[frameRefminDegree-1], n.values[frameRefminDegree-1]
+ frameRefzeroValueSlice(n.values[1:])
+ if n.hasChildren {
+ copy(left.children[:frameRefminDegree], n.children[:frameRefminDegree])
+ copy(right.children[:frameRefminDegree], n.children[frameRefminDegree:])
+ frameRefzeroNodeSlice(n.children[2:])
+ for i := 0; i < frameRefminDegree; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ right.children[i].parent = right
+ right.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = 1
+ n.hasChildren = true
+ n.children[0] = left
+ n.children[1] = right
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < frameRefminDegree {
+ return frameRefGapIterator{left, gap.index}
+ }
+ return frameRefGapIterator{right, gap.index - frameRefminDegree}
+ }
+
+ copy(n.parent.keys[n.parentIndex+1:], n.parent.keys[n.parentIndex:n.parent.nrSegments])
+ copy(n.parent.values[n.parentIndex+1:], n.parent.values[n.parentIndex:n.parent.nrSegments])
+ n.parent.keys[n.parentIndex], n.parent.values[n.parentIndex] = n.keys[frameRefminDegree-1], n.values[frameRefminDegree-1]
+ copy(n.parent.children[n.parentIndex+2:], n.parent.children[n.parentIndex+1:n.parent.nrSegments+1])
+ for i := n.parentIndex + 2; i < n.parent.nrSegments+2; i++ {
+ n.parent.children[i].parentIndex = i
+ }
+ sibling := &frameRefnode{
+ nrSegments: frameRefminDegree - 1,
+ parent: n.parent,
+ parentIndex: n.parentIndex + 1,
+ hasChildren: n.hasChildren,
+ }
+ n.parent.children[n.parentIndex+1] = sibling
+ n.parent.nrSegments++
+ copy(sibling.keys[:frameRefminDegree-1], n.keys[frameRefminDegree:])
+ copy(sibling.values[:frameRefminDegree-1], n.values[frameRefminDegree:])
+ frameRefzeroValueSlice(n.values[frameRefminDegree-1:])
+ if n.hasChildren {
+ copy(sibling.children[:frameRefminDegree], n.children[frameRefminDegree:])
+ frameRefzeroNodeSlice(n.children[frameRefminDegree:])
+ for i := 0; i < frameRefminDegree; i++ {
+ sibling.children[i].parent = sibling
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = frameRefminDegree - 1
+
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < frameRefminDegree {
+ return gap
+ }
+ return frameRefGapIterator{sibling, gap.index - frameRefminDegree}
+}
+
+// rebalanceAfterRemove "unsplits" n and its ancestors if they are deficient
+// (contain fewer segments than required by B-tree invariants), as required for
+// removal, and returns an updated iterator to the position represented by gap.
+//
+// Precondition: n is the only node in the tree that may currently violate a
+// B-tree invariant.
+func (n *frameRefnode) rebalanceAfterRemove(gap frameRefGapIterator) frameRefGapIterator {
+ for {
+ if n.nrSegments >= frameRefminDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ return gap
+ }
+
+ if sibling := n.prevSibling(); sibling != nil && sibling.nrSegments >= frameRefminDegree {
+ copy(n.keys[1:], n.keys[:n.nrSegments])
+ copy(n.values[1:], n.values[:n.nrSegments])
+ n.keys[0] = n.parent.keys[n.parentIndex-1]
+ n.values[0] = n.parent.values[n.parentIndex-1]
+ n.parent.keys[n.parentIndex-1] = sibling.keys[sibling.nrSegments-1]
+ n.parent.values[n.parentIndex-1] = sibling.values[sibling.nrSegments-1]
+ frameRefSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ copy(n.children[1:], n.children[:n.nrSegments+1])
+ n.children[0] = sibling.children[sibling.nrSegments]
+ sibling.children[sibling.nrSegments] = nil
+ n.children[0].parent = n
+ n.children[0].parentIndex = 0
+ for i := 1; i < n.nrSegments+2; i++ {
+ n.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling && gap.index == sibling.nrSegments {
+ return frameRefGapIterator{n, 0}
+ }
+ if gap.node == n {
+ return frameRefGapIterator{n, gap.index + 1}
+ }
+ return gap
+ }
+ if sibling := n.nextSibling(); sibling != nil && sibling.nrSegments >= frameRefminDegree {
+ n.keys[n.nrSegments] = n.parent.keys[n.parentIndex]
+ n.values[n.nrSegments] = n.parent.values[n.parentIndex]
+ n.parent.keys[n.parentIndex] = sibling.keys[0]
+ n.parent.values[n.parentIndex] = sibling.values[0]
+ copy(sibling.keys[:sibling.nrSegments-1], sibling.keys[1:])
+ copy(sibling.values[:sibling.nrSegments-1], sibling.values[1:])
+ frameRefSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ n.children[n.nrSegments+1] = sibling.children[0]
+ copy(sibling.children[:sibling.nrSegments], sibling.children[1:])
+ sibling.children[sibling.nrSegments] = nil
+ n.children[n.nrSegments+1].parent = n
+ n.children[n.nrSegments+1].parentIndex = n.nrSegments + 1
+ for i := 0; i < sibling.nrSegments; i++ {
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling {
+ if gap.index == 0 {
+ return frameRefGapIterator{n, n.nrSegments}
+ }
+ return frameRefGapIterator{sibling, gap.index - 1}
+ }
+ return gap
+ }
+
+ p := n.parent
+ if p.nrSegments == 1 {
+
+ left, right := p.children[0], p.children[1]
+ p.nrSegments = left.nrSegments + right.nrSegments + 1
+ p.hasChildren = left.hasChildren
+ p.keys[left.nrSegments] = p.keys[0]
+ p.values[left.nrSegments] = p.values[0]
+ copy(p.keys[:left.nrSegments], left.keys[:left.nrSegments])
+ copy(p.values[:left.nrSegments], left.values[:left.nrSegments])
+ copy(p.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(p.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(p.children[:left.nrSegments+1], left.children[:left.nrSegments+1])
+ copy(p.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := 0; i < p.nrSegments+1; i++ {
+ p.children[i].parent = p
+ p.children[i].parentIndex = i
+ }
+ } else {
+ p.children[0] = nil
+ p.children[1] = nil
+ }
+ if gap.node == left {
+ return frameRefGapIterator{p, gap.index}
+ }
+ if gap.node == right {
+ return frameRefGapIterator{p, gap.index + left.nrSegments + 1}
+ }
+ return gap
+ }
+ // Merge n and either sibling, along with the segment separating the
+ // two, into whichever of the two nodes comes first. This is the
+ // reverse of the non-root splitting case in
+ // node.rebalanceBeforeInsert.
+ var left, right *frameRefnode
+ if n.parentIndex > 0 {
+ left = n.prevSibling()
+ right = n
+ } else {
+ left = n
+ right = n.nextSibling()
+ }
+
+ if gap.node == right {
+ gap = frameRefGapIterator{left, gap.index + left.nrSegments + 1}
+ }
+ left.keys[left.nrSegments] = p.keys[left.parentIndex]
+ left.values[left.nrSegments] = p.values[left.parentIndex]
+ copy(left.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(left.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(left.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := left.nrSegments + 1; i < left.nrSegments+right.nrSegments+2; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ }
+ }
+ left.nrSegments += right.nrSegments + 1
+ copy(p.keys[left.parentIndex:], p.keys[left.parentIndex+1:p.nrSegments])
+ copy(p.values[left.parentIndex:], p.values[left.parentIndex+1:p.nrSegments])
+ frameRefSetFunctions{}.ClearValue(&p.values[p.nrSegments-1])
+ copy(p.children[left.parentIndex+1:], p.children[left.parentIndex+2:p.nrSegments+1])
+ for i := 0; i < p.nrSegments; i++ {
+ p.children[i].parentIndex = i
+ }
+ p.children[p.nrSegments] = nil
+ p.nrSegments--
+
+ n = p
+ }
+}
+
+// A Iterator is conceptually one of:
+//
+// - A pointer to a segment in a set; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Iterators are copyable values and are meaningfully equality-comparable. The
+// zero value of Iterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type frameRefIterator struct {
+ // node is the node containing the iterated segment. If the iterator is
+ // terminal, node is nil.
+ node *frameRefnode
+
+ // index is the index of the segment in node.keys/values.
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (seg frameRefIterator) Ok() bool {
+ return seg.node != nil
+}
+
+// Range returns the iterated segment's range key.
+func (seg frameRefIterator) Range() __generics_imported0.FileRange {
+ return seg.node.keys[seg.index]
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (seg frameRefIterator) Start() uint64 {
+ return seg.node.keys[seg.index].Start
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (seg frameRefIterator) End() uint64 {
+ return seg.node.keys[seg.index].End
+}
+
+// SetRangeUnchecked mutates the iterated segment's range key. This operation
+// does not invalidate any iterators.
+//
+// Preconditions:
+//
+// - r.Length() > 0.
+//
+// - The new range must not overlap an existing one: If seg.NextSegment().Ok(),
+// then r.end <= seg.NextSegment().Start(); if seg.PrevSegment().Ok(), then
+// r.start >= seg.PrevSegment().End().
+func (seg frameRefIterator) SetRangeUnchecked(r __generics_imported0.FileRange) {
+ seg.node.keys[seg.index] = r
+}
+
+// SetRange mutates the iterated segment's range key. If the new range would
+// cause the iterated segment to overlap another segment, or if the new range
+// is invalid, SetRange panics. This operation does not invalidate any
+// iterators.
+func (seg frameRefIterator) SetRange(r __generics_imported0.FileRange) {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && r.Start < prev.End() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, prev.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && r.End > next.Start() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, next.Range()))
+ }
+ seg.SetRangeUnchecked(r)
+}
+
+// SetStartUnchecked mutates the iterated segment's start. This operation does
+// not invalidate any iterators.
+//
+// Preconditions: The new start must be valid: start < seg.End(); if
+// seg.PrevSegment().Ok(), then start >= seg.PrevSegment().End().
+func (seg frameRefIterator) SetStartUnchecked(start uint64) {
+ seg.node.keys[seg.index].Start = start
+}
+
+// SetStart mutates the iterated segment's start. If the new start value would
+// cause the iterated segment to overlap another segment, or would result in an
+// invalid range, SetStart panics. This operation does not invalidate any
+// iterators.
+func (seg frameRefIterator) SetStart(start uint64) {
+ if start >= seg.End() {
+ panic(fmt.Sprintf("new start %v would invalidate segment range %v", start, seg.Range()))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && start < prev.End() {
+ panic(fmt.Sprintf("new start %v would cause segment range %v to overlap segment range %v", start, seg.Range(), prev.Range()))
+ }
+ seg.SetStartUnchecked(start)
+}
+
+// SetEndUnchecked mutates the iterated segment's end. This operation does not
+// invalidate any iterators.
+//
+// Preconditions: The new end must be valid: end > seg.Start(); if
+// seg.NextSegment().Ok(), then end <= seg.NextSegment().Start().
+func (seg frameRefIterator) SetEndUnchecked(end uint64) {
+ seg.node.keys[seg.index].End = end
+}
+
+// SetEnd mutates the iterated segment's end. If the new end value would cause
+// the iterated segment to overlap another segment, or would result in an
+// invalid range, SetEnd panics. This operation does not invalidate any
+// iterators.
+func (seg frameRefIterator) SetEnd(end uint64) {
+ if end <= seg.Start() {
+ panic(fmt.Sprintf("new end %v would invalidate segment range %v", end, seg.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && end > next.Start() {
+ panic(fmt.Sprintf("new end %v would cause segment range %v to overlap segment range %v", end, seg.Range(), next.Range()))
+ }
+ seg.SetEndUnchecked(end)
+}
+
+// Value returns a copy of the iterated segment's value.
+func (seg frameRefIterator) Value() uint64 {
+ return seg.node.values[seg.index]
+}
+
+// ValuePtr returns a pointer to the iterated segment's value. The pointer is
+// invalidated if the iterator is invalidated. This operation does not
+// invalidate any iterators.
+func (seg frameRefIterator) ValuePtr() *uint64 {
+ return &seg.node.values[seg.index]
+}
+
+// SetValue mutates the iterated segment's value. This operation does not
+// invalidate any iterators.
+func (seg frameRefIterator) SetValue(val uint64) {
+ seg.node.values[seg.index] = val
+}
+
+// PrevSegment returns the iterated segment's predecessor. If there is no
+// preceding segment, PrevSegment returns a terminal iterator.
+func (seg frameRefIterator) PrevSegment() frameRefIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index].lastSegment()
+ }
+ if seg.index > 0 {
+ return frameRefIterator{seg.node, seg.index - 1}
+ }
+ if seg.node.parent == nil {
+ return frameRefIterator{}
+ }
+ return frameRefsegmentBeforePosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// NextSegment returns the iterated segment's successor. If there is no
+// succeeding segment, NextSegment returns a terminal iterator.
+func (seg frameRefIterator) NextSegment() frameRefIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment()
+ }
+ if seg.index < seg.node.nrSegments-1 {
+ return frameRefIterator{seg.node, seg.index + 1}
+ }
+ if seg.node.parent == nil {
+ return frameRefIterator{}
+ }
+ return frameRefsegmentAfterPosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// PrevGap returns the gap immediately before the iterated segment.
+func (seg frameRefIterator) PrevGap() frameRefGapIterator {
+ if seg.node.hasChildren {
+
+ return seg.node.children[seg.index].lastSegment().NextGap()
+ }
+ return frameRefGapIterator{seg.node, seg.index}
+}
+
+// NextGap returns the gap immediately after the iterated segment.
+func (seg frameRefIterator) NextGap() frameRefGapIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment().PrevGap()
+ }
+ return frameRefGapIterator{seg.node, seg.index + 1}
+}
+
+// PrevNonEmpty returns the iterated segment's predecessor if it is adjacent,
+// or the gap before the iterated segment otherwise. If seg.Start() ==
+// Functions.MinKey(), PrevNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by PrevNonEmpty will be
+// non-terminal.
+func (seg frameRefIterator) PrevNonEmpty() (frameRefIterator, frameRefGapIterator) {
+ gap := seg.PrevGap()
+ if gap.Range().Length() != 0 {
+ return frameRefIterator{}, gap
+ }
+ return gap.PrevSegment(), frameRefGapIterator{}
+}
+
+// NextNonEmpty returns the iterated segment's successor if it is adjacent, or
+// the gap after the iterated segment otherwise. If seg.End() ==
+// Functions.MaxKey(), NextNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by NextNonEmpty will be
+// non-terminal.
+func (seg frameRefIterator) NextNonEmpty() (frameRefIterator, frameRefGapIterator) {
+ gap := seg.NextGap()
+ if gap.Range().Length() != 0 {
+ return frameRefIterator{}, gap
+ }
+ return gap.NextSegment(), frameRefGapIterator{}
+}
+
+// A GapIterator is conceptually one of:
+//
+// - A pointer to a position between two segments, before the first segment, or
+// after the last segment in a set, called a *gap*; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Note that the gap between two adjacent segments exists (iterators to it are
+// non-terminal), but has a length of zero. GapIterator.IsEmpty returns true
+// for such gaps. An empty set contains a single gap, spanning the entire range
+// of the set's keys.
+//
+// GapIterators are copyable values and are meaningfully equality-comparable.
+// The zero value of GapIterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type frameRefGapIterator struct {
+ // The representation of a GapIterator is identical to that of an Iterator,
+ // except that index corresponds to positions between segments in the same
+ // way as for node.children (see comment for node.nrSegments).
+ node *frameRefnode
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (gap frameRefGapIterator) Ok() bool {
+ return gap.node != nil
+}
+
+// Range returns the range spanned by the iterated gap.
+func (gap frameRefGapIterator) Range() __generics_imported0.FileRange {
+ return __generics_imported0.FileRange{gap.Start(), gap.End()}
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (gap frameRefGapIterator) Start() uint64 {
+ if ps := gap.PrevSegment(); ps.Ok() {
+ return ps.End()
+ }
+ return frameRefSetFunctions{}.MinKey()
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (gap frameRefGapIterator) End() uint64 {
+ if ns := gap.NextSegment(); ns.Ok() {
+ return ns.Start()
+ }
+ return frameRefSetFunctions{}.MaxKey()
+}
+
+// IsEmpty returns true if the iterated gap is empty (that is, the "gap" is
+// between two adjacent segments.)
+func (gap frameRefGapIterator) IsEmpty() bool {
+ return gap.Range().Length() == 0
+}
+
+// PrevSegment returns the segment immediately before the iterated gap. If no
+// such segment exists, PrevSegment returns a terminal iterator.
+func (gap frameRefGapIterator) PrevSegment() frameRefIterator {
+ return frameRefsegmentBeforePosition(gap.node, gap.index)
+}
+
+// NextSegment returns the segment immediately after the iterated gap. If no
+// such segment exists, NextSegment returns a terminal iterator.
+func (gap frameRefGapIterator) NextSegment() frameRefIterator {
+ return frameRefsegmentAfterPosition(gap.node, gap.index)
+}
+
+// PrevGap returns the iterated gap's predecessor. If no such gap exists,
+// PrevGap returns a terminal iterator.
+func (gap frameRefGapIterator) PrevGap() frameRefGapIterator {
+ seg := gap.PrevSegment()
+ if !seg.Ok() {
+ return frameRefGapIterator{}
+ }
+ return seg.PrevGap()
+}
+
+// NextGap returns the iterated gap's successor. If no such gap exists, NextGap
+// returns a terminal iterator.
+func (gap frameRefGapIterator) NextGap() frameRefGapIterator {
+ seg := gap.NextSegment()
+ if !seg.Ok() {
+ return frameRefGapIterator{}
+ }
+ return seg.NextGap()
+}
+
+// segmentBeforePosition returns the predecessor segment of the position given
+// by n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentBeforePosition returns a terminal iterator.
+func frameRefsegmentBeforePosition(n *frameRefnode, i int) frameRefIterator {
+ for i == 0 {
+ if n.parent == nil {
+ return frameRefIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return frameRefIterator{n, i - 1}
+}
+
+// segmentAfterPosition returns the successor segment of the position given by
+// n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentAfterPosition returns a terminal iterator.
+func frameRefsegmentAfterPosition(n *frameRefnode, i int) frameRefIterator {
+ for i == n.nrSegments {
+ if n.parent == nil {
+ return frameRefIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return frameRefIterator{n, i}
+}
+
+func frameRefzeroValueSlice(slice []uint64) {
+
+ for i := range slice {
+ frameRefSetFunctions{}.ClearValue(&slice[i])
+ }
+}
+
+func frameRefzeroNodeSlice(slice []*frameRefnode) {
+ for i := range slice {
+ slice[i] = nil
+ }
+}
+
+// String stringifies a Set for debugging.
+func (s *frameRefSet) String() string {
+ return s.root.String()
+}
+
+// String stringifies a node (and all of its children) for debugging.
+func (n *frameRefnode) String() string {
+ var buf bytes.Buffer
+ n.writeDebugString(&buf, "")
+ return buf.String()
+}
+
+func (n *frameRefnode) writeDebugString(buf *bytes.Buffer, prefix string) {
+ if n.hasChildren != (n.nrSegments > 0 && n.children[0] != nil) {
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent value of hasChildren: got %v, want %v\n", n.hasChildren, !n.hasChildren))
+ }
+ for i := 0; i < n.nrSegments; i++ {
+ if child := n.children[i]; child != nil {
+ cprefix := fmt.Sprintf("%s- % 3d ", prefix, i)
+ if child.parent != n || child.parentIndex != i {
+ buf.WriteString(cprefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent linkage to parent: got (%p, %d), want (%p, %d)\n", child.parent, child.parentIndex, n, i))
+ }
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, i))
+ }
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("- % 3d: %v => %v\n", i, n.keys[i], n.values[i]))
+ }
+ if child := n.children[n.nrSegments]; child != nil {
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, n.nrSegments))
+ }
+}
+
+// SegmentDataSlices represents segments from a set as slices of start, end, and
+// values. SegmentDataSlices is primarily used as an intermediate representation
+// for save/restore and the layout here is optimized for that.
+//
+// +stateify savable
+type frameRefSegmentDataSlices struct {
+ Start []uint64
+ End []uint64
+ Values []uint64
+}
+
+// ExportSortedSlice returns a copy of all segments in the given set, in ascending
+// key order.
+func (s *frameRefSet) ExportSortedSlices() *frameRefSegmentDataSlices {
+ var sds frameRefSegmentDataSlices
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sds.Start = append(sds.Start, seg.Start())
+ sds.End = append(sds.End, seg.End())
+ sds.Values = append(sds.Values, seg.Value())
+ }
+ sds.Start = sds.Start[:len(sds.Start):len(sds.Start)]
+ sds.End = sds.End[:len(sds.End):len(sds.End)]
+ sds.Values = sds.Values[:len(sds.Values):len(sds.Values)]
+ return &sds
+}
+
+// ImportSortedSlice initializes the given set from the given slice.
+//
+// Preconditions: s must be empty. sds must represent a valid set (the segments
+// in sds must have valid lengths that do not overlap). The segments in sds
+// must be sorted in ascending key order.
+func (s *frameRefSet) ImportSortedSlices(sds *frameRefSegmentDataSlices) error {
+ if !s.IsEmpty() {
+ return fmt.Errorf("cannot import into non-empty set %v", s)
+ }
+ gap := s.FirstGap()
+ for i := range sds.Start {
+ r := __generics_imported0.FileRange{sds.Start[i], sds.End[i]}
+ if !gap.Range().IsSupersetOf(r) {
+ return fmt.Errorf("segment overlaps a preceding segment or is incorrectly sorted: [%d, %d) => %v", sds.Start[i], sds.End[i], sds.Values[i])
+ }
+ gap = s.InsertWithoutMerging(gap, r, sds.Values[i]).NextGap()
+ }
+ return nil
+}
+func (s *frameRefSet) saveRoot() *frameRefSegmentDataSlices {
+ return s.ExportSortedSlices()
+}
+
+func (s *frameRefSet) loadRoot(sds *frameRefSegmentDataSlices) {
+ if err := s.ImportSortedSlices(sds); err != nil {
+ panic(err)
+ }
+}
diff --git a/pkg/sentry/fs/fsutil/fsutil_state_autogen.go b/pkg/sentry/fs/fsutil/fsutil_state_autogen.go
new file mode 100755
index 000000000..10671eb59
--- /dev/null
+++ b/pkg/sentry/fs/fsutil/fsutil_state_autogen.go
@@ -0,0 +1,363 @@
+// automatically generated by stateify.
+
+package fsutil
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *DirtyInfo) beforeSave() {}
+func (x *DirtyInfo) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Keep", &x.Keep)
+}
+
+func (x *DirtyInfo) afterLoad() {}
+func (x *DirtyInfo) load(m state.Map) {
+ m.Load("Keep", &x.Keep)
+}
+
+func (x *DirtySet) beforeSave() {}
+func (x *DirtySet) save(m state.Map) {
+ x.beforeSave()
+ var root *DirtySegmentDataSlices = x.saveRoot()
+ m.SaveValue("root", root)
+}
+
+func (x *DirtySet) afterLoad() {}
+func (x *DirtySet) load(m state.Map) {
+ m.LoadValue("root", new(*DirtySegmentDataSlices), func(y interface{}) { x.loadRoot(y.(*DirtySegmentDataSlices)) })
+}
+
+func (x *Dirtynode) beforeSave() {}
+func (x *Dirtynode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("nrSegments", &x.nrSegments)
+ m.Save("parent", &x.parent)
+ m.Save("parentIndex", &x.parentIndex)
+ m.Save("hasChildren", &x.hasChildren)
+ m.Save("keys", &x.keys)
+ m.Save("values", &x.values)
+ m.Save("children", &x.children)
+}
+
+func (x *Dirtynode) afterLoad() {}
+func (x *Dirtynode) load(m state.Map) {
+ m.Load("nrSegments", &x.nrSegments)
+ m.Load("parent", &x.parent)
+ m.Load("parentIndex", &x.parentIndex)
+ m.Load("hasChildren", &x.hasChildren)
+ m.Load("keys", &x.keys)
+ m.Load("values", &x.values)
+ m.Load("children", &x.children)
+}
+
+func (x *DirtySegmentDataSlices) beforeSave() {}
+func (x *DirtySegmentDataSlices) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Start", &x.Start)
+ m.Save("End", &x.End)
+ m.Save("Values", &x.Values)
+}
+
+func (x *DirtySegmentDataSlices) afterLoad() {}
+func (x *DirtySegmentDataSlices) load(m state.Map) {
+ m.Load("Start", &x.Start)
+ m.Load("End", &x.End)
+ m.Load("Values", &x.Values)
+}
+
+func (x *StaticDirFileOperations) beforeSave() {}
+func (x *StaticDirFileOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("dentryMap", &x.dentryMap)
+ m.Save("dirCursor", &x.dirCursor)
+}
+
+func (x *StaticDirFileOperations) afterLoad() {}
+func (x *StaticDirFileOperations) load(m state.Map) {
+ m.Load("dentryMap", &x.dentryMap)
+ m.Load("dirCursor", &x.dirCursor)
+}
+
+func (x *NoReadWriteFile) beforeSave() {}
+func (x *NoReadWriteFile) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *NoReadWriteFile) afterLoad() {}
+func (x *NoReadWriteFile) load(m state.Map) {
+}
+
+func (x *FileStaticContentReader) beforeSave() {}
+func (x *FileStaticContentReader) save(m state.Map) {
+ x.beforeSave()
+ m.Save("content", &x.content)
+}
+
+func (x *FileStaticContentReader) afterLoad() {}
+func (x *FileStaticContentReader) load(m state.Map) {
+ m.Load("content", &x.content)
+}
+
+func (x *FileRangeSet) beforeSave() {}
+func (x *FileRangeSet) save(m state.Map) {
+ x.beforeSave()
+ var root *FileRangeSegmentDataSlices = x.saveRoot()
+ m.SaveValue("root", root)
+}
+
+func (x *FileRangeSet) afterLoad() {}
+func (x *FileRangeSet) load(m state.Map) {
+ m.LoadValue("root", new(*FileRangeSegmentDataSlices), func(y interface{}) { x.loadRoot(y.(*FileRangeSegmentDataSlices)) })
+}
+
+func (x *FileRangenode) beforeSave() {}
+func (x *FileRangenode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("nrSegments", &x.nrSegments)
+ m.Save("parent", &x.parent)
+ m.Save("parentIndex", &x.parentIndex)
+ m.Save("hasChildren", &x.hasChildren)
+ m.Save("keys", &x.keys)
+ m.Save("values", &x.values)
+ m.Save("children", &x.children)
+}
+
+func (x *FileRangenode) afterLoad() {}
+func (x *FileRangenode) load(m state.Map) {
+ m.Load("nrSegments", &x.nrSegments)
+ m.Load("parent", &x.parent)
+ m.Load("parentIndex", &x.parentIndex)
+ m.Load("hasChildren", &x.hasChildren)
+ m.Load("keys", &x.keys)
+ m.Load("values", &x.values)
+ m.Load("children", &x.children)
+}
+
+func (x *FileRangeSegmentDataSlices) beforeSave() {}
+func (x *FileRangeSegmentDataSlices) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Start", &x.Start)
+ m.Save("End", &x.End)
+ m.Save("Values", &x.Values)
+}
+
+func (x *FileRangeSegmentDataSlices) afterLoad() {}
+func (x *FileRangeSegmentDataSlices) load(m state.Map) {
+ m.Load("Start", &x.Start)
+ m.Load("End", &x.End)
+ m.Load("Values", &x.Values)
+}
+
+func (x *frameRefSet) beforeSave() {}
+func (x *frameRefSet) save(m state.Map) {
+ x.beforeSave()
+ var root *frameRefSegmentDataSlices = x.saveRoot()
+ m.SaveValue("root", root)
+}
+
+func (x *frameRefSet) afterLoad() {}
+func (x *frameRefSet) load(m state.Map) {
+ m.LoadValue("root", new(*frameRefSegmentDataSlices), func(y interface{}) { x.loadRoot(y.(*frameRefSegmentDataSlices)) })
+}
+
+func (x *frameRefnode) beforeSave() {}
+func (x *frameRefnode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("nrSegments", &x.nrSegments)
+ m.Save("parent", &x.parent)
+ m.Save("parentIndex", &x.parentIndex)
+ m.Save("hasChildren", &x.hasChildren)
+ m.Save("keys", &x.keys)
+ m.Save("values", &x.values)
+ m.Save("children", &x.children)
+}
+
+func (x *frameRefnode) afterLoad() {}
+func (x *frameRefnode) load(m state.Map) {
+ m.Load("nrSegments", &x.nrSegments)
+ m.Load("parent", &x.parent)
+ m.Load("parentIndex", &x.parentIndex)
+ m.Load("hasChildren", &x.hasChildren)
+ m.Load("keys", &x.keys)
+ m.Load("values", &x.values)
+ m.Load("children", &x.children)
+}
+
+func (x *frameRefSegmentDataSlices) beforeSave() {}
+func (x *frameRefSegmentDataSlices) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Start", &x.Start)
+ m.Save("End", &x.End)
+ m.Save("Values", &x.Values)
+}
+
+func (x *frameRefSegmentDataSlices) afterLoad() {}
+func (x *frameRefSegmentDataSlices) load(m state.Map) {
+ m.Load("Start", &x.Start)
+ m.Load("End", &x.End)
+ m.Load("Values", &x.Values)
+}
+
+func (x *HostFileMapper) beforeSave() {}
+func (x *HostFileMapper) save(m state.Map) {
+ x.beforeSave()
+ m.Save("refs", &x.refs)
+}
+
+func (x *HostFileMapper) load(m state.Map) {
+ m.Load("refs", &x.refs)
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *HostMappable) beforeSave() {}
+func (x *HostMappable) save(m state.Map) {
+ x.beforeSave()
+ m.Save("hostFileMapper", &x.hostFileMapper)
+ m.Save("backingFile", &x.backingFile)
+ m.Save("mappings", &x.mappings)
+}
+
+func (x *HostMappable) afterLoad() {}
+func (x *HostMappable) load(m state.Map) {
+ m.Load("hostFileMapper", &x.hostFileMapper)
+ m.Load("backingFile", &x.backingFile)
+ m.Load("mappings", &x.mappings)
+}
+
+func (x *SimpleFileInode) beforeSave() {}
+func (x *SimpleFileInode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+}
+
+func (x *SimpleFileInode) afterLoad() {}
+func (x *SimpleFileInode) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+}
+
+func (x *NoReadWriteFileInode) beforeSave() {}
+func (x *NoReadWriteFileInode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+}
+
+func (x *NoReadWriteFileInode) afterLoad() {}
+func (x *NoReadWriteFileInode) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+}
+
+func (x *InodeSimpleAttributes) beforeSave() {}
+func (x *InodeSimpleAttributes) save(m state.Map) {
+ x.beforeSave()
+ m.Save("fsType", &x.fsType)
+ m.Save("unstable", &x.unstable)
+}
+
+func (x *InodeSimpleAttributes) afterLoad() {}
+func (x *InodeSimpleAttributes) load(m state.Map) {
+ m.Load("fsType", &x.fsType)
+ m.Load("unstable", &x.unstable)
+}
+
+func (x *InodeSimpleExtendedAttributes) beforeSave() {}
+func (x *InodeSimpleExtendedAttributes) save(m state.Map) {
+ x.beforeSave()
+ m.Save("xattrs", &x.xattrs)
+}
+
+func (x *InodeSimpleExtendedAttributes) afterLoad() {}
+func (x *InodeSimpleExtendedAttributes) load(m state.Map) {
+ m.Load("xattrs", &x.xattrs)
+}
+
+func (x *staticFile) beforeSave() {}
+func (x *staticFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("FileStaticContentReader", &x.FileStaticContentReader)
+}
+
+func (x *staticFile) afterLoad() {}
+func (x *staticFile) load(m state.Map) {
+ m.Load("FileStaticContentReader", &x.FileStaticContentReader)
+}
+
+func (x *InodeStaticFileGetter) beforeSave() {}
+func (x *InodeStaticFileGetter) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Contents", &x.Contents)
+}
+
+func (x *InodeStaticFileGetter) afterLoad() {}
+func (x *InodeStaticFileGetter) load(m state.Map) {
+ m.Load("Contents", &x.Contents)
+}
+
+func (x *CachingInodeOperations) beforeSave() {}
+func (x *CachingInodeOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("backingFile", &x.backingFile)
+ m.Save("mfp", &x.mfp)
+ m.Save("opts", &x.opts)
+ m.Save("attr", &x.attr)
+ m.Save("dirtyAttr", &x.dirtyAttr)
+ m.Save("mappings", &x.mappings)
+ m.Save("cache", &x.cache)
+ m.Save("dirty", &x.dirty)
+ m.Save("hostFileMapper", &x.hostFileMapper)
+ m.Save("refs", &x.refs)
+}
+
+func (x *CachingInodeOperations) afterLoad() {}
+func (x *CachingInodeOperations) load(m state.Map) {
+ m.Load("backingFile", &x.backingFile)
+ m.Load("mfp", &x.mfp)
+ m.Load("opts", &x.opts)
+ m.Load("attr", &x.attr)
+ m.Load("dirtyAttr", &x.dirtyAttr)
+ m.Load("mappings", &x.mappings)
+ m.Load("cache", &x.cache)
+ m.Load("dirty", &x.dirty)
+ m.Load("hostFileMapper", &x.hostFileMapper)
+ m.Load("refs", &x.refs)
+}
+
+func (x *CachingInodeOperationsOptions) beforeSave() {}
+func (x *CachingInodeOperationsOptions) save(m state.Map) {
+ x.beforeSave()
+ m.Save("ForcePageCache", &x.ForcePageCache)
+ m.Save("LimitHostFDTranslation", &x.LimitHostFDTranslation)
+}
+
+func (x *CachingInodeOperationsOptions) afterLoad() {}
+func (x *CachingInodeOperationsOptions) load(m state.Map) {
+ m.Load("ForcePageCache", &x.ForcePageCache)
+ m.Load("LimitHostFDTranslation", &x.LimitHostFDTranslation)
+}
+
+func init() {
+ state.Register("fsutil.DirtyInfo", (*DirtyInfo)(nil), state.Fns{Save: (*DirtyInfo).save, Load: (*DirtyInfo).load})
+ state.Register("fsutil.DirtySet", (*DirtySet)(nil), state.Fns{Save: (*DirtySet).save, Load: (*DirtySet).load})
+ state.Register("fsutil.Dirtynode", (*Dirtynode)(nil), state.Fns{Save: (*Dirtynode).save, Load: (*Dirtynode).load})
+ state.Register("fsutil.DirtySegmentDataSlices", (*DirtySegmentDataSlices)(nil), state.Fns{Save: (*DirtySegmentDataSlices).save, Load: (*DirtySegmentDataSlices).load})
+ state.Register("fsutil.StaticDirFileOperations", (*StaticDirFileOperations)(nil), state.Fns{Save: (*StaticDirFileOperations).save, Load: (*StaticDirFileOperations).load})
+ state.Register("fsutil.NoReadWriteFile", (*NoReadWriteFile)(nil), state.Fns{Save: (*NoReadWriteFile).save, Load: (*NoReadWriteFile).load})
+ state.Register("fsutil.FileStaticContentReader", (*FileStaticContentReader)(nil), state.Fns{Save: (*FileStaticContentReader).save, Load: (*FileStaticContentReader).load})
+ state.Register("fsutil.FileRangeSet", (*FileRangeSet)(nil), state.Fns{Save: (*FileRangeSet).save, Load: (*FileRangeSet).load})
+ state.Register("fsutil.FileRangenode", (*FileRangenode)(nil), state.Fns{Save: (*FileRangenode).save, Load: (*FileRangenode).load})
+ state.Register("fsutil.FileRangeSegmentDataSlices", (*FileRangeSegmentDataSlices)(nil), state.Fns{Save: (*FileRangeSegmentDataSlices).save, Load: (*FileRangeSegmentDataSlices).load})
+ state.Register("fsutil.frameRefSet", (*frameRefSet)(nil), state.Fns{Save: (*frameRefSet).save, Load: (*frameRefSet).load})
+ state.Register("fsutil.frameRefnode", (*frameRefnode)(nil), state.Fns{Save: (*frameRefnode).save, Load: (*frameRefnode).load})
+ state.Register("fsutil.frameRefSegmentDataSlices", (*frameRefSegmentDataSlices)(nil), state.Fns{Save: (*frameRefSegmentDataSlices).save, Load: (*frameRefSegmentDataSlices).load})
+ state.Register("fsutil.HostFileMapper", (*HostFileMapper)(nil), state.Fns{Save: (*HostFileMapper).save, Load: (*HostFileMapper).load})
+ state.Register("fsutil.HostMappable", (*HostMappable)(nil), state.Fns{Save: (*HostMappable).save, Load: (*HostMappable).load})
+ state.Register("fsutil.SimpleFileInode", (*SimpleFileInode)(nil), state.Fns{Save: (*SimpleFileInode).save, Load: (*SimpleFileInode).load})
+ state.Register("fsutil.NoReadWriteFileInode", (*NoReadWriteFileInode)(nil), state.Fns{Save: (*NoReadWriteFileInode).save, Load: (*NoReadWriteFileInode).load})
+ state.Register("fsutil.InodeSimpleAttributes", (*InodeSimpleAttributes)(nil), state.Fns{Save: (*InodeSimpleAttributes).save, Load: (*InodeSimpleAttributes).load})
+ state.Register("fsutil.InodeSimpleExtendedAttributes", (*InodeSimpleExtendedAttributes)(nil), state.Fns{Save: (*InodeSimpleExtendedAttributes).save, Load: (*InodeSimpleExtendedAttributes).load})
+ state.Register("fsutil.staticFile", (*staticFile)(nil), state.Fns{Save: (*staticFile).save, Load: (*staticFile).load})
+ state.Register("fsutil.InodeStaticFileGetter", (*InodeStaticFileGetter)(nil), state.Fns{Save: (*InodeStaticFileGetter).save, Load: (*InodeStaticFileGetter).load})
+ state.Register("fsutil.CachingInodeOperations", (*CachingInodeOperations)(nil), state.Fns{Save: (*CachingInodeOperations).save, Load: (*CachingInodeOperations).load})
+ state.Register("fsutil.CachingInodeOperationsOptions", (*CachingInodeOperationsOptions)(nil), state.Fns{Save: (*CachingInodeOperationsOptions).save, Load: (*CachingInodeOperationsOptions).load})
+}
diff --git a/pkg/sentry/fs/fsutil/inode_cached_test.go b/pkg/sentry/fs/fsutil/inode_cached_test.go
deleted file mode 100644
index eb5730c35..000000000
--- a/pkg/sentry/fs/fsutil/inode_cached_test.go
+++ /dev/null
@@ -1,389 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fsutil
-
-import (
- "bytes"
- "io"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time"
- "gvisor.dev/gvisor/pkg/sentry/memmap"
- "gvisor.dev/gvisor/pkg/sentry/safemem"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-type noopBackingFile struct{}
-
-func (noopBackingFile) ReadToBlocksAt(ctx context.Context, dsts safemem.BlockSeq, offset uint64) (uint64, error) {
- return dsts.NumBytes(), nil
-}
-
-func (noopBackingFile) WriteFromBlocksAt(ctx context.Context, srcs safemem.BlockSeq, offset uint64) (uint64, error) {
- return srcs.NumBytes(), nil
-}
-
-func (noopBackingFile) SetMaskedAttributes(context.Context, fs.AttrMask, fs.UnstableAttr) error {
- return nil
-}
-
-func (noopBackingFile) Sync(context.Context) error {
- return nil
-}
-
-func (noopBackingFile) FD() int {
- return -1
-}
-
-func (noopBackingFile) Allocate(ctx context.Context, offset int64, length int64) error {
- return nil
-}
-
-func TestSetPermissions(t *testing.T) {
- ctx := contexttest.Context(t)
-
- uattr := fs.WithCurrentTime(ctx, fs.UnstableAttr{
- Perms: fs.FilePermsFromMode(0444),
- })
- iops := NewCachingInodeOperations(ctx, noopBackingFile{}, uattr, CachingInodeOperationsOptions{})
- defer iops.Release()
-
- perms := fs.FilePermsFromMode(0777)
- if !iops.SetPermissions(ctx, nil, perms) {
- t.Fatalf("SetPermissions failed, want success")
- }
-
- // Did permissions change?
- if iops.attr.Perms != perms {
- t.Fatalf("got perms +%v, want +%v", iops.attr.Perms, perms)
- }
-
- // Did status change time change?
- if !iops.dirtyAttr.StatusChangeTime {
- t.Fatalf("got status change time not dirty, want dirty")
- }
- if iops.attr.StatusChangeTime.Equal(uattr.StatusChangeTime) {
- t.Fatalf("got status change time unchanged")
- }
-}
-
-func TestSetTimestamps(t *testing.T) {
- ctx := contexttest.Context(t)
- for _, test := range []struct {
- desc string
- ts fs.TimeSpec
- wantChanged fs.AttrMask
- }{
- {
- desc: "noop",
- ts: fs.TimeSpec{
- ATimeOmit: true,
- MTimeOmit: true,
- },
- wantChanged: fs.AttrMask{},
- },
- {
- desc: "access time only",
- ts: fs.TimeSpec{
- ATime: ktime.NowFromContext(ctx),
- MTimeOmit: true,
- },
- wantChanged: fs.AttrMask{
- AccessTime: true,
- },
- },
- {
- desc: "modification time only",
- ts: fs.TimeSpec{
- ATimeOmit: true,
- MTime: ktime.NowFromContext(ctx),
- },
- wantChanged: fs.AttrMask{
- ModificationTime: true,
- },
- },
- {
- desc: "access and modification time",
- ts: fs.TimeSpec{
- ATime: ktime.NowFromContext(ctx),
- MTime: ktime.NowFromContext(ctx),
- },
- wantChanged: fs.AttrMask{
- AccessTime: true,
- ModificationTime: true,
- },
- },
- {
- desc: "system time access and modification time",
- ts: fs.TimeSpec{
- ATimeSetSystemTime: true,
- MTimeSetSystemTime: true,
- },
- wantChanged: fs.AttrMask{
- AccessTime: true,
- ModificationTime: true,
- },
- },
- } {
- t.Run(test.desc, func(t *testing.T) {
- ctx := contexttest.Context(t)
-
- epoch := ktime.ZeroTime
- uattr := fs.UnstableAttr{
- AccessTime: epoch,
- ModificationTime: epoch,
- StatusChangeTime: epoch,
- }
- iops := NewCachingInodeOperations(ctx, noopBackingFile{}, uattr, CachingInodeOperationsOptions{})
- defer iops.Release()
-
- if err := iops.SetTimestamps(ctx, nil, test.ts); err != nil {
- t.Fatalf("SetTimestamps got error %v, want nil", err)
- }
- if test.wantChanged.AccessTime {
- if !iops.attr.AccessTime.After(uattr.AccessTime) {
- t.Fatalf("diritied access time did not advance, want %v > %v", iops.attr.AccessTime, uattr.AccessTime)
- }
- if !iops.dirtyAttr.StatusChangeTime {
- t.Fatalf("dirty access time requires dirty status change time")
- }
- if !iops.attr.StatusChangeTime.After(uattr.StatusChangeTime) {
- t.Fatalf("dirtied status change time did not advance")
- }
- }
- if test.wantChanged.ModificationTime {
- if !iops.attr.ModificationTime.After(uattr.ModificationTime) {
- t.Fatalf("diritied modification time did not advance")
- }
- if !iops.dirtyAttr.StatusChangeTime {
- t.Fatalf("dirty modification time requires dirty status change time")
- }
- if !iops.attr.StatusChangeTime.After(uattr.StatusChangeTime) {
- t.Fatalf("dirtied status change time did not advance")
- }
- }
- })
- }
-}
-
-func TestTruncate(t *testing.T) {
- ctx := contexttest.Context(t)
-
- uattr := fs.UnstableAttr{
- Size: 0,
- }
- iops := NewCachingInodeOperations(ctx, noopBackingFile{}, uattr, CachingInodeOperationsOptions{})
- defer iops.Release()
-
- if err := iops.Truncate(ctx, nil, uattr.Size); err != nil {
- t.Fatalf("Truncate got error %v, want nil", err)
- }
- var size int64 = 4096
- if err := iops.Truncate(ctx, nil, size); err != nil {
- t.Fatalf("Truncate got error %v, want nil", err)
- }
- if iops.attr.Size != size {
- t.Fatalf("Truncate got %d, want %d", iops.attr.Size, size)
- }
- if !iops.dirtyAttr.ModificationTime || !iops.dirtyAttr.StatusChangeTime {
- t.Fatalf("Truncate did not dirty modification and status change time")
- }
- if !iops.attr.ModificationTime.After(uattr.ModificationTime) {
- t.Fatalf("dirtied modification time did not change")
- }
- if !iops.attr.StatusChangeTime.After(uattr.StatusChangeTime) {
- t.Fatalf("dirtied status change time did not change")
- }
-}
-
-type sliceBackingFile struct {
- data []byte
-}
-
-func newSliceBackingFile(data []byte) *sliceBackingFile {
- return &sliceBackingFile{data}
-}
-
-func (f *sliceBackingFile) ReadToBlocksAt(ctx context.Context, dsts safemem.BlockSeq, offset uint64) (uint64, error) {
- r := safemem.BlockSeqReader{safemem.BlockSeqOf(safemem.BlockFromSafeSlice(f.data)).DropFirst64(offset)}
- return r.ReadToBlocks(dsts)
-}
-
-func (f *sliceBackingFile) WriteFromBlocksAt(ctx context.Context, srcs safemem.BlockSeq, offset uint64) (uint64, error) {
- w := safemem.BlockSeqWriter{safemem.BlockSeqOf(safemem.BlockFromSafeSlice(f.data)).DropFirst64(offset)}
- return w.WriteFromBlocks(srcs)
-}
-
-func (*sliceBackingFile) SetMaskedAttributes(context.Context, fs.AttrMask, fs.UnstableAttr) error {
- return nil
-}
-
-func (*sliceBackingFile) Sync(context.Context) error {
- return nil
-}
-
-func (*sliceBackingFile) FD() int {
- return -1
-}
-
-func (f *sliceBackingFile) Allocate(ctx context.Context, offset int64, length int64) error {
- return syserror.EOPNOTSUPP
-}
-
-type noopMappingSpace struct{}
-
-// Invalidate implements memmap.MappingSpace.Invalidate.
-func (noopMappingSpace) Invalidate(ar usermem.AddrRange, opts memmap.InvalidateOpts) {
-}
-
-func anonInode(ctx context.Context) *fs.Inode {
- return fs.NewInode(ctx, &SimpleFileInode{
- InodeSimpleAttributes: NewInodeSimpleAttributes(ctx, fs.FileOwnerFromContext(ctx), fs.FilePermissions{
- User: fs.PermMask{Read: true, Write: true},
- }, 0),
- }, fs.NewPseudoMountSource(ctx), fs.StableAttr{
- Type: fs.Anonymous,
- BlockSize: usermem.PageSize,
- })
-}
-
-func pagesOf(bs ...byte) []byte {
- buf := make([]byte, 0, len(bs)*usermem.PageSize)
- for _, b := range bs {
- buf = append(buf, bytes.Repeat([]byte{b}, usermem.PageSize)...)
- }
- return buf
-}
-
-func TestRead(t *testing.T) {
- ctx := contexttest.Context(t)
-
- // Construct a 3-page file.
- buf := pagesOf('a', 'b', 'c')
- file := fs.NewFile(ctx, fs.NewDirent(ctx, anonInode(ctx), "anon"), fs.FileFlags{}, nil)
- uattr := fs.UnstableAttr{
- Size: int64(len(buf)),
- }
- iops := NewCachingInodeOperations(ctx, newSliceBackingFile(buf), uattr, CachingInodeOperationsOptions{})
- defer iops.Release()
-
- // Expect the cache to be initially empty.
- if cached := iops.cache.Span(); cached != 0 {
- t.Errorf("Span got %d, want 0", cached)
- }
-
- // Create a memory mapping of the second page (as CachingInodeOperations
- // expects to only cache mapped pages), then call Translate to force it to
- // be cached.
- var ms noopMappingSpace
- ar := usermem.AddrRange{usermem.PageSize, 2 * usermem.PageSize}
- if err := iops.AddMapping(ctx, ms, ar, usermem.PageSize, true); err != nil {
- t.Fatalf("AddMapping got %v, want nil", err)
- }
- mr := memmap.MappableRange{usermem.PageSize, 2 * usermem.PageSize}
- if _, err := iops.Translate(ctx, mr, mr, usermem.Read); err != nil {
- t.Fatalf("Translate got %v, want nil", err)
- }
- if cached := iops.cache.Span(); cached != usermem.PageSize {
- t.Errorf("SpanRange got %d, want %d", cached, usermem.PageSize)
- }
-
- // Try to read 4 pages. The first and third pages should be read directly
- // from the "file", the second page should be read from the cache, and only
- // 3 pages (the size of the file) should be readable.
- rbuf := make([]byte, 4*usermem.PageSize)
- dst := usermem.BytesIOSequence(rbuf)
- n, err := iops.Read(ctx, file, dst, 0)
- if n != 3*usermem.PageSize || (err != nil && err != io.EOF) {
- t.Fatalf("Read got (%d, %v), want (%d, nil or EOF)", n, err, 3*usermem.PageSize)
- }
- rbuf = rbuf[:3*usermem.PageSize]
-
- // Did we get the bytes we expect?
- if !bytes.Equal(rbuf, buf) {
- t.Errorf("Read back bytes %v, want %v", rbuf, buf)
- }
-
- // Delete the memory mapping before iops.Release(). The cached page will
- // either be evicted by ctx's pgalloc.MemoryFile, or dropped by
- // iops.Release().
- iops.RemoveMapping(ctx, ms, ar, usermem.PageSize, true)
-}
-
-func TestWrite(t *testing.T) {
- ctx := contexttest.Context(t)
-
- // Construct a 4-page file.
- buf := pagesOf('a', 'b', 'c', 'd')
- orig := append([]byte(nil), buf...)
- inode := anonInode(ctx)
- uattr := fs.UnstableAttr{
- Size: int64(len(buf)),
- }
- iops := NewCachingInodeOperations(ctx, newSliceBackingFile(buf), uattr, CachingInodeOperationsOptions{})
- defer iops.Release()
-
- // Expect the cache to be initially empty.
- if cached := iops.cache.Span(); cached != 0 {
- t.Errorf("Span got %d, want 0", cached)
- }
-
- // Create a memory mapping of the second and third pages (as
- // CachingInodeOperations expects to only cache mapped pages), then call
- // Translate to force them to be cached.
- var ms noopMappingSpace
- ar := usermem.AddrRange{usermem.PageSize, 3 * usermem.PageSize}
- if err := iops.AddMapping(ctx, ms, ar, usermem.PageSize, true); err != nil {
- t.Fatalf("AddMapping got %v, want nil", err)
- }
- defer iops.RemoveMapping(ctx, ms, ar, usermem.PageSize, true)
- mr := memmap.MappableRange{usermem.PageSize, 3 * usermem.PageSize}
- if _, err := iops.Translate(ctx, mr, mr, usermem.Read); err != nil {
- t.Fatalf("Translate got %v, want nil", err)
- }
- if cached := iops.cache.Span(); cached != 2*usermem.PageSize {
- t.Errorf("SpanRange got %d, want %d", cached, 2*usermem.PageSize)
- }
-
- // Write to the first 2 pages.
- wbuf := pagesOf('e', 'f')
- src := usermem.BytesIOSequence(wbuf)
- n, err := iops.Write(ctx, src, 0)
- if n != 2*usermem.PageSize || err != nil {
- t.Fatalf("Write got (%d, %v), want (%d, nil)", n, err, 2*usermem.PageSize)
- }
-
- // The first page should have been written directly, since it was not cached.
- want := append([]byte(nil), orig...)
- copy(want, pagesOf('e'))
- if !bytes.Equal(buf, want) {
- t.Errorf("File contents are %v, want %v", buf, want)
- }
-
- // Sync back to the "backing file".
- if err := iops.WriteOut(ctx, inode); err != nil {
- t.Errorf("Sync got %v, want nil", err)
- }
-
- // Now the second page should have been written as well.
- copy(want[usermem.PageSize:], pagesOf('f'))
- if !bytes.Equal(buf, want) {
- t.Errorf("File contents are %v, want %v", buf, want)
- }
-}
diff --git a/pkg/sentry/fs/g3doc/inotify.md b/pkg/sentry/fs/g3doc/inotify.md
deleted file mode 100644
index 71a577d9d..000000000
--- a/pkg/sentry/fs/g3doc/inotify.md
+++ /dev/null
@@ -1,122 +0,0 @@
-# Inotify
-
-Inotify implements the like-named filesystem event notification system for the
-sentry, see `inotify(7)`.
-
-## Architecture
-
-For the most part, the sentry implementation of inotify mirrors the Linux
-architecture. Inotify instances (i.e. the fd returned by inotify_init(2)) are
-backed by a pseudo-filesystem. Events are generated from various places in the
-sentry, including the [syscall layer][syscall_dir], the [vfs layer][dirent] and
-the [process fd table][fd_table]. Watches are stored in inodes and generated
-events are queued to the inotify instance owning the watches for delivery to the
-user.
-
-## Objects
-
-Here is a brief description of the existing and new objects involved in the
-sentry inotify mechanism, and how they interact:
-
-### [`fs.Inotify`][inotify]
-
-- An inotify instances, created by inotify_init(2)/inotify_init1(2).
-- The inotify fd has a `fs.Dirent`, supports filesystem syscalls to read
- events.
-- Has multiple `fs.Watch`es, with at most one watch per target inode, per
- inotify instance.
-- Has an instance `id` which is globally unique. This is *not* the fd number
- for this instance, since the fd can be duped. This `id` is not externally
- visible.
-
-### [`fs.Watch`][watch]
-
-- An inotify watch, created/deleted by
- inotify_add_watch(2)/inotify_rm_watch(2).
-- Owned by an `fs.Inotify` instance, each watch keeps a pointer to the
- `owner`.
-- Associated with a single `fs.Inode`, which is the watch `target`. While the
- watch is active, it indirectly pins `target` to memory. See the "Reference
- Model" section for a detailed explanation.
-- Filesystem operations on `target` generate `fs.Event`s.
-
-### [`fs.Event`][event]
-
-- A simple struct encapsulating all the fields for an inotify event.
-- Generated by `fs.Watch`es and forwarded to the watches' `owner`s.
-- Serialized to the user during read(2) syscalls on the associated
- `fs.Inotify`'s fd.
-
-### [`fs.Dirent`][dirent]
-
-- Many inotify events are generated inside dirent methods. Events are
- generated in the dirent methods rather than `fs.Inode` methods because some
- events carry the name of the subject node, and node names are generally
- unavailable in an `fs.Inode`.
-- Dirents do not directly contain state for any watches. Instead, they forward
- notifications to the underlying `fs.Inode`.
-
-### [`fs.Inode`][inode]
-
-- Interacts with inotify through `fs.Watch`es.
-- Inodes contain a map of all active `fs.Watch`es on them.
-- An `fs.Inotify` instance can have at most one `fs.Watch` per inode.
- `fs.Watch`es on an inode are indexed by their `owner`'s `id`.
-- All inotify logic is encapsulated in the [`Watches`][inode_watches] struct
- in an inode. Logically, `Watches` is the set of inotify watches on the
- inode.
-
-## Reference Model
-
-The sentry inotify implementation has a complex reference model. An inotify
-watch observes a single inode. For efficient lookup, the state for a watch is
-stored directly on the target inode. This state needs to be persistent for the
-lifetime of watch. Unlike usual filesystem metadata, the watch state has no
-"on-disk" representation, so they cannot be reconstructed by the filesystem if
-the inode is flushed from memory. This effectively means we need to keep any
-inodes with actives watches pinned to memory.
-
-We can't just hold an extra ref on the inode to pin it to memory because some
-filesystems (such as gofer-based filesystems) don't have persistent inodes. In
-such a filesystem, if we just pin the inode, nothing prevents the enclosing
-dirent from being GCed. Once the dirent is GCed, the pinned inode is
-unreachable -- these filesystems generate a new inode by re-reading the node
-state on the next walk. Incidentally, hardlinks also don't work on these
-filesystems for this reason.
-
-To prevent the above scenario, when a new watch is added on an inode, we *pin*
-the dirent we used to reach the inode. Note that due to hardlinks, this dirent
-may not be the only dirent pointing to the inode. Attempting to set an inotify
-watch via multiple hardlinks to the same file results in the same watch being
-returned for both links. However, for each new dirent we use to reach the same
-inode, we add a new pin. We need a new pin for each new dirent used to reach the
-inode because we have no guarantees about the deletion order of the different
-links to the inode.
-
-## Lock Ordering
-
-There are 4 locks related to the inotify implementation:
-
-- `Inotify.mu`: the inotify instance lock.
-- `Inotify.evMu`: the inotify event queue lock.
-- `Watch.mu`: the watch lock, used to protect pins.
-- `fs.Watches.mu`: the inode watch set mu, used to protect the collection of
- watches on the inode.
-
-The correct lock ordering for inotify code is:
-
-`Inotify.mu` -> `fs.Watches.mu` -> `Watch.mu` -> `Inotify.evMu`.
-
-We need a distinct lock for the event queue because by the time a goroutine
-attempts to queue a new event, it is already holding `fs.Watches.mu`. If we used
-`Inotify.mu` to also protect the event queue, this would violate the above lock
-ordering.
-
-[dirent]: https://github.com/google/gvisor/blob/master/+/master/pkg/sentry/fs/dirent.go
-[event]: https://github.com/google/gvisor/blob/master/+/master/pkg/sentry/fs/inotify_event.go
-[fd_table]: https://github.com/google/gvisor/blob/master/+/master/pkg/sentry/kernel/fd_table.go
-[inode]: https://github.com/google/gvisor/blob/master/+/master/pkg/sentry/fs/inode.go
-[inode_watches]: https://github.com/google/gvisor/blob/master/+/master/pkg/sentry/fs/inode_inotify.go
-[inotify]: https://github.com/google/gvisor/blob/master/+/master/pkg/sentry/fs/inotify.go
-[syscall_dir]: https://github.com/google/gvisor/blob/master/+/master/pkg/sentry/syscalls/linux/
-[watch]: https://github.com/google/gvisor/blob/master/+/master/pkg/sentry/fs/inotify_watch.go
diff --git a/pkg/sentry/fs/gofer/BUILD b/pkg/sentry/fs/gofer/BUILD
deleted file mode 100644
index 2b71ca0e1..000000000
--- a/pkg/sentry/fs/gofer/BUILD
+++ /dev/null
@@ -1,67 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "gofer",
- srcs = [
- "attr.go",
- "cache_policy.go",
- "context_file.go",
- "device.go",
- "file.go",
- "file_state.go",
- "fs.go",
- "handles.go",
- "inode.go",
- "inode_state.go",
- "path.go",
- "session.go",
- "session_state.go",
- "socket.go",
- "util.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/gofer",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/fd",
- "//pkg/log",
- "//pkg/metric",
- "//pkg/p9",
- "//pkg/refs",
- "//pkg/secio",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fdpipe",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/fs/host",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/memmap",
- "//pkg/sentry/safemem",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/usermem",
- "//pkg/syserr",
- "//pkg/syserror",
- "//pkg/unet",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "gofer_test",
- size = "small",
- srcs = ["gofer_test.go"],
- embed = [":gofer"],
- deps = [
- "//pkg/p9",
- "//pkg/p9/p9test",
- "//pkg/sentry/context",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/fs",
- ],
-)
diff --git a/pkg/sentry/fs/gofer/gofer_state_autogen.go b/pkg/sentry/fs/gofer/gofer_state_autogen.go
new file mode 100755
index 000000000..b6c54f8f8
--- /dev/null
+++ b/pkg/sentry/fs/gofer/gofer_state_autogen.go
@@ -0,0 +1,115 @@
+// automatically generated by stateify.
+
+package gofer
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *fileOperations) beforeSave() {}
+func (x *fileOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("inodeOperations", &x.inodeOperations)
+ m.Save("dirCursor", &x.dirCursor)
+ m.Save("flags", &x.flags)
+}
+
+func (x *fileOperations) load(m state.Map) {
+ m.LoadWait("inodeOperations", &x.inodeOperations)
+ m.Load("dirCursor", &x.dirCursor)
+ m.LoadWait("flags", &x.flags)
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *filesystem) beforeSave() {}
+func (x *filesystem) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *filesystem) afterLoad() {}
+func (x *filesystem) load(m state.Map) {
+}
+
+func (x *inodeOperations) beforeSave() {}
+func (x *inodeOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("fileState", &x.fileState)
+ m.Save("cachingInodeOps", &x.cachingInodeOps)
+}
+
+func (x *inodeOperations) afterLoad() {}
+func (x *inodeOperations) load(m state.Map) {
+ m.LoadWait("fileState", &x.fileState)
+ m.Load("cachingInodeOps", &x.cachingInodeOps)
+}
+
+func (x *inodeFileState) save(m state.Map) {
+ x.beforeSave()
+ var loading struct{} = x.saveLoading()
+ m.SaveValue("loading", loading)
+ m.Save("s", &x.s)
+ m.Save("sattr", &x.sattr)
+ m.Save("savedUAttr", &x.savedUAttr)
+ m.Save("hostMappable", &x.hostMappable)
+}
+
+func (x *inodeFileState) load(m state.Map) {
+ m.LoadWait("s", &x.s)
+ m.LoadWait("sattr", &x.sattr)
+ m.Load("savedUAttr", &x.savedUAttr)
+ m.Load("hostMappable", &x.hostMappable)
+ m.LoadValue("loading", new(struct{}), func(y interface{}) { x.loadLoading(y.(struct{})) })
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *endpointMaps) beforeSave() {}
+func (x *endpointMaps) save(m state.Map) {
+ x.beforeSave()
+ m.Save("direntMap", &x.direntMap)
+ m.Save("pathMap", &x.pathMap)
+}
+
+func (x *endpointMaps) afterLoad() {}
+func (x *endpointMaps) load(m state.Map) {
+ m.Load("direntMap", &x.direntMap)
+ m.Load("pathMap", &x.pathMap)
+}
+
+func (x *session) save(m state.Map) {
+ x.beforeSave()
+ m.Save("AtomicRefCount", &x.AtomicRefCount)
+ m.Save("msize", &x.msize)
+ m.Save("version", &x.version)
+ m.Save("cachePolicy", &x.cachePolicy)
+ m.Save("aname", &x.aname)
+ m.Save("superBlockFlags", &x.superBlockFlags)
+ m.Save("limitHostFDTranslation", &x.limitHostFDTranslation)
+ m.Save("connID", &x.connID)
+ m.Save("inodeMappings", &x.inodeMappings)
+ m.Save("mounter", &x.mounter)
+ m.Save("endpoints", &x.endpoints)
+}
+
+func (x *session) load(m state.Map) {
+ m.Load("AtomicRefCount", &x.AtomicRefCount)
+ m.LoadWait("msize", &x.msize)
+ m.LoadWait("version", &x.version)
+ m.LoadWait("cachePolicy", &x.cachePolicy)
+ m.LoadWait("aname", &x.aname)
+ m.LoadWait("superBlockFlags", &x.superBlockFlags)
+ m.Load("limitHostFDTranslation", &x.limitHostFDTranslation)
+ m.LoadWait("connID", &x.connID)
+ m.LoadWait("inodeMappings", &x.inodeMappings)
+ m.LoadWait("mounter", &x.mounter)
+ m.LoadWait("endpoints", &x.endpoints)
+ m.AfterLoad(x.afterLoad)
+}
+
+func init() {
+ state.Register("gofer.fileOperations", (*fileOperations)(nil), state.Fns{Save: (*fileOperations).save, Load: (*fileOperations).load})
+ state.Register("gofer.filesystem", (*filesystem)(nil), state.Fns{Save: (*filesystem).save, Load: (*filesystem).load})
+ state.Register("gofer.inodeOperations", (*inodeOperations)(nil), state.Fns{Save: (*inodeOperations).save, Load: (*inodeOperations).load})
+ state.Register("gofer.inodeFileState", (*inodeFileState)(nil), state.Fns{Save: (*inodeFileState).save, Load: (*inodeFileState).load})
+ state.Register("gofer.endpointMaps", (*endpointMaps)(nil), state.Fns{Save: (*endpointMaps).save, Load: (*endpointMaps).load})
+ state.Register("gofer.session", (*session)(nil), state.Fns{Save: (*session).save, Load: (*session).load})
+}
diff --git a/pkg/sentry/fs/gofer/gofer_test.go b/pkg/sentry/fs/gofer/gofer_test.go
deleted file mode 100644
index 7fc3c32ae..000000000
--- a/pkg/sentry/fs/gofer/gofer_test.go
+++ /dev/null
@@ -1,310 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package gofer
-
-import (
- "fmt"
- "syscall"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/p9"
- "gvisor.dev/gvisor/pkg/p9/p9test"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
-)
-
-// rootTest runs a test with a p9 mock and an fs.InodeOperations created from
-// the attached root directory. The root file will be closed and client
-// disconnected, but additional files must be closed manually.
-func rootTest(t *testing.T, name string, cp cachePolicy, fn func(context.Context, *p9test.Harness, *p9test.Mock, *fs.Inode)) {
- t.Run(name, func(t *testing.T) {
- h, c := p9test.NewHarness(t)
- defer h.Finish()
-
- // Create a new root. Note that we pass an empty, but non-nil
- // map here. This allows tests to extend the root children
- // dynamically.
- root := h.NewDirectory(map[string]p9test.Generator{})(nil)
-
- // Return this as the root.
- h.Attacher.EXPECT().Attach().Return(root, nil).Times(1)
-
- // ... and open via the client.
- rootFile, err := c.Attach("/")
- if err != nil {
- t.Fatalf("unable to attach: %v", err)
- }
- defer rootFile.Close()
-
- // Wrap an a session.
- s := &session{
- mounter: fs.RootOwner,
- cachePolicy: cp,
- client: c,
- }
-
- // ... and an INode, with only the mode being explicitly valid for now.
- ctx := contexttest.Context(t)
- sattr, rootInodeOperations := newInodeOperations(ctx, s, contextFile{
- file: rootFile,
- }, root.QID, p9.AttrMaskAll(), root.Attr, false /* socket */)
- m := fs.NewMountSource(ctx, s, &filesystem{}, fs.MountSourceFlags{})
- rootInode := fs.NewInode(ctx, rootInodeOperations, m, sattr)
-
- // Ensure that the cache is fully invalidated, so that any
- // close actions actually take place before the full harness is
- // torn down.
- defer func() {
- m.FlushDirentRefs()
-
- // Wait for all resources to be released, otherwise the
- // operations may fail after we close the rootFile.
- fs.AsyncBarrier()
- }()
-
- // Execute the test.
- fn(ctx, h, root, rootInode)
- })
-}
-
-func TestLookup(t *testing.T) {
- type lookupTest struct {
- // Name of the test.
- name string
-
- // Expected return value.
- want error
- }
-
- tests := []lookupTest{
- {
- name: "mock Walk passes (function succeeds)",
- want: nil,
- },
- {
- name: "mock Walk fails (function fails)",
- want: syscall.ENOENT,
- },
- }
-
- const file = "file" // The walked target file.
-
- for _, test := range tests {
- rootTest(t, test.name, cacheNone, func(ctx context.Context, h *p9test.Harness, rootFile *p9test.Mock, rootInode *fs.Inode) {
- // Setup the appropriate result.
- rootFile.WalkCallback = func() error {
- return test.want
- }
- if test.want == nil {
- // Set the contents of the root. We expect a
- // normal file generator for ppp above. This is
- // overriden by setting WalkErr in the mock.
- rootFile.AddChild(file, h.NewFile())
- }
-
- // Call function.
- dirent, err := rootInode.Lookup(ctx, file)
-
- // Unwrap the InodeOperations.
- var newInodeOperations fs.InodeOperations
- if dirent != nil {
- if dirent.IsNegative() {
- err = syscall.ENOENT
- } else {
- newInodeOperations = dirent.Inode.InodeOperations
- }
- }
-
- // Check return values.
- if err != test.want {
- t.Errorf("Lookup got err %v, want %v", err, test.want)
- }
- if err == nil && newInodeOperations == nil {
- t.Errorf("Lookup got non-nil err and non-nil node, wanted at least one non-nil")
- }
- })
- }
-}
-
-func TestRevalidation(t *testing.T) {
- type revalidationTest struct {
- cachePolicy cachePolicy
-
- // Whether dirent should be reloaded before any modifications.
- preModificationWantReload bool
-
- // Whether dirent should be reloaded after updating an unstable
- // attribute on the remote fs.
- postModificationWantReload bool
-
- // Whether dirent unstable attributes should be updated after
- // updating an attribute on the remote fs.
- postModificationWantUpdatedAttrs bool
-
- // Whether dirent should be reloaded after the remote has
- // removed the file.
- postRemovalWantReload bool
- }
-
- tests := []revalidationTest{
- {
- // Policy cacheNone causes Revalidate to always return
- // true.
- cachePolicy: cacheNone,
- preModificationWantReload: true,
- postModificationWantReload: true,
- postModificationWantUpdatedAttrs: true,
- postRemovalWantReload: true,
- },
- {
- // Policy cacheAll causes Revalidate to always return
- // false.
- cachePolicy: cacheAll,
- preModificationWantReload: false,
- postModificationWantReload: false,
- postModificationWantUpdatedAttrs: false,
- postRemovalWantReload: false,
- },
- {
- // Policy cacheAllWritethrough causes Revalidate to
- // always return false.
- cachePolicy: cacheAllWritethrough,
- preModificationWantReload: false,
- postModificationWantReload: false,
- postModificationWantUpdatedAttrs: false,
- postRemovalWantReload: false,
- },
- {
- // Policy cacheRemoteRevalidating causes Revalidate to
- // return update cached unstable attrs, and returns
- // true only when the remote inode itself has been
- // removed or replaced.
- cachePolicy: cacheRemoteRevalidating,
- preModificationWantReload: false,
- postModificationWantReload: false,
- postModificationWantUpdatedAttrs: true,
- postRemovalWantReload: true,
- },
- }
-
- const file = "file" // The file walked below.
-
- for _, test := range tests {
- name := fmt.Sprintf("cachepolicy=%s", test.cachePolicy)
- rootTest(t, name, test.cachePolicy, func(ctx context.Context, h *p9test.Harness, rootFile *p9test.Mock, rootInode *fs.Inode) {
- // Wrap in a dirent object.
- rootDir := fs.NewDirent(ctx, rootInode, "root")
-
- // Create a mock file a child of the root. We save when
- // this is generated, so that when the time changed, we
- // can update the original entry.
- var origMocks []*p9test.Mock
- rootFile.AddChild(file, func(parent *p9test.Mock) *p9test.Mock {
- // Regular a regular file that has a consistent
- // path number. This might be used by
- // validation so we don't change it.
- m := h.NewMock(parent, 0, p9.Attr{
- Mode: p9.ModeRegular,
- })
- origMocks = append(origMocks, m)
- return m
- })
-
- // Do the walk.
- dirent, err := rootDir.Walk(ctx, rootDir, file)
- if err != nil {
- t.Fatalf("Lookup failed: %v", err)
- }
-
- // We must release the dirent, of the test will fail
- // with a reference leak. This is tracked by p9test.
- defer dirent.DecRef()
-
- // Walk again. Depending on the cache policy, we may
- // get a new dirent.
- newDirent, err := rootDir.Walk(ctx, rootDir, file)
- if err != nil {
- t.Fatalf("Lookup failed: %v", err)
- }
- if test.preModificationWantReload && dirent == newDirent {
- t.Errorf("Lookup with cachePolicy=%s got old dirent %+v, wanted a new dirent", test.cachePolicy, dirent)
- }
- if !test.preModificationWantReload && dirent != newDirent {
- t.Errorf("Lookup with cachePolicy=%s got new dirent %+v, wanted old dirent %+v", test.cachePolicy, newDirent, dirent)
- }
- newDirent.DecRef() // See above.
-
- // Modify the underlying mocked file's modification
- // time for the next walk that occurs.
- nowSeconds := time.Now().Unix()
- rootFile.AddChild(file, func(parent *p9test.Mock) *p9test.Mock {
- // Ensure that the path is the same as above,
- // but we change only the modification time of
- // the file.
- return h.NewMock(parent, 0, p9.Attr{
- Mode: p9.ModeRegular,
- MTimeSeconds: uint64(nowSeconds),
- })
- })
-
- // We also modify the original time, so that GetAttr
- // behaves as expected for the caching case.
- for _, m := range origMocks {
- m.Attr.MTimeSeconds = uint64(nowSeconds)
- }
-
- // Walk again. Depending on the cache policy, we may
- // get a new dirent.
- newDirent, err = rootDir.Walk(ctx, rootDir, file)
- if err != nil {
- t.Fatalf("Lookup failed: %v", err)
- }
- if test.postModificationWantReload && dirent == newDirent {
- t.Errorf("Lookup with cachePolicy=%s got old dirent, wanted a new dirent", test.cachePolicy)
- }
- if !test.postModificationWantReload && dirent != newDirent {
- t.Errorf("Lookup with cachePolicy=%s got new dirent, wanted old dirent", test.cachePolicy)
- }
- uattrs, err := newDirent.Inode.UnstableAttr(ctx)
- if err != nil {
- t.Fatalf("Error getting unstable attrs: %v", err)
- }
- gotModTimeSeconds := uattrs.ModificationTime.Seconds()
- if test.postModificationWantUpdatedAttrs && gotModTimeSeconds != nowSeconds {
- t.Fatalf("Lookup with cachePolicy=%s got new modification time %v, wanted %v", test.cachePolicy, gotModTimeSeconds, nowSeconds)
- }
- newDirent.DecRef() // See above.
-
- // Remove the file from the remote fs, subsequent walks
- // should now fail to find anything.
- rootFile.RemoveChild(file)
-
- // Walk again. Depending on the cache policy, we may
- // get ENOENT.
- newDirent, err = rootDir.Walk(ctx, rootDir, file)
- if test.postRemovalWantReload && err == nil {
- t.Errorf("Lookup with cachePolicy=%s got nil error, wanted ENOENT", test.cachePolicy)
- }
- if !test.postRemovalWantReload && (err != nil || dirent != newDirent) {
- t.Errorf("Lookup with cachePolicy=%s got new dirent and error %v, wanted old dirent and nil error", test.cachePolicy, err)
- }
- if err == nil {
- newDirent.DecRef() // See above.
- }
- })
- }
-}
diff --git a/pkg/sentry/fs/host/BUILD b/pkg/sentry/fs/host/BUILD
deleted file mode 100644
index 3e532332e..000000000
--- a/pkg/sentry/fs/host/BUILD
+++ /dev/null
@@ -1,85 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "host",
- srcs = [
- "control.go",
- "descriptor.go",
- "descriptor_state.go",
- "device.go",
- "file.go",
- "fs.go",
- "inode.go",
- "inode_state.go",
- "ioctl_unsafe.go",
- "socket.go",
- "socket_iovec.go",
- "socket_state.go",
- "socket_unsafe.go",
- "tty.go",
- "util.go",
- "util_unsafe.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/host",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/fd",
- "//pkg/fdnotifier",
- "//pkg/log",
- "//pkg/refs",
- "//pkg/secio",
- "//pkg/sentry/arch",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/memmap",
- "//pkg/sentry/safemem",
- "//pkg/sentry/socket/control",
- "//pkg/sentry/socket/unix",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/unimpl",
- "//pkg/sentry/uniqueid",
- "//pkg/sentry/usermem",
- "//pkg/syserr",
- "//pkg/syserror",
- "//pkg/tcpip",
- "//pkg/unet",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "host_test",
- size = "small",
- srcs = [
- "descriptor_test.go",
- "fs_test.go",
- "inode_test.go",
- "socket_test.go",
- "wait_test.go",
- ],
- embed = [":host"],
- deps = [
- "//pkg/fd",
- "//pkg/fdnotifier",
- "//pkg/sentry/context",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/fs",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/socket",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/usermem",
- "//pkg/syserr",
- "//pkg/tcpip",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/fs/host/descriptor_test.go b/pkg/sentry/fs/host/descriptor_test.go
deleted file mode 100644
index 4205981f5..000000000
--- a/pkg/sentry/fs/host/descriptor_test.go
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package host
-
-import (
- "io/ioutil"
- "path/filepath"
- "syscall"
- "testing"
-
- "gvisor.dev/gvisor/pkg/fdnotifier"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-func TestDescriptorRelease(t *testing.T) {
- for _, tc := range []struct {
- name string
- saveable bool
- wouldBlock bool
- }{
- {name: "all false"},
- {name: "saveable", saveable: true},
- {name: "wouldBlock", wouldBlock: true},
- } {
- t.Run(tc.name, func(t *testing.T) {
- dir, err := ioutil.TempDir("", "descriptor_test")
- if err != nil {
- t.Fatal("ioutil.TempDir() failed:", err)
- }
-
- fd, err := syscall.Open(filepath.Join(dir, "file"), syscall.O_RDWR|syscall.O_CREAT, 0666)
- if err != nil {
- t.Fatal("failed to open temp file:", err)
- }
-
- // FD ownership is transferred to the descritor.
- queue := &waiter.Queue{}
- d, err := newDescriptor(fd, false /* donated*/, tc.saveable, tc.wouldBlock, queue)
- if err != nil {
- syscall.Close(fd)
- t.Fatalf("newDescriptor(%d, %t, false, %t, queue) failed, err: %v", fd, tc.saveable, tc.wouldBlock, err)
- }
- if tc.saveable {
- if d.origFD < 0 {
- t.Errorf("saveable descriptor must preserve origFD, desc: %+v", d)
- }
- }
- if tc.wouldBlock {
- if !fdnotifier.HasFD(int32(d.value)) {
- t.Errorf("FD not registered with notifier, desc: %+v", d)
- }
- }
-
- oldVal := d.value
- d.Release()
- if d.value != -1 {
- t.Errorf("d.value want: -1, got: %d", d.value)
- }
- if tc.wouldBlock {
- if fdnotifier.HasFD(int32(oldVal)) {
- t.Errorf("FD not unregistered with notifier, desc: %+v", d)
- }
- }
- })
- }
-}
diff --git a/pkg/sentry/fs/host/fs_test.go b/pkg/sentry/fs/host/fs_test.go
deleted file mode 100644
index c6852ee30..000000000
--- a/pkg/sentry/fs/host/fs_test.go
+++ /dev/null
@@ -1,380 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package host
-
-import (
- "fmt"
- "io/ioutil"
- "os"
- "path"
- "reflect"
- "sort"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
-)
-
-// newTestMountNamespace creates a MountNamespace with a ramfs root.
-// It returns the host folder created, which should be removed when done.
-func newTestMountNamespace(t *testing.T) (*fs.MountNamespace, string, error) {
- p, err := ioutil.TempDir("", "root")
- if err != nil {
- return nil, "", err
- }
-
- fd, err := open(nil, p)
- if err != nil {
- os.RemoveAll(p)
- return nil, "", err
- }
- ctx := contexttest.Context(t)
- root, err := newInode(ctx, newMountSource(ctx, p, fs.RootOwner, &Filesystem{}, fs.MountSourceFlags{}, false), fd, false, false)
- if err != nil {
- os.RemoveAll(p)
- return nil, "", err
- }
- mm, err := fs.NewMountNamespace(ctx, root)
- if err != nil {
- os.RemoveAll(p)
- return nil, "", err
- }
- return mm, p, nil
-}
-
-// createTestDirs populates the root with some test files and directories.
-// /a/a1.txt
-// /a/a2.txt
-// /b/b1.txt
-// /b/c/c1.txt
-// /symlinks/normal.txt
-// /symlinks/to_normal.txt -> /symlinks/normal.txt
-// /symlinks/recursive -> /symlinks
-func createTestDirs(ctx context.Context, t *testing.T, m *fs.MountNamespace) error {
- r := m.Root()
- defer r.DecRef()
-
- if err := r.CreateDirectory(ctx, r, "a", fs.FilePermsFromMode(0777)); err != nil {
- return err
- }
-
- a, err := r.Walk(ctx, r, "a")
- if err != nil {
- return err
- }
- defer a.DecRef()
-
- a1, err := a.Create(ctx, r, "a1.txt", fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0666))
- if err != nil {
- return err
- }
- a1.DecRef()
-
- a2, err := a.Create(ctx, r, "a2.txt", fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0666))
- if err != nil {
- return err
- }
- a2.DecRef()
-
- if err := r.CreateDirectory(ctx, r, "b", fs.FilePermsFromMode(0777)); err != nil {
- return err
- }
-
- b, err := r.Walk(ctx, r, "b")
- if err != nil {
- return err
- }
- defer b.DecRef()
-
- b1, err := b.Create(ctx, r, "b1.txt", fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0666))
- if err != nil {
- return err
- }
- b1.DecRef()
-
- if err := b.CreateDirectory(ctx, r, "c", fs.FilePermsFromMode(0777)); err != nil {
- return err
- }
-
- c, err := b.Walk(ctx, r, "c")
- if err != nil {
- return err
- }
- defer c.DecRef()
-
- c1, err := c.Create(ctx, r, "c1.txt", fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0666))
- if err != nil {
- return err
- }
- c1.DecRef()
-
- if err := r.CreateDirectory(ctx, r, "symlinks", fs.FilePermsFromMode(0777)); err != nil {
- return err
- }
-
- symlinks, err := r.Walk(ctx, r, "symlinks")
- if err != nil {
- return err
- }
- defer symlinks.DecRef()
-
- normal, err := symlinks.Create(ctx, r, "normal.txt", fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0666))
- if err != nil {
- return err
- }
- normal.DecRef()
-
- if err := symlinks.CreateLink(ctx, r, "/symlinks/normal.txt", "to_normal.txt"); err != nil {
- return err
- }
-
- return symlinks.CreateLink(ctx, r, "/symlinks", "recursive")
-}
-
-// allPaths returns a slice of all paths of entries visible in the rootfs.
-func allPaths(ctx context.Context, t *testing.T, m *fs.MountNamespace, base string) ([]string, error) {
- var paths []string
- root := m.Root()
- defer root.DecRef()
-
- maxTraversals := uint(1)
- d, err := m.FindLink(ctx, root, nil, base, &maxTraversals)
- if err != nil {
- t.Logf("FindLink failed for %q", base)
- return paths, err
- }
- defer d.DecRef()
-
- if fs.IsDir(d.Inode.StableAttr) {
- dir, err := d.Inode.GetFile(ctx, d, fs.FileFlags{Read: true})
- if err != nil {
- return nil, fmt.Errorf("failed to open directory %q: %v", base, err)
- }
- iter, ok := dir.FileOperations.(fs.DirIterator)
- if !ok {
- return nil, fmt.Errorf("cannot directly iterate on host directory %q", base)
- }
- dirCtx := &fs.DirCtx{
- Serializer: noopDentrySerializer{},
- }
- if _, err := fs.DirentReaddir(ctx, d, iter, root, dirCtx, 0); err != nil {
- return nil, err
- }
- for name := range dirCtx.DentAttrs() {
- if name == "." || name == ".." {
- continue
- }
-
- fullName := path.Join(base, name)
- paths = append(paths, fullName)
-
- // Recurse.
- subpaths, err := allPaths(ctx, t, m, fullName)
- if err != nil {
- return paths, err
- }
- paths = append(paths, subpaths...)
- }
- }
-
- return paths, nil
-}
-
-type noopDentrySerializer struct{}
-
-func (noopDentrySerializer) CopyOut(string, fs.DentAttr) error {
- return nil
-}
-func (noopDentrySerializer) Written() int {
- return 4096
-}
-
-// pathsEqual returns true if the two string slices contain the same entries.
-func pathsEqual(got, want []string) bool {
- sort.Strings(got)
- sort.Strings(want)
-
- if len(got) != len(want) {
- return false
- }
-
- for i := range got {
- if got[i] != want[i] {
- return false
- }
- }
-
- return true
-}
-
-func TestWhitelist(t *testing.T) {
- for _, test := range []struct {
- // description of the test.
- desc string
- // paths are the paths to whitelist
- paths []string
- // want are all of the directory entries that should be
- // visible (nothing beyond this set should be visible).
- want []string
- }{
- {
- desc: "root",
- paths: []string{"/"},
- want: []string{"/a", "/a/a1.txt", "/a/a2.txt", "/b", "/b/b1.txt", "/b/c", "/b/c/c1.txt", "/symlinks", "/symlinks/normal.txt", "/symlinks/to_normal.txt", "/symlinks/recursive"},
- },
- {
- desc: "top-level directories",
- paths: []string{"/a", "/b"},
- want: []string{"/a", "/a/a1.txt", "/a/a2.txt", "/b", "/b/b1.txt", "/b/c", "/b/c/c1.txt"},
- },
- {
- desc: "nested directories (1/2)",
- paths: []string{"/b", "/b/c"},
- want: []string{"/b", "/b/b1.txt", "/b/c", "/b/c/c1.txt"},
- },
- {
- desc: "nested directories (2/2)",
- paths: []string{"/b/c", "/b"},
- want: []string{"/b", "/b/b1.txt", "/b/c", "/b/c/c1.txt"},
- },
- {
- desc: "single file",
- paths: []string{"/b/c/c1.txt"},
- want: []string{"/b", "/b/c", "/b/c/c1.txt"},
- },
- {
- desc: "single file and directory",
- paths: []string{"/a/a1.txt", "/b/c"},
- want: []string{"/a", "/a/a1.txt", "/b", "/b/c", "/b/c/c1.txt"},
- },
- {
- desc: "symlink",
- paths: []string{"/symlinks/to_normal.txt"},
- want: []string{"/symlinks", "/symlinks/normal.txt", "/symlinks/to_normal.txt"},
- },
- {
- desc: "recursive symlink",
- paths: []string{"/symlinks/recursive/normal.txt"},
- want: []string{"/symlinks", "/symlinks/normal.txt", "/symlinks/recursive"},
- },
- } {
- t.Run(test.desc, func(t *testing.T) {
- m, p, err := newTestMountNamespace(t)
- if err != nil {
- t.Errorf("Failed to create MountNamespace: %v", err)
- }
- defer os.RemoveAll(p)
-
- ctx := withRoot(contexttest.RootContext(t), m.Root())
- if err := createTestDirs(ctx, t, m); err != nil {
- t.Errorf("Failed to create test dirs: %v", err)
- }
-
- if err := installWhitelist(ctx, m, test.paths); err != nil {
- t.Errorf("installWhitelist(%v) err got %v want nil", test.paths, err)
- }
-
- got, err := allPaths(ctx, t, m, "/")
- if err != nil {
- t.Fatalf("Failed to lookup paths (whitelisted: %v): %v", test.paths, err)
- }
-
- if !pathsEqual(got, test.want) {
- t.Errorf("For paths %v got %v want %v", test.paths, got, test.want)
- }
- })
- }
-}
-
-func TestRootPath(t *testing.T) {
- // Create a temp dir, which will be the root of our mounted fs.
- rootPath, err := ioutil.TempDir(os.TempDir(), "root")
- if err != nil {
- t.Fatalf("TempDir failed: %v", err)
- }
- defer os.RemoveAll(rootPath)
-
- // Create two files inside the new root, one which will be whitelisted
- // and one not.
- whitelisted, err := ioutil.TempFile(rootPath, "white")
- if err != nil {
- t.Fatalf("TempFile failed: %v", err)
- }
- if _, err := ioutil.TempFile(rootPath, "black"); err != nil {
- t.Fatalf("TempFile failed: %v", err)
- }
-
- // Create a mount with a root path and single whitelisted file.
- hostFS := &Filesystem{}
- ctx := contexttest.Context(t)
- data := fmt.Sprintf("%s=%s,%s=%s", rootPathKey, rootPath, whitelistKey, whitelisted.Name())
- inode, err := hostFS.Mount(ctx, "", fs.MountSourceFlags{}, data, nil)
- if err != nil {
- t.Fatalf("Mount failed: %v", err)
- }
- mm, err := fs.NewMountNamespace(ctx, inode)
- if err != nil {
- t.Fatalf("NewMountNamespace failed: %v", err)
- }
- if err := hostFS.InstallWhitelist(ctx, mm); err != nil {
- t.Fatalf("InstallWhitelist failed: %v", err)
- }
-
- // Get the contents of the root directory.
- rootDir := mm.Root()
- rctx := withRoot(ctx, rootDir)
- f, err := rootDir.Inode.GetFile(rctx, rootDir, fs.FileFlags{})
- if err != nil {
- t.Fatalf("GetFile failed: %v", err)
- }
- c := &fs.CollectEntriesSerializer{}
- if err := f.Readdir(rctx, c); err != nil {
- t.Fatalf("Readdir failed: %v", err)
- }
-
- // We should have only our whitelisted file, plus the dots.
- want := []string{path.Base(whitelisted.Name()), ".", ".."}
- got := c.Order
- sort.Strings(want)
- sort.Strings(got)
- if !reflect.DeepEqual(got, want) {
- t.Errorf("Readdir got %v, wanted %v", got, want)
- }
-}
-
-type rootContext struct {
- context.Context
- root *fs.Dirent
-}
-
-// withRoot returns a copy of ctx with the given root.
-func withRoot(ctx context.Context, root *fs.Dirent) context.Context {
- return &rootContext{
- Context: ctx,
- root: root,
- }
-}
-
-// Value implements Context.Value.
-func (rc rootContext) Value(key interface{}) interface{} {
- switch key {
- case fs.CtxRoot:
- rc.root.IncRef()
- return rc.root
- default:
- return rc.Context.Value(key)
- }
-}
diff --git a/pkg/sentry/fs/host/host_state_autogen.go b/pkg/sentry/fs/host/host_state_autogen.go
new file mode 100755
index 000000000..f0e1c4b88
--- /dev/null
+++ b/pkg/sentry/fs/host/host_state_autogen.go
@@ -0,0 +1,138 @@
+// automatically generated by stateify.
+
+package host
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *descriptor) save(m state.Map) {
+ x.beforeSave()
+ m.Save("donated", &x.donated)
+ m.Save("origFD", &x.origFD)
+ m.Save("wouldBlock", &x.wouldBlock)
+}
+
+func (x *descriptor) load(m state.Map) {
+ m.Load("donated", &x.donated)
+ m.Load("origFD", &x.origFD)
+ m.Load("wouldBlock", &x.wouldBlock)
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *fileOperations) beforeSave() {}
+func (x *fileOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("iops", &x.iops)
+ m.Save("dirCursor", &x.dirCursor)
+}
+
+func (x *fileOperations) afterLoad() {}
+func (x *fileOperations) load(m state.Map) {
+ m.LoadWait("iops", &x.iops)
+ m.Load("dirCursor", &x.dirCursor)
+}
+
+func (x *Filesystem) beforeSave() {}
+func (x *Filesystem) save(m state.Map) {
+ x.beforeSave()
+ m.Save("paths", &x.paths)
+}
+
+func (x *Filesystem) afterLoad() {}
+func (x *Filesystem) load(m state.Map) {
+ m.Load("paths", &x.paths)
+}
+
+func (x *superOperations) beforeSave() {}
+func (x *superOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("SimpleMountSourceOperations", &x.SimpleMountSourceOperations)
+ m.Save("root", &x.root)
+ m.Save("inodeMappings", &x.inodeMappings)
+ m.Save("mounter", &x.mounter)
+ m.Save("dontTranslateOwnership", &x.dontTranslateOwnership)
+}
+
+func (x *superOperations) afterLoad() {}
+func (x *superOperations) load(m state.Map) {
+ m.Load("SimpleMountSourceOperations", &x.SimpleMountSourceOperations)
+ m.Load("root", &x.root)
+ m.Load("inodeMappings", &x.inodeMappings)
+ m.Load("mounter", &x.mounter)
+ m.Load("dontTranslateOwnership", &x.dontTranslateOwnership)
+}
+
+func (x *inodeOperations) beforeSave() {}
+func (x *inodeOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("fileState", &x.fileState)
+ m.Save("cachingInodeOps", &x.cachingInodeOps)
+}
+
+func (x *inodeOperations) afterLoad() {}
+func (x *inodeOperations) load(m state.Map) {
+ m.LoadWait("fileState", &x.fileState)
+ m.Load("cachingInodeOps", &x.cachingInodeOps)
+}
+
+func (x *inodeFileState) save(m state.Map) {
+ x.beforeSave()
+ if !state.IsZeroValue(x.queue) { m.Failf("queue is %v, expected zero", x.queue) }
+ m.Save("mops", &x.mops)
+ m.Save("descriptor", &x.descriptor)
+ m.Save("sattr", &x.sattr)
+ m.Save("savedUAttr", &x.savedUAttr)
+}
+
+func (x *inodeFileState) load(m state.Map) {
+ m.LoadWait("mops", &x.mops)
+ m.LoadWait("descriptor", &x.descriptor)
+ m.LoadWait("sattr", &x.sattr)
+ m.Load("savedUAttr", &x.savedUAttr)
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *ConnectedEndpoint) save(m state.Map) {
+ x.beforeSave()
+ m.Save("ref", &x.ref)
+ m.Save("queue", &x.queue)
+ m.Save("path", &x.path)
+ m.Save("srfd", &x.srfd)
+ m.Save("stype", &x.stype)
+}
+
+func (x *ConnectedEndpoint) load(m state.Map) {
+ m.Load("ref", &x.ref)
+ m.Load("queue", &x.queue)
+ m.Load("path", &x.path)
+ m.LoadWait("srfd", &x.srfd)
+ m.Load("stype", &x.stype)
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *TTYFileOperations) beforeSave() {}
+func (x *TTYFileOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("fileOperations", &x.fileOperations)
+ m.Save("session", &x.session)
+ m.Save("fgProcessGroup", &x.fgProcessGroup)
+}
+
+func (x *TTYFileOperations) afterLoad() {}
+func (x *TTYFileOperations) load(m state.Map) {
+ m.Load("fileOperations", &x.fileOperations)
+ m.Load("session", &x.session)
+ m.Load("fgProcessGroup", &x.fgProcessGroup)
+}
+
+func init() {
+ state.Register("host.descriptor", (*descriptor)(nil), state.Fns{Save: (*descriptor).save, Load: (*descriptor).load})
+ state.Register("host.fileOperations", (*fileOperations)(nil), state.Fns{Save: (*fileOperations).save, Load: (*fileOperations).load})
+ state.Register("host.Filesystem", (*Filesystem)(nil), state.Fns{Save: (*Filesystem).save, Load: (*Filesystem).load})
+ state.Register("host.superOperations", (*superOperations)(nil), state.Fns{Save: (*superOperations).save, Load: (*superOperations).load})
+ state.Register("host.inodeOperations", (*inodeOperations)(nil), state.Fns{Save: (*inodeOperations).save, Load: (*inodeOperations).load})
+ state.Register("host.inodeFileState", (*inodeFileState)(nil), state.Fns{Save: (*inodeFileState).save, Load: (*inodeFileState).load})
+ state.Register("host.ConnectedEndpoint", (*ConnectedEndpoint)(nil), state.Fns{Save: (*ConnectedEndpoint).save, Load: (*ConnectedEndpoint).load})
+ state.Register("host.TTYFileOperations", (*TTYFileOperations)(nil), state.Fns{Save: (*TTYFileOperations).save, Load: (*TTYFileOperations).load})
+}
diff --git a/pkg/sentry/fs/host/inode_test.go b/pkg/sentry/fs/host/inode_test.go
deleted file mode 100644
index 2d959f10d..000000000
--- a/pkg/sentry/fs/host/inode_test.go
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package host
-
-import (
- "io/ioutil"
- "os"
- "path"
- "syscall"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
-)
-
-// TestMultipleReaddir verifies that multiple Readdir calls return the same
-// thing if they use different dir contexts.
-func TestMultipleReaddir(t *testing.T) {
- p, err := ioutil.TempDir("", "readdir")
- if err != nil {
- t.Fatalf("Failed to create test dir: %v", err)
- }
- defer os.RemoveAll(p)
-
- f, err := os.Create(path.Join(p, "a.txt"))
- if err != nil {
- t.Fatalf("Failed to create a.txt: %v", err)
- }
- f.Close()
-
- f, err = os.Create(path.Join(p, "b.txt"))
- if err != nil {
- t.Fatalf("Failed to create b.txt: %v", err)
- }
- f.Close()
-
- fd, err := open(nil, p)
- if err != nil {
- t.Fatalf("Failed to open %q: %v", p, err)
- }
- ctx := contexttest.Context(t)
- n, err := newInode(ctx, newMountSource(ctx, p, fs.RootOwner, &Filesystem{}, fs.MountSourceFlags{}, false), fd, false, false)
- if err != nil {
- t.Fatalf("Failed to create inode: %v", err)
- }
-
- dirent := fs.NewDirent(ctx, n, "readdir")
- openFile, err := n.GetFile(ctx, dirent, fs.FileFlags{Read: true})
- if err != nil {
- t.Fatalf("Failed to get file: %v", err)
- }
- defer openFile.DecRef()
-
- c1 := &fs.DirCtx{DirCursor: new(string)}
- if _, err := openFile.FileOperations.(*fileOperations).IterateDir(ctx, dirent, c1, 0); err != nil {
- t.Fatalf("First Readdir failed: %v", err)
- }
-
- c2 := &fs.DirCtx{DirCursor: new(string)}
- if _, err := openFile.FileOperations.(*fileOperations).IterateDir(ctx, dirent, c2, 0); err != nil {
- t.Errorf("Second Readdir failed: %v", err)
- }
-
- if _, ok := c1.DentAttrs()["a.txt"]; !ok {
- t.Errorf("want a.txt in first Readdir, got %v", c1.DentAttrs())
- }
- if _, ok := c1.DentAttrs()["b.txt"]; !ok {
- t.Errorf("want b.txt in first Readdir, got %v", c1.DentAttrs())
- }
-
- if _, ok := c2.DentAttrs()["a.txt"]; !ok {
- t.Errorf("want a.txt in second Readdir, got %v", c2.DentAttrs())
- }
- if _, ok := c2.DentAttrs()["b.txt"]; !ok {
- t.Errorf("want b.txt in second Readdir, got %v", c2.DentAttrs())
- }
-}
-
-// TestCloseFD verifies fds will be closed.
-func TestCloseFD(t *testing.T) {
- var p [2]int
- if err := syscall.Pipe(p[0:]); err != nil {
- t.Fatalf("Failed to create pipe %v", err)
- }
- defer syscall.Close(p[0])
- defer syscall.Close(p[1])
-
- // Use the write-end because we will detect if it's closed on the read end.
- ctx := contexttest.Context(t)
- file, err := NewFile(ctx, p[1], fs.RootOwner)
- if err != nil {
- t.Fatalf("Failed to create File: %v", err)
- }
- file.DecRef()
-
- s := make([]byte, 10)
- if c, err := syscall.Read(p[0], s); c != 0 || err != nil {
- t.Errorf("want 0, nil (EOF) from read end, got %v, %v", c, err)
- }
-}
diff --git a/pkg/sentry/fs/host/socket_test.go b/pkg/sentry/fs/host/socket_test.go
deleted file mode 100644
index 68b38fd1c..000000000
--- a/pkg/sentry/fs/host/socket_test.go
+++ /dev/null
@@ -1,246 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package host
-
-import (
- "reflect"
- "syscall"
- "testing"
-
- "gvisor.dev/gvisor/pkg/fd"
- "gvisor.dev/gvisor/pkg/fdnotifier"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time"
- "gvisor.dev/gvisor/pkg/sentry/socket"
- "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/pkg/syserr"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-var (
- // Make sure that ConnectedEndpoint implements transport.ConnectedEndpoint.
- _ = transport.ConnectedEndpoint(new(ConnectedEndpoint))
-
- // Make sure that ConnectedEndpoint implements transport.Receiver.
- _ = transport.Receiver(new(ConnectedEndpoint))
-)
-
-func getFl(fd int) (uint32, error) {
- fl, _, err := syscall.RawSyscall(syscall.SYS_FCNTL, uintptr(fd), syscall.F_GETFL, 0)
- if err == 0 {
- return uint32(fl), nil
- }
- return 0, err
-}
-
-func TestSocketIsBlocking(t *testing.T) {
- // Using socketpair here because it's already connected.
- pair, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
- if err != nil {
- t.Fatalf("host socket creation failed: %v", err)
- }
-
- fl, err := getFl(pair[0])
- if err != nil {
- t.Fatalf("getFl: fcntl(%v, GETFL) => %v", pair[0], err)
- }
- if fl&syscall.O_NONBLOCK == syscall.O_NONBLOCK {
- t.Fatalf("Expected socket %v to be blocking", pair[0])
- }
- if fl, err = getFl(pair[1]); err != nil {
- t.Fatalf("getFl: fcntl(%v, GETFL) => %v", pair[1], err)
- }
- if fl&syscall.O_NONBLOCK == syscall.O_NONBLOCK {
- t.Fatalf("Expected socket %v to be blocking", pair[1])
- }
- sock, err := newSocket(contexttest.Context(t), pair[0], false)
- if err != nil {
- t.Fatalf("newSocket(%v) failed => %v", pair[0], err)
- }
- defer sock.DecRef()
- // Test that the socket now is non-blocking.
- if fl, err = getFl(pair[0]); err != nil {
- t.Fatalf("getFl: fcntl(%v, GETFL) => %v", pair[0], err)
- }
- if fl&syscall.O_NONBLOCK != syscall.O_NONBLOCK {
- t.Errorf("Expected socket %v to have become non-blocking", pair[0])
- }
- if fl, err = getFl(pair[1]); err != nil {
- t.Fatalf("getFl: fcntl(%v, GETFL) => %v", pair[1], err)
- }
- if fl&syscall.O_NONBLOCK == syscall.O_NONBLOCK {
- t.Errorf("Did not expect socket %v to become non-blocking", pair[1])
- }
-}
-
-func TestSocketWritev(t *testing.T) {
- // Using socketpair here because it's already connected.
- pair, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
- if err != nil {
- t.Fatalf("host socket creation failed: %v", err)
- }
- socket, err := newSocket(contexttest.Context(t), pair[0], false)
- if err != nil {
- t.Fatalf("newSocket(%v) => %v", pair[0], err)
- }
- defer socket.DecRef()
- buf := []byte("hello world\n")
- n, err := socket.Writev(contexttest.Context(t), usermem.BytesIOSequence(buf))
- if err != nil {
- t.Fatalf("socket writev failed: %v", err)
- }
-
- if n != int64(len(buf)) {
- t.Fatalf("socket writev wrote incorrect bytes: %d", n)
- }
-}
-
-func TestSocketWritevLen0(t *testing.T) {
- // Using socketpair here because it's already connected.
- pair, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
- if err != nil {
- t.Fatalf("host socket creation failed: %v", err)
- }
- socket, err := newSocket(contexttest.Context(t), pair[0], false)
- if err != nil {
- t.Fatalf("newSocket(%v) => %v", pair[0], err)
- }
- defer socket.DecRef()
- n, err := socket.Writev(contexttest.Context(t), usermem.BytesIOSequence(nil))
- if err != nil {
- t.Fatalf("socket writev failed: %v", err)
- }
-
- if n != 0 {
- t.Fatalf("socket writev wrote incorrect bytes: %d", n)
- }
-}
-
-func TestSocketSendMsgLen0(t *testing.T) {
- // Using socketpair here because it's already connected.
- pair, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
- if err != nil {
- t.Fatalf("host socket creation failed: %v", err)
- }
- sfile, err := newSocket(contexttest.Context(t), pair[0], false)
- if err != nil {
- t.Fatalf("newSocket(%v) => %v", pair[0], err)
- }
- defer sfile.DecRef()
-
- s := sfile.FileOperations.(socket.Socket)
- n, terr := s.SendMsg(nil, usermem.BytesIOSequence(nil), []byte{}, 0, false, ktime.Time{}, socket.ControlMessages{})
- if n != 0 {
- t.Fatalf("socket sendmsg() failed: %v wrote: %d", terr, n)
- }
-
- if terr != nil {
- t.Fatalf("socket sendmsg() failed: %v", terr)
- }
-}
-
-func TestListen(t *testing.T) {
- pair, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
- if err != nil {
- t.Fatalf("syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM, 0) => %v", err)
- }
- sfile1, err := newSocket(contexttest.Context(t), pair[0], false)
- if err != nil {
- t.Fatalf("newSocket(%v) => %v", pair[0], err)
- }
- defer sfile1.DecRef()
- socket1 := sfile1.FileOperations.(socket.Socket)
-
- sfile2, err := newSocket(contexttest.Context(t), pair[1], false)
- if err != nil {
- t.Fatalf("newSocket(%v) => %v", pair[1], err)
- }
- defer sfile2.DecRef()
- socket2 := sfile2.FileOperations.(socket.Socket)
-
- // Socketpairs can not be listened to.
- if err := socket1.Listen(nil, 64); err != syserr.ErrInvalidEndpointState {
- t.Fatalf("socket1.Listen(nil, 64) => %v, want syserr.ErrInvalidEndpointState", err)
- }
- if err := socket2.Listen(nil, 64); err != syserr.ErrInvalidEndpointState {
- t.Fatalf("socket2.Listen(nil, 64) => %v, want syserr.ErrInvalidEndpointState", err)
- }
-
- // Create a Unix socket, do not bind it.
- sock, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
- if err != nil {
- t.Fatalf("syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM, 0) => %v", err)
- }
- sfile3, err := newSocket(contexttest.Context(t), sock, false)
- if err != nil {
- t.Fatalf("newSocket(%v) => %v", sock, err)
- }
- defer sfile3.DecRef()
- socket3 := sfile3.FileOperations.(socket.Socket)
-
- // This socket is not bound so we can't listen on it.
- if err := socket3.Listen(nil, 64); err != syserr.ErrInvalidEndpointState {
- t.Fatalf("socket3.Listen(nil, 64) => %v, want syserr.ErrInvalidEndpointState", err)
- }
-}
-
-func TestPasscred(t *testing.T) {
- e := ConnectedEndpoint{}
- if got, want := e.Passcred(), false; got != want {
- t.Errorf("Got %#v.Passcred() = %t, want = %t", e, got, want)
- }
-}
-
-func TestGetLocalAddress(t *testing.T) {
- e := ConnectedEndpoint{path: "foo"}
- want := tcpip.FullAddress{Addr: tcpip.Address("foo")}
- if got, err := e.GetLocalAddress(); err != nil || got != want {
- t.Errorf("Got %#v.GetLocalAddress() = %#v, %v, want = %#v, %v", e, got, err, want, nil)
- }
-}
-
-func TestQueuedSize(t *testing.T) {
- e := ConnectedEndpoint{}
- tests := []struct {
- name string
- f func() int64
- }{
- {"SendQueuedSize", e.SendQueuedSize},
- {"RecvQueuedSize", e.RecvQueuedSize},
- }
-
- for _, test := range tests {
- if got, want := test.f(), int64(-1); got != want {
- t.Errorf("Got %#v.%s() = %d, want = %d", e, test.name, got, want)
- }
- }
-}
-
-func TestRelease(t *testing.T) {
- f, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
- if err != nil {
- t.Fatal("Creating socket:", err)
- }
- c := &ConnectedEndpoint{queue: &waiter.Queue{}, file: fd.New(f)}
- want := &ConnectedEndpoint{queue: c.queue}
- want.ref.DecRef()
- fdnotifier.AddFD(int32(c.file.FD()), nil)
- c.Release()
- if !reflect.DeepEqual(c, want) {
- t.Errorf("got = %#v, want = %#v", c, want)
- }
-}
diff --git a/pkg/sentry/fs/host/wait_test.go b/pkg/sentry/fs/host/wait_test.go
deleted file mode 100644
index 88d24d693..000000000
--- a/pkg/sentry/fs/host/wait_test.go
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package host
-
-import (
- "syscall"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-func TestWait(t *testing.T) {
- var fds [2]int
- err := syscall.Pipe(fds[:])
- if err != nil {
- t.Fatalf("Unable to create pipe: %v", err)
- }
-
- defer syscall.Close(fds[1])
-
- ctx := contexttest.Context(t)
- file, err := NewFile(ctx, fds[0], fs.RootOwner)
- if err != nil {
- syscall.Close(fds[0])
- t.Fatalf("NewFile failed: %v", err)
- }
-
- defer file.DecRef()
-
- r := file.Readiness(waiter.EventIn)
- if r != 0 {
- t.Fatalf("File is ready for read when it shouldn't be.")
- }
-
- e, ch := waiter.NewChannelEntry(nil)
- file.EventRegister(&e, waiter.EventIn)
- defer file.EventUnregister(&e)
-
- // Check that there are no notifications yet.
- if len(ch) != 0 {
- t.Fatalf("Channel is non-empty")
- }
-
- // Write to the pipe, so it should be writable now.
- syscall.Write(fds[1], []byte{1})
-
- // Check that we get a notification. We need to yield the current thread
- // so that the fdnotifier can deliver notifications, so we use a
- // 1-second timeout instead of just checking the length of the channel.
- select {
- case <-ch:
- case <-time.After(1 * time.Second):
- t.Fatalf("Channel not notified")
- }
-}
diff --git a/pkg/sentry/fs/inode_overlay_test.go b/pkg/sentry/fs/inode_overlay_test.go
deleted file mode 100644
index 8935aad65..000000000
--- a/pkg/sentry/fs/inode_overlay_test.go
+++ /dev/null
@@ -1,470 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fs_test
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- "gvisor.dev/gvisor/pkg/sentry/fs/fsutil"
- "gvisor.dev/gvisor/pkg/sentry/fs/ramfs"
- "gvisor.dev/gvisor/pkg/sentry/kernel/contexttest"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-func TestLookup(t *testing.T) {
- ctx := contexttest.Context(t)
- for _, test := range []struct {
- // Test description.
- desc string
-
- // Lookup parameters.
- dir *fs.Inode
- name string
-
- // Want from lookup.
- found bool
- hasUpper bool
- hasLower bool
- }{
- {
- desc: "no upper, lower has name",
- dir: fs.NewTestOverlayDir(ctx,
- nil, /* upper */
- newTestRamfsDir(ctx, []dirContent{
- {
- name: "a",
- dir: false,
- },
- }, nil), /* lower */
- false /* revalidate */),
- name: "a",
- found: true,
- hasUpper: false,
- hasLower: true,
- },
- {
- desc: "no lower, upper has name",
- dir: fs.NewTestOverlayDir(ctx,
- newTestRamfsDir(ctx, []dirContent{
- {
- name: "a",
- dir: false,
- },
- }, nil), /* upper */
- nil, /* lower */
- false /* revalidate */),
- name: "a",
- found: true,
- hasUpper: true,
- hasLower: false,
- },
- {
- desc: "upper and lower, only lower has name",
- dir: fs.NewTestOverlayDir(ctx,
- newTestRamfsDir(ctx, []dirContent{
- {
- name: "b",
- dir: false,
- },
- }, nil), /* upper */
- newTestRamfsDir(ctx, []dirContent{
- {
- name: "a",
- dir: false,
- },
- }, nil), /* lower */
- false /* revalidate */),
- name: "a",
- found: true,
- hasUpper: false,
- hasLower: true,
- },
- {
- desc: "upper and lower, only upper has name",
- dir: fs.NewTestOverlayDir(ctx,
- newTestRamfsDir(ctx, []dirContent{
- {
- name: "a",
- dir: false,
- },
- }, nil), /* upper */
- newTestRamfsDir(ctx, []dirContent{
- {
- name: "b",
- dir: false,
- },
- }, nil), /* lower */
- false /* revalidate */),
- name: "a",
- found: true,
- hasUpper: true,
- hasLower: false,
- },
- {
- desc: "upper and lower, both have file",
- dir: fs.NewTestOverlayDir(ctx,
- newTestRamfsDir(ctx, []dirContent{
- {
- name: "a",
- dir: false,
- },
- }, nil), /* upper */
- newTestRamfsDir(ctx, []dirContent{
- {
- name: "a",
- dir: false,
- },
- }, nil), /* lower */
- false /* revalidate */),
- name: "a",
- found: true,
- hasUpper: true,
- hasLower: false,
- },
- {
- desc: "upper and lower, both have directory",
- dir: fs.NewTestOverlayDir(ctx,
- newTestRamfsDir(ctx, []dirContent{
- {
- name: "a",
- dir: true,
- },
- }, nil), /* upper */
- newTestRamfsDir(ctx, []dirContent{
- {
- name: "a",
- dir: true,
- },
- }, nil), /* lower */
- false /* revalidate */),
- name: "a",
- found: true,
- hasUpper: true,
- hasLower: true,
- },
- {
- desc: "upper and lower, upper negative masks lower file",
- dir: fs.NewTestOverlayDir(ctx,
- newTestRamfsDir(ctx, nil, []string{"a"}), /* upper */
- newTestRamfsDir(ctx, []dirContent{
- {
- name: "a",
- dir: false,
- },
- }, nil), /* lower */
- false /* revalidate */),
- name: "a",
- found: false,
- hasUpper: false,
- hasLower: false,
- },
- {
- desc: "upper and lower, upper negative does not mask lower file",
- dir: fs.NewTestOverlayDir(ctx,
- newTestRamfsDir(ctx, nil, []string{"b"}), /* upper */
- newTestRamfsDir(ctx, []dirContent{
- {
- name: "a",
- dir: false,
- },
- }, nil), /* lower */
- false /* revalidate */),
- name: "a",
- found: true,
- hasUpper: false,
- hasLower: true,
- },
- } {
- t.Run(test.desc, func(t *testing.T) {
- dirent, err := test.dir.Lookup(ctx, test.name)
- if test.found && (err == syserror.ENOENT || dirent.IsNegative()) {
- t.Fatalf("lookup %q expected to find positive dirent, got dirent %v err %v", test.name, dirent, err)
- }
- if !test.found {
- if err != syserror.ENOENT && !dirent.IsNegative() {
- t.Errorf("lookup %q expected to return ENOENT or negative dirent, got dirent %v err %v", test.name, dirent, err)
- }
- // Nothing more to check.
- return
- }
- if hasUpper := dirent.Inode.TestHasUpperFS(); hasUpper != test.hasUpper {
- t.Fatalf("lookup got upper filesystem %v, want %v", hasUpper, test.hasUpper)
- }
- if hasLower := dirent.Inode.TestHasLowerFS(); hasLower != test.hasLower {
- t.Errorf("lookup got lower filesystem %v, want %v", hasLower, test.hasLower)
- }
- })
- }
-}
-
-func TestLookupRevalidation(t *testing.T) {
- // File name used in the tests.
- fileName := "foofile"
- ctx := contexttest.Context(t)
- for _, tc := range []struct {
- // Test description.
- desc string
-
- // Upper and lower fs for the overlay.
- upper *fs.Inode
- lower *fs.Inode
-
- // Whether the upper requires revalidation.
- revalidate bool
-
- // Whether we should get the same dirent on second lookup.
- wantSame bool
- }{
- {
- desc: "file from upper with no revalidation",
- upper: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
- lower: newTestRamfsDir(ctx, nil, nil),
- revalidate: false,
- wantSame: true,
- },
- {
- desc: "file from upper with revalidation",
- upper: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
- lower: newTestRamfsDir(ctx, nil, nil),
- revalidate: true,
- wantSame: false,
- },
- {
- desc: "file from lower with no revalidation",
- upper: newTestRamfsDir(ctx, nil, nil),
- lower: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
- revalidate: false,
- wantSame: true,
- },
- {
- desc: "file from lower with revalidation",
- upper: newTestRamfsDir(ctx, nil, nil),
- lower: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
- revalidate: true,
- // The file does not exist in the upper, so we do not
- // need to revalidate it.
- wantSame: true,
- },
- {
- desc: "file from upper and lower with no revalidation",
- upper: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
- lower: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
- revalidate: false,
- wantSame: true,
- },
- {
- desc: "file from upper and lower with revalidation",
- upper: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
- lower: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
- revalidate: true,
- wantSame: false,
- },
- } {
- t.Run(tc.desc, func(t *testing.T) {
- root := fs.NewDirent(ctx, newTestRamfsDir(ctx, nil, nil), "root")
- ctx = &rootContext{
- Context: ctx,
- root: root,
- }
- overlay := fs.NewDirent(ctx, fs.NewTestOverlayDir(ctx, tc.upper, tc.lower, tc.revalidate), "overlay")
- // Lookup the file twice through the overlay.
- first, err := overlay.Walk(ctx, root, fileName)
- if err != nil {
- t.Fatalf("overlay.Walk(%q) failed: %v", fileName, err)
- }
- second, err := overlay.Walk(ctx, root, fileName)
- if err != nil {
- t.Fatalf("overlay.Walk(%q) failed: %v", fileName, err)
- }
-
- if tc.wantSame && first != second {
- t.Errorf("dirent lookup got different dirents, wanted same\nfirst=%+v\nsecond=%+v", first, second)
- } else if !tc.wantSame && first == second {
- t.Errorf("dirent lookup got the same dirent, wanted different: %+v", first)
- }
- })
- }
-}
-
-func TestCacheFlush(t *testing.T) {
- ctx := contexttest.Context(t)
-
- // Upper and lower each have a file.
- upperFileName := "file-from-upper"
- lowerFileName := "file-from-lower"
- upper := newTestRamfsDir(ctx, []dirContent{{name: upperFileName}}, nil)
- lower := newTestRamfsDir(ctx, []dirContent{{name: lowerFileName}}, nil)
-
- overlay := fs.NewTestOverlayDir(ctx, upper, lower, true /* revalidate */)
-
- mns, err := fs.NewMountNamespace(ctx, overlay)
- if err != nil {
- t.Fatalf("NewMountNamespace failed: %v", err)
- }
- root := mns.Root()
- defer root.DecRef()
-
- ctx = &rootContext{
- Context: ctx,
- root: root,
- }
-
- for _, fileName := range []string{upperFileName, lowerFileName} {
- // Walk to the file.
- maxTraversals := uint(0)
- dirent, err := mns.FindInode(ctx, root, nil, fileName, &maxTraversals)
- if err != nil {
- t.Fatalf("FindInode(%q) failed: %v", fileName, err)
- }
-
- // Get a file from the dirent.
- file, err := dirent.Inode.GetFile(ctx, dirent, fs.FileFlags{Read: true})
- if err != nil {
- t.Fatalf("GetFile() failed: %v", err)
- }
-
- // The dirent should have 3 refs, one from us, one from the
- // file, and one from the dirent cache.
- // dirent cache.
- if got, want := dirent.ReadRefs(), 3; int(got) != want {
- t.Errorf("dirent.ReadRefs() got %d want %d", got, want)
- }
-
- // Drop the file reference.
- file.DecRef()
-
- // Dirent should have 2 refs left.
- if got, want := dirent.ReadRefs(), 2; int(got) != want {
- t.Errorf("dirent.ReadRefs() got %d want %d", got, want)
- }
-
- // Flush the dirent cache.
- mns.FlushMountSourceRefs()
-
- // Dirent should have 1 ref left from the dirent cache.
- if got, want := dirent.ReadRefs(), 1; int(got) != want {
- t.Errorf("dirent.ReadRefs() got %d want %d", got, want)
- }
-
- // Drop our ref.
- dirent.DecRef()
-
- // We should be back to zero refs.
- if got, want := dirent.ReadRefs(), 0; int(got) != want {
- t.Errorf("dirent.ReadRefs() got %d want %d", got, want)
- }
- }
-
-}
-
-type dir struct {
- fs.InodeOperations
-
- // List of negative child names.
- negative []string
-
- // ReaddirCalled records whether Readdir was called on a file
- // corresponding to this inode.
- ReaddirCalled bool
-}
-
-// Getxattr implements InodeOperations.Getxattr.
-func (d *dir) Getxattr(inode *fs.Inode, name string) (string, error) {
- for _, n := range d.negative {
- if name == fs.XattrOverlayWhiteout(n) {
- return "y", nil
- }
- }
- return "", syserror.ENOATTR
-}
-
-// GetFile implements InodeOperations.GetFile.
-func (d *dir) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) {
- file, err := d.InodeOperations.GetFile(ctx, dirent, flags)
- if err != nil {
- return nil, err
- }
- defer file.DecRef()
- // Wrap the file's FileOperations in a dirFile.
- fops := &dirFile{
- FileOperations: file.FileOperations,
- inode: d,
- }
- return fs.NewFile(ctx, dirent, flags, fops), nil
-}
-
-type dirContent struct {
- name string
- dir bool
-}
-
-type dirFile struct {
- fs.FileOperations
- inode *dir
-}
-
-type inode struct {
- fsutil.InodeGenericChecker `state:"nosave"`
- fsutil.InodeNoExtendedAttributes `state:"nosave"`
- fsutil.InodeNoopRelease `state:"nosave"`
- fsutil.InodeNoopWriteOut `state:"nosave"`
- fsutil.InodeNotAllocatable `state:"nosave"`
- fsutil.InodeNotDirectory `state:"nosave"`
- fsutil.InodeNotMappable `state:"nosave"`
- fsutil.InodeNotSocket `state:"nosave"`
- fsutil.InodeNotSymlink `state:"nosave"`
- fsutil.InodeNotTruncatable `state:"nosave"`
- fsutil.InodeNotVirtual `state:"nosave"`
-
- fsutil.InodeSimpleAttributes
- fsutil.InodeStaticFileGetter
-}
-
-// Readdir implements fs.FileOperations.Readdir. It sets the ReaddirCalled
-// field on the inode.
-func (f *dirFile) Readdir(ctx context.Context, file *fs.File, ser fs.DentrySerializer) (int64, error) {
- f.inode.ReaddirCalled = true
- return f.FileOperations.Readdir(ctx, file, ser)
-}
-
-func newTestRamfsInode(ctx context.Context, msrc *fs.MountSource) *fs.Inode {
- inode := fs.NewInode(ctx, &inode{
- InodeStaticFileGetter: fsutil.InodeStaticFileGetter{
- Contents: []byte("foobar"),
- },
- }, msrc, fs.StableAttr{Type: fs.RegularFile})
- return inode
-}
-
-func newTestRamfsDir(ctx context.Context, contains []dirContent, negative []string) *fs.Inode {
- msrc := fs.NewPseudoMountSource(ctx)
- contents := make(map[string]*fs.Inode)
- for _, c := range contains {
- if c.dir {
- contents[c.name] = newTestRamfsDir(ctx, nil, nil)
- } else {
- contents[c.name] = newTestRamfsInode(ctx, msrc)
- }
- }
- dops := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermissions{
- User: fs.PermMask{Read: true, Execute: true},
- })
- return fs.NewInode(ctx, &dir{
- InodeOperations: dops,
- negative: negative,
- }, msrc, fs.StableAttr{Type: fs.Directory})
-}
diff --git a/pkg/sentry/fs/lock/BUILD b/pkg/sentry/fs/lock/BUILD
deleted file mode 100644
index 5a7a5b8cd..000000000
--- a/pkg/sentry/fs/lock/BUILD
+++ /dev/null
@@ -1,60 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_template_instance(
- name = "lock_range",
- out = "lock_range.go",
- package = "lock",
- prefix = "Lock",
- template = "//pkg/segment:generic_range",
- types = {
- "T": "uint64",
- },
-)
-
-go_template_instance(
- name = "lock_set",
- out = "lock_set.go",
- consts = {
- "minDegree": "3",
- },
- package = "lock",
- prefix = "Lock",
- template = "//pkg/segment:generic_set",
- types = {
- "Key": "uint64",
- "Range": "LockRange",
- "Value": "Lock",
- "Functions": "lockSetFunctions",
- },
-)
-
-go_library(
- name = "lock",
- srcs = [
- "lock.go",
- "lock_range.go",
- "lock_set.go",
- "lock_set_functions.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/lock",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/log",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "lock_test",
- size = "small",
- srcs = [
- "lock_range_test.go",
- "lock_test.go",
- ],
- embed = [":lock"],
-)
diff --git a/pkg/segment/range.go b/pkg/sentry/fs/lock/lock_range.go
index 4d4aeffef..7a6f77640 100644..100755
--- a/pkg/segment/range.go
+++ b/pkg/sentry/fs/lock/lock_range.go
@@ -1,64 +1,47 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package segment
-
-// T is a required type parameter that must be an integral type.
-type T uint64
+package lock
// A Range represents a contiguous range of T.
//
// +stateify savable
-type Range struct {
+type LockRange struct {
// Start is the inclusive start of the range.
- Start T
+ Start uint64
// End is the exclusive end of the range.
- End T
+ End uint64
}
// WellFormed returns true if r.Start <= r.End. All other methods on a Range
// require that the Range is well-formed.
-func (r Range) WellFormed() bool {
+func (r LockRange) WellFormed() bool {
return r.Start <= r.End
}
// Length returns the length of the range.
-func (r Range) Length() T {
+func (r LockRange) Length() uint64 {
return r.End - r.Start
}
// Contains returns true if r contains x.
-func (r Range) Contains(x T) bool {
+func (r LockRange) Contains(x uint64) bool {
return r.Start <= x && x < r.End
}
// Overlaps returns true if r and r2 overlap.
-func (r Range) Overlaps(r2 Range) bool {
+func (r LockRange) Overlaps(r2 LockRange) bool {
return r.Start < r2.End && r2.Start < r.End
}
// IsSupersetOf returns true if r is a superset of r2; that is, the range r2 is
// contained within r.
-func (r Range) IsSupersetOf(r2 Range) bool {
+func (r LockRange) IsSupersetOf(r2 LockRange) bool {
return r.Start <= r2.Start && r.End >= r2.End
}
// Intersect returns a range consisting of the intersection between r and r2.
// If r and r2 do not overlap, Intersect returns a range with unspecified
// bounds, but for which Length() == 0.
-func (r Range) Intersect(r2 Range) Range {
+func (r LockRange) Intersect(r2 LockRange) LockRange {
if r.Start < r2.Start {
r.Start = r2.Start
}
@@ -74,6 +57,6 @@ func (r Range) Intersect(r2 Range) Range {
// CanSplitAt returns true if it is legal to split a segment spanning the range
// r at x; that is, splitting at x would produce two ranges, both of which have
// non-zero length.
-func (r Range) CanSplitAt(x T) bool {
+func (r LockRange) CanSplitAt(x uint64) bool {
return r.Contains(x) && r.Start < x
}
diff --git a/pkg/sentry/fs/lock/lock_range_test.go b/pkg/sentry/fs/lock/lock_range_test.go
deleted file mode 100644
index 6221199d1..000000000
--- a/pkg/sentry/fs/lock/lock_range_test.go
+++ /dev/null
@@ -1,136 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package lock
-
-import (
- "syscall"
- "testing"
-)
-
-func TestComputeRange(t *testing.T) {
- tests := []struct {
- // Description of test.
- name string
-
- // Requested start of the lock range.
- start int64
-
- // Requested length of the lock range,
- // can be negative :(
- length int64
-
- // Pre-computed file offset based on whence.
- // Will be added to start.
- offset int64
-
- // Expected error.
- err error
-
- // If error is nil, the expected LockRange.
- LockRange
- }{
- {
- name: "offset, start, and length all zero",
- LockRange: LockRange{Start: 0, End: LockEOF},
- },
- {
- name: "zero offset, zero start, positive length",
- start: 0,
- length: 4096,
- offset: 0,
- LockRange: LockRange{Start: 0, End: 4096},
- },
- {
- name: "zero offset, negative start",
- start: -4096,
- offset: 0,
- err: syscall.EINVAL,
- },
- {
- name: "large offset, negative start, positive length",
- start: -2048,
- length: 2048,
- offset: 4096,
- LockRange: LockRange{Start: 2048, End: 4096},
- },
- {
- name: "large offset, negative start, zero length",
- start: -2048,
- length: 0,
- offset: 4096,
- LockRange: LockRange{Start: 2048, End: LockEOF},
- },
- {
- name: "zero offset, zero start, negative length",
- start: 0,
- length: -4096,
- offset: 0,
- err: syscall.EINVAL,
- },
- {
- name: "large offset, zero start, negative length",
- start: 0,
- length: -4096,
- offset: 4096,
- LockRange: LockRange{Start: 0, End: 4096},
- },
- {
- name: "offset, start, and length equal, length is negative",
- start: 1024,
- length: -1024,
- offset: 1024,
- LockRange: LockRange{Start: 1024, End: 2048},
- },
- {
- name: "offset, start, and length equal, start is negative",
- start: -1024,
- length: 1024,
- offset: 1024,
- LockRange: LockRange{Start: 0, End: 1024},
- },
- {
- name: "offset, start, and length equal, offset is negative",
- start: 1024,
- length: 1024,
- offset: -1024,
- LockRange: LockRange{Start: 0, End: 1024},
- },
- {
- name: "offset, start, and length equal, all negative",
- start: -1024,
- length: -1024,
- offset: -1024,
- err: syscall.EINVAL,
- },
- {
- name: "offset, start, and length equal, all positive",
- start: 1024,
- length: 1024,
- offset: 1024,
- LockRange: LockRange{Start: 2048, End: 3072},
- },
- }
-
- for _, test := range tests {
- rng, err := ComputeRange(test.start, test.length, test.offset)
- if err != test.err {
- t.Errorf("%s: lockRange(%d, %d, %d) got error %v, want %v", test.name, test.start, test.length, test.offset, err, test.err)
- continue
- }
- if err == nil && rng != test.LockRange {
- t.Errorf("%s: lockRange(%d, %d, %d) got LockRange %v, want %v", test.name, test.start, test.length, test.offset, rng, test.LockRange)
- }
- }
-}
diff --git a/pkg/sentry/fs/lock/lock_set.go b/pkg/sentry/fs/lock/lock_set.go
new file mode 100755
index 000000000..2343ca0b4
--- /dev/null
+++ b/pkg/sentry/fs/lock/lock_set.go
@@ -0,0 +1,1270 @@
+package lock
+
+import (
+ "bytes"
+ "fmt"
+)
+
+const (
+ // minDegree is the minimum degree of an internal node in a Set B-tree.
+ //
+ // - Any non-root node has at least minDegree-1 segments.
+ //
+ // - Any non-root internal (non-leaf) node has at least minDegree children.
+ //
+ // - The root node may have fewer than minDegree-1 segments, but it may
+ // only have 0 segments if the tree is empty.
+ //
+ // Our implementation requires minDegree >= 3. Higher values of minDegree
+ // usually improve performance, but increase memory usage for small sets.
+ LockminDegree = 3
+
+ LockmaxDegree = 2 * LockminDegree
+)
+
+// A Set is a mapping of segments with non-overlapping Range keys. The zero
+// value for a Set is an empty set. Set values are not safely movable nor
+// copyable. Set is thread-compatible.
+//
+// +stateify savable
+type LockSet struct {
+ root Locknode `state:".(*LockSegmentDataSlices)"`
+}
+
+// IsEmpty returns true if the set contains no segments.
+func (s *LockSet) IsEmpty() bool {
+ return s.root.nrSegments == 0
+}
+
+// IsEmptyRange returns true iff no segments in the set overlap the given
+// range. This is semantically equivalent to s.SpanRange(r) == 0, but may be
+// more efficient.
+func (s *LockSet) IsEmptyRange(r LockRange) bool {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return true
+ }
+ _, gap := s.Find(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ return r.End <= gap.End()
+}
+
+// Span returns the total size of all segments in the set.
+func (s *LockSet) Span() uint64 {
+ var sz uint64
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sz += seg.Range().Length()
+ }
+ return sz
+}
+
+// SpanRange returns the total size of the intersection of segments in the set
+// with the given range.
+func (s *LockSet) SpanRange(r LockRange) uint64 {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return 0
+ }
+ var sz uint64
+ for seg := s.LowerBoundSegment(r.Start); seg.Ok() && seg.Start() < r.End; seg = seg.NextSegment() {
+ sz += seg.Range().Intersect(r).Length()
+ }
+ return sz
+}
+
+// FirstSegment returns the first segment in the set. If the set is empty,
+// FirstSegment returns a terminal iterator.
+func (s *LockSet) FirstSegment() LockIterator {
+ if s.root.nrSegments == 0 {
+ return LockIterator{}
+ }
+ return s.root.firstSegment()
+}
+
+// LastSegment returns the last segment in the set. If the set is empty,
+// LastSegment returns a terminal iterator.
+func (s *LockSet) LastSegment() LockIterator {
+ if s.root.nrSegments == 0 {
+ return LockIterator{}
+ }
+ return s.root.lastSegment()
+}
+
+// FirstGap returns the first gap in the set.
+func (s *LockSet) FirstGap() LockGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return LockGapIterator{n, 0}
+}
+
+// LastGap returns the last gap in the set.
+func (s *LockSet) LastGap() LockGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return LockGapIterator{n, n.nrSegments}
+}
+
+// Find returns the segment or gap whose range contains the given key. If a
+// segment is found, the returned Iterator is non-terminal and the
+// returned GapIterator is terminal. Otherwise, the returned Iterator is
+// terminal and the returned GapIterator is non-terminal.
+func (s *LockSet) Find(key uint64) (LockIterator, LockGapIterator) {
+ n := &s.root
+ for {
+
+ lower := 0
+ upper := n.nrSegments
+ for lower < upper {
+ i := lower + (upper-lower)/2
+ if r := n.keys[i]; key < r.End {
+ if key >= r.Start {
+ return LockIterator{n, i}, LockGapIterator{}
+ }
+ upper = i
+ } else {
+ lower = i + 1
+ }
+ }
+ i := lower
+ if !n.hasChildren {
+ return LockIterator{}, LockGapIterator{n, i}
+ }
+ n = n.children[i]
+ }
+}
+
+// FindSegment returns the segment whose range contains the given key. If no
+// such segment exists, FindSegment returns a terminal iterator.
+func (s *LockSet) FindSegment(key uint64) LockIterator {
+ seg, _ := s.Find(key)
+ return seg
+}
+
+// LowerBoundSegment returns the segment with the lowest range that contains a
+// key greater than or equal to min. If no such segment exists,
+// LowerBoundSegment returns a terminal iterator.
+func (s *LockSet) LowerBoundSegment(min uint64) LockIterator {
+ seg, gap := s.Find(min)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.NextSegment()
+}
+
+// UpperBoundSegment returns the segment with the highest range that contains a
+// key less than or equal to max. If no such segment exists, UpperBoundSegment
+// returns a terminal iterator.
+func (s *LockSet) UpperBoundSegment(max uint64) LockIterator {
+ seg, gap := s.Find(max)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.PrevSegment()
+}
+
+// FindGap returns the gap containing the given key. If no such gap exists
+// (i.e. the set contains a segment containing that key), FindGap returns a
+// terminal iterator.
+func (s *LockSet) FindGap(key uint64) LockGapIterator {
+ _, gap := s.Find(key)
+ return gap
+}
+
+// LowerBoundGap returns the gap with the lowest range that is greater than or
+// equal to min.
+func (s *LockSet) LowerBoundGap(min uint64) LockGapIterator {
+ seg, gap := s.Find(min)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.NextGap()
+}
+
+// UpperBoundGap returns the gap with the highest range that is less than or
+// equal to max.
+func (s *LockSet) UpperBoundGap(max uint64) LockGapIterator {
+ seg, gap := s.Find(max)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.PrevGap()
+}
+
+// Add inserts the given segment into the set and returns true. If the new
+// segment can be merged with adjacent segments, Add will do so. If the new
+// segment would overlap an existing segment, Add returns false. If Add
+// succeeds, all existing iterators are invalidated.
+func (s *LockSet) Add(r LockRange, val Lock) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.Insert(gap, r, val)
+ return true
+}
+
+// AddWithoutMerging inserts the given segment into the set and returns true.
+// If it would overlap an existing segment, AddWithoutMerging does nothing and
+// returns false. If AddWithoutMerging succeeds, all existing iterators are
+// invalidated.
+func (s *LockSet) AddWithoutMerging(r LockRange, val Lock) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.InsertWithoutMergingUnchecked(gap, r, val)
+ return true
+}
+
+// Insert inserts the given segment into the given gap. If the new segment can
+// be merged with adjacent segments, Insert will do so. Insert returns an
+// iterator to the segment containing the inserted value (which may have been
+// merged with other values). All existing iterators (including gap, but not
+// including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid, Insert panics.
+//
+// Insert is semantically equivalent to a InsertWithoutMerging followed by a
+// Merge, but may be more efficient. Note that there is no unchecked variant of
+// Insert since Insert must retrieve and inspect gap's predecessor and
+// successor segments regardless.
+func (s *LockSet) Insert(gap LockGapIterator, r LockRange, val Lock) LockIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ prev, next := gap.PrevSegment(), gap.NextSegment()
+ if prev.Ok() && prev.End() > r.Start {
+ panic(fmt.Sprintf("new segment %v overlaps predecessor %v", r, prev.Range()))
+ }
+ if next.Ok() && next.Start() < r.End {
+ panic(fmt.Sprintf("new segment %v overlaps successor %v", r, next.Range()))
+ }
+ if prev.Ok() && prev.End() == r.Start {
+ if mval, ok := (lockSetFunctions{}).Merge(prev.Range(), prev.Value(), r, val); ok {
+ prev.SetEndUnchecked(r.End)
+ prev.SetValue(mval)
+ if next.Ok() && next.Start() == r.End {
+ val = mval
+ if mval, ok := (lockSetFunctions{}).Merge(prev.Range(), val, next.Range(), next.Value()); ok {
+ prev.SetEndUnchecked(next.End())
+ prev.SetValue(mval)
+ return s.Remove(next).PrevSegment()
+ }
+ }
+ return prev
+ }
+ }
+ if next.Ok() && next.Start() == r.End {
+ if mval, ok := (lockSetFunctions{}).Merge(r, val, next.Range(), next.Value()); ok {
+ next.SetStartUnchecked(r.Start)
+ next.SetValue(mval)
+ return next
+ }
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMerging inserts the given segment into the given gap and
+// returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid,
+// InsertWithoutMerging panics.
+func (s *LockSet) InsertWithoutMerging(gap LockGapIterator, r LockRange, val Lock) LockIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if gr := gap.Range(); !gr.IsSupersetOf(r) {
+ panic(fmt.Sprintf("cannot insert segment range %v into gap range %v", r, gr))
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMergingUnchecked inserts the given segment into the given gap
+// and returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// Preconditions: r.Start >= gap.Start(); r.End <= gap.End().
+func (s *LockSet) InsertWithoutMergingUnchecked(gap LockGapIterator, r LockRange, val Lock) LockIterator {
+ gap = gap.node.rebalanceBeforeInsert(gap)
+ copy(gap.node.keys[gap.index+1:], gap.node.keys[gap.index:gap.node.nrSegments])
+ copy(gap.node.values[gap.index+1:], gap.node.values[gap.index:gap.node.nrSegments])
+ gap.node.keys[gap.index] = r
+ gap.node.values[gap.index] = val
+ gap.node.nrSegments++
+ return LockIterator{gap.node, gap.index}
+}
+
+// Remove removes the given segment and returns an iterator to the vacated gap.
+// All existing iterators (including seg, but not including the returned
+// iterator) are invalidated.
+func (s *LockSet) Remove(seg LockIterator) LockGapIterator {
+
+ if seg.node.hasChildren {
+
+ victim := seg.PrevSegment()
+
+ seg.SetRangeUnchecked(victim.Range())
+ seg.SetValue(victim.Value())
+ return s.Remove(victim).NextGap()
+ }
+ copy(seg.node.keys[seg.index:], seg.node.keys[seg.index+1:seg.node.nrSegments])
+ copy(seg.node.values[seg.index:], seg.node.values[seg.index+1:seg.node.nrSegments])
+ lockSetFunctions{}.ClearValue(&seg.node.values[seg.node.nrSegments-1])
+ seg.node.nrSegments--
+ return seg.node.rebalanceAfterRemove(LockGapIterator{seg.node, seg.index})
+}
+
+// RemoveAll removes all segments from the set. All existing iterators are
+// invalidated.
+func (s *LockSet) RemoveAll() {
+ s.root = Locknode{}
+}
+
+// RemoveRange removes all segments in the given range. An iterator to the
+// newly formed gap is returned, and all existing iterators are invalidated.
+func (s *LockSet) RemoveRange(r LockRange) LockGapIterator {
+ seg, gap := s.Find(r.Start)
+ if seg.Ok() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ for seg = gap.NextSegment(); seg.Ok() && seg.Start() < r.End; seg = gap.NextSegment() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ return gap
+}
+
+// Merge attempts to merge two neighboring segments. If successful, Merge
+// returns an iterator to the merged segment, and all existing iterators are
+// invalidated. Otherwise, Merge returns a terminal iterator.
+//
+// If first is not the predecessor of second, Merge panics.
+func (s *LockSet) Merge(first, second LockIterator) LockIterator {
+ if first.NextSegment() != second {
+ panic(fmt.Sprintf("attempt to merge non-neighboring segments %v, %v", first.Range(), second.Range()))
+ }
+ return s.MergeUnchecked(first, second)
+}
+
+// MergeUnchecked attempts to merge two neighboring segments. If successful,
+// MergeUnchecked returns an iterator to the merged segment, and all existing
+// iterators are invalidated. Otherwise, MergeUnchecked returns a terminal
+// iterator.
+//
+// Precondition: first is the predecessor of second: first.NextSegment() ==
+// second, first == second.PrevSegment().
+func (s *LockSet) MergeUnchecked(first, second LockIterator) LockIterator {
+ if first.End() == second.Start() {
+ if mval, ok := (lockSetFunctions{}).Merge(first.Range(), first.Value(), second.Range(), second.Value()); ok {
+
+ first.SetEndUnchecked(second.End())
+ first.SetValue(mval)
+ return s.Remove(second).PrevSegment()
+ }
+ }
+ return LockIterator{}
+}
+
+// MergeAll attempts to merge all adjacent segments in the set. All existing
+// iterators are invalidated.
+func (s *LockSet) MergeAll() {
+ seg := s.FirstSegment()
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeRange attempts to merge all adjacent segments that contain a key in the
+// specific range. All existing iterators are invalidated.
+func (s *LockSet) MergeRange(r LockRange) {
+ seg := s.LowerBoundSegment(r.Start)
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() && next.Range().Start < r.End {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeAdjacent attempts to merge the segment containing r.Start with its
+// predecessor, and the segment containing r.End-1 with its successor.
+func (s *LockSet) MergeAdjacent(r LockRange) {
+ first := s.FindSegment(r.Start)
+ if first.Ok() {
+ if prev := first.PrevSegment(); prev.Ok() {
+ s.Merge(prev, first)
+ }
+ }
+ last := s.FindSegment(r.End - 1)
+ if last.Ok() {
+ if next := last.NextSegment(); next.Ok() {
+ s.Merge(last, next)
+ }
+ }
+}
+
+// Split splits the given segment at the given key and returns iterators to the
+// two resulting segments. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+//
+// If the segment cannot be split at split (because split is at the start or
+// end of the segment's range, so splitting would produce a segment with zero
+// length, or because split falls outside the segment's range altogether),
+// Split panics.
+func (s *LockSet) Split(seg LockIterator, split uint64) (LockIterator, LockIterator) {
+ if !seg.Range().CanSplitAt(split) {
+ panic(fmt.Sprintf("can't split %v at %v", seg.Range(), split))
+ }
+ return s.SplitUnchecked(seg, split)
+}
+
+// SplitUnchecked splits the given segment at the given key and returns
+// iterators to the two resulting segments. All existing iterators (including
+// seg, but not including the returned iterators) are invalidated.
+//
+// Preconditions: seg.Start() < key < seg.End().
+func (s *LockSet) SplitUnchecked(seg LockIterator, split uint64) (LockIterator, LockIterator) {
+ val1, val2 := (lockSetFunctions{}).Split(seg.Range(), seg.Value(), split)
+ end2 := seg.End()
+ seg.SetEndUnchecked(split)
+ seg.SetValue(val1)
+ seg2 := s.InsertWithoutMergingUnchecked(seg.NextGap(), LockRange{split, end2}, val2)
+
+ return seg2.PrevSegment(), seg2
+}
+
+// SplitAt splits the segment straddling split, if one exists. SplitAt returns
+// true if a segment was split and false otherwise. If SplitAt splits a
+// segment, all existing iterators are invalidated.
+func (s *LockSet) SplitAt(split uint64) bool {
+ if seg := s.FindSegment(split); seg.Ok() && seg.Range().CanSplitAt(split) {
+ s.SplitUnchecked(seg, split)
+ return true
+ }
+ return false
+}
+
+// Isolate ensures that the given segment's range does not escape r by
+// splitting at r.Start and r.End if necessary, and returns an updated iterator
+// to the bounded segment. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+func (s *LockSet) Isolate(seg LockIterator, r LockRange) LockIterator {
+ if seg.Range().CanSplitAt(r.Start) {
+ _, seg = s.SplitUnchecked(seg, r.Start)
+ }
+ if seg.Range().CanSplitAt(r.End) {
+ seg, _ = s.SplitUnchecked(seg, r.End)
+ }
+ return seg
+}
+
+// ApplyContiguous applies a function to a contiguous range of segments,
+// splitting if necessary. The function is applied until the first gap is
+// encountered, at which point the gap is returned. If the function is applied
+// across the entire range, a terminal gap is returned. All existing iterators
+// are invalidated.
+//
+// N.B. The Iterator must not be invalidated by the function.
+func (s *LockSet) ApplyContiguous(r LockRange, fn func(seg LockIterator)) LockGapIterator {
+ seg, gap := s.Find(r.Start)
+ if !seg.Ok() {
+ return gap
+ }
+ for {
+ seg = s.Isolate(seg, r)
+ fn(seg)
+ if seg.End() >= r.End {
+ return LockGapIterator{}
+ }
+ gap = seg.NextGap()
+ if !gap.IsEmpty() {
+ return gap
+ }
+ seg = gap.NextSegment()
+ if !seg.Ok() {
+
+ return LockGapIterator{}
+ }
+ }
+}
+
+// +stateify savable
+type Locknode struct {
+ // An internal binary tree node looks like:
+ //
+ // K
+ // / \
+ // Cl Cr
+ //
+ // where all keys in the subtree rooted by Cl (the left subtree) are less
+ // than K (the key of the parent node), and all keys in the subtree rooted
+ // by Cr (the right subtree) are greater than K.
+ //
+ // An internal B-tree node's indexes work out to look like:
+ //
+ // K0 K1 K2 ... Kn-1
+ // / \/ \/ \ ... / \
+ // C0 C1 C2 C3 ... Cn-1 Cn
+ //
+ // where n is nrSegments.
+ nrSegments int
+
+ // parent is a pointer to this node's parent. If this node is root, parent
+ // is nil.
+ parent *Locknode
+
+ // parentIndex is the index of this node in parent.children.
+ parentIndex int
+
+ // Flag for internal nodes that is technically redundant with "children[0]
+ // != nil", but is stored in the first cache line. "hasChildren" rather
+ // than "isLeaf" because false must be the correct value for an empty root.
+ hasChildren bool
+
+ // Nodes store keys and values in separate arrays to maximize locality in
+ // the common case (scanning keys for lookup).
+ keys [LockmaxDegree - 1]LockRange
+ values [LockmaxDegree - 1]Lock
+ children [LockmaxDegree]*Locknode
+}
+
+// firstSegment returns the first segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *Locknode) firstSegment() LockIterator {
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return LockIterator{n, 0}
+}
+
+// lastSegment returns the last segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *Locknode) lastSegment() LockIterator {
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return LockIterator{n, n.nrSegments - 1}
+}
+
+func (n *Locknode) prevSibling() *Locknode {
+ if n.parent == nil || n.parentIndex == 0 {
+ return nil
+ }
+ return n.parent.children[n.parentIndex-1]
+}
+
+func (n *Locknode) nextSibling() *Locknode {
+ if n.parent == nil || n.parentIndex == n.parent.nrSegments {
+ return nil
+ }
+ return n.parent.children[n.parentIndex+1]
+}
+
+// rebalanceBeforeInsert splits n and its ancestors if they are full, as
+// required for insertion, and returns an updated iterator to the position
+// represented by gap.
+func (n *Locknode) rebalanceBeforeInsert(gap LockGapIterator) LockGapIterator {
+ if n.parent != nil {
+ gap = n.parent.rebalanceBeforeInsert(gap)
+ }
+ if n.nrSegments < LockmaxDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ left := &Locknode{
+ nrSegments: LockminDegree - 1,
+ parent: n,
+ parentIndex: 0,
+ hasChildren: n.hasChildren,
+ }
+ right := &Locknode{
+ nrSegments: LockminDegree - 1,
+ parent: n,
+ parentIndex: 1,
+ hasChildren: n.hasChildren,
+ }
+ copy(left.keys[:LockminDegree-1], n.keys[:LockminDegree-1])
+ copy(left.values[:LockminDegree-1], n.values[:LockminDegree-1])
+ copy(right.keys[:LockminDegree-1], n.keys[LockminDegree:])
+ copy(right.values[:LockminDegree-1], n.values[LockminDegree:])
+ n.keys[0], n.values[0] = n.keys[LockminDegree-1], n.values[LockminDegree-1]
+ LockzeroValueSlice(n.values[1:])
+ if n.hasChildren {
+ copy(left.children[:LockminDegree], n.children[:LockminDegree])
+ copy(right.children[:LockminDegree], n.children[LockminDegree:])
+ LockzeroNodeSlice(n.children[2:])
+ for i := 0; i < LockminDegree; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ right.children[i].parent = right
+ right.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = 1
+ n.hasChildren = true
+ n.children[0] = left
+ n.children[1] = right
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < LockminDegree {
+ return LockGapIterator{left, gap.index}
+ }
+ return LockGapIterator{right, gap.index - LockminDegree}
+ }
+
+ copy(n.parent.keys[n.parentIndex+1:], n.parent.keys[n.parentIndex:n.parent.nrSegments])
+ copy(n.parent.values[n.parentIndex+1:], n.parent.values[n.parentIndex:n.parent.nrSegments])
+ n.parent.keys[n.parentIndex], n.parent.values[n.parentIndex] = n.keys[LockminDegree-1], n.values[LockminDegree-1]
+ copy(n.parent.children[n.parentIndex+2:], n.parent.children[n.parentIndex+1:n.parent.nrSegments+1])
+ for i := n.parentIndex + 2; i < n.parent.nrSegments+2; i++ {
+ n.parent.children[i].parentIndex = i
+ }
+ sibling := &Locknode{
+ nrSegments: LockminDegree - 1,
+ parent: n.parent,
+ parentIndex: n.parentIndex + 1,
+ hasChildren: n.hasChildren,
+ }
+ n.parent.children[n.parentIndex+1] = sibling
+ n.parent.nrSegments++
+ copy(sibling.keys[:LockminDegree-1], n.keys[LockminDegree:])
+ copy(sibling.values[:LockminDegree-1], n.values[LockminDegree:])
+ LockzeroValueSlice(n.values[LockminDegree-1:])
+ if n.hasChildren {
+ copy(sibling.children[:LockminDegree], n.children[LockminDegree:])
+ LockzeroNodeSlice(n.children[LockminDegree:])
+ for i := 0; i < LockminDegree; i++ {
+ sibling.children[i].parent = sibling
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = LockminDegree - 1
+
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < LockminDegree {
+ return gap
+ }
+ return LockGapIterator{sibling, gap.index - LockminDegree}
+}
+
+// rebalanceAfterRemove "unsplits" n and its ancestors if they are deficient
+// (contain fewer segments than required by B-tree invariants), as required for
+// removal, and returns an updated iterator to the position represented by gap.
+//
+// Precondition: n is the only node in the tree that may currently violate a
+// B-tree invariant.
+func (n *Locknode) rebalanceAfterRemove(gap LockGapIterator) LockGapIterator {
+ for {
+ if n.nrSegments >= LockminDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ return gap
+ }
+
+ if sibling := n.prevSibling(); sibling != nil && sibling.nrSegments >= LockminDegree {
+ copy(n.keys[1:], n.keys[:n.nrSegments])
+ copy(n.values[1:], n.values[:n.nrSegments])
+ n.keys[0] = n.parent.keys[n.parentIndex-1]
+ n.values[0] = n.parent.values[n.parentIndex-1]
+ n.parent.keys[n.parentIndex-1] = sibling.keys[sibling.nrSegments-1]
+ n.parent.values[n.parentIndex-1] = sibling.values[sibling.nrSegments-1]
+ lockSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ copy(n.children[1:], n.children[:n.nrSegments+1])
+ n.children[0] = sibling.children[sibling.nrSegments]
+ sibling.children[sibling.nrSegments] = nil
+ n.children[0].parent = n
+ n.children[0].parentIndex = 0
+ for i := 1; i < n.nrSegments+2; i++ {
+ n.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling && gap.index == sibling.nrSegments {
+ return LockGapIterator{n, 0}
+ }
+ if gap.node == n {
+ return LockGapIterator{n, gap.index + 1}
+ }
+ return gap
+ }
+ if sibling := n.nextSibling(); sibling != nil && sibling.nrSegments >= LockminDegree {
+ n.keys[n.nrSegments] = n.parent.keys[n.parentIndex]
+ n.values[n.nrSegments] = n.parent.values[n.parentIndex]
+ n.parent.keys[n.parentIndex] = sibling.keys[0]
+ n.parent.values[n.parentIndex] = sibling.values[0]
+ copy(sibling.keys[:sibling.nrSegments-1], sibling.keys[1:])
+ copy(sibling.values[:sibling.nrSegments-1], sibling.values[1:])
+ lockSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ n.children[n.nrSegments+1] = sibling.children[0]
+ copy(sibling.children[:sibling.nrSegments], sibling.children[1:])
+ sibling.children[sibling.nrSegments] = nil
+ n.children[n.nrSegments+1].parent = n
+ n.children[n.nrSegments+1].parentIndex = n.nrSegments + 1
+ for i := 0; i < sibling.nrSegments; i++ {
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling {
+ if gap.index == 0 {
+ return LockGapIterator{n, n.nrSegments}
+ }
+ return LockGapIterator{sibling, gap.index - 1}
+ }
+ return gap
+ }
+
+ p := n.parent
+ if p.nrSegments == 1 {
+
+ left, right := p.children[0], p.children[1]
+ p.nrSegments = left.nrSegments + right.nrSegments + 1
+ p.hasChildren = left.hasChildren
+ p.keys[left.nrSegments] = p.keys[0]
+ p.values[left.nrSegments] = p.values[0]
+ copy(p.keys[:left.nrSegments], left.keys[:left.nrSegments])
+ copy(p.values[:left.nrSegments], left.values[:left.nrSegments])
+ copy(p.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(p.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(p.children[:left.nrSegments+1], left.children[:left.nrSegments+1])
+ copy(p.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := 0; i < p.nrSegments+1; i++ {
+ p.children[i].parent = p
+ p.children[i].parentIndex = i
+ }
+ } else {
+ p.children[0] = nil
+ p.children[1] = nil
+ }
+ if gap.node == left {
+ return LockGapIterator{p, gap.index}
+ }
+ if gap.node == right {
+ return LockGapIterator{p, gap.index + left.nrSegments + 1}
+ }
+ return gap
+ }
+ // Merge n and either sibling, along with the segment separating the
+ // two, into whichever of the two nodes comes first. This is the
+ // reverse of the non-root splitting case in
+ // node.rebalanceBeforeInsert.
+ var left, right *Locknode
+ if n.parentIndex > 0 {
+ left = n.prevSibling()
+ right = n
+ } else {
+ left = n
+ right = n.nextSibling()
+ }
+
+ if gap.node == right {
+ gap = LockGapIterator{left, gap.index + left.nrSegments + 1}
+ }
+ left.keys[left.nrSegments] = p.keys[left.parentIndex]
+ left.values[left.nrSegments] = p.values[left.parentIndex]
+ copy(left.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(left.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(left.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := left.nrSegments + 1; i < left.nrSegments+right.nrSegments+2; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ }
+ }
+ left.nrSegments += right.nrSegments + 1
+ copy(p.keys[left.parentIndex:], p.keys[left.parentIndex+1:p.nrSegments])
+ copy(p.values[left.parentIndex:], p.values[left.parentIndex+1:p.nrSegments])
+ lockSetFunctions{}.ClearValue(&p.values[p.nrSegments-1])
+ copy(p.children[left.parentIndex+1:], p.children[left.parentIndex+2:p.nrSegments+1])
+ for i := 0; i < p.nrSegments; i++ {
+ p.children[i].parentIndex = i
+ }
+ p.children[p.nrSegments] = nil
+ p.nrSegments--
+
+ n = p
+ }
+}
+
+// A Iterator is conceptually one of:
+//
+// - A pointer to a segment in a set; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Iterators are copyable values and are meaningfully equality-comparable. The
+// zero value of Iterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type LockIterator struct {
+ // node is the node containing the iterated segment. If the iterator is
+ // terminal, node is nil.
+ node *Locknode
+
+ // index is the index of the segment in node.keys/values.
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (seg LockIterator) Ok() bool {
+ return seg.node != nil
+}
+
+// Range returns the iterated segment's range key.
+func (seg LockIterator) Range() LockRange {
+ return seg.node.keys[seg.index]
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (seg LockIterator) Start() uint64 {
+ return seg.node.keys[seg.index].Start
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (seg LockIterator) End() uint64 {
+ return seg.node.keys[seg.index].End
+}
+
+// SetRangeUnchecked mutates the iterated segment's range key. This operation
+// does not invalidate any iterators.
+//
+// Preconditions:
+//
+// - r.Length() > 0.
+//
+// - The new range must not overlap an existing one: If seg.NextSegment().Ok(),
+// then r.end <= seg.NextSegment().Start(); if seg.PrevSegment().Ok(), then
+// r.start >= seg.PrevSegment().End().
+func (seg LockIterator) SetRangeUnchecked(r LockRange) {
+ seg.node.keys[seg.index] = r
+}
+
+// SetRange mutates the iterated segment's range key. If the new range would
+// cause the iterated segment to overlap another segment, or if the new range
+// is invalid, SetRange panics. This operation does not invalidate any
+// iterators.
+func (seg LockIterator) SetRange(r LockRange) {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && r.Start < prev.End() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, prev.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && r.End > next.Start() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, next.Range()))
+ }
+ seg.SetRangeUnchecked(r)
+}
+
+// SetStartUnchecked mutates the iterated segment's start. This operation does
+// not invalidate any iterators.
+//
+// Preconditions: The new start must be valid: start < seg.End(); if
+// seg.PrevSegment().Ok(), then start >= seg.PrevSegment().End().
+func (seg LockIterator) SetStartUnchecked(start uint64) {
+ seg.node.keys[seg.index].Start = start
+}
+
+// SetStart mutates the iterated segment's start. If the new start value would
+// cause the iterated segment to overlap another segment, or would result in an
+// invalid range, SetStart panics. This operation does not invalidate any
+// iterators.
+func (seg LockIterator) SetStart(start uint64) {
+ if start >= seg.End() {
+ panic(fmt.Sprintf("new start %v would invalidate segment range %v", start, seg.Range()))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && start < prev.End() {
+ panic(fmt.Sprintf("new start %v would cause segment range %v to overlap segment range %v", start, seg.Range(), prev.Range()))
+ }
+ seg.SetStartUnchecked(start)
+}
+
+// SetEndUnchecked mutates the iterated segment's end. This operation does not
+// invalidate any iterators.
+//
+// Preconditions: The new end must be valid: end > seg.Start(); if
+// seg.NextSegment().Ok(), then end <= seg.NextSegment().Start().
+func (seg LockIterator) SetEndUnchecked(end uint64) {
+ seg.node.keys[seg.index].End = end
+}
+
+// SetEnd mutates the iterated segment's end. If the new end value would cause
+// the iterated segment to overlap another segment, or would result in an
+// invalid range, SetEnd panics. This operation does not invalidate any
+// iterators.
+func (seg LockIterator) SetEnd(end uint64) {
+ if end <= seg.Start() {
+ panic(fmt.Sprintf("new end %v would invalidate segment range %v", end, seg.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && end > next.Start() {
+ panic(fmt.Sprintf("new end %v would cause segment range %v to overlap segment range %v", end, seg.Range(), next.Range()))
+ }
+ seg.SetEndUnchecked(end)
+}
+
+// Value returns a copy of the iterated segment's value.
+func (seg LockIterator) Value() Lock {
+ return seg.node.values[seg.index]
+}
+
+// ValuePtr returns a pointer to the iterated segment's value. The pointer is
+// invalidated if the iterator is invalidated. This operation does not
+// invalidate any iterators.
+func (seg LockIterator) ValuePtr() *Lock {
+ return &seg.node.values[seg.index]
+}
+
+// SetValue mutates the iterated segment's value. This operation does not
+// invalidate any iterators.
+func (seg LockIterator) SetValue(val Lock) {
+ seg.node.values[seg.index] = val
+}
+
+// PrevSegment returns the iterated segment's predecessor. If there is no
+// preceding segment, PrevSegment returns a terminal iterator.
+func (seg LockIterator) PrevSegment() LockIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index].lastSegment()
+ }
+ if seg.index > 0 {
+ return LockIterator{seg.node, seg.index - 1}
+ }
+ if seg.node.parent == nil {
+ return LockIterator{}
+ }
+ return LocksegmentBeforePosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// NextSegment returns the iterated segment's successor. If there is no
+// succeeding segment, NextSegment returns a terminal iterator.
+func (seg LockIterator) NextSegment() LockIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment()
+ }
+ if seg.index < seg.node.nrSegments-1 {
+ return LockIterator{seg.node, seg.index + 1}
+ }
+ if seg.node.parent == nil {
+ return LockIterator{}
+ }
+ return LocksegmentAfterPosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// PrevGap returns the gap immediately before the iterated segment.
+func (seg LockIterator) PrevGap() LockGapIterator {
+ if seg.node.hasChildren {
+
+ return seg.node.children[seg.index].lastSegment().NextGap()
+ }
+ return LockGapIterator{seg.node, seg.index}
+}
+
+// NextGap returns the gap immediately after the iterated segment.
+func (seg LockIterator) NextGap() LockGapIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment().PrevGap()
+ }
+ return LockGapIterator{seg.node, seg.index + 1}
+}
+
+// PrevNonEmpty returns the iterated segment's predecessor if it is adjacent,
+// or the gap before the iterated segment otherwise. If seg.Start() ==
+// Functions.MinKey(), PrevNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by PrevNonEmpty will be
+// non-terminal.
+func (seg LockIterator) PrevNonEmpty() (LockIterator, LockGapIterator) {
+ gap := seg.PrevGap()
+ if gap.Range().Length() != 0 {
+ return LockIterator{}, gap
+ }
+ return gap.PrevSegment(), LockGapIterator{}
+}
+
+// NextNonEmpty returns the iterated segment's successor if it is adjacent, or
+// the gap after the iterated segment otherwise. If seg.End() ==
+// Functions.MaxKey(), NextNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by NextNonEmpty will be
+// non-terminal.
+func (seg LockIterator) NextNonEmpty() (LockIterator, LockGapIterator) {
+ gap := seg.NextGap()
+ if gap.Range().Length() != 0 {
+ return LockIterator{}, gap
+ }
+ return gap.NextSegment(), LockGapIterator{}
+}
+
+// A GapIterator is conceptually one of:
+//
+// - A pointer to a position between two segments, before the first segment, or
+// after the last segment in a set, called a *gap*; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Note that the gap between two adjacent segments exists (iterators to it are
+// non-terminal), but has a length of zero. GapIterator.IsEmpty returns true
+// for such gaps. An empty set contains a single gap, spanning the entire range
+// of the set's keys.
+//
+// GapIterators are copyable values and are meaningfully equality-comparable.
+// The zero value of GapIterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type LockGapIterator struct {
+ // The representation of a GapIterator is identical to that of an Iterator,
+ // except that index corresponds to positions between segments in the same
+ // way as for node.children (see comment for node.nrSegments).
+ node *Locknode
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (gap LockGapIterator) Ok() bool {
+ return gap.node != nil
+}
+
+// Range returns the range spanned by the iterated gap.
+func (gap LockGapIterator) Range() LockRange {
+ return LockRange{gap.Start(), gap.End()}
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (gap LockGapIterator) Start() uint64 {
+ if ps := gap.PrevSegment(); ps.Ok() {
+ return ps.End()
+ }
+ return lockSetFunctions{}.MinKey()
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (gap LockGapIterator) End() uint64 {
+ if ns := gap.NextSegment(); ns.Ok() {
+ return ns.Start()
+ }
+ return lockSetFunctions{}.MaxKey()
+}
+
+// IsEmpty returns true if the iterated gap is empty (that is, the "gap" is
+// between two adjacent segments.)
+func (gap LockGapIterator) IsEmpty() bool {
+ return gap.Range().Length() == 0
+}
+
+// PrevSegment returns the segment immediately before the iterated gap. If no
+// such segment exists, PrevSegment returns a terminal iterator.
+func (gap LockGapIterator) PrevSegment() LockIterator {
+ return LocksegmentBeforePosition(gap.node, gap.index)
+}
+
+// NextSegment returns the segment immediately after the iterated gap. If no
+// such segment exists, NextSegment returns a terminal iterator.
+func (gap LockGapIterator) NextSegment() LockIterator {
+ return LocksegmentAfterPosition(gap.node, gap.index)
+}
+
+// PrevGap returns the iterated gap's predecessor. If no such gap exists,
+// PrevGap returns a terminal iterator.
+func (gap LockGapIterator) PrevGap() LockGapIterator {
+ seg := gap.PrevSegment()
+ if !seg.Ok() {
+ return LockGapIterator{}
+ }
+ return seg.PrevGap()
+}
+
+// NextGap returns the iterated gap's successor. If no such gap exists, NextGap
+// returns a terminal iterator.
+func (gap LockGapIterator) NextGap() LockGapIterator {
+ seg := gap.NextSegment()
+ if !seg.Ok() {
+ return LockGapIterator{}
+ }
+ return seg.NextGap()
+}
+
+// segmentBeforePosition returns the predecessor segment of the position given
+// by n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentBeforePosition returns a terminal iterator.
+func LocksegmentBeforePosition(n *Locknode, i int) LockIterator {
+ for i == 0 {
+ if n.parent == nil {
+ return LockIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return LockIterator{n, i - 1}
+}
+
+// segmentAfterPosition returns the successor segment of the position given by
+// n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentAfterPosition returns a terminal iterator.
+func LocksegmentAfterPosition(n *Locknode, i int) LockIterator {
+ for i == n.nrSegments {
+ if n.parent == nil {
+ return LockIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return LockIterator{n, i}
+}
+
+func LockzeroValueSlice(slice []Lock) {
+
+ for i := range slice {
+ lockSetFunctions{}.ClearValue(&slice[i])
+ }
+}
+
+func LockzeroNodeSlice(slice []*Locknode) {
+ for i := range slice {
+ slice[i] = nil
+ }
+}
+
+// String stringifies a Set for debugging.
+func (s *LockSet) String() string {
+ return s.root.String()
+}
+
+// String stringifies a node (and all of its children) for debugging.
+func (n *Locknode) String() string {
+ var buf bytes.Buffer
+ n.writeDebugString(&buf, "")
+ return buf.String()
+}
+
+func (n *Locknode) writeDebugString(buf *bytes.Buffer, prefix string) {
+ if n.hasChildren != (n.nrSegments > 0 && n.children[0] != nil) {
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent value of hasChildren: got %v, want %v\n", n.hasChildren, !n.hasChildren))
+ }
+ for i := 0; i < n.nrSegments; i++ {
+ if child := n.children[i]; child != nil {
+ cprefix := fmt.Sprintf("%s- % 3d ", prefix, i)
+ if child.parent != n || child.parentIndex != i {
+ buf.WriteString(cprefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent linkage to parent: got (%p, %d), want (%p, %d)\n", child.parent, child.parentIndex, n, i))
+ }
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, i))
+ }
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("- % 3d: %v => %v\n", i, n.keys[i], n.values[i]))
+ }
+ if child := n.children[n.nrSegments]; child != nil {
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, n.nrSegments))
+ }
+}
+
+// SegmentDataSlices represents segments from a set as slices of start, end, and
+// values. SegmentDataSlices is primarily used as an intermediate representation
+// for save/restore and the layout here is optimized for that.
+//
+// +stateify savable
+type LockSegmentDataSlices struct {
+ Start []uint64
+ End []uint64
+ Values []Lock
+}
+
+// ExportSortedSlice returns a copy of all segments in the given set, in ascending
+// key order.
+func (s *LockSet) ExportSortedSlices() *LockSegmentDataSlices {
+ var sds LockSegmentDataSlices
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sds.Start = append(sds.Start, seg.Start())
+ sds.End = append(sds.End, seg.End())
+ sds.Values = append(sds.Values, seg.Value())
+ }
+ sds.Start = sds.Start[:len(sds.Start):len(sds.Start)]
+ sds.End = sds.End[:len(sds.End):len(sds.End)]
+ sds.Values = sds.Values[:len(sds.Values):len(sds.Values)]
+ return &sds
+}
+
+// ImportSortedSlice initializes the given set from the given slice.
+//
+// Preconditions: s must be empty. sds must represent a valid set (the segments
+// in sds must have valid lengths that do not overlap). The segments in sds
+// must be sorted in ascending key order.
+func (s *LockSet) ImportSortedSlices(sds *LockSegmentDataSlices) error {
+ if !s.IsEmpty() {
+ return fmt.Errorf("cannot import into non-empty set %v", s)
+ }
+ gap := s.FirstGap()
+ for i := range sds.Start {
+ r := LockRange{sds.Start[i], sds.End[i]}
+ if !gap.Range().IsSupersetOf(r) {
+ return fmt.Errorf("segment overlaps a preceding segment or is incorrectly sorted: [%d, %d) => %v", sds.Start[i], sds.End[i], sds.Values[i])
+ }
+ gap = s.InsertWithoutMerging(gap, r, sds.Values[i]).NextGap()
+ }
+ return nil
+}
+func (s *LockSet) saveRoot() *LockSegmentDataSlices {
+ return s.ExportSortedSlices()
+}
+
+func (s *LockSet) loadRoot(sds *LockSegmentDataSlices) {
+ if err := s.ImportSortedSlices(sds); err != nil {
+ panic(err)
+ }
+}
diff --git a/pkg/sentry/fs/lock/lock_state_autogen.go b/pkg/sentry/fs/lock/lock_state_autogen.go
new file mode 100755
index 000000000..cb69e2cd0
--- /dev/null
+++ b/pkg/sentry/fs/lock/lock_state_autogen.go
@@ -0,0 +1,106 @@
+// automatically generated by stateify.
+
+package lock
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *Lock) beforeSave() {}
+func (x *Lock) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Readers", &x.Readers)
+ m.Save("HasWriter", &x.HasWriter)
+ m.Save("Writer", &x.Writer)
+}
+
+func (x *Lock) afterLoad() {}
+func (x *Lock) load(m state.Map) {
+ m.Load("Readers", &x.Readers)
+ m.Load("HasWriter", &x.HasWriter)
+ m.Load("Writer", &x.Writer)
+}
+
+func (x *Locks) beforeSave() {}
+func (x *Locks) save(m state.Map) {
+ x.beforeSave()
+ if !state.IsZeroValue(x.blockedQueue) { m.Failf("blockedQueue is %v, expected zero", x.blockedQueue) }
+ m.Save("locks", &x.locks)
+}
+
+func (x *Locks) afterLoad() {}
+func (x *Locks) load(m state.Map) {
+ m.Load("locks", &x.locks)
+}
+
+func (x *LockRange) beforeSave() {}
+func (x *LockRange) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Start", &x.Start)
+ m.Save("End", &x.End)
+}
+
+func (x *LockRange) afterLoad() {}
+func (x *LockRange) load(m state.Map) {
+ m.Load("Start", &x.Start)
+ m.Load("End", &x.End)
+}
+
+func (x *LockSet) beforeSave() {}
+func (x *LockSet) save(m state.Map) {
+ x.beforeSave()
+ var root *LockSegmentDataSlices = x.saveRoot()
+ m.SaveValue("root", root)
+}
+
+func (x *LockSet) afterLoad() {}
+func (x *LockSet) load(m state.Map) {
+ m.LoadValue("root", new(*LockSegmentDataSlices), func(y interface{}) { x.loadRoot(y.(*LockSegmentDataSlices)) })
+}
+
+func (x *Locknode) beforeSave() {}
+func (x *Locknode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("nrSegments", &x.nrSegments)
+ m.Save("parent", &x.parent)
+ m.Save("parentIndex", &x.parentIndex)
+ m.Save("hasChildren", &x.hasChildren)
+ m.Save("keys", &x.keys)
+ m.Save("values", &x.values)
+ m.Save("children", &x.children)
+}
+
+func (x *Locknode) afterLoad() {}
+func (x *Locknode) load(m state.Map) {
+ m.Load("nrSegments", &x.nrSegments)
+ m.Load("parent", &x.parent)
+ m.Load("parentIndex", &x.parentIndex)
+ m.Load("hasChildren", &x.hasChildren)
+ m.Load("keys", &x.keys)
+ m.Load("values", &x.values)
+ m.Load("children", &x.children)
+}
+
+func (x *LockSegmentDataSlices) beforeSave() {}
+func (x *LockSegmentDataSlices) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Start", &x.Start)
+ m.Save("End", &x.End)
+ m.Save("Values", &x.Values)
+}
+
+func (x *LockSegmentDataSlices) afterLoad() {}
+func (x *LockSegmentDataSlices) load(m state.Map) {
+ m.Load("Start", &x.Start)
+ m.Load("End", &x.End)
+ m.Load("Values", &x.Values)
+}
+
+func init() {
+ state.Register("lock.Lock", (*Lock)(nil), state.Fns{Save: (*Lock).save, Load: (*Lock).load})
+ state.Register("lock.Locks", (*Locks)(nil), state.Fns{Save: (*Locks).save, Load: (*Locks).load})
+ state.Register("lock.LockRange", (*LockRange)(nil), state.Fns{Save: (*LockRange).save, Load: (*LockRange).load})
+ state.Register("lock.LockSet", (*LockSet)(nil), state.Fns{Save: (*LockSet).save, Load: (*LockSet).load})
+ state.Register("lock.Locknode", (*Locknode)(nil), state.Fns{Save: (*Locknode).save, Load: (*Locknode).load})
+ state.Register("lock.LockSegmentDataSlices", (*LockSegmentDataSlices)(nil), state.Fns{Save: (*LockSegmentDataSlices).save, Load: (*LockSegmentDataSlices).load})
+}
diff --git a/pkg/sentry/fs/lock/lock_test.go b/pkg/sentry/fs/lock/lock_test.go
deleted file mode 100644
index ba002aeb7..000000000
--- a/pkg/sentry/fs/lock/lock_test.go
+++ /dev/null
@@ -1,1059 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package lock
-
-import (
- "reflect"
- "testing"
-)
-
-type entry struct {
- Lock
- LockRange
-}
-
-func equals(e0, e1 []entry) bool {
- if len(e0) != len(e1) {
- return false
- }
- for i := range e0 {
- for k := range e0[i].Lock.Readers {
- if !e1[i].Lock.Readers[k] {
- return false
- }
- }
- for k := range e1[i].Lock.Readers {
- if !e0[i].Lock.Readers[k] {
- return false
- }
- }
- if !reflect.DeepEqual(e0[i].LockRange, e1[i].LockRange) {
- return false
- }
- if e0[i].Lock.HasWriter != e1[i].Lock.HasWriter {
- return false
- }
- if e0[i].Lock.Writer != e1[i].Lock.Writer {
- return false
- }
- }
- return true
-}
-
-// fill a LockSet with consecutive region locks. Will panic if
-// LockRanges are not consecutive.
-func fill(entries []entry) LockSet {
- l := LockSet{}
- for _, e := range entries {
- gap := l.FindGap(e.LockRange.Start)
- if !gap.Ok() {
- panic("cannot insert into existing segment")
- }
- l.Insert(gap, e.LockRange, e.Lock)
- }
- return l
-}
-
-func TestCanLockEmpty(t *testing.T) {
- l := LockSet{}
-
- // Expect to be able to take any locks given that the set is empty.
- eof := l.FirstGap().End()
- r := LockRange{0, eof}
- if !l.canLock(1, ReadLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got false, want true", ReadLock, r, 1)
- }
- if !l.canLock(2, ReadLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got false, want true", ReadLock, r, 2)
- }
- if !l.canLock(1, WriteLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got false, want true", WriteLock, r, 1)
- }
- if !l.canLock(2, WriteLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got false, want true", WriteLock, r, 2)
- }
-}
-
-func TestCanLock(t *testing.T) {
- // + -------------- + ---------- + -------------- + --------- +
- // | Readers 1 & 2 | Readers 1 | Readers 1 & 3 | Writer 1 |
- // + ------------- + ---------- + -------------- + --------- +
- // 0 1024 2048 3072 4096
- l := fill([]entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{1: true, 2: true}},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{1: true}},
- LockRange: LockRange{1024, 2048},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{1: true, 3: true}},
- LockRange: LockRange{2048, 3072},
- },
- {
- Lock: Lock{HasWriter: true, Writer: 1},
- LockRange: LockRange{3072, 4096},
- },
- })
-
- // Now that we have a mildly interesting layout, try some checks on different
- // ranges, uids, and lock types.
- //
- // Expect to be able to extend the read lock, despite the writer lock, because
- // the writer has the same uid as the requested read lock.
- r := LockRange{0, 8192}
- if !l.canLock(1, ReadLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got false, want true", ReadLock, r, 1)
- }
- // Expect to *not* be able to extend the read lock since there is an overlapping
- // writer region locked by someone other than the uid.
- if l.canLock(2, ReadLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got true, want false", ReadLock, r, 2)
- }
- // Expect to be able to extend the read lock if there are only other readers in
- // the way.
- r = LockRange{64, 3072}
- if !l.canLock(2, ReadLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got false, want true", ReadLock, r, 2)
- }
- // Expect to be able to set a read lock beyond the range of any existing locks.
- r = LockRange{4096, 10240}
- if !l.canLock(2, ReadLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got false, want true", ReadLock, r, 2)
- }
-
- // Expect to not be able to take a write lock with other readers in the way.
- r = LockRange{0, 8192}
- if l.canLock(1, WriteLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got true, want false", WriteLock, r, 1)
- }
- // Expect to be able to extend the write lock for the same uid.
- r = LockRange{3072, 8192}
- if !l.canLock(1, WriteLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got false, want true", WriteLock, r, 1)
- }
- // Expect to not be able to overlap a write lock for two different uids.
- if l.canLock(2, WriteLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got true, want false", WriteLock, r, 2)
- }
- // Expect to be able to set a write lock that is beyond the range of any
- // existing locks.
- r = LockRange{8192, 10240}
- if !l.canLock(2, WriteLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got false, want true", WriteLock, r, 2)
- }
- // Expect to be able to upgrade a read lock (any portion of it).
- r = LockRange{1024, 2048}
- if !l.canLock(1, WriteLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got false, want true", WriteLock, r, 1)
- }
- r = LockRange{1080, 2000}
- if !l.canLock(1, WriteLock, r) {
- t.Fatalf("canLock type %d for range %v and uid %d got false, want true", WriteLock, r, 1)
- }
-}
-
-func TestSetLock(t *testing.T) {
- tests := []struct {
- // description of test.
- name string
-
- // LockSet entries to pre-fill.
- before []entry
-
- // Description of region to lock:
- //
- // start is the file offset of the lock.
- start uint64
- // end is the end file offset of the lock.
- end uint64
- // uid of lock attempter.
- uid UniqueID
- // lock type requested.
- lockType LockType
-
- // success is true if taking the above
- // lock should succeed.
- success bool
-
- // Expected layout of the set after locking
- // if success is true.
- after []entry
- }{
- {
- name: "set zero length ReadLock on empty set",
- start: 0,
- end: 0,
- uid: 0,
- lockType: ReadLock,
- success: true,
- },
- {
- name: "set zero length WriteLock on empty set",
- start: 0,
- end: 0,
- uid: 0,
- lockType: WriteLock,
- success: true,
- },
- {
- name: "set ReadLock on empty set",
- start: 0,
- end: LockEOF,
- uid: 0,
- lockType: ReadLock,
- success: true,
- // + ----------------------------------------- +
- // | Readers 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, LockEOF},
- },
- },
- },
- {
- name: "set WriteLock on empty set",
- start: 0,
- end: LockEOF,
- uid: 0,
- lockType: WriteLock,
- success: true,
- // + ----------------------------------------- +
- // | Writer 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- after: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, LockEOF},
- },
- },
- },
- {
- name: "set ReadLock on WriteLock same uid",
- // + ----------------------------------------- +
- // | Writer 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 0,
- end: 4096,
- uid: 0,
- lockType: ReadLock,
- success: true,
- // + ----------- + --------------------------- +
- // | Readers 0 | Writer 0 |
- // + ----------- + --------------------------- +
- // 0 4096 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, 4096},
- },
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- },
- {
- name: "set WriteLock on ReadLock same uid",
- // + ----------------------------------------- +
- // | Readers 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 0,
- end: 4096,
- uid: 0,
- lockType: WriteLock,
- success: true,
- // + ----------- + --------------------------- +
- // | Writer 0 | Readers 0 |
- // + ----------- + --------------------------- +
- // 0 4096 max uint64
- after: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, 4096},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- },
- {
- name: "set ReadLock on WriteLock different uid",
- // + ----------------------------------------- +
- // | Writer 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 0,
- end: 4096,
- uid: 1,
- lockType: ReadLock,
- success: false,
- },
- {
- name: "set WriteLock on ReadLock different uid",
- // + ----------------------------------------- +
- // | Readers 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 0,
- end: 4096,
- uid: 1,
- lockType: WriteLock,
- success: false,
- },
- {
- name: "split ReadLock for overlapping lock at start 0",
- // + ----------------------------------------- +
- // | Readers 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 0,
- end: 4096,
- uid: 1,
- lockType: ReadLock,
- success: true,
- // + -------------- + --------------------------- +
- // | Readers 0 & 1 | Readers 0 |
- // + -------------- + --------------------------- +
- // 0 4096 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{0, 4096},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- },
- {
- name: "split ReadLock for overlapping lock at non-zero start",
- // + ----------------------------------------- +
- // | Readers 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 4096,
- end: 8192,
- uid: 1,
- lockType: ReadLock,
- success: true,
- // + ---------- + -------------- + ----------- +
- // | Readers 0 | Readers 0 & 1 | Readers 0 |
- // + ---------- + -------------- + ----------- +
- // 0 4096 8192 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, 4096},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{4096, 8192},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{8192, LockEOF},
- },
- },
- },
- {
- name: "fill front gap with ReadLock",
- // + --------- + ---------------------------- +
- // | gap | Readers 0 |
- // + --------- + ---------------------------- +
- // 0 1024 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{1024, LockEOF},
- },
- },
- start: 0,
- end: 8192,
- uid: 0,
- lockType: ReadLock,
- success: true,
- // + ----------------------------------------- +
- // | Readers 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, LockEOF},
- },
- },
- },
- {
- name: "fill end gap with ReadLock",
- // + ---------------------------- +
- // | Readers 0 |
- // + ---------------------------- +
- // 0 4096
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, 4096},
- },
- },
- start: 1024,
- end: LockEOF,
- uid: 0,
- lockType: ReadLock,
- success: true,
- // Note that this is not merged after lock does a Split. This is
- // fine because the two locks will still *behave* as one. In other
- // words we can fragment any lock all we want and semantically it
- // makes no difference.
- //
- // + ----------- + --------------------------- +
- // | Readers 0 | Readers 0 |
- // + ----------- + --------------------------- +
- // 0 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{1024, LockEOF},
- },
- },
- },
- {
- name: "fill gap with ReadLock and split",
- // + --------- + ---------------------------- +
- // | gap | Readers 0 |
- // + --------- + ---------------------------- +
- // 0 1024 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{1024, LockEOF},
- },
- },
- start: 0,
- end: 4096,
- uid: 1,
- lockType: ReadLock,
- success: true,
- // + --------- + ------------- + ------------- +
- // | Reader 1 | Readers 0 & 1 | Reader 0 |
- // + ----------+ ------------- + ------------- +
- // 0 1024 4096 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{1: true}},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{1024, 4096},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- },
- {
- name: "upgrade ReadLock to WriteLock for single uid fill gap",
- // + ------------- + --------- + --- + ------------- +
- // | Readers 0 & 1 | Readers 0 | gap | Readers 0 & 2 |
- // + ------------- + --------- + --- + ------------- +
- // 0 1024 2048 4096 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{1024, 2048},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 2: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- start: 1024,
- end: 4096,
- uid: 0,
- lockType: WriteLock,
- success: true,
- // + ------------- + -------- + ------------- +
- // | Readers 0 & 1 | Writer 0 | Readers 0 & 2 |
- // + ------------- + -------- + ------------- +
- // 0 1024 4096 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{1024, 4096},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 2: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- },
- {
- name: "upgrade ReadLock to WriteLock for single uid keep gap",
- // + ------------- + --------- + --- + ------------- +
- // | Readers 0 & 1 | Readers 0 | gap | Readers 0 & 2 |
- // + ------------- + --------- + --- + ------------- +
- // 0 1024 2048 4096 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{1024, 2048},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 2: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- start: 1024,
- end: 3072,
- uid: 0,
- lockType: WriteLock,
- success: true,
- // + ------------- + -------- + --- + ------------- +
- // | Readers 0 & 1 | Writer 0 | gap | Readers 0 & 2 |
- // + ------------- + -------- + --- + ------------- +
- // 0 1024 3072 4096 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{1024, 3072},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 2: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- },
- {
- name: "fail to upgrade ReadLock to WriteLock with conflicting Reader",
- // + ------------- + --------- +
- // | Readers 0 & 1 | Readers 0 |
- // + ------------- + --------- +
- // 0 1024 2048
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{1024, 2048},
- },
- },
- start: 0,
- end: 2048,
- uid: 0,
- lockType: WriteLock,
- success: false,
- },
- {
- name: "take WriteLock on whole file if all uids are the same",
- // + ------------- + --------- + --------- + ---------- +
- // | Writer 0 | Readers 0 | Readers 0 | Readers 0 |
- // + ------------- + --------- + --------- + ---------- +
- // 0 1024 2048 4096 max uint64
- before: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{1024, 2048},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{2048, 4096},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- start: 0,
- end: LockEOF,
- uid: 0,
- lockType: WriteLock,
- success: true,
- // We do not manually merge locks. Semantically a fragmented lock
- // held by the same uid will behave as one lock so it makes no difference.
- //
- // + ------------- + ---------------------------- +
- // | Writer 0 | Writer 0 |
- // + ------------- + ---------------------------- +
- // 0 1024 max uint64
- after: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{1024, LockEOF},
- },
- },
- },
- }
-
- for _, test := range tests {
- l := fill(test.before)
-
- r := LockRange{Start: test.start, End: test.end}
- success := l.lock(test.uid, test.lockType, r)
- var got []entry
- for seg := l.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
- got = append(got, entry{
- Lock: seg.Value(),
- LockRange: seg.Range(),
- })
- }
-
- if success != test.success {
- t.Errorf("%s: setlock(%v, %+v, %d, %d) got success %v, want %v", test.name, test.before, r, test.uid, test.lockType, success, test.success)
- continue
- }
-
- if success {
- if !equals(got, test.after) {
- t.Errorf("%s: got set %+v, want %+v", test.name, got, test.after)
- }
- }
- }
-}
-
-func TestUnlock(t *testing.T) {
- tests := []struct {
- // description of test.
- name string
-
- // LockSet entries to pre-fill.
- before []entry
-
- // Description of region to unlock:
- //
- // start is the file start of the lock.
- start uint64
- // end is the end file start of the lock.
- end uint64
- // uid of lock holder.
- uid UniqueID
-
- // Expected layout of the set after unlocking.
- after []entry
- }{
- {
- name: "unlock zero length on empty set",
- start: 0,
- end: 0,
- uid: 0,
- },
- {
- name: "unlock on empty set (no-op)",
- start: 0,
- end: LockEOF,
- uid: 0,
- },
- {
- name: "unlock uid not locked (no-op)",
- // + --------------------------- +
- // | Readers 1 & 2 |
- // + --------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{1: true, 2: true}},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 1024,
- end: 4096,
- uid: 0,
- // + --------------------------- +
- // | Readers 1 & 2 |
- // + --------------------------- +
- // 0 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{1: true, 2: true}},
- LockRange: LockRange{0, LockEOF},
- },
- },
- },
- {
- name: "unlock ReadLock over entire file",
- // + ----------------------------------------- +
- // | Readers 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 0,
- end: LockEOF,
- uid: 0,
- },
- {
- name: "unlock WriteLock over entire file",
- // + ----------------------------------------- +
- // | Writer 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 0,
- end: LockEOF,
- uid: 0,
- },
- {
- name: "unlock partial ReadLock (start)",
- // + ----------------------------------------- +
- // | Readers 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 0,
- end: 4096,
- uid: 0,
- // + ------ + --------------------------- +
- // | gap | Readers 0 |
- // +------- + --------------------------- +
- // 0 4096 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- },
- {
- name: "unlock partial WriteLock (start)",
- // + ----------------------------------------- +
- // | Writer 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 0,
- end: 4096,
- uid: 0,
- // + ------ + --------------------------- +
- // | gap | Writer 0 |
- // +------- + --------------------------- +
- // 0 4096 max uint64
- after: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- },
- {
- name: "unlock partial ReadLock (end)",
- // + ----------------------------------------- +
- // | Readers 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 4096,
- end: LockEOF,
- uid: 0,
- // + --------------------------- +
- // | Readers 0 |
- // +---------------------------- +
- // 0 4096
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true}},
- LockRange: LockRange{0, 4096},
- },
- },
- },
- {
- name: "unlock partial WriteLock (end)",
- // + ----------------------------------------- +
- // | Writer 0 |
- // + ----------------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 4096,
- end: LockEOF,
- uid: 0,
- // + --------------------------- +
- // | Writer 0 |
- // +---------------------------- +
- // 0 4096
- after: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, 4096},
- },
- },
- },
- {
- name: "unlock for single uid",
- // + ------------- + --------- + ------------------- +
- // | Readers 0 & 1 | Writer 0 | Readers 0 & 1 & 2 |
- // + ------------- + --------- + ------------------- +
- // 0 1024 4096 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{1024, 4096},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true, 2: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- start: 0,
- end: LockEOF,
- uid: 0,
- // + --------- + --- + --------------- +
- // | Readers 1 | gap | Readers 1 & 2 |
- // + --------- + --- + --------------- +
- // 0 1024 4096 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{1: true}},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{1: true, 2: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- },
- {
- name: "unlock subsection locked",
- // + ------------------------------- +
- // | Readers 0 & 1 & 2 |
- // + ------------------------------- +
- // 0 max uint64
- before: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true, 2: true}},
- LockRange: LockRange{0, LockEOF},
- },
- },
- start: 1024,
- end: 4096,
- uid: 0,
- // + ----------------- + ------------- + ----------------- +
- // | Readers 0 & 1 & 2 | Readers 1 & 2 | Readers 0 & 1 & 2 |
- // + ----------------- + ------------- + ----------------- +
- // 0 1024 4096 max uint64
- after: []entry{
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true, 2: true}},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{1: true, 2: true}},
- LockRange: LockRange{1024, 4096},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true, 2: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- },
- {
- name: "unlock mid-gap to increase gap",
- // + --------- + ----- + ------------------- +
- // | Writer 0 | gap | Readers 0 & 1 |
- // + --------- + ----- + ------------------- +
- // 0 1024 4096 max uint64
- before: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- start: 8,
- end: 2048,
- uid: 0,
- // + --------- + ----- + ------------------- +
- // | Writer 0 | gap | Readers 0 & 1 |
- // + --------- + ----- + ------------------- +
- // 0 8 4096 max uint64
- after: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, 8},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- },
- {
- name: "unlock split region on uid mid-gap",
- // + --------- + ----- + ------------------- +
- // | Writer 0 | gap | Readers 0 & 1 |
- // + --------- + ----- + ------------------- +
- // 0 1024 4096 max uint64
- before: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{4096, LockEOF},
- },
- },
- start: 2048,
- end: 8192,
- uid: 0,
- // + --------- + ----- + --------- + ------------- +
- // | Writer 0 | gap | Readers 1 | Readers 0 & 1 |
- // + --------- + ----- + --------- + ------------- +
- // 0 1024 4096 8192 max uint64
- after: []entry{
- {
- Lock: Lock{HasWriter: true, Writer: 0},
- LockRange: LockRange{0, 1024},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{1: true}},
- LockRange: LockRange{4096, 8192},
- },
- {
- Lock: Lock{Readers: map[UniqueID]bool{0: true, 1: true}},
- LockRange: LockRange{8192, LockEOF},
- },
- },
- },
- }
-
- for _, test := range tests {
- l := fill(test.before)
-
- r := LockRange{Start: test.start, End: test.end}
- l.unlock(test.uid, r)
- var got []entry
- for seg := l.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
- got = append(got, entry{
- Lock: seg.Value(),
- LockRange: seg.Range(),
- })
- }
- if !equals(got, test.after) {
- t.Errorf("%s: got set %+v, want %+v", test.name, got, test.after)
- }
- }
-}
diff --git a/pkg/sentry/fs/mount_test.go b/pkg/sentry/fs/mount_test.go
deleted file mode 100644
index 0b84732aa..000000000
--- a/pkg/sentry/fs/mount_test.go
+++ /dev/null
@@ -1,271 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fs
-
-import (
- "fmt"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
-)
-
-// cacheReallyContains iterates through the dirent cache to determine whether
-// it contains the given dirent.
-func cacheReallyContains(cache *DirentCache, d *Dirent) bool {
- for i := cache.list.Front(); i != nil; i = i.Next() {
- if i == d {
- return true
- }
- }
- return false
-}
-
-func mountPathsAre(root *Dirent, got []*Mount, want ...string) error {
- gotPaths := make(map[string]struct{}, len(got))
- gotStr := make([]string, len(got))
- for i, g := range got {
- groot := g.Root()
- name, _ := groot.FullName(root)
- groot.DecRef()
- gotStr[i] = name
- gotPaths[name] = struct{}{}
- }
- if len(got) != len(want) {
- return fmt.Errorf("mount paths are different, got: %q, want: %q", gotStr, want)
- }
- for _, w := range want {
- if _, ok := gotPaths[w]; !ok {
- return fmt.Errorf("no mount with path %q found", w)
- }
- }
- return nil
-}
-
-// TestMountSourceOnlyCachedOnce tests that a Dirent that is mounted over only ends
-// up in a single Dirent Cache. NOTE(b/63848693): Having a dirent in multiple
-// caches causes major consistency issues.
-func TestMountSourceOnlyCachedOnce(t *testing.T) {
- ctx := contexttest.Context(t)
-
- rootCache := NewDirentCache(100)
- rootInode := NewMockInode(ctx, NewMockMountSource(rootCache), StableAttr{
- Type: Directory,
- })
- mm, err := NewMountNamespace(ctx, rootInode)
- if err != nil {
- t.Fatalf("NewMountNamespace failed: %v", err)
- }
- rootDirent := mm.Root()
- defer rootDirent.DecRef()
-
- // Get a child of the root which we will mount over. Note that the
- // MockInodeOperations causes Walk to always succeed.
- child, err := rootDirent.Walk(ctx, rootDirent, "child")
- if err != nil {
- t.Fatalf("failed to walk to child dirent: %v", err)
- }
- child.maybeExtendReference() // Cache.
-
- // Ensure that the root cache contains the child.
- if !cacheReallyContains(rootCache, child) {
- t.Errorf("wanted rootCache to contain child dirent, but it did not")
- }
-
- // Create a new cache and inode, and mount it over child.
- submountCache := NewDirentCache(100)
- submountInode := NewMockInode(ctx, NewMockMountSource(submountCache), StableAttr{
- Type: Directory,
- })
- if err := mm.Mount(ctx, child, submountInode); err != nil {
- t.Fatalf("failed to mount over child: %v", err)
- }
-
- // Walk to the child again.
- child2, err := rootDirent.Walk(ctx, rootDirent, "child")
- if err != nil {
- t.Fatalf("failed to walk to child dirent: %v", err)
- }
-
- // Should have a different Dirent than before.
- if child == child2 {
- t.Fatalf("expected %v not equal to %v, but they are the same", child, child2)
- }
-
- // Neither of the caches should no contain the child.
- if cacheReallyContains(rootCache, child) {
- t.Errorf("wanted rootCache not to contain child dirent, but it did")
- }
- if cacheReallyContains(submountCache, child) {
- t.Errorf("wanted submountCache not to contain child dirent, but it did")
- }
-}
-
-func TestAllMountsUnder(t *testing.T) {
- ctx := contexttest.Context(t)
-
- rootCache := NewDirentCache(100)
- rootInode := NewMockInode(ctx, NewMockMountSource(rootCache), StableAttr{
- Type: Directory,
- })
- mm, err := NewMountNamespace(ctx, rootInode)
- if err != nil {
- t.Fatalf("NewMountNamespace failed: %v", err)
- }
- rootDirent := mm.Root()
- defer rootDirent.DecRef()
-
- // Add mounts at the following paths:
- paths := []string{
- "/foo",
- "/foo/bar",
- "/foo/bar/baz",
- "/foo/qux",
- "/waldo",
- }
-
- var maxTraversals uint
- for _, p := range paths {
- maxTraversals = 0
- d, err := mm.FindLink(ctx, rootDirent, nil, p, &maxTraversals)
- if err != nil {
- t.Fatalf("could not find path %q in mount manager: %v", p, err)
- }
-
- submountInode := NewMockInode(ctx, NewMockMountSource(nil), StableAttr{
- Type: Directory,
- })
- if err := mm.Mount(ctx, d, submountInode); err != nil {
- t.Fatalf("could not mount at %q: %v", p, err)
- }
- d.DecRef()
- }
-
- // mm root should contain all submounts (and does not include the root mount).
- rootMnt := mm.FindMount(rootDirent)
- submounts := mm.AllMountsUnder(rootMnt)
- allPaths := append(paths, "/")
- if err := mountPathsAre(rootDirent, submounts, allPaths...); err != nil {
- t.Error(err)
- }
-
- // Each mount should have a unique ID.
- foundIDs := make(map[uint64]struct{})
- for _, m := range submounts {
- if _, ok := foundIDs[m.ID]; ok {
- t.Errorf("got multiple mounts with id %d", m.ID)
- }
- foundIDs[m.ID] = struct{}{}
- }
-
- // Root mount should have no parent.
- if p := rootMnt.ParentID; p != invalidMountID {
- t.Errorf("root.Parent got %v wanted nil", p)
- }
-
- // Check that "foo" mount has 3 children.
- maxTraversals = 0
- d, err := mm.FindLink(ctx, rootDirent, nil, "/foo", &maxTraversals)
- if err != nil {
- t.Fatalf("could not find path %q in mount manager: %v", "/foo", err)
- }
- defer d.DecRef()
- submounts = mm.AllMountsUnder(mm.FindMount(d))
- if err := mountPathsAre(rootDirent, submounts, "/foo", "/foo/bar", "/foo/qux", "/foo/bar/baz"); err != nil {
- t.Error(err)
- }
-
- // "waldo" mount should have no children.
- maxTraversals = 0
- waldo, err := mm.FindLink(ctx, rootDirent, nil, "/waldo", &maxTraversals)
- if err != nil {
- t.Fatalf("could not find path %q in mount manager: %v", "/waldo", err)
- }
- defer waldo.DecRef()
- submounts = mm.AllMountsUnder(mm.FindMount(waldo))
- if err := mountPathsAre(rootDirent, submounts, "/waldo"); err != nil {
- t.Error(err)
- }
-}
-
-func TestUnmount(t *testing.T) {
- ctx := contexttest.Context(t)
-
- rootCache := NewDirentCache(100)
- rootInode := NewMockInode(ctx, NewMockMountSource(rootCache), StableAttr{
- Type: Directory,
- })
- mm, err := NewMountNamespace(ctx, rootInode)
- if err != nil {
- t.Fatalf("NewMountNamespace failed: %v", err)
- }
- rootDirent := mm.Root()
- defer rootDirent.DecRef()
-
- // Add mounts at the following paths:
- paths := []string{
- "/foo",
- "/foo/bar",
- "/foo/bar/goo",
- "/foo/bar/goo/abc",
- "/foo/abc",
- "/foo/def",
- "/waldo",
- "/wally",
- }
-
- var maxTraversals uint
- for _, p := range paths {
- maxTraversals = 0
- d, err := mm.FindLink(ctx, rootDirent, nil, p, &maxTraversals)
- if err != nil {
- t.Fatalf("could not find path %q in mount manager: %v", p, err)
- }
-
- submountInode := NewMockInode(ctx, NewMockMountSource(nil), StableAttr{
- Type: Directory,
- })
- if err := mm.Mount(ctx, d, submountInode); err != nil {
- t.Fatalf("could not mount at %q: %v", p, err)
- }
- d.DecRef()
- }
-
- allPaths := make([]string, len(paths)+1)
- allPaths[0] = "/"
- copy(allPaths[1:], paths)
-
- rootMnt := mm.FindMount(rootDirent)
- for i := len(paths) - 1; i >= 0; i-- {
- maxTraversals = 0
- p := paths[i]
- d, err := mm.FindLink(ctx, rootDirent, nil, p, &maxTraversals)
- if err != nil {
- t.Fatalf("could not find path %q in mount manager: %v", p, err)
- }
-
- if err := mm.Unmount(ctx, d, false); err != nil {
- t.Fatalf("could not unmount at %q: %v", p, err)
- }
- d.DecRef()
-
- // Remove the path that has been unmounted and the check that the remaining
- // mounts are still there.
- allPaths = allPaths[:len(allPaths)-1]
- submounts := mm.AllMountsUnder(rootMnt)
- if err := mountPathsAre(rootDirent, submounts, allPaths...); err != nil {
- t.Error(err)
- }
- }
-}
diff --git a/pkg/sentry/fs/mounts_test.go b/pkg/sentry/fs/mounts_test.go
deleted file mode 100644
index c4c771f2c..000000000
--- a/pkg/sentry/fs/mounts_test.go
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fs_test
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- "gvisor.dev/gvisor/pkg/sentry/fs/fsutil"
- "gvisor.dev/gvisor/pkg/sentry/fs/ramfs"
- "gvisor.dev/gvisor/pkg/sentry/kernel/contexttest"
-)
-
-// Creates a new MountNamespace with filesystem:
-// / (root dir)
-// |-foo (dir)
-// |-bar (file)
-func createMountNamespace(ctx context.Context) (*fs.MountNamespace, error) {
- perms := fs.FilePermsFromMode(0777)
- m := fs.NewPseudoMountSource(ctx)
-
- barFile := fsutil.NewSimpleFileInode(ctx, fs.RootOwner, perms, 0)
- fooDir := ramfs.NewDir(ctx, map[string]*fs.Inode{
- "bar": fs.NewInode(ctx, barFile, m, fs.StableAttr{Type: fs.RegularFile}),
- }, fs.RootOwner, perms)
- rootDir := ramfs.NewDir(ctx, map[string]*fs.Inode{
- "foo": fs.NewInode(ctx, fooDir, m, fs.StableAttr{Type: fs.Directory}),
- }, fs.RootOwner, perms)
-
- return fs.NewMountNamespace(ctx, fs.NewInode(ctx, rootDir, m, fs.StableAttr{Type: fs.Directory}))
-}
-
-func TestFindLink(t *testing.T) {
- ctx := contexttest.Context(t)
- mm, err := createMountNamespace(ctx)
- if err != nil {
- t.Fatalf("createMountNamespace failed: %v", err)
- }
-
- root := mm.Root()
- defer root.DecRef()
- foo, err := root.Walk(ctx, root, "foo")
- if err != nil {
- t.Fatalf("Error walking to foo: %v", err)
- }
-
- // Positive cases.
- for _, tc := range []struct {
- findPath string
- wd *fs.Dirent
- wantPath string
- }{
- {".", root, "/"},
- {".", foo, "/foo"},
- {"..", foo, "/"},
- {"../../..", foo, "/"},
- {"///foo", foo, "/foo"},
- {"/foo", foo, "/foo"},
- {"/foo/bar", foo, "/foo/bar"},
- {"/foo/.///./bar", foo, "/foo/bar"},
- {"/foo///bar", foo, "/foo/bar"},
- {"/foo/../foo/bar", foo, "/foo/bar"},
- {"foo/bar", root, "/foo/bar"},
- {"foo////bar", root, "/foo/bar"},
- {"bar", foo, "/foo/bar"},
- } {
- wdPath, _ := tc.wd.FullName(root)
- maxTraversals := uint(0)
- if d, err := mm.FindLink(ctx, root, tc.wd, tc.findPath, &maxTraversals); err != nil {
- t.Errorf("FindLink(%q, wd=%q) failed: %v", tc.findPath, wdPath, err)
- } else if got, _ := d.FullName(root); got != tc.wantPath {
- t.Errorf("FindLink(%q, wd=%q) got dirent %q, want %q", tc.findPath, wdPath, got, tc.wantPath)
- }
- }
-
- // Negative cases.
- for _, tc := range []struct {
- findPath string
- wd *fs.Dirent
- }{
- {"bar", root},
- {"/bar", root},
- {"/foo/../../bar", root},
- {"foo", foo},
- } {
- wdPath, _ := tc.wd.FullName(root)
- maxTraversals := uint(0)
- if _, err := mm.FindLink(ctx, root, tc.wd, tc.findPath, &maxTraversals); err == nil {
- t.Errorf("FindLink(%q, wd=%q) did not return error", tc.findPath, wdPath)
- }
- }
-}
diff --git a/pkg/sentry/fs/path_test.go b/pkg/sentry/fs/path_test.go
deleted file mode 100644
index e6f57ebba..000000000
--- a/pkg/sentry/fs/path_test.go
+++ /dev/null
@@ -1,289 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fs
-
-import (
- "testing"
-)
-
-// TestSplitLast tests variants of path splitting.
-func TestSplitLast(t *testing.T) {
- cases := []struct {
- path string
- dir string
- file string
- }{
- {path: "/", dir: "/", file: "."},
- {path: "/.", dir: "/", file: "."},
- {path: "/./", dir: "/", file: "."},
- {path: "/./.", dir: "/.", file: "."},
- {path: "/././", dir: "/.", file: "."},
- {path: "/./..", dir: "/.", file: ".."},
- {path: "/./../", dir: "/.", file: ".."},
- {path: "/..", dir: "/", file: ".."},
- {path: "/../", dir: "/", file: ".."},
- {path: "/../.", dir: "/..", file: "."},
- {path: "/.././", dir: "/..", file: "."},
- {path: "/../..", dir: "/..", file: ".."},
- {path: "/../../", dir: "/..", file: ".."},
-
- {path: "", dir: ".", file: "."},
- {path: ".", dir: ".", file: "."},
- {path: "./", dir: ".", file: "."},
- {path: "./.", dir: ".", file: "."},
- {path: "././", dir: ".", file: "."},
- {path: "./..", dir: ".", file: ".."},
- {path: "./../", dir: ".", file: ".."},
- {path: "..", dir: ".", file: ".."},
- {path: "../", dir: ".", file: ".."},
- {path: "../.", dir: "..", file: "."},
- {path: ".././", dir: "..", file: "."},
- {path: "../..", dir: "..", file: ".."},
- {path: "../../", dir: "..", file: ".."},
-
- {path: "/foo", dir: "/", file: "foo"},
- {path: "/foo/", dir: "/", file: "foo"},
- {path: "/foo/.", dir: "/foo", file: "."},
- {path: "/foo/./", dir: "/foo", file: "."},
- {path: "/foo/./.", dir: "/foo/.", file: "."},
- {path: "/foo/./..", dir: "/foo/.", file: ".."},
- {path: "/foo/..", dir: "/foo", file: ".."},
- {path: "/foo/../", dir: "/foo", file: ".."},
- {path: "/foo/../.", dir: "/foo/..", file: "."},
- {path: "/foo/../..", dir: "/foo/..", file: ".."},
-
- {path: "/foo/bar", dir: "/foo", file: "bar"},
- {path: "/foo/bar/", dir: "/foo", file: "bar"},
- {path: "/foo/bar/.", dir: "/foo/bar", file: "."},
- {path: "/foo/bar/./", dir: "/foo/bar", file: "."},
- {path: "/foo/bar/./.", dir: "/foo/bar/.", file: "."},
- {path: "/foo/bar/./..", dir: "/foo/bar/.", file: ".."},
- {path: "/foo/bar/..", dir: "/foo/bar", file: ".."},
- {path: "/foo/bar/../", dir: "/foo/bar", file: ".."},
- {path: "/foo/bar/../.", dir: "/foo/bar/..", file: "."},
- {path: "/foo/bar/../..", dir: "/foo/bar/..", file: ".."},
-
- {path: "foo", dir: ".", file: "foo"},
- {path: "foo", dir: ".", file: "foo"},
- {path: "foo/", dir: ".", file: "foo"},
- {path: "foo/.", dir: "foo", file: "."},
- {path: "foo/./", dir: "foo", file: "."},
- {path: "foo/./.", dir: "foo/.", file: "."},
- {path: "foo/./..", dir: "foo/.", file: ".."},
- {path: "foo/..", dir: "foo", file: ".."},
- {path: "foo/../", dir: "foo", file: ".."},
- {path: "foo/../.", dir: "foo/..", file: "."},
- {path: "foo/../..", dir: "foo/..", file: ".."},
- {path: "foo/", dir: ".", file: "foo"},
- {path: "foo/.", dir: "foo", file: "."},
-
- {path: "foo/bar", dir: "foo", file: "bar"},
- {path: "foo/bar/", dir: "foo", file: "bar"},
- {path: "foo/bar/.", dir: "foo/bar", file: "."},
- {path: "foo/bar/./", dir: "foo/bar", file: "."},
- {path: "foo/bar/./.", dir: "foo/bar/.", file: "."},
- {path: "foo/bar/./..", dir: "foo/bar/.", file: ".."},
- {path: "foo/bar/..", dir: "foo/bar", file: ".."},
- {path: "foo/bar/../", dir: "foo/bar", file: ".."},
- {path: "foo/bar/../.", dir: "foo/bar/..", file: "."},
- {path: "foo/bar/../..", dir: "foo/bar/..", file: ".."},
- {path: "foo/bar/", dir: "foo", file: "bar"},
- {path: "foo/bar/.", dir: "foo/bar", file: "."},
- }
-
- for _, c := range cases {
- dir, file := SplitLast(c.path)
- if dir != c.dir || file != c.file {
- t.Errorf("SplitLast(%q) got (%q, %q), expected (%q, %q)", c.path, dir, file, c.dir, c.file)
- }
- }
-}
-
-// TestSplitFirst tests variants of path splitting.
-func TestSplitFirst(t *testing.T) {
- cases := []struct {
- path string
- first string
- remainder string
- }{
- {path: "/", first: "/", remainder: ""},
- {path: "/.", first: "/", remainder: "."},
- {path: "///.", first: "/", remainder: "//."},
- {path: "/.///", first: "/", remainder: "."},
- {path: "/./.", first: "/", remainder: "./."},
- {path: "/././", first: "/", remainder: "./."},
- {path: "/./..", first: "/", remainder: "./.."},
- {path: "/./../", first: "/", remainder: "./.."},
- {path: "/..", first: "/", remainder: ".."},
- {path: "/../", first: "/", remainder: ".."},
- {path: "/../.", first: "/", remainder: "../."},
- {path: "/.././", first: "/", remainder: "../."},
- {path: "/../..", first: "/", remainder: "../.."},
- {path: "/../../", first: "/", remainder: "../.."},
-
- {path: "", first: ".", remainder: ""},
- {path: ".", first: ".", remainder: ""},
- {path: "./", first: ".", remainder: ""},
- {path: ".///", first: ".", remainder: ""},
- {path: "./.", first: ".", remainder: "."},
- {path: "././", first: ".", remainder: "."},
- {path: "./..", first: ".", remainder: ".."},
- {path: "./../", first: ".", remainder: ".."},
- {path: "..", first: "..", remainder: ""},
- {path: "../", first: "..", remainder: ""},
- {path: "../.", first: "..", remainder: "."},
- {path: ".././", first: "..", remainder: "."},
- {path: "../..", first: "..", remainder: ".."},
- {path: "../../", first: "..", remainder: ".."},
-
- {path: "/foo", first: "/", remainder: "foo"},
- {path: "/foo/", first: "/", remainder: "foo"},
- {path: "/foo///", first: "/", remainder: "foo"},
- {path: "/foo/.", first: "/", remainder: "foo/."},
- {path: "/foo/./", first: "/", remainder: "foo/."},
- {path: "/foo/./.", first: "/", remainder: "foo/./."},
- {path: "/foo/./..", first: "/", remainder: "foo/./.."},
- {path: "/foo/..", first: "/", remainder: "foo/.."},
- {path: "/foo/../", first: "/", remainder: "foo/.."},
- {path: "/foo/../.", first: "/", remainder: "foo/../."},
- {path: "/foo/../..", first: "/", remainder: "foo/../.."},
-
- {path: "/foo/bar", first: "/", remainder: "foo/bar"},
- {path: "///foo/bar", first: "/", remainder: "//foo/bar"},
- {path: "/foo///bar", first: "/", remainder: "foo///bar"},
- {path: "/foo/bar/.", first: "/", remainder: "foo/bar/."},
- {path: "/foo/bar/./", first: "/", remainder: "foo/bar/."},
- {path: "/foo/bar/./.", first: "/", remainder: "foo/bar/./."},
- {path: "/foo/bar/./..", first: "/", remainder: "foo/bar/./.."},
- {path: "/foo/bar/..", first: "/", remainder: "foo/bar/.."},
- {path: "/foo/bar/../", first: "/", remainder: "foo/bar/.."},
- {path: "/foo/bar/../.", first: "/", remainder: "foo/bar/../."},
- {path: "/foo/bar/../..", first: "/", remainder: "foo/bar/../.."},
-
- {path: "foo", first: "foo", remainder: ""},
- {path: "foo", first: "foo", remainder: ""},
- {path: "foo/", first: "foo", remainder: ""},
- {path: "foo///", first: "foo", remainder: ""},
- {path: "foo/.", first: "foo", remainder: "."},
- {path: "foo/./", first: "foo", remainder: "."},
- {path: "foo/./.", first: "foo", remainder: "./."},
- {path: "foo/./..", first: "foo", remainder: "./.."},
- {path: "foo/..", first: "foo", remainder: ".."},
- {path: "foo/../", first: "foo", remainder: ".."},
- {path: "foo/../.", first: "foo", remainder: "../."},
- {path: "foo/../..", first: "foo", remainder: "../.."},
- {path: "foo/", first: "foo", remainder: ""},
- {path: "foo/.", first: "foo", remainder: "."},
-
- {path: "foo/bar", first: "foo", remainder: "bar"},
- {path: "foo///bar", first: "foo", remainder: "bar"},
- {path: "foo/bar/", first: "foo", remainder: "bar"},
- {path: "foo/bar/.", first: "foo", remainder: "bar/."},
- {path: "foo/bar/./", first: "foo", remainder: "bar/."},
- {path: "foo/bar/./.", first: "foo", remainder: "bar/./."},
- {path: "foo/bar/./..", first: "foo", remainder: "bar/./.."},
- {path: "foo/bar/..", first: "foo", remainder: "bar/.."},
- {path: "foo/bar/../", first: "foo", remainder: "bar/.."},
- {path: "foo/bar/../.", first: "foo", remainder: "bar/../."},
- {path: "foo/bar/../..", first: "foo", remainder: "bar/../.."},
- {path: "foo/bar/", first: "foo", remainder: "bar"},
- {path: "foo/bar/.", first: "foo", remainder: "bar/."},
- }
-
- for _, c := range cases {
- first, remainder := SplitFirst(c.path)
- if first != c.first || remainder != c.remainder {
- t.Errorf("SplitFirst(%q) got (%q, %q), expected (%q, %q)", c.path, first, remainder, c.first, c.remainder)
- }
- }
-}
-
-// TestIsSubpath tests the IsSubpath method.
-func TestIsSubpath(t *testing.T) {
- tcs := []struct {
- // Two absolute paths.
- pathA string
- pathB string
-
- // Whether pathA is a subpath of pathB.
- wantIsSubpath bool
-
- // Relative path from pathA to pathB. Only checked if
- // wantIsSubpath is true.
- wantRelpath string
- }{
- {
- pathA: "/foo/bar/baz",
- pathB: "/foo",
- wantIsSubpath: true,
- wantRelpath: "bar/baz",
- },
- {
- pathA: "/foo",
- pathB: "/foo/bar/baz",
- wantIsSubpath: false,
- },
- {
- pathA: "/foo",
- pathB: "/foo",
- wantIsSubpath: false,
- },
- {
- pathA: "/foobar",
- pathB: "/foo",
- wantIsSubpath: false,
- },
- {
- pathA: "/foo",
- pathB: "/foobar",
- wantIsSubpath: false,
- },
- {
- pathA: "/foo",
- pathB: "/foobar",
- wantIsSubpath: false,
- },
- {
- pathA: "/",
- pathB: "/foo",
- wantIsSubpath: false,
- },
- {
- pathA: "/foo",
- pathB: "/",
- wantIsSubpath: true,
- wantRelpath: "foo",
- },
- {
- pathA: "/foo/bar/../bar",
- pathB: "/foo",
- wantIsSubpath: true,
- wantRelpath: "bar",
- },
- {
- pathA: "/foo/bar",
- pathB: "/foo/../foo",
- wantIsSubpath: true,
- wantRelpath: "bar",
- },
- }
-
- for _, tc := range tcs {
- gotRelpath, gotIsSubpath := IsSubpath(tc.pathA, tc.pathB)
- if gotRelpath != tc.wantRelpath || gotIsSubpath != tc.wantIsSubpath {
- t.Errorf("IsSubpath(%q, %q) got %q %t, want %q %t", tc.pathA, tc.pathB, gotRelpath, gotIsSubpath, tc.wantRelpath, tc.wantIsSubpath)
- }
- }
-}
diff --git a/pkg/sentry/fs/proc/BUILD b/pkg/sentry/fs/proc/BUILD
deleted file mode 100644
index 1c93e8886..000000000
--- a/pkg/sentry/fs/proc/BUILD
+++ /dev/null
@@ -1,74 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "proc",
- srcs = [
- "cgroup.go",
- "cpuinfo.go",
- "exec_args.go",
- "fds.go",
- "filesystems.go",
- "fs.go",
- "inode.go",
- "loadavg.go",
- "meminfo.go",
- "mounts.go",
- "net.go",
- "proc.go",
- "rpcinet_proc.go",
- "stat.go",
- "sys.go",
- "sys_net.go",
- "sys_net_state.go",
- "task.go",
- "uid_gid_map.go",
- "uptime.go",
- "version.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/proc",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/log",
- "//pkg/sentry/context",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/fs/proc/device",
- "//pkg/sentry/fs/proc/seqfile",
- "//pkg/sentry/fs/ramfs",
- "//pkg/sentry/inet",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/limits",
- "//pkg/sentry/mm",
- "//pkg/sentry/socket",
- "//pkg/sentry/socket/rpcinet",
- "//pkg/sentry/socket/unix",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/usage",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "proc_test",
- size = "small",
- srcs = [
- "net_test.go",
- "sys_net_test.go",
- ],
- embed = [":proc"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/sentry/context",
- "//pkg/sentry/inet",
- "//pkg/sentry/usermem",
- ],
-)
diff --git a/pkg/sentry/fs/proc/README.md b/pkg/sentry/fs/proc/README.md
deleted file mode 100644
index 5d4ec6c7b..000000000
--- a/pkg/sentry/fs/proc/README.md
+++ /dev/null
@@ -1,332 +0,0 @@
-This document tracks what is implemented in procfs. Refer to
-Documentation/filesystems/proc.txt in the Linux project for information about
-procfs generally.
-
-**NOTE**: This document is not guaranteed to be up to date. If you find an
-inconsistency, please file a bug.
-
-[TOC]
-
-## Kernel data
-
-The following files are implemented:
-
-| File /proc/ | Content |
-| :------------------------ | :---------------------------------------------------- |
-| [cpuinfo](#cpuinfo) | Info about the CPU |
-| [filesystems](#filesystems) | Supported filesystems |
-| [loadavg](#loadavg) | Load average of last 1, 5 & 15 minutes |
-| [meminfo](#meminfo) | Overall memory info |
-| [stat](#stat) | Overall kernel statistics |
-| [sys](#sys) | Change parameters within the kernel |
-| [uptime](#uptime) | Wall clock since boot, combined idle time of all cpus |
-| [version](#version) | Kernel version |
-
-### cpuinfo
-
-```bash
-$ cat /proc/cpuinfo
-processor : 0
-vendor_id : GenuineIntel
-cpu family : 6
-model : 45
-model name : unknown
-stepping : unknown
-cpu MHz : 1234.588
-fpu : yes
-fpu_exception : yes
-cpuid level : 13
-wp : yes
-flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx xsaveopt
-bogomips : 1234.59
-clflush size : 64
-cache_alignment : 64
-address sizes : 46 bits physical, 48 bits virtual
-power management:
-
-...
-```
-
-Notable divergences:
-
-Field name | Notes
-:--------------- | :---------------------------------------
-model name | Always unknown
-stepping | Always unknown
-fpu | Always yes
-fpu_exception | Always yes
-wp | Always yes
-bogomips | Bogus value (matches cpu MHz)
-clflush size | Always 64
-cache_alignment | Always 64
-address sizes | Always 46 bits physical, 48 bits virtual
-power management | Always blank
-
-Otherwise fields are derived from the sentry configuration.
-
-### filesystems
-
-```bash
-$ cat /proc/filesystems
-nodev 9p
-nodev devpts
-nodev devtmpfs
-nodev proc
-nodev sysfs
-nodev tmpfs
-```
-
-### loadavg
-
-```bash
-$ cat /proc/loadavg
-0.00 0.00 0.00 0/0 0
-```
-
-Column | Notes
-:------------------------------------ | :----------
-CPU.IO utilization in last 1 minute | Always zero
-CPU.IO utilization in last 5 minutes | Always zero
-CPU.IO utilization in last 10 minutes | Always zero
-Num currently running processes | Always zero
-Total num processes | Always zero
-
-TODO(b/62345059): Populate the columns with accurate statistics.
-
-### meminfo
-
-```bash
-$ cat /proc/meminfo
-MemTotal: 2097152 kB
-MemFree: 2083540 kB
-MemAvailable: 2083540 kB
-Buffers: 0 kB
-Cached: 4428 kB
-SwapCache: 0 kB
-Active: 10812 kB
-Inactive: 2216 kB
-Active(anon): 8600 kB
-Inactive(anon): 0 kB
-Active(file): 2212 kB
-Inactive(file): 2216 kB
-Unevictable: 0 kB
-Mlocked: 0 kB
-SwapTotal: 0 kB
-SwapFree: 0 kB
-Dirty: 0 kB
-Writeback: 0 kB
-AnonPages: 8600 kB
-Mapped: 4428 kB
-Shmem: 0 kB
-
-```
-
-Notable divergences:
-
-Field name | Notes
-:---------------- | :-----------------------------------------------------
-Buffers | Always zero, no block devices
-SwapCache | Always zero, no swap
-Inactive(anon) | Always zero, see SwapCache
-Unevictable | Always zero TODO(b/31823263)
-Mlocked | Always zero TODO(b/31823263)
-SwapTotal | Always zero, no swap
-SwapFree | Always zero, no swap
-Dirty | Always zero TODO(b/31823263)
-Writeback | Always zero TODO(b/31823263)
-MemAvailable | Uses the same value as MemFree since there is no swap.
-Slab | Missing
-SReclaimable | Missing
-SUnreclaim | Missing
-KernelStack | Missing
-PageTables | Missing
-NFS_Unstable | Missing
-Bounce | Missing
-WritebackTmp | Missing
-CommitLimit | Missing
-Committed_AS | Missing
-VmallocTotal | Missing
-VmallocUsed | Missing
-VmallocChunk | Missing
-HardwareCorrupted | Missing
-AnonHugePages | Missing
-ShmemHugePages | Missing
-ShmemPmdMapped | Missing
-HugePages_Total | Missing
-HugePages_Free | Missing
-HugePages_Rsvd | Missing
-HugePages_Surp | Missing
-Hugepagesize | Missing
-DirectMap4k | Missing
-DirectMap2M | Missing
-DirectMap1G | Missing
-
-### stat
-
-```bash
-$ cat /proc/stat
-cpu 0 0 0 0 0 0 0 0 0 0
-cpu0 0 0 0 0 0 0 0 0 0 0
-cpu1 0 0 0 0 0 0 0 0 0 0
-cpu2 0 0 0 0 0 0 0 0 0 0
-cpu3 0 0 0 0 0 0 0 0 0 0
-cpu4 0 0 0 0 0 0 0 0 0 0
-cpu5 0 0 0 0 0 0 0 0 0 0
-cpu6 0 0 0 0 0 0 0 0 0 0
-cpu7 0 0 0 0 0 0 0 0 0 0
-intr 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
-ctxt 0
-btime 1504040968
-processes 0
-procs_running 0
-procs_blokkcked 0
-softirq 0 0 0 0 0 0 0 0 0 0 0
-```
-
-All fields except for `btime` are always zero.
-
-TODO(b/37226836): Populate with accurate fields.
-
-### sys
-
-```bash
-$ ls /proc/sys
-kernel vm
-```
-
-Directory | Notes
-:-------- | :----------------------------
-abi | Missing
-debug | Missing
-dev | Missing
-fs | Missing
-kernel | Contains hostname (only)
-net | Missing
-user | Missing
-vm | Contains mmap_min_addr (only)
-
-### uptime
-
-```bash
-$ cat /proc/uptime
-3204.62 0.00
-```
-
-Column | Notes
-:------------------------------- | :----------------------------
-Total num seconds system running | Time since procfs was mounted
-Number of seconds idle | Always zero
-
-### version
-
-```bash
-$ cat /proc/version
-Linux version 4.4 #1 SMP Sun Jan 10 15:06:54 PST 2016
-```
-
-## Process-specific data
-
-The following files are implemented:
-
-File /proc/PID | Content
-:---------------------- | :---------------------------------------------------
-[auxv](#auxv) | Copy of auxiliary vector for the process
-[cmdline](#cmdline) | Command line arguments
-[comm](#comm) | Command name associated with the process
-[environ](#environ) | Process environment
-[exe](#exe) | Symlink to the process's executable
-[fd](#fd) | Directory containing links to open file descriptors
-[fdinfo](#fdinfo) | Information associated with open file descriptors
-[gid_map](#gid_map) | Mappings for group IDs inside the user namespace
-[io](#io) | IO statistics
-[maps](#maps) | Memory mappings (anon, executables, library files)
-[mounts](#mounts) | Mounted filesystems
-[mountinfo](#mountinfo) | Information about mounts
-[ns](#ns) | Directory containing info about supported namespaces
-[stat](#stat) | Process statistics
-[statm](#statm) | Process memory statistics
-[status](#status) | Process status in human readable format
-[task](#task) | Directory containing info about running threads
-[uid_map](#uid_map) | Mappings for user IDs inside the user namespace
-
-### auxv
-
-TODO
-
-### cmdline
-
-TODO
-
-### comm
-
-TODO
-
-### environment
-
-TODO
-
-### exe
-
-TODO
-
-### fd
-
-TODO
-
-### fdinfo
-
-TODO
-
-### gid_map
-
-TODO
-
-### io
-
-Only has data for rchar, wchar, syscr, and syscw.
-
-TODO: add more detail.
-
-### maps
-
-TODO
-
-### mounts
-
-TODO
-
-### mountinfo
-
-TODO
-
-### ns
-
-TODO
-
-### stat
-
-Only has data for pid, comm, state, ppid, utime, stime, cutime, cstime,
-num_threads, and exit_signal.
-
-TODO: add more detail.
-
-### statm
-
-Only has data for vss and rss.
-
-TODO: add more detail.
-
-### status
-
-Contains data for Name, State, Tgid, Pid, Ppid, TracerPid, FDSize, VmSize,
-VmRSS, Threads, CapInh, CapPrm, CapEff, CapBnd, Seccomp.
-
-TODO: add more detail.
-
-### task
-
-TODO
-
-### uid_map
-
-TODO
diff --git a/pkg/sentry/fs/proc/device/BUILD b/pkg/sentry/fs/proc/device/BUILD
deleted file mode 100644
index 0394451d4..000000000
--- a/pkg/sentry/fs/proc/device/BUILD
+++ /dev/null
@@ -1,11 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "device",
- srcs = ["device.go"],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/proc/device",
- visibility = ["//pkg/sentry:internal"],
- deps = ["//pkg/sentry/device"],
-)
diff --git a/pkg/sentry/fs/proc/device/device_state_autogen.go b/pkg/sentry/fs/proc/device/device_state_autogen.go
new file mode 100755
index 000000000..be407ac45
--- /dev/null
+++ b/pkg/sentry/fs/proc/device/device_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package device
+
diff --git a/pkg/sentry/fs/proc/net_test.go b/pkg/sentry/fs/proc/net_test.go
deleted file mode 100644
index f18681405..000000000
--- a/pkg/sentry/fs/proc/net_test.go
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package proc
-
-import (
- "reflect"
- "testing"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/inet"
-)
-
-func newIPv6TestStack() *inet.TestStack {
- s := inet.NewTestStack()
- s.SupportsIPv6Flag = true
- return s
-}
-
-func TestIfinet6NoAddresses(t *testing.T) {
- n := &ifinet6{s: newIPv6TestStack()}
- if got := n.contents(); got != nil {
- t.Errorf("Got n.contents() = %v, want = %v", got, nil)
- }
-}
-
-func TestIfinet6(t *testing.T) {
- s := newIPv6TestStack()
- s.InterfacesMap[1] = inet.Interface{Name: "eth0"}
- s.InterfaceAddrsMap[1] = []inet.InterfaceAddr{
- {
- Family: linux.AF_INET6,
- PrefixLen: 128,
- Addr: []byte("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"),
- },
- }
- s.InterfacesMap[2] = inet.Interface{Name: "eth1"}
- s.InterfaceAddrsMap[2] = []inet.InterfaceAddr{
- {
- Family: linux.AF_INET6,
- PrefixLen: 128,
- Addr: []byte("\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"),
- },
- }
- want := map[string]struct{}{
- "000102030405060708090a0b0c0d0e0f 01 80 00 00 eth0\n": {},
- "101112131415161718191a1b1c1d1e1f 02 80 00 00 eth1\n": {},
- }
-
- n := &ifinet6{s: s}
- contents := n.contents()
- if len(contents) != len(want) {
- t.Errorf("Got len(n.contents()) = %d, want = %d", len(contents), len(want))
- }
- got := map[string]struct{}{}
- for _, l := range contents {
- got[l] = struct{}{}
- }
-
- if !reflect.DeepEqual(got, want) {
- t.Errorf("Got n.contents() = %v, want = %v", got, want)
- }
-}
diff --git a/pkg/sentry/fs/proc/proc_state_autogen.go b/pkg/sentry/fs/proc/proc_state_autogen.go
new file mode 100755
index 000000000..a1b87637d
--- /dev/null
+++ b/pkg/sentry/fs/proc/proc_state_autogen.go
@@ -0,0 +1,681 @@
+// automatically generated by stateify.
+
+package proc
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *execArgInode) beforeSave() {}
+func (x *execArgInode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("SimpleFileInode", &x.SimpleFileInode)
+ m.Save("arg", &x.arg)
+ m.Save("t", &x.t)
+}
+
+func (x *execArgInode) afterLoad() {}
+func (x *execArgInode) load(m state.Map) {
+ m.Load("SimpleFileInode", &x.SimpleFileInode)
+ m.Load("arg", &x.arg)
+ m.Load("t", &x.t)
+}
+
+func (x *execArgFile) beforeSave() {}
+func (x *execArgFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("arg", &x.arg)
+ m.Save("t", &x.t)
+}
+
+func (x *execArgFile) afterLoad() {}
+func (x *execArgFile) load(m state.Map) {
+ m.Load("arg", &x.arg)
+ m.Load("t", &x.t)
+}
+
+func (x *fdDir) beforeSave() {}
+func (x *fdDir) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Dir", &x.Dir)
+ m.Save("t", &x.t)
+}
+
+func (x *fdDir) afterLoad() {}
+func (x *fdDir) load(m state.Map) {
+ m.Load("Dir", &x.Dir)
+ m.Load("t", &x.t)
+}
+
+func (x *fdDirFile) beforeSave() {}
+func (x *fdDirFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("isInfoFile", &x.isInfoFile)
+ m.Save("t", &x.t)
+}
+
+func (x *fdDirFile) afterLoad() {}
+func (x *fdDirFile) load(m state.Map) {
+ m.Load("isInfoFile", &x.isInfoFile)
+ m.Load("t", &x.t)
+}
+
+func (x *fdInfoDir) beforeSave() {}
+func (x *fdInfoDir) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Dir", &x.Dir)
+ m.Save("t", &x.t)
+}
+
+func (x *fdInfoDir) afterLoad() {}
+func (x *fdInfoDir) load(m state.Map) {
+ m.Load("Dir", &x.Dir)
+ m.Load("t", &x.t)
+}
+
+func (x *filesystemsData) beforeSave() {}
+func (x *filesystemsData) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *filesystemsData) afterLoad() {}
+func (x *filesystemsData) load(m state.Map) {
+}
+
+func (x *filesystem) beforeSave() {}
+func (x *filesystem) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *filesystem) afterLoad() {}
+func (x *filesystem) load(m state.Map) {
+}
+
+func (x *taskOwnedInodeOps) beforeSave() {}
+func (x *taskOwnedInodeOps) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeOperations", &x.InodeOperations)
+ m.Save("t", &x.t)
+}
+
+func (x *taskOwnedInodeOps) afterLoad() {}
+func (x *taskOwnedInodeOps) load(m state.Map) {
+ m.Load("InodeOperations", &x.InodeOperations)
+ m.Load("t", &x.t)
+}
+
+func (x *staticFileInodeOps) beforeSave() {}
+func (x *staticFileInodeOps) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Save("InodeStaticFileGetter", &x.InodeStaticFileGetter)
+}
+
+func (x *staticFileInodeOps) afterLoad() {}
+func (x *staticFileInodeOps) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Load("InodeStaticFileGetter", &x.InodeStaticFileGetter)
+}
+
+func (x *loadavgData) beforeSave() {}
+func (x *loadavgData) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *loadavgData) afterLoad() {}
+func (x *loadavgData) load(m state.Map) {
+}
+
+func (x *meminfoData) beforeSave() {}
+func (x *meminfoData) save(m state.Map) {
+ x.beforeSave()
+ m.Save("k", &x.k)
+}
+
+func (x *meminfoData) afterLoad() {}
+func (x *meminfoData) load(m state.Map) {
+ m.Load("k", &x.k)
+}
+
+func (x *mountInfoFile) beforeSave() {}
+func (x *mountInfoFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("t", &x.t)
+}
+
+func (x *mountInfoFile) afterLoad() {}
+func (x *mountInfoFile) load(m state.Map) {
+ m.Load("t", &x.t)
+}
+
+func (x *mountsFile) beforeSave() {}
+func (x *mountsFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("t", &x.t)
+}
+
+func (x *mountsFile) afterLoad() {}
+func (x *mountsFile) load(m state.Map) {
+ m.Load("t", &x.t)
+}
+
+func (x *ifinet6) beforeSave() {}
+func (x *ifinet6) save(m state.Map) {
+ x.beforeSave()
+ m.Save("s", &x.s)
+}
+
+func (x *ifinet6) afterLoad() {}
+func (x *ifinet6) load(m state.Map) {
+ m.Load("s", &x.s)
+}
+
+func (x *netDev) beforeSave() {}
+func (x *netDev) save(m state.Map) {
+ x.beforeSave()
+ m.Save("s", &x.s)
+}
+
+func (x *netDev) afterLoad() {}
+func (x *netDev) load(m state.Map) {
+ m.Load("s", &x.s)
+}
+
+func (x *netUnix) beforeSave() {}
+func (x *netUnix) save(m state.Map) {
+ x.beforeSave()
+ m.Save("k", &x.k)
+}
+
+func (x *netUnix) afterLoad() {}
+func (x *netUnix) load(m state.Map) {
+ m.Load("k", &x.k)
+}
+
+func (x *netTCP) beforeSave() {}
+func (x *netTCP) save(m state.Map) {
+ x.beforeSave()
+ m.Save("k", &x.k)
+}
+
+func (x *netTCP) afterLoad() {}
+func (x *netTCP) load(m state.Map) {
+ m.Load("k", &x.k)
+}
+
+func (x *netUDP) beforeSave() {}
+func (x *netUDP) save(m state.Map) {
+ x.beforeSave()
+ m.Save("k", &x.k)
+}
+
+func (x *netUDP) afterLoad() {}
+func (x *netUDP) load(m state.Map) {
+ m.Load("k", &x.k)
+}
+
+func (x *proc) beforeSave() {}
+func (x *proc) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Dir", &x.Dir)
+ m.Save("k", &x.k)
+ m.Save("pidns", &x.pidns)
+ m.Save("cgroupControllers", &x.cgroupControllers)
+}
+
+func (x *proc) afterLoad() {}
+func (x *proc) load(m state.Map) {
+ m.Load("Dir", &x.Dir)
+ m.Load("k", &x.k)
+ m.Load("pidns", &x.pidns)
+ m.Load("cgroupControllers", &x.cgroupControllers)
+}
+
+func (x *self) beforeSave() {}
+func (x *self) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Symlink", &x.Symlink)
+ m.Save("pidns", &x.pidns)
+}
+
+func (x *self) afterLoad() {}
+func (x *self) load(m state.Map) {
+ m.Load("Symlink", &x.Symlink)
+ m.Load("pidns", &x.pidns)
+}
+
+func (x *threadSelf) beforeSave() {}
+func (x *threadSelf) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Symlink", &x.Symlink)
+ m.Save("pidns", &x.pidns)
+}
+
+func (x *threadSelf) afterLoad() {}
+func (x *threadSelf) load(m state.Map) {
+ m.Load("Symlink", &x.Symlink)
+ m.Load("pidns", &x.pidns)
+}
+
+func (x *rootProcFile) beforeSave() {}
+func (x *rootProcFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("iops", &x.iops)
+}
+
+func (x *rootProcFile) afterLoad() {}
+func (x *rootProcFile) load(m state.Map) {
+ m.Load("iops", &x.iops)
+}
+
+func (x *statData) beforeSave() {}
+func (x *statData) save(m state.Map) {
+ x.beforeSave()
+ m.Save("k", &x.k)
+}
+
+func (x *statData) afterLoad() {}
+func (x *statData) load(m state.Map) {
+ m.Load("k", &x.k)
+}
+
+func (x *mmapMinAddrData) beforeSave() {}
+func (x *mmapMinAddrData) save(m state.Map) {
+ x.beforeSave()
+ m.Save("k", &x.k)
+}
+
+func (x *mmapMinAddrData) afterLoad() {}
+func (x *mmapMinAddrData) load(m state.Map) {
+ m.Load("k", &x.k)
+}
+
+func (x *overcommitMemory) beforeSave() {}
+func (x *overcommitMemory) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *overcommitMemory) afterLoad() {}
+func (x *overcommitMemory) load(m state.Map) {
+}
+
+func (x *hostname) beforeSave() {}
+func (x *hostname) save(m state.Map) {
+ x.beforeSave()
+ m.Save("SimpleFileInode", &x.SimpleFileInode)
+}
+
+func (x *hostname) afterLoad() {}
+func (x *hostname) load(m state.Map) {
+ m.Load("SimpleFileInode", &x.SimpleFileInode)
+}
+
+func (x *hostnameFile) beforeSave() {}
+func (x *hostnameFile) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *hostnameFile) afterLoad() {}
+func (x *hostnameFile) load(m state.Map) {
+}
+
+func (x *tcpMemInode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("SimpleFileInode", &x.SimpleFileInode)
+ m.Save("dir", &x.dir)
+ m.Save("s", &x.s)
+ m.Save("size", &x.size)
+}
+
+func (x *tcpMemInode) load(m state.Map) {
+ m.Load("SimpleFileInode", &x.SimpleFileInode)
+ m.Load("dir", &x.dir)
+ m.LoadWait("s", &x.s)
+ m.Load("size", &x.size)
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *tcpMemFile) beforeSave() {}
+func (x *tcpMemFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("tcpMemInode", &x.tcpMemInode)
+}
+
+func (x *tcpMemFile) afterLoad() {}
+func (x *tcpMemFile) load(m state.Map) {
+ m.Load("tcpMemInode", &x.tcpMemInode)
+}
+
+func (x *tcpSack) beforeSave() {}
+func (x *tcpSack) save(m state.Map) {
+ x.beforeSave()
+ m.Save("stack", &x.stack)
+ m.Save("enabled", &x.enabled)
+ m.Save("SimpleFileInode", &x.SimpleFileInode)
+}
+
+func (x *tcpSack) load(m state.Map) {
+ m.LoadWait("stack", &x.stack)
+ m.Load("enabled", &x.enabled)
+ m.Load("SimpleFileInode", &x.SimpleFileInode)
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *tcpSackFile) beforeSave() {}
+func (x *tcpSackFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("tcpSack", &x.tcpSack)
+ m.Save("stack", &x.stack)
+}
+
+func (x *tcpSackFile) afterLoad() {}
+func (x *tcpSackFile) load(m state.Map) {
+ m.Load("tcpSack", &x.tcpSack)
+ m.LoadWait("stack", &x.stack)
+}
+
+func (x *taskDir) beforeSave() {}
+func (x *taskDir) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Dir", &x.Dir)
+ m.Save("t", &x.t)
+ m.Save("pidns", &x.pidns)
+}
+
+func (x *taskDir) afterLoad() {}
+func (x *taskDir) load(m state.Map) {
+ m.Load("Dir", &x.Dir)
+ m.Load("t", &x.t)
+ m.Load("pidns", &x.pidns)
+}
+
+func (x *subtasks) beforeSave() {}
+func (x *subtasks) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Dir", &x.Dir)
+ m.Save("t", &x.t)
+ m.Save("p", &x.p)
+}
+
+func (x *subtasks) afterLoad() {}
+func (x *subtasks) load(m state.Map) {
+ m.Load("Dir", &x.Dir)
+ m.Load("t", &x.t)
+ m.Load("p", &x.p)
+}
+
+func (x *subtasksFile) beforeSave() {}
+func (x *subtasksFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("t", &x.t)
+ m.Save("pidns", &x.pidns)
+}
+
+func (x *subtasksFile) afterLoad() {}
+func (x *subtasksFile) load(m state.Map) {
+ m.Load("t", &x.t)
+ m.Load("pidns", &x.pidns)
+}
+
+func (x *exe) beforeSave() {}
+func (x *exe) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Symlink", &x.Symlink)
+ m.Save("t", &x.t)
+}
+
+func (x *exe) afterLoad() {}
+func (x *exe) load(m state.Map) {
+ m.Load("Symlink", &x.Symlink)
+ m.Load("t", &x.t)
+}
+
+func (x *namespaceSymlink) beforeSave() {}
+func (x *namespaceSymlink) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Symlink", &x.Symlink)
+ m.Save("t", &x.t)
+}
+
+func (x *namespaceSymlink) afterLoad() {}
+func (x *namespaceSymlink) load(m state.Map) {
+ m.Load("Symlink", &x.Symlink)
+ m.Load("t", &x.t)
+}
+
+func (x *mapsData) beforeSave() {}
+func (x *mapsData) save(m state.Map) {
+ x.beforeSave()
+ m.Save("t", &x.t)
+}
+
+func (x *mapsData) afterLoad() {}
+func (x *mapsData) load(m state.Map) {
+ m.Load("t", &x.t)
+}
+
+func (x *smapsData) beforeSave() {}
+func (x *smapsData) save(m state.Map) {
+ x.beforeSave()
+ m.Save("t", &x.t)
+}
+
+func (x *smapsData) afterLoad() {}
+func (x *smapsData) load(m state.Map) {
+ m.Load("t", &x.t)
+}
+
+func (x *taskStatData) beforeSave() {}
+func (x *taskStatData) save(m state.Map) {
+ x.beforeSave()
+ m.Save("t", &x.t)
+ m.Save("tgstats", &x.tgstats)
+ m.Save("pidns", &x.pidns)
+}
+
+func (x *taskStatData) afterLoad() {}
+func (x *taskStatData) load(m state.Map) {
+ m.Load("t", &x.t)
+ m.Load("tgstats", &x.tgstats)
+ m.Load("pidns", &x.pidns)
+}
+
+func (x *statmData) beforeSave() {}
+func (x *statmData) save(m state.Map) {
+ x.beforeSave()
+ m.Save("t", &x.t)
+}
+
+func (x *statmData) afterLoad() {}
+func (x *statmData) load(m state.Map) {
+ m.Load("t", &x.t)
+}
+
+func (x *statusData) beforeSave() {}
+func (x *statusData) save(m state.Map) {
+ x.beforeSave()
+ m.Save("t", &x.t)
+ m.Save("pidns", &x.pidns)
+}
+
+func (x *statusData) afterLoad() {}
+func (x *statusData) load(m state.Map) {
+ m.Load("t", &x.t)
+ m.Load("pidns", &x.pidns)
+}
+
+func (x *ioData) beforeSave() {}
+func (x *ioData) save(m state.Map) {
+ x.beforeSave()
+ m.Save("ioUsage", &x.ioUsage)
+}
+
+func (x *ioData) afterLoad() {}
+func (x *ioData) load(m state.Map) {
+ m.Load("ioUsage", &x.ioUsage)
+}
+
+func (x *comm) beforeSave() {}
+func (x *comm) save(m state.Map) {
+ x.beforeSave()
+ m.Save("SimpleFileInode", &x.SimpleFileInode)
+ m.Save("t", &x.t)
+}
+
+func (x *comm) afterLoad() {}
+func (x *comm) load(m state.Map) {
+ m.Load("SimpleFileInode", &x.SimpleFileInode)
+ m.Load("t", &x.t)
+}
+
+func (x *commFile) beforeSave() {}
+func (x *commFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("t", &x.t)
+}
+
+func (x *commFile) afterLoad() {}
+func (x *commFile) load(m state.Map) {
+ m.Load("t", &x.t)
+}
+
+func (x *auxvec) beforeSave() {}
+func (x *auxvec) save(m state.Map) {
+ x.beforeSave()
+ m.Save("SimpleFileInode", &x.SimpleFileInode)
+ m.Save("t", &x.t)
+}
+
+func (x *auxvec) afterLoad() {}
+func (x *auxvec) load(m state.Map) {
+ m.Load("SimpleFileInode", &x.SimpleFileInode)
+ m.Load("t", &x.t)
+}
+
+func (x *auxvecFile) beforeSave() {}
+func (x *auxvecFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("t", &x.t)
+}
+
+func (x *auxvecFile) afterLoad() {}
+func (x *auxvecFile) load(m state.Map) {
+ m.Load("t", &x.t)
+}
+
+func (x *idMapInodeOperations) beforeSave() {}
+func (x *idMapInodeOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Save("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes)
+ m.Save("t", &x.t)
+ m.Save("gids", &x.gids)
+}
+
+func (x *idMapInodeOperations) afterLoad() {}
+func (x *idMapInodeOperations) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Load("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes)
+ m.Load("t", &x.t)
+ m.Load("gids", &x.gids)
+}
+
+func (x *idMapFileOperations) beforeSave() {}
+func (x *idMapFileOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("iops", &x.iops)
+}
+
+func (x *idMapFileOperations) afterLoad() {}
+func (x *idMapFileOperations) load(m state.Map) {
+ m.Load("iops", &x.iops)
+}
+
+func (x *uptime) beforeSave() {}
+func (x *uptime) save(m state.Map) {
+ x.beforeSave()
+ m.Save("SimpleFileInode", &x.SimpleFileInode)
+ m.Save("startTime", &x.startTime)
+}
+
+func (x *uptime) afterLoad() {}
+func (x *uptime) load(m state.Map) {
+ m.Load("SimpleFileInode", &x.SimpleFileInode)
+ m.Load("startTime", &x.startTime)
+}
+
+func (x *uptimeFile) beforeSave() {}
+func (x *uptimeFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("startTime", &x.startTime)
+}
+
+func (x *uptimeFile) afterLoad() {}
+func (x *uptimeFile) load(m state.Map) {
+ m.Load("startTime", &x.startTime)
+}
+
+func (x *versionData) beforeSave() {}
+func (x *versionData) save(m state.Map) {
+ x.beforeSave()
+ m.Save("k", &x.k)
+}
+
+func (x *versionData) afterLoad() {}
+func (x *versionData) load(m state.Map) {
+ m.Load("k", &x.k)
+}
+
+func init() {
+ state.Register("proc.execArgInode", (*execArgInode)(nil), state.Fns{Save: (*execArgInode).save, Load: (*execArgInode).load})
+ state.Register("proc.execArgFile", (*execArgFile)(nil), state.Fns{Save: (*execArgFile).save, Load: (*execArgFile).load})
+ state.Register("proc.fdDir", (*fdDir)(nil), state.Fns{Save: (*fdDir).save, Load: (*fdDir).load})
+ state.Register("proc.fdDirFile", (*fdDirFile)(nil), state.Fns{Save: (*fdDirFile).save, Load: (*fdDirFile).load})
+ state.Register("proc.fdInfoDir", (*fdInfoDir)(nil), state.Fns{Save: (*fdInfoDir).save, Load: (*fdInfoDir).load})
+ state.Register("proc.filesystemsData", (*filesystemsData)(nil), state.Fns{Save: (*filesystemsData).save, Load: (*filesystemsData).load})
+ state.Register("proc.filesystem", (*filesystem)(nil), state.Fns{Save: (*filesystem).save, Load: (*filesystem).load})
+ state.Register("proc.taskOwnedInodeOps", (*taskOwnedInodeOps)(nil), state.Fns{Save: (*taskOwnedInodeOps).save, Load: (*taskOwnedInodeOps).load})
+ state.Register("proc.staticFileInodeOps", (*staticFileInodeOps)(nil), state.Fns{Save: (*staticFileInodeOps).save, Load: (*staticFileInodeOps).load})
+ state.Register("proc.loadavgData", (*loadavgData)(nil), state.Fns{Save: (*loadavgData).save, Load: (*loadavgData).load})
+ state.Register("proc.meminfoData", (*meminfoData)(nil), state.Fns{Save: (*meminfoData).save, Load: (*meminfoData).load})
+ state.Register("proc.mountInfoFile", (*mountInfoFile)(nil), state.Fns{Save: (*mountInfoFile).save, Load: (*mountInfoFile).load})
+ state.Register("proc.mountsFile", (*mountsFile)(nil), state.Fns{Save: (*mountsFile).save, Load: (*mountsFile).load})
+ state.Register("proc.ifinet6", (*ifinet6)(nil), state.Fns{Save: (*ifinet6).save, Load: (*ifinet6).load})
+ state.Register("proc.netDev", (*netDev)(nil), state.Fns{Save: (*netDev).save, Load: (*netDev).load})
+ state.Register("proc.netUnix", (*netUnix)(nil), state.Fns{Save: (*netUnix).save, Load: (*netUnix).load})
+ state.Register("proc.netTCP", (*netTCP)(nil), state.Fns{Save: (*netTCP).save, Load: (*netTCP).load})
+ state.Register("proc.netUDP", (*netUDP)(nil), state.Fns{Save: (*netUDP).save, Load: (*netUDP).load})
+ state.Register("proc.proc", (*proc)(nil), state.Fns{Save: (*proc).save, Load: (*proc).load})
+ state.Register("proc.self", (*self)(nil), state.Fns{Save: (*self).save, Load: (*self).load})
+ state.Register("proc.threadSelf", (*threadSelf)(nil), state.Fns{Save: (*threadSelf).save, Load: (*threadSelf).load})
+ state.Register("proc.rootProcFile", (*rootProcFile)(nil), state.Fns{Save: (*rootProcFile).save, Load: (*rootProcFile).load})
+ state.Register("proc.statData", (*statData)(nil), state.Fns{Save: (*statData).save, Load: (*statData).load})
+ state.Register("proc.mmapMinAddrData", (*mmapMinAddrData)(nil), state.Fns{Save: (*mmapMinAddrData).save, Load: (*mmapMinAddrData).load})
+ state.Register("proc.overcommitMemory", (*overcommitMemory)(nil), state.Fns{Save: (*overcommitMemory).save, Load: (*overcommitMemory).load})
+ state.Register("proc.hostname", (*hostname)(nil), state.Fns{Save: (*hostname).save, Load: (*hostname).load})
+ state.Register("proc.hostnameFile", (*hostnameFile)(nil), state.Fns{Save: (*hostnameFile).save, Load: (*hostnameFile).load})
+ state.Register("proc.tcpMemInode", (*tcpMemInode)(nil), state.Fns{Save: (*tcpMemInode).save, Load: (*tcpMemInode).load})
+ state.Register("proc.tcpMemFile", (*tcpMemFile)(nil), state.Fns{Save: (*tcpMemFile).save, Load: (*tcpMemFile).load})
+ state.Register("proc.tcpSack", (*tcpSack)(nil), state.Fns{Save: (*tcpSack).save, Load: (*tcpSack).load})
+ state.Register("proc.tcpSackFile", (*tcpSackFile)(nil), state.Fns{Save: (*tcpSackFile).save, Load: (*tcpSackFile).load})
+ state.Register("proc.taskDir", (*taskDir)(nil), state.Fns{Save: (*taskDir).save, Load: (*taskDir).load})
+ state.Register("proc.subtasks", (*subtasks)(nil), state.Fns{Save: (*subtasks).save, Load: (*subtasks).load})
+ state.Register("proc.subtasksFile", (*subtasksFile)(nil), state.Fns{Save: (*subtasksFile).save, Load: (*subtasksFile).load})
+ state.Register("proc.exe", (*exe)(nil), state.Fns{Save: (*exe).save, Load: (*exe).load})
+ state.Register("proc.namespaceSymlink", (*namespaceSymlink)(nil), state.Fns{Save: (*namespaceSymlink).save, Load: (*namespaceSymlink).load})
+ state.Register("proc.mapsData", (*mapsData)(nil), state.Fns{Save: (*mapsData).save, Load: (*mapsData).load})
+ state.Register("proc.smapsData", (*smapsData)(nil), state.Fns{Save: (*smapsData).save, Load: (*smapsData).load})
+ state.Register("proc.taskStatData", (*taskStatData)(nil), state.Fns{Save: (*taskStatData).save, Load: (*taskStatData).load})
+ state.Register("proc.statmData", (*statmData)(nil), state.Fns{Save: (*statmData).save, Load: (*statmData).load})
+ state.Register("proc.statusData", (*statusData)(nil), state.Fns{Save: (*statusData).save, Load: (*statusData).load})
+ state.Register("proc.ioData", (*ioData)(nil), state.Fns{Save: (*ioData).save, Load: (*ioData).load})
+ state.Register("proc.comm", (*comm)(nil), state.Fns{Save: (*comm).save, Load: (*comm).load})
+ state.Register("proc.commFile", (*commFile)(nil), state.Fns{Save: (*commFile).save, Load: (*commFile).load})
+ state.Register("proc.auxvec", (*auxvec)(nil), state.Fns{Save: (*auxvec).save, Load: (*auxvec).load})
+ state.Register("proc.auxvecFile", (*auxvecFile)(nil), state.Fns{Save: (*auxvecFile).save, Load: (*auxvecFile).load})
+ state.Register("proc.idMapInodeOperations", (*idMapInodeOperations)(nil), state.Fns{Save: (*idMapInodeOperations).save, Load: (*idMapInodeOperations).load})
+ state.Register("proc.idMapFileOperations", (*idMapFileOperations)(nil), state.Fns{Save: (*idMapFileOperations).save, Load: (*idMapFileOperations).load})
+ state.Register("proc.uptime", (*uptime)(nil), state.Fns{Save: (*uptime).save, Load: (*uptime).load})
+ state.Register("proc.uptimeFile", (*uptimeFile)(nil), state.Fns{Save: (*uptimeFile).save, Load: (*uptimeFile).load})
+ state.Register("proc.versionData", (*versionData)(nil), state.Fns{Save: (*versionData).save, Load: (*versionData).load})
+}
diff --git a/pkg/sentry/fs/proc/seqfile/BUILD b/pkg/sentry/fs/proc/seqfile/BUILD
deleted file mode 100644
index 76433c7d0..000000000
--- a/pkg/sentry/fs/proc/seqfile/BUILD
+++ /dev/null
@@ -1,37 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "seqfile",
- srcs = ["seqfile.go"],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/proc/seqfile",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/sentry/context",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/fs/proc/device",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "seqfile_test",
- size = "small",
- srcs = ["seqfile_test.go"],
- embed = [":seqfile"],
- deps = [
- "//pkg/sentry/context",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/ramfs",
- "//pkg/sentry/usermem",
- ],
-)
diff --git a/pkg/sentry/fs/proc/seqfile/seqfile_state_autogen.go b/pkg/sentry/fs/proc/seqfile/seqfile_state_autogen.go
new file mode 100755
index 000000000..db9f7ceb9
--- /dev/null
+++ b/pkg/sentry/fs/proc/seqfile/seqfile_state_autogen.go
@@ -0,0 +1,58 @@
+// automatically generated by stateify.
+
+package seqfile
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *SeqData) beforeSave() {}
+func (x *SeqData) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Buf", &x.Buf)
+ m.Save("Handle", &x.Handle)
+}
+
+func (x *SeqData) afterLoad() {}
+func (x *SeqData) load(m state.Map) {
+ m.Load("Buf", &x.Buf)
+ m.Load("Handle", &x.Handle)
+}
+
+func (x *SeqFile) beforeSave() {}
+func (x *SeqFile) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes)
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Save("SeqSource", &x.SeqSource)
+ m.Save("source", &x.source)
+ m.Save("generation", &x.generation)
+ m.Save("lastRead", &x.lastRead)
+}
+
+func (x *SeqFile) afterLoad() {}
+func (x *SeqFile) load(m state.Map) {
+ m.Load("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes)
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Load("SeqSource", &x.SeqSource)
+ m.Load("source", &x.source)
+ m.Load("generation", &x.generation)
+ m.Load("lastRead", &x.lastRead)
+}
+
+func (x *seqFileOperations) beforeSave() {}
+func (x *seqFileOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("seqFile", &x.seqFile)
+}
+
+func (x *seqFileOperations) afterLoad() {}
+func (x *seqFileOperations) load(m state.Map) {
+ m.Load("seqFile", &x.seqFile)
+}
+
+func init() {
+ state.Register("seqfile.SeqData", (*SeqData)(nil), state.Fns{Save: (*SeqData).save, Load: (*SeqData).load})
+ state.Register("seqfile.SeqFile", (*SeqFile)(nil), state.Fns{Save: (*SeqFile).save, Load: (*SeqFile).load})
+ state.Register("seqfile.seqFileOperations", (*seqFileOperations)(nil), state.Fns{Save: (*seqFileOperations).save, Load: (*seqFileOperations).load})
+}
diff --git a/pkg/sentry/fs/proc/seqfile/seqfile_test.go b/pkg/sentry/fs/proc/seqfile/seqfile_test.go
deleted file mode 100644
index ebfeee835..000000000
--- a/pkg/sentry/fs/proc/seqfile/seqfile_test.go
+++ /dev/null
@@ -1,279 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package seqfile
-
-import (
- "bytes"
- "fmt"
- "io"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- "gvisor.dev/gvisor/pkg/sentry/fs/ramfs"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
-)
-
-type seqTest struct {
- actual []SeqData
- update bool
-}
-
-func (s *seqTest) Init() {
- var sq []SeqData
- // Create some SeqData.
- for i := 0; i < 10; i++ {
- var b []byte
- for j := 0; j < 10; j++ {
- b = append(b, byte(i))
- }
- sq = append(sq, SeqData{
- Buf: b,
- Handle: &testHandle{i: i},
- })
- }
- s.actual = sq
-}
-
-// NeedsUpdate reports whether we need to update the data we've previously read.
-func (s *seqTest) NeedsUpdate(int64) bool {
- return s.update
-}
-
-// ReadSeqFiledata returns a slice of SeqData which contains elements
-// greater than the handle.
-func (s *seqTest) ReadSeqFileData(ctx context.Context, handle SeqHandle) ([]SeqData, int64) {
- if handle == nil {
- return s.actual, 0
- }
- h := *handle.(*testHandle)
- var ret []SeqData
- for _, b := range s.actual {
- // We want the next one.
- h2 := *b.Handle.(*testHandle)
- if h2.i > h.i {
- ret = append(ret, b)
- }
- }
- return ret, 0
-}
-
-// Flatten a slice of slices into one slice.
-func flatten(buf ...[]byte) []byte {
- var flat []byte
- for _, b := range buf {
- flat = append(flat, b...)
- }
- return flat
-}
-
-type testHandle struct {
- i int
-}
-
-type testTable struct {
- offset int64
- readBufferSize int
- expectedData []byte
- expectedError error
-}
-
-func runTableTests(ctx context.Context, table []testTable, dirent *fs.Dirent) error {
- for _, tt := range table {
- file, err := dirent.Inode.InodeOperations.GetFile(ctx, dirent, fs.FileFlags{Read: true})
- if err != nil {
- return fmt.Errorf("GetFile returned error: %v", err)
- }
-
- data := make([]byte, tt.readBufferSize)
- resultLen, err := file.Preadv(ctx, usermem.BytesIOSequence(data), tt.offset)
- if err != tt.expectedError {
- return fmt.Errorf("t.Preadv(len: %v, offset: %v) (error) => %v expected %v", tt.readBufferSize, tt.offset, err, tt.expectedError)
- }
- expectedLen := int64(len(tt.expectedData))
- if resultLen != expectedLen {
- // We make this just an error so we wall through and print the data below.
- return fmt.Errorf("t.Preadv(len: %v, offset: %v) (size) => %v expected %v", tt.readBufferSize, tt.offset, resultLen, expectedLen)
- }
- if !bytes.Equal(data[:expectedLen], tt.expectedData) {
- return fmt.Errorf("t.Preadv(len: %v, offset: %v) (data) => %v expected %v", tt.readBufferSize, tt.offset, data[:expectedLen], tt.expectedData)
- }
- }
- return nil
-}
-
-func TestSeqFile(t *testing.T) {
- testSource := &seqTest{}
- testSource.Init()
-
- // Create a file that can be R/W.
- ctx := contexttest.Context(t)
- m := fs.NewPseudoMountSource(ctx)
- contents := map[string]*fs.Inode{
- "foo": NewSeqFileInode(ctx, testSource, m),
- }
- root := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0777))
-
- // How about opening it?
- inode := fs.NewInode(ctx, root, m, fs.StableAttr{Type: fs.Directory})
- dirent2, err := root.Lookup(ctx, inode, "foo")
- if err != nil {
- t.Fatalf("failed to walk to foo for n2: %v", err)
- }
- n2 := dirent2.Inode.InodeOperations
- file2, err := n2.GetFile(ctx, dirent2, fs.FileFlags{Read: true, Write: true})
- if err != nil {
- t.Fatalf("GetFile returned error: %v", err)
- }
-
- // Writing?
- if _, err := file2.Writev(ctx, usermem.BytesIOSequence([]byte("test"))); err == nil {
- t.Fatalf("managed to write to n2: %v", err)
- }
-
- // How about reading?
- dirent3, err := root.Lookup(ctx, inode, "foo")
- if err != nil {
- t.Fatalf("failed to walk to foo: %v", err)
- }
- n3 := dirent3.Inode.InodeOperations
- if n2 != n3 {
- t.Error("got n2 != n3, want same")
- }
-
- testSource.update = true
-
- table := []testTable{
- // Read past the end.
- {100, 4, []byte{}, io.EOF},
- {110, 4, []byte{}, io.EOF},
- {200, 4, []byte{}, io.EOF},
- // Read a truncated first line.
- {0, 4, testSource.actual[0].Buf[:4], nil},
- // Read the whole first line.
- {0, 10, testSource.actual[0].Buf, nil},
- // Read the whole first line + 5 bytes of second line.
- {0, 15, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf[:5]), nil},
- // First 4 bytes of the second line.
- {10, 4, testSource.actual[1].Buf[:4], nil},
- // Read the two first lines.
- {0, 20, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf), nil},
- // Read three lines.
- {0, 30, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf, testSource.actual[2].Buf), nil},
- // Read everything, but use a bigger buffer than necessary.
- {0, 150, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf, testSource.actual[2].Buf, testSource.actual[3].Buf, testSource.actual[4].Buf, testSource.actual[5].Buf, testSource.actual[6].Buf, testSource.actual[7].Buf, testSource.actual[8].Buf, testSource.actual[9].Buf), nil},
- // Read the last 3 bytes.
- {97, 10, testSource.actual[9].Buf[7:], nil},
- }
- if err := runTableTests(ctx, table, dirent2); err != nil {
- t.Errorf("runTableTest failed with testSource.update = %v : %v", testSource.update, err)
- }
-
- // Disable updates and do it again.
- testSource.update = false
- if err := runTableTests(ctx, table, dirent2); err != nil {
- t.Errorf("runTableTest failed with testSource.update = %v: %v", testSource.update, err)
- }
-}
-
-// Test that we behave correctly when the file is updated.
-func TestSeqFileFileUpdated(t *testing.T) {
- testSource := &seqTest{}
- testSource.Init()
- testSource.update = true
-
- // Create a file that can be R/W.
- ctx := contexttest.Context(t)
- m := fs.NewPseudoMountSource(ctx)
- contents := map[string]*fs.Inode{
- "foo": NewSeqFileInode(ctx, testSource, m),
- }
- root := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0777))
-
- // How about opening it?
- inode := fs.NewInode(ctx, root, m, fs.StableAttr{Type: fs.Directory})
- dirent2, err := root.Lookup(ctx, inode, "foo")
- if err != nil {
- t.Fatalf("failed to walk to foo for dirent2: %v", err)
- }
-
- table := []testTable{
- {0, 16, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf[:6]), nil},
- }
- if err := runTableTests(ctx, table, dirent2); err != nil {
- t.Errorf("runTableTest failed: %v", err)
- }
- // Delete the first entry.
- cut := testSource.actual[0].Buf
- testSource.actual = testSource.actual[1:]
-
- table = []testTable{
- // Try reading buffer 0 with an offset. This will not delete the old data.
- {1, 5, cut[1:6], nil},
- // Reset our file by reading at offset 0.
- {0, 10, testSource.actual[0].Buf, nil},
- {16, 14, flatten(testSource.actual[1].Buf[6:], testSource.actual[2].Buf), nil},
- // Read the same data a second time.
- {16, 14, flatten(testSource.actual[1].Buf[6:], testSource.actual[2].Buf), nil},
- // Read the following two lines.
- {30, 20, flatten(testSource.actual[3].Buf, testSource.actual[4].Buf), nil},
- }
- if err := runTableTests(ctx, table, dirent2); err != nil {
- t.Errorf("runTableTest failed after removing first entry: %v", err)
- }
-
- // Add a new duplicate line in the middle (6666...)
- after := testSource.actual[5:]
- testSource.actual = testSource.actual[:4]
- // Note the list must be sorted.
- testSource.actual = append(testSource.actual, after[0])
- testSource.actual = append(testSource.actual, after...)
-
- table = []testTable{
- {50, 20, flatten(testSource.actual[4].Buf, testSource.actual[5].Buf), nil},
- }
- if err := runTableTests(ctx, table, dirent2); err != nil {
- t.Errorf("runTableTest failed after adding middle entry: %v", err)
- }
- // This will be used in a later test.
- oldTestData := testSource.actual
-
- // Delete everything.
- testSource.actual = testSource.actual[:0]
- table = []testTable{
- {20, 20, []byte{}, io.EOF},
- }
- if err := runTableTests(ctx, table, dirent2); err != nil {
- t.Errorf("runTableTest failed after removing all entries: %v", err)
- }
- // Restore some of the data.
- testSource.actual = oldTestData[:1]
- table = []testTable{
- {6, 20, testSource.actual[0].Buf[6:], nil},
- }
- if err := runTableTests(ctx, table, dirent2); err != nil {
- t.Errorf("runTableTest failed after adding first entry back: %v", err)
- }
-
- // Re-extend the data
- testSource.actual = oldTestData
- table = []testTable{
- {30, 20, flatten(testSource.actual[3].Buf, testSource.actual[4].Buf), nil},
- }
- if err := runTableTests(ctx, table, dirent2); err != nil {
- t.Errorf("runTableTest failed after extending testSource: %v", err)
- }
-}
diff --git a/pkg/sentry/fs/proc/sys_net_test.go b/pkg/sentry/fs/proc/sys_net_test.go
deleted file mode 100644
index 6abae7a60..000000000
--- a/pkg/sentry/fs/proc/sys_net_test.go
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package proc
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/inet"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
-)
-
-func TestQuerySendBufferSize(t *testing.T) {
- ctx := context.Background()
- s := inet.NewTestStack()
- s.TCPSendBufSize = inet.TCPBufferSize{100, 200, 300}
- tmi := &tcpMemInode{s: s, dir: tcpWMem}
- tmf := &tcpMemFile{tcpMemInode: tmi}
-
- buf := make([]byte, 100)
- dst := usermem.BytesIOSequence(buf)
- n, err := tmf.Read(ctx, nil, dst, 0)
- if err != nil {
- t.Fatalf("Read failed: %v", err)
- }
-
- if got, want := string(buf[:n]), "100\t200\t300\n"; got != want {
- t.Fatalf("Bad string: got %v, want %v", got, want)
- }
-}
-
-func TestQueryRecvBufferSize(t *testing.T) {
- ctx := context.Background()
- s := inet.NewTestStack()
- s.TCPRecvBufSize = inet.TCPBufferSize{100, 200, 300}
- tmi := &tcpMemInode{s: s, dir: tcpRMem}
- tmf := &tcpMemFile{tcpMemInode: tmi}
-
- buf := make([]byte, 100)
- dst := usermem.BytesIOSequence(buf)
- n, err := tmf.Read(ctx, nil, dst, 0)
- if err != nil {
- t.Fatalf("Read failed: %v", err)
- }
-
- if got, want := string(buf[:n]), "100\t200\t300\n"; got != want {
- t.Fatalf("Bad string: got %v, want %v", got, want)
- }
-}
-
-var cases = []struct {
- str string
- initial inet.TCPBufferSize
- final inet.TCPBufferSize
-}{
- {
- str: "",
- initial: inet.TCPBufferSize{1, 2, 3},
- final: inet.TCPBufferSize{1, 2, 3},
- },
- {
- str: "100\n",
- initial: inet.TCPBufferSize{1, 100, 200},
- final: inet.TCPBufferSize{100, 100, 200},
- },
- {
- str: "100 200 300\n",
- initial: inet.TCPBufferSize{1, 2, 3},
- final: inet.TCPBufferSize{100, 200, 300},
- },
-}
-
-func TestConfigureSendBufferSize(t *testing.T) {
- ctx := context.Background()
- s := inet.NewTestStack()
- for _, c := range cases {
- s.TCPSendBufSize = c.initial
- tmi := &tcpMemInode{s: s, dir: tcpWMem}
- tmf := &tcpMemFile{tcpMemInode: tmi}
-
- // Write the values.
- src := usermem.BytesIOSequence([]byte(c.str))
- if n, err := tmf.Write(ctx, nil, src, 0); n != int64(len(c.str)) || err != nil {
- t.Errorf("Write, case = %q: got (%d, %v), wanted (%d, nil)", c.str, n, err, len(c.str))
- }
-
- // Read the values from the stack and check them.
- if s.TCPSendBufSize != c.final {
- t.Errorf("TCPSendBufferSize, case = %q: got %v, wanted %v", c.str, s.TCPSendBufSize, c.final)
- }
- }
-}
-
-func TestConfigureRecvBufferSize(t *testing.T) {
- ctx := context.Background()
- s := inet.NewTestStack()
- for _, c := range cases {
- s.TCPRecvBufSize = c.initial
- tmi := &tcpMemInode{s: s, dir: tcpRMem}
- tmf := &tcpMemFile{tcpMemInode: tmi}
-
- // Write the values.
- src := usermem.BytesIOSequence([]byte(c.str))
- if n, err := tmf.Write(ctx, nil, src, 0); n != int64(len(c.str)) || err != nil {
- t.Errorf("Write, case = %q: got (%d, %v), wanted (%d, nil)", c.str, n, err, len(c.str))
- }
-
- // Read the values from the stack and check them.
- if s.TCPRecvBufSize != c.final {
- t.Errorf("TCPRecvBufferSize, case = %q: got %v, wanted %v", c.str, s.TCPRecvBufSize, c.final)
- }
- }
-}
diff --git a/pkg/sentry/fs/ramfs/BUILD b/pkg/sentry/fs/ramfs/BUILD
deleted file mode 100644
index d0f351e5a..000000000
--- a/pkg/sentry/fs/ramfs/BUILD
+++ /dev/null
@@ -1,39 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "ramfs",
- srcs = [
- "dir.go",
- "socket.go",
- "symlink.go",
- "tree.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/ramfs",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/sentry/context",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/anon",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "ramfs_test",
- size = "small",
- srcs = ["tree_test.go"],
- embed = [":ramfs"],
- deps = [
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/fs",
- ],
-)
diff --git a/pkg/sentry/fs/ramfs/ramfs_state_autogen.go b/pkg/sentry/fs/ramfs/ramfs_state_autogen.go
new file mode 100755
index 000000000..bc86a01a1
--- /dev/null
+++ b/pkg/sentry/fs/ramfs/ramfs_state_autogen.go
@@ -0,0 +1,94 @@
+// automatically generated by stateify.
+
+package ramfs
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *Dir) beforeSave() {}
+func (x *Dir) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Save("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes)
+ m.Save("children", &x.children)
+ m.Save("dentryMap", &x.dentryMap)
+}
+
+func (x *Dir) afterLoad() {}
+func (x *Dir) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Load("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes)
+ m.Load("children", &x.children)
+ m.Load("dentryMap", &x.dentryMap)
+}
+
+func (x *dirFileOperations) beforeSave() {}
+func (x *dirFileOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("dirCursor", &x.dirCursor)
+ m.Save("dir", &x.dir)
+}
+
+func (x *dirFileOperations) afterLoad() {}
+func (x *dirFileOperations) load(m state.Map) {
+ m.Load("dirCursor", &x.dirCursor)
+ m.Load("dir", &x.dir)
+}
+
+func (x *Socket) beforeSave() {}
+func (x *Socket) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Save("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes)
+ m.Save("ep", &x.ep)
+}
+
+func (x *Socket) afterLoad() {}
+func (x *Socket) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Load("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes)
+ m.Load("ep", &x.ep)
+}
+
+func (x *socketFileOperations) beforeSave() {}
+func (x *socketFileOperations) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *socketFileOperations) afterLoad() {}
+func (x *socketFileOperations) load(m state.Map) {
+}
+
+func (x *Symlink) beforeSave() {}
+func (x *Symlink) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Save("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes)
+ m.Save("Target", &x.Target)
+}
+
+func (x *Symlink) afterLoad() {}
+func (x *Symlink) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Load("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes)
+ m.Load("Target", &x.Target)
+}
+
+func (x *symlinkFileOperations) beforeSave() {}
+func (x *symlinkFileOperations) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *symlinkFileOperations) afterLoad() {}
+func (x *symlinkFileOperations) load(m state.Map) {
+}
+
+func init() {
+ state.Register("ramfs.Dir", (*Dir)(nil), state.Fns{Save: (*Dir).save, Load: (*Dir).load})
+ state.Register("ramfs.dirFileOperations", (*dirFileOperations)(nil), state.Fns{Save: (*dirFileOperations).save, Load: (*dirFileOperations).load})
+ state.Register("ramfs.Socket", (*Socket)(nil), state.Fns{Save: (*Socket).save, Load: (*Socket).load})
+ state.Register("ramfs.socketFileOperations", (*socketFileOperations)(nil), state.Fns{Save: (*socketFileOperations).save, Load: (*socketFileOperations).load})
+ state.Register("ramfs.Symlink", (*Symlink)(nil), state.Fns{Save: (*Symlink).save, Load: (*Symlink).load})
+ state.Register("ramfs.symlinkFileOperations", (*symlinkFileOperations)(nil), state.Fns{Save: (*symlinkFileOperations).save, Load: (*symlinkFileOperations).load})
+}
diff --git a/pkg/sentry/fs/ramfs/tree_test.go b/pkg/sentry/fs/ramfs/tree_test.go
deleted file mode 100644
index 61a7e2900..000000000
--- a/pkg/sentry/fs/ramfs/tree_test.go
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ramfs
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
-)
-
-func TestMakeDirectoryTree(t *testing.T) {
-
- for _, test := range []struct {
- name string
- subdirs []string
- }{
- {
- name: "abs paths",
- subdirs: []string{
- "/tmp",
- "/tmp/a/b",
- "/tmp/a/c/d",
- "/tmp/c",
- "/proc",
- "/dev/a/b",
- "/tmp",
- },
- },
- {
- name: "rel paths",
- subdirs: []string{
- "tmp",
- "tmp/a/b",
- "tmp/a/c/d",
- "tmp/c",
- "proc",
- "dev/a/b",
- "tmp",
- },
- },
- } {
- ctx := contexttest.Context(t)
- mount := fs.NewPseudoMountSource(ctx)
- tree, err := MakeDirectoryTree(ctx, mount, test.subdirs)
- if err != nil {
- t.Errorf("%s: failed to make ramfs tree, got error %v, want nil", test.name, err)
- continue
- }
-
- // Expect to be able to find each of the paths.
- mm, err := fs.NewMountNamespace(ctx, tree)
- if err != nil {
- t.Errorf("%s: failed to create mount manager: %v", test.name, err)
- continue
- }
- root := mm.Root()
- defer mm.DecRef()
-
- for _, p := range test.subdirs {
- maxTraversals := uint(0)
- if _, err := mm.FindInode(ctx, root, nil, p, &maxTraversals); err != nil {
- t.Errorf("%s: failed to find node %s: %v", test.name, p, err)
- break
- }
- }
- }
-}
diff --git a/pkg/sentry/fs/sys/BUILD b/pkg/sentry/fs/sys/BUILD
deleted file mode 100644
index 70fa3af89..000000000
--- a/pkg/sentry/fs/sys/BUILD
+++ /dev/null
@@ -1,25 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "sys",
- srcs = [
- "device.go",
- "devices.go",
- "fs.go",
- "sys.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/sys",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/fs/ramfs",
- "//pkg/sentry/kernel",
- "//pkg/sentry/usermem",
- ],
-)
diff --git a/pkg/sentry/fs/sys/sys_state_autogen.go b/pkg/sentry/fs/sys/sys_state_autogen.go
new file mode 100755
index 000000000..603057309
--- /dev/null
+++ b/pkg/sentry/fs/sys/sys_state_autogen.go
@@ -0,0 +1,34 @@
+// automatically generated by stateify.
+
+package sys
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *cpunum) beforeSave() {}
+func (x *cpunum) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Save("InodeStaticFileGetter", &x.InodeStaticFileGetter)
+}
+
+func (x *cpunum) afterLoad() {}
+func (x *cpunum) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Load("InodeStaticFileGetter", &x.InodeStaticFileGetter)
+}
+
+func (x *filesystem) beforeSave() {}
+func (x *filesystem) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *filesystem) afterLoad() {}
+func (x *filesystem) load(m state.Map) {
+}
+
+func init() {
+ state.Register("sys.cpunum", (*cpunum)(nil), state.Fns{Save: (*cpunum).save, Load: (*cpunum).load})
+ state.Register("sys.filesystem", (*filesystem)(nil), state.Fns{Save: (*filesystem).save, Load: (*filesystem).load})
+}
diff --git a/pkg/sentry/fs/timerfd/BUILD b/pkg/sentry/fs/timerfd/BUILD
deleted file mode 100644
index 1d80daeaf..000000000
--- a/pkg/sentry/fs/timerfd/BUILD
+++ /dev/null
@@ -1,20 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "timerfd",
- srcs = ["timerfd.go"],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/timerfd",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/sentry/context",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/anon",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/fs/timerfd/timerfd_state_autogen.go b/pkg/sentry/fs/timerfd/timerfd_state_autogen.go
new file mode 100755
index 000000000..e8d98af97
--- /dev/null
+++ b/pkg/sentry/fs/timerfd/timerfd_state_autogen.go
@@ -0,0 +1,25 @@
+// automatically generated by stateify.
+
+package timerfd
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *TimerOperations) beforeSave() {}
+func (x *TimerOperations) save(m state.Map) {
+ x.beforeSave()
+ if !state.IsZeroValue(x.events) { m.Failf("events is %v, expected zero", x.events) }
+ m.Save("timer", &x.timer)
+ m.Save("val", &x.val)
+}
+
+func (x *TimerOperations) afterLoad() {}
+func (x *TimerOperations) load(m state.Map) {
+ m.Load("timer", &x.timer)
+ m.Load("val", &x.val)
+}
+
+func init() {
+ state.Register("timerfd.TimerOperations", (*TimerOperations)(nil), state.Fns{Save: (*TimerOperations).save, Load: (*TimerOperations).load})
+}
diff --git a/pkg/sentry/fs/tmpfs/BUILD b/pkg/sentry/fs/tmpfs/BUILD
deleted file mode 100644
index 11b680929..000000000
--- a/pkg/sentry/fs/tmpfs/BUILD
+++ /dev/null
@@ -1,52 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "tmpfs",
- srcs = [
- "device.go",
- "file_regular.go",
- "fs.go",
- "inode_file.go",
- "tmpfs.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/tmpfs",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/metric",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/fs/ramfs",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/kernel/pipe",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/memmap",
- "//pkg/sentry/safemem",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/usage",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "tmpfs_test",
- size = "small",
- srcs = ["file_test.go"],
- embed = [":tmpfs"],
- deps = [
- "//pkg/sentry/context",
- "//pkg/sentry/fs",
- "//pkg/sentry/kernel/contexttest",
- "//pkg/sentry/usage",
- "//pkg/sentry/usermem",
- ],
-)
diff --git a/pkg/sentry/fs/tmpfs/file_test.go b/pkg/sentry/fs/tmpfs/file_test.go
deleted file mode 100644
index 0075ef023..000000000
--- a/pkg/sentry/fs/tmpfs/file_test.go
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tmpfs
-
-import (
- "bytes"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- "gvisor.dev/gvisor/pkg/sentry/kernel/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/usage"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
-)
-
-func newFileInode(ctx context.Context) *fs.Inode {
- m := fs.NewCachingMountSource(ctx, &Filesystem{}, fs.MountSourceFlags{})
- iops := NewInMemoryFile(ctx, usage.Tmpfs, fs.WithCurrentTime(ctx, fs.UnstableAttr{}))
- return fs.NewInode(ctx, iops, m, fs.StableAttr{
- DeviceID: tmpfsDevice.DeviceID(),
- InodeID: tmpfsDevice.NextIno(),
- BlockSize: usermem.PageSize,
- Type: fs.RegularFile,
- })
-}
-
-func newFile(ctx context.Context) *fs.File {
- inode := newFileInode(ctx)
- f, _ := inode.GetFile(ctx, fs.NewDirent(ctx, inode, "stub"), fs.FileFlags{Read: true, Write: true})
- return f
-}
-
-// Allocate once, write twice.
-func TestGrow(t *testing.T) {
- ctx := contexttest.Context(t)
- f := newFile(ctx)
- defer f.DecRef()
-
- abuf := bytes.Repeat([]byte{'a'}, 68)
- n, err := f.Pwritev(ctx, usermem.BytesIOSequence(abuf), 0)
- if n != int64(len(abuf)) || err != nil {
- t.Fatalf("Pwritev got (%d, %v) want (%d, nil)", n, err, len(abuf))
- }
-
- bbuf := bytes.Repeat([]byte{'b'}, 856)
- n, err = f.Pwritev(ctx, usermem.BytesIOSequence(bbuf), 68)
- if n != int64(len(bbuf)) || err != nil {
- t.Fatalf("Pwritev got (%d, %v) want (%d, nil)", n, err, len(bbuf))
- }
-
- rbuf := make([]byte, len(abuf)+len(bbuf))
- n, err = f.Preadv(ctx, usermem.BytesIOSequence(rbuf), 0)
- if n != int64(len(rbuf)) || err != nil {
- t.Fatalf("Preadv got (%d, %v) want (%d, nil)", n, err, len(rbuf))
- }
-
- if want := append(abuf, bbuf...); !bytes.Equal(rbuf, want) {
- t.Fatalf("Read %v, want %v", rbuf, want)
- }
-}
diff --git a/pkg/sentry/fs/tmpfs/tmpfs_state_autogen.go b/pkg/sentry/fs/tmpfs/tmpfs_state_autogen.go
new file mode 100755
index 000000000..7d73d1c77
--- /dev/null
+++ b/pkg/sentry/fs/tmpfs/tmpfs_state_autogen.go
@@ -0,0 +1,108 @@
+// automatically generated by stateify.
+
+package tmpfs
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *regularFileOperations) beforeSave() {}
+func (x *regularFileOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("iops", &x.iops)
+}
+
+func (x *regularFileOperations) afterLoad() {}
+func (x *regularFileOperations) load(m state.Map) {
+ m.Load("iops", &x.iops)
+}
+
+func (x *Filesystem) beforeSave() {}
+func (x *Filesystem) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *Filesystem) afterLoad() {}
+func (x *Filesystem) load(m state.Map) {
+}
+
+func (x *fileInodeOperations) beforeSave() {}
+func (x *fileInodeOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes)
+ m.Save("kernel", &x.kernel)
+ m.Save("memUsage", &x.memUsage)
+ m.Save("attr", &x.attr)
+ m.Save("mappings", &x.mappings)
+ m.Save("writableMappingPages", &x.writableMappingPages)
+ m.Save("data", &x.data)
+ m.Save("seals", &x.seals)
+}
+
+func (x *fileInodeOperations) afterLoad() {}
+func (x *fileInodeOperations) load(m state.Map) {
+ m.Load("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes)
+ m.Load("kernel", &x.kernel)
+ m.Load("memUsage", &x.memUsage)
+ m.Load("attr", &x.attr)
+ m.Load("mappings", &x.mappings)
+ m.Load("writableMappingPages", &x.writableMappingPages)
+ m.Load("data", &x.data)
+ m.Load("seals", &x.seals)
+}
+
+func (x *Dir) beforeSave() {}
+func (x *Dir) save(m state.Map) {
+ x.beforeSave()
+ m.Save("ramfsDir", &x.ramfsDir)
+ m.Save("kernel", &x.kernel)
+}
+
+func (x *Dir) load(m state.Map) {
+ m.Load("ramfsDir", &x.ramfsDir)
+ m.Load("kernel", &x.kernel)
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *Symlink) beforeSave() {}
+func (x *Symlink) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Symlink", &x.Symlink)
+}
+
+func (x *Symlink) afterLoad() {}
+func (x *Symlink) load(m state.Map) {
+ m.Load("Symlink", &x.Symlink)
+}
+
+func (x *Socket) beforeSave() {}
+func (x *Socket) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Socket", &x.Socket)
+}
+
+func (x *Socket) afterLoad() {}
+func (x *Socket) load(m state.Map) {
+ m.Load("Socket", &x.Socket)
+}
+
+func (x *Fifo) beforeSave() {}
+func (x *Fifo) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeOperations", &x.InodeOperations)
+}
+
+func (x *Fifo) afterLoad() {}
+func (x *Fifo) load(m state.Map) {
+ m.Load("InodeOperations", &x.InodeOperations)
+}
+
+func init() {
+ state.Register("tmpfs.regularFileOperations", (*regularFileOperations)(nil), state.Fns{Save: (*regularFileOperations).save, Load: (*regularFileOperations).load})
+ state.Register("tmpfs.Filesystem", (*Filesystem)(nil), state.Fns{Save: (*Filesystem).save, Load: (*Filesystem).load})
+ state.Register("tmpfs.fileInodeOperations", (*fileInodeOperations)(nil), state.Fns{Save: (*fileInodeOperations).save, Load: (*fileInodeOperations).load})
+ state.Register("tmpfs.Dir", (*Dir)(nil), state.Fns{Save: (*Dir).save, Load: (*Dir).load})
+ state.Register("tmpfs.Symlink", (*Symlink)(nil), state.Fns{Save: (*Symlink).save, Load: (*Symlink).load})
+ state.Register("tmpfs.Socket", (*Socket)(nil), state.Fns{Save: (*Socket).save, Load: (*Socket).load})
+ state.Register("tmpfs.Fifo", (*Fifo)(nil), state.Fns{Save: (*Fifo).save, Load: (*Fifo).load})
+}
diff --git a/pkg/sentry/fs/tty/BUILD b/pkg/sentry/fs/tty/BUILD
deleted file mode 100644
index d799de748..000000000
--- a/pkg/sentry/fs/tty/BUILD
+++ /dev/null
@@ -1,48 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "tty",
- srcs = [
- "dir.go",
- "fs.go",
- "line_discipline.go",
- "master.go",
- "queue.go",
- "slave.go",
- "terminal.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fs/tty",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/refs",
- "//pkg/sentry/arch",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/safemem",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/unimpl",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "tty_test",
- size = "small",
- srcs = ["tty_test.go"],
- embed = [":tty"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/usermem",
- ],
-)
diff --git a/pkg/sentry/fs/tty/tty_state_autogen.go b/pkg/sentry/fs/tty/tty_state_autogen.go
new file mode 100755
index 000000000..6c9845627
--- /dev/null
+++ b/pkg/sentry/fs/tty/tty_state_autogen.go
@@ -0,0 +1,202 @@
+// automatically generated by stateify.
+
+package tty
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *dirInodeOperations) beforeSave() {}
+func (x *dirInodeOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Save("msrc", &x.msrc)
+ m.Save("master", &x.master)
+ m.Save("slaves", &x.slaves)
+ m.Save("dentryMap", &x.dentryMap)
+ m.Save("next", &x.next)
+}
+
+func (x *dirInodeOperations) afterLoad() {}
+func (x *dirInodeOperations) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Load("msrc", &x.msrc)
+ m.Load("master", &x.master)
+ m.Load("slaves", &x.slaves)
+ m.Load("dentryMap", &x.dentryMap)
+ m.Load("next", &x.next)
+}
+
+func (x *dirFileOperations) beforeSave() {}
+func (x *dirFileOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("di", &x.di)
+ m.Save("dirCursor", &x.dirCursor)
+}
+
+func (x *dirFileOperations) afterLoad() {}
+func (x *dirFileOperations) load(m state.Map) {
+ m.Load("di", &x.di)
+ m.Load("dirCursor", &x.dirCursor)
+}
+
+func (x *filesystem) beforeSave() {}
+func (x *filesystem) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *filesystem) afterLoad() {}
+func (x *filesystem) load(m state.Map) {
+}
+
+func (x *superOperations) beforeSave() {}
+func (x *superOperations) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *superOperations) afterLoad() {}
+func (x *superOperations) load(m state.Map) {
+}
+
+func (x *lineDiscipline) beforeSave() {}
+func (x *lineDiscipline) save(m state.Map) {
+ x.beforeSave()
+ if !state.IsZeroValue(x.masterWaiter) { m.Failf("masterWaiter is %v, expected zero", x.masterWaiter) }
+ if !state.IsZeroValue(x.slaveWaiter) { m.Failf("slaveWaiter is %v, expected zero", x.slaveWaiter) }
+ m.Save("size", &x.size)
+ m.Save("inQueue", &x.inQueue)
+ m.Save("outQueue", &x.outQueue)
+ m.Save("termios", &x.termios)
+ m.Save("column", &x.column)
+}
+
+func (x *lineDiscipline) afterLoad() {}
+func (x *lineDiscipline) load(m state.Map) {
+ m.Load("size", &x.size)
+ m.Load("inQueue", &x.inQueue)
+ m.Load("outQueue", &x.outQueue)
+ m.Load("termios", &x.termios)
+ m.Load("column", &x.column)
+}
+
+func (x *outputQueueTransformer) beforeSave() {}
+func (x *outputQueueTransformer) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *outputQueueTransformer) afterLoad() {}
+func (x *outputQueueTransformer) load(m state.Map) {
+}
+
+func (x *inputQueueTransformer) beforeSave() {}
+func (x *inputQueueTransformer) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *inputQueueTransformer) afterLoad() {}
+func (x *inputQueueTransformer) load(m state.Map) {
+}
+
+func (x *masterInodeOperations) beforeSave() {}
+func (x *masterInodeOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("SimpleFileInode", &x.SimpleFileInode)
+ m.Save("d", &x.d)
+}
+
+func (x *masterInodeOperations) afterLoad() {}
+func (x *masterInodeOperations) load(m state.Map) {
+ m.Load("SimpleFileInode", &x.SimpleFileInode)
+ m.Load("d", &x.d)
+}
+
+func (x *masterFileOperations) beforeSave() {}
+func (x *masterFileOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("d", &x.d)
+ m.Save("t", &x.t)
+}
+
+func (x *masterFileOperations) afterLoad() {}
+func (x *masterFileOperations) load(m state.Map) {
+ m.Load("d", &x.d)
+ m.Load("t", &x.t)
+}
+
+func (x *queue) beforeSave() {}
+func (x *queue) save(m state.Map) {
+ x.beforeSave()
+ m.Save("readBuf", &x.readBuf)
+ m.Save("waitBuf", &x.waitBuf)
+ m.Save("waitBufLen", &x.waitBufLen)
+ m.Save("readable", &x.readable)
+ m.Save("transformer", &x.transformer)
+}
+
+func (x *queue) afterLoad() {}
+func (x *queue) load(m state.Map) {
+ m.Load("readBuf", &x.readBuf)
+ m.Load("waitBuf", &x.waitBuf)
+ m.Load("waitBufLen", &x.waitBufLen)
+ m.Load("readable", &x.readable)
+ m.Load("transformer", &x.transformer)
+}
+
+func (x *slaveInodeOperations) beforeSave() {}
+func (x *slaveInodeOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("SimpleFileInode", &x.SimpleFileInode)
+ m.Save("d", &x.d)
+ m.Save("t", &x.t)
+}
+
+func (x *slaveInodeOperations) afterLoad() {}
+func (x *slaveInodeOperations) load(m state.Map) {
+ m.Load("SimpleFileInode", &x.SimpleFileInode)
+ m.Load("d", &x.d)
+ m.Load("t", &x.t)
+}
+
+func (x *slaveFileOperations) beforeSave() {}
+func (x *slaveFileOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("si", &x.si)
+}
+
+func (x *slaveFileOperations) afterLoad() {}
+func (x *slaveFileOperations) load(m state.Map) {
+ m.Load("si", &x.si)
+}
+
+func (x *Terminal) beforeSave() {}
+func (x *Terminal) save(m state.Map) {
+ x.beforeSave()
+ m.Save("AtomicRefCount", &x.AtomicRefCount)
+ m.Save("n", &x.n)
+ m.Save("d", &x.d)
+ m.Save("ld", &x.ld)
+}
+
+func (x *Terminal) afterLoad() {}
+func (x *Terminal) load(m state.Map) {
+ m.Load("AtomicRefCount", &x.AtomicRefCount)
+ m.Load("n", &x.n)
+ m.Load("d", &x.d)
+ m.Load("ld", &x.ld)
+}
+
+func init() {
+ state.Register("tty.dirInodeOperations", (*dirInodeOperations)(nil), state.Fns{Save: (*dirInodeOperations).save, Load: (*dirInodeOperations).load})
+ state.Register("tty.dirFileOperations", (*dirFileOperations)(nil), state.Fns{Save: (*dirFileOperations).save, Load: (*dirFileOperations).load})
+ state.Register("tty.filesystem", (*filesystem)(nil), state.Fns{Save: (*filesystem).save, Load: (*filesystem).load})
+ state.Register("tty.superOperations", (*superOperations)(nil), state.Fns{Save: (*superOperations).save, Load: (*superOperations).load})
+ state.Register("tty.lineDiscipline", (*lineDiscipline)(nil), state.Fns{Save: (*lineDiscipline).save, Load: (*lineDiscipline).load})
+ state.Register("tty.outputQueueTransformer", (*outputQueueTransformer)(nil), state.Fns{Save: (*outputQueueTransformer).save, Load: (*outputQueueTransformer).load})
+ state.Register("tty.inputQueueTransformer", (*inputQueueTransformer)(nil), state.Fns{Save: (*inputQueueTransformer).save, Load: (*inputQueueTransformer).load})
+ state.Register("tty.masterInodeOperations", (*masterInodeOperations)(nil), state.Fns{Save: (*masterInodeOperations).save, Load: (*masterInodeOperations).load})
+ state.Register("tty.masterFileOperations", (*masterFileOperations)(nil), state.Fns{Save: (*masterFileOperations).save, Load: (*masterFileOperations).load})
+ state.Register("tty.queue", (*queue)(nil), state.Fns{Save: (*queue).save, Load: (*queue).load})
+ state.Register("tty.slaveInodeOperations", (*slaveInodeOperations)(nil), state.Fns{Save: (*slaveInodeOperations).save, Load: (*slaveInodeOperations).load})
+ state.Register("tty.slaveFileOperations", (*slaveFileOperations)(nil), state.Fns{Save: (*slaveFileOperations).save, Load: (*slaveFileOperations).load})
+ state.Register("tty.Terminal", (*Terminal)(nil), state.Fns{Save: (*Terminal).save, Load: (*Terminal).load})
+}
diff --git a/pkg/sentry/fs/tty/tty_test.go b/pkg/sentry/fs/tty/tty_test.go
deleted file mode 100644
index 59f07ff8e..000000000
--- a/pkg/sentry/fs/tty/tty_test.go
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tty
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
-)
-
-func TestSimpleMasterToSlave(t *testing.T) {
- ld := newLineDiscipline(linux.DefaultSlaveTermios)
- ctx := contexttest.Context(t)
- inBytes := []byte("hello, tty\n")
- src := usermem.BytesIOSequence(inBytes)
- outBytes := make([]byte, 32)
- dst := usermem.BytesIOSequence(outBytes)
-
- // Write to the input queue.
- nw, err := ld.inputQueueWrite(ctx, src)
- if err != nil {
- t.Fatalf("error writing to input queue: %v", err)
- }
- if nw != int64(len(inBytes)) {
- t.Fatalf("wrote wrong length: got %d, want %d", nw, len(inBytes))
- }
-
- // Read from the input queue.
- nr, err := ld.inputQueueRead(ctx, dst)
- if err != nil {
- t.Fatalf("error reading from input queue: %v", err)
- }
- if nr != int64(len(inBytes)) {
- t.Fatalf("read wrong length: got %d, want %d", nr, len(inBytes))
- }
-
- outStr := string(outBytes[:nr])
- inStr := string(inBytes)
- if outStr != inStr {
- t.Fatalf("written and read strings do not match: got %q, want %q", outStr, inStr)
- }
-}
diff --git a/pkg/sentry/fsimpl/ext/BUILD b/pkg/sentry/fsimpl/ext/BUILD
deleted file mode 100644
index b0c286b7a..000000000
--- a/pkg/sentry/fsimpl/ext/BUILD
+++ /dev/null
@@ -1,88 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-
-go_template_instance(
- name = "dirent_list",
- out = "dirent_list.go",
- package = "ext",
- prefix = "dirent",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*dirent",
- "Linker": "*dirent",
- },
-)
-
-go_library(
- name = "ext",
- srcs = [
- "block_map_file.go",
- "dentry.go",
- "directory.go",
- "dirent_list.go",
- "ext.go",
- "extent_file.go",
- "file_description.go",
- "filesystem.go",
- "inode.go",
- "regular_file.go",
- "symlink.go",
- "utils.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fsimpl/ext",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/binary",
- "//pkg/fd",
- "//pkg/log",
- "//pkg/sentry/arch",
- "//pkg/sentry/context",
- "//pkg/sentry/fs",
- "//pkg/sentry/fsimpl/ext/disklayout",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/memmap",
- "//pkg/sentry/safemem",
- "//pkg/sentry/syscalls/linux",
- "//pkg/sentry/usermem",
- "//pkg/sentry/vfs",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "ext_test",
- size = "small",
- srcs = [
- "block_map_test.go",
- "ext_test.go",
- "extent_test.go",
- ],
- data = [
- "//pkg/sentry/fsimpl/ext:assets/bigfile.txt",
- "//pkg/sentry/fsimpl/ext:assets/file.txt",
- "//pkg/sentry/fsimpl/ext:assets/tiny.ext2",
- "//pkg/sentry/fsimpl/ext:assets/tiny.ext3",
- "//pkg/sentry/fsimpl/ext:assets/tiny.ext4",
- ],
- embed = [":ext"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/binary",
- "//pkg/sentry/context",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/fsimpl/ext/disklayout",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/usermem",
- "//pkg/sentry/vfs",
- "//pkg/syserror",
- "//runsc/testutil",
- "@com_github_google_go-cmp//cmp:go_default_library",
- "@com_github_google_go-cmp//cmp/cmpopts:go_default_library",
- ],
-)
diff --git a/pkg/sentry/fsimpl/ext/README.md b/pkg/sentry/fsimpl/ext/README.md
deleted file mode 100644
index af00cfda8..000000000
--- a/pkg/sentry/fsimpl/ext/README.md
+++ /dev/null
@@ -1,117 +0,0 @@
-## EXT(2/3/4) File System
-
-This is a filesystem driver which supports ext2, ext3 and ext4 filesystems.
-Linux has specialized drivers for each variant but none which supports all. This
-library takes advantage of ext's backward compatibility and understands the
-internal organization of on-disk structures to support all variants.
-
-This driver implementation diverges from the Linux implementations in being more
-forgiving about versioning. For instance, if a filesystem contains both extent
-based inodes and classical block map based inodes, this driver will not complain
-and interpret them both correctly. While in Linux this would be an issue. This
-blurs the line between the three ext fs variants.
-
-Ext2 is considered deprecated as of Red Hat Enterprise Linux 7, and ext3 has
-been superseded by ext4 by large performance gains. Thus it is recommended to
-upgrade older filesystem images to ext4 using e2fsprogs for better performance.
-
-### Read Only
-
-This driver currently only allows read only operations. A lot of the design
-decisions are based on this feature. There are plans to implement write (the
-process for which is documented in the future work section).
-
-### Performance
-
-One of the biggest wins about this driver is that it directly talks to the
-underlying block device (or whatever persistent storage is being used), instead
-of making expensive RPCs to a gofer.
-
-Another advantage is that ext fs supports fast concurrent reads. Currently the
-device is represented using a `io.ReaderAt` which allows for concurrent reads.
-All reads are directly passed to the device driver which intelligently serves
-the read requests in the optimal order. There is no congestion due to locking
-while reading in the filesystem level.
-
-Reads are optimized further in the way file data is transferred over to user
-memory. Ext fs directly copies over file data from disk into user memory with no
-additional allocations on the way. We can only get faster by preloading file
-data into memory (see future work section).
-
-The internal structures used to represent files, inodes and file descriptors use
-a lot of inheritance. With the level of indirection that an interface adds with
-an internal pointer, it can quickly fragment a structure across memory. As this
-runs along side a full blown kernel (which is memory intensive), having a
-fragmented struct might hurt performance. Hence these internal structures,
-though interfaced, are tightly packed in memory using the same inheritance
-pattern that pkg/sentry/vfs uses. The pkg/sentry/fsimpl/ext/disklayout package
-makes an execption to this pattern for reasons documented in the package.
-
-### Security
-
-This driver also intends to help sandbox the container better by reducing the
-surface of the host kernel that the application touches. It prevents the
-application from exploiting vulnerabilities in the host filesystem driver. All
-`io.ReaderAt.ReadAt()` calls are translated to `pread(2)` which are directly
-passed to the device driver in the kernel. Hence this reduces the surface for
-attack.
-
-The application can not affect any host filesystems other than the one passed
-via block device by the user.
-
-### Future Work
-
-#### Write
-
-To support write operations we would need to modify the block device underneath.
-Currently, the driver does not modify the device at all, not even for updating
-the access times for reads. Modifying the filesystem incorrectly can corrupt it
-and render it unreadable for other correct ext(x) drivers. Hence caution must be
-maintained while modifying metadata structures.
-
-Ext4 specifically is built for performance and has added a lot of complexity as
-to how metadata structures are modified. For instance, files that are organized
-via an extent tree which must be balanced and file data blocks must be placed in
-the same extent as much as possible to increase locality. Such properties must
-be maintained while modifying the tree.
-
-Ext filesystems boast a lot about locality, which plays a big role in them being
-performant. The block allocation algorithm in Linux does a good job in keeping
-related data together. This behavior must be maintained as much as possible,
-else we might end up degrading the filesystem performance over time.
-
-Ext4 also supports a wide variety of features which are specialized for varying
-use cases. Implementing all of them can get difficult very quickly.
-
-Ext(x) checksums all its metadata structures to check for corruption, so
-modification of any metadata struct must correspond with re-checksumming the
-struct. Linux filesystem drivers also order on-disk updates intelligently to not
-corrupt the filesystem and also remain performant. The in-memory metadata
-structures must be kept in sync with what is on disk.
-
-There is also replication of some important structures across the filesystem.
-All replicas must be updated when their original copy is updated. There is also
-provisioning for snapshotting which must be kept in mind, although it should not
-affect this implementation unless we allow users to create filesystem snapshots.
-
-Ext4 also introduced journaling (jbd2). The journal must be updated
-appropriately.
-
-#### Performance
-
-To improve performance we should implement a buffer cache, and optionally, read
-ahead for small files. While doing so we must also keep in mind the memory usage
-and have a reasonable cap on how much file data we want to hold in memory.
-
-#### Features
-
-Our current implementation will work with most ext4 filesystems for readonly
-purposed. However, the following features are not supported yet:
-
-- Journal
-- Snapshotting
-- Extended Attributes
-- Hash Tree Directories
-- Meta Block Groups
-- Multiple Mount Protection
-- Bigalloc
diff --git a/pkg/sentry/fsimpl/ext/assets/README.md b/pkg/sentry/fsimpl/ext/assets/README.md
deleted file mode 100644
index 6f1e81b3a..000000000
--- a/pkg/sentry/fsimpl/ext/assets/README.md
+++ /dev/null
@@ -1,36 +0,0 @@
-### Tiny Ext(2/3/4) Images
-
-The images are of size 64Kb which supports 64 1k blocks and 16 inodes. This is
-the smallest size mkfs.ext(2/3/4) works with.
-
-These images were generated using the following commands.
-
-```bash
-fallocate -l 64K tiny.ext$VERSION
-mkfs.ext$VERSION -j tiny.ext$VERSION
-```
-
-where `VERSION` is `2`, `3` or `4`.
-
-You can mount it using:
-
-```bash
-sudo mount -o loop tiny.ext$VERSION $MOUNTPOINT
-```
-
-`file.txt`, `bigfile.txt` and `symlink.txt` were added to this image by just
-mounting it and copying (while preserving links) those files to the mountpoint
-directory using:
-
-```bash
-sudo cp -P {file.txt,symlink.txt,bigfile.txt} $MOUNTPOINT
-```
-
-The files in this directory mirror the contents and organisation of the files
-stored in the image.
-
-You can umount the filesystem using:
-
-```bash
-sudo umount $MOUNTPOINT
-```
diff --git a/pkg/sentry/fsimpl/ext/assets/bigfile.txt b/pkg/sentry/fsimpl/ext/assets/bigfile.txt
deleted file mode 100644
index 3857cf516..000000000
--- a/pkg/sentry/fsimpl/ext/assets/bigfile.txt
+++ /dev/null
@@ -1,41 +0,0 @@
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus faucibus eleifend orci, ut ornare nibh faucibus eu. Cras at condimentum massa. Nullam luctus, elit non porttitor congue, sapien diam feugiat sapien, sed eleifend nulla mauris non arcu. Sed lacinia mauris magna, eu mollis libero varius sit amet. Donec mollis, quam convallis commodo posuere, dolor nisi placerat nisi, in faucibus augue mi eu lorem. In pharetra consectetur faucibus. Ut euismod ex efficitur egestas tincidunt. Maecenas condimentum ut ante in rutrum. Vivamus sed arcu tempor, faucibus turpis et, lacinia diam.
-
-Sed in lacus vel nisl interdum bibendum in sed justo. Nunc tellus risus, molestie vitae arcu sed, molestie tempus ligula. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc risus neque, volutpat et ante non, ullamcorper condimentum ante. Aliquam sed metus in urna condimentum convallis. Vivamus ut libero mauris. Proin mollis posuere consequat. Vestibulum placerat mollis est et pulvinar.
-
-Donec rutrum odio ac diam pharetra, id fermentum magna cursus. Pellentesque in dapibus elit, et condimentum orci. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Suspendisse euismod dapibus est, id vestibulum mauris. Nulla facilisi. Nulla cursus gravida nisi. Phasellus vestibulum rutrum lectus, a dignissim mauris hendrerit vitae. In at elementum mauris. Integer vel efficitur velit. Nullam fringilla sapien mi, quis luctus neque efficitur ac. Aenean nec quam dapibus nunc commodo pharetra. Proin sapien mi, fermentum aliquet vulputate non, aliquet porttitor diam. Quisque lacinia, urna et finibus fermentum, nunc lacus vehicula ex, sed congue metus lectus ac quam. Aliquam erat volutpat. Suspendisse sodales, dolor ut tincidunt finibus, augue erat varius tellus, a interdum erat sem at nunc. Vestibulum cursus iaculis sapien, vitae feugiat dui auctor quis.
-
-Pellentesque nec maximus nulla, eu blandit diam. Maecenas quis arcu ornare, congue ante at, vehicula ipsum. Praesent feugiat mauris rutrum sem fermentum, nec luctus ipsum placerat. Pellentesque placerat ipsum at dignissim fringilla. Vivamus et posuere sem, eget hendrerit felis. Aenean vulputate, augue vel mollis feugiat, justo ipsum mollis dolor, eu mollis elit neque ut ipsum. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce bibendum sem quam, vulputate laoreet mi dapibus imperdiet. Sed a purus non nibh pretium aliquet. Integer eget luctus augue, vitae tincidunt magna. Ut eros enim, egestas eu nulla et, lobortis egestas arcu. Cras id ipsum ac justo lacinia rutrum. Vivamus lectus leo, ultricies sed justo at, pellentesque feugiat magna. Ut sollicitudin neque elit, vel ornare mauris commodo id.
-
-Duis dapibus orci et sapien finibus finibus. Mauris eleifend, lacus at vestibulum maximus, quam ligula pharetra erat, sit amet dapibus neque elit vitae neque. In bibendum sollicitudin erat, eget ultricies tortor malesuada at. Sed sit amet orci turpis. Donec feugiat ligula nibh, molestie tincidunt lectus elementum id. Donec volutpat maximus nibh, in vulputate felis posuere eu. Cras tincidunt ullamcorper lacus. Phasellus porta lorem auctor, congue magna a, commodo elit.
-
-Etiam auctor mi quis elit sodales, eu pulvinar arcu condimentum. Aenean imperdiet risus et dapibus tincidunt. Nullam tincidunt dictum dui, sed commodo urna rutrum id. Ut mollis libero vel elit laoreet bibendum. Quisque arcu arcu, tincidunt at ultricies id, vulputate nec metus. In tristique posuere quam sit amet volutpat. Vivamus scelerisque et nunc at dapibus. Fusce finibus libero ut ligula pretium rhoncus. Mauris non elit in arcu finibus imperdiet. Pellentesque nec massa odio. Proin rutrum mauris non sagittis efficitur. Aliquam auctor quam at dignissim faucibus. Ut eget ligula in magna posuere ultricies vitae sit amet turpis. Duis maximus odio nulla. Donec gravida sem tristique tempus scelerisque.
-
-Interdum et malesuada fames ac ante ipsum primis in faucibus. Fusce pharetra magna vulputate aliquet tempus. Duis id hendrerit arcu. Quisque ut ex elit. Integer velit orci, venenatis ut sapien ac, placerat porttitor dui. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nunc hendrerit cursus diam, hendrerit finibus ipsum scelerisque ut. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.
-
-Nulla non euismod neque. Phasellus vel sapien eu metus pulvinar rhoncus. Suspendisse eu mollis tellus, quis vestibulum tortor. Maecenas interdum dolor sed nulla fermentum maximus. Donec imperdiet ullamcorper condimentum. Nam quis nibh ante. Praesent quis tellus ut tortor pulvinar blandit sit amet ut sapien. Vestibulum est orci, pellentesque vitae tristique sit amet, tristique non felis.
-
-Vivamus sodales pellentesque varius. Sed vel tempus ligula. Nulla tristique nisl vel dui facilisis, ac sodales augue hendrerit. Proin augue nisi, vestibulum quis augue nec, sagittis tincidunt velit. Vestibulum euismod, nulla nec sodales faucibus, urna sapien vulputate magna, id varius metus sapien ut neque. Duis in mollis urna, in scelerisque enim. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc condimentum dictum turpis, et egestas neque dapibus eget. Quisque fringilla, dui eu venenatis eleifend, erat nibh lacinia urna, at lacinia lacus sapien eu dui. Duis eu erat ut mi lacinia convallis a sed ex.
-
-Fusce elit metus, tincidunt nec eleifend a, hendrerit nec ligula. Duis placerat finibus sollicitudin. In euismod porta tellus, in luctus justo bibendum bibendum. Maecenas at magna eleifend lectus tincidunt suscipit ut a ligula. Nulla tempor accumsan felis, fermentum dapibus est eleifend vitae. Mauris urna sem, fringilla at ultricies non, ultrices in arcu. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam vehicula nunc at laoreet imperdiet. Nunc tristique ut risus id aliquet. Integer eleifend massa orci.
-
-Vestibulum sed ante sollicitudin nisi fringilla bibendum nec vel quam. Sed pretium augue eu ligula congue pulvinar. Donec vitae magna tincidunt, pharetra lacus id, convallis nulla. Cras viverra nisl nisl, varius convallis leo vulputate nec. Morbi at consequat dui, sed aliquet metus. Sed suscipit fermentum mollis. Maecenas nec mi sodales, tincidunt purus in, tristique mauris. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec interdum mi in velit efficitur, quis ultrices ex imperdiet. Sed vestibulum, magna ut tristique pretium, mi ipsum placerat tellus, non tempor enim augue et ex. Pellentesque eget felis quis ante sodales viverra ac sed lacus. Donec suscipit tempus massa, eget laoreet massa molestie at.
-
-Aenean fringilla dui non aliquet consectetur. Fusce cursus quam nec orci hendrerit faucibus. Donec consequat suscipit enim, non volutpat lectus auctor interdum. Proin lorem purus, maximus vel orci vitae, suscipit egestas turpis. Donec risus urna, congue a sem eu, aliquet placerat odio. Morbi gravida tristique turpis, quis efficitur enim. Nunc interdum gravida ipsum vel facilisis. Nunc congue finibus sollicitudin. Quisque euismod aliquet lectus et tincidunt. Curabitur ultrices sem ut mi fringilla fermentum. Morbi pretium, nisi sit amet dapibus congue, dolor enim consectetur risus, a interdum ligula odio sed odio. Quisque facilisis, mi at suscipit gravida, nunc sapien cursus justo, ut luctus odio nulla quis leo. Integer condimentum lobortis mauris, non egestas tellus lobortis sit amet.
-
-In sollicitudin velit ac ante vehicula, vitae varius tortor mollis. In hac habitasse platea dictumst. Quisque et orci lorem. Integer malesuada fringilla luctus. Pellentesque malesuada, mi non lobortis porttitor, ante ligula vulputate ante, nec dictum risus eros sit amet sapien. Nulla aliquam lorem libero, ac varius nulla tristique eget. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Ut pellentesque mauris orci, vel consequat mi varius a. Ut sit amet elit vulputate, lacinia metus non, fermentum nisl. Pellentesque eu nisi sed quam egestas blandit. Duis sit amet lobortis dolor. Donec consectetur sem interdum, tristique elit sit amet, sodales lacus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Fusce id aliquam augue. Sed pretium congue risus vitae lacinia. Vestibulum non vulputate risus, ut malesuada justo.
-
-Sed odio elit, consectetur ac mauris quis, consequat commodo libero. Fusce sodales velit vulputate pulvinar fermentum. Donec iaculis nec nisl eget faucibus. Mauris at dictum velit. Donec fermentum lectus eu viverra volutpat. Aliquam consequat facilisis lorem, cursus consequat dui bibendum ullamcorper. Pellentesque nulla magna, imperdiet at magna et, cursus egestas enim. Nullam semper molestie lectus sit amet semper. Duis eget tincidunt est. Integer id neque risus. Integer ultricies hendrerit vestibulum. Donec blandit blandit sagittis. Nunc consectetur vitae nisi consectetur volutpat.
-
-Nulla id lorem fermentum, efficitur magna a, hendrerit dui. Vivamus sagittis orci gravida, bibendum quam eget, molestie est. Phasellus nec enim tincidunt, volutpat sapien non, laoreet diam. Nulla posuere enim nec porttitor lobortis. Donec auctor odio ut orci eleifend, ut eleifend purus convallis. Interdum et malesuada fames ac ante ipsum primis in faucibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut hendrerit, purus eget viverra tincidunt, sem magna imperdiet libero, et aliquam turpis neque vitae elit. Maecenas semper varius iaculis. Cras non lorem quis quam bibendum eleifend in et libero. Curabitur at purus mauris. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus porta diam sed elit eleifend gravida.
-
-Nulla facilisi. Ut ultricies diam vel diam consectetur, vel porta augue molestie. Fusce interdum sapien et metus facilisis pellentesque. Nulla convallis sem at nunc vehicula facilisis. Nam ac rutrum purus. Nunc bibendum, dolor sit amet tempus ullamcorper, lorem leo tempor sem, id fringilla nunc augue scelerisque augue. Nullam sit amet rutrum nisl. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec sed mauris gravida eros vehicula sagittis at eget orci. Cras elementum, eros at accumsan bibendum, libero neque blandit purus, vitae vestibulum libero massa ac nibh. Integer at placerat nulla. Mauris eu eleifend orci. Aliquam consequat ligula vitae erat porta lobortis. Duis fermentum elit ac aliquet ornare.
-
-Mauris eget cursus tellus, eget sodales purus. Aliquam malesuada, augue id vulputate finibus, nisi ex bibendum nisl, sit amet laoreet quam urna a dolor. Nullam ultricies, sapien eu laoreet consequat, erat eros dignissim diam, ultrices sodales lectus mauris et leo. Morbi lacinia eu ante at tempus. Sed iaculis finibus magna malesuada efficitur. Donec faucibus erat sit amet elementum feugiat. Praesent a placerat nisi. Etiam lacinia gravida diam, et sollicitudin sapien tincidunt ut.
-
-Maecenas felis quam, tincidunt vitae venenatis scelerisque, viverra vitae odio. Phasellus enim neque, ultricies suscipit malesuada sit amet, vehicula sit amet purus. Nulla placerat sit amet dui vel tincidunt. Nam quis neque vel magna commodo egestas. Vestibulum sagittis rutrum lorem ut congue. Maecenas vel ultrices tellus. Donec efficitur, urna ac consequat iaculis, lorem felis pharetra eros, eget faucibus orci lectus sit amet arcu.
-
-Ut a tempus nisi. Nulla facilisi. Praesent vulputate maximus mi et dapibus. Sed sit amet libero ac augue hendrerit efficitur in a sapien. Mauris placerat velit sit amet tellus sollicitudin faucibus. Donec egestas a magna ac suscipit. Duis enim sapien, mollis sed egestas et, vestibulum vel leo.
-
-Proin quis dapibus dui. Donec eu tincidunt nunc. Vivamus eget purus consectetur, maximus ante vitae, tincidunt elit. Aenean mattis dolor a gravida aliquam. Praesent quis tellus id sem maximus vulputate nec sed nulla. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur metus nulla, volutpat volutpat est eu, hendrerit congue erat. Aliquam sollicitudin augue ante. Sed sollicitudin, magna eu consequat elementum, mi augue ullamcorper felis, molestie imperdiet erat metus iaculis est. Proin ac tortor nisi. Pellentesque quis nisi risus. Integer enim sapien, tincidunt quis tortor id, accumsan venenatis mi. Nulla facilisi.
-
-Cras pretium sit amet quam congue maximus. Morbi lacus libero, imperdiet commodo massa sed, scelerisque placerat libero. Cras nisl nisi, consectetur sed bibendum eu, venenatis at enim. Proin sodales justo at quam aliquam, a consectetur mi ornare. Donec porta ac est sit amet efficitur. Suspendisse vestibulum tortor id neque imperdiet, id lacinia risus vehicula. Phasellus ac eleifend purus. Mauris vel gravida ante. Aliquam vitae lobortis risus. Sed vehicula consectetur tincidunt. Nam et justo vitae purus molestie consequat. Pellentesque ipsum ex, convallis quis blandit non, gravida et urna. Donec diam ligula amet.
diff --git a/pkg/sentry/fsimpl/ext/assets/file.txt b/pkg/sentry/fsimpl/ext/assets/file.txt
deleted file mode 100644
index 980a0d5f1..000000000
--- a/pkg/sentry/fsimpl/ext/assets/file.txt
+++ /dev/null
@@ -1 +0,0 @@
-Hello World!
diff --git a/pkg/sentry/fsimpl/ext/assets/symlink.txt b/pkg/sentry/fsimpl/ext/assets/symlink.txt
deleted file mode 120000
index 4c330738c..000000000
--- a/pkg/sentry/fsimpl/ext/assets/symlink.txt
+++ /dev/null
@@ -1 +0,0 @@
-file.txt \ No newline at end of file
diff --git a/pkg/sentry/fsimpl/ext/assets/tiny.ext2 b/pkg/sentry/fsimpl/ext/assets/tiny.ext2
deleted file mode 100644
index 381ade9bf..000000000
--- a/pkg/sentry/fsimpl/ext/assets/tiny.ext2
+++ /dev/null
Binary files differ
diff --git a/pkg/sentry/fsimpl/ext/assets/tiny.ext3 b/pkg/sentry/fsimpl/ext/assets/tiny.ext3
deleted file mode 100644
index 0e97a324c..000000000
--- a/pkg/sentry/fsimpl/ext/assets/tiny.ext3
+++ /dev/null
Binary files differ
diff --git a/pkg/sentry/fsimpl/ext/assets/tiny.ext4 b/pkg/sentry/fsimpl/ext/assets/tiny.ext4
deleted file mode 100644
index a6859736d..000000000
--- a/pkg/sentry/fsimpl/ext/assets/tiny.ext4
+++ /dev/null
Binary files differ
diff --git a/pkg/sentry/fsimpl/ext/benchmark/BUILD b/pkg/sentry/fsimpl/ext/benchmark/BUILD
deleted file mode 100644
index bfc46dfa6..000000000
--- a/pkg/sentry/fsimpl/ext/benchmark/BUILD
+++ /dev/null
@@ -1,16 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_test(
- name = "benchmark_test",
- size = "small",
- srcs = ["benchmark_test.go"],
- deps = [
- "//pkg/sentry/context",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/fsimpl/ext",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/vfs",
- ],
-)
diff --git a/pkg/sentry/fsimpl/ext/benchmark/benchmark_test.go b/pkg/sentry/fsimpl/ext/benchmark/benchmark_test.go
deleted file mode 100644
index 10a8083a0..000000000
--- a/pkg/sentry/fsimpl/ext/benchmark/benchmark_test.go
+++ /dev/null
@@ -1,193 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// These benchmarks emulate memfs benchmarks. Ext4 images must be created
-// before this benchmark is run using the `make_deep_ext4.sh` script at
-// /tmp/image-{depth}.ext4 for all the depths tested below.
-package benchmark_test
-
-import (
- "fmt"
- "os"
- "runtime"
- "strings"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/ext"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
-)
-
-var depths = []int{1, 2, 3, 8, 64, 100}
-
-const filename = "file.txt"
-
-// setUp opens imagePath as an ext Filesystem and returns all necessary
-// elements required to run tests. If error is nil, it also returns a tear
-// down function which must be called after the test is run for clean up.
-func setUp(b *testing.B, imagePath string) (context.Context, *vfs.VirtualFilesystem, *vfs.VirtualDentry, func(), error) {
- f, err := os.Open(imagePath)
- if err != nil {
- return nil, nil, nil, nil, err
- }
-
- ctx := contexttest.Context(b)
- creds := auth.CredentialsFromContext(ctx)
-
- // Create VFS.
- vfsObj := vfs.New()
- vfsObj.MustRegisterFilesystemType("extfs", ext.FilesystemType{})
- mntns, err := vfsObj.NewMountNamespace(ctx, creds, imagePath, "extfs", &vfs.NewFilesystemOptions{InternalData: int(f.Fd())})
- if err != nil {
- f.Close()
- return nil, nil, nil, nil, err
- }
-
- root := mntns.Root()
-
- tearDown := func() {
- root.DecRef()
-
- if err := f.Close(); err != nil {
- b.Fatalf("tearDown failed: %v", err)
- }
- }
- return ctx, vfsObj, &root, tearDown, nil
-}
-
-// mount mounts extfs at the path operation passed. Returns a tear down
-// function which must be called after the test is run for clean up.
-func mount(b *testing.B, imagePath string, vfsfs *vfs.VirtualFilesystem, pop *vfs.PathOperation) func() {
- b.Helper()
-
- f, err := os.Open(imagePath)
- if err != nil {
- b.Fatalf("could not open image at %s: %v", imagePath, err)
- }
-
- ctx := contexttest.Context(b)
- creds := auth.CredentialsFromContext(ctx)
-
- if err := vfsfs.NewMount(ctx, creds, imagePath, pop, "extfs", &vfs.NewFilesystemOptions{InternalData: int(f.Fd())}); err != nil {
- b.Fatalf("failed to mount tmpfs submount: %v", err)
- }
- return func() {
- if err := f.Close(); err != nil {
- b.Fatalf("tearDown failed: %v", err)
- }
- }
-}
-
-// BenchmarkVFS2Ext4fsStat emulates BenchmarkVFS2MemfsStat.
-func BenchmarkVFS2Ext4fsStat(b *testing.B) {
- for _, depth := range depths {
- b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
- ctx, vfsfs, root, tearDown, err := setUp(b, fmt.Sprintf("/tmp/image-%d.ext4", depth))
- if err != nil {
- b.Fatalf("setUp failed: %v", err)
- }
- defer tearDown()
-
- creds := auth.CredentialsFromContext(ctx)
- var filePathBuilder strings.Builder
- filePathBuilder.WriteByte('/')
- for i := 1; i <= depth; i++ {
- filePathBuilder.WriteString(fmt.Sprintf("%d", i))
- filePathBuilder.WriteByte('/')
- }
- filePathBuilder.WriteString(filename)
- filePath := filePathBuilder.String()
-
- runtime.GC()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- stat, err := vfsfs.StatAt(ctx, creds, &vfs.PathOperation{
- Root: *root,
- Start: *root,
- Pathname: filePath,
- FollowFinalSymlink: true,
- }, &vfs.StatOptions{})
- if err != nil {
- b.Fatalf("stat(%q) failed: %v", filePath, err)
- }
- // Sanity check.
- if stat.Size > 0 {
- b.Fatalf("got wrong file size (%d)", stat.Size)
- }
- }
- })
- }
-}
-
-// BenchmarkVFS2ExtfsMountStat emulates BenchmarkVFS2MemfsMountStat.
-func BenchmarkVFS2ExtfsMountStat(b *testing.B) {
- for _, depth := range depths {
- b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
- // Create root extfs with depth 1 so we can mount extfs again at /1/.
- ctx, vfsfs, root, tearDown, err := setUp(b, fmt.Sprintf("/tmp/image-%d.ext4", 1))
- if err != nil {
- b.Fatalf("setUp failed: %v", err)
- }
- defer tearDown()
-
- creds := auth.CredentialsFromContext(ctx)
- mountPointName := "/1/"
- pop := vfs.PathOperation{
- Root: *root,
- Start: *root,
- Pathname: mountPointName,
- }
-
- // Save the mount point for later use.
- mountPoint, err := vfsfs.GetDentryAt(ctx, creds, &pop, &vfs.GetDentryOptions{})
- if err != nil {
- b.Fatalf("failed to walk to mount point: %v", err)
- }
- defer mountPoint.DecRef()
-
- // Create extfs submount.
- mountTearDown := mount(b, fmt.Sprintf("/tmp/image-%d.ext4", depth), vfsfs, &pop)
- defer mountTearDown()
-
- var filePathBuilder strings.Builder
- filePathBuilder.WriteString(mountPointName)
- for i := 1; i <= depth; i++ {
- filePathBuilder.WriteString(fmt.Sprintf("%d", i))
- filePathBuilder.WriteByte('/')
- }
- filePathBuilder.WriteString(filename)
- filePath := filePathBuilder.String()
-
- runtime.GC()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- stat, err := vfsfs.StatAt(ctx, creds, &vfs.PathOperation{
- Root: *root,
- Start: *root,
- Pathname: filePath,
- FollowFinalSymlink: true,
- }, &vfs.StatOptions{})
- if err != nil {
- b.Fatalf("stat(%q) failed: %v", filePath, err)
- }
- // Sanity check. touch(1) always creates files of size 0 (empty).
- if stat.Size > 0 {
- b.Fatalf("got wrong file size (%d)", stat.Size)
- }
- }
- })
- }
-}
diff --git a/pkg/sentry/fsimpl/ext/benchmark/make_deep_ext4.sh b/pkg/sentry/fsimpl/ext/benchmark/make_deep_ext4.sh
deleted file mode 100755
index d0910da1f..000000000
--- a/pkg/sentry/fsimpl/ext/benchmark/make_deep_ext4.sh
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# This script creates an ext4 image with $1 depth of directories and a file in
-# the inner most directory. The created file is at path /1/2/.../depth/file.txt.
-# The ext4 image is written to $2. The image is temporarily mounted at
-# /tmp/mountpoint. This script must be run with sudo privileges.
-
-# Usage:
-# sudo bash make_deep_ext4.sh {depth} {output path}
-
-# Check positional arguments.
-if [ "$#" -ne 2 ]; then
- echo "Usage: sudo bash make_deep_ext4.sh {depth} {output path}"
- exit 1
-fi
-
-# Make sure depth is a non-negative number.
-if ! [[ "$1" =~ ^[0-9]+$ ]]; then
- echo "Depth must be a non-negative number."
- exit 1
-fi
-
-# Create a 1 MB filesystem image at the requested output path.
-rm -f $2
-fallocate -l 1M $2
-if [ $? -ne 0 ]; then
- echo "fallocate failed"
- exit $?
-fi
-
-# Convert that blank into an ext4 image.
-mkfs.ext4 -j $2
-if [ $? -ne 0 ]; then
- echo "mkfs.ext4 failed"
- exit $?
-fi
-
-# Mount the image.
-MOUNTPOINT=/tmp/mountpoint
-mkdir -p $MOUNTPOINT
-mount -o loop $2 $MOUNTPOINT
-if [ $? -ne 0 ]; then
- echo "mount failed"
- exit $?
-fi
-
-# Create nested directories and the file.
-if [ "$1" -eq 0 ]; then
- FILEPATH=$MOUNTPOINT/file.txt
-else
- FILEPATH=$MOUNTPOINT/$(seq -s '/' 1 $1)/file.txt
-fi
-mkdir -p $(dirname $FILEPATH) || exit
-touch $FILEPATH
-
-# Clean up.
-umount $MOUNTPOINT
-rm -rf $MOUNTPOINT
diff --git a/pkg/sentry/fsimpl/ext/block_map_file.go b/pkg/sentry/fsimpl/ext/block_map_file.go
deleted file mode 100644
index cea89bcd9..000000000
--- a/pkg/sentry/fsimpl/ext/block_map_file.go
+++ /dev/null
@@ -1,200 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ext
-
-import (
- "io"
- "math"
-
- "gvisor.dev/gvisor/pkg/binary"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-const (
- // numDirectBlks is the number of direct blocks in ext block map inodes.
- numDirectBlks = 12
-)
-
-// blockMapFile is a type of regular file which uses direct/indirect block
-// addressing to store file data. This was deprecated in ext4.
-type blockMapFile struct {
- regFile regularFile
-
- // directBlks are the direct blocks numbers. The physical blocks pointed by
- // these holds file data. Contains file blocks 0 to 11.
- directBlks [numDirectBlks]uint32
-
- // indirectBlk is the physical block which contains (blkSize/4) direct block
- // numbers (as uint32 integers).
- indirectBlk uint32
-
- // doubleIndirectBlk is the physical block which contains (blkSize/4) indirect
- // block numbers (as uint32 integers).
- doubleIndirectBlk uint32
-
- // tripleIndirectBlk is the physical block which contains (blkSize/4) doubly
- // indirect block numbers (as uint32 integers).
- tripleIndirectBlk uint32
-
- // coverage at (i)th index indicates the amount of file data a node at
- // height (i) covers. Height 0 is the direct block.
- coverage [4]uint64
-}
-
-// Compiles only if blockMapFile implements io.ReaderAt.
-var _ io.ReaderAt = (*blockMapFile)(nil)
-
-// newBlockMapFile is the blockMapFile constructor. It initializes the file to
-// physical blocks map with (at most) the first 12 (direct) blocks.
-func newBlockMapFile(regFile regularFile) (*blockMapFile, error) {
- file := &blockMapFile{regFile: regFile}
- file.regFile.impl = file
-
- for i := uint(0); i < 4; i++ {
- file.coverage[i] = getCoverage(regFile.inode.blkSize, i)
- }
-
- blkMap := regFile.inode.diskInode.Data()
- binary.Unmarshal(blkMap[:numDirectBlks*4], binary.LittleEndian, &file.directBlks)
- binary.Unmarshal(blkMap[numDirectBlks*4:(numDirectBlks+1)*4], binary.LittleEndian, &file.indirectBlk)
- binary.Unmarshal(blkMap[(numDirectBlks+1)*4:(numDirectBlks+2)*4], binary.LittleEndian, &file.doubleIndirectBlk)
- binary.Unmarshal(blkMap[(numDirectBlks+2)*4:(numDirectBlks+3)*4], binary.LittleEndian, &file.tripleIndirectBlk)
- return file, nil
-}
-
-// ReadAt implements io.ReaderAt.ReadAt.
-func (f *blockMapFile) ReadAt(dst []byte, off int64) (int, error) {
- if len(dst) == 0 {
- return 0, nil
- }
-
- if off < 0 {
- return 0, syserror.EINVAL
- }
-
- offset := uint64(off)
- size := f.regFile.inode.diskInode.Size()
- if offset >= size {
- return 0, io.EOF
- }
-
- // dirBlksEnd is the file offset until which direct blocks cover file data.
- // Direct blocks cover 0 <= file offset < dirBlksEnd.
- dirBlksEnd := numDirectBlks * f.coverage[0]
-
- // indirBlkEnd is the file offset until which the indirect block covers file
- // data. The indirect block covers dirBlksEnd <= file offset < indirBlkEnd.
- indirBlkEnd := dirBlksEnd + f.coverage[1]
-
- // doubIndirBlkEnd is the file offset until which the double indirect block
- // covers file data. The double indirect block covers the range
- // indirBlkEnd <= file offset < doubIndirBlkEnd.
- doubIndirBlkEnd := indirBlkEnd + f.coverage[2]
-
- read := 0
- toRead := len(dst)
- if uint64(toRead)+offset > size {
- toRead = int(size - offset)
- }
- for read < toRead {
- var err error
- var curR int
-
- // Figure out which block to delegate the read to.
- switch {
- case offset < dirBlksEnd:
- // Direct block.
- curR, err = f.read(f.directBlks[offset/f.regFile.inode.blkSize], offset%f.regFile.inode.blkSize, 0, dst[read:])
- case offset < indirBlkEnd:
- // Indirect block.
- curR, err = f.read(f.indirectBlk, offset-dirBlksEnd, 1, dst[read:])
- case offset < doubIndirBlkEnd:
- // Doubly indirect block.
- curR, err = f.read(f.doubleIndirectBlk, offset-indirBlkEnd, 2, dst[read:])
- default:
- // Triply indirect block.
- curR, err = f.read(f.tripleIndirectBlk, offset-doubIndirBlkEnd, 3, dst[read:])
- }
-
- read += curR
- offset += uint64(curR)
- if err != nil {
- return read, err
- }
- }
-
- if read < len(dst) {
- return read, io.EOF
- }
- return read, nil
-}
-
-// read is the recursive step of the ReadAt function. It relies on knowing the
-// current node's location on disk (curPhyBlk) and its height in the block map
-// tree. A height of 0 shows that the current node is actually holding file
-// data. relFileOff tells the offset from which we need to start to reading
-// under the current node. It is completely relative to the current node.
-func (f *blockMapFile) read(curPhyBlk uint32, relFileOff uint64, height uint, dst []byte) (int, error) {
- curPhyBlkOff := int64(curPhyBlk) * int64(f.regFile.inode.blkSize)
- if height == 0 {
- toRead := int(f.regFile.inode.blkSize - relFileOff)
- if len(dst) < toRead {
- toRead = len(dst)
- }
-
- n, _ := f.regFile.inode.dev.ReadAt(dst[:toRead], curPhyBlkOff+int64(relFileOff))
- if n < toRead {
- return n, syserror.EIO
- }
- return n, nil
- }
-
- childCov := f.coverage[height-1]
- startIdx := relFileOff / childCov
- endIdx := f.regFile.inode.blkSize / 4 // This is exclusive.
- wantEndIdx := (relFileOff + uint64(len(dst))) / childCov
- wantEndIdx++ // Make this exclusive.
- if wantEndIdx < endIdx {
- endIdx = wantEndIdx
- }
-
- read := 0
- curChildOff := relFileOff % childCov
- for i := startIdx; i < endIdx; i++ {
- var childPhyBlk uint32
- err := readFromDisk(f.regFile.inode.dev, curPhyBlkOff+int64(i*4), &childPhyBlk)
- if err != nil {
- return read, err
- }
-
- n, err := f.read(childPhyBlk, curChildOff, height-1, dst[read:])
- read += n
- if err != nil {
- return read, err
- }
-
- curChildOff = 0
- }
-
- return read, nil
-}
-
-// getCoverage returns the number of bytes a node at the given height covers.
-// Height 0 is the file data block itself. Height 1 is the indirect block.
-//
-// Formula: blkSize * ((blkSize / 4)^height)
-func getCoverage(blkSize uint64, height uint) uint64 {
- return blkSize * uint64(math.Pow(float64(blkSize/4), float64(height)))
-}
diff --git a/pkg/sentry/fsimpl/ext/block_map_test.go b/pkg/sentry/fsimpl/ext/block_map_test.go
deleted file mode 100644
index 213aa3919..000000000
--- a/pkg/sentry/fsimpl/ext/block_map_test.go
+++ /dev/null
@@ -1,157 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ext
-
-import (
- "bytes"
- "math/rand"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "gvisor.dev/gvisor/pkg/binary"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/ext/disklayout"
-)
-
-// These consts are for mocking the block map tree.
-const (
- mockBMBlkSize = uint32(16)
- mockBMDiskSize = 2500
-)
-
-// TestBlockMapReader stress tests block map reader functionality. It performs
-// random length reads from all possible positions in the block map structure.
-func TestBlockMapReader(t *testing.T) {
- mockBMFile, want := blockMapSetUp(t)
- n := len(want)
-
- for from := 0; from < n; from++ {
- got := make([]byte, n-from)
-
- if read, err := mockBMFile.ReadAt(got, int64(from)); err != nil {
- t.Fatalf("file read operation from offset %d to %d only read %d bytes: %v", from, n, read, err)
- }
-
- if diff := cmp.Diff(got, want[from:]); diff != "" {
- t.Fatalf("file data from offset %d to %d mismatched (-want +got):\n%s", from, n, diff)
- }
- }
-}
-
-// blkNumGen is a number generator which gives block numbers for building the
-// block map file on disk. It gives unique numbers in a random order which
-// facilitates in creating an extremely fragmented filesystem.
-type blkNumGen struct {
- nums []uint32
-}
-
-// newBlkNumGen is the blkNumGen constructor.
-func newBlkNumGen() *blkNumGen {
- blkNums := &blkNumGen{}
- lim := mockBMDiskSize / mockBMBlkSize
- blkNums.nums = make([]uint32, lim)
- for i := uint32(0); i < lim; i++ {
- blkNums.nums[i] = i
- }
-
- rand.Shuffle(int(lim), func(i, j int) {
- blkNums.nums[i], blkNums.nums[j] = blkNums.nums[j], blkNums.nums[i]
- })
- return blkNums
-}
-
-// next returns the next random block number.
-func (n *blkNumGen) next() uint32 {
- ret := n.nums[0]
- n.nums = n.nums[1:]
- return ret
-}
-
-// blockMapSetUp creates a mock disk and a block map file. It initializes the
-// block map file with 12 direct block, 1 indirect block, 1 double indirect
-// block and 1 triple indirect block (basically fill it till the rim). It
-// initializes the disk to reflect the inode. Also returns the file data that
-// the inode covers and that is written to disk.
-func blockMapSetUp(t *testing.T) (*blockMapFile, []byte) {
- mockDisk := make([]byte, mockBMDiskSize)
- regFile := regularFile{
- inode: inode{
- diskInode: &disklayout.InodeNew{
- InodeOld: disklayout.InodeOld{
- SizeLo: getMockBMFileFize(),
- },
- },
- dev: bytes.NewReader(mockDisk),
- blkSize: uint64(mockBMBlkSize),
- },
- }
-
- var fileData []byte
- blkNums := newBlkNumGen()
- var data []byte
-
- // Write the direct blocks.
- for i := 0; i < numDirectBlks; i++ {
- curBlkNum := blkNums.next()
- data = binary.Marshal(data, binary.LittleEndian, curBlkNum)
- fileData = append(fileData, writeFileDataToBlock(mockDisk, curBlkNum, 0, blkNums)...)
- }
-
- // Write to indirect block.
- indirectBlk := blkNums.next()
- data = binary.Marshal(data, binary.LittleEndian, indirectBlk)
- fileData = append(fileData, writeFileDataToBlock(mockDisk, indirectBlk, 1, blkNums)...)
-
- // Write to indirect block.
- doublyIndirectBlk := blkNums.next()
- data = binary.Marshal(data, binary.LittleEndian, doublyIndirectBlk)
- fileData = append(fileData, writeFileDataToBlock(mockDisk, doublyIndirectBlk, 2, blkNums)...)
-
- // Write to indirect block.
- triplyIndirectBlk := blkNums.next()
- data = binary.Marshal(data, binary.LittleEndian, triplyIndirectBlk)
- fileData = append(fileData, writeFileDataToBlock(mockDisk, triplyIndirectBlk, 3, blkNums)...)
-
- copy(regFile.inode.diskInode.Data(), data)
-
- mockFile, err := newBlockMapFile(regFile)
- if err != nil {
- t.Fatalf("newBlockMapFile failed: %v", err)
- }
- return mockFile, fileData
-}
-
-// writeFileDataToBlock writes random bytes to the block on disk.
-func writeFileDataToBlock(disk []byte, blkNum uint32, height uint, blkNums *blkNumGen) []byte {
- if height == 0 {
- start := blkNum * mockBMBlkSize
- end := start + mockBMBlkSize
- rand.Read(disk[start:end])
- return disk[start:end]
- }
-
- var fileData []byte
- for off := blkNum * mockBMBlkSize; off < (blkNum+1)*mockBMBlkSize; off += 4 {
- curBlkNum := blkNums.next()
- copy(disk[off:off+4], binary.Marshal(nil, binary.LittleEndian, curBlkNum))
- fileData = append(fileData, writeFileDataToBlock(disk, curBlkNum, height-1, blkNums)...)
- }
- return fileData
-}
-
-// getMockBMFileFize gets the size of the mock block map file which is used for
-// testing.
-func getMockBMFileFize() uint32 {
- return uint32(numDirectBlks*getCoverage(uint64(mockBMBlkSize), 0) + getCoverage(uint64(mockBMBlkSize), 1) + getCoverage(uint64(mockBMBlkSize), 2) + getCoverage(uint64(mockBMBlkSize), 3))
-}
diff --git a/pkg/sentry/fsimpl/ext/dentry.go b/pkg/sentry/fsimpl/ext/dentry.go
deleted file mode 100644
index 054fb42b6..000000000
--- a/pkg/sentry/fsimpl/ext/dentry.go
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ext
-
-import (
- "gvisor.dev/gvisor/pkg/sentry/vfs"
-)
-
-// dentry implements vfs.DentryImpl.
-type dentry struct {
- vfsd vfs.Dentry
-
- // inode is the inode represented by this dentry. Multiple Dentries may
- // share a single non-directory Inode (with hard links). inode is
- // immutable.
- inode *inode
-}
-
-// Compiles only if dentry implements vfs.DentryImpl.
-var _ vfs.DentryImpl = (*dentry)(nil)
-
-// newDentry is the dentry constructor.
-func newDentry(in *inode) *dentry {
- d := &dentry{
- inode: in,
- }
- d.vfsd.Init(d)
- return d
-}
-
-// IncRef implements vfs.DentryImpl.IncRef.
-func (d *dentry) IncRef(vfsfs *vfs.Filesystem) {
- d.inode.incRef()
-}
-
-// TryIncRef implements vfs.DentryImpl.TryIncRef.
-func (d *dentry) TryIncRef(vfsfs *vfs.Filesystem) bool {
- return d.inode.tryIncRef()
-}
-
-// DecRef implements vfs.DentryImpl.DecRef.
-func (d *dentry) DecRef(vfsfs *vfs.Filesystem) {
- d.inode.decRef(vfsfs.Impl().(*filesystem))
-}
diff --git a/pkg/sentry/fsimpl/ext/directory.go b/pkg/sentry/fsimpl/ext/directory.go
deleted file mode 100644
index b51f3e18d..000000000
--- a/pkg/sentry/fsimpl/ext/directory.go
+++ /dev/null
@@ -1,308 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ext
-
-import (
- "sync"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/binary"
- "gvisor.dev/gvisor/pkg/log"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/ext/disklayout"
- "gvisor.dev/gvisor/pkg/sentry/memmap"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-// directory represents a directory inode. It holds the childList in memory.
-type directory struct {
- inode inode
-
- // mu serializes the changes to childList.
- // Lock Order (outermost locks must be taken first):
- // directory.mu
- // filesystem.mu
- mu sync.Mutex
-
- // childList is a list containing (1) child dirents and (2) fake dirents
- // (with diskDirent == nil) that represent the iteration position of
- // directoryFDs. childList is used to support directoryFD.IterDirents()
- // efficiently. childList is protected by mu.
- childList direntList
-
- // childMap maps the child's filename to the dirent structure stored in
- // childList. This adds some data replication but helps in faster path
- // traversal. For consistency, key == childMap[key].diskDirent.FileName().
- // Immutable.
- childMap map[string]*dirent
-}
-
-// newDirectroy is the directory constructor.
-func newDirectroy(inode inode, newDirent bool) (*directory, error) {
- file := &directory{inode: inode, childMap: make(map[string]*dirent)}
- file.inode.impl = file
-
- // Initialize childList by reading dirents from the underlying file.
- if inode.diskInode.Flags().Index {
- // TODO(b/134676337): Support hash tree directories. Currently only the '.'
- // and '..' entries are read in.
-
- // Users cannot navigate this hash tree directory yet.
- log.Warningf("hash tree directory being used which is unsupported")
- return file, nil
- }
-
- // The dirents are organized in a linear array in the file data.
- // Extract the file data and decode the dirents.
- regFile, err := newRegularFile(inode)
- if err != nil {
- return nil, err
- }
-
- // buf is used as scratch space for reading in dirents from disk and
- // unmarshalling them into dirent structs.
- buf := make([]byte, disklayout.DirentSize)
- size := inode.diskInode.Size()
- for off, inc := uint64(0), uint64(0); off < size; off += inc {
- toRead := size - off
- if toRead > disklayout.DirentSize {
- toRead = disklayout.DirentSize
- }
- if n, err := regFile.impl.ReadAt(buf[:toRead], int64(off)); uint64(n) < toRead {
- return nil, err
- }
-
- var curDirent dirent
- if newDirent {
- curDirent.diskDirent = &disklayout.DirentNew{}
- } else {
- curDirent.diskDirent = &disklayout.DirentOld{}
- }
- binary.Unmarshal(buf, binary.LittleEndian, curDirent.diskDirent)
-
- if curDirent.diskDirent.Inode() != 0 && len(curDirent.diskDirent.FileName()) != 0 {
- // Inode number and name length fields being set to 0 is used to indicate
- // an unused dirent.
- file.childList.PushBack(&curDirent)
- file.childMap[curDirent.diskDirent.FileName()] = &curDirent
- }
-
- // The next dirent is placed exactly after this dirent record on disk.
- inc = uint64(curDirent.diskDirent.RecordSize())
- }
-
- return file, nil
-}
-
-func (i *inode) isDir() bool {
- _, ok := i.impl.(*directory)
- return ok
-}
-
-// dirent is the directory.childList node.
-type dirent struct {
- diskDirent disklayout.Dirent
-
- // direntEntry links dirents into their parent directory.childList.
- direntEntry
-}
-
-// directoryFD represents a directory file description. It implements
-// vfs.FileDescriptionImpl.
-type directoryFD struct {
- fileDescription
- vfs.DirectoryFileDescriptionDefaultImpl
-
- // Protected by directory.mu.
- iter *dirent
- off int64
-}
-
-// Compiles only if directoryFD implements vfs.FileDescriptionImpl.
-var _ vfs.FileDescriptionImpl = (*directoryFD)(nil)
-
-// Release implements vfs.FileDescriptionImpl.Release.
-func (fd *directoryFD) Release() {
- if fd.iter == nil {
- return
- }
-
- dir := fd.inode().impl.(*directory)
- dir.mu.Lock()
- dir.childList.Remove(fd.iter)
- dir.mu.Unlock()
- fd.iter = nil
-}
-
-// IterDirents implements vfs.FileDescriptionImpl.IterDirents.
-func (fd *directoryFD) IterDirents(ctx context.Context, cb vfs.IterDirentsCallback) error {
- extfs := fd.filesystem()
- dir := fd.inode().impl.(*directory)
-
- dir.mu.Lock()
- defer dir.mu.Unlock()
-
- // Ensure that fd.iter exists and is not linked into dir.childList.
- var child *dirent
- if fd.iter == nil {
- // Start iteration at the beginning of dir.
- child = dir.childList.Front()
- fd.iter = &dirent{}
- } else {
- // Continue iteration from where we left off.
- child = fd.iter.Next()
- dir.childList.Remove(fd.iter)
- }
- for ; child != nil; child = child.Next() {
- // Skip other directoryFD iterators.
- if child.diskDirent != nil {
- childType, ok := child.diskDirent.FileType()
- if !ok {
- // We will need to read the inode off disk. Do not increment
- // ref count here because this inode is not being added to the
- // dentry tree.
- extfs.mu.Lock()
- childInode, err := extfs.getOrCreateInodeLocked(child.diskDirent.Inode())
- extfs.mu.Unlock()
- if err != nil {
- // Usage of the file description after the error is
- // undefined. This implementation would continue reading
- // from the next dirent.
- fd.off++
- dir.childList.InsertAfter(child, fd.iter)
- return err
- }
- childType = fs.ToInodeType(childInode.diskInode.Mode().FileType())
- }
-
- if !cb.Handle(vfs.Dirent{
- Name: child.diskDirent.FileName(),
- Type: fs.ToDirentType(childType),
- Ino: uint64(child.diskDirent.Inode()),
- Off: fd.off,
- }) {
- dir.childList.InsertBefore(child, fd.iter)
- return nil
- }
- fd.off++
- }
- }
- dir.childList.PushBack(fd.iter)
- return nil
-}
-
-// Seek implements vfs.FileDescriptionImpl.Seek.
-func (fd *directoryFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
- if whence != linux.SEEK_SET && whence != linux.SEEK_CUR {
- return 0, syserror.EINVAL
- }
-
- dir := fd.inode().impl.(*directory)
-
- dir.mu.Lock()
- defer dir.mu.Unlock()
-
- // Find resulting offset.
- if whence == linux.SEEK_CUR {
- offset += fd.off
- }
-
- if offset < 0 {
- // lseek(2) specifies that EINVAL should be returned if the resulting offset
- // is negative.
- return 0, syserror.EINVAL
- }
-
- n := int64(len(dir.childMap))
- realWantOff := offset
- if realWantOff > n {
- realWantOff = n
- }
- realCurOff := fd.off
- if realCurOff > n {
- realCurOff = n
- }
-
- // Ensure that fd.iter exists and is linked into dir.childList so we can
- // intelligently seek from the optimal position.
- if fd.iter == nil {
- fd.iter = &dirent{}
- dir.childList.PushFront(fd.iter)
- }
-
- // Guess that iterating from the current position is optimal.
- child := fd.iter
- diff := realWantOff - realCurOff // Shows direction and magnitude of travel.
-
- // See if starting from the beginning or end is better.
- abDiff := diff
- if diff < 0 {
- abDiff = -diff
- }
- if abDiff > realWantOff {
- // Starting from the beginning is best.
- child = dir.childList.Front()
- diff = realWantOff
- } else if abDiff > (n - realWantOff) {
- // Starting from the end is best.
- child = dir.childList.Back()
- // (n - 1) because the last non-nil dirent represents the (n-1)th offset.
- diff = realWantOff - (n - 1)
- }
-
- for child != nil {
- // Skip other directoryFD iterators.
- if child.diskDirent != nil {
- if diff == 0 {
- if child != fd.iter {
- dir.childList.Remove(fd.iter)
- dir.childList.InsertBefore(child, fd.iter)
- }
-
- fd.off = offset
- return offset, nil
- }
-
- if diff < 0 {
- diff++
- child = child.Prev()
- } else {
- diff--
- child = child.Next()
- }
- continue
- }
-
- if diff < 0 {
- child = child.Prev()
- } else {
- child = child.Next()
- }
- }
-
- // Reaching here indicates that the offset is beyond the end of the childList.
- dir.childList.Remove(fd.iter)
- dir.childList.PushBack(fd.iter)
- fd.off = offset
- return offset, nil
-}
-
-// IterDirents implements vfs.FileDescriptionImpl.IterDirents.
-func (fd *directoryFD) ConfigureMMap(ctx context.Context, opts memmap.MMapOpts) error {
- // mmap(2) specifies that EACCESS should be returned for non-regular file fds.
- return syserror.EACCES
-}
diff --git a/pkg/sentry/fsimpl/ext/disklayout/BUILD b/pkg/sentry/fsimpl/ext/disklayout/BUILD
deleted file mode 100644
index 2d50e30aa..000000000
--- a/pkg/sentry/fsimpl/ext/disklayout/BUILD
+++ /dev/null
@@ -1,50 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "disklayout",
- srcs = [
- "block_group.go",
- "block_group_32.go",
- "block_group_64.go",
- "dirent.go",
- "dirent_new.go",
- "dirent_old.go",
- "disklayout.go",
- "extent.go",
- "inode.go",
- "inode_new.go",
- "inode_old.go",
- "superblock.go",
- "superblock_32.go",
- "superblock_64.go",
- "superblock_old.go",
- "test_utils.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fsimpl/ext/disklayout",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/binary",
- "//pkg/sentry/fs",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/kernel/time",
- ],
-)
-
-go_test(
- name = "disklayout_test",
- size = "small",
- srcs = [
- "block_group_test.go",
- "dirent_test.go",
- "extent_test.go",
- "inode_test.go",
- "superblock_test.go",
- ],
- embed = [":disklayout"],
- deps = ["//pkg/sentry/kernel/time"],
-)
diff --git a/pkg/sentry/fsimpl/ext/disklayout/block_group.go b/pkg/sentry/fsimpl/ext/disklayout/block_group.go
deleted file mode 100644
index ad6f4fef8..000000000
--- a/pkg/sentry/fsimpl/ext/disklayout/block_group.go
+++ /dev/null
@@ -1,137 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package disklayout
-
-// BlockGroup represents a Linux ext block group descriptor. An ext file system
-// is split into a series of block groups. This provides an access layer to
-// information needed to access and use a block group.
-//
-// Location:
-// - The block group descriptor table is always placed in the blocks
-// immediately after the block containing the superblock.
-// - The 1st block group descriptor in the original table is in the
-// (sb.FirstDataBlock() + 1)th block.
-// - See SuperBlock docs to see where the block group descriptor table is
-// replicated.
-// - sb.BgDescSize() must be used as the block group descriptor entry size
-// while reading the table from disk.
-//
-// See https://www.kernel.org/doc/html/latest/filesystems/ext4/globals.html#block-group-descriptors.
-type BlockGroup interface {
- // InodeTable returns the absolute block number of the block containing the
- // inode table. This points to an array of Inode structs. Inode tables are
- // statically allocated at mkfs time. The superblock records the number of
- // inodes per group (length of this table) and the size of each inode struct.
- InodeTable() uint64
-
- // BlockBitmap returns the absolute block number of the block containing the
- // block bitmap. This bitmap tracks the usage of data blocks within this block
- // group and has its own checksum.
- BlockBitmap() uint64
-
- // InodeBitmap returns the absolute block number of the block containing the
- // inode bitmap. This bitmap tracks the usage of this group's inode table
- // entries and has its own checksum.
- InodeBitmap() uint64
-
- // ExclusionBitmap returns the absolute block number of the snapshot exclusion
- // bitmap.
- ExclusionBitmap() uint64
-
- // FreeBlocksCount returns the number of free blocks in the group.
- FreeBlocksCount() uint32
-
- // FreeInodesCount returns the number of free inodes in the group.
- FreeInodesCount() uint32
-
- // DirectoryCount returns the number of inodes that represent directories
- // under this block group.
- DirectoryCount() uint32
-
- // UnusedInodeCount returns the number of unused inodes beyond the last used
- // inode in this group's inode table. As a result, we needn’t scan past the
- // (InodesPerGroup - UnusedInodeCount())th entry in the inode table.
- UnusedInodeCount() uint32
-
- // BlockBitmapChecksum returns the block bitmap checksum. This is calculated
- // using crc32c(FS UUID + group number + entire bitmap).
- BlockBitmapChecksum() uint32
-
- // InodeBitmapChecksum returns the inode bitmap checksum. This is calculated
- // using crc32c(FS UUID + group number + entire bitmap).
- InodeBitmapChecksum() uint32
-
- // Checksum returns this block group's checksum.
- //
- // If SbMetadataCsum feature is set:
- // - checksum is crc32c(FS UUID + group number + group descriptor
- // structure) & 0xFFFF.
- //
- // If SbGdtCsum feature is set:
- // - checksum is crc16(FS UUID + group number + group descriptor
- // structure).
- //
- // SbMetadataCsum and SbGdtCsum should not be both set.
- // If they are, Linux warns and asks to run fsck.
- Checksum() uint16
-
- // Flags returns BGFlags which represents the block group flags.
- Flags() BGFlags
-}
-
-// These are the different block group flags.
-const (
- // BgInodeUninit indicates that inode table and bitmap are not initialized.
- BgInodeUninit uint16 = 0x1
-
- // BgBlockUninit indicates that block bitmap is not initialized.
- BgBlockUninit uint16 = 0x2
-
- // BgInodeZeroed indicates that inode table is zeroed.
- BgInodeZeroed uint16 = 0x4
-)
-
-// BGFlags represents all the different combinations of block group flags.
-type BGFlags struct {
- InodeUninit bool
- BlockUninit bool
- InodeZeroed bool
-}
-
-// ToInt converts a BGFlags struct back to its 16-bit representation.
-func (f BGFlags) ToInt() uint16 {
- var res uint16
-
- if f.InodeUninit {
- res |= BgInodeUninit
- }
- if f.BlockUninit {
- res |= BgBlockUninit
- }
- if f.InodeZeroed {
- res |= BgInodeZeroed
- }
-
- return res
-}
-
-// BGFlagsFromInt converts the 16-bit flag representation to a BGFlags struct.
-func BGFlagsFromInt(flags uint16) BGFlags {
- return BGFlags{
- InodeUninit: flags&BgInodeUninit > 0,
- BlockUninit: flags&BgBlockUninit > 0,
- InodeZeroed: flags&BgInodeZeroed > 0,
- }
-}
diff --git a/pkg/sentry/fsimpl/ext/disklayout/block_group_32.go b/pkg/sentry/fsimpl/ext/disklayout/block_group_32.go
deleted file mode 100644
index 3e16c76db..000000000
--- a/pkg/sentry/fsimpl/ext/disklayout/block_group_32.go
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package disklayout
-
-// BlockGroup32Bit emulates the first half of struct ext4_group_desc in
-// fs/ext4/ext4.h. It is the block group descriptor struct for ext2, ext3 and
-// 32-bit ext4 filesystems. It implements BlockGroup interface.
-type BlockGroup32Bit struct {
- BlockBitmapLo uint32
- InodeBitmapLo uint32
- InodeTableLo uint32
- FreeBlocksCountLo uint16
- FreeInodesCountLo uint16
- UsedDirsCountLo uint16
- FlagsRaw uint16
- ExcludeBitmapLo uint32
- BlockBitmapChecksumLo uint16
- InodeBitmapChecksumLo uint16
- ItableUnusedLo uint16
- ChecksumRaw uint16
-}
-
-// Compiles only if BlockGroup32Bit implements BlockGroup.
-var _ BlockGroup = (*BlockGroup32Bit)(nil)
-
-// InodeTable implements BlockGroup.InodeTable.
-func (bg *BlockGroup32Bit) InodeTable() uint64 { return uint64(bg.InodeTableLo) }
-
-// BlockBitmap implements BlockGroup.BlockBitmap.
-func (bg *BlockGroup32Bit) BlockBitmap() uint64 { return uint64(bg.BlockBitmapLo) }
-
-// InodeBitmap implements BlockGroup.InodeBitmap.
-func (bg *BlockGroup32Bit) InodeBitmap() uint64 { return uint64(bg.InodeBitmapLo) }
-
-// ExclusionBitmap implements BlockGroup.ExclusionBitmap.
-func (bg *BlockGroup32Bit) ExclusionBitmap() uint64 { return uint64(bg.ExcludeBitmapLo) }
-
-// FreeBlocksCount implements BlockGroup.FreeBlocksCount.
-func (bg *BlockGroup32Bit) FreeBlocksCount() uint32 { return uint32(bg.FreeBlocksCountLo) }
-
-// FreeInodesCount implements BlockGroup.FreeInodesCount.
-func (bg *BlockGroup32Bit) FreeInodesCount() uint32 { return uint32(bg.FreeInodesCountLo) }
-
-// DirectoryCount implements BlockGroup.DirectoryCount.
-func (bg *BlockGroup32Bit) DirectoryCount() uint32 { return uint32(bg.UsedDirsCountLo) }
-
-// UnusedInodeCount implements BlockGroup.UnusedInodeCount.
-func (bg *BlockGroup32Bit) UnusedInodeCount() uint32 { return uint32(bg.ItableUnusedLo) }
-
-// BlockBitmapChecksum implements BlockGroup.BlockBitmapChecksum.
-func (bg *BlockGroup32Bit) BlockBitmapChecksum() uint32 { return uint32(bg.BlockBitmapChecksumLo) }
-
-// InodeBitmapChecksum implements BlockGroup.InodeBitmapChecksum.
-func (bg *BlockGroup32Bit) InodeBitmapChecksum() uint32 { return uint32(bg.InodeBitmapChecksumLo) }
-
-// Checksum implements BlockGroup.Checksum.
-func (bg *BlockGroup32Bit) Checksum() uint16 { return bg.ChecksumRaw }
-
-// Flags implements BlockGroup.Flags.
-func (bg *BlockGroup32Bit) Flags() BGFlags { return BGFlagsFromInt(bg.FlagsRaw) }
diff --git a/pkg/sentry/fsimpl/ext/disklayout/block_group_64.go b/pkg/sentry/fsimpl/ext/disklayout/block_group_64.go
deleted file mode 100644
index 9a809197a..000000000
--- a/pkg/sentry/fsimpl/ext/disklayout/block_group_64.go
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package disklayout
-
-// BlockGroup64Bit emulates struct ext4_group_desc in fs/ext4/ext4.h.
-// It is the block group descriptor struct for 64-bit ext4 filesystems.
-// It implements BlockGroup interface. It is an extension of the 32-bit
-// version of BlockGroup.
-type BlockGroup64Bit struct {
- // We embed the 32-bit struct here because 64-bit version is just an extension
- // of the 32-bit version.
- BlockGroup32Bit
-
- // 64-bit specific fields.
- BlockBitmapHi uint32
- InodeBitmapHi uint32
- InodeTableHi uint32
- FreeBlocksCountHi uint16
- FreeInodesCountHi uint16
- UsedDirsCountHi uint16
- ItableUnusedHi uint16
- ExcludeBitmapHi uint32
- BlockBitmapChecksumHi uint16
- InodeBitmapChecksumHi uint16
- _ uint32 // Padding to 64 bytes.
-}
-
-// Compiles only if BlockGroup64Bit implements BlockGroup.
-var _ BlockGroup = (*BlockGroup64Bit)(nil)
-
-// Methods to override. Checksum() and Flags() are not overridden.
-
-// InodeTable implements BlockGroup.InodeTable.
-func (bg *BlockGroup64Bit) InodeTable() uint64 {
- return (uint64(bg.InodeTableHi) << 32) | uint64(bg.InodeTableLo)
-}
-
-// BlockBitmap implements BlockGroup.BlockBitmap.
-func (bg *BlockGroup64Bit) BlockBitmap() uint64 {
- return (uint64(bg.BlockBitmapHi) << 32) | uint64(bg.BlockBitmapLo)
-}
-
-// InodeBitmap implements BlockGroup.InodeBitmap.
-func (bg *BlockGroup64Bit) InodeBitmap() uint64 {
- return (uint64(bg.InodeBitmapHi) << 32) | uint64(bg.InodeBitmapLo)
-}
-
-// ExclusionBitmap implements BlockGroup.ExclusionBitmap.
-func (bg *BlockGroup64Bit) ExclusionBitmap() uint64 {
- return (uint64(bg.ExcludeBitmapHi) << 32) | uint64(bg.ExcludeBitmapLo)
-}
-
-// FreeBlocksCount implements BlockGroup.FreeBlocksCount.
-func (bg *BlockGroup64Bit) FreeBlocksCount() uint32 {
- return (uint32(bg.FreeBlocksCountHi) << 16) | uint32(bg.FreeBlocksCountLo)
-}
-
-// FreeInodesCount implements BlockGroup.FreeInodesCount.
-func (bg *BlockGroup64Bit) FreeInodesCount() uint32 {
- return (uint32(bg.FreeInodesCountHi) << 16) | uint32(bg.FreeInodesCountLo)
-}
-
-// DirectoryCount implements BlockGroup.DirectoryCount.
-func (bg *BlockGroup64Bit) DirectoryCount() uint32 {
- return (uint32(bg.UsedDirsCountHi) << 16) | uint32(bg.UsedDirsCountLo)
-}
-
-// UnusedInodeCount implements BlockGroup.UnusedInodeCount.
-func (bg *BlockGroup64Bit) UnusedInodeCount() uint32 {
- return (uint32(bg.ItableUnusedHi) << 16) | uint32(bg.ItableUnusedLo)
-}
-
-// BlockBitmapChecksum implements BlockGroup.BlockBitmapChecksum.
-func (bg *BlockGroup64Bit) BlockBitmapChecksum() uint32 {
- return (uint32(bg.BlockBitmapChecksumHi) << 16) | uint32(bg.BlockBitmapChecksumLo)
-}
-
-// InodeBitmapChecksum implements BlockGroup.InodeBitmapChecksum.
-func (bg *BlockGroup64Bit) InodeBitmapChecksum() uint32 {
- return (uint32(bg.InodeBitmapChecksumHi) << 16) | uint32(bg.InodeBitmapChecksumLo)
-}
diff --git a/pkg/sentry/fsimpl/ext/disklayout/block_group_test.go b/pkg/sentry/fsimpl/ext/disklayout/block_group_test.go
deleted file mode 100644
index 0ef4294c0..000000000
--- a/pkg/sentry/fsimpl/ext/disklayout/block_group_test.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package disklayout
-
-import (
- "testing"
-)
-
-// TestBlockGroupSize tests that the block group descriptor structs are of the
-// correct size.
-func TestBlockGroupSize(t *testing.T) {
- assertSize(t, BlockGroup32Bit{}, 32)
- assertSize(t, BlockGroup64Bit{}, 64)
-}
diff --git a/pkg/sentry/fsimpl/ext/disklayout/dirent.go b/pkg/sentry/fsimpl/ext/disklayout/dirent.go
deleted file mode 100644
index 417b6cf65..000000000
--- a/pkg/sentry/fsimpl/ext/disklayout/dirent.go
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package disklayout
-
-import (
- "gvisor.dev/gvisor/pkg/sentry/fs"
-)
-
-const (
- // MaxFileName is the maximum length of an ext fs file's name.
- MaxFileName = 255
-
- // DirentSize is the size of ext dirent structures.
- DirentSize = 263
-)
-
-var (
- // inodeTypeByFileType maps ext4 file types to vfs inode types.
- //
- // See https://www.kernel.org/doc/html/latest/filesystems/ext4/dynamic.html#ftype.
- inodeTypeByFileType = map[uint8]fs.InodeType{
- 0: fs.Anonymous,
- 1: fs.RegularFile,
- 2: fs.Directory,
- 3: fs.CharacterDevice,
- 4: fs.BlockDevice,
- 5: fs.Pipe,
- 6: fs.Socket,
- 7: fs.Symlink,
- }
-)
-
-// The Dirent interface should be implemented by structs representing ext
-// directory entries. These are for the linear classical directories which
-// just store a list of dirent structs. A directory is a series of data blocks
-// where is each data block contains a linear array of dirents. The last entry
-// of the block has a record size that takes it to the end of the block. The
-// end of the directory is when you read dirInode.Size() bytes from the blocks.
-//
-// See https://www.kernel.org/doc/html/latest/filesystems/ext4/dynamic.html#linear-classic-directories.
-type Dirent interface {
- // Inode returns the absolute inode number of the underlying inode.
- // Inode number 0 signifies an unused dirent.
- Inode() uint32
-
- // RecordSize returns the record length of this dirent on disk. The next
- // dirent in the dirent list should be read after these many bytes from
- // the current dirent. Must be a multiple of 4.
- RecordSize() uint16
-
- // FileName returns the name of the file. Can be at most 255 is length.
- FileName() string
-
- // FileType returns the inode type of the underlying inode. This is a
- // performance hack so that we do not have to read the underlying inode struct
- // to know the type of inode. This will only work when the SbDirentFileType
- // feature is set. If not, the second returned value will be false indicating
- // that user code has to use the inode mode to extract the file type.
- FileType() (fs.InodeType, bool)
-}
diff --git a/pkg/sentry/fsimpl/ext/disklayout/dirent_new.go b/pkg/sentry/fsimpl/ext/disklayout/dirent_new.go
deleted file mode 100644
index 29ae4a5c2..000000000
--- a/pkg/sentry/fsimpl/ext/disklayout/dirent_new.go
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package disklayout
-
-import (
- "fmt"
-
- "gvisor.dev/gvisor/pkg/sentry/fs"
-)
-
-// DirentNew represents the ext4 directory entry struct. This emulates Linux's
-// ext4_dir_entry_2 struct. The FileName can not be more than 255 bytes so we
-// only need 8 bits to store the NameLength. As a result, NameLength has been
-// shortened and the other 8 bits are used to encode the file type. Use the
-// FileTypeRaw field only if the SbDirentFileType feature is set.
-//
-// Note: This struct can be of variable size on disk. The one described below
-// is of maximum size and the FileName beyond NameLength bytes might contain
-// garbage.
-type DirentNew struct {
- InodeNumber uint32
- RecordLength uint16
- NameLength uint8
- FileTypeRaw uint8
- FileNameRaw [MaxFileName]byte
-}
-
-// Compiles only if DirentNew implements Dirent.
-var _ Dirent = (*DirentNew)(nil)
-
-// Inode implements Dirent.Inode.
-func (d *DirentNew) Inode() uint32 { return d.InodeNumber }
-
-// RecordSize implements Dirent.RecordSize.
-func (d *DirentNew) RecordSize() uint16 { return d.RecordLength }
-
-// FileName implements Dirent.FileName.
-func (d *DirentNew) FileName() string {
- return string(d.FileNameRaw[:d.NameLength])
-}
-
-// FileType implements Dirent.FileType.
-func (d *DirentNew) FileType() (fs.InodeType, bool) {
- if inodeType, ok := inodeTypeByFileType[d.FileTypeRaw]; ok {
- return inodeType, true
- }
-
- panic(fmt.Sprintf("unknown file type %v", d.FileTypeRaw))
-}
diff --git a/pkg/sentry/fsimpl/ext/disklayout/dirent_old.go b/pkg/sentry/fsimpl/ext/disklayout/dirent_old.go
deleted file mode 100644
index 6fff12a6e..000000000
--- a/pkg/sentry/fsimpl/ext/disklayout/dirent_old.go
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package disklayout
-
-import "gvisor.dev/gvisor/pkg/sentry/fs"
-
-// DirentOld represents the old directory entry struct which does not contain
-// the file type. This emulates Linux's ext4_dir_entry struct.
-//
-// Note: This struct can be of variable size on disk. The one described below
-// is of maximum size and the FileName beyond NameLength bytes might contain
-// garbage.
-type DirentOld struct {
- InodeNumber uint32
- RecordLength uint16
- NameLength uint16
- FileNameRaw [MaxFileName]byte
-}
-
-// Compiles only if DirentOld implements Dirent.
-var _ Dirent = (*DirentOld)(nil)
-
-// Inode implements Dirent.Inode.
-func (d *DirentOld) Inode() uint32 { return d.InodeNumber }
-
-// RecordSize implements Dirent.RecordSize.
-func (d *DirentOld) RecordSize() uint16 { return d.RecordLength }
-
-// FileName implements Dirent.FileName.
-func (d *DirentOld) FileName() string {
- return string(d.FileNameRaw[:d.NameLength])
-}
-
-// FileType implements Dirent.FileType.
-func (d *DirentOld) FileType() (fs.InodeType, bool) {
- return fs.Anonymous, false
-}
diff --git a/pkg/sentry/fsimpl/ext/disklayout/dirent_test.go b/pkg/sentry/fsimpl/ext/disklayout/dirent_test.go
deleted file mode 100644
index 934919f8a..000000000
--- a/pkg/sentry/fsimpl/ext/disklayout/dirent_test.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package disklayout
-
-import (
- "testing"
-)
-
-// TestDirentSize tests that the dirent structs are of the correct
-// size.
-func TestDirentSize(t *testing.T) {
- assertSize(t, DirentOld{}, uintptr(DirentSize))
- assertSize(t, DirentNew{}, uintptr(DirentSize))
-}
diff --git a/pkg/sentry/fsimpl/ext/disklayout/disklayout.go b/pkg/sentry/fsimpl/ext/disklayout/disklayout.go
deleted file mode 100644
index bdf4e2132..000000000
--- a/pkg/sentry/fsimpl/ext/disklayout/disklayout.go
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package disklayout provides Linux ext file system's disk level structures
-// which can be directly read into from the underlying device. Structs aim to
-// emulate structures `exactly` how they are layed out on disk.
-//
-// This library aims to be compatible with all ext(2/3/4) systems so it
-// provides a generic interface for all major structures and various
-// implementations (for different versions). The user code is responsible for
-// using appropriate implementations based on the underlying device.
-//
-// Interfacing all major structures here serves a few purposes:
-// - Abstracts away the complexity of the underlying structure from client
-// code. The client only has to figure out versioning on set up and then
-// can use these as black boxes and pass it higher up the stack.
-// - Having pointer receivers forces the user to use pointers to these
-// heavy structs. Hence, prevents the client code from unintentionally
-// copying these by value while passing the interface around.
-// - Version-based implementation selection is resolved on set up hence
-// avoiding per call overhead of choosing implementation.
-// - All interface methods are pretty light weight (do not take in any
-// parameters by design). Passing pointer arguments to interface methods
-// can lead to heap allocation as the compiler won't be able to perform
-// escape analysis on an unknown implementation at compile time.
-//
-// Notes:
-// - All fields in these structs are exported because binary.Read would
-// panic otherwise.
-// - All structures on disk are in little-endian order. Only jbd2 (journal)
-// structures are in big-endian order.
-// - All OS dependent fields in these structures will be interpretted using
-// the Linux version of that field.
-// - The suffix `Lo` in field names stands for lower bits of that field.
-// - The suffix `Hi` in field names stands for upper bits of that field.
-// - The suffix `Raw` has been added to indicate that the field is not split
-// into Lo and Hi fields and also to resolve name collision with the
-// respective interface.
-package disklayout
diff --git a/pkg/sentry/fsimpl/ext/disklayout/extent.go b/pkg/sentry/fsimpl/ext/disklayout/extent.go
deleted file mode 100644
index 567523d32..000000000
--- a/pkg/sentry/fsimpl/ext/disklayout/extent.go
+++ /dev/null
@@ -1,139 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package disklayout
-
-// Extents were introduced in ext4 and provide huge performance gains in terms
-// data locality and reduced metadata block usage. Extents are organized in
-// extent trees. The root node is contained in inode.BlocksRaw.
-//
-// Terminology:
-// - Physical Block:
-// Filesystem data block which is addressed normally wrt the entire
-// filesystem (addressed with 48 bits).
-//
-// - File Block:
-// Data block containing *only* file data and addressed wrt to the file
-// with only 32 bits. The (i)th file block contains file data from
-// byte (i * sb.BlockSize()) to ((i+1) * sb.BlockSize()).
-
-const (
- // ExtentStructsSize is the size of all the three extent on-disk structs.
- ExtentStructsSize = 12
-
- // ExtentMagic is the magic number which must be present in the header.
- ExtentMagic = 0xf30a
-)
-
-// ExtentEntryPair couples an in-memory ExtendNode with the ExtentEntry that
-// points to it. We want to cache these structs in memory to avoid repeated
-// disk reads.
-//
-// Note: This struct itself does not represent an on-disk struct.
-type ExtentEntryPair struct {
- // Entry points to the child node on disk.
- Entry ExtentEntry
- // Node points to child node in memory. Is nil if the current node is a leaf.
- Node *ExtentNode
-}
-
-// ExtentNode represents an extent tree node. For internal nodes, all Entries
-// will be ExtendIdxs. For leaf nodes, they will all be Extents.
-//
-// Note: This struct itself does not represent an on-disk struct.
-type ExtentNode struct {
- Header ExtentHeader
- Entries []ExtentEntryPair
-}
-
-// ExtentEntry reprsents an extent tree node entry. The entry can either be
-// an ExtentIdx or Extent itself. This exists to simplify navigation logic.
-type ExtentEntry interface {
- // FileBlock returns the first file block number covered by this entry.
- FileBlock() uint32
-
- // PhysicalBlock returns the child physical block that this entry points to.
- PhysicalBlock() uint64
-}
-
-// ExtentHeader emulates the ext4_extent_header struct in ext4. Each extent
-// tree node begins with this and is followed by `NumEntries` number of:
-// - Extent if `Depth` == 0
-// - ExtentIdx otherwise
-type ExtentHeader struct {
- // Magic in the extent magic number, must be 0xf30a.
- Magic uint16
-
- // NumEntries indicates the number of valid entries following the header.
- NumEntries uint16
-
- // MaxEntries that could follow the header. Used while adding entries.
- MaxEntries uint16
-
- // Height represents the distance of this node from the farthest leaf. Please
- // note that Linux incorrectly calls this `Depth` (which means the distance
- // of the node from the root).
- Height uint16
- _ uint32
-}
-
-// ExtentIdx emulates the ext4_extent_idx struct in ext4. Only present in
-// internal nodes. Sorted in ascending order based on FirstFileBlock since
-// Linux does a binary search on this. This points to a block containing the
-// child node.
-type ExtentIdx struct {
- FirstFileBlock uint32
- ChildBlockLo uint32
- ChildBlockHi uint16
- _ uint16
-}
-
-// Compiles only if ExtentIdx implements ExtentEntry.
-var _ ExtentEntry = (*ExtentIdx)(nil)
-
-// FileBlock implements ExtentEntry.FileBlock.
-func (ei *ExtentIdx) FileBlock() uint32 {
- return ei.FirstFileBlock
-}
-
-// PhysicalBlock implements ExtentEntry.PhysicalBlock. It returns the
-// physical block number of the child block.
-func (ei *ExtentIdx) PhysicalBlock() uint64 {
- return (uint64(ei.ChildBlockHi) << 32) | uint64(ei.ChildBlockLo)
-}
-
-// Extent represents the ext4_extent struct in ext4. Only present in leaf
-// nodes. Sorted in ascending order based on FirstFileBlock since Linux does a
-// binary search on this. This points to an array of data blocks containing the
-// file data. It covers `Length` data blocks starting from `StartBlock`.
-type Extent struct {
- FirstFileBlock uint32
- Length uint16
- StartBlockHi uint16
- StartBlockLo uint32
-}
-
-// Compiles only if Extent implements ExtentEntry.
-var _ ExtentEntry = (*Extent)(nil)
-
-// FileBlock implements ExtentEntry.FileBlock.
-func (e *Extent) FileBlock() uint32 {
- return e.FirstFileBlock
-}
-
-// PhysicalBlock implements ExtentEntry.PhysicalBlock. It returns the
-// physical block number of the first data block this extent covers.
-func (e *Extent) PhysicalBlock() uint64 {
- return (uint64(e.StartBlockHi) << 32) | uint64(e.StartBlockLo)
-}
diff --git a/pkg/sentry/fsimpl/ext/disklayout/extent_test.go b/pkg/sentry/fsimpl/ext/disklayout/extent_test.go
deleted file mode 100644
index b0fad9b71..000000000
--- a/pkg/sentry/fsimpl/ext/disklayout/extent_test.go
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package disklayout
-
-import (
- "testing"
-)
-
-// TestExtentSize tests that the extent structs are of the correct
-// size.
-func TestExtentSize(t *testing.T) {
- assertSize(t, ExtentHeader{}, ExtentStructsSize)
- assertSize(t, ExtentIdx{}, ExtentStructsSize)
- assertSize(t, Extent{}, ExtentStructsSize)
-}
diff --git a/pkg/sentry/fsimpl/ext/disklayout/inode.go b/pkg/sentry/fsimpl/ext/disklayout/inode.go
deleted file mode 100644
index 88ae913f5..000000000
--- a/pkg/sentry/fsimpl/ext/disklayout/inode.go
+++ /dev/null
@@ -1,274 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package disklayout
-
-import (
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/kernel/time"
-)
-
-// Special inodes. See https://www.kernel.org/doc/html/latest/filesystems/ext4/overview.html#special-inodes.
-const (
- // RootDirInode is the inode number of the root directory inode.
- RootDirInode = 2
-)
-
-// The Inode interface must be implemented by structs representing ext inodes.
-// The inode stores all the metadata pertaining to the file (except for the
-// file name which is held by the directory entry). It does NOT expose all
-// fields and should be extended if need be.
-//
-// Some file systems (e.g. FAT) use the directory entry to store all this
-// information. Ext file systems do not so that they can support hard links.
-// However, ext4 cheats a little bit and duplicates the file type in the
-// directory entry for performance gains.
-//
-// See https://www.kernel.org/doc/html/latest/filesystems/ext4/dynamic.html#index-nodes.
-type Inode interface {
- // Mode returns the linux file mode which is majorly used to extract
- // information like:
- // - File permissions (read/write/execute by user/group/others).
- // - Sticky, set UID and GID bits.
- // - File type.
- //
- // Masks to extract this information are provided in pkg/abi/linux/file.go.
- Mode() linux.FileMode
-
- // UID returns the owner UID.
- UID() auth.KUID
-
- // GID returns the owner GID.
- GID() auth.KGID
-
- // Size returns the size of the file in bytes.
- Size() uint64
-
- // InodeSize returns the size of this inode struct in bytes.
- // In ext2 and ext3, the inode struct and inode disk record size was fixed at
- // 128 bytes. Ext4 makes it possible for the inode struct to be bigger.
- // However, accessing any field beyond the 128 bytes marker must be verified
- // using this method.
- InodeSize() uint16
-
- // AccessTime returns the last access time. Shows when the file was last read.
- //
- // If InExtendedAttr is set, then this should NOT be used because the
- // underlying field is used to store the extended attribute value checksum.
- AccessTime() time.Time
-
- // ChangeTime returns the last change time. Shows when the file meta data
- // (like permissions) was last changed.
- //
- // If InExtendedAttr is set, then this should NOT be used because the
- // underlying field is used to store the lower 32 bits of the attribute
- // value’s reference count.
- ChangeTime() time.Time
-
- // ModificationTime returns the last modification time. Shows when the file
- // content was last modified.
- //
- // If InExtendedAttr is set, then this should NOT be used because
- // the underlying field contains the number of the inode that owns the
- // extended attribute.
- ModificationTime() time.Time
-
- // DeletionTime returns the deletion time. Inodes are marked as deleted by
- // writing to the underlying field. FS tools can restore files until they are
- // actually overwritten.
- DeletionTime() time.Time
-
- // LinksCount returns the number of hard links to this inode.
- //
- // Normally there is an upper limit on the number of hard links:
- // - ext2/ext3 = 32,000
- // - ext4 = 65,000
- //
- // This implies that an ext4 directory cannot have more than 64,998
- // subdirectories because each subdirectory will have a hard link to the
- // directory via the `..` entry. The directory has hard link via the `.` entry
- // of its own. And finally the inode is initiated with 1 hard link (itself).
- //
- // The underlying value is reset to 1 if all the following hold:
- // - Inode is a directory.
- // - SbDirNlink is enabled.
- // - Number of hard links is incremented past 64,999.
- // Hard link value of 1 for a directory would indicate that the number of hard
- // links is unknown because a directory can have minimum 2 hard links (itself
- // and `.` entry).
- LinksCount() uint16
-
- // Flags returns InodeFlags which represents the inode flags.
- Flags() InodeFlags
-
- // Data returns the underlying inode.i_block array as a slice so it's
- // modifiable. This field is special and is used to store various kinds of
- // things depending on the filesystem version and inode type. The underlying
- // field name in Linux is a little misleading.
- // - In ext2/ext3, it contains the block map.
- // - In ext4, it contains the extent tree root node.
- // - For inline files, it contains the file contents.
- // - For symlinks, it contains the link path (if it fits here).
- //
- // See https://www.kernel.org/doc/html/latest/filesystems/ext4/dynamic.html#the-contents-of-inode-i-block.
- Data() []byte
-}
-
-// Inode flags. This is not comprehensive and flags which were not used in
-// the Linux kernel have been excluded.
-const (
- // InSync indicates that all writes to the file must be synchronous.
- InSync = 0x8
-
- // InImmutable indicates that this file is immutable.
- InImmutable = 0x10
-
- // InAppend indicates that this file can only be appended to.
- InAppend = 0x20
-
- // InNoDump indicates that teh dump(1) utility should not dump this file.
- InNoDump = 0x40
-
- // InNoAccessTime indicates that the access time of this inode must not be
- // updated.
- InNoAccessTime = 0x80
-
- // InIndex indicates that this directory has hashed indexes.
- InIndex = 0x1000
-
- // InJournalData indicates that file data must always be written through a
- // journal device.
- InJournalData = 0x4000
-
- // InDirSync indicates that all the directory entiry data must be written
- // synchronously.
- InDirSync = 0x10000
-
- // InTopDir indicates that this inode is at the top of the directory hierarchy.
- InTopDir = 0x20000
-
- // InHugeFile indicates that this is a huge file.
- InHugeFile = 0x40000
-
- // InExtents indicates that this inode uses extents.
- InExtents = 0x80000
-
- // InExtendedAttr indicates that this inode stores a large extended attribute
- // value in its data blocks.
- InExtendedAttr = 0x200000
-
- // InInline indicates that this inode has inline data.
- InInline = 0x10000000
-
- // InReserved indicates that this inode is reserved for the ext4 library.
- InReserved = 0x80000000
-)
-
-// InodeFlags represents all possible combinations of inode flags. It aims to
-// cover the bit masks and provide a more user-friendly interface.
-type InodeFlags struct {
- Sync bool
- Immutable bool
- Append bool
- NoDump bool
- NoAccessTime bool
- Index bool
- JournalData bool
- DirSync bool
- TopDir bool
- HugeFile bool
- Extents bool
- ExtendedAttr bool
- Inline bool
- Reserved bool
-}
-
-// ToInt converts inode flags back to its 32-bit rep.
-func (f InodeFlags) ToInt() uint32 {
- var res uint32
-
- if f.Sync {
- res |= InSync
- }
- if f.Immutable {
- res |= InImmutable
- }
- if f.Append {
- res |= InAppend
- }
- if f.NoDump {
- res |= InNoDump
- }
- if f.NoAccessTime {
- res |= InNoAccessTime
- }
- if f.Index {
- res |= InIndex
- }
- if f.JournalData {
- res |= InJournalData
- }
- if f.DirSync {
- res |= InDirSync
- }
- if f.TopDir {
- res |= InTopDir
- }
- if f.HugeFile {
- res |= InHugeFile
- }
- if f.Extents {
- res |= InExtents
- }
- if f.ExtendedAttr {
- res |= InExtendedAttr
- }
- if f.Inline {
- res |= InInline
- }
- if f.Reserved {
- res |= InReserved
- }
-
- return res
-}
-
-// InodeFlagsFromInt converts the integer representation of inode flags to
-// a InodeFlags struct.
-func InodeFlagsFromInt(f uint32) InodeFlags {
- return InodeFlags{
- Sync: f&InSync > 0,
- Immutable: f&InImmutable > 0,
- Append: f&InAppend > 0,
- NoDump: f&InNoDump > 0,
- NoAccessTime: f&InNoAccessTime > 0,
- Index: f&InIndex > 0,
- JournalData: f&InJournalData > 0,
- DirSync: f&InDirSync > 0,
- TopDir: f&InTopDir > 0,
- HugeFile: f&InHugeFile > 0,
- Extents: f&InExtents > 0,
- ExtendedAttr: f&InExtendedAttr > 0,
- Inline: f&InInline > 0,
- Reserved: f&InReserved > 0,
- }
-}
-
-// These masks define how users can view/modify inode flags. The rest of the
-// flags are for internal kernel usage only.
-const (
- InUserReadFlagMask = 0x4BDFFF
- InUserWriteFlagMask = 0x4B80FF
-)
diff --git a/pkg/sentry/fsimpl/ext/disklayout/inode_new.go b/pkg/sentry/fsimpl/ext/disklayout/inode_new.go
deleted file mode 100644
index 8f9f574ce..000000000
--- a/pkg/sentry/fsimpl/ext/disklayout/inode_new.go
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package disklayout
-
-import "gvisor.dev/gvisor/pkg/sentry/kernel/time"
-
-// InodeNew represents ext4 inode structure which can be bigger than
-// OldInodeSize. The actual size of this struct should be determined using
-// inode.ExtraInodeSize. Accessing any field here should be verified with the
-// actual size. The extra space between the end of the inode struct and end of
-// the inode record can be used to store extended attr.
-//
-// If the TimeExtra fields are in scope, the lower 2 bits of those are used
-// to extend their counter part to be 34 bits wide; the rest (upper) 30 bits
-// are used to provide nanoscond precision. Hence, these timestamps will now
-// overflow in May 2446.
-// See https://www.kernel.org/doc/html/latest/filesystems/ext4/dynamic.html#inode-timestamps.
-type InodeNew struct {
- InodeOld
-
- ExtraInodeSize uint16
- ChecksumHi uint16
- ChangeTimeExtra uint32
- ModificationTimeExtra uint32
- AccessTimeExtra uint32
- CreationTime uint32
- CreationTimeExtra uint32
- VersionHi uint32
- ProjectID uint32
-}
-
-// Compiles only if InodeNew implements Inode.
-var _ Inode = (*InodeNew)(nil)
-
-// fromExtraTime decodes the extra time and constructs the kernel time struct
-// with nanosecond precision.
-func fromExtraTime(lo int32, extra uint32) time.Time {
- // See description above InodeNew for format.
- seconds := (int64(extra&0x3) << 32) + int64(lo)
- nanoseconds := int64(extra >> 2)
- return time.FromUnix(seconds, nanoseconds)
-}
-
-// Only override methods which change due to ext4 specific fields.
-
-// Size implements Inode.Size.
-func (in *InodeNew) Size() uint64 {
- return (uint64(in.SizeHi) << 32) | uint64(in.SizeLo)
-}
-
-// InodeSize implements Inode.InodeSize.
-func (in *InodeNew) InodeSize() uint16 {
- return OldInodeSize + in.ExtraInodeSize
-}
-
-// ChangeTime implements Inode.ChangeTime.
-func (in *InodeNew) ChangeTime() time.Time {
- // Apply new timestamp logic if inode.ChangeTimeExtra is in scope.
- if in.ExtraInodeSize >= 8 {
- return fromExtraTime(in.ChangeTimeRaw, in.ChangeTimeExtra)
- }
-
- return in.InodeOld.ChangeTime()
-}
-
-// ModificationTime implements Inode.ModificationTime.
-func (in *InodeNew) ModificationTime() time.Time {
- // Apply new timestamp logic if inode.ModificationTimeExtra is in scope.
- if in.ExtraInodeSize >= 12 {
- return fromExtraTime(in.ModificationTimeRaw, in.ModificationTimeExtra)
- }
-
- return in.InodeOld.ModificationTime()
-}
-
-// AccessTime implements Inode.AccessTime.
-func (in *InodeNew) AccessTime() time.Time {
- // Apply new timestamp logic if inode.AccessTimeExtra is in scope.
- if in.ExtraInodeSize >= 16 {
- return fromExtraTime(in.AccessTimeRaw, in.AccessTimeExtra)
- }
-
- return in.InodeOld.AccessTime()
-}
diff --git a/pkg/sentry/fsimpl/ext/disklayout/inode_old.go b/pkg/sentry/fsimpl/ext/disklayout/inode_old.go
deleted file mode 100644
index db25b11b6..000000000
--- a/pkg/sentry/fsimpl/ext/disklayout/inode_old.go
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package disklayout
-
-import (
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/kernel/time"
-)
-
-const (
- // OldInodeSize is the inode size in ext2/ext3.
- OldInodeSize = 128
-)
-
-// InodeOld implements Inode interface. It emulates ext2/ext3 inode struct.
-// Inode struct size and record size are both 128 bytes for this.
-//
-// All fields representing time are in seconds since the epoch. Which means that
-// they will overflow in January 2038.
-type InodeOld struct {
- ModeRaw uint16
- UIDLo uint16
- SizeLo uint32
-
- // The time fields are signed integers because they could be negative to
- // represent time before the epoch.
- AccessTimeRaw int32
- ChangeTimeRaw int32
- ModificationTimeRaw int32
- DeletionTimeRaw int32
-
- GIDLo uint16
- LinksCountRaw uint16
- BlocksCountLo uint32
- FlagsRaw uint32
- VersionLo uint32 // This is OS dependent.
- DataRaw [60]byte
- Generation uint32
- FileACLLo uint32
- SizeHi uint32
- ObsoFaddr uint32
-
- // OS dependent fields have been inlined here.
- BlocksCountHi uint16
- FileACLHi uint16
- UIDHi uint16
- GIDHi uint16
- ChecksumLo uint16
- _ uint16
-}
-
-// Compiles only if InodeOld implements Inode.
-var _ Inode = (*InodeOld)(nil)
-
-// Mode implements Inode.Mode.
-func (in *InodeOld) Mode() linux.FileMode { return linux.FileMode(in.ModeRaw) }
-
-// UID implements Inode.UID.
-func (in *InodeOld) UID() auth.KUID {
- return auth.KUID((uint32(in.UIDHi) << 16) | uint32(in.UIDLo))
-}
-
-// GID implements Inode.GID.
-func (in *InodeOld) GID() auth.KGID {
- return auth.KGID((uint32(in.GIDHi) << 16) | uint32(in.GIDLo))
-}
-
-// Size implements Inode.Size.
-func (in *InodeOld) Size() uint64 {
- // In ext2/ext3, in.SizeHi did not exist, it was instead named in.DirACL.
- return uint64(in.SizeLo)
-}
-
-// InodeSize implements Inode.InodeSize.
-func (in *InodeOld) InodeSize() uint16 { return OldInodeSize }
-
-// AccessTime implements Inode.AccessTime.
-func (in *InodeOld) AccessTime() time.Time {
- return time.FromUnix(int64(in.AccessTimeRaw), 0)
-}
-
-// ChangeTime implements Inode.ChangeTime.
-func (in *InodeOld) ChangeTime() time.Time {
- return time.FromUnix(int64(in.ChangeTimeRaw), 0)
-}
-
-// ModificationTime implements Inode.ModificationTime.
-func (in *InodeOld) ModificationTime() time.Time {
- return time.FromUnix(int64(in.ModificationTimeRaw), 0)
-}
-
-// DeletionTime implements Inode.DeletionTime.
-func (in *InodeOld) DeletionTime() time.Time {
- return time.FromUnix(int64(in.DeletionTimeRaw), 0)
-}
-
-// LinksCount implements Inode.LinksCount.
-func (in *InodeOld) LinksCount() uint16 { return in.LinksCountRaw }
-
-// Flags implements Inode.Flags.
-func (in *InodeOld) Flags() InodeFlags { return InodeFlagsFromInt(in.FlagsRaw) }
-
-// Data implements Inode.Data.
-func (in *InodeOld) Data() []byte { return in.DataRaw[:] }
diff --git a/pkg/sentry/fsimpl/ext/disklayout/inode_test.go b/pkg/sentry/fsimpl/ext/disklayout/inode_test.go
deleted file mode 100644
index dd03ee50e..000000000
--- a/pkg/sentry/fsimpl/ext/disklayout/inode_test.go
+++ /dev/null
@@ -1,222 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package disklayout
-
-import (
- "fmt"
- "strconv"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/kernel/time"
-)
-
-// TestInodeSize tests that the inode structs are of the correct size.
-func TestInodeSize(t *testing.T) {
- assertSize(t, InodeOld{}, OldInodeSize)
-
- // This was updated from 156 bytes to 160 bytes in Oct 2015.
- assertSize(t, InodeNew{}, 160)
-}
-
-// TestTimestampSeconds tests that the seconds part of [a/c/m] timestamps in
-// ext4 inode structs are decoded correctly.
-//
-// These tests are derived from the table under https://www.kernel.org/doc/html/latest/filesystems/ext4/dynamic.html#inode-timestamps.
-func TestTimestampSeconds(t *testing.T) {
- type timestampTest struct {
- // msbSet tells if the most significant bit of InodeOld.[X]TimeRaw is set.
- // If this is set then the 32-bit time is negative.
- msbSet bool
-
- // lowerBound tells if we should take the lowest possible value of
- // InodeOld.[X]TimeRaw while satisfying test.msbSet condition. If set to
- // false it tells to take the highest possible value.
- lowerBound bool
-
- // extraBits is InodeNew.[X]TimeExtra.
- extraBits uint32
-
- // want is the kernel time struct that is expected.
- want time.Time
- }
-
- tests := []timestampTest{
- // 1901-12-13
- {
- msbSet: true,
- lowerBound: true,
- extraBits: 0,
- want: time.FromUnix(int64(-0x80000000), 0),
- },
-
- // 1969-12-31
- {
- msbSet: true,
- lowerBound: false,
- extraBits: 0,
- want: time.FromUnix(int64(-1), 0),
- },
-
- // 1970-01-01
- {
- msbSet: false,
- lowerBound: true,
- extraBits: 0,
- want: time.FromUnix(int64(0), 0),
- },
-
- // 2038-01-19
- {
- msbSet: false,
- lowerBound: false,
- extraBits: 0,
- want: time.FromUnix(int64(0x7fffffff), 0),
- },
-
- // 2038-01-19
- {
- msbSet: true,
- lowerBound: true,
- extraBits: 1,
- want: time.FromUnix(int64(0x80000000), 0),
- },
-
- // 2106-02-07
- {
- msbSet: true,
- lowerBound: false,
- extraBits: 1,
- want: time.FromUnix(int64(0xffffffff), 0),
- },
-
- // 2106-02-07
- {
- msbSet: false,
- lowerBound: true,
- extraBits: 1,
- want: time.FromUnix(int64(0x100000000), 0),
- },
-
- // 2174-02-25
- {
- msbSet: false,
- lowerBound: false,
- extraBits: 1,
- want: time.FromUnix(int64(0x17fffffff), 0),
- },
-
- // 2174-02-25
- {
- msbSet: true,
- lowerBound: true,
- extraBits: 2,
- want: time.FromUnix(int64(0x180000000), 0),
- },
-
- // 2242-03-16
- {
- msbSet: true,
- lowerBound: false,
- extraBits: 2,
- want: time.FromUnix(int64(0x1ffffffff), 0),
- },
-
- // 2242-03-16
- {
- msbSet: false,
- lowerBound: true,
- extraBits: 2,
- want: time.FromUnix(int64(0x200000000), 0),
- },
-
- // 2310-04-04
- {
- msbSet: false,
- lowerBound: false,
- extraBits: 2,
- want: time.FromUnix(int64(0x27fffffff), 0),
- },
-
- // 2310-04-04
- {
- msbSet: true,
- lowerBound: true,
- extraBits: 3,
- want: time.FromUnix(int64(0x280000000), 0),
- },
-
- // 2378-04-22
- {
- msbSet: true,
- lowerBound: false,
- extraBits: 3,
- want: time.FromUnix(int64(0x2ffffffff), 0),
- },
-
- // 2378-04-22
- {
- msbSet: false,
- lowerBound: true,
- extraBits: 3,
- want: time.FromUnix(int64(0x300000000), 0),
- },
-
- // 2446-05-10
- {
- msbSet: false,
- lowerBound: false,
- extraBits: 3,
- want: time.FromUnix(int64(0x37fffffff), 0),
- },
- }
-
- lowerMSB0 := int32(0) // binary: 00000000 00000000 00000000 00000000
- upperMSB0 := int32(0x7fffffff) // binary: 01111111 11111111 11111111 11111111
- lowerMSB1 := int32(-0x80000000) // binary: 10000000 00000000 00000000 00000000
- upperMSB1 := int32(-1) // binary: 11111111 11111111 11111111 11111111
-
- get32BitTime := func(test timestampTest) int32 {
- if test.msbSet {
- if test.lowerBound {
- return lowerMSB1
- }
-
- return upperMSB1
- }
-
- if test.lowerBound {
- return lowerMSB0
- }
-
- return upperMSB0
- }
-
- getTestName := func(test timestampTest) string {
- return fmt.Sprintf(
- "Tests time decoding with epoch bits 0b%s and 32-bit raw time: MSB set=%t, lower bound=%t",
- strconv.FormatInt(int64(test.extraBits), 2),
- test.msbSet,
- test.lowerBound,
- )
- }
-
- for _, test := range tests {
- t.Run(getTestName(test), func(t *testing.T) {
- if got := fromExtraTime(get32BitTime(test), test.extraBits); got != test.want {
- t.Errorf("Expected: %v, Got: %v", test.want, got)
- }
- })
- }
-}
diff --git a/pkg/sentry/fsimpl/ext/disklayout/superblock.go b/pkg/sentry/fsimpl/ext/disklayout/superblock.go
deleted file mode 100644
index 8bb327006..000000000
--- a/pkg/sentry/fsimpl/ext/disklayout/superblock.go
+++ /dev/null
@@ -1,471 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package disklayout
-
-const (
- // SbOffset is the absolute offset at which the superblock is placed.
- SbOffset = 1024
-)
-
-// SuperBlock should be implemented by structs representing the ext superblock.
-// The superblock holds a lot of information about the enclosing filesystem.
-// This interface aims to provide access methods to important information held
-// by the superblock. It does NOT expose all fields of the superblock, only the
-// ones necessary. This can be expanded when need be.
-//
-// Location and replication:
-// - The superblock is located at offset 1024 in block group 0.
-// - Redundant copies of the superblock and group descriptors are kept in
-// all groups if SbSparse feature flag is NOT set. If it is set, the
-// replicas only exist in groups whose group number is either 0 or a
-// power of 3, 5, or 7.
-// - There is also a sparse superblock feature v2 in which there are just
-// two replicas saved in the block groups pointed by sb.s_backup_bgs.
-//
-// Replicas should eventually be updated if the superblock is updated.
-//
-// See https://www.kernel.org/doc/html/latest/filesystems/ext4/globals.html#super-block.
-type SuperBlock interface {
- // InodesCount returns the total number of inodes in this filesystem.
- InodesCount() uint32
-
- // BlocksCount returns the total number of data blocks in this filesystem.
- BlocksCount() uint64
-
- // FreeBlocksCount returns the number of free blocks in this filesystem.
- FreeBlocksCount() uint64
-
- // FreeInodesCount returns the number of free inodes in this filesystem.
- FreeInodesCount() uint32
-
- // MountCount returns the number of mounts since the last fsck.
- MountCount() uint16
-
- // MaxMountCount returns the number of mounts allowed beyond which a fsck is
- // needed.
- MaxMountCount() uint16
-
- // FirstDataBlock returns the absolute block number of the first data block,
- // which contains the super block itself.
- //
- // If the filesystem has 1kb data blocks then this should return 1. For all
- // other configurations, this typically returns 0.
- FirstDataBlock() uint32
-
- // BlockSize returns the size of one data block in this filesystem.
- // This can be calculated by 2^(10 + sb.s_log_block_size). This ensures that
- // the smallest block size is 1kb.
- BlockSize() uint64
-
- // BlocksPerGroup returns the number of data blocks in a block group.
- BlocksPerGroup() uint32
-
- // ClusterSize returns block cluster size (set during mkfs time by admin).
- // This can be calculated by 2^(10 + sb.s_log_cluster_size). This ensures that
- // the smallest cluster size is 1kb.
- //
- // sb.s_log_cluster_size must equal sb.s_log_block_size if bigalloc feature
- // is NOT set and consequently BlockSize() = ClusterSize() in that case.
- ClusterSize() uint64
-
- // ClustersPerGroup returns:
- // - number of clusters per group if bigalloc is enabled.
- // - BlocksPerGroup() otherwise.
- ClustersPerGroup() uint32
-
- // InodeSize returns the size of the inode disk record size in bytes. Use this
- // to iterate over inode arrays on disk.
- //
- // In ext2 and ext3:
- // - Each inode had a disk record of 128 bytes.
- // - The inode struct size was fixed at 128 bytes.
- //
- // In ext4 its possible to allocate larger on-disk inodes:
- // - Inode disk record size = sb.s_inode_size (function return value).
- // = 256 (default)
- // - Inode struct size = 128 + inode.i_extra_isize.
- // = 128 + 32 = 160 (default)
- InodeSize() uint16
-
- // InodesPerGroup returns the number of inodes in a block group.
- InodesPerGroup() uint32
-
- // BgDescSize returns the size of the block group descriptor struct.
- //
- // In ext2, ext3, ext4 (without 64-bit feature), the block group descriptor
- // is only 32 bytes long.
- // In ext4 with 64-bit feature, the block group descriptor expands to AT LEAST
- // 64 bytes. It might be bigger than that.
- BgDescSize() uint16
-
- // CompatibleFeatures returns the CompatFeatures struct which holds all the
- // compatible features this fs supports.
- CompatibleFeatures() CompatFeatures
-
- // IncompatibleFeatures returns the CompatFeatures struct which holds all the
- // incompatible features this fs supports.
- IncompatibleFeatures() IncompatFeatures
-
- // ReadOnlyCompatibleFeatures returns the CompatFeatures struct which holds all the
- // readonly compatible features this fs supports.
- ReadOnlyCompatibleFeatures() RoCompatFeatures
-
- // Magic() returns the magic signature which must be 0xef53.
- Magic() uint16
-
- // Revision returns the superblock revision. Superblock struct fields from
- // offset 0x54 till 0x150 should only be used if superblock has DynamicRev.
- Revision() SbRevision
-}
-
-// SbRevision is the type for superblock revisions.
-type SbRevision uint32
-
-// Super block revisions.
-const (
- // OldRev is the good old (original) format.
- OldRev SbRevision = 0
-
- // DynamicRev is v2 format w/ dynamic inode sizes.
- DynamicRev SbRevision = 1
-)
-
-// Superblock compatible features.
-// This is not exhaustive, unused features are not listed.
-const (
- // SbDirPrealloc indicates directory preallocation.
- SbDirPrealloc = 0x1
-
- // SbHasJournal indicates the presence of a journal. jbd2 should only work
- // with this being set.
- SbHasJournal = 0x4
-
- // SbExtAttr indicates extended attributes support.
- SbExtAttr = 0x8
-
- // SbResizeInode indicates that the fs has reserved GDT blocks (right after
- // group descriptors) for fs expansion.
- SbResizeInode = 0x10
-
- // SbDirIndex indicates that the fs has directory indices.
- SbDirIndex = 0x20
-
- // SbSparseV2 stands for Sparse superblock version 2.
- SbSparseV2 = 0x200
-)
-
-// CompatFeatures represents a superblock's compatible feature set. If the
-// kernel does not understand any of these feature, it can still read/write
-// to this fs.
-type CompatFeatures struct {
- DirPrealloc bool
- HasJournal bool
- ExtAttr bool
- ResizeInode bool
- DirIndex bool
- SparseV2 bool
-}
-
-// ToInt converts superblock compatible features back to its 32-bit rep.
-func (f CompatFeatures) ToInt() uint32 {
- var res uint32
-
- if f.DirPrealloc {
- res |= SbDirPrealloc
- }
- if f.HasJournal {
- res |= SbHasJournal
- }
- if f.ExtAttr {
- res |= SbExtAttr
- }
- if f.ResizeInode {
- res |= SbResizeInode
- }
- if f.DirIndex {
- res |= SbDirIndex
- }
- if f.SparseV2 {
- res |= SbSparseV2
- }
-
- return res
-}
-
-// CompatFeaturesFromInt converts the integer representation of superblock
-// compatible features to CompatFeatures struct.
-func CompatFeaturesFromInt(f uint32) CompatFeatures {
- return CompatFeatures{
- DirPrealloc: f&SbDirPrealloc > 0,
- HasJournal: f&SbHasJournal > 0,
- ExtAttr: f&SbExtAttr > 0,
- ResizeInode: f&SbResizeInode > 0,
- DirIndex: f&SbDirIndex > 0,
- SparseV2: f&SbSparseV2 > 0,
- }
-}
-
-// Superblock incompatible features.
-// This is not exhaustive, unused features are not listed.
-const (
- // SbDirentFileType indicates that directory entries record the file type.
- // We should use struct DirentNew for dirents then.
- SbDirentFileType = 0x2
-
- // SbRecovery indicates that the filesystem needs recovery.
- SbRecovery = 0x4
-
- // SbJournalDev indicates that the filesystem has a separate journal device.
- SbJournalDev = 0x8
-
- // SbMetaBG indicates that the filesystem is using Meta block groups. Moves
- // the group descriptors from the congested first block group into the first
- // group of each metablock group to increase the maximum block groups limit
- // and hence support much larger filesystems.
- //
- // See https://www.kernel.org/doc/html/latest/filesystems/ext4/overview.html#meta-block-groups.
- SbMetaBG = 0x10
-
- // SbExtents indicates that the filesystem uses extents. Must be set in ext4
- // filesystems.
- SbExtents = 0x40
-
- // SbIs64Bit indicates that this filesystem addresses blocks with 64-bits.
- // Hence can support 2^64 data blocks.
- SbIs64Bit = 0x80
-
- // SbMMP indicates that this filesystem has multiple mount protection.
- //
- // See https://www.kernel.org/doc/html/latest/filesystems/ext4/globals.html#multiple-mount-protection.
- SbMMP = 0x100
-
- // SbFlexBg indicates that this filesystem has flexible block groups. Several
- // block groups are tied into one logical block group so that all the metadata
- // for the block groups (bitmaps and inode tables) are close together for
- // faster loading. Consequently, large files will be continuous on disk.
- // However, this does not affect the placement of redundant superblocks and
- // group descriptors.
- //
- // See https://www.kernel.org/doc/html/latest/filesystems/ext4/overview.html#flexible-block-groups.
- SbFlexBg = 0x200
-
- // SbLargeDir shows that large directory enabled. Directory htree can be 3
- // levels deep. Directory htrees are allowed to be 2 levels deep otherwise.
- SbLargeDir = 0x4000
-
- // SbInlineData allows inline data in inodes for really small files.
- SbInlineData = 0x8000
-
- // SbEncrypted indicates that this fs contains encrypted inodes.
- SbEncrypted = 0x10000
-)
-
-// IncompatFeatures represents a superblock's incompatible feature set. If the
-// kernel does not understand any of these feature, it should refuse to mount.
-type IncompatFeatures struct {
- DirentFileType bool
- Recovery bool
- JournalDev bool
- MetaBG bool
- Extents bool
- Is64Bit bool
- MMP bool
- FlexBg bool
- LargeDir bool
- InlineData bool
- Encrypted bool
-}
-
-// ToInt converts superblock incompatible features back to its 32-bit rep.
-func (f IncompatFeatures) ToInt() uint32 {
- var res uint32
-
- if f.DirentFileType {
- res |= SbDirentFileType
- }
- if f.Recovery {
- res |= SbRecovery
- }
- if f.JournalDev {
- res |= SbJournalDev
- }
- if f.MetaBG {
- res |= SbMetaBG
- }
- if f.Extents {
- res |= SbExtents
- }
- if f.Is64Bit {
- res |= SbIs64Bit
- }
- if f.MMP {
- res |= SbMMP
- }
- if f.FlexBg {
- res |= SbFlexBg
- }
- if f.LargeDir {
- res |= SbLargeDir
- }
- if f.InlineData {
- res |= SbInlineData
- }
- if f.Encrypted {
- res |= SbEncrypted
- }
-
- return res
-}
-
-// IncompatFeaturesFromInt converts the integer representation of superblock
-// incompatible features to IncompatFeatures struct.
-func IncompatFeaturesFromInt(f uint32) IncompatFeatures {
- return IncompatFeatures{
- DirentFileType: f&SbDirentFileType > 0,
- Recovery: f&SbRecovery > 0,
- JournalDev: f&SbJournalDev > 0,
- MetaBG: f&SbMetaBG > 0,
- Extents: f&SbExtents > 0,
- Is64Bit: f&SbIs64Bit > 0,
- MMP: f&SbMMP > 0,
- FlexBg: f&SbFlexBg > 0,
- LargeDir: f&SbLargeDir > 0,
- InlineData: f&SbInlineData > 0,
- Encrypted: f&SbEncrypted > 0,
- }
-}
-
-// Superblock readonly compatible features.
-// This is not exhaustive, unused features are not listed.
-const (
- // SbSparse indicates sparse superblocks. Only groups with number either 0 or
- // a power of 3, 5, or 7 will have redundant copies of the superblock and
- // block descriptors.
- SbSparse = 0x1
-
- // SbLargeFile indicates that this fs has been used to store a file >= 2GiB.
- SbLargeFile = 0x2
-
- // SbHugeFile indicates that this fs contains files whose sizes are
- // represented in units of logicals blocks, not 512-byte sectors.
- SbHugeFile = 0x8
-
- // SbGdtCsum indicates that group descriptors have checksums.
- SbGdtCsum = 0x10
-
- // SbDirNlink indicates that the new subdirectory limit is 64,999. Ext3 has a
- // 32,000 subdirectory limit.
- SbDirNlink = 0x20
-
- // SbExtraIsize indicates that large inodes exist on this filesystem.
- SbExtraIsize = 0x40
-
- // SbHasSnapshot indicates the existence of a snapshot.
- SbHasSnapshot = 0x80
-
- // SbQuota enables usage tracking for all quota types.
- SbQuota = 0x100
-
- // SbBigalloc maps to the bigalloc feature. When set, the minimum allocation
- // unit becomes a cluster rather than a data block. Then block bitmaps track
- // clusters, not data blocks.
- //
- // See https://www.kernel.org/doc/html/latest/filesystems/ext4/overview.html#bigalloc.
- SbBigalloc = 0x200
-
- // SbMetadataCsum indicates that the fs supports metadata checksumming.
- SbMetadataCsum = 0x400
-
- // SbReadOnly marks this filesystem as readonly. Should refuse to mount in
- // read/write mode.
- SbReadOnly = 0x1000
-)
-
-// RoCompatFeatures represents a superblock's readonly compatible feature set.
-// If the kernel does not understand any of these feature, it can still mount
-// readonly. But if the user wants to mount read/write, the kernel should
-// refuse to mount.
-type RoCompatFeatures struct {
- Sparse bool
- LargeFile bool
- HugeFile bool
- GdtCsum bool
- DirNlink bool
- ExtraIsize bool
- HasSnapshot bool
- Quota bool
- Bigalloc bool
- MetadataCsum bool
- ReadOnly bool
-}
-
-// ToInt converts superblock readonly compatible features to its 32-bit rep.
-func (f RoCompatFeatures) ToInt() uint32 {
- var res uint32
-
- if f.Sparse {
- res |= SbSparse
- }
- if f.LargeFile {
- res |= SbLargeFile
- }
- if f.HugeFile {
- res |= SbHugeFile
- }
- if f.GdtCsum {
- res |= SbGdtCsum
- }
- if f.DirNlink {
- res |= SbDirNlink
- }
- if f.ExtraIsize {
- res |= SbExtraIsize
- }
- if f.HasSnapshot {
- res |= SbHasSnapshot
- }
- if f.Quota {
- res |= SbQuota
- }
- if f.Bigalloc {
- res |= SbBigalloc
- }
- if f.MetadataCsum {
- res |= SbMetadataCsum
- }
- if f.ReadOnly {
- res |= SbReadOnly
- }
-
- return res
-}
-
-// RoCompatFeaturesFromInt converts the integer representation of superblock
-// readonly compatible features to RoCompatFeatures struct.
-func RoCompatFeaturesFromInt(f uint32) RoCompatFeatures {
- return RoCompatFeatures{
- Sparse: f&SbSparse > 0,
- LargeFile: f&SbLargeFile > 0,
- HugeFile: f&SbHugeFile > 0,
- GdtCsum: f&SbGdtCsum > 0,
- DirNlink: f&SbDirNlink > 0,
- ExtraIsize: f&SbExtraIsize > 0,
- HasSnapshot: f&SbHasSnapshot > 0,
- Quota: f&SbQuota > 0,
- Bigalloc: f&SbBigalloc > 0,
- MetadataCsum: f&SbMetadataCsum > 0,
- ReadOnly: f&SbReadOnly > 0,
- }
-}
diff --git a/pkg/sentry/fsimpl/ext/disklayout/superblock_32.go b/pkg/sentry/fsimpl/ext/disklayout/superblock_32.go
deleted file mode 100644
index 53e515fd3..000000000
--- a/pkg/sentry/fsimpl/ext/disklayout/superblock_32.go
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package disklayout
-
-// SuperBlock32Bit implements SuperBlock and represents the 32-bit version of
-// the ext4_super_block struct in fs/ext4/ext4.h. Should be used only if
-// RevLevel = DynamicRev and 64-bit feature is disabled.
-type SuperBlock32Bit struct {
- // We embed the old superblock struct here because the 32-bit version is just
- // an extension of the old version.
- SuperBlockOld
-
- FirstInode uint32
- InodeSizeRaw uint16
- BlockGroupNumber uint16
- FeatureCompat uint32
- FeatureIncompat uint32
- FeatureRoCompat uint32
- UUID [16]byte
- VolumeName [16]byte
- LastMounted [64]byte
- AlgoUsageBitmap uint32
- PreallocBlocks uint8
- PreallocDirBlocks uint8
- ReservedGdtBlocks uint16
- JournalUUID [16]byte
- JournalInum uint32
- JournalDev uint32
- LastOrphan uint32
- HashSeed [4]uint32
- DefaultHashVersion uint8
- JnlBackupType uint8
- BgDescSizeRaw uint16
- DefaultMountOpts uint32
- FirstMetaBg uint32
- MkfsTime uint32
- JnlBlocks [17]uint32
-}
-
-// Compiles only if SuperBlock32Bit implements SuperBlock.
-var _ SuperBlock = (*SuperBlock32Bit)(nil)
-
-// Only override methods which change based on the additional fields above.
-// Not overriding SuperBlock.BgDescSize because it would still return 32 here.
-
-// InodeSize implements SuperBlock.InodeSize.
-func (sb *SuperBlock32Bit) InodeSize() uint16 {
- return sb.InodeSizeRaw
-}
-
-// CompatibleFeatures implements SuperBlock.CompatibleFeatures.
-func (sb *SuperBlock32Bit) CompatibleFeatures() CompatFeatures {
- return CompatFeaturesFromInt(sb.FeatureCompat)
-}
-
-// IncompatibleFeatures implements SuperBlock.IncompatibleFeatures.
-func (sb *SuperBlock32Bit) IncompatibleFeatures() IncompatFeatures {
- return IncompatFeaturesFromInt(sb.FeatureIncompat)
-}
-
-// ReadOnlyCompatibleFeatures implements SuperBlock.ReadOnlyCompatibleFeatures.
-func (sb *SuperBlock32Bit) ReadOnlyCompatibleFeatures() RoCompatFeatures {
- return RoCompatFeaturesFromInt(sb.FeatureRoCompat)
-}
diff --git a/pkg/sentry/fsimpl/ext/disklayout/superblock_64.go b/pkg/sentry/fsimpl/ext/disklayout/superblock_64.go
deleted file mode 100644
index 7c1053fb4..000000000
--- a/pkg/sentry/fsimpl/ext/disklayout/superblock_64.go
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package disklayout
-
-// SuperBlock64Bit implements SuperBlock and represents the 64-bit version of
-// the ext4_super_block struct in fs/ext4/ext4.h. This sums up to be exactly
-// 1024 bytes (smallest possible block size) and hence the superblock always
-// fits in no more than one data block. Should only be used when the 64-bit
-// feature is set.
-type SuperBlock64Bit struct {
- // We embed the 32-bit struct here because 64-bit version is just an extension
- // of the 32-bit version.
- SuperBlock32Bit
-
- BlocksCountHi uint32
- ReservedBlocksCountHi uint32
- FreeBlocksCountHi uint32
- MinInodeSize uint16
- WantInodeSize uint16
- Flags uint32
- RaidStride uint16
- MmpInterval uint16
- MmpBlock uint64
- RaidStripeWidth uint32
- LogGroupsPerFlex uint8
- ChecksumType uint8
- _ uint16
- KbytesWritten uint64
- SnapshotInum uint32
- SnapshotID uint32
- SnapshotRsrvBlocksCount uint64
- SnapshotList uint32
- ErrorCount uint32
- FirstErrorTime uint32
- FirstErrorInode uint32
- FirstErrorBlock uint64
- FirstErrorFunction [32]byte
- FirstErrorLine uint32
- LastErrorTime uint32
- LastErrorInode uint32
- LastErrorLine uint32
- LastErrorBlock uint64
- LastErrorFunction [32]byte
- MountOpts [64]byte
- UserQuotaInum uint32
- GroupQuotaInum uint32
- OverheadBlocks uint32
- BackupBgs [2]uint32
- EncryptAlgos [4]uint8
- EncryptPwSalt [16]uint8
- LostFoundInode uint32
- ProjectQuotaInode uint32
- ChecksumSeed uint32
- WtimeHi uint8
- MtimeHi uint8
- MkfsTimeHi uint8
- LastCheckHi uint8
- FirstErrorTimeHi uint8
- LastErrorTimeHi uint8
- _ [2]uint8
- Encoding uint16
- EncodingFlags uint16
- _ [95]uint32
- Checksum uint32
-}
-
-// Compiles only if SuperBlock64Bit implements SuperBlock.
-var _ SuperBlock = (*SuperBlock64Bit)(nil)
-
-// Only override methods which change based on the 64-bit feature.
-
-// BlocksCount implements SuperBlock.BlocksCount.
-func (sb *SuperBlock64Bit) BlocksCount() uint64 {
- return (uint64(sb.BlocksCountHi) << 32) | uint64(sb.BlocksCountLo)
-}
-
-// FreeBlocksCount implements SuperBlock.FreeBlocksCount.
-func (sb *SuperBlock64Bit) FreeBlocksCount() uint64 {
- return (uint64(sb.FreeBlocksCountHi) << 32) | uint64(sb.FreeBlocksCountLo)
-}
-
-// BgDescSize implements SuperBlock.BgDescSize.
-func (sb *SuperBlock64Bit) BgDescSize() uint16 { return sb.BgDescSizeRaw }
diff --git a/pkg/sentry/fsimpl/ext/disklayout/superblock_old.go b/pkg/sentry/fsimpl/ext/disklayout/superblock_old.go
deleted file mode 100644
index 9221e0251..000000000
--- a/pkg/sentry/fsimpl/ext/disklayout/superblock_old.go
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package disklayout
-
-// SuperBlockOld implements SuperBlock and represents the old version of the
-// superblock struct. Should be used only if RevLevel = OldRev.
-type SuperBlockOld struct {
- InodesCountRaw uint32
- BlocksCountLo uint32
- ReservedBlocksCount uint32
- FreeBlocksCountLo uint32
- FreeInodesCountRaw uint32
- FirstDataBlockRaw uint32
- LogBlockSize uint32
- LogClusterSize uint32
- BlocksPerGroupRaw uint32
- ClustersPerGroupRaw uint32
- InodesPerGroupRaw uint32
- Mtime uint32
- Wtime uint32
- MountCountRaw uint16
- MaxMountCountRaw uint16
- MagicRaw uint16
- State uint16
- Errors uint16
- MinorRevLevel uint16
- LastCheck uint32
- CheckInterval uint32
- CreatorOS uint32
- RevLevel uint32
- DefResUID uint16
- DefResGID uint16
-}
-
-// Compiles only if SuperBlockOld implements SuperBlock.
-var _ SuperBlock = (*SuperBlockOld)(nil)
-
-// InodesCount implements SuperBlock.InodesCount.
-func (sb *SuperBlockOld) InodesCount() uint32 { return sb.InodesCountRaw }
-
-// BlocksCount implements SuperBlock.BlocksCount.
-func (sb *SuperBlockOld) BlocksCount() uint64 { return uint64(sb.BlocksCountLo) }
-
-// FreeBlocksCount implements SuperBlock.FreeBlocksCount.
-func (sb *SuperBlockOld) FreeBlocksCount() uint64 { return uint64(sb.FreeBlocksCountLo) }
-
-// FreeInodesCount implements SuperBlock.FreeInodesCount.
-func (sb *SuperBlockOld) FreeInodesCount() uint32 { return sb.FreeInodesCountRaw }
-
-// MountCount implements SuperBlock.MountCount.
-func (sb *SuperBlockOld) MountCount() uint16 { return sb.MountCountRaw }
-
-// MaxMountCount implements SuperBlock.MaxMountCount.
-func (sb *SuperBlockOld) MaxMountCount() uint16 { return sb.MaxMountCountRaw }
-
-// FirstDataBlock implements SuperBlock.FirstDataBlock.
-func (sb *SuperBlockOld) FirstDataBlock() uint32 { return sb.FirstDataBlockRaw }
-
-// BlockSize implements SuperBlock.BlockSize.
-func (sb *SuperBlockOld) BlockSize() uint64 { return 1 << (10 + sb.LogBlockSize) }
-
-// BlocksPerGroup implements SuperBlock.BlocksPerGroup.
-func (sb *SuperBlockOld) BlocksPerGroup() uint32 { return sb.BlocksPerGroupRaw }
-
-// ClusterSize implements SuperBlock.ClusterSize.
-func (sb *SuperBlockOld) ClusterSize() uint64 { return 1 << (10 + sb.LogClusterSize) }
-
-// ClustersPerGroup implements SuperBlock.ClustersPerGroup.
-func (sb *SuperBlockOld) ClustersPerGroup() uint32 { return sb.ClustersPerGroupRaw }
-
-// InodeSize implements SuperBlock.InodeSize.
-func (sb *SuperBlockOld) InodeSize() uint16 { return OldInodeSize }
-
-// InodesPerGroup implements SuperBlock.InodesPerGroup.
-func (sb *SuperBlockOld) InodesPerGroup() uint32 { return sb.InodesPerGroupRaw }
-
-// BgDescSize implements SuperBlock.BgDescSize.
-func (sb *SuperBlockOld) BgDescSize() uint16 { return 32 }
-
-// CompatibleFeatures implements SuperBlock.CompatibleFeatures.
-func (sb *SuperBlockOld) CompatibleFeatures() CompatFeatures { return CompatFeatures{} }
-
-// IncompatibleFeatures implements SuperBlock.IncompatibleFeatures.
-func (sb *SuperBlockOld) IncompatibleFeatures() IncompatFeatures { return IncompatFeatures{} }
-
-// ReadOnlyCompatibleFeatures implements SuperBlock.ReadOnlyCompatibleFeatures.
-func (sb *SuperBlockOld) ReadOnlyCompatibleFeatures() RoCompatFeatures { return RoCompatFeatures{} }
-
-// Magic implements SuperBlock.Magic.
-func (sb *SuperBlockOld) Magic() uint16 { return sb.MagicRaw }
-
-// Revision implements SuperBlock.Revision.
-func (sb *SuperBlockOld) Revision() SbRevision { return SbRevision(sb.RevLevel) }
diff --git a/pkg/sentry/fsimpl/ext/disklayout/superblock_test.go b/pkg/sentry/fsimpl/ext/disklayout/superblock_test.go
deleted file mode 100644
index 463b5ba21..000000000
--- a/pkg/sentry/fsimpl/ext/disklayout/superblock_test.go
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package disklayout
-
-import (
- "testing"
-)
-
-// TestSuperBlockSize tests that the superblock structs are of the correct
-// size.
-func TestSuperBlockSize(t *testing.T) {
- assertSize(t, SuperBlockOld{}, 84)
- assertSize(t, SuperBlock32Bit{}, 336)
- assertSize(t, SuperBlock64Bit{}, 1024)
-}
diff --git a/pkg/sentry/fsimpl/ext/disklayout/test_utils.go b/pkg/sentry/fsimpl/ext/disklayout/test_utils.go
deleted file mode 100644
index 9c63f04c0..000000000
--- a/pkg/sentry/fsimpl/ext/disklayout/test_utils.go
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package disklayout
-
-import (
- "reflect"
- "testing"
-
- "gvisor.dev/gvisor/pkg/binary"
-)
-
-func assertSize(t *testing.T, v interface{}, want uintptr) {
- t.Helper()
-
- if got := binary.Size(v); got != want {
- t.Errorf("struct %s should be exactly %d bytes but is %d bytes", reflect.TypeOf(v).Name(), want, got)
- }
-}
diff --git a/pkg/sentry/fsimpl/ext/ext.go b/pkg/sentry/fsimpl/ext/ext.go
deleted file mode 100644
index f10accafc..000000000
--- a/pkg/sentry/fsimpl/ext/ext.go
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package ext implements readonly ext(2/3/4) filesystems.
-package ext
-
-import (
- "errors"
- "fmt"
- "io"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/fd"
- "gvisor.dev/gvisor/pkg/log"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/ext/disklayout"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-// FilesystemType implements vfs.FilesystemType.
-type FilesystemType struct{}
-
-// Compiles only if FilesystemType implements vfs.FilesystemType.
-var _ vfs.FilesystemType = (*FilesystemType)(nil)
-
-// getDeviceFd returns an io.ReaderAt to the underlying device.
-// Currently there are two ways of mounting an ext(2/3/4) fs:
-// 1. Specify a mount with our internal special MountType in the OCI spec.
-// 2. Expose the device to the container and mount it from application layer.
-func getDeviceFd(source string, opts vfs.NewFilesystemOptions) (io.ReaderAt, error) {
- if opts.InternalData == nil {
- // User mount call.
- // TODO(b/134676337): Open the device specified by `source` and return that.
- panic("unimplemented")
- }
-
- // NewFilesystem call originated from within the sentry.
- devFd, ok := opts.InternalData.(int)
- if !ok {
- return nil, errors.New("internal data for ext fs must be an int containing the file descriptor to device")
- }
-
- if devFd < 0 {
- return nil, fmt.Errorf("ext device file descriptor is not valid: %d", devFd)
- }
-
- // The fd.ReadWriter returned from fd.NewReadWriter() does not take ownership
- // of the file descriptor and hence will not close it when it is garbage
- // collected.
- return fd.NewReadWriter(devFd), nil
-}
-
-// isCompatible checks if the superblock has feature sets which are compatible.
-// We only need to check the superblock incompatible feature set since we are
-// mounting readonly. We will also need to check readonly compatible feature
-// set when mounting for read/write.
-func isCompatible(sb disklayout.SuperBlock) bool {
- // Please note that what is being checked is limited based on the fact that we
- // are mounting readonly and that we are not journaling. When mounting
- // read/write or with a journal, this must be reevaluated.
- incompatFeatures := sb.IncompatibleFeatures()
- if incompatFeatures.MetaBG {
- log.Warningf("ext fs: meta block groups are not supported")
- return false
- }
- if incompatFeatures.MMP {
- log.Warningf("ext fs: multiple mount protection is not supported")
- return false
- }
- if incompatFeatures.Encrypted {
- log.Warningf("ext fs: encrypted inodes not supported")
- return false
- }
- if incompatFeatures.InlineData {
- log.Warningf("ext fs: inline files not supported")
- return false
- }
- return true
-}
-
-// NewFilesystem implements vfs.FilesystemType.NewFilesystem.
-func (FilesystemType) NewFilesystem(ctx context.Context, creds *auth.Credentials, source string, opts vfs.NewFilesystemOptions) (*vfs.Filesystem, *vfs.Dentry, error) {
- // TODO(b/134676337): Ensure that the user is mounting readonly. If not,
- // EACCESS should be returned according to mount(2). Filesystem independent
- // flags (like readonly) are currently not available in pkg/sentry/vfs.
-
- dev, err := getDeviceFd(source, opts)
- if err != nil {
- return nil, nil, err
- }
-
- fs := filesystem{dev: dev, inodeCache: make(map[uint32]*inode)}
- fs.vfsfs.Init(&fs)
- fs.sb, err = readSuperBlock(dev)
- if err != nil {
- return nil, nil, err
- }
-
- if fs.sb.Magic() != linux.EXT_SUPER_MAGIC {
- // mount(2) specifies that EINVAL should be returned if the superblock is
- // invalid.
- return nil, nil, syserror.EINVAL
- }
-
- // Refuse to mount if the filesystem is incompatible.
- if !isCompatible(fs.sb) {
- return nil, nil, syserror.EINVAL
- }
-
- fs.bgs, err = readBlockGroups(dev, fs.sb)
- if err != nil {
- return nil, nil, err
- }
-
- rootInode, err := fs.getOrCreateInodeLocked(disklayout.RootDirInode)
- if err != nil {
- return nil, nil, err
- }
- rootInode.incRef()
-
- return &fs.vfsfs, &newDentry(rootInode).vfsd, nil
-}
diff --git a/pkg/sentry/fsimpl/ext/ext_test.go b/pkg/sentry/fsimpl/ext/ext_test.go
deleted file mode 100644
index 63cf7aeaf..000000000
--- a/pkg/sentry/fsimpl/ext/ext_test.go
+++ /dev/null
@@ -1,917 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ext
-
-import (
- "fmt"
- "io"
- "os"
- "path"
- "sort"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/ext/disklayout"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
- "gvisor.dev/gvisor/pkg/syserror"
-
- "gvisor.dev/gvisor/runsc/testutil"
-)
-
-const (
- assetsDir = "pkg/sentry/fsimpl/ext/assets"
-)
-
-var (
- ext2ImagePath = path.Join(assetsDir, "tiny.ext2")
- ext3ImagePath = path.Join(assetsDir, "tiny.ext3")
- ext4ImagePath = path.Join(assetsDir, "tiny.ext4")
-)
-
-// setUp opens imagePath as an ext Filesystem and returns all necessary
-// elements required to run tests. If error is non-nil, it also returns a tear
-// down function which must be called after the test is run for clean up.
-func setUp(t *testing.T, imagePath string) (context.Context, *vfs.VirtualFilesystem, *vfs.VirtualDentry, func(), error) {
- localImagePath, err := testutil.FindFile(imagePath)
- if err != nil {
- return nil, nil, nil, nil, fmt.Errorf("failed to open local image at path %s: %v", imagePath, err)
- }
-
- f, err := os.Open(localImagePath)
- if err != nil {
- return nil, nil, nil, nil, err
- }
-
- ctx := contexttest.Context(t)
- creds := auth.CredentialsFromContext(ctx)
-
- // Create VFS.
- vfsObj := vfs.New()
- vfsObj.MustRegisterFilesystemType("extfs", FilesystemType{})
- mntns, err := vfsObj.NewMountNamespace(ctx, creds, localImagePath, "extfs", &vfs.NewFilesystemOptions{InternalData: int(f.Fd())})
- if err != nil {
- f.Close()
- return nil, nil, nil, nil, err
- }
-
- root := mntns.Root()
-
- tearDown := func() {
- root.DecRef()
-
- if err := f.Close(); err != nil {
- t.Fatalf("tearDown failed: %v", err)
- }
- }
- return ctx, vfsObj, &root, tearDown, nil
-}
-
-// TODO(b/134676337): Test vfs.FilesystemImpl.ReadlinkAt and
-// vfs.FilesystemImpl.StatFSAt which are not implemented in
-// vfs.VirtualFilesystem yet.
-
-// TestSeek tests vfs.FileDescriptionImpl.Seek functionality.
-func TestSeek(t *testing.T) {
- type seekTest struct {
- name string
- image string
- path string
- }
-
- tests := []seekTest{
- {
- name: "ext4 root dir seek",
- image: ext4ImagePath,
- path: "/",
- },
- {
- name: "ext3 root dir seek",
- image: ext3ImagePath,
- path: "/",
- },
- {
- name: "ext2 root dir seek",
- image: ext2ImagePath,
- path: "/",
- },
- {
- name: "ext4 reg file seek",
- image: ext4ImagePath,
- path: "/file.txt",
- },
- {
- name: "ext3 reg file seek",
- image: ext3ImagePath,
- path: "/file.txt",
- },
- {
- name: "ext2 reg file seek",
- image: ext2ImagePath,
- path: "/file.txt",
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- ctx, vfsfs, root, tearDown, err := setUp(t, test.image)
- if err != nil {
- t.Fatalf("setUp failed: %v", err)
- }
- defer tearDown()
-
- fd, err := vfsfs.OpenAt(
- ctx,
- auth.CredentialsFromContext(ctx),
- &vfs.PathOperation{Root: *root, Start: *root, Pathname: test.path},
- &vfs.OpenOptions{},
- )
- if err != nil {
- t.Fatalf("vfsfs.OpenAt failed: %v", err)
- }
-
- if n, err := fd.Impl().Seek(ctx, 0, linux.SEEK_SET); n != 0 || err != nil {
- t.Errorf("expected seek position 0, got %d and error %v", n, err)
- }
-
- stat, err := fd.Impl().Stat(ctx, vfs.StatOptions{})
- if err != nil {
- t.Errorf("fd.stat failed for file %s in image %s: %v", test.path, test.image, err)
- }
-
- // We should be able to seek beyond the end of file.
- size := int64(stat.Size)
- if n, err := fd.Impl().Seek(ctx, size, linux.SEEK_SET); n != size || err != nil {
- t.Errorf("expected seek position %d, got %d and error %v", size, n, err)
- }
-
- // EINVAL should be returned if the resulting offset is negative.
- if _, err := fd.Impl().Seek(ctx, -1, linux.SEEK_SET); err != syserror.EINVAL {
- t.Errorf("expected error EINVAL but got %v", err)
- }
-
- if n, err := fd.Impl().Seek(ctx, 3, linux.SEEK_CUR); n != size+3 || err != nil {
- t.Errorf("expected seek position %d, got %d and error %v", size+3, n, err)
- }
-
- // Make sure negative offsets work with SEEK_CUR.
- if n, err := fd.Impl().Seek(ctx, -2, linux.SEEK_CUR); n != size+1 || err != nil {
- t.Errorf("expected seek position %d, got %d and error %v", size+1, n, err)
- }
-
- // EINVAL should be returned if the resulting offset is negative.
- if _, err := fd.Impl().Seek(ctx, -(size + 2), linux.SEEK_CUR); err != syserror.EINVAL {
- t.Errorf("expected error EINVAL but got %v", err)
- }
-
- // Make sure SEEK_END works with regular files.
- switch fd.Impl().(type) {
- case *regularFileFD:
- // Seek back to 0.
- if n, err := fd.Impl().Seek(ctx, -size, linux.SEEK_END); n != 0 || err != nil {
- t.Errorf("expected seek position %d, got %d and error %v", 0, n, err)
- }
-
- // Seek forward beyond EOF.
- if n, err := fd.Impl().Seek(ctx, 1, linux.SEEK_END); n != size+1 || err != nil {
- t.Errorf("expected seek position %d, got %d and error %v", size+1, n, err)
- }
-
- // EINVAL should be returned if the resulting offset is negative.
- if _, err := fd.Impl().Seek(ctx, -(size + 1), linux.SEEK_END); err != syserror.EINVAL {
- t.Errorf("expected error EINVAL but got %v", err)
- }
- }
- })
- }
-}
-
-// TestStatAt tests filesystem.StatAt functionality.
-func TestStatAt(t *testing.T) {
- type statAtTest struct {
- name string
- image string
- path string
- want linux.Statx
- }
-
- tests := []statAtTest{
- {
- name: "ext4 statx small file",
- image: ext4ImagePath,
- path: "/file.txt",
- want: linux.Statx{
- Blksize: 0x400,
- Nlink: 1,
- UID: 0,
- GID: 0,
- Mode: 0644 | linux.ModeRegular,
- Size: 13,
- },
- },
- {
- name: "ext3 statx small file",
- image: ext3ImagePath,
- path: "/file.txt",
- want: linux.Statx{
- Blksize: 0x400,
- Nlink: 1,
- UID: 0,
- GID: 0,
- Mode: 0644 | linux.ModeRegular,
- Size: 13,
- },
- },
- {
- name: "ext2 statx small file",
- image: ext2ImagePath,
- path: "/file.txt",
- want: linux.Statx{
- Blksize: 0x400,
- Nlink: 1,
- UID: 0,
- GID: 0,
- Mode: 0644 | linux.ModeRegular,
- Size: 13,
- },
- },
- {
- name: "ext4 statx big file",
- image: ext4ImagePath,
- path: "/bigfile.txt",
- want: linux.Statx{
- Blksize: 0x400,
- Nlink: 1,
- UID: 0,
- GID: 0,
- Mode: 0644 | linux.ModeRegular,
- Size: 13042,
- },
- },
- {
- name: "ext3 statx big file",
- image: ext3ImagePath,
- path: "/bigfile.txt",
- want: linux.Statx{
- Blksize: 0x400,
- Nlink: 1,
- UID: 0,
- GID: 0,
- Mode: 0644 | linux.ModeRegular,
- Size: 13042,
- },
- },
- {
- name: "ext2 statx big file",
- image: ext2ImagePath,
- path: "/bigfile.txt",
- want: linux.Statx{
- Blksize: 0x400,
- Nlink: 1,
- UID: 0,
- GID: 0,
- Mode: 0644 | linux.ModeRegular,
- Size: 13042,
- },
- },
- {
- name: "ext4 statx symlink file",
- image: ext4ImagePath,
- path: "/symlink.txt",
- want: linux.Statx{
- Blksize: 0x400,
- Nlink: 1,
- UID: 0,
- GID: 0,
- Mode: 0777 | linux.ModeSymlink,
- Size: 8,
- },
- },
- {
- name: "ext3 statx symlink file",
- image: ext3ImagePath,
- path: "/symlink.txt",
- want: linux.Statx{
- Blksize: 0x400,
- Nlink: 1,
- UID: 0,
- GID: 0,
- Mode: 0777 | linux.ModeSymlink,
- Size: 8,
- },
- },
- {
- name: "ext2 statx symlink file",
- image: ext2ImagePath,
- path: "/symlink.txt",
- want: linux.Statx{
- Blksize: 0x400,
- Nlink: 1,
- UID: 0,
- GID: 0,
- Mode: 0777 | linux.ModeSymlink,
- Size: 8,
- },
- },
- }
-
- // Ignore the fields that are not supported by filesystem.StatAt yet and
- // those which are likely to change as the image does.
- ignoredFields := map[string]bool{
- "Attributes": true,
- "AttributesMask": true,
- "Atime": true,
- "Blocks": true,
- "Btime": true,
- "Ctime": true,
- "DevMajor": true,
- "DevMinor": true,
- "Ino": true,
- "Mask": true,
- "Mtime": true,
- "RdevMajor": true,
- "RdevMinor": true,
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- ctx, vfsfs, root, tearDown, err := setUp(t, test.image)
- if err != nil {
- t.Fatalf("setUp failed: %v", err)
- }
- defer tearDown()
-
- got, err := vfsfs.StatAt(ctx,
- auth.CredentialsFromContext(ctx),
- &vfs.PathOperation{Root: *root, Start: *root, Pathname: test.path},
- &vfs.StatOptions{},
- )
- if err != nil {
- t.Fatalf("vfsfs.StatAt failed for file %s in image %s: %v", test.path, test.image, err)
- }
-
- cmpIgnoreFields := cmp.FilterPath(func(p cmp.Path) bool {
- _, ok := ignoredFields[p.String()]
- return ok
- }, cmp.Ignore())
- if diff := cmp.Diff(got, test.want, cmpIgnoreFields, cmpopts.IgnoreUnexported(linux.Statx{})); diff != "" {
- t.Errorf("stat mismatch (-want +got):\n%s", diff)
- }
- })
- }
-}
-
-// TestRead tests the read functionality for vfs file descriptions.
-func TestRead(t *testing.T) {
- type readTest struct {
- name string
- image string
- absPath string
- }
-
- tests := []readTest{
- {
- name: "ext4 read small file",
- image: ext4ImagePath,
- absPath: "/file.txt",
- },
- {
- name: "ext3 read small file",
- image: ext3ImagePath,
- absPath: "/file.txt",
- },
- {
- name: "ext2 read small file",
- image: ext2ImagePath,
- absPath: "/file.txt",
- },
- {
- name: "ext4 read big file",
- image: ext4ImagePath,
- absPath: "/bigfile.txt",
- },
- {
- name: "ext3 read big file",
- image: ext3ImagePath,
- absPath: "/bigfile.txt",
- },
- {
- name: "ext2 read big file",
- image: ext2ImagePath,
- absPath: "/bigfile.txt",
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- ctx, vfsfs, root, tearDown, err := setUp(t, test.image)
- if err != nil {
- t.Fatalf("setUp failed: %v", err)
- }
- defer tearDown()
-
- fd, err := vfsfs.OpenAt(
- ctx,
- auth.CredentialsFromContext(ctx),
- &vfs.PathOperation{Root: *root, Start: *root, Pathname: test.absPath},
- &vfs.OpenOptions{},
- )
- if err != nil {
- t.Fatalf("vfsfs.OpenAt failed: %v", err)
- }
-
- // Get a local file descriptor and compare its functionality with a vfs file
- // description for the same file.
- localFile, err := testutil.FindFile(path.Join(assetsDir, test.absPath))
- if err != nil {
- t.Fatalf("testutil.FindFile failed for %s: %v", test.absPath, err)
- }
-
- f, err := os.Open(localFile)
- if err != nil {
- t.Fatalf("os.Open failed for %s: %v", localFile, err)
- }
- defer f.Close()
-
- // Read the entire file by reading one byte repeatedly. Doing this stress
- // tests the underlying file reader implementation.
- got := make([]byte, 1)
- want := make([]byte, 1)
- for {
- n, err := f.Read(want)
- fd.Impl().Read(ctx, usermem.BytesIOSequence(got), vfs.ReadOptions{})
-
- if diff := cmp.Diff(got, want); diff != "" {
- t.Errorf("file data mismatch (-want +got):\n%s", diff)
- }
-
- // Make sure there is no more file data left after getting EOF.
- if n == 0 || err == io.EOF {
- if n, _ := fd.Impl().Read(ctx, usermem.BytesIOSequence(got), vfs.ReadOptions{}); n != 0 {
- t.Errorf("extra unexpected file data in file %s in image %s", test.absPath, test.image)
- }
-
- break
- }
-
- if err != nil {
- t.Fatalf("read failed: %v", err)
- }
- }
- })
- }
-}
-
-// iterDirentsCb is a simple callback which just keeps adding the dirents to an
-// internal list. Implements vfs.IterDirentsCallback.
-type iterDirentsCb struct {
- dirents []vfs.Dirent
-}
-
-// Compiles only if iterDirentCb implements vfs.IterDirentsCallback.
-var _ vfs.IterDirentsCallback = (*iterDirentsCb)(nil)
-
-// newIterDirentsCb is the iterDirent
-func newIterDirentCb() *iterDirentsCb {
- return &iterDirentsCb{dirents: make([]vfs.Dirent, 0)}
-}
-
-// Handle implements vfs.IterDirentsCallback.Handle.
-func (cb *iterDirentsCb) Handle(dirent vfs.Dirent) bool {
- cb.dirents = append(cb.dirents, dirent)
- return true
-}
-
-// TestIterDirents tests the FileDescriptionImpl.IterDirents functionality.
-func TestIterDirents(t *testing.T) {
- type iterDirentTest struct {
- name string
- image string
- path string
- want []vfs.Dirent
- }
-
- wantDirents := []vfs.Dirent{
- vfs.Dirent{
- Name: ".",
- Type: linux.DT_DIR,
- },
- vfs.Dirent{
- Name: "..",
- Type: linux.DT_DIR,
- },
- vfs.Dirent{
- Name: "lost+found",
- Type: linux.DT_DIR,
- },
- vfs.Dirent{
- Name: "file.txt",
- Type: linux.DT_REG,
- },
- vfs.Dirent{
- Name: "bigfile.txt",
- Type: linux.DT_REG,
- },
- vfs.Dirent{
- Name: "symlink.txt",
- Type: linux.DT_LNK,
- },
- }
- tests := []iterDirentTest{
- {
- name: "ext4 root dir iteration",
- image: ext4ImagePath,
- path: "/",
- want: wantDirents,
- },
- {
- name: "ext3 root dir iteration",
- image: ext3ImagePath,
- path: "/",
- want: wantDirents,
- },
- {
- name: "ext2 root dir iteration",
- image: ext2ImagePath,
- path: "/",
- want: wantDirents,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- ctx, vfsfs, root, tearDown, err := setUp(t, test.image)
- if err != nil {
- t.Fatalf("setUp failed: %v", err)
- }
- defer tearDown()
-
- fd, err := vfsfs.OpenAt(
- ctx,
- auth.CredentialsFromContext(ctx),
- &vfs.PathOperation{Root: *root, Start: *root, Pathname: test.path},
- &vfs.OpenOptions{},
- )
- if err != nil {
- t.Fatalf("vfsfs.OpenAt failed: %v", err)
- }
-
- cb := &iterDirentsCb{}
- if err = fd.Impl().IterDirents(ctx, cb); err != nil {
- t.Fatalf("dir fd.IterDirents() failed: %v", err)
- }
-
- sort.Slice(cb.dirents, func(i int, j int) bool { return cb.dirents[i].Name < cb.dirents[j].Name })
- sort.Slice(test.want, func(i int, j int) bool { return test.want[i].Name < test.want[j].Name })
-
- // Ignore the inode number and offset of dirents because those are likely to
- // change as the underlying image changes.
- cmpIgnoreFields := cmp.FilterPath(func(p cmp.Path) bool {
- return p.String() == "Ino" || p.String() == "Off"
- }, cmp.Ignore())
- if diff := cmp.Diff(cb.dirents, test.want, cmpIgnoreFields); diff != "" {
- t.Errorf("dirents mismatch (-want +got):\n%s", diff)
- }
- })
- }
-}
-
-// TestRootDir tests that the root directory inode is correctly initialized and
-// returned from setUp.
-func TestRootDir(t *testing.T) {
- type inodeProps struct {
- Mode linux.FileMode
- UID auth.KUID
- GID auth.KGID
- Size uint64
- InodeSize uint16
- Links uint16
- Flags disklayout.InodeFlags
- }
-
- type rootDirTest struct {
- name string
- image string
- wantInode inodeProps
- }
-
- tests := []rootDirTest{
- {
- name: "ext4 root dir",
- image: ext4ImagePath,
- wantInode: inodeProps{
- Mode: linux.ModeDirectory | 0755,
- Size: 0x400,
- InodeSize: 0x80,
- Links: 3,
- Flags: disklayout.InodeFlags{Extents: true},
- },
- },
- {
- name: "ext3 root dir",
- image: ext3ImagePath,
- wantInode: inodeProps{
- Mode: linux.ModeDirectory | 0755,
- Size: 0x400,
- InodeSize: 0x80,
- Links: 3,
- },
- },
- {
- name: "ext2 root dir",
- image: ext2ImagePath,
- wantInode: inodeProps{
- Mode: linux.ModeDirectory | 0755,
- Size: 0x400,
- InodeSize: 0x80,
- Links: 3,
- },
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- _, _, vd, tearDown, err := setUp(t, test.image)
- if err != nil {
- t.Fatalf("setUp failed: %v", err)
- }
- defer tearDown()
-
- d, ok := vd.Dentry().Impl().(*dentry)
- if !ok {
- t.Fatalf("ext dentry of incorrect type: %T", vd.Dentry().Impl())
- }
-
- // Offload inode contents into local structs for comparison.
- gotInode := inodeProps{
- Mode: d.inode.diskInode.Mode(),
- UID: d.inode.diskInode.UID(),
- GID: d.inode.diskInode.GID(),
- Size: d.inode.diskInode.Size(),
- InodeSize: d.inode.diskInode.InodeSize(),
- Links: d.inode.diskInode.LinksCount(),
- Flags: d.inode.diskInode.Flags(),
- }
-
- if diff := cmp.Diff(gotInode, test.wantInode); diff != "" {
- t.Errorf("inode mismatch (-want +got):\n%s", diff)
- }
- })
- }
-}
-
-// TestFilesystemInit tests that the filesystem superblock and block group
-// descriptors are correctly read in and initialized.
-func TestFilesystemInit(t *testing.T) {
- // sb only contains the immutable properties of the superblock.
- type sb struct {
- InodesCount uint32
- BlocksCount uint64
- MaxMountCount uint16
- FirstDataBlock uint32
- BlockSize uint64
- BlocksPerGroup uint32
- ClusterSize uint64
- ClustersPerGroup uint32
- InodeSize uint16
- InodesPerGroup uint32
- BgDescSize uint16
- Magic uint16
- Revision disklayout.SbRevision
- CompatFeatures disklayout.CompatFeatures
- IncompatFeatures disklayout.IncompatFeatures
- RoCompatFeatures disklayout.RoCompatFeatures
- }
-
- // bg only contains the immutable properties of the block group descriptor.
- type bg struct {
- InodeTable uint64
- BlockBitmap uint64
- InodeBitmap uint64
- ExclusionBitmap uint64
- Flags disklayout.BGFlags
- }
-
- type fsInitTest struct {
- name string
- image string
- wantSb sb
- wantBgs []bg
- }
-
- tests := []fsInitTest{
- {
- name: "ext4 filesystem init",
- image: ext4ImagePath,
- wantSb: sb{
- InodesCount: 0x10,
- BlocksCount: 0x40,
- MaxMountCount: 0xffff,
- FirstDataBlock: 0x1,
- BlockSize: 0x400,
- BlocksPerGroup: 0x2000,
- ClusterSize: 0x400,
- ClustersPerGroup: 0x2000,
- InodeSize: 0x80,
- InodesPerGroup: 0x10,
- BgDescSize: 0x40,
- Magic: linux.EXT_SUPER_MAGIC,
- Revision: disklayout.DynamicRev,
- CompatFeatures: disklayout.CompatFeatures{
- ExtAttr: true,
- ResizeInode: true,
- DirIndex: true,
- },
- IncompatFeatures: disklayout.IncompatFeatures{
- DirentFileType: true,
- Extents: true,
- Is64Bit: true,
- FlexBg: true,
- },
- RoCompatFeatures: disklayout.RoCompatFeatures{
- Sparse: true,
- LargeFile: true,
- HugeFile: true,
- DirNlink: true,
- ExtraIsize: true,
- MetadataCsum: true,
- },
- },
- wantBgs: []bg{
- {
- InodeTable: 0x23,
- BlockBitmap: 0x3,
- InodeBitmap: 0x13,
- Flags: disklayout.BGFlags{
- InodeZeroed: true,
- },
- },
- },
- },
- {
- name: "ext3 filesystem init",
- image: ext3ImagePath,
- wantSb: sb{
- InodesCount: 0x10,
- BlocksCount: 0x40,
- MaxMountCount: 0xffff,
- FirstDataBlock: 0x1,
- BlockSize: 0x400,
- BlocksPerGroup: 0x2000,
- ClusterSize: 0x400,
- ClustersPerGroup: 0x2000,
- InodeSize: 0x80,
- InodesPerGroup: 0x10,
- BgDescSize: 0x20,
- Magic: linux.EXT_SUPER_MAGIC,
- Revision: disklayout.DynamicRev,
- CompatFeatures: disklayout.CompatFeatures{
- ExtAttr: true,
- ResizeInode: true,
- DirIndex: true,
- },
- IncompatFeatures: disklayout.IncompatFeatures{
- DirentFileType: true,
- },
- RoCompatFeatures: disklayout.RoCompatFeatures{
- Sparse: true,
- LargeFile: true,
- },
- },
- wantBgs: []bg{
- {
- InodeTable: 0x5,
- BlockBitmap: 0x3,
- InodeBitmap: 0x4,
- Flags: disklayout.BGFlags{
- InodeZeroed: true,
- },
- },
- },
- },
- {
- name: "ext2 filesystem init",
- image: ext2ImagePath,
- wantSb: sb{
- InodesCount: 0x10,
- BlocksCount: 0x40,
- MaxMountCount: 0xffff,
- FirstDataBlock: 0x1,
- BlockSize: 0x400,
- BlocksPerGroup: 0x2000,
- ClusterSize: 0x400,
- ClustersPerGroup: 0x2000,
- InodeSize: 0x80,
- InodesPerGroup: 0x10,
- BgDescSize: 0x20,
- Magic: linux.EXT_SUPER_MAGIC,
- Revision: disklayout.DynamicRev,
- CompatFeatures: disklayout.CompatFeatures{
- ExtAttr: true,
- ResizeInode: true,
- DirIndex: true,
- },
- IncompatFeatures: disklayout.IncompatFeatures{
- DirentFileType: true,
- },
- RoCompatFeatures: disklayout.RoCompatFeatures{
- Sparse: true,
- LargeFile: true,
- },
- },
- wantBgs: []bg{
- {
- InodeTable: 0x5,
- BlockBitmap: 0x3,
- InodeBitmap: 0x4,
- Flags: disklayout.BGFlags{
- InodeZeroed: true,
- },
- },
- },
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- _, _, vd, tearDown, err := setUp(t, test.image)
- if err != nil {
- t.Fatalf("setUp failed: %v", err)
- }
- defer tearDown()
-
- fs, ok := vd.Mount().Filesystem().Impl().(*filesystem)
- if !ok {
- t.Fatalf("ext filesystem of incorrect type: %T", vd.Mount().Filesystem().Impl())
- }
-
- // Offload superblock and block group descriptors contents into
- // local structs for comparison.
- totalFreeInodes := uint32(0)
- totalFreeBlocks := uint64(0)
- gotSb := sb{
- InodesCount: fs.sb.InodesCount(),
- BlocksCount: fs.sb.BlocksCount(),
- MaxMountCount: fs.sb.MaxMountCount(),
- FirstDataBlock: fs.sb.FirstDataBlock(),
- BlockSize: fs.sb.BlockSize(),
- BlocksPerGroup: fs.sb.BlocksPerGroup(),
- ClusterSize: fs.sb.ClusterSize(),
- ClustersPerGroup: fs.sb.ClustersPerGroup(),
- InodeSize: fs.sb.InodeSize(),
- InodesPerGroup: fs.sb.InodesPerGroup(),
- BgDescSize: fs.sb.BgDescSize(),
- Magic: fs.sb.Magic(),
- Revision: fs.sb.Revision(),
- CompatFeatures: fs.sb.CompatibleFeatures(),
- IncompatFeatures: fs.sb.IncompatibleFeatures(),
- RoCompatFeatures: fs.sb.ReadOnlyCompatibleFeatures(),
- }
- gotNumBgs := len(fs.bgs)
- gotBgs := make([]bg, gotNumBgs)
- for i := 0; i < gotNumBgs; i++ {
- gotBgs[i].InodeTable = fs.bgs[i].InodeTable()
- gotBgs[i].BlockBitmap = fs.bgs[i].BlockBitmap()
- gotBgs[i].InodeBitmap = fs.bgs[i].InodeBitmap()
- gotBgs[i].ExclusionBitmap = fs.bgs[i].ExclusionBitmap()
- gotBgs[i].Flags = fs.bgs[i].Flags()
-
- totalFreeInodes += fs.bgs[i].FreeInodesCount()
- totalFreeBlocks += uint64(fs.bgs[i].FreeBlocksCount())
- }
-
- if diff := cmp.Diff(gotSb, test.wantSb); diff != "" {
- t.Errorf("superblock mismatch (-want +got):\n%s", diff)
- }
-
- if diff := cmp.Diff(gotBgs, test.wantBgs); diff != "" {
- t.Errorf("block group descriptors mismatch (-want +got):\n%s", diff)
- }
-
- if diff := cmp.Diff(totalFreeInodes, fs.sb.FreeInodesCount()); diff != "" {
- t.Errorf("total free inodes mismatch (-want +got):\n%s", diff)
- }
-
- if diff := cmp.Diff(totalFreeBlocks, fs.sb.FreeBlocksCount()); diff != "" {
- t.Errorf("total free blocks mismatch (-want +got):\n%s", diff)
- }
- })
- }
-}
diff --git a/pkg/sentry/fsimpl/ext/extent_file.go b/pkg/sentry/fsimpl/ext/extent_file.go
deleted file mode 100644
index 38b68a2d3..000000000
--- a/pkg/sentry/fsimpl/ext/extent_file.go
+++ /dev/null
@@ -1,237 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ext
-
-import (
- "io"
- "sort"
-
- "gvisor.dev/gvisor/pkg/binary"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/ext/disklayout"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-// extentFile is a type of regular file which uses extents to store file data.
-type extentFile struct {
- regFile regularFile
-
- // root is the root extent node. This lives in the 60 byte diskInode.Data().
- // Immutable.
- root disklayout.ExtentNode
-}
-
-// Compiles only if extentFile implements io.ReaderAt.
-var _ io.ReaderAt = (*extentFile)(nil)
-
-// newExtentFile is the extent file constructor. It reads the entire extent
-// tree into memory.
-// TODO(b/134676337): Build extent tree on demand to reduce memory usage.
-func newExtentFile(regFile regularFile) (*extentFile, error) {
- file := &extentFile{regFile: regFile}
- file.regFile.impl = file
- err := file.buildExtTree()
- if err != nil {
- return nil, err
- }
- return file, nil
-}
-
-// buildExtTree builds the extent tree by reading it from disk by doing
-// running a simple DFS. It first reads the root node from the inode struct in
-// memory. Then it recursively builds the rest of the tree by reading it off
-// disk.
-//
-// Precondition: inode flag InExtents must be set.
-func (f *extentFile) buildExtTree() error {
- rootNodeData := f.regFile.inode.diskInode.Data()
-
- binary.Unmarshal(rootNodeData[:disklayout.ExtentStructsSize], binary.LittleEndian, &f.root.Header)
-
- // Root node can not have more than 4 entries: 60 bytes = 1 header + 4 entries.
- if f.root.Header.NumEntries > 4 {
- // read(2) specifies that EINVAL should be returned if the file is unsuitable
- // for reading.
- return syserror.EINVAL
- }
-
- f.root.Entries = make([]disklayout.ExtentEntryPair, f.root.Header.NumEntries)
- for i, off := uint16(0), disklayout.ExtentStructsSize; i < f.root.Header.NumEntries; i, off = i+1, off+disklayout.ExtentStructsSize {
- var curEntry disklayout.ExtentEntry
- if f.root.Header.Height == 0 {
- // Leaf node.
- curEntry = &disklayout.Extent{}
- } else {
- // Internal node.
- curEntry = &disklayout.ExtentIdx{}
- }
- binary.Unmarshal(rootNodeData[off:off+disklayout.ExtentStructsSize], binary.LittleEndian, curEntry)
- f.root.Entries[i].Entry = curEntry
- }
-
- // If this node is internal, perform DFS.
- if f.root.Header.Height > 0 {
- for i := uint16(0); i < f.root.Header.NumEntries; i++ {
- var err error
- if f.root.Entries[i].Node, err = f.buildExtTreeFromDisk(f.root.Entries[i].Entry); err != nil {
- return err
- }
- }
- }
-
- return nil
-}
-
-// buildExtTreeFromDisk reads the extent tree nodes from disk and recursively
-// builds the tree. Performs a simple DFS. It returns the ExtentNode pointed to
-// by the ExtentEntry.
-func (f *extentFile) buildExtTreeFromDisk(entry disklayout.ExtentEntry) (*disklayout.ExtentNode, error) {
- var header disklayout.ExtentHeader
- off := entry.PhysicalBlock() * f.regFile.inode.blkSize
- err := readFromDisk(f.regFile.inode.dev, int64(off), &header)
- if err != nil {
- return nil, err
- }
-
- entries := make([]disklayout.ExtentEntryPair, header.NumEntries)
- for i, off := uint16(0), off+disklayout.ExtentStructsSize; i < header.NumEntries; i, off = i+1, off+disklayout.ExtentStructsSize {
- var curEntry disklayout.ExtentEntry
- if header.Height == 0 {
- // Leaf node.
- curEntry = &disklayout.Extent{}
- } else {
- // Internal node.
- curEntry = &disklayout.ExtentIdx{}
- }
-
- err := readFromDisk(f.regFile.inode.dev, int64(off), curEntry)
- if err != nil {
- return nil, err
- }
- entries[i].Entry = curEntry
- }
-
- // If this node is internal, perform DFS.
- if header.Height > 0 {
- for i := uint16(0); i < header.NumEntries; i++ {
- var err error
- entries[i].Node, err = f.buildExtTreeFromDisk(entries[i].Entry)
- if err != nil {
- return nil, err
- }
- }
- }
-
- return &disklayout.ExtentNode{header, entries}, nil
-}
-
-// ReadAt implements io.ReaderAt.ReadAt.
-func (f *extentFile) ReadAt(dst []byte, off int64) (int, error) {
- if len(dst) == 0 {
- return 0, nil
- }
-
- if off < 0 {
- return 0, syserror.EINVAL
- }
-
- if uint64(off) >= f.regFile.inode.diskInode.Size() {
- return 0, io.EOF
- }
-
- n, err := f.read(&f.root, uint64(off), dst)
- if n < len(dst) && err == nil {
- err = io.EOF
- }
- return n, err
-}
-
-// read is the recursive step of extentFile.ReadAt which traverses the extent
-// tree from the node passed and reads file data.
-func (f *extentFile) read(node *disklayout.ExtentNode, off uint64, dst []byte) (int, error) {
- // Perform a binary search for the node covering bytes starting at r.fileOff.
- // A highly fragmented filesystem can have upto 340 entries and so linear
- // search should be avoided. Finds the first entry which does not cover the
- // file block we want and subtracts 1 to get the desired index.
- fileBlk := uint32(off / f.regFile.inode.blkSize)
- n := len(node.Entries)
- found := sort.Search(n, func(i int) bool {
- return node.Entries[i].Entry.FileBlock() > fileBlk
- }) - 1
-
- // We should be in this recursive step only if the data we want exists under
- // the current node.
- if found < 0 {
- panic("searching for a file block in an extent entry which does not cover it")
- }
-
- read := 0
- toRead := len(dst)
- var curR int
- var err error
- for i := found; i < n && read < toRead; i++ {
- if node.Header.Height == 0 {
- curR, err = f.readFromExtent(node.Entries[i].Entry.(*disklayout.Extent), off, dst[read:])
- } else {
- curR, err = f.read(node.Entries[i].Node, off, dst[read:])
- }
-
- read += curR
- off += uint64(curR)
- if err != nil {
- return read, err
- }
- }
-
- return read, nil
-}
-
-// readFromExtent reads file data from the extent. It takes advantage of the
-// sequential nature of extents and reads file data from multiple blocks in one
-// call.
-//
-// A non-nil error indicates that this is a partial read and there is probably
-// more to read from this extent. The caller should propagate the error upward
-// and not move to the next extent in the tree.
-//
-// A subsequent call to extentReader.Read should continue reading from where we
-// left off as expected.
-func (f *extentFile) readFromExtent(ex *disklayout.Extent, off uint64, dst []byte) (int, error) {
- curFileBlk := uint32(off / f.regFile.inode.blkSize)
- exFirstFileBlk := ex.FileBlock()
- exLastFileBlk := exFirstFileBlk + uint32(ex.Length) // This is exclusive.
-
- // We should be in this recursive step only if the data we want exists under
- // the current extent.
- if curFileBlk < exFirstFileBlk || exLastFileBlk <= curFileBlk {
- panic("searching for a file block in an extent which does not cover it")
- }
-
- curPhyBlk := uint64(curFileBlk-exFirstFileBlk) + ex.PhysicalBlock()
- readStart := curPhyBlk*f.regFile.inode.blkSize + (off % f.regFile.inode.blkSize)
-
- endPhyBlk := ex.PhysicalBlock() + uint64(ex.Length)
- extentEnd := endPhyBlk * f.regFile.inode.blkSize // This is exclusive.
-
- toRead := int(extentEnd - readStart)
- if len(dst) < toRead {
- toRead = len(dst)
- }
-
- n, _ := f.regFile.inode.dev.ReadAt(dst[:toRead], int64(readStart))
- if n < toRead {
- return n, syserror.EIO
- }
- return n, nil
-}
diff --git a/pkg/sentry/fsimpl/ext/extent_test.go b/pkg/sentry/fsimpl/ext/extent_test.go
deleted file mode 100644
index 42d0a484b..000000000
--- a/pkg/sentry/fsimpl/ext/extent_test.go
+++ /dev/null
@@ -1,265 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ext
-
-import (
- "bytes"
- "math/rand"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "gvisor.dev/gvisor/pkg/binary"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/ext/disklayout"
-)
-
-const (
- // mockExtentBlkSize is the mock block size used for testing.
- // No block has more than 1 header + 4 entries.
- mockExtentBlkSize = uint64(64)
-)
-
-// The tree described below looks like:
-//
-// 0.{Head}[Idx][Idx]
-// / \
-// / \
-// 1.{Head}[Ext][Ext] 2.{Head}[Idx]
-// / | \
-// [Phy] [Phy, Phy] 3.{Head}[Ext]
-// |
-// [Phy, Phy, Phy]
-//
-// Legend:
-// - Head = ExtentHeader
-// - Idx = ExtentIdx
-// - Ext = Extent
-// - Phy = Physical Block
-//
-// Please note that ext4 might not construct extent trees looking like this.
-// This is purely for testing the tree traversal logic.
-var (
- node3 = &disklayout.ExtentNode{
- Header: disklayout.ExtentHeader{
- Magic: disklayout.ExtentMagic,
- NumEntries: 1,
- MaxEntries: 4,
- Height: 0,
- },
- Entries: []disklayout.ExtentEntryPair{
- {
- Entry: &disklayout.Extent{
- FirstFileBlock: 3,
- Length: 3,
- StartBlockLo: 6,
- },
- Node: nil,
- },
- },
- }
-
- node2 = &disklayout.ExtentNode{
- Header: disklayout.ExtentHeader{
- Magic: disklayout.ExtentMagic,
- NumEntries: 1,
- MaxEntries: 4,
- Height: 1,
- },
- Entries: []disklayout.ExtentEntryPair{
- {
- Entry: &disklayout.ExtentIdx{
- FirstFileBlock: 3,
- ChildBlockLo: 2,
- },
- Node: node3,
- },
- },
- }
-
- node1 = &disklayout.ExtentNode{
- Header: disklayout.ExtentHeader{
- Magic: disklayout.ExtentMagic,
- NumEntries: 2,
- MaxEntries: 4,
- Height: 0,
- },
- Entries: []disklayout.ExtentEntryPair{
- {
- Entry: &disklayout.Extent{
- FirstFileBlock: 0,
- Length: 1,
- StartBlockLo: 3,
- },
- Node: nil,
- },
- {
- Entry: &disklayout.Extent{
- FirstFileBlock: 1,
- Length: 2,
- StartBlockLo: 4,
- },
- Node: nil,
- },
- },
- }
-
- node0 = &disklayout.ExtentNode{
- Header: disklayout.ExtentHeader{
- Magic: disklayout.ExtentMagic,
- NumEntries: 2,
- MaxEntries: 4,
- Height: 2,
- },
- Entries: []disklayout.ExtentEntryPair{
- {
- Entry: &disklayout.ExtentIdx{
- FirstFileBlock: 0,
- ChildBlockLo: 0,
- },
- Node: node1,
- },
- {
- Entry: &disklayout.ExtentIdx{
- FirstFileBlock: 3,
- ChildBlockLo: 1,
- },
- Node: node2,
- },
- },
- }
-)
-
-// TestExtentReader stress tests extentReader functionality. It performs random
-// length reads from all possible positions in the extent tree.
-func TestExtentReader(t *testing.T) {
- mockExtentFile, want := extentTreeSetUp(t, node0)
- n := len(want)
-
- for from := 0; from < n; from++ {
- got := make([]byte, n-from)
-
- if read, err := mockExtentFile.ReadAt(got, int64(from)); err != nil {
- t.Fatalf("file read operation from offset %d to %d only read %d bytes: %v", from, n, read, err)
- }
-
- if diff := cmp.Diff(got, want[from:]); diff != "" {
- t.Fatalf("file data from offset %d to %d mismatched (-want +got):\n%s", from, n, diff)
- }
- }
-}
-
-// TestBuildExtentTree tests the extent tree building logic.
-func TestBuildExtentTree(t *testing.T) {
- mockExtentFile, _ := extentTreeSetUp(t, node0)
-
- opt := cmpopts.IgnoreUnexported(disklayout.ExtentIdx{}, disklayout.ExtentHeader{})
- if diff := cmp.Diff(&mockExtentFile.root, node0, opt); diff != "" {
- t.Errorf("extent tree mismatch (-want +got):\n%s", diff)
- }
-}
-
-// extentTreeSetUp writes the passed extent tree to a mock disk as an extent
-// tree. It also constucts a mock extent file with the same tree built in it.
-// It also writes random data file data and returns it.
-func extentTreeSetUp(t *testing.T, root *disklayout.ExtentNode) (*extentFile, []byte) {
- t.Helper()
-
- mockDisk := make([]byte, mockExtentBlkSize*10)
- mockExtentFile := &extentFile{
- regFile: regularFile{
- inode: inode{
- diskInode: &disklayout.InodeNew{
- InodeOld: disklayout.InodeOld{
- SizeLo: uint32(mockExtentBlkSize) * getNumPhyBlks(root),
- },
- },
- blkSize: mockExtentBlkSize,
- dev: bytes.NewReader(mockDisk),
- },
- },
- }
-
- fileData := writeTree(&mockExtentFile.regFile.inode, mockDisk, node0, mockExtentBlkSize)
-
- if err := mockExtentFile.buildExtTree(); err != nil {
- t.Fatalf("inode.buildExtTree failed: %v", err)
- }
- return mockExtentFile, fileData
-}
-
-// writeTree writes the tree represented by `root` to the inode and disk. It
-// also writes random file data on disk.
-func writeTree(in *inode, disk []byte, root *disklayout.ExtentNode, mockExtentBlkSize uint64) []byte {
- rootData := binary.Marshal(nil, binary.LittleEndian, root.Header)
- for _, ep := range root.Entries {
- rootData = binary.Marshal(rootData, binary.LittleEndian, ep.Entry)
- }
-
- copy(in.diskInode.Data(), rootData)
-
- var fileData []byte
- for _, ep := range root.Entries {
- if root.Header.Height == 0 {
- fileData = append(fileData, writeFileDataToExtent(disk, ep.Entry.(*disklayout.Extent))...)
- } else {
- fileData = append(fileData, writeTreeToDisk(disk, ep)...)
- }
- }
- return fileData
-}
-
-// writeTreeToDisk is the recursive step for writeTree which writes the tree
-// on the disk only. Also writes random file data on disk.
-func writeTreeToDisk(disk []byte, curNode disklayout.ExtentEntryPair) []byte {
- nodeData := binary.Marshal(nil, binary.LittleEndian, curNode.Node.Header)
- for _, ep := range curNode.Node.Entries {
- nodeData = binary.Marshal(nodeData, binary.LittleEndian, ep.Entry)
- }
-
- copy(disk[curNode.Entry.PhysicalBlock()*mockExtentBlkSize:], nodeData)
-
- var fileData []byte
- for _, ep := range curNode.Node.Entries {
- if curNode.Node.Header.Height == 0 {
- fileData = append(fileData, writeFileDataToExtent(disk, ep.Entry.(*disklayout.Extent))...)
- } else {
- fileData = append(fileData, writeTreeToDisk(disk, ep)...)
- }
- }
- return fileData
-}
-
-// writeFileDataToExtent writes random bytes to the blocks on disk that the
-// passed extent points to.
-func writeFileDataToExtent(disk []byte, ex *disklayout.Extent) []byte {
- phyExStartBlk := ex.PhysicalBlock()
- phyExStartOff := phyExStartBlk * mockExtentBlkSize
- phyExEndOff := phyExStartOff + uint64(ex.Length)*mockExtentBlkSize
- rand.Read(disk[phyExStartOff:phyExEndOff])
- return disk[phyExStartOff:phyExEndOff]
-}
-
-// getNumPhyBlks returns the number of physical blocks covered under the node.
-func getNumPhyBlks(node *disklayout.ExtentNode) uint32 {
- var res uint32
- for _, ep := range node.Entries {
- if node.Header.Height == 0 {
- res += uint32(ep.Entry.(*disklayout.Extent).Length)
- } else {
- res += getNumPhyBlks(ep.Node)
- }
- }
- return res
-}
diff --git a/pkg/sentry/fsimpl/ext/file_description.go b/pkg/sentry/fsimpl/ext/file_description.go
deleted file mode 100644
index a0065343b..000000000
--- a/pkg/sentry/fsimpl/ext/file_description.go
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ext
-
-import (
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-// fileDescription is embedded by ext implementations of
-// vfs.FileDescriptionImpl.
-type fileDescription struct {
- vfsfd vfs.FileDescription
- vfs.FileDescriptionDefaultImpl
-
- // flags is the same as vfs.OpenOptions.Flags which are passed to
- // vfs.FilesystemImpl.OpenAt.
- // TODO(b/134676337): syscalls like read(2), write(2), fchmod(2), fchown(2),
- // fgetxattr(2), ioctl(2), mmap(2) should fail with EBADF if O_PATH is set.
- // Only close(2), fstat(2), fstatfs(2) should work.
- flags uint32
-}
-
-func (fd *fileDescription) filesystem() *filesystem {
- return fd.vfsfd.VirtualDentry().Mount().Filesystem().Impl().(*filesystem)
-}
-
-func (fd *fileDescription) inode() *inode {
- return fd.vfsfd.VirtualDentry().Dentry().Impl().(*dentry).inode
-}
-
-// OnClose implements vfs.FileDescriptionImpl.OnClose.
-func (fd *fileDescription) OnClose() error { return nil }
-
-// StatusFlags implements vfs.FileDescriptionImpl.StatusFlags.
-func (fd *fileDescription) StatusFlags(ctx context.Context) (uint32, error) {
- return fd.flags, nil
-}
-
-// SetStatusFlags implements vfs.FileDescriptionImpl.SetStatusFlags.
-func (fd *fileDescription) SetStatusFlags(ctx context.Context, flags uint32) error {
- // None of the flags settable by fcntl(F_SETFL) are supported, so this is a
- // no-op.
- return nil
-}
-
-// Stat implements vfs.FileDescriptionImpl.Stat.
-func (fd *fileDescription) Stat(ctx context.Context, opts vfs.StatOptions) (linux.Statx, error) {
- var stat linux.Statx
- fd.inode().statTo(&stat)
- return stat, nil
-}
-
-// SetStat implements vfs.FileDescriptionImpl.SetStat.
-func (fd *fileDescription) SetStat(ctx context.Context, opts vfs.SetStatOptions) error {
- if opts.Stat.Mask == 0 {
- return nil
- }
- return syserror.EPERM
-}
-
-// SetStat implements vfs.FileDescriptionImpl.StatFS.
-func (fd *fileDescription) StatFS(ctx context.Context) (linux.Statfs, error) {
- var stat linux.Statfs
- fd.filesystem().statTo(&stat)
- return stat, nil
-}
-
-// Sync implements vfs.FileDescriptionImpl.Sync.
-func (fd *fileDescription) Sync(ctx context.Context) error {
- return nil
-}
diff --git a/pkg/sentry/fsimpl/ext/filesystem.go b/pkg/sentry/fsimpl/ext/filesystem.go
deleted file mode 100644
index 2d15e8aaf..000000000
--- a/pkg/sentry/fsimpl/ext/filesystem.go
+++ /dev/null
@@ -1,443 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ext
-
-import (
- "errors"
- "io"
- "sync"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/ext/disklayout"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-var (
- // errResolveDirent indicates that the vfs.ResolvingPath.Component() does
- // not exist on the dentry tree but does exist on disk. So it has to be read in
- // using the in-memory dirent and added to the dentry tree. Usually indicates
- // the need to lock filesystem.mu for writing.
- errResolveDirent = errors.New("resolve path component using dirent")
-)
-
-// filesystem implements vfs.FilesystemImpl.
-type filesystem struct {
- vfsfs vfs.Filesystem
-
- // mu serializes changes to the Dentry tree.
- mu sync.RWMutex
-
- // dev represents the underlying fs device. It does not require protection
- // because io.ReaderAt permits concurrent read calls to it. It translates to
- // the pread syscall which passes on the read request directly to the device
- // driver. Device drivers are intelligent in serving multiple concurrent read
- // requests in the optimal order (taking locality into consideration).
- dev io.ReaderAt
-
- // inodeCache maps absolute inode numbers to the corresponding Inode struct.
- // Inodes should be removed from this once their reference count hits 0.
- //
- // Protected by mu because most additions (see IterDirents) and all removals
- // from this corresponds to a change in the dentry tree.
- inodeCache map[uint32]*inode
-
- // sb represents the filesystem superblock. Immutable after initialization.
- sb disklayout.SuperBlock
-
- // bgs represents all the block group descriptors for the filesystem.
- // Immutable after initialization.
- bgs []disklayout.BlockGroup
-}
-
-// Compiles only if filesystem implements vfs.FilesystemImpl.
-var _ vfs.FilesystemImpl = (*filesystem)(nil)
-
-// stepLocked resolves rp.Component() in parent directory vfsd. The write
-// parameter passed tells if the caller has acquired filesystem.mu for writing
-// or not. If set to true, an existing inode on disk can be added to the dentry
-// tree if not present already.
-//
-// stepLocked is loosely analogous to fs/namei.c:walk_component().
-//
-// Preconditions:
-// - filesystem.mu must be locked (for writing if write param is true).
-// - !rp.Done().
-// - inode == vfsd.Impl().(*Dentry).inode.
-func stepLocked(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, inode *inode, write bool) (*vfs.Dentry, *inode, error) {
- if !inode.isDir() {
- return nil, nil, syserror.ENOTDIR
- }
- if err := inode.checkPermissions(rp.Credentials(), vfs.MayExec); err != nil {
- return nil, nil, err
- }
-
- for {
- nextVFSD, err := rp.ResolveComponent(vfsd)
- if err != nil {
- return nil, nil, err
- }
- if nextVFSD == nil {
- // Since the Dentry tree is not the sole source of truth for extfs, if it's
- // not in the Dentry tree, it might need to be pulled from disk.
- childDirent, ok := inode.impl.(*directory).childMap[rp.Component()]
- if !ok {
- // The underlying inode does not exist on disk.
- return nil, nil, syserror.ENOENT
- }
-
- if !write {
- // filesystem.mu must be held for writing to add to the dentry tree.
- return nil, nil, errResolveDirent
- }
-
- // Create and add the component's dirent to the dentry tree.
- fs := rp.Mount().Filesystem().Impl().(*filesystem)
- childInode, err := fs.getOrCreateInodeLocked(childDirent.diskDirent.Inode())
- if err != nil {
- return nil, nil, err
- }
- // incRef because this is being added to the dentry tree.
- childInode.incRef()
- child := newDentry(childInode)
- vfsd.InsertChild(&child.vfsd, rp.Component())
-
- // Continue as usual now that nextVFSD is not nil.
- nextVFSD = &child.vfsd
- }
- nextInode := nextVFSD.Impl().(*dentry).inode
- if nextInode.isSymlink() && rp.ShouldFollowSymlink() {
- if err := rp.HandleSymlink(inode.impl.(*symlink).target); err != nil {
- return nil, nil, err
- }
- continue
- }
- rp.Advance()
- return nextVFSD, nextInode, nil
- }
-}
-
-// walkLocked resolves rp to an existing file. The write parameter
-// passed tells if the caller has acquired filesystem.mu for writing or not.
-// If set to true, additions can be made to the dentry tree while walking.
-// If errResolveDirent is returned, the walk needs to be continued with an
-// upgraded filesystem.mu.
-//
-// walkLocked is loosely analogous to Linux's fs/namei.c:path_lookupat().
-//
-// Preconditions:
-// - filesystem.mu must be locked (for writing if write param is true).
-func walkLocked(rp *vfs.ResolvingPath, write bool) (*vfs.Dentry, *inode, error) {
- vfsd := rp.Start()
- inode := vfsd.Impl().(*dentry).inode
- for !rp.Done() {
- var err error
- vfsd, inode, err = stepLocked(rp, vfsd, inode, write)
- if err != nil {
- return nil, nil, err
- }
- }
- if rp.MustBeDir() && !inode.isDir() {
- return nil, nil, syserror.ENOTDIR
- }
- return vfsd, inode, nil
-}
-
-// walkParentLocked resolves all but the last path component of rp to an
-// existing directory. It does not check that the returned directory is
-// searchable by the provider of rp. The write parameter passed tells if the
-// caller has acquired filesystem.mu for writing or not. If set to true,
-// additions can be made to the dentry tree while walking.
-// If errResolveDirent is returned, the walk needs to be continued with an
-// upgraded filesystem.mu.
-//
-// walkParentLocked is loosely analogous to Linux's fs/namei.c:path_parentat().
-//
-// Preconditions:
-// - filesystem.mu must be locked (for writing if write param is true).
-// - !rp.Done().
-func walkParentLocked(rp *vfs.ResolvingPath, write bool) (*vfs.Dentry, *inode, error) {
- vfsd := rp.Start()
- inode := vfsd.Impl().(*dentry).inode
- for !rp.Final() {
- var err error
- vfsd, inode, err = stepLocked(rp, vfsd, inode, write)
- if err != nil {
- return nil, nil, err
- }
- }
- if !inode.isDir() {
- return nil, nil, syserror.ENOTDIR
- }
- return vfsd, inode, nil
-}
-
-// walk resolves rp to an existing file. If parent is set to true, it resolves
-// the rp till the parent of the last component which should be an existing
-// directory. If parent is false then resolves rp entirely. Attemps to resolve
-// the path as far as it can with a read lock and upgrades the lock if needed.
-func (fs *filesystem) walk(rp *vfs.ResolvingPath, parent bool) (*vfs.Dentry, *inode, error) {
- var (
- vfsd *vfs.Dentry
- inode *inode
- err error
- )
-
- // Try walking with the hopes that all dentries have already been pulled out
- // of disk. This reduces congestion (allows concurrent walks).
- fs.mu.RLock()
- if parent {
- vfsd, inode, err = walkParentLocked(rp, false)
- } else {
- vfsd, inode, err = walkLocked(rp, false)
- }
- fs.mu.RUnlock()
-
- if err == errResolveDirent {
- // Upgrade lock and continue walking. Lock upgrading in the middle of the
- // walk is fine as this is a read only filesystem.
- fs.mu.Lock()
- if parent {
- vfsd, inode, err = walkParentLocked(rp, true)
- } else {
- vfsd, inode, err = walkLocked(rp, true)
- }
- fs.mu.Unlock()
- }
-
- return vfsd, inode, err
-}
-
-// getOrCreateInodeLocked gets the inode corresponding to the inode number passed in.
-// It creates a new one with the given inode number if one does not exist.
-// The caller must increment the ref count if adding this to the dentry tree.
-//
-// Precondition: must be holding fs.mu for writing.
-func (fs *filesystem) getOrCreateInodeLocked(inodeNum uint32) (*inode, error) {
- if in, ok := fs.inodeCache[inodeNum]; ok {
- return in, nil
- }
-
- in, err := newInode(fs, inodeNum)
- if err != nil {
- return nil, err
- }
-
- fs.inodeCache[inodeNum] = in
- return in, nil
-}
-
-// statTo writes the statfs fields to the output parameter.
-func (fs *filesystem) statTo(stat *linux.Statfs) {
- stat.Type = uint64(fs.sb.Magic())
- stat.BlockSize = int64(fs.sb.BlockSize())
- stat.Blocks = fs.sb.BlocksCount()
- stat.BlocksFree = fs.sb.FreeBlocksCount()
- stat.BlocksAvailable = fs.sb.FreeBlocksCount()
- stat.Files = uint64(fs.sb.InodesCount())
- stat.FilesFree = uint64(fs.sb.FreeInodesCount())
- stat.NameLength = disklayout.MaxFileName
- stat.FragmentSize = int64(fs.sb.BlockSize())
- // TODO(b/134676337): Set Statfs.Flags and Statfs.FSID.
-}
-
-// GetDentryAt implements vfs.FilesystemImpl.GetDentryAt.
-func (fs *filesystem) GetDentryAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.GetDentryOptions) (*vfs.Dentry, error) {
- vfsd, inode, err := fs.walk(rp, false)
- if err != nil {
- return nil, err
- }
-
- if opts.CheckSearchable {
- if !inode.isDir() {
- return nil, syserror.ENOTDIR
- }
- if err := inode.checkPermissions(rp.Credentials(), vfs.MayExec); err != nil {
- return nil, err
- }
- }
-
- inode.incRef()
- return vfsd, nil
-}
-
-// OpenAt implements vfs.FilesystemImpl.OpenAt.
-func (fs *filesystem) OpenAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
- vfsd, inode, err := fs.walk(rp, false)
- if err != nil {
- return nil, err
- }
-
- // EROFS is returned if write access is needed.
- if vfs.MayWriteFileWithOpenFlags(opts.Flags) || opts.Flags&(linux.O_CREAT|linux.O_EXCL|linux.O_TMPFILE) != 0 {
- return nil, syserror.EROFS
- }
- return inode.open(rp, vfsd, opts.Flags)
-}
-
-// ReadlinkAt implements vfs.FilesystemImpl.ReadlinkAt.
-func (fs *filesystem) ReadlinkAt(ctx context.Context, rp *vfs.ResolvingPath) (string, error) {
- _, inode, err := fs.walk(rp, false)
- if err != nil {
- return "", err
- }
- symlink, ok := inode.impl.(*symlink)
- if !ok {
- return "", syserror.EINVAL
- }
- return symlink.target, nil
-}
-
-// StatAt implements vfs.FilesystemImpl.StatAt.
-func (fs *filesystem) StatAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.StatOptions) (linux.Statx, error) {
- _, inode, err := fs.walk(rp, false)
- if err != nil {
- return linux.Statx{}, err
- }
- var stat linux.Statx
- inode.statTo(&stat)
- return stat, nil
-}
-
-// StatFSAt implements vfs.FilesystemImpl.StatFSAt.
-func (fs *filesystem) StatFSAt(ctx context.Context, rp *vfs.ResolvingPath) (linux.Statfs, error) {
- if _, _, err := fs.walk(rp, false); err != nil {
- return linux.Statfs{}, err
- }
-
- var stat linux.Statfs
- fs.statTo(&stat)
- return stat, nil
-}
-
-// Release implements vfs.FilesystemImpl.Release.
-func (fs *filesystem) Release() {}
-
-// Sync implements vfs.FilesystemImpl.Sync.
-func (fs *filesystem) Sync(ctx context.Context) error {
- // This is a readonly filesystem for now.
- return nil
-}
-
-// The vfs.FilesystemImpl functions below return EROFS because their respective
-// man pages say that EROFS must be returned if the path resolves to a file on
-// this read-only filesystem.
-
-// LinkAt implements vfs.FilesystemImpl.LinkAt.
-func (fs *filesystem) LinkAt(ctx context.Context, rp *vfs.ResolvingPath, vd vfs.VirtualDentry) error {
- if rp.Done() {
- return syserror.EEXIST
- }
-
- if _, _, err := fs.walk(rp, true); err != nil {
- return err
- }
-
- return syserror.EROFS
-}
-
-// MkdirAt implements vfs.FilesystemImpl.MkdirAt.
-func (fs *filesystem) MkdirAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.MkdirOptions) error {
- if rp.Done() {
- return syserror.EEXIST
- }
-
- if _, _, err := fs.walk(rp, true); err != nil {
- return err
- }
-
- return syserror.EROFS
-}
-
-// MknodAt implements vfs.FilesystemImpl.MknodAt.
-func (fs *filesystem) MknodAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.MknodOptions) error {
- if rp.Done() {
- return syserror.EEXIST
- }
-
- _, _, err := fs.walk(rp, true)
- if err != nil {
- return err
- }
-
- return syserror.EROFS
-}
-
-// RenameAt implements vfs.FilesystemImpl.RenameAt.
-func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, vd vfs.VirtualDentry, opts vfs.RenameOptions) error {
- if rp.Done() {
- return syserror.ENOENT
- }
-
- _, _, err := fs.walk(rp, false)
- if err != nil {
- return err
- }
-
- return syserror.EROFS
-}
-
-// RmdirAt implements vfs.FilesystemImpl.RmdirAt.
-func (fs *filesystem) RmdirAt(ctx context.Context, rp *vfs.ResolvingPath) error {
- _, inode, err := fs.walk(rp, false)
- if err != nil {
- return err
- }
-
- if !inode.isDir() {
- return syserror.ENOTDIR
- }
-
- return syserror.EROFS
-}
-
-// SetStatAt implements vfs.FilesystemImpl.SetStatAt.
-func (fs *filesystem) SetStatAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.SetStatOptions) error {
- _, _, err := fs.walk(rp, false)
- if err != nil {
- return err
- }
-
- return syserror.EROFS
-}
-
-// SymlinkAt implements vfs.FilesystemImpl.SymlinkAt.
-func (fs *filesystem) SymlinkAt(ctx context.Context, rp *vfs.ResolvingPath, target string) error {
- if rp.Done() {
- return syserror.EEXIST
- }
-
- _, _, err := fs.walk(rp, true)
- if err != nil {
- return err
- }
-
- return syserror.EROFS
-}
-
-// UnlinkAt implements vfs.FilesystemImpl.UnlinkAt.
-func (fs *filesystem) UnlinkAt(ctx context.Context, rp *vfs.ResolvingPath) error {
- _, inode, err := fs.walk(rp, false)
- if err != nil {
- return err
- }
-
- if inode.isDir() {
- return syserror.EISDIR
- }
-
- return syserror.EROFS
-}
diff --git a/pkg/sentry/fsimpl/ext/inode.go b/pkg/sentry/fsimpl/ext/inode.go
deleted file mode 100644
index e6c847a71..000000000
--- a/pkg/sentry/fsimpl/ext/inode.go
+++ /dev/null
@@ -1,219 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ext
-
-import (
- "fmt"
- "io"
- "sync/atomic"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/ext/disklayout"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-// inode represents an ext inode.
-//
-// inode uses the same inheritance pattern that pkg/sentry/vfs structures use.
-// This has been done to increase memory locality.
-//
-// Implementations:
-// inode --
-// |-- dir
-// |-- symlink
-// |-- regular--
-// |-- extent file
-// |-- block map file
-type inode struct {
- // refs is a reference count. refs is accessed using atomic memory operations.
- refs int64
-
- // inodeNum is the inode number of this inode on disk. This is used to
- // identify inodes within the ext filesystem.
- inodeNum uint32
-
- // dev represents the underlying device. Same as filesystem.dev.
- dev io.ReaderAt
-
- // blkSize is the fs data block size. Same as filesystem.sb.BlockSize().
- blkSize uint64
-
- // diskInode gives us access to the inode struct on disk. Immutable.
- diskInode disklayout.Inode
-
- // This is immutable. The first field of the implementations must have inode
- // as the first field to ensure temporality.
- impl interface{}
-}
-
-// incRef increments the inode ref count.
-func (in *inode) incRef() {
- atomic.AddInt64(&in.refs, 1)
-}
-
-// tryIncRef tries to increment the ref count. Returns true if successful.
-func (in *inode) tryIncRef() bool {
- for {
- refs := atomic.LoadInt64(&in.refs)
- if refs == 0 {
- return false
- }
- if atomic.CompareAndSwapInt64(&in.refs, refs, refs+1) {
- return true
- }
- }
-}
-
-// decRef decrements the inode ref count and releases the inode resources if
-// the ref count hits 0.
-//
-// Precondition: Must have locked fs.mu.
-func (in *inode) decRef(fs *filesystem) {
- if refs := atomic.AddInt64(&in.refs, -1); refs == 0 {
- delete(fs.inodeCache, in.inodeNum)
- } else if refs < 0 {
- panic("ext.inode.decRef() called without holding a reference")
- }
-}
-
-// newInode is the inode constructor. Reads the inode off disk. Identifies
-// inodes based on the absolute inode number on disk.
-func newInode(fs *filesystem, inodeNum uint32) (*inode, error) {
- if inodeNum == 0 {
- panic("inode number 0 on ext filesystems is not possible")
- }
-
- inodeRecordSize := fs.sb.InodeSize()
- var diskInode disklayout.Inode
- if inodeRecordSize == disklayout.OldInodeSize {
- diskInode = &disklayout.InodeOld{}
- } else {
- diskInode = &disklayout.InodeNew{}
- }
-
- // Calculate where the inode is actually placed.
- inodesPerGrp := fs.sb.InodesPerGroup()
- blkSize := fs.sb.BlockSize()
- inodeTableOff := fs.bgs[getBGNum(inodeNum, inodesPerGrp)].InodeTable() * blkSize
- inodeOff := inodeTableOff + uint64(uint32(inodeRecordSize)*getBGOff(inodeNum, inodesPerGrp))
-
- if err := readFromDisk(fs.dev, int64(inodeOff), diskInode); err != nil {
- return nil, err
- }
-
- // Build the inode based on its type.
- inode := inode{
- inodeNum: inodeNum,
- dev: fs.dev,
- blkSize: blkSize,
- diskInode: diskInode,
- }
-
- switch diskInode.Mode().FileType() {
- case linux.ModeSymlink:
- f, err := newSymlink(inode)
- if err != nil {
- return nil, err
- }
- return &f.inode, nil
- case linux.ModeRegular:
- f, err := newRegularFile(inode)
- if err != nil {
- return nil, err
- }
- return &f.inode, nil
- case linux.ModeDirectory:
- f, err := newDirectroy(inode, fs.sb.IncompatibleFeatures().DirentFileType)
- if err != nil {
- return nil, err
- }
- return &f.inode, nil
- default:
- // TODO(b/134676337): Return appropriate errors for sockets, pipes and devices.
- return nil, syserror.EINVAL
- }
-}
-
-// open creates and returns a file description for the dentry passed in.
-func (in *inode) open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, flags uint32) (*vfs.FileDescription, error) {
- ats := vfs.AccessTypesForOpenFlags(flags)
- if err := in.checkPermissions(rp.Credentials(), ats); err != nil {
- return nil, err
- }
- switch in.impl.(type) {
- case *regularFile:
- var fd regularFileFD
- fd.flags = flags
- fd.vfsfd.Init(&fd, rp.Mount(), vfsd)
- return &fd.vfsfd, nil
- case *directory:
- // Can't open directories writably. This check is not necessary for a read
- // only filesystem but will be required when write is implemented.
- if ats&vfs.MayWrite != 0 {
- return nil, syserror.EISDIR
- }
- var fd directoryFD
- fd.vfsfd.Init(&fd, rp.Mount(), vfsd)
- fd.flags = flags
- return &fd.vfsfd, nil
- case *symlink:
- if flags&linux.O_PATH == 0 {
- // Can't open symlinks without O_PATH.
- return nil, syserror.ELOOP
- }
- var fd symlinkFD
- fd.flags = flags
- fd.vfsfd.Init(&fd, rp.Mount(), vfsd)
- return &fd.vfsfd, nil
- default:
- panic(fmt.Sprintf("unknown inode type: %T", in.impl))
- }
-}
-
-func (in *inode) checkPermissions(creds *auth.Credentials, ats vfs.AccessTypes) error {
- return vfs.GenericCheckPermissions(creds, ats, in.isDir(), uint16(in.diskInode.Mode()), in.diskInode.UID(), in.diskInode.GID())
-}
-
-// statTo writes the statx fields to the output parameter.
-func (in *inode) statTo(stat *linux.Statx) {
- stat.Mask = linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_NLINK |
- linux.STATX_UID | linux.STATX_GID | linux.STATX_INO | linux.STATX_SIZE |
- linux.STATX_ATIME | linux.STATX_CTIME | linux.STATX_MTIME
- stat.Blksize = uint32(in.blkSize)
- stat.Mode = uint16(in.diskInode.Mode())
- stat.Nlink = uint32(in.diskInode.LinksCount())
- stat.UID = uint32(in.diskInode.UID())
- stat.GID = uint32(in.diskInode.GID())
- stat.Ino = uint64(in.inodeNum)
- stat.Size = in.diskInode.Size()
- stat.Atime = in.diskInode.AccessTime().StatxTimestamp()
- stat.Ctime = in.diskInode.ChangeTime().StatxTimestamp()
- stat.Mtime = in.diskInode.ModificationTime().StatxTimestamp()
- // TODO(b/134676337): Set stat.Blocks which is the number of 512 byte blocks
- // (including metadata blocks) required to represent this file.
-}
-
-// getBGNum returns the block group number that a given inode belongs to.
-func getBGNum(inodeNum uint32, inodesPerGrp uint32) uint32 {
- return (inodeNum - 1) / inodesPerGrp
-}
-
-// getBGOff returns the offset at which the given inode lives in the block
-// group's inode table, i.e. the index of the inode in the inode table.
-func getBGOff(inodeNum uint32, inodesPerGrp uint32) uint32 {
- return (inodeNum - 1) % inodesPerGrp
-}
diff --git a/pkg/sentry/fsimpl/ext/regular_file.go b/pkg/sentry/fsimpl/ext/regular_file.go
deleted file mode 100644
index ffc76ba5b..000000000
--- a/pkg/sentry/fsimpl/ext/regular_file.go
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ext
-
-import (
- "io"
- "sync"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/memmap"
- "gvisor.dev/gvisor/pkg/sentry/safemem"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-// regularFile represents a regular file's inode. This too follows the
-// inheritance pattern prevelant in the vfs layer described in
-// pkg/sentry/vfs/README.md.
-type regularFile struct {
- inode inode
-
- // This is immutable. The first field of fileReader implementations must be
- // regularFile to ensure temporality.
- // io.ReaderAt is more strict than io.Reader in the sense that a partial read
- // is always accompanied by an error. If a read spans past the end of file, a
- // partial read (within file range) is done and io.EOF is returned.
- impl io.ReaderAt
-}
-
-// newRegularFile is the regularFile constructor. It figures out what kind of
-// file this is and initializes the fileReader.
-func newRegularFile(inode inode) (*regularFile, error) {
- regFile := regularFile{
- inode: inode,
- }
-
- inodeFlags := inode.diskInode.Flags()
-
- if inodeFlags.Extents {
- file, err := newExtentFile(regFile)
- if err != nil {
- return nil, err
- }
-
- file.regFile.inode.impl = &file.regFile
- return &file.regFile, nil
- }
-
- file, err := newBlockMapFile(regFile)
- if err != nil {
- return nil, err
- }
- file.regFile.inode.impl = &file.regFile
- return &file.regFile, nil
-}
-
-func (in *inode) isRegular() bool {
- _, ok := in.impl.(*regularFile)
- return ok
-}
-
-// directoryFD represents a directory file description. It implements
-// vfs.FileDescriptionImpl.
-type regularFileFD struct {
- fileDescription
-
- // off is the file offset. off is accessed using atomic memory operations.
- off int64
-
- // offMu serializes operations that may mutate off.
- offMu sync.Mutex
-}
-
-// Release implements vfs.FileDescriptionImpl.Release.
-func (fd *regularFileFD) Release() {}
-
-// PRead implements vfs.FileDescriptionImpl.PRead.
-func (fd *regularFileFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
- safeReader := safemem.FromIOReaderAt{
- ReaderAt: fd.inode().impl.(*regularFile).impl,
- Offset: offset,
- }
-
- // Copies data from disk directly into usermem without any intermediate
- // allocations (if dst is converted into BlockSeq such that it does not need
- // safe copying).
- return dst.CopyOutFrom(ctx, safeReader)
-}
-
-// Read implements vfs.FileDescriptionImpl.Read.
-func (fd *regularFileFD) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
- n, err := fd.PRead(ctx, dst, fd.off, opts)
- fd.offMu.Lock()
- fd.off += n
- fd.offMu.Unlock()
- return n, err
-}
-
-// PWrite implements vfs.FileDescriptionImpl.PWrite.
-func (fd *regularFileFD) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
- // write(2) specifies that EBADF must be returned if the fd is not open for
- // writing.
- return 0, syserror.EBADF
-}
-
-// Write implements vfs.FileDescriptionImpl.Write.
-func (fd *regularFileFD) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
- n, err := fd.PWrite(ctx, src, fd.off, opts)
- fd.offMu.Lock()
- fd.off += n
- fd.offMu.Unlock()
- return n, err
-}
-
-// IterDirents implements vfs.FileDescriptionImpl.IterDirents.
-func (fd *regularFileFD) IterDirents(ctx context.Context, cb vfs.IterDirentsCallback) error {
- return syserror.ENOTDIR
-}
-
-// Seek implements vfs.FileDescriptionImpl.Seek.
-func (fd *regularFileFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
- fd.offMu.Lock()
- defer fd.offMu.Unlock()
- switch whence {
- case linux.SEEK_SET:
- // Use offset as specified.
- case linux.SEEK_CUR:
- offset += fd.off
- case linux.SEEK_END:
- offset += int64(fd.inode().diskInode.Size())
- default:
- return 0, syserror.EINVAL
- }
- if offset < 0 {
- return 0, syserror.EINVAL
- }
- fd.off = offset
- return offset, nil
-}
-
-// IterDirents implements vfs.FileDescriptionImpl.IterDirents.
-func (fd *regularFileFD) ConfigureMMap(ctx context.Context, opts memmap.MMapOpts) error {
- // TODO(b/134676337): Implement mmap(2).
- return syserror.ENODEV
-}
diff --git a/pkg/sentry/fsimpl/ext/symlink.go b/pkg/sentry/fsimpl/ext/symlink.go
deleted file mode 100644
index e06548a98..000000000
--- a/pkg/sentry/fsimpl/ext/symlink.go
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ext
-
-import (
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/memmap"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-// symlink represents a symlink inode.
-type symlink struct {
- inode inode
- target string // immutable
-}
-
-// newSymlink is the symlink constructor. It reads out the symlink target from
-// the inode (however it might have been stored).
-func newSymlink(inode inode) (*symlink, error) {
- var file *symlink
- var link []byte
-
- // If the symlink target is lesser than 60 bytes, its stores in inode.Data().
- // Otherwise either extents or block maps will be used to store the link.
- size := inode.diskInode.Size()
- if size < 60 {
- link = inode.diskInode.Data()[:size]
- } else {
- // Create a regular file out of this inode and read out the target.
- regFile, err := newRegularFile(inode)
- if err != nil {
- return nil, err
- }
-
- link = make([]byte, size)
- if n, err := regFile.impl.ReadAt(link, 0); uint64(n) < size {
- return nil, err
- }
- }
-
- file = &symlink{inode: inode, target: string(link)}
- file.inode.impl = file
- return file, nil
-}
-
-func (in *inode) isSymlink() bool {
- _, ok := in.impl.(*symlink)
- return ok
-}
-
-// symlinkFD represents a symlink file description and implements implements
-// vfs.FileDescriptionImpl. which may only be used if open options contains
-// O_PATH. For this reason most of the functions return EBADF.
-type symlinkFD struct {
- fileDescription
-}
-
-// Compiles only if symlinkFD implements vfs.FileDescriptionImpl.
-var _ vfs.FileDescriptionImpl = (*symlinkFD)(nil)
-
-// Release implements vfs.FileDescriptionImpl.Release.
-func (fd *symlinkFD) Release() {}
-
-// PRead implements vfs.FileDescriptionImpl.PRead.
-func (fd *symlinkFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
- return 0, syserror.EBADF
-}
-
-// Read implements vfs.FileDescriptionImpl.Read.
-func (fd *symlinkFD) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
- return 0, syserror.EBADF
-}
-
-// PWrite implements vfs.FileDescriptionImpl.PWrite.
-func (fd *symlinkFD) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
- return 0, syserror.EBADF
-}
-
-// Write implements vfs.FileDescriptionImpl.Write.
-func (fd *symlinkFD) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
- return 0, syserror.EBADF
-}
-
-// IterDirents implements vfs.FileDescriptionImpl.IterDirents.
-func (fd *symlinkFD) IterDirents(ctx context.Context, cb vfs.IterDirentsCallback) error {
- return syserror.ENOTDIR
-}
-
-// Seek implements vfs.FileDescriptionImpl.Seek.
-func (fd *symlinkFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
- return 0, syserror.EBADF
-}
-
-// IterDirents implements vfs.FileDescriptionImpl.IterDirents.
-func (fd *symlinkFD) ConfigureMMap(ctx context.Context, opts memmap.MMapOpts) error {
- return syserror.EBADF
-}
diff --git a/pkg/sentry/fsimpl/ext/utils.go b/pkg/sentry/fsimpl/ext/utils.go
deleted file mode 100644
index d8b728f8c..000000000
--- a/pkg/sentry/fsimpl/ext/utils.go
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ext
-
-import (
- "io"
-
- "gvisor.dev/gvisor/pkg/binary"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/ext/disklayout"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-// readFromDisk performs a binary read from disk into the given struct from
-// the absolute offset provided.
-func readFromDisk(dev io.ReaderAt, abOff int64, v interface{}) error {
- n := binary.Size(v)
- buf := make([]byte, n)
- if read, _ := dev.ReadAt(buf, abOff); read < int(n) {
- return syserror.EIO
- }
-
- binary.Unmarshal(buf, binary.LittleEndian, v)
- return nil
-}
-
-// readSuperBlock reads the SuperBlock from block group 0 in the underlying
-// device. There are three versions of the superblock. This function identifies
-// and returns the correct version.
-func readSuperBlock(dev io.ReaderAt) (disklayout.SuperBlock, error) {
- var sb disklayout.SuperBlock = &disklayout.SuperBlockOld{}
- if err := readFromDisk(dev, disklayout.SbOffset, sb); err != nil {
- return nil, err
- }
- if sb.Revision() == disklayout.OldRev {
- return sb, nil
- }
-
- sb = &disklayout.SuperBlock32Bit{}
- if err := readFromDisk(dev, disklayout.SbOffset, sb); err != nil {
- return nil, err
- }
- if !sb.IncompatibleFeatures().Is64Bit {
- return sb, nil
- }
-
- sb = &disklayout.SuperBlock64Bit{}
- if err := readFromDisk(dev, disklayout.SbOffset, sb); err != nil {
- return nil, err
- }
- return sb, nil
-}
-
-// blockGroupsCount returns the number of block groups in the ext fs.
-func blockGroupsCount(sb disklayout.SuperBlock) uint64 {
- blocksCount := sb.BlocksCount()
- blocksPerGroup := uint64(sb.BlocksPerGroup())
-
- // Round up the result. float64 can compromise precision so do it manually.
- return (blocksCount + blocksPerGroup - 1) / blocksPerGroup
-}
-
-// readBlockGroups reads the block group descriptor table from block group 0 in
-// the underlying device.
-func readBlockGroups(dev io.ReaderAt, sb disklayout.SuperBlock) ([]disklayout.BlockGroup, error) {
- bgCount := blockGroupsCount(sb)
- bgdSize := uint64(sb.BgDescSize())
- is64Bit := sb.IncompatibleFeatures().Is64Bit
- bgds := make([]disklayout.BlockGroup, bgCount)
-
- for i, off := uint64(0), uint64(sb.FirstDataBlock()+1)*sb.BlockSize(); i < bgCount; i, off = i+1, off+bgdSize {
- if is64Bit {
- bgds[i] = &disklayout.BlockGroup64Bit{}
- } else {
- bgds[i] = &disklayout.BlockGroup32Bit{}
- }
-
- if err := readFromDisk(dev, int64(off), bgds[i]); err != nil {
- return nil, err
- }
- }
- return bgds, nil
-}
diff --git a/pkg/sentry/fsimpl/memfs/BUILD b/pkg/sentry/fsimpl/memfs/BUILD
deleted file mode 100644
index 7e364c5fd..000000000
--- a/pkg/sentry/fsimpl/memfs/BUILD
+++ /dev/null
@@ -1,56 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-
-go_template_instance(
- name = "dentry_list",
- out = "dentry_list.go",
- package = "memfs",
- prefix = "dentry",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*dentry",
- "Linker": "*dentry",
- },
-)
-
-go_library(
- name = "memfs",
- srcs = [
- "dentry_list.go",
- "directory.go",
- "filesystem.go",
- "memfs.go",
- "regular_file.go",
- "symlink.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fsimpl/memfs",
- deps = [
- "//pkg/abi/linux",
- "//pkg/sentry/context",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/usermem",
- "//pkg/sentry/vfs",
- "//pkg/syserror",
- ],
-)
-
-go_test(
- name = "benchmark_test",
- size = "small",
- srcs = ["benchmark_test.go"],
- deps = [
- ":memfs",
- "//pkg/abi/linux",
- "//pkg/sentry/context",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/tmpfs",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/vfs",
- "//pkg/syserror",
- ],
-)
diff --git a/pkg/sentry/fsimpl/memfs/benchmark_test.go b/pkg/sentry/fsimpl/memfs/benchmark_test.go
deleted file mode 100644
index a94b17db6..000000000
--- a/pkg/sentry/fsimpl/memfs/benchmark_test.go
+++ /dev/null
@@ -1,464 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package benchmark_test
-
-import (
- "fmt"
- "runtime"
- "strings"
- "testing"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- _ "gvisor.dev/gvisor/pkg/sentry/fs/tmpfs"
- "gvisor.dev/gvisor/pkg/sentry/fsimpl/memfs"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-// Differences from stat_benchmark:
-//
-// - Syscall interception, CopyInPath, copyOutStat, and overlayfs overheads are
-// not included.
-//
-// - *MountStat benchmarks use a tmpfs root mount and a tmpfs submount at /tmp.
-// Non-MountStat benchmarks use a tmpfs root mount and no submounts.
-// stat_benchmark uses a varying root mount, a tmpfs submount at /tmp, and a
-// subdirectory /tmp/<top_dir> (assuming TEST_TMPDIR == "/tmp"). Thus
-// stat_benchmark at depth 1 does a comparable amount of work to *MountStat
-// benchmarks at depth 2, and non-MountStat benchmarks at depth 3.
-var depths = []int{1, 2, 3, 8, 64, 100}
-
-const (
- mountPointName = "tmp"
- filename = "gvisor_test_temp_0_1557494568"
-)
-
-// This is copied from syscalls/linux/sys_file.go, with the dependency on
-// kernel.Task stripped out.
-func fileOpOn(ctx context.Context, mntns *fs.MountNamespace, root, wd *fs.Dirent, dirFD int32, path string, resolve bool, fn func(root *fs.Dirent, d *fs.Dirent) error) error {
- var (
- d *fs.Dirent // The file.
- rel *fs.Dirent // The relative directory for search (if required.)
- err error
- )
-
- // Extract the working directory (maybe).
- if len(path) > 0 && path[0] == '/' {
- // Absolute path; rel can be nil.
- } else if dirFD == linux.AT_FDCWD {
- // Need to reference the working directory.
- rel = wd
- } else {
- // Need to extract the given FD.
- return syserror.EBADF
- }
-
- // Lookup the node.
- remainingTraversals := uint(linux.MaxSymlinkTraversals)
- if resolve {
- d, err = mntns.FindInode(ctx, root, rel, path, &remainingTraversals)
- } else {
- d, err = mntns.FindLink(ctx, root, rel, path, &remainingTraversals)
- }
- if err != nil {
- return err
- }
-
- err = fn(root, d)
- d.DecRef()
- return err
-}
-
-func BenchmarkVFS1TmpfsStat(b *testing.B) {
- for _, depth := range depths {
- b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
- ctx := contexttest.Context(b)
-
- // Create VFS.
- tmpfsFS, ok := fs.FindFilesystem("tmpfs")
- if !ok {
- b.Fatalf("failed to find tmpfs filesystem type")
- }
- rootInode, err := tmpfsFS.Mount(ctx, "tmpfs", fs.MountSourceFlags{}, "", nil)
- if err != nil {
- b.Fatalf("failed to create tmpfs root mount: %v", err)
- }
- mntns, err := fs.NewMountNamespace(ctx, rootInode)
- if err != nil {
- b.Fatalf("failed to create mount namespace: %v", err)
- }
- defer mntns.DecRef()
-
- var filePathBuilder strings.Builder
- filePathBuilder.WriteByte('/')
-
- // Create nested directories with given depth.
- root := mntns.Root()
- defer root.DecRef()
- d := root
- d.IncRef()
- defer d.DecRef()
- for i := depth; i > 0; i-- {
- name := fmt.Sprintf("%d", i)
- if err := d.Inode.CreateDirectory(ctx, d, name, fs.FilePermsFromMode(0755)); err != nil {
- b.Fatalf("failed to create directory %q: %v", name, err)
- }
- next, err := d.Walk(ctx, root, name)
- if err != nil {
- b.Fatalf("failed to walk to directory %q: %v", name, err)
- }
- d.DecRef()
- d = next
- filePathBuilder.WriteString(name)
- filePathBuilder.WriteByte('/')
- }
-
- // Create the file that will be stat'd.
- file, err := d.Inode.Create(ctx, d, filename, fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0644))
- if err != nil {
- b.Fatalf("failed to create file %q: %v", filename, err)
- }
- file.DecRef()
- filePathBuilder.WriteString(filename)
- filePath := filePathBuilder.String()
-
- dirPath := false
- runtime.GC()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- err := fileOpOn(ctx, mntns, root, root, linux.AT_FDCWD, filePath, true /* resolve */, func(root *fs.Dirent, d *fs.Dirent) error {
- if dirPath && !fs.IsDir(d.Inode.StableAttr) {
- return syserror.ENOTDIR
- }
- uattr, err := d.Inode.UnstableAttr(ctx)
- if err != nil {
- return err
- }
- // Sanity check.
- if uattr.Perms.User.Execute {
- b.Fatalf("got wrong permissions (%0o)", uattr.Perms.LinuxMode())
- }
- return nil
- })
- if err != nil {
- b.Fatalf("stat(%q) failed: %v", filePath, err)
- }
- }
- })
- }
-}
-
-func BenchmarkVFS2MemfsStat(b *testing.B) {
- for _, depth := range depths {
- b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
- ctx := contexttest.Context(b)
- creds := auth.CredentialsFromContext(ctx)
-
- // Create VFS.
- vfsObj := vfs.New()
- vfsObj.MustRegisterFilesystemType("memfs", memfs.FilesystemType{})
- mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "memfs", &vfs.NewFilesystemOptions{})
- if err != nil {
- b.Fatalf("failed to create tmpfs root mount: %v", err)
- }
-
- var filePathBuilder strings.Builder
- filePathBuilder.WriteByte('/')
-
- // Create nested directories with given depth.
- root := mntns.Root()
- defer root.DecRef()
- vd := root
- vd.IncRef()
- defer vd.DecRef()
- for i := depth; i > 0; i-- {
- name := fmt.Sprintf("%d", i)
- pop := vfs.PathOperation{
- Root: root,
- Start: vd,
- Pathname: name,
- }
- if err := vfsObj.MkdirAt(ctx, creds, &pop, &vfs.MkdirOptions{
- Mode: 0755,
- }); err != nil {
- b.Fatalf("failed to create directory %q: %v", name, err)
- }
- nextVD, err := vfsObj.GetDentryAt(ctx, creds, &pop, &vfs.GetDentryOptions{})
- if err != nil {
- b.Fatalf("failed to walk to directory %q: %v", name, err)
- }
- vd.DecRef()
- vd = nextVD
- filePathBuilder.WriteString(name)
- filePathBuilder.WriteByte('/')
- }
-
- // Create the file that will be stat'd.
- fd, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{
- Root: root,
- Start: vd,
- Pathname: filename,
- FollowFinalSymlink: true,
- }, &vfs.OpenOptions{
- Flags: linux.O_RDWR | linux.O_CREAT | linux.O_EXCL,
- Mode: 0644,
- })
- if err != nil {
- b.Fatalf("failed to create file %q: %v", filename, err)
- }
- defer fd.DecRef()
- filePathBuilder.WriteString(filename)
- filePath := filePathBuilder.String()
-
- runtime.GC()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- stat, err := vfsObj.StatAt(ctx, creds, &vfs.PathOperation{
- Root: root,
- Start: root,
- Pathname: filePath,
- FollowFinalSymlink: true,
- }, &vfs.StatOptions{})
- if err != nil {
- b.Fatalf("stat(%q) failed: %v", filePath, err)
- }
- // Sanity check.
- if stat.Mode&^linux.S_IFMT != 0644 {
- b.Fatalf("got wrong permissions (%0o)", stat.Mode)
- }
- }
- })
- }
-}
-
-func BenchmarkVFS1TmpfsMountStat(b *testing.B) {
- for _, depth := range depths {
- b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
- ctx := contexttest.Context(b)
-
- // Create VFS.
- tmpfsFS, ok := fs.FindFilesystem("tmpfs")
- if !ok {
- b.Fatalf("failed to find tmpfs filesystem type")
- }
- rootInode, err := tmpfsFS.Mount(ctx, "tmpfs", fs.MountSourceFlags{}, "", nil)
- if err != nil {
- b.Fatalf("failed to create tmpfs root mount: %v", err)
- }
- mntns, err := fs.NewMountNamespace(ctx, rootInode)
- if err != nil {
- b.Fatalf("failed to create mount namespace: %v", err)
- }
- defer mntns.DecRef()
-
- var filePathBuilder strings.Builder
- filePathBuilder.WriteByte('/')
-
- // Create and mount the submount.
- root := mntns.Root()
- defer root.DecRef()
- if err := root.Inode.CreateDirectory(ctx, root, mountPointName, fs.FilePermsFromMode(0755)); err != nil {
- b.Fatalf("failed to create mount point: %v", err)
- }
- mountPoint, err := root.Walk(ctx, root, mountPointName)
- if err != nil {
- b.Fatalf("failed to walk to mount point: %v", err)
- }
- defer mountPoint.DecRef()
- submountInode, err := tmpfsFS.Mount(ctx, "tmpfs", fs.MountSourceFlags{}, "", nil)
- if err != nil {
- b.Fatalf("failed to create tmpfs submount: %v", err)
- }
- if err := mntns.Mount(ctx, mountPoint, submountInode); err != nil {
- b.Fatalf("failed to mount tmpfs submount: %v", err)
- }
- filePathBuilder.WriteString(mountPointName)
- filePathBuilder.WriteByte('/')
-
- // Create nested directories with given depth.
- d, err := root.Walk(ctx, root, mountPointName)
- if err != nil {
- b.Fatalf("failed to walk to mount root: %v", err)
- }
- defer d.DecRef()
- for i := depth; i > 0; i-- {
- name := fmt.Sprintf("%d", i)
- if err := d.Inode.CreateDirectory(ctx, d, name, fs.FilePermsFromMode(0755)); err != nil {
- b.Fatalf("failed to create directory %q: %v", name, err)
- }
- next, err := d.Walk(ctx, root, name)
- if err != nil {
- b.Fatalf("failed to walk to directory %q: %v", name, err)
- }
- d.DecRef()
- d = next
- filePathBuilder.WriteString(name)
- filePathBuilder.WriteByte('/')
- }
-
- // Create the file that will be stat'd.
- file, err := d.Inode.Create(ctx, d, filename, fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0644))
- if err != nil {
- b.Fatalf("failed to create file %q: %v", filename, err)
- }
- file.DecRef()
- filePathBuilder.WriteString(filename)
- filePath := filePathBuilder.String()
-
- dirPath := false
- runtime.GC()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- err := fileOpOn(ctx, mntns, root, root, linux.AT_FDCWD, filePath, true /* resolve */, func(root *fs.Dirent, d *fs.Dirent) error {
- if dirPath && !fs.IsDir(d.Inode.StableAttr) {
- return syserror.ENOTDIR
- }
- uattr, err := d.Inode.UnstableAttr(ctx)
- if err != nil {
- return err
- }
- // Sanity check.
- if uattr.Perms.User.Execute {
- b.Fatalf("got wrong permissions (%0o)", uattr.Perms.LinuxMode())
- }
- return nil
- })
- if err != nil {
- b.Fatalf("stat(%q) failed: %v", filePath, err)
- }
- }
- })
- }
-}
-
-func BenchmarkVFS2MemfsMountStat(b *testing.B) {
- for _, depth := range depths {
- b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
- ctx := contexttest.Context(b)
- creds := auth.CredentialsFromContext(ctx)
-
- // Create VFS.
- vfsObj := vfs.New()
- vfsObj.MustRegisterFilesystemType("memfs", memfs.FilesystemType{})
- mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "memfs", &vfs.NewFilesystemOptions{})
- if err != nil {
- b.Fatalf("failed to create tmpfs root mount: %v", err)
- }
-
- var filePathBuilder strings.Builder
- filePathBuilder.WriteByte('/')
-
- // Create the mount point.
- root := mntns.Root()
- defer root.DecRef()
- pop := vfs.PathOperation{
- Root: root,
- Start: root,
- Pathname: mountPointName,
- }
- if err := vfsObj.MkdirAt(ctx, creds, &pop, &vfs.MkdirOptions{
- Mode: 0755,
- }); err != nil {
- b.Fatalf("failed to create mount point: %v", err)
- }
- // Save the mount point for later use.
- mountPoint, err := vfsObj.GetDentryAt(ctx, creds, &pop, &vfs.GetDentryOptions{})
- if err != nil {
- b.Fatalf("failed to walk to mount point: %v", err)
- }
- defer mountPoint.DecRef()
- // Create and mount the submount.
- if err := vfsObj.NewMount(ctx, creds, "", &pop, "memfs", &vfs.NewFilesystemOptions{}); err != nil {
- b.Fatalf("failed to mount tmpfs submount: %v", err)
- }
- filePathBuilder.WriteString(mountPointName)
- filePathBuilder.WriteByte('/')
-
- // Create nested directories with given depth.
- vd, err := vfsObj.GetDentryAt(ctx, creds, &pop, &vfs.GetDentryOptions{})
- if err != nil {
- b.Fatalf("failed to walk to mount root: %v", err)
- }
- defer vd.DecRef()
- for i := depth; i > 0; i-- {
- name := fmt.Sprintf("%d", i)
- pop := vfs.PathOperation{
- Root: root,
- Start: vd,
- Pathname: name,
- }
- if err := vfsObj.MkdirAt(ctx, creds, &pop, &vfs.MkdirOptions{
- Mode: 0755,
- }); err != nil {
- b.Fatalf("failed to create directory %q: %v", name, err)
- }
- nextVD, err := vfsObj.GetDentryAt(ctx, creds, &pop, &vfs.GetDentryOptions{})
- if err != nil {
- b.Fatalf("failed to walk to directory %q: %v", name, err)
- }
- vd.DecRef()
- vd = nextVD
- filePathBuilder.WriteString(name)
- filePathBuilder.WriteByte('/')
- }
-
- // Verify that we didn't create any directories under the mount
- // point (i.e. they were all created on the submount).
- firstDirName := fmt.Sprintf("%d", depth)
- if child := mountPoint.Dentry().Child(firstDirName); child != nil {
- b.Fatalf("created directory %q under root mount, not submount", firstDirName)
- }
-
- // Create the file that will be stat'd.
- fd, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{
- Root: root,
- Start: vd,
- Pathname: filename,
- FollowFinalSymlink: true,
- }, &vfs.OpenOptions{
- Flags: linux.O_RDWR | linux.O_CREAT | linux.O_EXCL,
- Mode: 0644,
- })
- if err != nil {
- b.Fatalf("failed to create file %q: %v", filename, err)
- }
- fd.DecRef()
- filePathBuilder.WriteString(filename)
- filePath := filePathBuilder.String()
-
- runtime.GC()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- stat, err := vfsObj.StatAt(ctx, creds, &vfs.PathOperation{
- Root: root,
- Start: root,
- Pathname: filePath,
- FollowFinalSymlink: true,
- }, &vfs.StatOptions{})
- if err != nil {
- b.Fatalf("stat(%q) failed: %v", filePath, err)
- }
- // Sanity check.
- if stat.Mode&^linux.S_IFMT != 0644 {
- b.Fatalf("got wrong permissions (%0o)", stat.Mode)
- }
- }
- })
- }
-}
diff --git a/pkg/sentry/fsimpl/memfs/directory.go b/pkg/sentry/fsimpl/memfs/directory.go
deleted file mode 100644
index c52dc781c..000000000
--- a/pkg/sentry/fsimpl/memfs/directory.go
+++ /dev/null
@@ -1,187 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package memfs
-
-import (
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-type directory struct {
- inode inode
-
- // childList is a list containing (1) child Dentries and (2) fake Dentries
- // (with inode == nil) that represent the iteration position of
- // directoryFDs. childList is used to support directoryFD.IterDirents()
- // efficiently. childList is protected by filesystem.mu.
- childList dentryList
-}
-
-func (fs *filesystem) newDirectory(creds *auth.Credentials, mode uint16) *inode {
- dir := &directory{}
- dir.inode.init(dir, fs, creds, mode)
- dir.inode.nlink = 2 // from "." and parent directory or ".." for root
- return &dir.inode
-}
-
-func (i *inode) isDir() bool {
- _, ok := i.impl.(*directory)
- return ok
-}
-
-type directoryFD struct {
- fileDescription
- vfs.DirectoryFileDescriptionDefaultImpl
-
- // Protected by filesystem.mu.
- iter *dentry
- off int64
-}
-
-// Release implements vfs.FileDescriptionImpl.Release.
-func (fd *directoryFD) Release() {
- if fd.iter != nil {
- fs := fd.filesystem()
- dir := fd.inode().impl.(*directory)
- fs.mu.Lock()
- dir.childList.Remove(fd.iter)
- fs.mu.Unlock()
- fd.iter = nil
- }
-}
-
-// IterDirents implements vfs.FileDescriptionImpl.IterDirents.
-func (fd *directoryFD) IterDirents(ctx context.Context, cb vfs.IterDirentsCallback) error {
- fs := fd.filesystem()
- vfsd := fd.vfsfd.VirtualDentry().Dentry()
-
- fs.mu.Lock()
- defer fs.mu.Unlock()
-
- if fd.off == 0 {
- if !cb.Handle(vfs.Dirent{
- Name: ".",
- Type: linux.DT_DIR,
- Ino: vfsd.Impl().(*dentry).inode.ino,
- Off: 0,
- }) {
- return nil
- }
- fd.off++
- }
- if fd.off == 1 {
- parentInode := vfsd.ParentOrSelf().Impl().(*dentry).inode
- if !cb.Handle(vfs.Dirent{
- Name: "..",
- Type: parentInode.direntType(),
- Ino: parentInode.ino,
- Off: 1,
- }) {
- return nil
- }
- fd.off++
- }
-
- dir := vfsd.Impl().(*dentry).inode.impl.(*directory)
- var child *dentry
- if fd.iter == nil {
- // Start iteration at the beginning of dir.
- child = dir.childList.Front()
- fd.iter = &dentry{}
- } else {
- // Continue iteration from where we left off.
- child = fd.iter.Next()
- dir.childList.Remove(fd.iter)
- }
- for child != nil {
- // Skip other directoryFD iterators.
- if child.inode != nil {
- if !cb.Handle(vfs.Dirent{
- Name: child.vfsd.Name(),
- Type: child.inode.direntType(),
- Ino: child.inode.ino,
- Off: fd.off,
- }) {
- dir.childList.InsertBefore(child, fd.iter)
- return nil
- }
- fd.off++
- }
- child = child.Next()
- }
- dir.childList.PushBack(fd.iter)
- return nil
-}
-
-// Seek implements vfs.FileDescriptionImpl.Seek.
-func (fd *directoryFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
- fs := fd.filesystem()
- fs.mu.Lock()
- defer fs.mu.Unlock()
-
- switch whence {
- case linux.SEEK_SET:
- // Use offset as given.
- case linux.SEEK_CUR:
- offset += fd.off
- default:
- return 0, syserror.EINVAL
- }
- if offset < 0 {
- return 0, syserror.EINVAL
- }
-
- // If the offset isn't changing (e.g. due to lseek(0, SEEK_CUR)), don't
- // seek even if doing so might reposition the iterator due to concurrent
- // mutation of the directory. Compare fs/libfs.c:dcache_dir_lseek().
- if fd.off == offset {
- return offset, nil
- }
-
- fd.off = offset
- // Compensate for "." and "..".
- remChildren := int64(0)
- if offset >= 2 {
- remChildren = offset - 2
- }
-
- dir := fd.inode().impl.(*directory)
-
- // Ensure that fd.iter exists and is not linked into dir.childList.
- if fd.iter == nil {
- fd.iter = &dentry{}
- } else {
- dir.childList.Remove(fd.iter)
- }
- // Insert fd.iter before the remChildren'th child, or at the end of the
- // list if remChildren >= number of children.
- child := dir.childList.Front()
- for child != nil {
- // Skip other directoryFD iterators.
- if child.inode != nil {
- if remChildren == 0 {
- dir.childList.InsertBefore(child, fd.iter)
- return offset, nil
- }
- remChildren--
- }
- child = child.Next()
- }
- dir.childList.PushBack(fd.iter)
- return offset, nil
-}
diff --git a/pkg/sentry/fsimpl/memfs/filesystem.go b/pkg/sentry/fsimpl/memfs/filesystem.go
deleted file mode 100644
index f79e2d9c8..000000000
--- a/pkg/sentry/fsimpl/memfs/filesystem.go
+++ /dev/null
@@ -1,544 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package memfs
-
-import (
- "fmt"
- "sync/atomic"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-// stepLocked resolves rp.Component() in parent directory vfsd.
-//
-// stepLocked is loosely analogous to fs/namei.c:walk_component().
-//
-// Preconditions: filesystem.mu must be locked. !rp.Done(). inode ==
-// vfsd.Impl().(*dentry).inode.
-func stepLocked(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, inode *inode) (*vfs.Dentry, *inode, error) {
- if !inode.isDir() {
- return nil, nil, syserror.ENOTDIR
- }
- if err := inode.checkPermissions(rp.Credentials(), vfs.MayExec, true); err != nil {
- return nil, nil, err
- }
-afterSymlink:
- nextVFSD, err := rp.ResolveComponent(vfsd)
- if err != nil {
- return nil, nil, err
- }
- if nextVFSD == nil {
- // Since the Dentry tree is the sole source of truth for memfs, if it's
- // not in the Dentry tree, it doesn't exist.
- return nil, nil, syserror.ENOENT
- }
- nextInode := nextVFSD.Impl().(*dentry).inode
- if symlink, ok := nextInode.impl.(*symlink); ok && rp.ShouldFollowSymlink() {
- // TODO: symlink traversals update access time
- if err := rp.HandleSymlink(symlink.target); err != nil {
- return nil, nil, err
- }
- goto afterSymlink // don't check the current directory again
- }
- rp.Advance()
- return nextVFSD, nextInode, nil
-}
-
-// walkExistingLocked resolves rp to an existing file.
-//
-// walkExistingLocked is loosely analogous to Linux's
-// fs/namei.c:path_lookupat().
-//
-// Preconditions: filesystem.mu must be locked.
-func walkExistingLocked(rp *vfs.ResolvingPath) (*vfs.Dentry, *inode, error) {
- vfsd := rp.Start()
- inode := vfsd.Impl().(*dentry).inode
- for !rp.Done() {
- var err error
- vfsd, inode, err = stepLocked(rp, vfsd, inode)
- if err != nil {
- return nil, nil, err
- }
- }
- if rp.MustBeDir() && !inode.isDir() {
- return nil, nil, syserror.ENOTDIR
- }
- return vfsd, inode, nil
-}
-
-// walkParentDirLocked resolves all but the last path component of rp to an
-// existing directory. It does not check that the returned directory is
-// searchable by the provider of rp.
-//
-// walkParentDirLocked is loosely analogous to Linux's
-// fs/namei.c:path_parentat().
-//
-// Preconditions: filesystem.mu must be locked. !rp.Done().
-func walkParentDirLocked(rp *vfs.ResolvingPath) (*vfs.Dentry, *inode, error) {
- vfsd := rp.Start()
- inode := vfsd.Impl().(*dentry).inode
- for !rp.Final() {
- var err error
- vfsd, inode, err = stepLocked(rp, vfsd, inode)
- if err != nil {
- return nil, nil, err
- }
- }
- if !inode.isDir() {
- return nil, nil, syserror.ENOTDIR
- }
- return vfsd, inode, nil
-}
-
-// checkCreateLocked checks that a file named rp.Component() may be created in
-// directory parentVFSD, then returns rp.Component().
-//
-// Preconditions: filesystem.mu must be locked. parentInode ==
-// parentVFSD.Impl().(*dentry).inode. parentInode.isDir() == true.
-func checkCreateLocked(rp *vfs.ResolvingPath, parentVFSD *vfs.Dentry, parentInode *inode) (string, error) {
- if err := parentInode.checkPermissions(rp.Credentials(), vfs.MayWrite|vfs.MayExec, true); err != nil {
- return "", err
- }
- pc := rp.Component()
- if pc == "." || pc == ".." {
- return "", syserror.EEXIST
- }
- childVFSD, err := rp.ResolveChild(parentVFSD, pc)
- if err != nil {
- return "", err
- }
- if childVFSD != nil {
- return "", syserror.EEXIST
- }
- if parentVFSD.IsDisowned() {
- return "", syserror.ENOENT
- }
- return pc, nil
-}
-
-// checkDeleteLocked checks that the file represented by vfsd may be deleted.
-func checkDeleteLocked(vfsd *vfs.Dentry) error {
- parentVFSD := vfsd.Parent()
- if parentVFSD == nil {
- return syserror.EBUSY
- }
- if parentVFSD.IsDisowned() {
- return syserror.ENOENT
- }
- return nil
-}
-
-// GetDentryAt implements vfs.FilesystemImpl.GetDentryAt.
-func (fs *filesystem) GetDentryAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.GetDentryOptions) (*vfs.Dentry, error) {
- fs.mu.RLock()
- defer fs.mu.RUnlock()
- vfsd, inode, err := walkExistingLocked(rp)
- if err != nil {
- return nil, err
- }
- if opts.CheckSearchable {
- if !inode.isDir() {
- return nil, syserror.ENOTDIR
- }
- if err := inode.checkPermissions(rp.Credentials(), vfs.MayExec, true); err != nil {
- return nil, err
- }
- }
- inode.incRef() // vfsd.IncRef(&fs.vfsfs)
- return vfsd, nil
-}
-
-// LinkAt implements vfs.FilesystemImpl.LinkAt.
-func (fs *filesystem) LinkAt(ctx context.Context, rp *vfs.ResolvingPath, vd vfs.VirtualDentry) error {
- if rp.Done() {
- return syserror.EEXIST
- }
- fs.mu.Lock()
- defer fs.mu.Unlock()
- parentVFSD, parentInode, err := walkParentDirLocked(rp)
- if err != nil {
- return err
- }
- pc, err := checkCreateLocked(rp, parentVFSD, parentInode)
- if err != nil {
- return err
- }
- if rp.Mount() != vd.Mount() {
- return syserror.EXDEV
- }
- if err := rp.Mount().CheckBeginWrite(); err != nil {
- return err
- }
- defer rp.Mount().EndWrite()
- d := vd.Dentry().Impl().(*dentry)
- if d.inode.isDir() {
- return syserror.EPERM
- }
- d.inode.incLinksLocked()
- child := fs.newDentry(d.inode)
- parentVFSD.InsertChild(&child.vfsd, pc)
- parentInode.impl.(*directory).childList.PushBack(child)
- return nil
-}
-
-// MkdirAt implements vfs.FilesystemImpl.MkdirAt.
-func (fs *filesystem) MkdirAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.MkdirOptions) error {
- if rp.Done() {
- return syserror.EEXIST
- }
- fs.mu.Lock()
- defer fs.mu.Unlock()
- parentVFSD, parentInode, err := walkParentDirLocked(rp)
- if err != nil {
- return err
- }
- pc, err := checkCreateLocked(rp, parentVFSD, parentInode)
- if err != nil {
- return err
- }
- if err := rp.Mount().CheckBeginWrite(); err != nil {
- return err
- }
- defer rp.Mount().EndWrite()
- child := fs.newDentry(fs.newDirectory(rp.Credentials(), opts.Mode))
- parentVFSD.InsertChild(&child.vfsd, pc)
- parentInode.impl.(*directory).childList.PushBack(child)
- parentInode.incLinksLocked() // from child's ".."
- return nil
-}
-
-// MknodAt implements vfs.FilesystemImpl.MknodAt.
-func (fs *filesystem) MknodAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.MknodOptions) error {
- if rp.Done() {
- return syserror.EEXIST
- }
- fs.mu.Lock()
- defer fs.mu.Unlock()
- parentVFSD, parentInode, err := walkParentDirLocked(rp)
- if err != nil {
- return err
- }
- _, err = checkCreateLocked(rp, parentVFSD, parentInode)
- if err != nil {
- return err
- }
- if err := rp.Mount().CheckBeginWrite(); err != nil {
- return err
- }
- defer rp.Mount().EndWrite()
- // TODO: actually implement mknod
- return syserror.EPERM
-}
-
-// OpenAt implements vfs.FilesystemImpl.OpenAt.
-func (fs *filesystem) OpenAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
- // Filter out flags that are not supported by memfs. O_DIRECTORY and
- // O_NOFOLLOW have no effect here (they're handled by VFS by setting
- // appropriate bits in rp), but are returned by
- // FileDescriptionImpl.StatusFlags().
- opts.Flags &= linux.O_ACCMODE | linux.O_CREAT | linux.O_EXCL | linux.O_TRUNC | linux.O_DIRECTORY | linux.O_NOFOLLOW
-
- if opts.Flags&linux.O_CREAT == 0 {
- fs.mu.RLock()
- defer fs.mu.RUnlock()
- vfsd, inode, err := walkExistingLocked(rp)
- if err != nil {
- return nil, err
- }
- return inode.open(rp, vfsd, opts.Flags, false)
- }
-
- mustCreate := opts.Flags&linux.O_EXCL != 0
- vfsd := rp.Start()
- inode := vfsd.Impl().(*dentry).inode
- fs.mu.Lock()
- defer fs.mu.Unlock()
- if rp.Done() {
- if rp.MustBeDir() {
- return nil, syserror.EISDIR
- }
- if mustCreate {
- return nil, syserror.EEXIST
- }
- return inode.open(rp, vfsd, opts.Flags, false)
- }
-afterTrailingSymlink:
- // Walk to the parent directory of the last path component.
- for !rp.Final() {
- var err error
- vfsd, inode, err = stepLocked(rp, vfsd, inode)
- if err != nil {
- return nil, err
- }
- }
- if !inode.isDir() {
- return nil, syserror.ENOTDIR
- }
- // Check for search permission in the parent directory.
- if err := inode.checkPermissions(rp.Credentials(), vfs.MayExec, true); err != nil {
- return nil, err
- }
- // Reject attempts to open directories with O_CREAT.
- if rp.MustBeDir() {
- return nil, syserror.EISDIR
- }
- pc := rp.Component()
- if pc == "." || pc == ".." {
- return nil, syserror.EISDIR
- }
- // Determine whether or not we need to create a file.
- childVFSD, err := rp.ResolveChild(vfsd, pc)
- if err != nil {
- return nil, err
- }
- if childVFSD == nil {
- // Already checked for searchability above; now check for writability.
- if err := inode.checkPermissions(rp.Credentials(), vfs.MayWrite, true); err != nil {
- return nil, err
- }
- if err := rp.Mount().CheckBeginWrite(); err != nil {
- return nil, err
- }
- defer rp.Mount().EndWrite()
- // Create and open the child.
- childInode := fs.newRegularFile(rp.Credentials(), opts.Mode)
- child := fs.newDentry(childInode)
- vfsd.InsertChild(&child.vfsd, pc)
- inode.impl.(*directory).childList.PushBack(child)
- return childInode.open(rp, &child.vfsd, opts.Flags, true)
- }
- // Open existing file or follow symlink.
- if mustCreate {
- return nil, syserror.EEXIST
- }
- childInode := childVFSD.Impl().(*dentry).inode
- if symlink, ok := childInode.impl.(*symlink); ok && rp.ShouldFollowSymlink() {
- // TODO: symlink traversals update access time
- if err := rp.HandleSymlink(symlink.target); err != nil {
- return nil, err
- }
- // rp.Final() may no longer be true since we now need to resolve the
- // symlink target.
- goto afterTrailingSymlink
- }
- return childInode.open(rp, childVFSD, opts.Flags, false)
-}
-
-func (i *inode) open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, flags uint32, afterCreate bool) (*vfs.FileDescription, error) {
- ats := vfs.AccessTypesForOpenFlags(flags)
- if !afterCreate {
- if err := i.checkPermissions(rp.Credentials(), ats, i.isDir()); err != nil {
- return nil, err
- }
- }
- switch impl := i.impl.(type) {
- case *regularFile:
- var fd regularFileFD
- fd.flags = flags
- fd.readable = vfs.MayReadFileWithOpenFlags(flags)
- fd.writable = vfs.MayWriteFileWithOpenFlags(flags)
- if fd.writable {
- if err := rp.Mount().CheckBeginWrite(); err != nil {
- return nil, err
- }
- // Mount.EndWrite() is called by regularFileFD.Release().
- }
- fd.vfsfd.Init(&fd, rp.Mount(), vfsd)
- if flags&linux.O_TRUNC != 0 {
- impl.mu.Lock()
- impl.data = impl.data[:0]
- atomic.StoreInt64(&impl.dataLen, 0)
- impl.mu.Unlock()
- }
- return &fd.vfsfd, nil
- case *directory:
- // Can't open directories writably.
- if ats&vfs.MayWrite != 0 {
- return nil, syserror.EISDIR
- }
- var fd directoryFD
- fd.vfsfd.Init(&fd, rp.Mount(), vfsd)
- fd.flags = flags
- return &fd.vfsfd, nil
- case *symlink:
- // Can't open symlinks without O_PATH (which is unimplemented).
- return nil, syserror.ELOOP
- default:
- panic(fmt.Sprintf("unknown inode type: %T", i.impl))
- }
-}
-
-// ReadlinkAt implements vfs.FilesystemImpl.ReadlinkAt.
-func (fs *filesystem) ReadlinkAt(ctx context.Context, rp *vfs.ResolvingPath) (string, error) {
- fs.mu.RLock()
- _, inode, err := walkExistingLocked(rp)
- fs.mu.RUnlock()
- if err != nil {
- return "", err
- }
- symlink, ok := inode.impl.(*symlink)
- if !ok {
- return "", syserror.EINVAL
- }
- return symlink.target, nil
-}
-
-// RenameAt implements vfs.FilesystemImpl.RenameAt.
-func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, vd vfs.VirtualDentry, opts vfs.RenameOptions) error {
- if rp.Done() {
- return syserror.ENOENT
- }
- fs.mu.Lock()
- defer fs.mu.Unlock()
- parentVFSD, parentInode, err := walkParentDirLocked(rp)
- if err != nil {
- return err
- }
- _, err = checkCreateLocked(rp, parentVFSD, parentInode)
- if err != nil {
- return err
- }
- if err := rp.Mount().CheckBeginWrite(); err != nil {
- return err
- }
- defer rp.Mount().EndWrite()
- // TODO: actually implement RenameAt
- return syserror.EPERM
-}
-
-// RmdirAt implements vfs.FilesystemImpl.RmdirAt.
-func (fs *filesystem) RmdirAt(ctx context.Context, rp *vfs.ResolvingPath) error {
- fs.mu.Lock()
- defer fs.mu.Unlock()
- vfsd, inode, err := walkExistingLocked(rp)
- if err != nil {
- return err
- }
- if err := rp.Mount().CheckBeginWrite(); err != nil {
- return err
- }
- defer rp.Mount().EndWrite()
- if err := checkDeleteLocked(vfsd); err != nil {
- return err
- }
- if !inode.isDir() {
- return syserror.ENOTDIR
- }
- if vfsd.HasChildren() {
- return syserror.ENOTEMPTY
- }
- if err := rp.VirtualFilesystem().DeleteDentry(vfs.MountNamespaceFromContext(ctx), vfsd); err != nil {
- return err
- }
- // Remove from parent directory's childList.
- vfsd.Parent().Impl().(*dentry).inode.impl.(*directory).childList.Remove(vfsd.Impl().(*dentry))
- inode.decRef()
- return nil
-}
-
-// SetStatAt implements vfs.FilesystemImpl.SetStatAt.
-func (fs *filesystem) SetStatAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.SetStatOptions) error {
- fs.mu.RLock()
- _, _, err := walkExistingLocked(rp)
- fs.mu.RUnlock()
- if err != nil {
- return err
- }
- if opts.Stat.Mask == 0 {
- return nil
- }
- // TODO: implement inode.setStat
- return syserror.EPERM
-}
-
-// StatAt implements vfs.FilesystemImpl.StatAt.
-func (fs *filesystem) StatAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.StatOptions) (linux.Statx, error) {
- fs.mu.RLock()
- _, inode, err := walkExistingLocked(rp)
- fs.mu.RUnlock()
- if err != nil {
- return linux.Statx{}, err
- }
- var stat linux.Statx
- inode.statTo(&stat)
- return stat, nil
-}
-
-// StatFSAt implements vfs.FilesystemImpl.StatFSAt.
-func (fs *filesystem) StatFSAt(ctx context.Context, rp *vfs.ResolvingPath) (linux.Statfs, error) {
- fs.mu.RLock()
- _, _, err := walkExistingLocked(rp)
- fs.mu.RUnlock()
- if err != nil {
- return linux.Statfs{}, err
- }
- // TODO: actually implement statfs
- return linux.Statfs{}, syserror.ENOSYS
-}
-
-// SymlinkAt implements vfs.FilesystemImpl.SymlinkAt.
-func (fs *filesystem) SymlinkAt(ctx context.Context, rp *vfs.ResolvingPath, target string) error {
- if rp.Done() {
- return syserror.EEXIST
- }
- fs.mu.Lock()
- defer fs.mu.Unlock()
- parentVFSD, parentInode, err := walkParentDirLocked(rp)
- if err != nil {
- return err
- }
- pc, err := checkCreateLocked(rp, parentVFSD, parentInode)
- if err != nil {
- return err
- }
- if err := rp.Mount().CheckBeginWrite(); err != nil {
- return err
- }
- defer rp.Mount().EndWrite()
- child := fs.newDentry(fs.newSymlink(rp.Credentials(), target))
- parentVFSD.InsertChild(&child.vfsd, pc)
- parentInode.impl.(*directory).childList.PushBack(child)
- return nil
-}
-
-// UnlinkAt implements vfs.FilesystemImpl.UnlinkAt.
-func (fs *filesystem) UnlinkAt(ctx context.Context, rp *vfs.ResolvingPath) error {
- fs.mu.Lock()
- defer fs.mu.Unlock()
- vfsd, inode, err := walkExistingLocked(rp)
- if err != nil {
- return err
- }
- if err := rp.Mount().CheckBeginWrite(); err != nil {
- return err
- }
- defer rp.Mount().EndWrite()
- if err := checkDeleteLocked(vfsd); err != nil {
- return err
- }
- if inode.isDir() {
- return syserror.EISDIR
- }
- if err := rp.VirtualFilesystem().DeleteDentry(vfs.MountNamespaceFromContext(ctx), vfsd); err != nil {
- return err
- }
- // Remove from parent directory's childList.
- vfsd.Parent().Impl().(*dentry).inode.impl.(*directory).childList.Remove(vfsd.Impl().(*dentry))
- inode.decLinksLocked()
- return nil
-}
diff --git a/pkg/sentry/fsimpl/memfs/memfs.go b/pkg/sentry/fsimpl/memfs/memfs.go
deleted file mode 100644
index 45cd42b3e..000000000
--- a/pkg/sentry/fsimpl/memfs/memfs.go
+++ /dev/null
@@ -1,300 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package memfs provides a filesystem implementation that behaves like tmpfs:
-// the Dentry tree is the sole source of truth for the state of the filesystem.
-//
-// memfs is intended primarily to demonstrate filesystem implementation
-// patterns. Real uses cases for an in-memory filesystem should use tmpfs
-// instead.
-//
-// Lock order:
-//
-// filesystem.mu
-// regularFileFD.offMu
-// regularFile.mu
-// inode.mu
-package memfs
-
-import (
- "fmt"
- "sync"
- "sync/atomic"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-// FilesystemType implements vfs.FilesystemType.
-type FilesystemType struct{}
-
-// filesystem implements vfs.FilesystemImpl.
-type filesystem struct {
- vfsfs vfs.Filesystem
-
- // mu serializes changes to the Dentry tree.
- mu sync.RWMutex
-
- nextInoMinusOne uint64 // accessed using atomic memory operations
-}
-
-// NewFilesystem implements vfs.FilesystemType.NewFilesystem.
-func (fstype FilesystemType) NewFilesystem(ctx context.Context, creds *auth.Credentials, source string, opts vfs.NewFilesystemOptions) (*vfs.Filesystem, *vfs.Dentry, error) {
- var fs filesystem
- fs.vfsfs.Init(&fs)
- root := fs.newDentry(fs.newDirectory(creds, 01777))
- return &fs.vfsfs, &root.vfsd, nil
-}
-
-// Release implements vfs.FilesystemImpl.Release.
-func (fs *filesystem) Release() {
-}
-
-// Sync implements vfs.FilesystemImpl.Sync.
-func (fs *filesystem) Sync(ctx context.Context) error {
- // All filesystem state is in-memory.
- return nil
-}
-
-// dentry implements vfs.DentryImpl.
-type dentry struct {
- vfsd vfs.Dentry
-
- // inode is the inode represented by this dentry. Multiple Dentries may
- // share a single non-directory inode (with hard links). inode is
- // immutable.
- inode *inode
-
- // memfs doesn't count references on dentries; because the dentry tree is
- // the sole source of truth, it is by definition always consistent with the
- // state of the filesystem. However, it does count references on inodes,
- // because inode resources are released when all references are dropped.
- // (memfs doesn't really have resources to release, but we implement
- // reference counting because tmpfs regular files will.)
-
- // dentryEntry (ugh) links dentries into their parent directory.childList.
- dentryEntry
-}
-
-func (fs *filesystem) newDentry(inode *inode) *dentry {
- d := &dentry{
- inode: inode,
- }
- d.vfsd.Init(d)
- return d
-}
-
-// IncRef implements vfs.DentryImpl.IncRef.
-func (d *dentry) IncRef(vfsfs *vfs.Filesystem) {
- d.inode.incRef()
-}
-
-// TryIncRef implements vfs.DentryImpl.TryIncRef.
-func (d *dentry) TryIncRef(vfsfs *vfs.Filesystem) bool {
- return d.inode.tryIncRef()
-}
-
-// DecRef implements vfs.DentryImpl.DecRef.
-func (d *dentry) DecRef(vfsfs *vfs.Filesystem) {
- d.inode.decRef()
-}
-
-// inode represents a filesystem object.
-type inode struct {
- // refs is a reference count. refs is accessed using atomic memory
- // operations.
- //
- // A reference is held on all inodes that are reachable in the filesystem
- // tree. For non-directories (which may have multiple hard links), this
- // means that a reference is dropped when nlink reaches 0. For directories,
- // nlink never reaches 0 due to the "." entry; instead,
- // filesystem.RmdirAt() drops the reference.
- refs int64
-
- // Inode metadata; protected by mu and accessed using atomic memory
- // operations unless otherwise specified.
- mu sync.RWMutex
- mode uint32 // excluding file type bits, which are based on impl
- nlink uint32 // protected by filesystem.mu instead of inode.mu
- uid uint32 // auth.KUID, but stored as raw uint32 for sync/atomic
- gid uint32 // auth.KGID, but ...
- ino uint64 // immutable
-
- impl interface{} // immutable
-}
-
-func (i *inode) init(impl interface{}, fs *filesystem, creds *auth.Credentials, mode uint16) {
- i.refs = 1
- i.mode = uint32(mode)
- i.uid = uint32(creds.EffectiveKUID)
- i.gid = uint32(creds.EffectiveKGID)
- i.ino = atomic.AddUint64(&fs.nextInoMinusOne, 1)
- // i.nlink initialized by caller
- i.impl = impl
-}
-
-// Preconditions: filesystem.mu must be locked for writing.
-func (i *inode) incLinksLocked() {
- if atomic.AddUint32(&i.nlink, 1) <= 1 {
- panic("memfs.inode.incLinksLocked() called with no existing links")
- }
-}
-
-// Preconditions: filesystem.mu must be locked for writing.
-func (i *inode) decLinksLocked() {
- if nlink := atomic.AddUint32(&i.nlink, ^uint32(0)); nlink == 0 {
- i.decRef()
- } else if nlink == ^uint32(0) { // negative overflow
- panic("memfs.inode.decLinksLocked() called with no existing links")
- }
-}
-
-func (i *inode) incRef() {
- if atomic.AddInt64(&i.refs, 1) <= 1 {
- panic("memfs.inode.incRef() called without holding a reference")
- }
-}
-
-func (i *inode) tryIncRef() bool {
- for {
- refs := atomic.LoadInt64(&i.refs)
- if refs == 0 {
- return false
- }
- if atomic.CompareAndSwapInt64(&i.refs, refs, refs+1) {
- return true
- }
- }
-}
-
-func (i *inode) decRef() {
- if refs := atomic.AddInt64(&i.refs, -1); refs == 0 {
- // This is unnecessary; it's mostly to simulate what tmpfs would do.
- if regfile, ok := i.impl.(*regularFile); ok {
- regfile.mu.Lock()
- regfile.data = nil
- atomic.StoreInt64(&regfile.dataLen, 0)
- regfile.mu.Unlock()
- }
- } else if refs < 0 {
- panic("memfs.inode.decRef() called without holding a reference")
- }
-}
-
-func (i *inode) checkPermissions(creds *auth.Credentials, ats vfs.AccessTypes, isDir bool) error {
- return vfs.GenericCheckPermissions(creds, ats, isDir, uint16(atomic.LoadUint32(&i.mode)), auth.KUID(atomic.LoadUint32(&i.uid)), auth.KGID(atomic.LoadUint32(&i.gid)))
-}
-
-// Go won't inline this function, and returning linux.Statx (which is quite
-// big) means spending a lot of time in runtime.duffcopy(), so instead it's an
-// output parameter.
-func (i *inode) statTo(stat *linux.Statx) {
- stat.Mask = linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_NLINK | linux.STATX_UID | linux.STATX_GID | linux.STATX_INO
- stat.Blksize = 1 // usermem.PageSize in tmpfs
- stat.Nlink = atomic.LoadUint32(&i.nlink)
- stat.UID = atomic.LoadUint32(&i.uid)
- stat.GID = atomic.LoadUint32(&i.gid)
- stat.Mode = uint16(atomic.LoadUint32(&i.mode))
- stat.Ino = i.ino
- // TODO: device number
- switch impl := i.impl.(type) {
- case *regularFile:
- stat.Mode |= linux.S_IFREG
- stat.Mask |= linux.STATX_SIZE | linux.STATX_BLOCKS
- stat.Size = uint64(atomic.LoadInt64(&impl.dataLen))
- // In tmpfs, this will be FileRangeSet.Span() / 512 (but also cached in
- // a uint64 accessed using atomic memory operations to avoid taking
- // locks).
- stat.Blocks = allocatedBlocksForSize(stat.Size)
- case *directory:
- stat.Mode |= linux.S_IFDIR
- case *symlink:
- stat.Mode |= linux.S_IFLNK
- stat.Mask |= linux.STATX_SIZE | linux.STATX_BLOCKS
- stat.Size = uint64(len(impl.target))
- stat.Blocks = allocatedBlocksForSize(stat.Size)
- default:
- panic(fmt.Sprintf("unknown inode type: %T", i.impl))
- }
-}
-
-// allocatedBlocksForSize returns the number of 512B blocks needed to
-// accommodate the given size in bytes, as appropriate for struct
-// stat::st_blocks and struct statx::stx_blocks. (Note that this 512B block
-// size is independent of the "preferred block size for I/O", struct
-// stat::st_blksize and struct statx::stx_blksize.)
-func allocatedBlocksForSize(size uint64) uint64 {
- return (size + 511) / 512
-}
-
-func (i *inode) direntType() uint8 {
- switch i.impl.(type) {
- case *regularFile:
- return linux.DT_REG
- case *directory:
- return linux.DT_DIR
- case *symlink:
- return linux.DT_LNK
- default:
- panic(fmt.Sprintf("unknown inode type: %T", i.impl))
- }
-}
-
-// fileDescription is embedded by memfs implementations of
-// vfs.FileDescriptionImpl.
-type fileDescription struct {
- vfsfd vfs.FileDescription
- vfs.FileDescriptionDefaultImpl
-
- flags uint32 // status flags; immutable
-}
-
-func (fd *fileDescription) filesystem() *filesystem {
- return fd.vfsfd.VirtualDentry().Mount().Filesystem().Impl().(*filesystem)
-}
-
-func (fd *fileDescription) inode() *inode {
- return fd.vfsfd.VirtualDentry().Dentry().Impl().(*dentry).inode
-}
-
-// StatusFlags implements vfs.FileDescriptionImpl.StatusFlags.
-func (fd *fileDescription) StatusFlags(ctx context.Context) (uint32, error) {
- return fd.flags, nil
-}
-
-// SetStatusFlags implements vfs.FileDescriptionImpl.SetStatusFlags.
-func (fd *fileDescription) SetStatusFlags(ctx context.Context, flags uint32) error {
- // None of the flags settable by fcntl(F_SETFL) are supported, so this is a
- // no-op.
- return nil
-}
-
-// Stat implements vfs.FileDescriptionImpl.Stat.
-func (fd *fileDescription) Stat(ctx context.Context, opts vfs.StatOptions) (linux.Statx, error) {
- var stat linux.Statx
- fd.inode().statTo(&stat)
- return stat, nil
-}
-
-// SetStat implements vfs.FileDescriptionImpl.SetStat.
-func (fd *fileDescription) SetStat(ctx context.Context, opts vfs.SetStatOptions) error {
- if opts.Stat.Mask == 0 {
- return nil
- }
- // TODO: implement inode.setStat
- return syserror.EPERM
-}
diff --git a/pkg/sentry/fsimpl/memfs/regular_file.go b/pkg/sentry/fsimpl/memfs/regular_file.go
deleted file mode 100644
index 55f869798..000000000
--- a/pkg/sentry/fsimpl/memfs/regular_file.go
+++ /dev/null
@@ -1,154 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package memfs
-
-import (
- "io"
- "sync"
- "sync/atomic"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-type regularFile struct {
- inode inode
-
- mu sync.RWMutex
- data []byte
- // dataLen is len(data), but accessed using atomic memory operations to
- // avoid locking in inode.stat().
- dataLen int64
-}
-
-func (fs *filesystem) newRegularFile(creds *auth.Credentials, mode uint16) *inode {
- file := &regularFile{}
- file.inode.init(file, fs, creds, mode)
- file.inode.nlink = 1 // from parent directory
- return &file.inode
-}
-
-type regularFileFD struct {
- fileDescription
-
- // These are immutable.
- readable bool
- writable bool
-
- // off is the file offset. off is accessed using atomic memory operations.
- // offMu serializes operations that may mutate off.
- off int64
- offMu sync.Mutex
-}
-
-// Release implements vfs.FileDescriptionImpl.Release.
-func (fd *regularFileFD) Release() {
- if fd.writable {
- fd.vfsfd.VirtualDentry().Mount().EndWrite()
- }
-}
-
-// PRead implements vfs.FileDescriptionImpl.PRead.
-func (fd *regularFileFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
- if !fd.readable {
- return 0, syserror.EINVAL
- }
- f := fd.inode().impl.(*regularFile)
- f.mu.RLock()
- if offset >= int64(len(f.data)) {
- f.mu.RUnlock()
- return 0, io.EOF
- }
- n, err := dst.CopyOut(ctx, f.data[offset:])
- f.mu.RUnlock()
- return int64(n), err
-}
-
-// Read implements vfs.FileDescriptionImpl.Read.
-func (fd *regularFileFD) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
- fd.offMu.Lock()
- n, err := fd.PRead(ctx, dst, fd.off, opts)
- fd.off += n
- fd.offMu.Unlock()
- return n, err
-}
-
-// PWrite implements vfs.FileDescriptionImpl.PWrite.
-func (fd *regularFileFD) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
- if !fd.writable {
- return 0, syserror.EINVAL
- }
- if offset < 0 {
- return 0, syserror.EINVAL
- }
- srclen := src.NumBytes()
- if srclen == 0 {
- return 0, nil
- }
- f := fd.inode().impl.(*regularFile)
- f.mu.Lock()
- end := offset + srclen
- if end < offset {
- // Overflow.
- f.mu.Unlock()
- return 0, syserror.EFBIG
- }
- if end > f.dataLen {
- f.data = append(f.data, make([]byte, end-f.dataLen)...)
- atomic.StoreInt64(&f.dataLen, end)
- }
- n, err := src.CopyIn(ctx, f.data[offset:end])
- f.mu.Unlock()
- return int64(n), err
-}
-
-// Write implements vfs.FileDescriptionImpl.Write.
-func (fd *regularFileFD) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
- fd.offMu.Lock()
- n, err := fd.PWrite(ctx, src, fd.off, opts)
- fd.off += n
- fd.offMu.Unlock()
- return n, err
-}
-
-// Seek implements vfs.FileDescriptionImpl.Seek.
-func (fd *regularFileFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
- fd.offMu.Lock()
- defer fd.offMu.Unlock()
- switch whence {
- case linux.SEEK_SET:
- // use offset as specified
- case linux.SEEK_CUR:
- offset += fd.off
- case linux.SEEK_END:
- offset += atomic.LoadInt64(&fd.inode().impl.(*regularFile).dataLen)
- default:
- return 0, syserror.EINVAL
- }
- if offset < 0 {
- return 0, syserror.EINVAL
- }
- fd.off = offset
- return offset, nil
-}
-
-// Sync implements vfs.FileDescriptionImpl.Sync.
-func (fd *regularFileFD) Sync(ctx context.Context) error {
- return nil
-}
diff --git a/pkg/sentry/fsimpl/memfs/symlink.go b/pkg/sentry/fsimpl/memfs/symlink.go
deleted file mode 100644
index b2ac2cbeb..000000000
--- a/pkg/sentry/fsimpl/memfs/symlink.go
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package memfs
-
-import (
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
-)
-
-type symlink struct {
- inode inode
- target string // immutable
-}
-
-func (fs *filesystem) newSymlink(creds *auth.Credentials, target string) *inode {
- link := &symlink{
- target: target,
- }
- link.inode.init(link, fs, creds, 0777)
- link.inode.nlink = 1 // from parent directory
- return &link.inode
-}
-
-// O_PATH is unimplemented, so there's no way to get a FileDescription
-// representing a symlink yet.
diff --git a/pkg/sentry/fsimpl/proc/BUILD b/pkg/sentry/fsimpl/proc/BUILD
deleted file mode 100644
index ade6ac946..000000000
--- a/pkg/sentry/fsimpl/proc/BUILD
+++ /dev/null
@@ -1,50 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "proc",
- srcs = [
- "filesystems.go",
- "loadavg.go",
- "meminfo.go",
- "mounts.go",
- "net.go",
- "proc.go",
- "stat.go",
- "sys.go",
- "task.go",
- "version.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/fsimpl/proc",
- deps = [
- "//pkg/abi/linux",
- "//pkg/binary",
- "//pkg/log",
- "//pkg/sentry/context",
- "//pkg/sentry/fs",
- "//pkg/sentry/inet",
- "//pkg/sentry/kernel",
- "//pkg/sentry/limits",
- "//pkg/sentry/mm",
- "//pkg/sentry/socket",
- "//pkg/sentry/socket/unix",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/usage",
- "//pkg/sentry/usermem",
- "//pkg/sentry/vfs",
- ],
-)
-
-go_test(
- name = "proc_test",
- size = "small",
- srcs = ["net_test.go"],
- embed = [":proc"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/inet",
- ],
-)
diff --git a/pkg/sentry/fsimpl/proc/filesystems.go b/pkg/sentry/fsimpl/proc/filesystems.go
deleted file mode 100644
index c36c4aff5..000000000
--- a/pkg/sentry/fsimpl/proc/filesystems.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package proc
-
-// filesystemsData implements vfs.DynamicBytesSource for /proc/filesystems.
-//
-// +stateify savable
-type filesystemsData struct{}
-
-// TODO(b/138862512): Implement vfs.DynamicBytesSource.Generate for
-// filesystemsData. We would need to retrive filesystem names from
-// vfs.VirtualFilesystem. Also needs vfs replacement for
-// fs.Filesystem.AllowUserList() and fs.FilesystemRequiresDev.
diff --git a/pkg/sentry/fsimpl/proc/loadavg.go b/pkg/sentry/fsimpl/proc/loadavg.go
deleted file mode 100644
index 9135afef1..000000000
--- a/pkg/sentry/fsimpl/proc/loadavg.go
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package proc
-
-import (
- "bytes"
- "fmt"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
-)
-
-// loadavgData backs /proc/loadavg.
-//
-// +stateify savable
-type loadavgData struct{}
-
-var _ vfs.DynamicBytesSource = (*loadavgData)(nil)
-
-// Generate implements vfs.DynamicBytesSource.Generate.
-func (d *loadavgData) Generate(ctx context.Context, buf *bytes.Buffer) error {
- // TODO(b/62345059): Include real data in fields.
- // Column 1-3: CPU and IO utilization of the last 1, 5, and 10 minute periods.
- // Column 4-5: currently running processes and the total number of processes.
- // Column 6: the last process ID used.
- fmt.Fprintf(buf, "%.2f %.2f %.2f %d/%d %d\n", 0.00, 0.00, 0.00, 0, 0, 0)
- return nil
-}
diff --git a/pkg/sentry/fsimpl/proc/meminfo.go b/pkg/sentry/fsimpl/proc/meminfo.go
deleted file mode 100644
index 9a827cd66..000000000
--- a/pkg/sentry/fsimpl/proc/meminfo.go
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package proc
-
-import (
- "bytes"
- "fmt"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/kernel"
- "gvisor.dev/gvisor/pkg/sentry/usage"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
-)
-
-// meminfoData implements vfs.DynamicBytesSource for /proc/meminfo.
-//
-// +stateify savable
-type meminfoData struct {
- // k is the owning Kernel.
- k *kernel.Kernel
-}
-
-var _ vfs.DynamicBytesSource = (*meminfoData)(nil)
-
-// Generate implements vfs.DynamicBytesSource.Generate.
-func (d *meminfoData) Generate(ctx context.Context, buf *bytes.Buffer) error {
- mf := d.k.MemoryFile()
- mf.UpdateUsage()
- snapshot, totalUsage := usage.MemoryAccounting.Copy()
- totalSize := usage.TotalMemory(mf.TotalSize(), totalUsage)
- anon := snapshot.Anonymous + snapshot.Tmpfs
- file := snapshot.PageCache + snapshot.Mapped
- // We don't actually have active/inactive LRUs, so just make up numbers.
- activeFile := (file / 2) &^ (usermem.PageSize - 1)
- inactiveFile := file - activeFile
-
- fmt.Fprintf(buf, "MemTotal: %8d kB\n", totalSize/1024)
- memFree := (totalSize - totalUsage) / 1024
- // We use MemFree as MemAvailable because we don't swap.
- // TODO(rahat): When reclaim is implemented the value of MemAvailable
- // should change.
- fmt.Fprintf(buf, "MemFree: %8d kB\n", memFree)
- fmt.Fprintf(buf, "MemAvailable: %8d kB\n", memFree)
- fmt.Fprintf(buf, "Buffers: 0 kB\n") // memory usage by block devices
- fmt.Fprintf(buf, "Cached: %8d kB\n", (file+snapshot.Tmpfs)/1024)
- // Emulate a system with no swap, which disables inactivation of anon pages.
- fmt.Fprintf(buf, "SwapCache: 0 kB\n")
- fmt.Fprintf(buf, "Active: %8d kB\n", (anon+activeFile)/1024)
- fmt.Fprintf(buf, "Inactive: %8d kB\n", inactiveFile/1024)
- fmt.Fprintf(buf, "Active(anon): %8d kB\n", anon/1024)
- fmt.Fprintf(buf, "Inactive(anon): 0 kB\n")
- fmt.Fprintf(buf, "Active(file): %8d kB\n", activeFile/1024)
- fmt.Fprintf(buf, "Inactive(file): %8d kB\n", inactiveFile/1024)
- fmt.Fprintf(buf, "Unevictable: 0 kB\n") // TODO(b/31823263)
- fmt.Fprintf(buf, "Mlocked: 0 kB\n") // TODO(b/31823263)
- fmt.Fprintf(buf, "SwapTotal: 0 kB\n")
- fmt.Fprintf(buf, "SwapFree: 0 kB\n")
- fmt.Fprintf(buf, "Dirty: 0 kB\n")
- fmt.Fprintf(buf, "Writeback: 0 kB\n")
- fmt.Fprintf(buf, "AnonPages: %8d kB\n", anon/1024)
- fmt.Fprintf(buf, "Mapped: %8d kB\n", file/1024) // doesn't count mapped tmpfs, which we don't know
- fmt.Fprintf(buf, "Shmem: %8d kB\n", snapshot.Tmpfs/1024)
- return nil
-}
diff --git a/pkg/sentry/fsimpl/proc/mounts.go b/pkg/sentry/fsimpl/proc/mounts.go
deleted file mode 100644
index e81b1e910..000000000
--- a/pkg/sentry/fsimpl/proc/mounts.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package proc
-
-import "gvisor.dev/gvisor/pkg/sentry/kernel"
-
-// TODO(b/138862512): Implement mountInfoFile and mountsFile.
-
-// mountInfoFile implements vfs.DynamicBytesSource for /proc/[pid]/mountinfo.
-//
-// +stateify savable
-type mountInfoFile struct {
- t *kernel.Task
-}
-
-// mountsFile implements vfs.DynamicBytesSource for /proc/[pid]/mounts.
-//
-// +stateify savable
-type mountsFile struct {
- t *kernel.Task
-}
diff --git a/pkg/sentry/fsimpl/proc/net.go b/pkg/sentry/fsimpl/proc/net.go
deleted file mode 100644
index fd46eebf8..000000000
--- a/pkg/sentry/fsimpl/proc/net.go
+++ /dev/null
@@ -1,338 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package proc
-
-import (
- "bytes"
- "fmt"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/binary"
- "gvisor.dev/gvisor/pkg/log"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- "gvisor.dev/gvisor/pkg/sentry/inet"
- "gvisor.dev/gvisor/pkg/sentry/kernel"
- "gvisor.dev/gvisor/pkg/sentry/socket"
- "gvisor.dev/gvisor/pkg/sentry/socket/unix"
- "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
-)
-
-// ifinet6 implements vfs.DynamicBytesSource for /proc/net/if_inet6.
-//
-// +stateify savable
-type ifinet6 struct {
- s inet.Stack
-}
-
-var _ vfs.DynamicBytesSource = (*ifinet6)(nil)
-
-func (n *ifinet6) contents() []string {
- var lines []string
- nics := n.s.Interfaces()
- for id, naddrs := range n.s.InterfaceAddrs() {
- nic, ok := nics[id]
- if !ok {
- // NIC was added after NICNames was called. We'll just
- // ignore it.
- continue
- }
-
- for _, a := range naddrs {
- // IPv6 only.
- if a.Family != linux.AF_INET6 {
- continue
- }
-
- // Fields:
- // IPv6 address displayed in 32 hexadecimal chars without colons
- // Netlink device number (interface index) in hexadecimal (use nic id)
- // Prefix length in hexadecimal
- // Scope value (use 0)
- // Interface flags
- // Device name
- lines = append(lines, fmt.Sprintf("%032x %02x %02x %02x %02x %8s\n", a.Addr, id, a.PrefixLen, 0, a.Flags, nic.Name))
- }
- }
- return lines
-}
-
-// Generate implements vfs.DynamicBytesSource.Generate.
-func (n *ifinet6) Generate(ctx context.Context, buf *bytes.Buffer) error {
- for _, l := range n.contents() {
- buf.WriteString(l)
- }
- return nil
-}
-
-// netDev implements vfs.DynamicBytesSource for /proc/net/dev.
-//
-// +stateify savable
-type netDev struct {
- s inet.Stack
-}
-
-var _ vfs.DynamicBytesSource = (*netDev)(nil)
-
-// Generate implements vfs.DynamicBytesSource.Generate.
-func (n *netDev) Generate(ctx context.Context, buf *bytes.Buffer) error {
- interfaces := n.s.Interfaces()
- buf.WriteString("Inter-| Receive | Transmit\n")
- buf.WriteString(" face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed\n")
-
- for _, i := range interfaces {
- // Implements the same format as
- // net/core/net-procfs.c:dev_seq_printf_stats.
- var stats inet.StatDev
- if err := n.s.Statistics(&stats, i.Name); err != nil {
- log.Warningf("Failed to retrieve interface statistics for %v: %v", i.Name, err)
- continue
- }
- fmt.Fprintf(
- buf,
- "%6s: %7d %7d %4d %4d %4d %5d %10d %9d %8d %7d %4d %4d %4d %5d %7d %10d\n",
- i.Name,
- // Received
- stats[0], // bytes
- stats[1], // packets
- stats[2], // errors
- stats[3], // dropped
- stats[4], // fifo
- stats[5], // frame
- stats[6], // compressed
- stats[7], // multicast
- // Transmitted
- stats[8], // bytes
- stats[9], // packets
- stats[10], // errors
- stats[11], // dropped
- stats[12], // fifo
- stats[13], // frame
- stats[14], // compressed
- stats[15], // multicast
- )
- }
-
- return nil
-}
-
-// netUnix implements vfs.DynamicBytesSource for /proc/net/unix.
-//
-// +stateify savable
-type netUnix struct {
- k *kernel.Kernel
-}
-
-var _ vfs.DynamicBytesSource = (*netUnix)(nil)
-
-// Generate implements vfs.DynamicBytesSource.Generate.
-func (n *netUnix) Generate(ctx context.Context, buf *bytes.Buffer) error {
- buf.WriteString("Num RefCount Protocol Flags Type St Inode Path\n")
- for _, se := range n.k.ListSockets() {
- s := se.Sock.Get()
- if s == nil {
- log.Debugf("Couldn't resolve weakref %v in socket table, racing with destruction?", se.Sock)
- continue
- }
- sfile := s.(*fs.File)
- if family, _, _ := sfile.FileOperations.(socket.Socket).Type(); family != linux.AF_UNIX {
- s.DecRef()
- // Not a unix socket.
- continue
- }
- sops := sfile.FileOperations.(*unix.SocketOperations)
-
- addr, err := sops.Endpoint().GetLocalAddress()
- if err != nil {
- log.Warningf("Failed to retrieve socket name from %+v: %v", sfile, err)
- addr.Addr = "<unknown>"
- }
-
- sockFlags := 0
- if ce, ok := sops.Endpoint().(transport.ConnectingEndpoint); ok {
- if ce.Listening() {
- // For unix domain sockets, linux reports a single flag
- // value if the socket is listening, of __SO_ACCEPTCON.
- sockFlags = linux.SO_ACCEPTCON
- }
- }
-
- // In the socket entry below, the value for the 'Num' field requires
- // some consideration. Linux prints the address to the struct
- // unix_sock representing a socket in the kernel, but may redact the
- // value for unprivileged users depending on the kptr_restrict
- // sysctl.
- //
- // One use for this field is to allow a privileged user to
- // introspect into the kernel memory to determine information about
- // a socket not available through procfs, such as the socket's peer.
- //
- // In gvisor, returning a pointer to our internal structures would
- // be pointless, as it wouldn't match the memory layout for struct
- // unix_sock, making introspection difficult. We could populate a
- // struct unix_sock with the appropriate data, but even that
- // requires consideration for which kernel version to emulate, as
- // the definition of this struct changes over time.
- //
- // For now, we always redact this pointer.
- fmt.Fprintf(buf, "%#016p: %08X %08X %08X %04X %02X %5d",
- (*unix.SocketOperations)(nil), // Num, pointer to kernel socket struct.
- sfile.ReadRefs()-1, // RefCount, don't count our own ref.
- 0, // Protocol, always 0 for UDS.
- sockFlags, // Flags.
- sops.Endpoint().Type(), // Type.
- sops.State(), // State.
- sfile.InodeID(), // Inode.
- )
-
- // Path
- if len(addr.Addr) != 0 {
- if addr.Addr[0] == 0 {
- // Abstract path.
- fmt.Fprintf(buf, " @%s", string(addr.Addr[1:]))
- } else {
- fmt.Fprintf(buf, " %s", string(addr.Addr))
- }
- }
- fmt.Fprintf(buf, "\n")
-
- s.DecRef()
- }
- return nil
-}
-
-// netTCP implements vfs.DynamicBytesSource for /proc/net/tcp.
-//
-// +stateify savable
-type netTCP struct {
- k *kernel.Kernel
-}
-
-var _ vfs.DynamicBytesSource = (*netTCP)(nil)
-
-func (n *netTCP) Generate(ctx context.Context, buf *bytes.Buffer) error {
- t := kernel.TaskFromContext(ctx)
- buf.WriteString(" sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode \n")
- for _, se := range n.k.ListSockets() {
- s := se.Sock.Get()
- if s == nil {
- log.Debugf("Couldn't resolve weakref %+v in socket table, racing with destruction?", se.Sock)
- continue
- }
- sfile := s.(*fs.File)
- sops, ok := sfile.FileOperations.(socket.Socket)
- if !ok {
- panic(fmt.Sprintf("Found non-socket file in socket table: %+v", sfile))
- }
- if family, stype, _ := sops.Type(); !(family == linux.AF_INET && stype == linux.SOCK_STREAM) {
- s.DecRef()
- // Not tcp4 sockets.
- continue
- }
-
- // Linux's documentation for the fields below can be found at
- // https://www.kernel.org/doc/Documentation/networking/proc_net_tcp.txt.
- // For Linux's implementation, see net/ipv4/tcp_ipv4.c:get_tcp4_sock().
- // Note that the header doesn't contain labels for all the fields.
-
- // Field: sl; entry number.
- fmt.Fprintf(buf, "%4d: ", se.ID)
-
- portBuf := make([]byte, 2)
-
- // Field: local_adddress.
- var localAddr linux.SockAddrInet
- if local, _, err := sops.GetSockName(t); err == nil {
- localAddr = *local.(*linux.SockAddrInet)
- }
- binary.LittleEndian.PutUint16(portBuf, localAddr.Port)
- fmt.Fprintf(buf, "%08X:%04X ",
- binary.LittleEndian.Uint32(localAddr.Addr[:]),
- portBuf)
-
- // Field: rem_address.
- var remoteAddr linux.SockAddrInet
- if remote, _, err := sops.GetPeerName(t); err == nil {
- remoteAddr = *remote.(*linux.SockAddrInet)
- }
- binary.LittleEndian.PutUint16(portBuf, remoteAddr.Port)
- fmt.Fprintf(buf, "%08X:%04X ",
- binary.LittleEndian.Uint32(remoteAddr.Addr[:]),
- portBuf)
-
- // Field: state; socket state.
- fmt.Fprintf(buf, "%02X ", sops.State())
-
- // Field: tx_queue, rx_queue; number of packets in the transmit and
- // receive queue. Unimplemented.
- fmt.Fprintf(buf, "%08X:%08X ", 0, 0)
-
- // Field: tr, tm->when; timer active state and number of jiffies
- // until timer expires. Unimplemented.
- fmt.Fprintf(buf, "%02X:%08X ", 0, 0)
-
- // Field: retrnsmt; number of unrecovered RTO timeouts.
- // Unimplemented.
- fmt.Fprintf(buf, "%08X ", 0)
-
- // Field: uid.
- uattr, err := sfile.Dirent.Inode.UnstableAttr(ctx)
- if err != nil {
- log.Warningf("Failed to retrieve unstable attr for socket file: %v", err)
- fmt.Fprintf(buf, "%5d ", 0)
- } else {
- fmt.Fprintf(buf, "%5d ", uint32(uattr.Owner.UID.In(t.UserNamespace()).OrOverflow()))
- }
-
- // Field: timeout; number of unanswered 0-window probes.
- // Unimplemented.
- fmt.Fprintf(buf, "%8d ", 0)
-
- // Field: inode.
- fmt.Fprintf(buf, "%8d ", sfile.InodeID())
-
- // Field: refcount. Don't count the ref we obtain while deferencing
- // the weakref to this socket.
- fmt.Fprintf(buf, "%d ", sfile.ReadRefs()-1)
-
- // Field: Socket struct address. Redacted due to the same reason as
- // the 'Num' field in /proc/net/unix, see netUnix.ReadSeqFileData.
- fmt.Fprintf(buf, "%#016p ", (*socket.Socket)(nil))
-
- // Field: retransmit timeout. Unimplemented.
- fmt.Fprintf(buf, "%d ", 0)
-
- // Field: predicted tick of soft clock (delayed ACK control data).
- // Unimplemented.
- fmt.Fprintf(buf, "%d ", 0)
-
- // Field: (ack.quick<<1)|ack.pingpong, Unimplemented.
- fmt.Fprintf(buf, "%d ", 0)
-
- // Field: sending congestion window, Unimplemented.
- fmt.Fprintf(buf, "%d ", 0)
-
- // Field: Slow start size threshold, -1 if threshold >= 0xFFFF.
- // Unimplemented, report as large threshold.
- fmt.Fprintf(buf, "%d", -1)
-
- fmt.Fprintf(buf, "\n")
-
- s.DecRef()
- }
-
- return nil
-}
diff --git a/pkg/sentry/fsimpl/proc/net_test.go b/pkg/sentry/fsimpl/proc/net_test.go
deleted file mode 100644
index 20a77a8ca..000000000
--- a/pkg/sentry/fsimpl/proc/net_test.go
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package proc
-
-import (
- "bytes"
- "reflect"
- "testing"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/inet"
-)
-
-func newIPv6TestStack() *inet.TestStack {
- s := inet.NewTestStack()
- s.SupportsIPv6Flag = true
- return s
-}
-
-func TestIfinet6NoAddresses(t *testing.T) {
- n := &ifinet6{s: newIPv6TestStack()}
- var buf bytes.Buffer
- n.Generate(contexttest.Context(t), &buf)
- if buf.Len() > 0 {
- t.Errorf("n.Generate() generated = %v, want = %v", buf.Bytes(), []byte{})
- }
-}
-
-func TestIfinet6(t *testing.T) {
- s := newIPv6TestStack()
- s.InterfacesMap[1] = inet.Interface{Name: "eth0"}
- s.InterfaceAddrsMap[1] = []inet.InterfaceAddr{
- {
- Family: linux.AF_INET6,
- PrefixLen: 128,
- Addr: []byte("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"),
- },
- }
- s.InterfacesMap[2] = inet.Interface{Name: "eth1"}
- s.InterfaceAddrsMap[2] = []inet.InterfaceAddr{
- {
- Family: linux.AF_INET6,
- PrefixLen: 128,
- Addr: []byte("\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"),
- },
- }
- want := map[string]struct{}{
- "000102030405060708090a0b0c0d0e0f 01 80 00 00 eth0\n": {},
- "101112131415161718191a1b1c1d1e1f 02 80 00 00 eth1\n": {},
- }
-
- n := &ifinet6{s: s}
- contents := n.contents()
- if len(contents) != len(want) {
- t.Errorf("Got len(n.contents()) = %d, want = %d", len(contents), len(want))
- }
- got := map[string]struct{}{}
- for _, l := range contents {
- got[l] = struct{}{}
- }
-
- if !reflect.DeepEqual(got, want) {
- t.Errorf("Got n.contents() = %v, want = %v", got, want)
- }
-}
diff --git a/pkg/sentry/fsimpl/proc/proc.go b/pkg/sentry/fsimpl/proc/proc.go
deleted file mode 100644
index 31dec36de..000000000
--- a/pkg/sentry/fsimpl/proc/proc.go
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package proc implements a partial in-memory file system for procfs.
-package proc
diff --git a/pkg/sentry/fsimpl/proc/stat.go b/pkg/sentry/fsimpl/proc/stat.go
deleted file mode 100644
index 720db3828..000000000
--- a/pkg/sentry/fsimpl/proc/stat.go
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package proc
-
-import (
- "bytes"
- "fmt"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/kernel"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
-)
-
-// cpuStats contains the breakdown of CPU time for /proc/stat.
-type cpuStats struct {
- // user is time spent in userspace tasks with non-positive niceness.
- user uint64
-
- // nice is time spent in userspace tasks with positive niceness.
- nice uint64
-
- // system is time spent in non-interrupt kernel context.
- system uint64
-
- // idle is time spent idle.
- idle uint64
-
- // ioWait is time spent waiting for IO.
- ioWait uint64
-
- // irq is time spent in interrupt context.
- irq uint64
-
- // softirq is time spent in software interrupt context.
- softirq uint64
-
- // steal is involuntary wait time.
- steal uint64
-
- // guest is time spent in guests with non-positive niceness.
- guest uint64
-
- // guestNice is time spent in guests with positive niceness.
- guestNice uint64
-}
-
-// String implements fmt.Stringer.
-func (c cpuStats) String() string {
- return fmt.Sprintf("%d %d %d %d %d %d %d %d %d %d", c.user, c.nice, c.system, c.idle, c.ioWait, c.irq, c.softirq, c.steal, c.guest, c.guestNice)
-}
-
-// statData implements vfs.DynamicBytesSource for /proc/stat.
-//
-// +stateify savable
-type statData struct {
- // k is the owning Kernel.
- k *kernel.Kernel
-}
-
-var _ vfs.DynamicBytesSource = (*statData)(nil)
-
-// Generate implements vfs.DynamicBytesSource.Generate.
-func (s *statData) Generate(ctx context.Context, buf *bytes.Buffer) error {
- // TODO(b/37226836): We currently export only zero CPU stats. We could
- // at least provide some aggregate stats.
- var cpu cpuStats
- fmt.Fprintf(buf, "cpu %s\n", cpu)
-
- for c, max := uint(0), s.k.ApplicationCores(); c < max; c++ {
- fmt.Fprintf(buf, "cpu%d %s\n", c, cpu)
- }
-
- // The total number of interrupts is dependent on the CPUs and PCI
- // devices on the system. See arch_probe_nr_irqs.
- //
- // Since we don't report real interrupt stats, just choose an arbitrary
- // value from a representative VM.
- const numInterrupts = 256
-
- // The Kernel doesn't handle real interrupts, so report all zeroes.
- // TODO(b/37226836): We could count page faults as #PF.
- fmt.Fprintf(buf, "intr 0") // total
- for i := 0; i < numInterrupts; i++ {
- fmt.Fprintf(buf, " 0")
- }
- fmt.Fprintf(buf, "\n")
-
- // Total number of context switches.
- // TODO(b/37226836): Count this.
- fmt.Fprintf(buf, "ctxt 0\n")
-
- // CLOCK_REALTIME timestamp from boot, in seconds.
- fmt.Fprintf(buf, "btime %d\n", s.k.Timekeeper().BootTime().Seconds())
-
- // Total number of clones.
- // TODO(b/37226836): Count this.
- fmt.Fprintf(buf, "processes 0\n")
-
- // Number of runnable tasks.
- // TODO(b/37226836): Count this.
- fmt.Fprintf(buf, "procs_running 0\n")
-
- // Number of tasks waiting on IO.
- // TODO(b/37226836): Count this.
- fmt.Fprintf(buf, "procs_blocked 0\n")
-
- // Number of each softirq handled.
- fmt.Fprintf(buf, "softirq 0") // total
- for i := 0; i < linux.NumSoftIRQ; i++ {
- fmt.Fprintf(buf, " 0")
- }
- fmt.Fprintf(buf, "\n")
- return nil
-}
diff --git a/pkg/sentry/fsimpl/proc/sys.go b/pkg/sentry/fsimpl/proc/sys.go
deleted file mode 100644
index b88256e12..000000000
--- a/pkg/sentry/fsimpl/proc/sys.go
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package proc
-
-import (
- "bytes"
- "fmt"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/kernel"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
-)
-
-// mmapMinAddrData implements vfs.DynamicBytesSource for
-// /proc/sys/vm/mmap_min_addr.
-//
-// +stateify savable
-type mmapMinAddrData struct {
- k *kernel.Kernel
-}
-
-var _ vfs.DynamicBytesSource = (*mmapMinAddrData)(nil)
-
-// Generate implements vfs.DynamicBytesSource.Generate.
-func (d *mmapMinAddrData) Generate(ctx context.Context, buf *bytes.Buffer) error {
- fmt.Fprintf(buf, "%d\n", d.k.Platform.MinUserAddress())
- return nil
-}
-
-// +stateify savable
-type overcommitMemory struct{}
-
-var _ vfs.DynamicBytesSource = (*overcommitMemory)(nil)
-
-// Generate implements vfs.DynamicBytesSource.Generate.
-func (d *overcommitMemory) Generate(ctx context.Context, buf *bytes.Buffer) error {
- fmt.Fprintf(buf, "0\n")
- return nil
-}
diff --git a/pkg/sentry/fsimpl/proc/task.go b/pkg/sentry/fsimpl/proc/task.go
deleted file mode 100644
index c46e05c3a..000000000
--- a/pkg/sentry/fsimpl/proc/task.go
+++ /dev/null
@@ -1,261 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package proc
-
-import (
- "bytes"
- "fmt"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/kernel"
- "gvisor.dev/gvisor/pkg/sentry/limits"
- "gvisor.dev/gvisor/pkg/sentry/mm"
- "gvisor.dev/gvisor/pkg/sentry/usage"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
-)
-
-// mapsCommon is embedded by mapsData and smapsData.
-type mapsCommon struct {
- t *kernel.Task
-}
-
-// mm gets the kernel task's MemoryManager. No additional reference is taken on
-// mm here. This is safe because MemoryManager.destroy is required to leave the
-// MemoryManager in a state where it's still usable as a DynamicBytesSource.
-func (md *mapsCommon) mm() *mm.MemoryManager {
- var tmm *mm.MemoryManager
- md.t.WithMuLocked(func(t *kernel.Task) {
- if mm := t.MemoryManager(); mm != nil {
- tmm = mm
- }
- })
- return tmm
-}
-
-// mapsData implements vfs.DynamicBytesSource for /proc/[pid]/maps.
-//
-// +stateify savable
-type mapsData struct {
- mapsCommon
-}
-
-var _ vfs.DynamicBytesSource = (*mapsData)(nil)
-
-// Generate implements vfs.DynamicBytesSource.Generate.
-func (md *mapsData) Generate(ctx context.Context, buf *bytes.Buffer) error {
- if mm := md.mm(); mm != nil {
- mm.ReadMapsDataInto(ctx, buf)
- }
- return nil
-}
-
-// smapsData implements vfs.DynamicBytesSource for /proc/[pid]/smaps.
-//
-// +stateify savable
-type smapsData struct {
- mapsCommon
-}
-
-var _ vfs.DynamicBytesSource = (*smapsData)(nil)
-
-// Generate implements vfs.DynamicBytesSource.Generate.
-func (sd *smapsData) Generate(ctx context.Context, buf *bytes.Buffer) error {
- if mm := sd.mm(); mm != nil {
- mm.ReadSmapsDataInto(ctx, buf)
- }
- return nil
-}
-
-// +stateify savable
-type taskStatData struct {
- t *kernel.Task
-
- // If tgstats is true, accumulate fault stats (not implemented) and CPU
- // time across all tasks in t's thread group.
- tgstats bool
-
- // pidns is the PID namespace associated with the proc filesystem that
- // includes the file using this statData.
- pidns *kernel.PIDNamespace
-}
-
-var _ vfs.DynamicBytesSource = (*taskStatData)(nil)
-
-// Generate implements vfs.DynamicBytesSource.Generate.
-func (s *taskStatData) Generate(ctx context.Context, buf *bytes.Buffer) error {
- fmt.Fprintf(buf, "%d ", s.pidns.IDOfTask(s.t))
- fmt.Fprintf(buf, "(%s) ", s.t.Name())
- fmt.Fprintf(buf, "%c ", s.t.StateStatus()[0])
- ppid := kernel.ThreadID(0)
- if parent := s.t.Parent(); parent != nil {
- ppid = s.pidns.IDOfThreadGroup(parent.ThreadGroup())
- }
- fmt.Fprintf(buf, "%d ", ppid)
- fmt.Fprintf(buf, "%d ", s.pidns.IDOfProcessGroup(s.t.ThreadGroup().ProcessGroup()))
- fmt.Fprintf(buf, "%d ", s.pidns.IDOfSession(s.t.ThreadGroup().Session()))
- fmt.Fprintf(buf, "0 0 " /* tty_nr tpgid */)
- fmt.Fprintf(buf, "0 " /* flags */)
- fmt.Fprintf(buf, "0 0 0 0 " /* minflt cminflt majflt cmajflt */)
- var cputime usage.CPUStats
- if s.tgstats {
- cputime = s.t.ThreadGroup().CPUStats()
- } else {
- cputime = s.t.CPUStats()
- }
- fmt.Fprintf(buf, "%d %d ", linux.ClockTFromDuration(cputime.UserTime), linux.ClockTFromDuration(cputime.SysTime))
- cputime = s.t.ThreadGroup().JoinedChildCPUStats()
- fmt.Fprintf(buf, "%d %d ", linux.ClockTFromDuration(cputime.UserTime), linux.ClockTFromDuration(cputime.SysTime))
- fmt.Fprintf(buf, "%d %d ", s.t.Priority(), s.t.Niceness())
- fmt.Fprintf(buf, "%d ", s.t.ThreadGroup().Count())
-
- // itrealvalue. Since kernel 2.6.17, this field is no longer
- // maintained, and is hard coded as 0.
- fmt.Fprintf(buf, "0 ")
-
- // Start time is relative to boot time, expressed in clock ticks.
- fmt.Fprintf(buf, "%d ", linux.ClockTFromDuration(s.t.StartTime().Sub(s.t.Kernel().Timekeeper().BootTime())))
-
- var vss, rss uint64
- s.t.WithMuLocked(func(t *kernel.Task) {
- if mm := t.MemoryManager(); mm != nil {
- vss = mm.VirtualMemorySize()
- rss = mm.ResidentSetSize()
- }
- })
- fmt.Fprintf(buf, "%d %d ", vss, rss/usermem.PageSize)
-
- // rsslim.
- fmt.Fprintf(buf, "%d ", s.t.ThreadGroup().Limits().Get(limits.Rss).Cur)
-
- fmt.Fprintf(buf, "0 0 0 0 0 " /* startcode endcode startstack kstkesp kstkeip */)
- fmt.Fprintf(buf, "0 0 0 0 0 " /* signal blocked sigignore sigcatch wchan */)
- fmt.Fprintf(buf, "0 0 " /* nswap cnswap */)
- terminationSignal := linux.Signal(0)
- if s.t == s.t.ThreadGroup().Leader() {
- terminationSignal = s.t.ThreadGroup().TerminationSignal()
- }
- fmt.Fprintf(buf, "%d ", terminationSignal)
- fmt.Fprintf(buf, "0 0 0 " /* processor rt_priority policy */)
- fmt.Fprintf(buf, "0 0 0 " /* delayacct_blkio_ticks guest_time cguest_time */)
- fmt.Fprintf(buf, "0 0 0 0 0 0 0 " /* start_data end_data start_brk arg_start arg_end env_start env_end */)
- fmt.Fprintf(buf, "0\n" /* exit_code */)
-
- return nil
-}
-
-// statmData implements vfs.DynamicBytesSource for /proc/[pid]/statm.
-//
-// +stateify savable
-type statmData struct {
- t *kernel.Task
-}
-
-var _ vfs.DynamicBytesSource = (*statmData)(nil)
-
-// Generate implements vfs.DynamicBytesSource.Generate.
-func (s *statmData) Generate(ctx context.Context, buf *bytes.Buffer) error {
- var vss, rss uint64
- s.t.WithMuLocked(func(t *kernel.Task) {
- if mm := t.MemoryManager(); mm != nil {
- vss = mm.VirtualMemorySize()
- rss = mm.ResidentSetSize()
- }
- })
-
- fmt.Fprintf(buf, "%d %d 0 0 0 0 0\n", vss/usermem.PageSize, rss/usermem.PageSize)
- return nil
-}
-
-// statusData implements vfs.DynamicBytesSource for /proc/[pid]/status.
-//
-// +stateify savable
-type statusData struct {
- t *kernel.Task
- pidns *kernel.PIDNamespace
-}
-
-var _ vfs.DynamicBytesSource = (*statusData)(nil)
-
-// Generate implements vfs.DynamicBytesSource.Generate.
-func (s *statusData) Generate(ctx context.Context, buf *bytes.Buffer) error {
- fmt.Fprintf(buf, "Name:\t%s\n", s.t.Name())
- fmt.Fprintf(buf, "State:\t%s\n", s.t.StateStatus())
- fmt.Fprintf(buf, "Tgid:\t%d\n", s.pidns.IDOfThreadGroup(s.t.ThreadGroup()))
- fmt.Fprintf(buf, "Pid:\t%d\n", s.pidns.IDOfTask(s.t))
- ppid := kernel.ThreadID(0)
- if parent := s.t.Parent(); parent != nil {
- ppid = s.pidns.IDOfThreadGroup(parent.ThreadGroup())
- }
- fmt.Fprintf(buf, "PPid:\t%d\n", ppid)
- tpid := kernel.ThreadID(0)
- if tracer := s.t.Tracer(); tracer != nil {
- tpid = s.pidns.IDOfTask(tracer)
- }
- fmt.Fprintf(buf, "TracerPid:\t%d\n", tpid)
- var fds int
- var vss, rss, data uint64
- s.t.WithMuLocked(func(t *kernel.Task) {
- if fdTable := t.FDTable(); fdTable != nil {
- fds = fdTable.Size()
- }
- if mm := t.MemoryManager(); mm != nil {
- vss = mm.VirtualMemorySize()
- rss = mm.ResidentSetSize()
- data = mm.VirtualDataSize()
- }
- })
- fmt.Fprintf(buf, "FDSize:\t%d\n", fds)
- fmt.Fprintf(buf, "VmSize:\t%d kB\n", vss>>10)
- fmt.Fprintf(buf, "VmRSS:\t%d kB\n", rss>>10)
- fmt.Fprintf(buf, "VmData:\t%d kB\n", data>>10)
- fmt.Fprintf(buf, "Threads:\t%d\n", s.t.ThreadGroup().Count())
- creds := s.t.Credentials()
- fmt.Fprintf(buf, "CapInh:\t%016x\n", creds.InheritableCaps)
- fmt.Fprintf(buf, "CapPrm:\t%016x\n", creds.PermittedCaps)
- fmt.Fprintf(buf, "CapEff:\t%016x\n", creds.EffectiveCaps)
- fmt.Fprintf(buf, "CapBnd:\t%016x\n", creds.BoundingCaps)
- fmt.Fprintf(buf, "Seccomp:\t%d\n", s.t.SeccompMode())
- return nil
-}
-
-// ioUsage is the /proc/<pid>/io and /proc/<pid>/task/<tid>/io data provider.
-type ioUsage interface {
- // IOUsage returns the io usage data.
- IOUsage() *usage.IO
-}
-
-// +stateify savable
-type ioData struct {
- ioUsage
-}
-
-var _ vfs.DynamicBytesSource = (*ioData)(nil)
-
-// Generate implements vfs.DynamicBytesSource.Generate.
-func (i *ioData) Generate(ctx context.Context, buf *bytes.Buffer) error {
- io := usage.IO{}
- io.Accumulate(i.IOUsage())
-
- fmt.Fprintf(buf, "char: %d\n", io.CharsRead)
- fmt.Fprintf(buf, "wchar: %d\n", io.CharsWritten)
- fmt.Fprintf(buf, "syscr: %d\n", io.ReadSyscalls)
- fmt.Fprintf(buf, "syscw: %d\n", io.WriteSyscalls)
- fmt.Fprintf(buf, "read_bytes: %d\n", io.BytesRead)
- fmt.Fprintf(buf, "write_bytes: %d\n", io.BytesWritten)
- fmt.Fprintf(buf, "cancelled_write_bytes: %d\n", io.BytesWriteCancelled)
- return nil
-}
diff --git a/pkg/sentry/fsimpl/proc/version.go b/pkg/sentry/fsimpl/proc/version.go
deleted file mode 100644
index e1643d4e0..000000000
--- a/pkg/sentry/fsimpl/proc/version.go
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package proc
-
-import (
- "bytes"
- "fmt"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/kernel"
- "gvisor.dev/gvisor/pkg/sentry/vfs"
-)
-
-// versionData implements vfs.DynamicBytesSource for /proc/version.
-//
-// +stateify savable
-type versionData struct {
- // k is the owning Kernel.
- k *kernel.Kernel
-}
-
-var _ vfs.DynamicBytesSource = (*versionData)(nil)
-
-// Generate implements vfs.DynamicBytesSource.Generate.
-func (v *versionData) Generate(ctx context.Context, buf *bytes.Buffer) error {
- init := v.k.GlobalInit()
- if init == nil {
- // Attempted to read before the init Task is created. This can
- // only occur during startup, which should never need to read
- // this file.
- panic("Attempted to read version before initial Task is available")
- }
-
- // /proc/version takes the form:
- //
- // "SYSNAME version RELEASE (COMPILE_USER@COMPILE_HOST)
- // (COMPILER_VERSION) VERSION"
- //
- // where:
- // - SYSNAME, RELEASE, and VERSION are the same as returned by
- // sys_utsname
- // - COMPILE_USER is the user that build the kernel
- // - COMPILE_HOST is the hostname of the machine on which the kernel
- // was built
- // - COMPILER_VERSION is the version reported by the building compiler
- //
- // Since we don't really want to expose build information to
- // applications, those fields are omitted.
- //
- // FIXME(mpratt): Using Version from the init task SyscallTable
- // disregards the different version a task may have (e.g., in a uts
- // namespace).
- ver := init.Leader().SyscallTable().Version
- fmt.Fprintf(buf, "%s version %s %s\n", ver.Sysname, ver.Release, ver.Version)
- return nil
-}
diff --git a/pkg/sentry/hostcpu/BUILD b/pkg/sentry/hostcpu/BUILD
deleted file mode 100644
index d4a420e60..000000000
--- a/pkg/sentry/hostcpu/BUILD
+++ /dev/null
@@ -1,21 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "hostcpu",
- srcs = [
- "getcpu_amd64.s",
- "hostcpu.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/hostcpu",
- visibility = ["//:sandbox"],
-)
-
-go_test(
- name = "hostcpu_test",
- size = "small",
- srcs = ["hostcpu_test.go"],
- embed = [":hostcpu"],
-)
diff --git a/pkg/sentry/hostcpu/hostcpu_state_autogen.go b/pkg/sentry/hostcpu/hostcpu_state_autogen.go
new file mode 100755
index 000000000..f04a56ec0
--- /dev/null
+++ b/pkg/sentry/hostcpu/hostcpu_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package hostcpu
+
diff --git a/pkg/sentry/hostcpu/hostcpu_test.go b/pkg/sentry/hostcpu/hostcpu_test.go
deleted file mode 100644
index 7d6885c9e..000000000
--- a/pkg/sentry/hostcpu/hostcpu_test.go
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package hostcpu
-
-import (
- "fmt"
- "testing"
-)
-
-func TestMaxValueInLinuxBitmap(t *testing.T) {
- for _, test := range []struct {
- str string
- max uint64
- }{
- {"0", 0},
- {"0\n", 0},
- {"0,2", 2},
- {"0-63", 63},
- {"0-3,8-11", 11},
- } {
- t.Run(fmt.Sprintf("%q", test.str), func(t *testing.T) {
- max, err := maxValueInLinuxBitmap(test.str)
- if err != nil || max != test.max {
- t.Errorf("maxValueInLinuxBitmap: got (%d, %v), wanted (%d, nil)", max, err, test.max)
- }
- })
- }
-}
-
-func TestMaxValueInLinuxBitmapErrors(t *testing.T) {
- for _, str := range []string{"", "\n"} {
- t.Run(fmt.Sprintf("%q", str), func(t *testing.T) {
- max, err := maxValueInLinuxBitmap(str)
- if err == nil {
- t.Errorf("maxValueInLinuxBitmap: got (%d, nil), wanted (_, error)", max)
- }
- t.Log(err)
- })
- }
-}
diff --git a/pkg/sentry/hostmm/BUILD b/pkg/sentry/hostmm/BUILD
deleted file mode 100644
index 67831d5a1..000000000
--- a/pkg/sentry/hostmm/BUILD
+++ /dev/null
@@ -1,18 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "hostmm",
- srcs = [
- "cgroup.go",
- "hostmm.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/hostmm",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/fd",
- "//pkg/log",
- "//pkg/sentry/usermem",
- ],
-)
diff --git a/pkg/sentry/hostmm/hostmm_state_autogen.go b/pkg/sentry/hostmm/hostmm_state_autogen.go
new file mode 100755
index 000000000..730de5101
--- /dev/null
+++ b/pkg/sentry/hostmm/hostmm_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package hostmm
+
diff --git a/pkg/sentry/inet/BUILD b/pkg/sentry/inet/BUILD
deleted file mode 100644
index 184b566d9..000000000
--- a/pkg/sentry/inet/BUILD
+++ /dev/null
@@ -1,17 +0,0 @@
-package(
- default_visibility = ["//:sandbox"],
- licenses = ["notice"],
-)
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "inet",
- srcs = [
- "context.go",
- "inet.go",
- "test_stack.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/inet",
- deps = ["//pkg/sentry/context"],
-)
diff --git a/pkg/sentry/inet/inet_state_autogen.go b/pkg/sentry/inet/inet_state_autogen.go
new file mode 100755
index 000000000..00f40556c
--- /dev/null
+++ b/pkg/sentry/inet/inet_state_autogen.go
@@ -0,0 +1,26 @@
+// automatically generated by stateify.
+
+package inet
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *TCPBufferSize) beforeSave() {}
+func (x *TCPBufferSize) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Min", &x.Min)
+ m.Save("Default", &x.Default)
+ m.Save("Max", &x.Max)
+}
+
+func (x *TCPBufferSize) afterLoad() {}
+func (x *TCPBufferSize) load(m state.Map) {
+ m.Load("Min", &x.Min)
+ m.Load("Default", &x.Default)
+ m.Load("Max", &x.Max)
+}
+
+func init() {
+ state.Register("inet.TCPBufferSize", (*TCPBufferSize)(nil), state.Fns{Save: (*TCPBufferSize).save, Load: (*TCPBufferSize).load})
+}
diff --git a/pkg/sentry/kernel/BUILD b/pkg/sentry/kernel/BUILD
deleted file mode 100644
index e964a991b..000000000
--- a/pkg/sentry/kernel/BUILD
+++ /dev/null
@@ -1,239 +0,0 @@
-load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_template_instance(
- name = "pending_signals_list",
- out = "pending_signals_list.go",
- package = "kernel",
- prefix = "pendingSignal",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*pendingSignal",
- "Linker": "*pendingSignal",
- },
-)
-
-go_template_instance(
- name = "process_group_list",
- out = "process_group_list.go",
- package = "kernel",
- prefix = "processGroup",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*ProcessGroup",
- "Linker": "*ProcessGroup",
- },
-)
-
-go_template_instance(
- name = "seqatomic_taskgoroutineschedinfo",
- out = "seqatomic_taskgoroutineschedinfo_unsafe.go",
- package = "kernel",
- suffix = "TaskGoroutineSchedInfo",
- template = "//third_party/gvsync:generic_seqatomic",
- types = {
- "Value": "TaskGoroutineSchedInfo",
- },
-)
-
-go_template_instance(
- name = "session_list",
- out = "session_list.go",
- package = "kernel",
- prefix = "session",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*Session",
- "Linker": "*Session",
- },
-)
-
-go_template_instance(
- name = "task_list",
- out = "task_list.go",
- package = "kernel",
- prefix = "task",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*Task",
- "Linker": "*Task",
- },
-)
-
-go_template_instance(
- name = "socket_list",
- out = "socket_list.go",
- package = "kernel",
- prefix = "socket",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*SocketEntry",
- "Linker": "*SocketEntry",
- },
-)
-
-proto_library(
- name = "uncaught_signal_proto",
- srcs = ["uncaught_signal.proto"],
- visibility = ["//visibility:public"],
- deps = ["//pkg/sentry/arch:registers_proto"],
-)
-
-go_proto_library(
- name = "uncaught_signal_go_proto",
- importpath = "gvisor.dev/gvisor/pkg/sentry/kernel/uncaught_signal_go_proto",
- proto = ":uncaught_signal_proto",
- visibility = ["//visibility:public"],
- deps = ["//pkg/sentry/arch:registers_go_proto"],
-)
-
-go_library(
- name = "kernel",
- srcs = [
- "abstract_socket_namespace.go",
- "context.go",
- "fd_table.go",
- "fd_table_unsafe.go",
- "fs_context.go",
- "ipc_namespace.go",
- "kernel.go",
- "kernel_state.go",
- "pending_signals.go",
- "pending_signals_list.go",
- "pending_signals_state.go",
- "posixtimer.go",
- "process_group_list.go",
- "ptrace.go",
- "ptrace_amd64.go",
- "ptrace_arm64.go",
- "rseq.go",
- "seccomp.go",
- "seqatomic_taskgoroutineschedinfo_unsafe.go",
- "session_list.go",
- "sessions.go",
- "signal.go",
- "signal_handlers.go",
- "socket_list.go",
- "syscalls.go",
- "syscalls_state.go",
- "syslog.go",
- "task.go",
- "task_acct.go",
- "task_block.go",
- "task_clone.go",
- "task_context.go",
- "task_exec.go",
- "task_exit.go",
- "task_futex.go",
- "task_identity.go",
- "task_list.go",
- "task_log.go",
- "task_net.go",
- "task_run.go",
- "task_sched.go",
- "task_signals.go",
- "task_start.go",
- "task_stop.go",
- "task_syscall.go",
- "task_usermem.go",
- "thread_group.go",
- "threads.go",
- "timekeeper.go",
- "timekeeper_state.go",
- "uts_namespace.go",
- "vdso.go",
- "version.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/kernel",
- imports = [
- "gvisor.dev/gvisor/pkg/bpf",
- "gvisor.dev/gvisor/pkg/sentry/device",
- "gvisor.dev/gvisor/pkg/tcpip",
- ],
- visibility = ["//:sandbox"],
- deps = [
- ":uncaught_signal_go_proto",
- "//pkg/abi",
- "//pkg/abi/linux",
- "//pkg/amutex",
- "//pkg/binary",
- "//pkg/bits",
- "//pkg/bpf",
- "//pkg/cpuid",
- "//pkg/eventchannel",
- "//pkg/log",
- "//pkg/metric",
- "//pkg/refs",
- "//pkg/secio",
- "//pkg/sentry/arch",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/lock",
- "//pkg/sentry/fs/timerfd",
- "//pkg/sentry/hostcpu",
- "//pkg/sentry/inet",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/kernel/epoll",
- "//pkg/sentry/kernel/futex",
- "//pkg/sentry/kernel/sched",
- "//pkg/sentry/kernel/semaphore",
- "//pkg/sentry/kernel/shm",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/limits",
- "//pkg/sentry/loader",
- "//pkg/sentry/memmap",
- "//pkg/sentry/mm",
- "//pkg/sentry/pgalloc",
- "//pkg/sentry/platform",
- "//pkg/sentry/safemem",
- "//pkg/sentry/socket/netlink/port",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/time",
- "//pkg/sentry/unimpl",
- "//pkg/sentry/unimpl:unimplemented_syscall_go_proto",
- "//pkg/sentry/uniqueid",
- "//pkg/sentry/usage",
- "//pkg/sentry/usermem",
- "//pkg/state",
- "//pkg/state/statefile",
- "//pkg/syserr",
- "//pkg/syserror",
- "//pkg/tcpip",
- "//pkg/tcpip/stack",
- "//pkg/waiter",
- "//third_party/gvsync",
- ],
-)
-
-go_test(
- name = "kernel_test",
- size = "small",
- srcs = [
- "fd_table_test.go",
- "table_test.go",
- "task_test.go",
- "timekeeper_test.go",
- ],
- embed = [":kernel"],
- deps = [
- "//pkg/abi",
- "//pkg/sentry/arch",
- "//pkg/sentry/context",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/filetest",
- "//pkg/sentry/kernel/sched",
- "//pkg/sentry/limits",
- "//pkg/sentry/pgalloc",
- "//pkg/sentry/time",
- "//pkg/sentry/usage",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- ],
-)
diff --git a/pkg/sentry/kernel/README.md b/pkg/sentry/kernel/README.md
deleted file mode 100644
index 427311be8..000000000
--- a/pkg/sentry/kernel/README.md
+++ /dev/null
@@ -1,108 +0,0 @@
-This package contains:
-
-- A (partial) emulation of the "core Linux kernel", which governs task
- execution and scheduling, system call dispatch, and signal handling. See
- below for details.
-
-- The top-level interface for the sentry's Linux kernel emulation in general,
- used by the `main` function of all versions of the sentry. This interface
- revolves around the `Env` type (defined in `kernel.go`).
-
-# Background
-
-In Linux, each schedulable context is referred to interchangeably as a "task" or
-"thread". Tasks can be divided into userspace and kernel tasks. In the sentry,
-scheduling is managed by the Go runtime, so each schedulable context is a
-goroutine; only "userspace" (application) contexts are referred to as tasks, and
-represented by Task objects. (From this point forward, "task" refers to the
-sentry's notion of a task unless otherwise specified.)
-
-At a high level, Linux application threads can be thought of as repeating a "run
-loop":
-
-- Some amount of application code is executed in userspace.
-
-- A trap (explicit syscall invocation, hardware interrupt or exception, etc.)
- causes control flow to switch to the kernel.
-
-- Some amount of kernel code is executed in kernelspace, e.g. to handle the
- cause of the trap.
-
-- The kernel "returns from the trap" into application code.
-
-Analogously, each task in the sentry is associated with a *task goroutine* that
-executes that task's run loop (`Task.run` in `task_run.go`). However, the
-sentry's task run loop differs in structure in order to support saving execution
-state to, and resuming execution from, checkpoints.
-
-While in kernelspace, a Linux thread can be descheduled (cease execution) in a
-variety of ways:
-
-- It can yield or be preempted, becoming temporarily descheduled but still
- runnable. At present, the sentry delegates scheduling of runnable threads to
- the Go runtime.
-
-- It can exit, becoming permanently descheduled. The sentry's equivalent is
- returning from `Task.run`, terminating the task goroutine.
-
-- It can enter interruptible sleep, a state in which it can be woken by a
- caller-defined wakeup or the receipt of a signal. In the sentry,
- interruptible sleep (which is ambiguously referred to as *blocking*) is
- implemented by making all events that can end blocking (including signal
- notifications) communicated via Go channels and using `select` to multiplex
- wakeup sources; see `task_block.go`.
-
-- It can enter uninterruptible sleep, a state in which it can only be woken by
- a caller-defined wakeup. Killable sleep is a closely related variant in
- which the task can also be woken by SIGKILL. (These definitions also include
- Linux's "group-stopped" (`TASK_STOPPED`) and "ptrace-stopped"
- (`TASK_TRACED`) states.)
-
-To maximize compatibility with Linux, sentry checkpointing appears as a spurious
-signal-delivery interrupt on all tasks; interrupted system calls return `EINTR`
-or are automatically restarted as usual. However, these semantics require that
-uninterruptible and killable sleeps do not appear to be interrupted. In other
-words, the state of the task, including its progress through the interrupted
-operation, must be preserved by checkpointing. For many such sleeps, the wakeup
-condition is application-controlled, making it infeasible to wait for the sleep
-to end before checkpointing. Instead, we must support checkpointing progress
-through sleeping operations.
-
-# Implementation
-
-We break the task's control flow graph into *states*, delimited by:
-
-1. Points where uninterruptible and killable sleeps may occur. For example,
- there exists a state boundary between signal dequeueing and signal delivery
- because there may be an intervening ptrace signal-delivery-stop.
-
-2. Points where sleep-induced branches may "rejoin" normal execution. For
- example, the syscall exit state exists because it can be reached immediately
- following a synchronous syscall, or after a task that is sleeping in
- `execve()` or `vfork()` resumes execution.
-
-3. Points containing large branches. This is strictly for organizational
- purposes. For example, the state that processes interrupt-signaled
- conditions is kept separate from the main "app" state to reduce the size of
- the latter.
-
-4. `SyscallReinvoke`, which does not correspond to anything in Linux, and
- exists solely to serve the autosave feature.
-
-![dot -Tpng -Goverlap=false -orun_states.png run_states.dot](g3doc/run_states.png "Task control flow graph")
-
-States before which a stop may occur are represented as implementations of the
-`taskRunState` interface named `run(state)`, allowing them to be saved and
-restored. States that cannot be immediately preceded by a stop are simply `Task`
-methods named `do(state)`.
-
-Conditions that can require task goroutines to cease execution for unknown
-lengths of time are called *stops*. Stops are divided into *internal stops*,
-which are stops whose start and end conditions are implemented within the
-sentry, and *external stops*, which are stops whose start and end conditions are
-not known to the sentry. Hence all uninterruptible and killable sleeps are
-internal stops, and the existence of a pending checkpoint operation is an
-external stop. Internal stops are reified into instances of the `TaskStop` type,
-while external stops are merely counted. The task run loop alternates between
-checking for stops and advancing the task's state. This allows checkpointing to
-hold tasks in a stopped state while waiting for all tasks in the system to stop.
diff --git a/pkg/sentry/kernel/auth/BUILD b/pkg/sentry/kernel/auth/BUILD
deleted file mode 100644
index 1d00a6310..000000000
--- a/pkg/sentry/kernel/auth/BUILD
+++ /dev/null
@@ -1,69 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_template_instance(
- name = "atomicptr_credentials",
- out = "atomicptr_credentials_unsafe.go",
- package = "auth",
- suffix = "Credentials",
- template = "//third_party/gvsync:generic_atomicptr",
- types = {
- "Value": "Credentials",
- },
-)
-
-go_template_instance(
- name = "id_map_range",
- out = "id_map_range.go",
- package = "auth",
- prefix = "idMap",
- template = "//pkg/segment:generic_range",
- types = {
- "T": "uint32",
- },
-)
-
-go_template_instance(
- name = "id_map_set",
- out = "id_map_set.go",
- consts = {
- "minDegree": "3",
- },
- package = "auth",
- prefix = "idMap",
- template = "//pkg/segment:generic_set",
- types = {
- "Key": "uint32",
- "Range": "idMapRange",
- "Value": "uint32",
- "Functions": "idMapFunctions",
- },
-)
-
-go_library(
- name = "auth",
- srcs = [
- "atomicptr_credentials_unsafe.go",
- "auth.go",
- "capability_set.go",
- "context.go",
- "credentials.go",
- "id.go",
- "id_map.go",
- "id_map_functions.go",
- "id_map_range.go",
- "id_map_set.go",
- "user_namespace.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/kernel/auth",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/bits",
- "//pkg/log",
- "//pkg/sentry/context",
- "//pkg/syserror",
- ],
-)
diff --git a/third_party/gvsync/atomicptr_unsafe.go b/pkg/sentry/kernel/auth/atomicptr_credentials_unsafe.go
index 525c4beed..4535c958f 100644..100755
--- a/third_party/gvsync/atomicptr_unsafe.go
+++ b/pkg/sentry/kernel/auth/atomicptr_credentials_unsafe.go
@@ -1,20 +1,10 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package template doesn't exist. This file must be instantiated using the
-// go_template_instance rule in tools/go_generics/defs.bzl.
-package template
+package auth
import (
"sync/atomic"
"unsafe"
)
-// Value is a required type parameter.
-type Value struct{}
-
// An AtomicPtr is a pointer to a value of type Value that can be atomically
// loaded and stored. The zero value of an AtomicPtr represents nil.
//
@@ -23,25 +13,25 @@ type Value struct{}
// this case, do `dst.Store(src.Load())` instead.
//
// +stateify savable
-type AtomicPtr struct {
- ptr unsafe.Pointer `state:".(*Value)"`
+type AtomicPtrCredentials struct {
+ ptr unsafe.Pointer `state:".(*Credentials)"`
}
-func (p *AtomicPtr) savePtr() *Value {
+func (p *AtomicPtrCredentials) savePtr() *Credentials {
return p.Load()
}
-func (p *AtomicPtr) loadPtr(v *Value) {
+func (p *AtomicPtrCredentials) loadPtr(v *Credentials) {
p.Store(v)
}
// Load returns the value set by the most recent Store. It returns nil if there
// has been no previous call to Store.
-func (p *AtomicPtr) Load() *Value {
- return (*Value)(atomic.LoadPointer(&p.ptr))
+func (p *AtomicPtrCredentials) Load() *Credentials {
+ return (*Credentials)(atomic.LoadPointer(&p.ptr))
}
// Store sets the value returned by Load to x.
-func (p *AtomicPtr) Store(x *Value) {
+func (p *AtomicPtrCredentials) Store(x *Credentials) {
atomic.StorePointer(&p.ptr, (unsafe.Pointer)(x))
}
diff --git a/pkg/sentry/kernel/auth/auth_state_autogen.go b/pkg/sentry/kernel/auth/auth_state_autogen.go
new file mode 100755
index 000000000..ef5f8d957
--- /dev/null
+++ b/pkg/sentry/kernel/auth/auth_state_autogen.go
@@ -0,0 +1,164 @@
+// automatically generated by stateify.
+
+package auth
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *AtomicPtrCredentials) beforeSave() {}
+func (x *AtomicPtrCredentials) save(m state.Map) {
+ x.beforeSave()
+ var ptr *Credentials = x.savePtr()
+ m.SaveValue("ptr", ptr)
+}
+
+func (x *AtomicPtrCredentials) afterLoad() {}
+func (x *AtomicPtrCredentials) load(m state.Map) {
+ m.LoadValue("ptr", new(*Credentials), func(y interface{}) { x.loadPtr(y.(*Credentials)) })
+}
+
+func (x *Credentials) beforeSave() {}
+func (x *Credentials) save(m state.Map) {
+ x.beforeSave()
+ m.Save("RealKUID", &x.RealKUID)
+ m.Save("EffectiveKUID", &x.EffectiveKUID)
+ m.Save("SavedKUID", &x.SavedKUID)
+ m.Save("RealKGID", &x.RealKGID)
+ m.Save("EffectiveKGID", &x.EffectiveKGID)
+ m.Save("SavedKGID", &x.SavedKGID)
+ m.Save("ExtraKGIDs", &x.ExtraKGIDs)
+ m.Save("PermittedCaps", &x.PermittedCaps)
+ m.Save("InheritableCaps", &x.InheritableCaps)
+ m.Save("EffectiveCaps", &x.EffectiveCaps)
+ m.Save("BoundingCaps", &x.BoundingCaps)
+ m.Save("KeepCaps", &x.KeepCaps)
+ m.Save("UserNamespace", &x.UserNamespace)
+}
+
+func (x *Credentials) afterLoad() {}
+func (x *Credentials) load(m state.Map) {
+ m.Load("RealKUID", &x.RealKUID)
+ m.Load("EffectiveKUID", &x.EffectiveKUID)
+ m.Load("SavedKUID", &x.SavedKUID)
+ m.Load("RealKGID", &x.RealKGID)
+ m.Load("EffectiveKGID", &x.EffectiveKGID)
+ m.Load("SavedKGID", &x.SavedKGID)
+ m.Load("ExtraKGIDs", &x.ExtraKGIDs)
+ m.Load("PermittedCaps", &x.PermittedCaps)
+ m.Load("InheritableCaps", &x.InheritableCaps)
+ m.Load("EffectiveCaps", &x.EffectiveCaps)
+ m.Load("BoundingCaps", &x.BoundingCaps)
+ m.Load("KeepCaps", &x.KeepCaps)
+ m.Load("UserNamespace", &x.UserNamespace)
+}
+
+func (x *IDMapEntry) beforeSave() {}
+func (x *IDMapEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("FirstID", &x.FirstID)
+ m.Save("FirstParentID", &x.FirstParentID)
+ m.Save("Length", &x.Length)
+}
+
+func (x *IDMapEntry) afterLoad() {}
+func (x *IDMapEntry) load(m state.Map) {
+ m.Load("FirstID", &x.FirstID)
+ m.Load("FirstParentID", &x.FirstParentID)
+ m.Load("Length", &x.Length)
+}
+
+func (x *idMapRange) beforeSave() {}
+func (x *idMapRange) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Start", &x.Start)
+ m.Save("End", &x.End)
+}
+
+func (x *idMapRange) afterLoad() {}
+func (x *idMapRange) load(m state.Map) {
+ m.Load("Start", &x.Start)
+ m.Load("End", &x.End)
+}
+
+func (x *idMapSet) beforeSave() {}
+func (x *idMapSet) save(m state.Map) {
+ x.beforeSave()
+ var root *idMapSegmentDataSlices = x.saveRoot()
+ m.SaveValue("root", root)
+}
+
+func (x *idMapSet) afterLoad() {}
+func (x *idMapSet) load(m state.Map) {
+ m.LoadValue("root", new(*idMapSegmentDataSlices), func(y interface{}) { x.loadRoot(y.(*idMapSegmentDataSlices)) })
+}
+
+func (x *idMapnode) beforeSave() {}
+func (x *idMapnode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("nrSegments", &x.nrSegments)
+ m.Save("parent", &x.parent)
+ m.Save("parentIndex", &x.parentIndex)
+ m.Save("hasChildren", &x.hasChildren)
+ m.Save("keys", &x.keys)
+ m.Save("values", &x.values)
+ m.Save("children", &x.children)
+}
+
+func (x *idMapnode) afterLoad() {}
+func (x *idMapnode) load(m state.Map) {
+ m.Load("nrSegments", &x.nrSegments)
+ m.Load("parent", &x.parent)
+ m.Load("parentIndex", &x.parentIndex)
+ m.Load("hasChildren", &x.hasChildren)
+ m.Load("keys", &x.keys)
+ m.Load("values", &x.values)
+ m.Load("children", &x.children)
+}
+
+func (x *idMapSegmentDataSlices) beforeSave() {}
+func (x *idMapSegmentDataSlices) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Start", &x.Start)
+ m.Save("End", &x.End)
+ m.Save("Values", &x.Values)
+}
+
+func (x *idMapSegmentDataSlices) afterLoad() {}
+func (x *idMapSegmentDataSlices) load(m state.Map) {
+ m.Load("Start", &x.Start)
+ m.Load("End", &x.End)
+ m.Load("Values", &x.Values)
+}
+
+func (x *UserNamespace) beforeSave() {}
+func (x *UserNamespace) save(m state.Map) {
+ x.beforeSave()
+ m.Save("parent", &x.parent)
+ m.Save("owner", &x.owner)
+ m.Save("uidMapFromParent", &x.uidMapFromParent)
+ m.Save("uidMapToParent", &x.uidMapToParent)
+ m.Save("gidMapFromParent", &x.gidMapFromParent)
+ m.Save("gidMapToParent", &x.gidMapToParent)
+}
+
+func (x *UserNamespace) afterLoad() {}
+func (x *UserNamespace) load(m state.Map) {
+ m.Load("parent", &x.parent)
+ m.Load("owner", &x.owner)
+ m.Load("uidMapFromParent", &x.uidMapFromParent)
+ m.Load("uidMapToParent", &x.uidMapToParent)
+ m.Load("gidMapFromParent", &x.gidMapFromParent)
+ m.Load("gidMapToParent", &x.gidMapToParent)
+}
+
+func init() {
+ state.Register("auth.AtomicPtrCredentials", (*AtomicPtrCredentials)(nil), state.Fns{Save: (*AtomicPtrCredentials).save, Load: (*AtomicPtrCredentials).load})
+ state.Register("auth.Credentials", (*Credentials)(nil), state.Fns{Save: (*Credentials).save, Load: (*Credentials).load})
+ state.Register("auth.IDMapEntry", (*IDMapEntry)(nil), state.Fns{Save: (*IDMapEntry).save, Load: (*IDMapEntry).load})
+ state.Register("auth.idMapRange", (*idMapRange)(nil), state.Fns{Save: (*idMapRange).save, Load: (*idMapRange).load})
+ state.Register("auth.idMapSet", (*idMapSet)(nil), state.Fns{Save: (*idMapSet).save, Load: (*idMapSet).load})
+ state.Register("auth.idMapnode", (*idMapnode)(nil), state.Fns{Save: (*idMapnode).save, Load: (*idMapnode).load})
+ state.Register("auth.idMapSegmentDataSlices", (*idMapSegmentDataSlices)(nil), state.Fns{Save: (*idMapSegmentDataSlices).save, Load: (*idMapSegmentDataSlices).load})
+ state.Register("auth.UserNamespace", (*UserNamespace)(nil), state.Fns{Save: (*UserNamespace).save, Load: (*UserNamespace).load})
+}
diff --git a/pkg/sentry/kernel/auth/id_map_range.go b/pkg/sentry/kernel/auth/id_map_range.go
new file mode 100755
index 000000000..833fa3518
--- /dev/null
+++ b/pkg/sentry/kernel/auth/id_map_range.go
@@ -0,0 +1,62 @@
+package auth
+
+// A Range represents a contiguous range of T.
+//
+// +stateify savable
+type idMapRange struct {
+ // Start is the inclusive start of the range.
+ Start uint32
+
+ // End is the exclusive end of the range.
+ End uint32
+}
+
+// WellFormed returns true if r.Start <= r.End. All other methods on a Range
+// require that the Range is well-formed.
+func (r idMapRange) WellFormed() bool {
+ return r.Start <= r.End
+}
+
+// Length returns the length of the range.
+func (r idMapRange) Length() uint32 {
+ return r.End - r.Start
+}
+
+// Contains returns true if r contains x.
+func (r idMapRange) Contains(x uint32) bool {
+ return r.Start <= x && x < r.End
+}
+
+// Overlaps returns true if r and r2 overlap.
+func (r idMapRange) Overlaps(r2 idMapRange) bool {
+ return r.Start < r2.End && r2.Start < r.End
+}
+
+// IsSupersetOf returns true if r is a superset of r2; that is, the range r2 is
+// contained within r.
+func (r idMapRange) IsSupersetOf(r2 idMapRange) bool {
+ return r.Start <= r2.Start && r.End >= r2.End
+}
+
+// Intersect returns a range consisting of the intersection between r and r2.
+// If r and r2 do not overlap, Intersect returns a range with unspecified
+// bounds, but for which Length() == 0.
+func (r idMapRange) Intersect(r2 idMapRange) idMapRange {
+ if r.Start < r2.Start {
+ r.Start = r2.Start
+ }
+ if r.End > r2.End {
+ r.End = r2.End
+ }
+ if r.End < r.Start {
+ r.End = r.Start
+ }
+ return r
+}
+
+// CanSplitAt returns true if it is legal to split a segment spanning the range
+// r at x; that is, splitting at x would produce two ranges, both of which have
+// non-zero length.
+func (r idMapRange) CanSplitAt(x uint32) bool {
+ return r.Contains(x) && r.Start < x
+}
diff --git a/pkg/sentry/kernel/auth/id_map_set.go b/pkg/sentry/kernel/auth/id_map_set.go
new file mode 100755
index 000000000..73a17f281
--- /dev/null
+++ b/pkg/sentry/kernel/auth/id_map_set.go
@@ -0,0 +1,1270 @@
+package auth
+
+import (
+ "bytes"
+ "fmt"
+)
+
+const (
+ // minDegree is the minimum degree of an internal node in a Set B-tree.
+ //
+ // - Any non-root node has at least minDegree-1 segments.
+ //
+ // - Any non-root internal (non-leaf) node has at least minDegree children.
+ //
+ // - The root node may have fewer than minDegree-1 segments, but it may
+ // only have 0 segments if the tree is empty.
+ //
+ // Our implementation requires minDegree >= 3. Higher values of minDegree
+ // usually improve performance, but increase memory usage for small sets.
+ idMapminDegree = 3
+
+ idMapmaxDegree = 2 * idMapminDegree
+)
+
+// A Set is a mapping of segments with non-overlapping Range keys. The zero
+// value for a Set is an empty set. Set values are not safely movable nor
+// copyable. Set is thread-compatible.
+//
+// +stateify savable
+type idMapSet struct {
+ root idMapnode `state:".(*idMapSegmentDataSlices)"`
+}
+
+// IsEmpty returns true if the set contains no segments.
+func (s *idMapSet) IsEmpty() bool {
+ return s.root.nrSegments == 0
+}
+
+// IsEmptyRange returns true iff no segments in the set overlap the given
+// range. This is semantically equivalent to s.SpanRange(r) == 0, but may be
+// more efficient.
+func (s *idMapSet) IsEmptyRange(r idMapRange) bool {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return true
+ }
+ _, gap := s.Find(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ return r.End <= gap.End()
+}
+
+// Span returns the total size of all segments in the set.
+func (s *idMapSet) Span() uint32 {
+ var sz uint32
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sz += seg.Range().Length()
+ }
+ return sz
+}
+
+// SpanRange returns the total size of the intersection of segments in the set
+// with the given range.
+func (s *idMapSet) SpanRange(r idMapRange) uint32 {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return 0
+ }
+ var sz uint32
+ for seg := s.LowerBoundSegment(r.Start); seg.Ok() && seg.Start() < r.End; seg = seg.NextSegment() {
+ sz += seg.Range().Intersect(r).Length()
+ }
+ return sz
+}
+
+// FirstSegment returns the first segment in the set. If the set is empty,
+// FirstSegment returns a terminal iterator.
+func (s *idMapSet) FirstSegment() idMapIterator {
+ if s.root.nrSegments == 0 {
+ return idMapIterator{}
+ }
+ return s.root.firstSegment()
+}
+
+// LastSegment returns the last segment in the set. If the set is empty,
+// LastSegment returns a terminal iterator.
+func (s *idMapSet) LastSegment() idMapIterator {
+ if s.root.nrSegments == 0 {
+ return idMapIterator{}
+ }
+ return s.root.lastSegment()
+}
+
+// FirstGap returns the first gap in the set.
+func (s *idMapSet) FirstGap() idMapGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return idMapGapIterator{n, 0}
+}
+
+// LastGap returns the last gap in the set.
+func (s *idMapSet) LastGap() idMapGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return idMapGapIterator{n, n.nrSegments}
+}
+
+// Find returns the segment or gap whose range contains the given key. If a
+// segment is found, the returned Iterator is non-terminal and the
+// returned GapIterator is terminal. Otherwise, the returned Iterator is
+// terminal and the returned GapIterator is non-terminal.
+func (s *idMapSet) Find(key uint32) (idMapIterator, idMapGapIterator) {
+ n := &s.root
+ for {
+
+ lower := 0
+ upper := n.nrSegments
+ for lower < upper {
+ i := lower + (upper-lower)/2
+ if r := n.keys[i]; key < r.End {
+ if key >= r.Start {
+ return idMapIterator{n, i}, idMapGapIterator{}
+ }
+ upper = i
+ } else {
+ lower = i + 1
+ }
+ }
+ i := lower
+ if !n.hasChildren {
+ return idMapIterator{}, idMapGapIterator{n, i}
+ }
+ n = n.children[i]
+ }
+}
+
+// FindSegment returns the segment whose range contains the given key. If no
+// such segment exists, FindSegment returns a terminal iterator.
+func (s *idMapSet) FindSegment(key uint32) idMapIterator {
+ seg, _ := s.Find(key)
+ return seg
+}
+
+// LowerBoundSegment returns the segment with the lowest range that contains a
+// key greater than or equal to min. If no such segment exists,
+// LowerBoundSegment returns a terminal iterator.
+func (s *idMapSet) LowerBoundSegment(min uint32) idMapIterator {
+ seg, gap := s.Find(min)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.NextSegment()
+}
+
+// UpperBoundSegment returns the segment with the highest range that contains a
+// key less than or equal to max. If no such segment exists, UpperBoundSegment
+// returns a terminal iterator.
+func (s *idMapSet) UpperBoundSegment(max uint32) idMapIterator {
+ seg, gap := s.Find(max)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.PrevSegment()
+}
+
+// FindGap returns the gap containing the given key. If no such gap exists
+// (i.e. the set contains a segment containing that key), FindGap returns a
+// terminal iterator.
+func (s *idMapSet) FindGap(key uint32) idMapGapIterator {
+ _, gap := s.Find(key)
+ return gap
+}
+
+// LowerBoundGap returns the gap with the lowest range that is greater than or
+// equal to min.
+func (s *idMapSet) LowerBoundGap(min uint32) idMapGapIterator {
+ seg, gap := s.Find(min)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.NextGap()
+}
+
+// UpperBoundGap returns the gap with the highest range that is less than or
+// equal to max.
+func (s *idMapSet) UpperBoundGap(max uint32) idMapGapIterator {
+ seg, gap := s.Find(max)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.PrevGap()
+}
+
+// Add inserts the given segment into the set and returns true. If the new
+// segment can be merged with adjacent segments, Add will do so. If the new
+// segment would overlap an existing segment, Add returns false. If Add
+// succeeds, all existing iterators are invalidated.
+func (s *idMapSet) Add(r idMapRange, val uint32) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.Insert(gap, r, val)
+ return true
+}
+
+// AddWithoutMerging inserts the given segment into the set and returns true.
+// If it would overlap an existing segment, AddWithoutMerging does nothing and
+// returns false. If AddWithoutMerging succeeds, all existing iterators are
+// invalidated.
+func (s *idMapSet) AddWithoutMerging(r idMapRange, val uint32) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.InsertWithoutMergingUnchecked(gap, r, val)
+ return true
+}
+
+// Insert inserts the given segment into the given gap. If the new segment can
+// be merged with adjacent segments, Insert will do so. Insert returns an
+// iterator to the segment containing the inserted value (which may have been
+// merged with other values). All existing iterators (including gap, but not
+// including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid, Insert panics.
+//
+// Insert is semantically equivalent to a InsertWithoutMerging followed by a
+// Merge, but may be more efficient. Note that there is no unchecked variant of
+// Insert since Insert must retrieve and inspect gap's predecessor and
+// successor segments regardless.
+func (s *idMapSet) Insert(gap idMapGapIterator, r idMapRange, val uint32) idMapIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ prev, next := gap.PrevSegment(), gap.NextSegment()
+ if prev.Ok() && prev.End() > r.Start {
+ panic(fmt.Sprintf("new segment %v overlaps predecessor %v", r, prev.Range()))
+ }
+ if next.Ok() && next.Start() < r.End {
+ panic(fmt.Sprintf("new segment %v overlaps successor %v", r, next.Range()))
+ }
+ if prev.Ok() && prev.End() == r.Start {
+ if mval, ok := (idMapFunctions{}).Merge(prev.Range(), prev.Value(), r, val); ok {
+ prev.SetEndUnchecked(r.End)
+ prev.SetValue(mval)
+ if next.Ok() && next.Start() == r.End {
+ val = mval
+ if mval, ok := (idMapFunctions{}).Merge(prev.Range(), val, next.Range(), next.Value()); ok {
+ prev.SetEndUnchecked(next.End())
+ prev.SetValue(mval)
+ return s.Remove(next).PrevSegment()
+ }
+ }
+ return prev
+ }
+ }
+ if next.Ok() && next.Start() == r.End {
+ if mval, ok := (idMapFunctions{}).Merge(r, val, next.Range(), next.Value()); ok {
+ next.SetStartUnchecked(r.Start)
+ next.SetValue(mval)
+ return next
+ }
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMerging inserts the given segment into the given gap and
+// returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid,
+// InsertWithoutMerging panics.
+func (s *idMapSet) InsertWithoutMerging(gap idMapGapIterator, r idMapRange, val uint32) idMapIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if gr := gap.Range(); !gr.IsSupersetOf(r) {
+ panic(fmt.Sprintf("cannot insert segment range %v into gap range %v", r, gr))
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMergingUnchecked inserts the given segment into the given gap
+// and returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// Preconditions: r.Start >= gap.Start(); r.End <= gap.End().
+func (s *idMapSet) InsertWithoutMergingUnchecked(gap idMapGapIterator, r idMapRange, val uint32) idMapIterator {
+ gap = gap.node.rebalanceBeforeInsert(gap)
+ copy(gap.node.keys[gap.index+1:], gap.node.keys[gap.index:gap.node.nrSegments])
+ copy(gap.node.values[gap.index+1:], gap.node.values[gap.index:gap.node.nrSegments])
+ gap.node.keys[gap.index] = r
+ gap.node.values[gap.index] = val
+ gap.node.nrSegments++
+ return idMapIterator{gap.node, gap.index}
+}
+
+// Remove removes the given segment and returns an iterator to the vacated gap.
+// All existing iterators (including seg, but not including the returned
+// iterator) are invalidated.
+func (s *idMapSet) Remove(seg idMapIterator) idMapGapIterator {
+
+ if seg.node.hasChildren {
+
+ victim := seg.PrevSegment()
+
+ seg.SetRangeUnchecked(victim.Range())
+ seg.SetValue(victim.Value())
+ return s.Remove(victim).NextGap()
+ }
+ copy(seg.node.keys[seg.index:], seg.node.keys[seg.index+1:seg.node.nrSegments])
+ copy(seg.node.values[seg.index:], seg.node.values[seg.index+1:seg.node.nrSegments])
+ idMapFunctions{}.ClearValue(&seg.node.values[seg.node.nrSegments-1])
+ seg.node.nrSegments--
+ return seg.node.rebalanceAfterRemove(idMapGapIterator{seg.node, seg.index})
+}
+
+// RemoveAll removes all segments from the set. All existing iterators are
+// invalidated.
+func (s *idMapSet) RemoveAll() {
+ s.root = idMapnode{}
+}
+
+// RemoveRange removes all segments in the given range. An iterator to the
+// newly formed gap is returned, and all existing iterators are invalidated.
+func (s *idMapSet) RemoveRange(r idMapRange) idMapGapIterator {
+ seg, gap := s.Find(r.Start)
+ if seg.Ok() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ for seg = gap.NextSegment(); seg.Ok() && seg.Start() < r.End; seg = gap.NextSegment() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ return gap
+}
+
+// Merge attempts to merge two neighboring segments. If successful, Merge
+// returns an iterator to the merged segment, and all existing iterators are
+// invalidated. Otherwise, Merge returns a terminal iterator.
+//
+// If first is not the predecessor of second, Merge panics.
+func (s *idMapSet) Merge(first, second idMapIterator) idMapIterator {
+ if first.NextSegment() != second {
+ panic(fmt.Sprintf("attempt to merge non-neighboring segments %v, %v", first.Range(), second.Range()))
+ }
+ return s.MergeUnchecked(first, second)
+}
+
+// MergeUnchecked attempts to merge two neighboring segments. If successful,
+// MergeUnchecked returns an iterator to the merged segment, and all existing
+// iterators are invalidated. Otherwise, MergeUnchecked returns a terminal
+// iterator.
+//
+// Precondition: first is the predecessor of second: first.NextSegment() ==
+// second, first == second.PrevSegment().
+func (s *idMapSet) MergeUnchecked(first, second idMapIterator) idMapIterator {
+ if first.End() == second.Start() {
+ if mval, ok := (idMapFunctions{}).Merge(first.Range(), first.Value(), second.Range(), second.Value()); ok {
+
+ first.SetEndUnchecked(second.End())
+ first.SetValue(mval)
+ return s.Remove(second).PrevSegment()
+ }
+ }
+ return idMapIterator{}
+}
+
+// MergeAll attempts to merge all adjacent segments in the set. All existing
+// iterators are invalidated.
+func (s *idMapSet) MergeAll() {
+ seg := s.FirstSegment()
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeRange attempts to merge all adjacent segments that contain a key in the
+// specific range. All existing iterators are invalidated.
+func (s *idMapSet) MergeRange(r idMapRange) {
+ seg := s.LowerBoundSegment(r.Start)
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() && next.Range().Start < r.End {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeAdjacent attempts to merge the segment containing r.Start with its
+// predecessor, and the segment containing r.End-1 with its successor.
+func (s *idMapSet) MergeAdjacent(r idMapRange) {
+ first := s.FindSegment(r.Start)
+ if first.Ok() {
+ if prev := first.PrevSegment(); prev.Ok() {
+ s.Merge(prev, first)
+ }
+ }
+ last := s.FindSegment(r.End - 1)
+ if last.Ok() {
+ if next := last.NextSegment(); next.Ok() {
+ s.Merge(last, next)
+ }
+ }
+}
+
+// Split splits the given segment at the given key and returns iterators to the
+// two resulting segments. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+//
+// If the segment cannot be split at split (because split is at the start or
+// end of the segment's range, so splitting would produce a segment with zero
+// length, or because split falls outside the segment's range altogether),
+// Split panics.
+func (s *idMapSet) Split(seg idMapIterator, split uint32) (idMapIterator, idMapIterator) {
+ if !seg.Range().CanSplitAt(split) {
+ panic(fmt.Sprintf("can't split %v at %v", seg.Range(), split))
+ }
+ return s.SplitUnchecked(seg, split)
+}
+
+// SplitUnchecked splits the given segment at the given key and returns
+// iterators to the two resulting segments. All existing iterators (including
+// seg, but not including the returned iterators) are invalidated.
+//
+// Preconditions: seg.Start() < key < seg.End().
+func (s *idMapSet) SplitUnchecked(seg idMapIterator, split uint32) (idMapIterator, idMapIterator) {
+ val1, val2 := (idMapFunctions{}).Split(seg.Range(), seg.Value(), split)
+ end2 := seg.End()
+ seg.SetEndUnchecked(split)
+ seg.SetValue(val1)
+ seg2 := s.InsertWithoutMergingUnchecked(seg.NextGap(), idMapRange{split, end2}, val2)
+
+ return seg2.PrevSegment(), seg2
+}
+
+// SplitAt splits the segment straddling split, if one exists. SplitAt returns
+// true if a segment was split and false otherwise. If SplitAt splits a
+// segment, all existing iterators are invalidated.
+func (s *idMapSet) SplitAt(split uint32) bool {
+ if seg := s.FindSegment(split); seg.Ok() && seg.Range().CanSplitAt(split) {
+ s.SplitUnchecked(seg, split)
+ return true
+ }
+ return false
+}
+
+// Isolate ensures that the given segment's range does not escape r by
+// splitting at r.Start and r.End if necessary, and returns an updated iterator
+// to the bounded segment. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+func (s *idMapSet) Isolate(seg idMapIterator, r idMapRange) idMapIterator {
+ if seg.Range().CanSplitAt(r.Start) {
+ _, seg = s.SplitUnchecked(seg, r.Start)
+ }
+ if seg.Range().CanSplitAt(r.End) {
+ seg, _ = s.SplitUnchecked(seg, r.End)
+ }
+ return seg
+}
+
+// ApplyContiguous applies a function to a contiguous range of segments,
+// splitting if necessary. The function is applied until the first gap is
+// encountered, at which point the gap is returned. If the function is applied
+// across the entire range, a terminal gap is returned. All existing iterators
+// are invalidated.
+//
+// N.B. The Iterator must not be invalidated by the function.
+func (s *idMapSet) ApplyContiguous(r idMapRange, fn func(seg idMapIterator)) idMapGapIterator {
+ seg, gap := s.Find(r.Start)
+ if !seg.Ok() {
+ return gap
+ }
+ for {
+ seg = s.Isolate(seg, r)
+ fn(seg)
+ if seg.End() >= r.End {
+ return idMapGapIterator{}
+ }
+ gap = seg.NextGap()
+ if !gap.IsEmpty() {
+ return gap
+ }
+ seg = gap.NextSegment()
+ if !seg.Ok() {
+
+ return idMapGapIterator{}
+ }
+ }
+}
+
+// +stateify savable
+type idMapnode struct {
+ // An internal binary tree node looks like:
+ //
+ // K
+ // / \
+ // Cl Cr
+ //
+ // where all keys in the subtree rooted by Cl (the left subtree) are less
+ // than K (the key of the parent node), and all keys in the subtree rooted
+ // by Cr (the right subtree) are greater than K.
+ //
+ // An internal B-tree node's indexes work out to look like:
+ //
+ // K0 K1 K2 ... Kn-1
+ // / \/ \/ \ ... / \
+ // C0 C1 C2 C3 ... Cn-1 Cn
+ //
+ // where n is nrSegments.
+ nrSegments int
+
+ // parent is a pointer to this node's parent. If this node is root, parent
+ // is nil.
+ parent *idMapnode
+
+ // parentIndex is the index of this node in parent.children.
+ parentIndex int
+
+ // Flag for internal nodes that is technically redundant with "children[0]
+ // != nil", but is stored in the first cache line. "hasChildren" rather
+ // than "isLeaf" because false must be the correct value for an empty root.
+ hasChildren bool
+
+ // Nodes store keys and values in separate arrays to maximize locality in
+ // the common case (scanning keys for lookup).
+ keys [idMapmaxDegree - 1]idMapRange
+ values [idMapmaxDegree - 1]uint32
+ children [idMapmaxDegree]*idMapnode
+}
+
+// firstSegment returns the first segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *idMapnode) firstSegment() idMapIterator {
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return idMapIterator{n, 0}
+}
+
+// lastSegment returns the last segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *idMapnode) lastSegment() idMapIterator {
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return idMapIterator{n, n.nrSegments - 1}
+}
+
+func (n *idMapnode) prevSibling() *idMapnode {
+ if n.parent == nil || n.parentIndex == 0 {
+ return nil
+ }
+ return n.parent.children[n.parentIndex-1]
+}
+
+func (n *idMapnode) nextSibling() *idMapnode {
+ if n.parent == nil || n.parentIndex == n.parent.nrSegments {
+ return nil
+ }
+ return n.parent.children[n.parentIndex+1]
+}
+
+// rebalanceBeforeInsert splits n and its ancestors if they are full, as
+// required for insertion, and returns an updated iterator to the position
+// represented by gap.
+func (n *idMapnode) rebalanceBeforeInsert(gap idMapGapIterator) idMapGapIterator {
+ if n.parent != nil {
+ gap = n.parent.rebalanceBeforeInsert(gap)
+ }
+ if n.nrSegments < idMapmaxDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ left := &idMapnode{
+ nrSegments: idMapminDegree - 1,
+ parent: n,
+ parentIndex: 0,
+ hasChildren: n.hasChildren,
+ }
+ right := &idMapnode{
+ nrSegments: idMapminDegree - 1,
+ parent: n,
+ parentIndex: 1,
+ hasChildren: n.hasChildren,
+ }
+ copy(left.keys[:idMapminDegree-1], n.keys[:idMapminDegree-1])
+ copy(left.values[:idMapminDegree-1], n.values[:idMapminDegree-1])
+ copy(right.keys[:idMapminDegree-1], n.keys[idMapminDegree:])
+ copy(right.values[:idMapminDegree-1], n.values[idMapminDegree:])
+ n.keys[0], n.values[0] = n.keys[idMapminDegree-1], n.values[idMapminDegree-1]
+ idMapzeroValueSlice(n.values[1:])
+ if n.hasChildren {
+ copy(left.children[:idMapminDegree], n.children[:idMapminDegree])
+ copy(right.children[:idMapminDegree], n.children[idMapminDegree:])
+ idMapzeroNodeSlice(n.children[2:])
+ for i := 0; i < idMapminDegree; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ right.children[i].parent = right
+ right.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = 1
+ n.hasChildren = true
+ n.children[0] = left
+ n.children[1] = right
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < idMapminDegree {
+ return idMapGapIterator{left, gap.index}
+ }
+ return idMapGapIterator{right, gap.index - idMapminDegree}
+ }
+
+ copy(n.parent.keys[n.parentIndex+1:], n.parent.keys[n.parentIndex:n.parent.nrSegments])
+ copy(n.parent.values[n.parentIndex+1:], n.parent.values[n.parentIndex:n.parent.nrSegments])
+ n.parent.keys[n.parentIndex], n.parent.values[n.parentIndex] = n.keys[idMapminDegree-1], n.values[idMapminDegree-1]
+ copy(n.parent.children[n.parentIndex+2:], n.parent.children[n.parentIndex+1:n.parent.nrSegments+1])
+ for i := n.parentIndex + 2; i < n.parent.nrSegments+2; i++ {
+ n.parent.children[i].parentIndex = i
+ }
+ sibling := &idMapnode{
+ nrSegments: idMapminDegree - 1,
+ parent: n.parent,
+ parentIndex: n.parentIndex + 1,
+ hasChildren: n.hasChildren,
+ }
+ n.parent.children[n.parentIndex+1] = sibling
+ n.parent.nrSegments++
+ copy(sibling.keys[:idMapminDegree-1], n.keys[idMapminDegree:])
+ copy(sibling.values[:idMapminDegree-1], n.values[idMapminDegree:])
+ idMapzeroValueSlice(n.values[idMapminDegree-1:])
+ if n.hasChildren {
+ copy(sibling.children[:idMapminDegree], n.children[idMapminDegree:])
+ idMapzeroNodeSlice(n.children[idMapminDegree:])
+ for i := 0; i < idMapminDegree; i++ {
+ sibling.children[i].parent = sibling
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = idMapminDegree - 1
+
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < idMapminDegree {
+ return gap
+ }
+ return idMapGapIterator{sibling, gap.index - idMapminDegree}
+}
+
+// rebalanceAfterRemove "unsplits" n and its ancestors if they are deficient
+// (contain fewer segments than required by B-tree invariants), as required for
+// removal, and returns an updated iterator to the position represented by gap.
+//
+// Precondition: n is the only node in the tree that may currently violate a
+// B-tree invariant.
+func (n *idMapnode) rebalanceAfterRemove(gap idMapGapIterator) idMapGapIterator {
+ for {
+ if n.nrSegments >= idMapminDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ return gap
+ }
+
+ if sibling := n.prevSibling(); sibling != nil && sibling.nrSegments >= idMapminDegree {
+ copy(n.keys[1:], n.keys[:n.nrSegments])
+ copy(n.values[1:], n.values[:n.nrSegments])
+ n.keys[0] = n.parent.keys[n.parentIndex-1]
+ n.values[0] = n.parent.values[n.parentIndex-1]
+ n.parent.keys[n.parentIndex-1] = sibling.keys[sibling.nrSegments-1]
+ n.parent.values[n.parentIndex-1] = sibling.values[sibling.nrSegments-1]
+ idMapFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ copy(n.children[1:], n.children[:n.nrSegments+1])
+ n.children[0] = sibling.children[sibling.nrSegments]
+ sibling.children[sibling.nrSegments] = nil
+ n.children[0].parent = n
+ n.children[0].parentIndex = 0
+ for i := 1; i < n.nrSegments+2; i++ {
+ n.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling && gap.index == sibling.nrSegments {
+ return idMapGapIterator{n, 0}
+ }
+ if gap.node == n {
+ return idMapGapIterator{n, gap.index + 1}
+ }
+ return gap
+ }
+ if sibling := n.nextSibling(); sibling != nil && sibling.nrSegments >= idMapminDegree {
+ n.keys[n.nrSegments] = n.parent.keys[n.parentIndex]
+ n.values[n.nrSegments] = n.parent.values[n.parentIndex]
+ n.parent.keys[n.parentIndex] = sibling.keys[0]
+ n.parent.values[n.parentIndex] = sibling.values[0]
+ copy(sibling.keys[:sibling.nrSegments-1], sibling.keys[1:])
+ copy(sibling.values[:sibling.nrSegments-1], sibling.values[1:])
+ idMapFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ n.children[n.nrSegments+1] = sibling.children[0]
+ copy(sibling.children[:sibling.nrSegments], sibling.children[1:])
+ sibling.children[sibling.nrSegments] = nil
+ n.children[n.nrSegments+1].parent = n
+ n.children[n.nrSegments+1].parentIndex = n.nrSegments + 1
+ for i := 0; i < sibling.nrSegments; i++ {
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling {
+ if gap.index == 0 {
+ return idMapGapIterator{n, n.nrSegments}
+ }
+ return idMapGapIterator{sibling, gap.index - 1}
+ }
+ return gap
+ }
+
+ p := n.parent
+ if p.nrSegments == 1 {
+
+ left, right := p.children[0], p.children[1]
+ p.nrSegments = left.nrSegments + right.nrSegments + 1
+ p.hasChildren = left.hasChildren
+ p.keys[left.nrSegments] = p.keys[0]
+ p.values[left.nrSegments] = p.values[0]
+ copy(p.keys[:left.nrSegments], left.keys[:left.nrSegments])
+ copy(p.values[:left.nrSegments], left.values[:left.nrSegments])
+ copy(p.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(p.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(p.children[:left.nrSegments+1], left.children[:left.nrSegments+1])
+ copy(p.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := 0; i < p.nrSegments+1; i++ {
+ p.children[i].parent = p
+ p.children[i].parentIndex = i
+ }
+ } else {
+ p.children[0] = nil
+ p.children[1] = nil
+ }
+ if gap.node == left {
+ return idMapGapIterator{p, gap.index}
+ }
+ if gap.node == right {
+ return idMapGapIterator{p, gap.index + left.nrSegments + 1}
+ }
+ return gap
+ }
+ // Merge n and either sibling, along with the segment separating the
+ // two, into whichever of the two nodes comes first. This is the
+ // reverse of the non-root splitting case in
+ // node.rebalanceBeforeInsert.
+ var left, right *idMapnode
+ if n.parentIndex > 0 {
+ left = n.prevSibling()
+ right = n
+ } else {
+ left = n
+ right = n.nextSibling()
+ }
+
+ if gap.node == right {
+ gap = idMapGapIterator{left, gap.index + left.nrSegments + 1}
+ }
+ left.keys[left.nrSegments] = p.keys[left.parentIndex]
+ left.values[left.nrSegments] = p.values[left.parentIndex]
+ copy(left.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(left.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(left.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := left.nrSegments + 1; i < left.nrSegments+right.nrSegments+2; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ }
+ }
+ left.nrSegments += right.nrSegments + 1
+ copy(p.keys[left.parentIndex:], p.keys[left.parentIndex+1:p.nrSegments])
+ copy(p.values[left.parentIndex:], p.values[left.parentIndex+1:p.nrSegments])
+ idMapFunctions{}.ClearValue(&p.values[p.nrSegments-1])
+ copy(p.children[left.parentIndex+1:], p.children[left.parentIndex+2:p.nrSegments+1])
+ for i := 0; i < p.nrSegments; i++ {
+ p.children[i].parentIndex = i
+ }
+ p.children[p.nrSegments] = nil
+ p.nrSegments--
+
+ n = p
+ }
+}
+
+// A Iterator is conceptually one of:
+//
+// - A pointer to a segment in a set; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Iterators are copyable values and are meaningfully equality-comparable. The
+// zero value of Iterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type idMapIterator struct {
+ // node is the node containing the iterated segment. If the iterator is
+ // terminal, node is nil.
+ node *idMapnode
+
+ // index is the index of the segment in node.keys/values.
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (seg idMapIterator) Ok() bool {
+ return seg.node != nil
+}
+
+// Range returns the iterated segment's range key.
+func (seg idMapIterator) Range() idMapRange {
+ return seg.node.keys[seg.index]
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (seg idMapIterator) Start() uint32 {
+ return seg.node.keys[seg.index].Start
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (seg idMapIterator) End() uint32 {
+ return seg.node.keys[seg.index].End
+}
+
+// SetRangeUnchecked mutates the iterated segment's range key. This operation
+// does not invalidate any iterators.
+//
+// Preconditions:
+//
+// - r.Length() > 0.
+//
+// - The new range must not overlap an existing one: If seg.NextSegment().Ok(),
+// then r.end <= seg.NextSegment().Start(); if seg.PrevSegment().Ok(), then
+// r.start >= seg.PrevSegment().End().
+func (seg idMapIterator) SetRangeUnchecked(r idMapRange) {
+ seg.node.keys[seg.index] = r
+}
+
+// SetRange mutates the iterated segment's range key. If the new range would
+// cause the iterated segment to overlap another segment, or if the new range
+// is invalid, SetRange panics. This operation does not invalidate any
+// iterators.
+func (seg idMapIterator) SetRange(r idMapRange) {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && r.Start < prev.End() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, prev.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && r.End > next.Start() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, next.Range()))
+ }
+ seg.SetRangeUnchecked(r)
+}
+
+// SetStartUnchecked mutates the iterated segment's start. This operation does
+// not invalidate any iterators.
+//
+// Preconditions: The new start must be valid: start < seg.End(); if
+// seg.PrevSegment().Ok(), then start >= seg.PrevSegment().End().
+func (seg idMapIterator) SetStartUnchecked(start uint32) {
+ seg.node.keys[seg.index].Start = start
+}
+
+// SetStart mutates the iterated segment's start. If the new start value would
+// cause the iterated segment to overlap another segment, or would result in an
+// invalid range, SetStart panics. This operation does not invalidate any
+// iterators.
+func (seg idMapIterator) SetStart(start uint32) {
+ if start >= seg.End() {
+ panic(fmt.Sprintf("new start %v would invalidate segment range %v", start, seg.Range()))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && start < prev.End() {
+ panic(fmt.Sprintf("new start %v would cause segment range %v to overlap segment range %v", start, seg.Range(), prev.Range()))
+ }
+ seg.SetStartUnchecked(start)
+}
+
+// SetEndUnchecked mutates the iterated segment's end. This operation does not
+// invalidate any iterators.
+//
+// Preconditions: The new end must be valid: end > seg.Start(); if
+// seg.NextSegment().Ok(), then end <= seg.NextSegment().Start().
+func (seg idMapIterator) SetEndUnchecked(end uint32) {
+ seg.node.keys[seg.index].End = end
+}
+
+// SetEnd mutates the iterated segment's end. If the new end value would cause
+// the iterated segment to overlap another segment, or would result in an
+// invalid range, SetEnd panics. This operation does not invalidate any
+// iterators.
+func (seg idMapIterator) SetEnd(end uint32) {
+ if end <= seg.Start() {
+ panic(fmt.Sprintf("new end %v would invalidate segment range %v", end, seg.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && end > next.Start() {
+ panic(fmt.Sprintf("new end %v would cause segment range %v to overlap segment range %v", end, seg.Range(), next.Range()))
+ }
+ seg.SetEndUnchecked(end)
+}
+
+// Value returns a copy of the iterated segment's value.
+func (seg idMapIterator) Value() uint32 {
+ return seg.node.values[seg.index]
+}
+
+// ValuePtr returns a pointer to the iterated segment's value. The pointer is
+// invalidated if the iterator is invalidated. This operation does not
+// invalidate any iterators.
+func (seg idMapIterator) ValuePtr() *uint32 {
+ return &seg.node.values[seg.index]
+}
+
+// SetValue mutates the iterated segment's value. This operation does not
+// invalidate any iterators.
+func (seg idMapIterator) SetValue(val uint32) {
+ seg.node.values[seg.index] = val
+}
+
+// PrevSegment returns the iterated segment's predecessor. If there is no
+// preceding segment, PrevSegment returns a terminal iterator.
+func (seg idMapIterator) PrevSegment() idMapIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index].lastSegment()
+ }
+ if seg.index > 0 {
+ return idMapIterator{seg.node, seg.index - 1}
+ }
+ if seg.node.parent == nil {
+ return idMapIterator{}
+ }
+ return idMapsegmentBeforePosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// NextSegment returns the iterated segment's successor. If there is no
+// succeeding segment, NextSegment returns a terminal iterator.
+func (seg idMapIterator) NextSegment() idMapIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment()
+ }
+ if seg.index < seg.node.nrSegments-1 {
+ return idMapIterator{seg.node, seg.index + 1}
+ }
+ if seg.node.parent == nil {
+ return idMapIterator{}
+ }
+ return idMapsegmentAfterPosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// PrevGap returns the gap immediately before the iterated segment.
+func (seg idMapIterator) PrevGap() idMapGapIterator {
+ if seg.node.hasChildren {
+
+ return seg.node.children[seg.index].lastSegment().NextGap()
+ }
+ return idMapGapIterator{seg.node, seg.index}
+}
+
+// NextGap returns the gap immediately after the iterated segment.
+func (seg idMapIterator) NextGap() idMapGapIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment().PrevGap()
+ }
+ return idMapGapIterator{seg.node, seg.index + 1}
+}
+
+// PrevNonEmpty returns the iterated segment's predecessor if it is adjacent,
+// or the gap before the iterated segment otherwise. If seg.Start() ==
+// Functions.MinKey(), PrevNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by PrevNonEmpty will be
+// non-terminal.
+func (seg idMapIterator) PrevNonEmpty() (idMapIterator, idMapGapIterator) {
+ gap := seg.PrevGap()
+ if gap.Range().Length() != 0 {
+ return idMapIterator{}, gap
+ }
+ return gap.PrevSegment(), idMapGapIterator{}
+}
+
+// NextNonEmpty returns the iterated segment's successor if it is adjacent, or
+// the gap after the iterated segment otherwise. If seg.End() ==
+// Functions.MaxKey(), NextNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by NextNonEmpty will be
+// non-terminal.
+func (seg idMapIterator) NextNonEmpty() (idMapIterator, idMapGapIterator) {
+ gap := seg.NextGap()
+ if gap.Range().Length() != 0 {
+ return idMapIterator{}, gap
+ }
+ return gap.NextSegment(), idMapGapIterator{}
+}
+
+// A GapIterator is conceptually one of:
+//
+// - A pointer to a position between two segments, before the first segment, or
+// after the last segment in a set, called a *gap*; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Note that the gap between two adjacent segments exists (iterators to it are
+// non-terminal), but has a length of zero. GapIterator.IsEmpty returns true
+// for such gaps. An empty set contains a single gap, spanning the entire range
+// of the set's keys.
+//
+// GapIterators are copyable values and are meaningfully equality-comparable.
+// The zero value of GapIterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type idMapGapIterator struct {
+ // The representation of a GapIterator is identical to that of an Iterator,
+ // except that index corresponds to positions between segments in the same
+ // way as for node.children (see comment for node.nrSegments).
+ node *idMapnode
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (gap idMapGapIterator) Ok() bool {
+ return gap.node != nil
+}
+
+// Range returns the range spanned by the iterated gap.
+func (gap idMapGapIterator) Range() idMapRange {
+ return idMapRange{gap.Start(), gap.End()}
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (gap idMapGapIterator) Start() uint32 {
+ if ps := gap.PrevSegment(); ps.Ok() {
+ return ps.End()
+ }
+ return idMapFunctions{}.MinKey()
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (gap idMapGapIterator) End() uint32 {
+ if ns := gap.NextSegment(); ns.Ok() {
+ return ns.Start()
+ }
+ return idMapFunctions{}.MaxKey()
+}
+
+// IsEmpty returns true if the iterated gap is empty (that is, the "gap" is
+// between two adjacent segments.)
+func (gap idMapGapIterator) IsEmpty() bool {
+ return gap.Range().Length() == 0
+}
+
+// PrevSegment returns the segment immediately before the iterated gap. If no
+// such segment exists, PrevSegment returns a terminal iterator.
+func (gap idMapGapIterator) PrevSegment() idMapIterator {
+ return idMapsegmentBeforePosition(gap.node, gap.index)
+}
+
+// NextSegment returns the segment immediately after the iterated gap. If no
+// such segment exists, NextSegment returns a terminal iterator.
+func (gap idMapGapIterator) NextSegment() idMapIterator {
+ return idMapsegmentAfterPosition(gap.node, gap.index)
+}
+
+// PrevGap returns the iterated gap's predecessor. If no such gap exists,
+// PrevGap returns a terminal iterator.
+func (gap idMapGapIterator) PrevGap() idMapGapIterator {
+ seg := gap.PrevSegment()
+ if !seg.Ok() {
+ return idMapGapIterator{}
+ }
+ return seg.PrevGap()
+}
+
+// NextGap returns the iterated gap's successor. If no such gap exists, NextGap
+// returns a terminal iterator.
+func (gap idMapGapIterator) NextGap() idMapGapIterator {
+ seg := gap.NextSegment()
+ if !seg.Ok() {
+ return idMapGapIterator{}
+ }
+ return seg.NextGap()
+}
+
+// segmentBeforePosition returns the predecessor segment of the position given
+// by n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentBeforePosition returns a terminal iterator.
+func idMapsegmentBeforePosition(n *idMapnode, i int) idMapIterator {
+ for i == 0 {
+ if n.parent == nil {
+ return idMapIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return idMapIterator{n, i - 1}
+}
+
+// segmentAfterPosition returns the successor segment of the position given by
+// n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentAfterPosition returns a terminal iterator.
+func idMapsegmentAfterPosition(n *idMapnode, i int) idMapIterator {
+ for i == n.nrSegments {
+ if n.parent == nil {
+ return idMapIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return idMapIterator{n, i}
+}
+
+func idMapzeroValueSlice(slice []uint32) {
+
+ for i := range slice {
+ idMapFunctions{}.ClearValue(&slice[i])
+ }
+}
+
+func idMapzeroNodeSlice(slice []*idMapnode) {
+ for i := range slice {
+ slice[i] = nil
+ }
+}
+
+// String stringifies a Set for debugging.
+func (s *idMapSet) String() string {
+ return s.root.String()
+}
+
+// String stringifies a node (and all of its children) for debugging.
+func (n *idMapnode) String() string {
+ var buf bytes.Buffer
+ n.writeDebugString(&buf, "")
+ return buf.String()
+}
+
+func (n *idMapnode) writeDebugString(buf *bytes.Buffer, prefix string) {
+ if n.hasChildren != (n.nrSegments > 0 && n.children[0] != nil) {
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent value of hasChildren: got %v, want %v\n", n.hasChildren, !n.hasChildren))
+ }
+ for i := 0; i < n.nrSegments; i++ {
+ if child := n.children[i]; child != nil {
+ cprefix := fmt.Sprintf("%s- % 3d ", prefix, i)
+ if child.parent != n || child.parentIndex != i {
+ buf.WriteString(cprefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent linkage to parent: got (%p, %d), want (%p, %d)\n", child.parent, child.parentIndex, n, i))
+ }
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, i))
+ }
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("- % 3d: %v => %v\n", i, n.keys[i], n.values[i]))
+ }
+ if child := n.children[n.nrSegments]; child != nil {
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, n.nrSegments))
+ }
+}
+
+// SegmentDataSlices represents segments from a set as slices of start, end, and
+// values. SegmentDataSlices is primarily used as an intermediate representation
+// for save/restore and the layout here is optimized for that.
+//
+// +stateify savable
+type idMapSegmentDataSlices struct {
+ Start []uint32
+ End []uint32
+ Values []uint32
+}
+
+// ExportSortedSlice returns a copy of all segments in the given set, in ascending
+// key order.
+func (s *idMapSet) ExportSortedSlices() *idMapSegmentDataSlices {
+ var sds idMapSegmentDataSlices
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sds.Start = append(sds.Start, seg.Start())
+ sds.End = append(sds.End, seg.End())
+ sds.Values = append(sds.Values, seg.Value())
+ }
+ sds.Start = sds.Start[:len(sds.Start):len(sds.Start)]
+ sds.End = sds.End[:len(sds.End):len(sds.End)]
+ sds.Values = sds.Values[:len(sds.Values):len(sds.Values)]
+ return &sds
+}
+
+// ImportSortedSlice initializes the given set from the given slice.
+//
+// Preconditions: s must be empty. sds must represent a valid set (the segments
+// in sds must have valid lengths that do not overlap). The segments in sds
+// must be sorted in ascending key order.
+func (s *idMapSet) ImportSortedSlices(sds *idMapSegmentDataSlices) error {
+ if !s.IsEmpty() {
+ return fmt.Errorf("cannot import into non-empty set %v", s)
+ }
+ gap := s.FirstGap()
+ for i := range sds.Start {
+ r := idMapRange{sds.Start[i], sds.End[i]}
+ if !gap.Range().IsSupersetOf(r) {
+ return fmt.Errorf("segment overlaps a preceding segment or is incorrectly sorted: [%d, %d) => %v", sds.Start[i], sds.End[i], sds.Values[i])
+ }
+ gap = s.InsertWithoutMerging(gap, r, sds.Values[i]).NextGap()
+ }
+ return nil
+}
+func (s *idMapSet) saveRoot() *idMapSegmentDataSlices {
+ return s.ExportSortedSlices()
+}
+
+func (s *idMapSet) loadRoot(sds *idMapSegmentDataSlices) {
+ if err := s.ImportSortedSlices(sds); err != nil {
+ panic(err)
+ }
+}
diff --git a/pkg/sentry/kernel/contexttest/BUILD b/pkg/sentry/kernel/contexttest/BUILD
deleted file mode 100644
index bec13a3d9..000000000
--- a/pkg/sentry/kernel/contexttest/BUILD
+++ /dev/null
@@ -1,18 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "contexttest",
- testonly = 1,
- srcs = ["contexttest.go"],
- importpath = "gvisor.dev/gvisor/pkg/sentry/kernel/contexttest",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/sentry/context",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/kernel",
- "//pkg/sentry/pgalloc",
- "//pkg/sentry/platform",
- ],
-)
diff --git a/pkg/sentry/kernel/contexttest/contexttest.go b/pkg/sentry/kernel/contexttest/contexttest.go
deleted file mode 100644
index 82f9d8922..000000000
--- a/pkg/sentry/kernel/contexttest/contexttest.go
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package contexttest provides a test context.Context which includes
-// a dummy kernel pointing to a valid platform.
-package contexttest
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/kernel"
- "gvisor.dev/gvisor/pkg/sentry/pgalloc"
- "gvisor.dev/gvisor/pkg/sentry/platform"
-)
-
-// Context returns a Context that may be used in tests. Uses ptrace as the
-// platform.Platform, and provides a stub kernel that only serves to point to
-// the platform.
-func Context(tb testing.TB) context.Context {
- ctx := contexttest.Context(tb)
- k := &kernel.Kernel{
- Platform: platform.FromContext(ctx),
- }
- k.SetMemoryFile(pgalloc.MemoryFileFromContext(ctx))
- ctx.(*contexttest.TestContext).RegisterValue(kernel.CtxKernel, k)
- return ctx
-}
diff --git a/pkg/sentry/kernel/epoll/BUILD b/pkg/sentry/kernel/epoll/BUILD
deleted file mode 100644
index 65427b112..000000000
--- a/pkg/sentry/kernel/epoll/BUILD
+++ /dev/null
@@ -1,52 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_template_instance(
- name = "epoll_list",
- out = "epoll_list.go",
- package = "epoll",
- prefix = "pollEntry",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*pollEntry",
- "Linker": "*pollEntry",
- },
-)
-
-go_library(
- name = "epoll",
- srcs = [
- "epoll.go",
- "epoll_list.go",
- "epoll_state.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/kernel/epoll",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/refs",
- "//pkg/sentry/context",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/anon",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/usermem",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "epoll_test",
- size = "small",
- srcs = [
- "epoll_test.go",
- ],
- embed = [":epoll"],
- deps = [
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/fs/filetest",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/kernel/epoll/epoll_list.go b/pkg/sentry/kernel/epoll/epoll_list.go
new file mode 100755
index 000000000..94d5c9e57
--- /dev/null
+++ b/pkg/sentry/kernel/epoll/epoll_list.go
@@ -0,0 +1,173 @@
+package epoll
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type pollEntryElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (pollEntryElementMapper) linkerFor(elem *pollEntry) *pollEntry { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type pollEntryList struct {
+ head *pollEntry
+ tail *pollEntry
+}
+
+// Reset resets list l to the empty state.
+func (l *pollEntryList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *pollEntryList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *pollEntryList) Front() *pollEntry {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *pollEntryList) Back() *pollEntry {
+ return l.tail
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *pollEntryList) PushFront(e *pollEntry) {
+ pollEntryElementMapper{}.linkerFor(e).SetNext(l.head)
+ pollEntryElementMapper{}.linkerFor(e).SetPrev(nil)
+
+ if l.head != nil {
+ pollEntryElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *pollEntryList) PushBack(e *pollEntry) {
+ pollEntryElementMapper{}.linkerFor(e).SetNext(nil)
+ pollEntryElementMapper{}.linkerFor(e).SetPrev(l.tail)
+
+ if l.tail != nil {
+ pollEntryElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *pollEntryList) PushBackList(m *pollEntryList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ pollEntryElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ pollEntryElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *pollEntryList) InsertAfter(b, e *pollEntry) {
+ a := pollEntryElementMapper{}.linkerFor(b).Next()
+ pollEntryElementMapper{}.linkerFor(e).SetNext(a)
+ pollEntryElementMapper{}.linkerFor(e).SetPrev(b)
+ pollEntryElementMapper{}.linkerFor(b).SetNext(e)
+
+ if a != nil {
+ pollEntryElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *pollEntryList) InsertBefore(a, e *pollEntry) {
+ b := pollEntryElementMapper{}.linkerFor(a).Prev()
+ pollEntryElementMapper{}.linkerFor(e).SetNext(a)
+ pollEntryElementMapper{}.linkerFor(e).SetPrev(b)
+ pollEntryElementMapper{}.linkerFor(a).SetPrev(e)
+
+ if b != nil {
+ pollEntryElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *pollEntryList) Remove(e *pollEntry) {
+ prev := pollEntryElementMapper{}.linkerFor(e).Prev()
+ next := pollEntryElementMapper{}.linkerFor(e).Next()
+
+ if prev != nil {
+ pollEntryElementMapper{}.linkerFor(prev).SetNext(next)
+ } else {
+ l.head = next
+ }
+
+ if next != nil {
+ pollEntryElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else {
+ l.tail = prev
+ }
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type pollEntryEntry struct {
+ next *pollEntry
+ prev *pollEntry
+}
+
+// Next returns the entry that follows e in the list.
+func (e *pollEntryEntry) Next() *pollEntry {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *pollEntryEntry) Prev() *pollEntry {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *pollEntryEntry) SetNext(elem *pollEntry) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *pollEntryEntry) SetPrev(elem *pollEntry) {
+ e.prev = elem
+}
diff --git a/pkg/sentry/kernel/epoll/epoll_state_autogen.go b/pkg/sentry/kernel/epoll/epoll_state_autogen.go
new file mode 100755
index 000000000..c738a9355
--- /dev/null
+++ b/pkg/sentry/kernel/epoll/epoll_state_autogen.go
@@ -0,0 +1,99 @@
+// automatically generated by stateify.
+
+package epoll
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *FileIdentifier) beforeSave() {}
+func (x *FileIdentifier) save(m state.Map) {
+ x.beforeSave()
+ m.Save("File", &x.File)
+ m.Save("Fd", &x.Fd)
+}
+
+func (x *FileIdentifier) afterLoad() {}
+func (x *FileIdentifier) load(m state.Map) {
+ m.LoadWait("File", &x.File)
+ m.Load("Fd", &x.Fd)
+}
+
+func (x *pollEntry) beforeSave() {}
+func (x *pollEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("pollEntryEntry", &x.pollEntryEntry)
+ m.Save("id", &x.id)
+ m.Save("userData", &x.userData)
+ m.Save("mask", &x.mask)
+ m.Save("flags", &x.flags)
+ m.Save("epoll", &x.epoll)
+}
+
+func (x *pollEntry) load(m state.Map) {
+ m.Load("pollEntryEntry", &x.pollEntryEntry)
+ m.LoadWait("id", &x.id)
+ m.Load("userData", &x.userData)
+ m.Load("mask", &x.mask)
+ m.Load("flags", &x.flags)
+ m.Load("epoll", &x.epoll)
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *EventPoll) beforeSave() {}
+func (x *EventPoll) save(m state.Map) {
+ x.beforeSave()
+ if !state.IsZeroValue(x.FilePipeSeek) { m.Failf("FilePipeSeek is %v, expected zero", x.FilePipeSeek) }
+ if !state.IsZeroValue(x.FileNotDirReaddir) { m.Failf("FileNotDirReaddir is %v, expected zero", x.FileNotDirReaddir) }
+ if !state.IsZeroValue(x.FileNoFsync) { m.Failf("FileNoFsync is %v, expected zero", x.FileNoFsync) }
+ if !state.IsZeroValue(x.FileNoopFlush) { m.Failf("FileNoopFlush is %v, expected zero", x.FileNoopFlush) }
+ if !state.IsZeroValue(x.FileNoIoctl) { m.Failf("FileNoIoctl is %v, expected zero", x.FileNoIoctl) }
+ if !state.IsZeroValue(x.FileNoMMap) { m.Failf("FileNoMMap is %v, expected zero", x.FileNoMMap) }
+ if !state.IsZeroValue(x.Queue) { m.Failf("Queue is %v, expected zero", x.Queue) }
+ m.Save("files", &x.files)
+ m.Save("readyList", &x.readyList)
+ m.Save("waitingList", &x.waitingList)
+ m.Save("disabledList", &x.disabledList)
+}
+
+func (x *EventPoll) load(m state.Map) {
+ m.Load("files", &x.files)
+ m.Load("readyList", &x.readyList)
+ m.Load("waitingList", &x.waitingList)
+ m.Load("disabledList", &x.disabledList)
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *pollEntryList) beforeSave() {}
+func (x *pollEntryList) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *pollEntryList) afterLoad() {}
+func (x *pollEntryList) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *pollEntryEntry) beforeSave() {}
+func (x *pollEntryEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *pollEntryEntry) afterLoad() {}
+func (x *pollEntryEntry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func init() {
+ state.Register("epoll.FileIdentifier", (*FileIdentifier)(nil), state.Fns{Save: (*FileIdentifier).save, Load: (*FileIdentifier).load})
+ state.Register("epoll.pollEntry", (*pollEntry)(nil), state.Fns{Save: (*pollEntry).save, Load: (*pollEntry).load})
+ state.Register("epoll.EventPoll", (*EventPoll)(nil), state.Fns{Save: (*EventPoll).save, Load: (*EventPoll).load})
+ state.Register("epoll.pollEntryList", (*pollEntryList)(nil), state.Fns{Save: (*pollEntryList).save, Load: (*pollEntryList).load})
+ state.Register("epoll.pollEntryEntry", (*pollEntryEntry)(nil), state.Fns{Save: (*pollEntryEntry).save, Load: (*pollEntryEntry).load})
+}
diff --git a/pkg/sentry/kernel/epoll/epoll_test.go b/pkg/sentry/kernel/epoll/epoll_test.go
deleted file mode 100644
index 4a20d4c82..000000000
--- a/pkg/sentry/kernel/epoll/epoll_test.go
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package epoll
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs/filetest"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-func TestFileDestroyed(t *testing.T) {
- f := filetest.NewTestFile(t)
- id := FileIdentifier{f, 12}
-
- efile := NewEventPoll(contexttest.Context(t))
- e := efile.FileOperations.(*EventPoll)
- if err := e.AddEntry(id, 0, waiter.EventIn, [2]int32{}); err != nil {
- t.Fatalf("addEntry failed: %v", err)
- }
-
- // Check that we get an event reported twice in a row.
- evt := e.ReadEvents(1)
- if len(evt) != 1 {
- t.Fatalf("Unexpected number of ready events: want %v, got %v", 1, len(evt))
- }
-
- evt = e.ReadEvents(1)
- if len(evt) != 1 {
- t.Fatalf("Unexpected number of ready events: want %v, got %v", 1, len(evt))
- }
-
- // Destroy the file. Check that we get no more events.
- f.DecRef()
-
- evt = e.ReadEvents(1)
- if len(evt) != 0 {
- t.Fatalf("Unexpected number of ready events: want %v, got %v", 0, len(evt))
- }
-
-}
diff --git a/pkg/sentry/kernel/eventfd/BUILD b/pkg/sentry/kernel/eventfd/BUILD
deleted file mode 100644
index 983ca67ed..000000000
--- a/pkg/sentry/kernel/eventfd/BUILD
+++ /dev/null
@@ -1,35 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "eventfd",
- srcs = ["eventfd.go"],
- importpath = "gvisor.dev/gvisor/pkg/sentry/kernel/eventfd",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/fdnotifier",
- "//pkg/sentry/context",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/anon",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "eventfd_test",
- size = "small",
- srcs = ["eventfd_test.go"],
- embed = [":eventfd"],
- deps = [
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/usermem",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/kernel/eventfd/eventfd_state_autogen.go b/pkg/sentry/kernel/eventfd/eventfd_state_autogen.go
new file mode 100755
index 000000000..f6e94b521
--- /dev/null
+++ b/pkg/sentry/kernel/eventfd/eventfd_state_autogen.go
@@ -0,0 +1,27 @@
+// automatically generated by stateify.
+
+package eventfd
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *EventOperations) beforeSave() {}
+func (x *EventOperations) save(m state.Map) {
+ x.beforeSave()
+ if !state.IsZeroValue(x.wq) { m.Failf("wq is %v, expected zero", x.wq) }
+ m.Save("val", &x.val)
+ m.Save("semMode", &x.semMode)
+ m.Save("hostfd", &x.hostfd)
+}
+
+func (x *EventOperations) afterLoad() {}
+func (x *EventOperations) load(m state.Map) {
+ m.Load("val", &x.val)
+ m.Load("semMode", &x.semMode)
+ m.Load("hostfd", &x.hostfd)
+}
+
+func init() {
+ state.Register("eventfd.EventOperations", (*EventOperations)(nil), state.Fns{Save: (*EventOperations).save, Load: (*EventOperations).load})
+}
diff --git a/pkg/sentry/kernel/eventfd/eventfd_test.go b/pkg/sentry/kernel/eventfd/eventfd_test.go
deleted file mode 100644
index 018c7f3ef..000000000
--- a/pkg/sentry/kernel/eventfd/eventfd_test.go
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package eventfd
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-func TestEventfd(t *testing.T) {
- initVals := []uint64{
- 0,
- // Using a non-zero initial value verifies that writing to an
- // eventfd signals when the eventfd's counter was already
- // non-zero.
- 343,
- }
-
- for _, initVal := range initVals {
- ctx := contexttest.Context(t)
-
- // Make a new event that is writable.
- event := New(ctx, initVal, false)
-
- // Register a callback for a write event.
- w, ch := waiter.NewChannelEntry(nil)
- event.EventRegister(&w, waiter.EventIn)
- defer event.EventUnregister(&w)
-
- data := []byte("00000124")
- // Create and submit a write request.
- n, err := event.Writev(ctx, usermem.BytesIOSequence(data))
- if err != nil {
- t.Fatal(err)
- }
- if n != 8 {
- t.Errorf("eventfd.write wrote %d bytes, not full int64", n)
- }
-
- // Check if the callback fired due to the write event.
- select {
- case <-ch:
- default:
- t.Errorf("Didn't get notified of EventIn after write")
- }
- }
-}
-
-func TestEventfdStat(t *testing.T) {
- ctx := contexttest.Context(t)
-
- // Make a new event that is writable.
- event := New(ctx, 0, false)
-
- // Create and submit an stat request.
- uattr, err := event.Dirent.Inode.UnstableAttr(ctx)
- if err != nil {
- t.Fatalf("eventfd stat request failed: %v", err)
- }
- if uattr.Size != 0 {
- t.Fatal("EventFD size should be 0")
- }
-}
diff --git a/pkg/sentry/kernel/fasync/BUILD b/pkg/sentry/kernel/fasync/BUILD
deleted file mode 100644
index 5eddca115..000000000
--- a/pkg/sentry/kernel/fasync/BUILD
+++ /dev/null
@@ -1,17 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "fasync",
- srcs = ["fasync.go"],
- importpath = "gvisor.dev/gvisor/pkg/sentry/kernel/fasync",
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/sentry/fs",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/kernel/fasync/fasync_state_autogen.go b/pkg/sentry/kernel/fasync/fasync_state_autogen.go
new file mode 100755
index 000000000..c17daa8a1
--- /dev/null
+++ b/pkg/sentry/kernel/fasync/fasync_state_autogen.go
@@ -0,0 +1,32 @@
+// automatically generated by stateify.
+
+package fasync
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *FileAsync) beforeSave() {}
+func (x *FileAsync) save(m state.Map) {
+ x.beforeSave()
+ m.Save("e", &x.e)
+ m.Save("requester", &x.requester)
+ m.Save("registered", &x.registered)
+ m.Save("recipientPG", &x.recipientPG)
+ m.Save("recipientTG", &x.recipientTG)
+ m.Save("recipientT", &x.recipientT)
+}
+
+func (x *FileAsync) afterLoad() {}
+func (x *FileAsync) load(m state.Map) {
+ m.Load("e", &x.e)
+ m.Load("requester", &x.requester)
+ m.Load("registered", &x.registered)
+ m.Load("recipientPG", &x.recipientPG)
+ m.Load("recipientTG", &x.recipientTG)
+ m.Load("recipientT", &x.recipientT)
+}
+
+func init() {
+ state.Register("fasync.FileAsync", (*FileAsync)(nil), state.Fns{Save: (*FileAsync).save, Load: (*FileAsync).load})
+}
diff --git a/pkg/sentry/kernel/fd_table_test.go b/pkg/sentry/kernel/fd_table_test.go
deleted file mode 100644
index 2413788e7..000000000
--- a/pkg/sentry/kernel/fd_table_test.go
+++ /dev/null
@@ -1,192 +0,0 @@
-// Copyright 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package kernel
-
-import (
- "runtime"
- "sync"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- "gvisor.dev/gvisor/pkg/sentry/fs/filetest"
- "gvisor.dev/gvisor/pkg/sentry/limits"
-)
-
-const (
- // maxFD is the maximum FD to try to create in the map.
- //
- // This number of open files has been seen in the wild.
- maxFD = 2 * 1024
-)
-
-func runTest(t testing.TB, fn func(ctx context.Context, fdTable *FDTable, file *fs.File, limitSet *limits.LimitSet)) {
- t.Helper() // Don't show in stacks.
-
- // Create the limits and context.
- limitSet := limits.NewLimitSet()
- limitSet.Set(limits.NumberOfFiles, limits.Limit{maxFD, maxFD}, true)
- ctx := contexttest.WithLimitSet(contexttest.Context(t), limitSet)
-
- // Create a test file.;
- file := filetest.NewTestFile(t)
-
- // Create the table.
- fdTable := new(FDTable)
- fdTable.init()
-
- // Run the test.
- fn(ctx, fdTable, file, limitSet)
-}
-
-// TestFDTableMany allocates maxFD FDs, i.e. maxes out the FDTable, until there
-// is no room, then makes sure that NewFDAt works and also that if we remove
-// one and add one that works too.
-func TestFDTableMany(t *testing.T) {
- runTest(t, func(ctx context.Context, fdTable *FDTable, file *fs.File, _ *limits.LimitSet) {
- for i := 0; i < maxFD; i++ {
- if _, err := fdTable.NewFDs(ctx, 0, []*fs.File{file}, FDFlags{}); err != nil {
- t.Fatalf("Allocated %v FDs but wanted to allocate %v", i, maxFD)
- }
- }
-
- if _, err := fdTable.NewFDs(ctx, 0, []*fs.File{file}, FDFlags{}); err == nil {
- t.Fatalf("fdTable.NewFDs(0, r) in full map: got nil, wanted error")
- }
-
- if err := fdTable.NewFDAt(ctx, 1, file, FDFlags{}); err != nil {
- t.Fatalf("fdTable.NewFDAt(1, r, FDFlags{}): got %v, wanted nil", err)
- }
- })
-}
-
-// TestFDTable does a set of simple tests to make sure simple adds, removes,
-// GetRefs, and DecRefs work. The ordering is just weird enough that a
-// table-driven approach seemed clumsy.
-func TestFDTable(t *testing.T) {
- runTest(t, func(ctx context.Context, fdTable *FDTable, file *fs.File, limitSet *limits.LimitSet) {
- // Cap the limit at one.
- limitSet.Set(limits.NumberOfFiles, limits.Limit{1, maxFD}, true)
-
- if _, err := fdTable.NewFDs(ctx, 0, []*fs.File{file}, FDFlags{}); err != nil {
- t.Fatalf("Adding an FD to an empty 1-size map: got %v, want nil", err)
- }
-
- if _, err := fdTable.NewFDs(ctx, 0, []*fs.File{file}, FDFlags{}); err == nil {
- t.Fatalf("Adding an FD to a filled 1-size map: got nil, wanted an error")
- }
-
- // Remove the previous limit.
- limitSet.Set(limits.NumberOfFiles, limits.Limit{maxFD, maxFD}, true)
-
- if fds, err := fdTable.NewFDs(ctx, 0, []*fs.File{file}, FDFlags{}); err != nil {
- t.Fatalf("Adding an FD to a resized map: got %v, want nil", err)
- } else if len(fds) != 1 || fds[0] != 1 {
- t.Fatalf("Added an FD to a resized map: got %v, want {1}", fds)
- }
-
- if err := fdTable.NewFDAt(ctx, 1, file, FDFlags{}); err != nil {
- t.Fatalf("Replacing FD 1 via fdTable.NewFDAt(1, r, FDFlags{}): got %v, wanted nil", err)
- }
-
- if err := fdTable.NewFDAt(ctx, maxFD+1, file, FDFlags{}); err == nil {
- t.Fatalf("Using an FD that was too large via fdTable.NewFDAt(%v, r, FDFlags{}): got nil, wanted an error", maxFD+1)
- }
-
- if ref, _ := fdTable.Get(1); ref == nil {
- t.Fatalf("fdTable.Get(1): got nil, wanted %v", file)
- }
-
- if ref, _ := fdTable.Get(2); ref != nil {
- t.Fatalf("fdTable.Get(2): got a %v, wanted nil", ref)
- }
-
- ref := fdTable.Remove(1)
- if ref == nil {
- t.Fatalf("fdTable.Remove(1) for an existing FD: failed, want success")
- }
- ref.DecRef()
-
- if ref := fdTable.Remove(1); ref != nil {
- t.Fatalf("r.Remove(1) for a removed FD: got success, want failure")
- }
- })
-}
-
-func TestDescriptorFlags(t *testing.T) {
- runTest(t, func(ctx context.Context, fdTable *FDTable, file *fs.File, _ *limits.LimitSet) {
- if err := fdTable.NewFDAt(ctx, 2, file, FDFlags{CloseOnExec: true}); err != nil {
- t.Fatalf("fdTable.NewFDAt(2, r, FDFlags{}): got %v, wanted nil", err)
- }
-
- newFile, flags := fdTable.Get(2)
- if newFile == nil {
- t.Fatalf("fdTable.Get(2): got a %v, wanted nil", newFile)
- }
-
- if !flags.CloseOnExec {
- t.Fatalf("new File flags %v don't match original %d\n", flags, 0)
- }
- })
-}
-
-func BenchmarkFDLookupAndDecRef(b *testing.B) {
- b.StopTimer() // Setup.
-
- runTest(b, func(ctx context.Context, fdTable *FDTable, file *fs.File, _ *limits.LimitSet) {
- fds, err := fdTable.NewFDs(ctx, 0, []*fs.File{file, file, file, file, file}, FDFlags{})
- if err != nil {
- b.Fatalf("fdTable.NewFDs: got %v, wanted nil", err)
- }
-
- b.StartTimer() // Benchmark.
- for i := 0; i < b.N; i++ {
- tf, _ := fdTable.Get(fds[i%len(fds)])
- tf.DecRef()
- }
- })
-}
-
-func BenchmarkFDLookupAndDecRefConcurrent(b *testing.B) {
- b.StopTimer() // Setup.
-
- runTest(b, func(ctx context.Context, fdTable *FDTable, file *fs.File, _ *limits.LimitSet) {
- fds, err := fdTable.NewFDs(ctx, 0, []*fs.File{file, file, file, file, file}, FDFlags{})
- if err != nil {
- b.Fatalf("fdTable.NewFDs: got %v, wanted nil", err)
- }
-
- concurrency := runtime.GOMAXPROCS(0)
- if concurrency < 4 {
- concurrency = 4
- }
- each := b.N / concurrency
-
- b.StartTimer() // Benchmark.
- var wg sync.WaitGroup
- for i := 0; i < concurrency; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for i := 0; i < each; i++ {
- tf, _ := fdTable.Get(fds[i%len(fds)])
- tf.DecRef()
- }
- }()
- }
- wg.Wait()
- })
-}
diff --git a/pkg/sentry/kernel/futex/BUILD b/pkg/sentry/kernel/futex/BUILD
deleted file mode 100644
index 41f44999c..000000000
--- a/pkg/sentry/kernel/futex/BUILD
+++ /dev/null
@@ -1,56 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_template_instance(
- name = "atomicptr_bucket",
- out = "atomicptr_bucket_unsafe.go",
- package = "futex",
- suffix = "Bucket",
- template = "//third_party/gvsync:generic_atomicptr",
- types = {
- "Value": "bucket",
- },
-)
-
-go_template_instance(
- name = "waiter_list",
- out = "waiter_list.go",
- package = "futex",
- prefix = "waiter",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*Waiter",
- "Linker": "*Waiter",
- },
-)
-
-go_library(
- name = "futex",
- srcs = [
- "atomicptr_bucket_unsafe.go",
- "futex.go",
- "waiter_list.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/kernel/futex",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/log",
- "//pkg/sentry/context",
- "//pkg/sentry/memmap",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- ],
-)
-
-go_test(
- name = "futex_test",
- size = "small",
- srcs = ["futex_test.go"],
- embed = [":futex"],
- deps = ["//pkg/sentry/usermem"],
-)
diff --git a/pkg/sentry/kernel/futex/atomicptr_bucket_unsafe.go b/pkg/sentry/kernel/futex/atomicptr_bucket_unsafe.go
new file mode 100755
index 000000000..d3fdf09b0
--- /dev/null
+++ b/pkg/sentry/kernel/futex/atomicptr_bucket_unsafe.go
@@ -0,0 +1,37 @@
+package futex
+
+import (
+ "sync/atomic"
+ "unsafe"
+)
+
+// An AtomicPtr is a pointer to a value of type Value that can be atomically
+// loaded and stored. The zero value of an AtomicPtr represents nil.
+//
+// Note that copying AtomicPtr by value performs a non-atomic read of the
+// stored pointer, which is unsafe if Store() can be called concurrently; in
+// this case, do `dst.Store(src.Load())` instead.
+//
+// +stateify savable
+type AtomicPtrBucket struct {
+ ptr unsafe.Pointer `state:".(*bucket)"`
+}
+
+func (p *AtomicPtrBucket) savePtr() *bucket {
+ return p.Load()
+}
+
+func (p *AtomicPtrBucket) loadPtr(v *bucket) {
+ p.Store(v)
+}
+
+// Load returns the value set by the most recent Store. It returns nil if there
+// has been no previous call to Store.
+func (p *AtomicPtrBucket) Load() *bucket {
+ return (*bucket)(atomic.LoadPointer(&p.ptr))
+}
+
+// Store sets the value returned by Load to x.
+func (p *AtomicPtrBucket) Store(x *bucket) {
+ atomic.StorePointer(&p.ptr, (unsafe.Pointer)(x))
+}
diff --git a/pkg/sentry/kernel/futex/futex_state_autogen.go b/pkg/sentry/kernel/futex/futex_state_autogen.go
new file mode 100755
index 000000000..b2ff39f17
--- /dev/null
+++ b/pkg/sentry/kernel/futex/futex_state_autogen.go
@@ -0,0 +1,75 @@
+// automatically generated by stateify.
+
+package futex
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *AtomicPtrBucket) beforeSave() {}
+func (x *AtomicPtrBucket) save(m state.Map) {
+ x.beforeSave()
+ var ptr *bucket = x.savePtr()
+ m.SaveValue("ptr", ptr)
+}
+
+func (x *AtomicPtrBucket) afterLoad() {}
+func (x *AtomicPtrBucket) load(m state.Map) {
+ m.LoadValue("ptr", new(*bucket), func(y interface{}) { x.loadPtr(y.(*bucket)) })
+}
+
+func (x *bucket) beforeSave() {}
+func (x *bucket) save(m state.Map) {
+ x.beforeSave()
+ if !state.IsZeroValue(x.waiters) { m.Failf("waiters is %v, expected zero", x.waiters) }
+}
+
+func (x *bucket) afterLoad() {}
+func (x *bucket) load(m state.Map) {
+}
+
+func (x *Manager) beforeSave() {}
+func (x *Manager) save(m state.Map) {
+ x.beforeSave()
+ if !state.IsZeroValue(x.privateBuckets) { m.Failf("privateBuckets is %v, expected zero", x.privateBuckets) }
+ m.Save("sharedBucket", &x.sharedBucket)
+}
+
+func (x *Manager) afterLoad() {}
+func (x *Manager) load(m state.Map) {
+ m.Load("sharedBucket", &x.sharedBucket)
+}
+
+func (x *waiterList) beforeSave() {}
+func (x *waiterList) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *waiterList) afterLoad() {}
+func (x *waiterList) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *waiterEntry) beforeSave() {}
+func (x *waiterEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *waiterEntry) afterLoad() {}
+func (x *waiterEntry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func init() {
+ state.Register("futex.AtomicPtrBucket", (*AtomicPtrBucket)(nil), state.Fns{Save: (*AtomicPtrBucket).save, Load: (*AtomicPtrBucket).load})
+ state.Register("futex.bucket", (*bucket)(nil), state.Fns{Save: (*bucket).save, Load: (*bucket).load})
+ state.Register("futex.Manager", (*Manager)(nil), state.Fns{Save: (*Manager).save, Load: (*Manager).load})
+ state.Register("futex.waiterList", (*waiterList)(nil), state.Fns{Save: (*waiterList).save, Load: (*waiterList).load})
+ state.Register("futex.waiterEntry", (*waiterEntry)(nil), state.Fns{Save: (*waiterEntry).save, Load: (*waiterEntry).load})
+}
diff --git a/pkg/sentry/kernel/futex/futex_test.go b/pkg/sentry/kernel/futex/futex_test.go
deleted file mode 100644
index 65e5d1428..000000000
--- a/pkg/sentry/kernel/futex/futex_test.go
+++ /dev/null
@@ -1,530 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package futex
-
-import (
- "math"
- "runtime"
- "sync"
- "sync/atomic"
- "syscall"
- "testing"
- "unsafe"
-
- "gvisor.dev/gvisor/pkg/sentry/usermem"
-)
-
-// testData implements the Target interface, and allows us to
-// treat the address passed for futex operations as an index in
-// a byte slice for testing simplicity.
-type testData []byte
-
-const sizeofInt32 = 4
-
-func newTestData(size uint) testData {
- return make([]byte, size)
-}
-
-func (t testData) SwapUint32(addr usermem.Addr, new uint32) (uint32, error) {
- val := atomic.SwapUint32((*uint32)(unsafe.Pointer(&t[addr])), new)
- return val, nil
-}
-
-func (t testData) CompareAndSwapUint32(addr usermem.Addr, old, new uint32) (uint32, error) {
- if atomic.CompareAndSwapUint32((*uint32)(unsafe.Pointer(&t[addr])), old, new) {
- return old, nil
- }
- return atomic.LoadUint32((*uint32)(unsafe.Pointer(&t[addr]))), nil
-}
-
-func (t testData) LoadUint32(addr usermem.Addr) (uint32, error) {
- return atomic.LoadUint32((*uint32)(unsafe.Pointer(&t[addr]))), nil
-}
-
-func (t testData) GetSharedKey(addr usermem.Addr) (Key, error) {
- return Key{
- Kind: KindSharedMappable,
- Offset: uint64(addr),
- }, nil
-}
-
-func futexKind(private bool) string {
- if private {
- return "private"
- }
- return "shared"
-}
-
-func newPreparedTestWaiter(t *testing.T, m *Manager, ta Target, addr usermem.Addr, private bool, val uint32, bitmask uint32) *Waiter {
- w := NewWaiter()
- if err := m.WaitPrepare(w, ta, addr, private, val, bitmask); err != nil {
- t.Fatalf("WaitPrepare failed: %v", err)
- }
- return w
-}
-
-func TestFutexWake(t *testing.T) {
- for _, private := range []bool{false, true} {
- t.Run(futexKind(private), func(t *testing.T) {
- m := NewManager()
- d := newTestData(sizeofInt32)
-
- // Start waiting for wakeup.
- w := newPreparedTestWaiter(t, m, d, 0, private, 0, ^uint32(0))
- defer m.WaitComplete(w)
-
- // Perform a wakeup.
- if n, err := m.Wake(d, 0, private, ^uint32(0), 1); err != nil || n != 1 {
- t.Errorf("Wake: got (%d, %v), wanted (1, nil)", n, err)
- }
-
- // Expect the waiter to have been woken.
- if !w.woken() {
- t.Error("waiter not woken")
- }
- })
- }
-}
-
-func TestFutexWakeBitmask(t *testing.T) {
- for _, private := range []bool{false, true} {
- t.Run(futexKind(private), func(t *testing.T) {
- m := NewManager()
- d := newTestData(sizeofInt32)
-
- // Start waiting for wakeup.
- w := newPreparedTestWaiter(t, m, d, 0, private, 0, 0x0000ffff)
- defer m.WaitComplete(w)
-
- // Perform a wakeup using the wrong bitmask.
- if n, err := m.Wake(d, 0, private, 0xffff0000, 1); err != nil || n != 0 {
- t.Errorf("Wake with non-matching bitmask: got (%d, %v), wanted (0, nil)", n, err)
- }
-
- // Expect the waiter to still be waiting.
- if w.woken() {
- t.Error("waiter woken unexpectedly")
- }
-
- // Perform a wakeup using the right bitmask.
- if n, err := m.Wake(d, 0, private, 0x00000001, 1); err != nil || n != 1 {
- t.Errorf("Wake with matching bitmask: got (%d, %v), wanted (1, nil)", n, err)
- }
-
- // Expect that the waiter was woken.
- if !w.woken() {
- t.Error("waiter not woken")
- }
- })
- }
-}
-
-func TestFutexWakeTwo(t *testing.T) {
- for _, private := range []bool{false, true} {
- t.Run(futexKind(private), func(t *testing.T) {
- m := NewManager()
- d := newTestData(sizeofInt32)
-
- // Start three waiters waiting for wakeup.
- var ws [3]*Waiter
- for i := range ws {
- ws[i] = newPreparedTestWaiter(t, m, d, 0, private, 0, ^uint32(0))
- defer m.WaitComplete(ws[i])
- }
-
- // Perform two wakeups.
- const wakeups = 2
- if n, err := m.Wake(d, 0, private, ^uint32(0), 2); err != nil || n != wakeups {
- t.Errorf("Wake: got (%d, %v), wanted (%d, nil)", n, err, wakeups)
- }
-
- // Expect that exactly two waiters were woken.
- // We don't get guarantees about exactly which two,
- // (although we expect them to be w1 and w2).
- awake := 0
- for i := range ws {
- if ws[i].woken() {
- awake++
- }
- }
- if awake != wakeups {
- t.Errorf("got %d woken waiters, wanted %d", awake, wakeups)
- }
- })
- }
-}
-
-func TestFutexWakeUnrelated(t *testing.T) {
- for _, private := range []bool{false, true} {
- t.Run(futexKind(private), func(t *testing.T) {
- m := NewManager()
- d := newTestData(2 * sizeofInt32)
-
- // Start two waiters waiting for wakeup on different addresses.
- w1 := newPreparedTestWaiter(t, m, d, 0*sizeofInt32, private, 0, ^uint32(0))
- defer m.WaitComplete(w1)
- w2 := newPreparedTestWaiter(t, m, d, 1*sizeofInt32, private, 0, ^uint32(0))
- defer m.WaitComplete(w2)
-
- // Perform two wakeups on the second address.
- if n, err := m.Wake(d, 1*sizeofInt32, private, ^uint32(0), 2); err != nil || n != 1 {
- t.Errorf("Wake: got (%d, %v), wanted (1, nil)", n, err)
- }
-
- // Expect that only the second waiter was woken.
- if w1.woken() {
- t.Error("w1 woken unexpectedly")
- }
- if !w2.woken() {
- t.Error("w2 not woken")
- }
- })
- }
-}
-
-func TestWakeOpEmpty(t *testing.T) {
- for _, private := range []bool{false, true} {
- t.Run(futexKind(private), func(t *testing.T) {
- m := NewManager()
- d := newTestData(2 * sizeofInt32)
-
- // Perform wakeups with no waiters.
- if n, err := m.WakeOp(d, 0, sizeofInt32, private, 10, 10, 0); err != nil || n != 0 {
- t.Fatalf("WakeOp: got (%d, %v), wanted (0, nil)", n, err)
- }
- })
- }
-}
-
-func TestWakeOpFirstNonEmpty(t *testing.T) {
- for _, private := range []bool{false, true} {
- t.Run(futexKind(private), func(t *testing.T) {
- m := NewManager()
- d := newTestData(8)
-
- // Add two waiters on address 0.
- w1 := newPreparedTestWaiter(t, m, d, 0, private, 0, ^uint32(0))
- defer m.WaitComplete(w1)
- w2 := newPreparedTestWaiter(t, m, d, 0, private, 0, ^uint32(0))
- defer m.WaitComplete(w2)
-
- // Perform 10 wakeups on address 0.
- if n, err := m.WakeOp(d, 0, sizeofInt32, private, 10, 0, 0); err != nil || n != 2 {
- t.Errorf("WakeOp: got (%d, %v), wanted (2, nil)", n, err)
- }
-
- // Expect that both waiters were woken.
- if !w1.woken() {
- t.Error("w1 not woken")
- }
- if !w2.woken() {
- t.Error("w2 not woken")
- }
- })
- }
-}
-
-func TestWakeOpSecondNonEmpty(t *testing.T) {
- for _, private := range []bool{false, true} {
- t.Run(futexKind(private), func(t *testing.T) {
- m := NewManager()
- d := newTestData(8)
-
- // Add two waiters on address sizeofInt32.
- w1 := newPreparedTestWaiter(t, m, d, sizeofInt32, private, 0, ^uint32(0))
- defer m.WaitComplete(w1)
- w2 := newPreparedTestWaiter(t, m, d, sizeofInt32, private, 0, ^uint32(0))
- defer m.WaitComplete(w2)
-
- // Perform 10 wakeups on address sizeofInt32 (contingent on
- // d.Op(0), which should succeed).
- if n, err := m.WakeOp(d, 0, sizeofInt32, private, 0, 10, 0); err != nil || n != 2 {
- t.Errorf("WakeOp: got (%d, %v), wanted (2, nil)", n, err)
- }
-
- // Expect that both waiters were woken.
- if !w1.woken() {
- t.Error("w1 not woken")
- }
- if !w2.woken() {
- t.Error("w2 not woken")
- }
- })
- }
-}
-
-func TestWakeOpSecondNonEmptyFailingOp(t *testing.T) {
- for _, private := range []bool{false, true} {
- t.Run(futexKind(private), func(t *testing.T) {
- m := NewManager()
- d := newTestData(8)
-
- // Add two waiters on address sizeofInt32.
- w1 := newPreparedTestWaiter(t, m, d, sizeofInt32, private, 0, ^uint32(0))
- defer m.WaitComplete(w1)
- w2 := newPreparedTestWaiter(t, m, d, sizeofInt32, private, 0, ^uint32(0))
- defer m.WaitComplete(w2)
-
- // Perform 10 wakeups on address sizeofInt32 (contingent on
- // d.Op(1), which should fail).
- if n, err := m.WakeOp(d, 0, sizeofInt32, private, 0, 10, 1); err != nil || n != 0 {
- t.Errorf("WakeOp: got (%d, %v), wanted (0, nil)", n, err)
- }
-
- // Expect that neither waiter was woken.
- if w1.woken() {
- t.Error("w1 woken unexpectedly")
- }
- if w2.woken() {
- t.Error("w2 woken unexpectedly")
- }
- })
- }
-}
-
-func TestWakeOpAllNonEmpty(t *testing.T) {
- for _, private := range []bool{false, true} {
- t.Run(futexKind(private), func(t *testing.T) {
- m := NewManager()
- d := newTestData(8)
-
- // Add two waiters on address 0.
- w1 := newPreparedTestWaiter(t, m, d, 0, private, 0, ^uint32(0))
- defer m.WaitComplete(w1)
- w2 := newPreparedTestWaiter(t, m, d, 0, private, 0, ^uint32(0))
- defer m.WaitComplete(w2)
-
- // Add two waiters on address sizeofInt32.
- w3 := newPreparedTestWaiter(t, m, d, sizeofInt32, private, 0, ^uint32(0))
- defer m.WaitComplete(w3)
- w4 := newPreparedTestWaiter(t, m, d, sizeofInt32, private, 0, ^uint32(0))
- defer m.WaitComplete(w4)
-
- // Perform 10 wakeups on address 0 (unconditionally), and 10
- // wakeups on address sizeofInt32 (contingent on d.Op(0), which
- // should succeed).
- if n, err := m.WakeOp(d, 0, sizeofInt32, private, 10, 10, 0); err != nil || n != 4 {
- t.Errorf("WakeOp: got (%d, %v), wanted (4, nil)", n, err)
- }
-
- // Expect that all waiters were woken.
- if !w1.woken() {
- t.Error("w1 not woken")
- }
- if !w2.woken() {
- t.Error("w2 not woken")
- }
- if !w3.woken() {
- t.Error("w3 not woken")
- }
- if !w4.woken() {
- t.Error("w4 not woken")
- }
- })
- }
-}
-
-func TestWakeOpAllNonEmptyFailingOp(t *testing.T) {
- for _, private := range []bool{false, true} {
- t.Run(futexKind(private), func(t *testing.T) {
- m := NewManager()
- d := newTestData(8)
-
- // Add two waiters on address 0.
- w1 := newPreparedTestWaiter(t, m, d, 0, private, 0, ^uint32(0))
- defer m.WaitComplete(w1)
- w2 := newPreparedTestWaiter(t, m, d, 0, private, 0, ^uint32(0))
- defer m.WaitComplete(w2)
-
- // Add two waiters on address sizeofInt32.
- w3 := newPreparedTestWaiter(t, m, d, sizeofInt32, private, 0, ^uint32(0))
- defer m.WaitComplete(w3)
- w4 := newPreparedTestWaiter(t, m, d, sizeofInt32, private, 0, ^uint32(0))
- defer m.WaitComplete(w4)
-
- // Perform 10 wakeups on address 0 (unconditionally), and 10
- // wakeups on address sizeofInt32 (contingent on d.Op(1), which
- // should fail).
- if n, err := m.WakeOp(d, 0, sizeofInt32, private, 10, 10, 1); err != nil || n != 2 {
- t.Errorf("WakeOp: got (%d, %v), wanted (2, nil)", n, err)
- }
-
- // Expect that only the first two waiters were woken.
- if !w1.woken() {
- t.Error("w1 not woken")
- }
- if !w2.woken() {
- t.Error("w2 not woken")
- }
- if w3.woken() {
- t.Error("w3 woken unexpectedly")
- }
- if w4.woken() {
- t.Error("w4 woken unexpectedly")
- }
- })
- }
-}
-
-func TestWakeOpSameAddress(t *testing.T) {
- for _, private := range []bool{false, true} {
- t.Run(futexKind(private), func(t *testing.T) {
- m := NewManager()
- d := newTestData(8)
-
- // Add four waiters on address 0.
- var ws [4]*Waiter
- for i := range ws {
- ws[i] = newPreparedTestWaiter(t, m, d, 0, private, 0, ^uint32(0))
- defer m.WaitComplete(ws[i])
- }
-
- // Perform 1 wakeup on address 0 (unconditionally), and 1 wakeup
- // on address 0 (contingent on d.Op(0), which should succeed).
- const wakeups = 2
- if n, err := m.WakeOp(d, 0, 0, private, 1, 1, 0); err != nil || n != wakeups {
- t.Errorf("WakeOp: got (%d, %v), wanted (%d, nil)", n, err, wakeups)
- }
-
- // Expect that exactly two waiters were woken.
- awake := 0
- for i := range ws {
- if ws[i].woken() {
- awake++
- }
- }
- if awake != wakeups {
- t.Errorf("got %d woken waiters, wanted %d", awake, wakeups)
- }
- })
- }
-}
-
-func TestWakeOpSameAddressFailingOp(t *testing.T) {
- for _, private := range []bool{false, true} {
- t.Run(futexKind(private), func(t *testing.T) {
- m := NewManager()
- d := newTestData(8)
-
- // Add four waiters on address 0.
- var ws [4]*Waiter
- for i := range ws {
- ws[i] = newPreparedTestWaiter(t, m, d, 0, private, 0, ^uint32(0))
- defer m.WaitComplete(ws[i])
- }
-
- // Perform 1 wakeup on address 0 (unconditionally), and 1 wakeup
- // on address 0 (contingent on d.Op(1), which should fail).
- const wakeups = 1
- if n, err := m.WakeOp(d, 0, 0, private, 1, 1, 1); err != nil || n != wakeups {
- t.Errorf("WakeOp: got (%d, %v), wanted (%d, nil)", n, err, wakeups)
- }
-
- // Expect that exactly one waiter was woken.
- awake := 0
- for i := range ws {
- if ws[i].woken() {
- awake++
- }
- }
- if awake != wakeups {
- t.Errorf("got %d woken waiters, wanted %d", awake, wakeups)
- }
- })
- }
-}
-
-const (
- testMutexSize = sizeofInt32
- testMutexLocked uint32 = 1
- testMutexUnlocked uint32 = 0
-)
-
-// testMutex ties together a testData slice, an address, and a
-// futex manager in order to implement the sync.Locker interface.
-// Beyond being used as a Locker, this is a simple mechanism for
-// changing the underlying values for simpler tests.
-type testMutex struct {
- a usermem.Addr
- d testData
- m *Manager
-}
-
-func newTestMutex(addr usermem.Addr, d testData, m *Manager) *testMutex {
- return &testMutex{a: addr, d: d, m: m}
-}
-
-// Lock acquires the testMutex.
-// This may wait for it to be available via the futex manager.
-func (t *testMutex) Lock() {
- for {
- // Attempt to grab the lock.
- if atomic.CompareAndSwapUint32(
- (*uint32)(unsafe.Pointer(&t.d[t.a])),
- testMutexUnlocked,
- testMutexLocked) {
- // Lock held.
- return
- }
-
- // Wait for it to be "not locked".
- w := NewWaiter()
- err := t.m.WaitPrepare(w, t.d, t.a, true, testMutexLocked, ^uint32(0))
- if err == syscall.EAGAIN {
- continue
- }
- if err != nil {
- // Should never happen.
- panic("WaitPrepare returned unexpected error: " + err.Error())
- }
- <-w.C
- t.m.WaitComplete(w)
- }
-}
-
-// Unlock releases the testMutex.
-// This will notify any waiters via the futex manager.
-func (t *testMutex) Unlock() {
- // Unlock.
- atomic.StoreUint32((*uint32)(unsafe.Pointer(&t.d[t.a])), testMutexUnlocked)
-
- // Notify all waiters.
- t.m.Wake(t.d, t.a, true, ^uint32(0), math.MaxInt32)
-}
-
-// This function was shamelessly stolen from mutex_test.go.
-func HammerMutex(l sync.Locker, loops int, cdone chan bool) {
- for i := 0; i < loops; i++ {
- l.Lock()
- runtime.Gosched()
- l.Unlock()
- }
- cdone <- true
-}
-
-func TestMutexStress(t *testing.T) {
- m := NewManager()
- d := newTestData(testMutexSize)
- tm := newTestMutex(0*testMutexSize, d, m)
- c := make(chan bool)
-
- for i := 0; i < 10; i++ {
- go HammerMutex(tm, 1000, c)
- }
-
- for i := 0; i < 10; i++ {
- <-c
- }
-}
diff --git a/pkg/sentry/kernel/futex/waiter_list.go b/pkg/sentry/kernel/futex/waiter_list.go
new file mode 100755
index 000000000..cca5c4721
--- /dev/null
+++ b/pkg/sentry/kernel/futex/waiter_list.go
@@ -0,0 +1,173 @@
+package futex
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type waiterElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (waiterElementMapper) linkerFor(elem *Waiter) *Waiter { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type waiterList struct {
+ head *Waiter
+ tail *Waiter
+}
+
+// Reset resets list l to the empty state.
+func (l *waiterList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *waiterList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *waiterList) Front() *Waiter {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *waiterList) Back() *Waiter {
+ return l.tail
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *waiterList) PushFront(e *Waiter) {
+ waiterElementMapper{}.linkerFor(e).SetNext(l.head)
+ waiterElementMapper{}.linkerFor(e).SetPrev(nil)
+
+ if l.head != nil {
+ waiterElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *waiterList) PushBack(e *Waiter) {
+ waiterElementMapper{}.linkerFor(e).SetNext(nil)
+ waiterElementMapper{}.linkerFor(e).SetPrev(l.tail)
+
+ if l.tail != nil {
+ waiterElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *waiterList) PushBackList(m *waiterList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ waiterElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ waiterElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *waiterList) InsertAfter(b, e *Waiter) {
+ a := waiterElementMapper{}.linkerFor(b).Next()
+ waiterElementMapper{}.linkerFor(e).SetNext(a)
+ waiterElementMapper{}.linkerFor(e).SetPrev(b)
+ waiterElementMapper{}.linkerFor(b).SetNext(e)
+
+ if a != nil {
+ waiterElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *waiterList) InsertBefore(a, e *Waiter) {
+ b := waiterElementMapper{}.linkerFor(a).Prev()
+ waiterElementMapper{}.linkerFor(e).SetNext(a)
+ waiterElementMapper{}.linkerFor(e).SetPrev(b)
+ waiterElementMapper{}.linkerFor(a).SetPrev(e)
+
+ if b != nil {
+ waiterElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *waiterList) Remove(e *Waiter) {
+ prev := waiterElementMapper{}.linkerFor(e).Prev()
+ next := waiterElementMapper{}.linkerFor(e).Next()
+
+ if prev != nil {
+ waiterElementMapper{}.linkerFor(prev).SetNext(next)
+ } else {
+ l.head = next
+ }
+
+ if next != nil {
+ waiterElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else {
+ l.tail = prev
+ }
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type waiterEntry struct {
+ next *Waiter
+ prev *Waiter
+}
+
+// Next returns the entry that follows e in the list.
+func (e *waiterEntry) Next() *Waiter {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *waiterEntry) Prev() *Waiter {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *waiterEntry) SetNext(elem *Waiter) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *waiterEntry) SetPrev(elem *Waiter) {
+ e.prev = elem
+}
diff --git a/pkg/sentry/kernel/g3doc/run_states.dot b/pkg/sentry/kernel/g3doc/run_states.dot
deleted file mode 100644
index 7861fe1f5..000000000
--- a/pkg/sentry/kernel/g3doc/run_states.dot
+++ /dev/null
@@ -1,99 +0,0 @@
-digraph {
- subgraph {
- App;
- }
- subgraph {
- Interrupt;
- InterruptAfterSignalDeliveryStop;
- }
- subgraph {
- Syscall;
- SyscallAfterPtraceEventSeccomp;
- SyscallEnter;
- SyscallAfterSyscallEnterStop;
- SyscallAfterSysemuStop;
- SyscallInvoke;
- SyscallAfterPtraceEventClone;
- SyscallAfterExecStop;
- SyscallAfterVforkStop;
- SyscallReinvoke;
- SyscallExit;
- }
- subgraph {
- Vsyscall;
- VsyscallAfterPtraceEventSeccomp;
- VsyscallInvoke;
- }
- subgraph {
- Exit;
- ExitMain; // leave thread group, release resources, reparent children, kill PID namespace and wait if TGID 1
- ExitNotify; // signal parent/tracer, become waitable
- ExitDone; // represented by t.runState == nil
- }
-
- // Task exit
- Exit -> ExitMain;
- ExitMain -> ExitNotify;
- ExitNotify -> ExitDone;
-
- // Execution of untrusted application code
- App -> App;
-
- // Interrupts (usually signal delivery)
- App -> Interrupt;
- Interrupt -> Interrupt; // if other interrupt conditions may still apply
- Interrupt -> Exit; // if killed
-
- // Syscalls
- App -> Syscall;
- Syscall -> SyscallEnter;
- SyscallEnter -> SyscallInvoke;
- SyscallInvoke -> SyscallExit;
- SyscallExit -> App;
-
- // exit, exit_group
- SyscallInvoke -> Exit;
-
- // execve
- SyscallInvoke -> SyscallAfterExecStop;
- SyscallAfterExecStop -> SyscallExit;
- SyscallAfterExecStop -> App; // fatal signal pending
-
- // vfork
- SyscallInvoke -> SyscallAfterVforkStop;
- SyscallAfterVforkStop -> SyscallExit;
-
- // Vsyscalls
- App -> Vsyscall;
- Vsyscall -> VsyscallInvoke;
- Vsyscall -> App; // fault while reading return address from stack
- VsyscallInvoke -> App;
-
- // ptrace-specific branches
- Interrupt -> InterruptAfterSignalDeliveryStop;
- InterruptAfterSignalDeliveryStop -> Interrupt;
- SyscallEnter -> SyscallAfterSyscallEnterStop;
- SyscallAfterSyscallEnterStop -> SyscallInvoke;
- SyscallAfterSyscallEnterStop -> SyscallExit; // skipped by tracer
- SyscallAfterSyscallEnterStop -> App; // fatal signal pending
- SyscallEnter -> SyscallAfterSysemuStop;
- SyscallAfterSysemuStop -> SyscallExit;
- SyscallAfterSysemuStop -> App; // fatal signal pending
- SyscallInvoke -> SyscallAfterPtraceEventClone;
- SyscallAfterPtraceEventClone -> SyscallExit;
- SyscallAfterPtraceEventClone -> SyscallAfterVforkStop;
-
- // seccomp
- Syscall -> App; // SECCOMP_RET_TRAP, SECCOMP_RET_ERRNO, SECCOMP_RET_KILL, SECCOMP_RET_TRACE without tracer
- Syscall -> SyscallAfterPtraceEventSeccomp; // SECCOMP_RET_TRACE
- SyscallAfterPtraceEventSeccomp -> SyscallEnter;
- SyscallAfterPtraceEventSeccomp -> SyscallExit; // skipped by tracer
- SyscallAfterPtraceEventSeccomp -> App; // fatal signal pending
- Vsyscall -> VsyscallAfterPtraceEventSeccomp;
- VsyscallAfterPtraceEventSeccomp -> VsyscallInvoke;
- VsyscallAfterPtraceEventSeccomp -> App;
-
- // Autosave
- SyscallInvoke -> SyscallReinvoke;
- SyscallReinvoke -> SyscallInvoke;
-}
diff --git a/pkg/sentry/kernel/g3doc/run_states.png b/pkg/sentry/kernel/g3doc/run_states.png
deleted file mode 100644
index b63b60f02..000000000
--- a/pkg/sentry/kernel/g3doc/run_states.png
+++ /dev/null
Binary files differ
diff --git a/pkg/sentry/kernel/kernel_state_autogen.go b/pkg/sentry/kernel/kernel_state_autogen.go
new file mode 100755
index 000000000..bf909f2fc
--- /dev/null
+++ b/pkg/sentry/kernel/kernel_state_autogen.go
@@ -0,0 +1,1182 @@
+// automatically generated by stateify.
+
+package kernel
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+ "gvisor.dev/gvisor/pkg/bpf"
+ "gvisor.dev/gvisor/pkg/sentry/device"
+ "gvisor.dev/gvisor/pkg/tcpip"
+)
+
+func (x *abstractEndpoint) beforeSave() {}
+func (x *abstractEndpoint) save(m state.Map) {
+ x.beforeSave()
+ m.Save("ep", &x.ep)
+ m.Save("wr", &x.wr)
+ m.Save("name", &x.name)
+ m.Save("ns", &x.ns)
+}
+
+func (x *abstractEndpoint) afterLoad() {}
+func (x *abstractEndpoint) load(m state.Map) {
+ m.Load("ep", &x.ep)
+ m.Load("wr", &x.wr)
+ m.Load("name", &x.name)
+ m.Load("ns", &x.ns)
+}
+
+func (x *AbstractSocketNamespace) beforeSave() {}
+func (x *AbstractSocketNamespace) save(m state.Map) {
+ x.beforeSave()
+ m.Save("endpoints", &x.endpoints)
+}
+
+func (x *AbstractSocketNamespace) afterLoad() {}
+func (x *AbstractSocketNamespace) load(m state.Map) {
+ m.Load("endpoints", &x.endpoints)
+}
+
+func (x *FDFlags) beforeSave() {}
+func (x *FDFlags) save(m state.Map) {
+ x.beforeSave()
+ m.Save("CloseOnExec", &x.CloseOnExec)
+}
+
+func (x *FDFlags) afterLoad() {}
+func (x *FDFlags) load(m state.Map) {
+ m.Load("CloseOnExec", &x.CloseOnExec)
+}
+
+func (x *descriptor) beforeSave() {}
+func (x *descriptor) save(m state.Map) {
+ x.beforeSave()
+ m.Save("file", &x.file)
+ m.Save("flags", &x.flags)
+}
+
+func (x *descriptor) afterLoad() {}
+func (x *descriptor) load(m state.Map) {
+ m.Load("file", &x.file)
+ m.Load("flags", &x.flags)
+}
+
+func (x *FDTable) beforeSave() {}
+func (x *FDTable) save(m state.Map) {
+ x.beforeSave()
+ var descriptorTable map[int32]descriptor = x.saveDescriptorTable()
+ m.SaveValue("descriptorTable", descriptorTable)
+ m.Save("AtomicRefCount", &x.AtomicRefCount)
+ m.Save("k", &x.k)
+ m.Save("uid", &x.uid)
+ m.Save("used", &x.used)
+}
+
+func (x *FDTable) afterLoad() {}
+func (x *FDTable) load(m state.Map) {
+ m.Load("AtomicRefCount", &x.AtomicRefCount)
+ m.Load("k", &x.k)
+ m.Load("uid", &x.uid)
+ m.Load("used", &x.used)
+ m.LoadValue("descriptorTable", new(map[int32]descriptor), func(y interface{}) { x.loadDescriptorTable(y.(map[int32]descriptor)) })
+}
+
+func (x *FSContext) beforeSave() {}
+func (x *FSContext) save(m state.Map) {
+ x.beforeSave()
+ m.Save("AtomicRefCount", &x.AtomicRefCount)
+ m.Save("root", &x.root)
+ m.Save("cwd", &x.cwd)
+ m.Save("umask", &x.umask)
+}
+
+func (x *FSContext) afterLoad() {}
+func (x *FSContext) load(m state.Map) {
+ m.Load("AtomicRefCount", &x.AtomicRefCount)
+ m.Load("root", &x.root)
+ m.Load("cwd", &x.cwd)
+ m.Load("umask", &x.umask)
+}
+
+func (x *IPCNamespace) beforeSave() {}
+func (x *IPCNamespace) save(m state.Map) {
+ x.beforeSave()
+ m.Save("userNS", &x.userNS)
+ m.Save("semaphores", &x.semaphores)
+ m.Save("shms", &x.shms)
+}
+
+func (x *IPCNamespace) afterLoad() {}
+func (x *IPCNamespace) load(m state.Map) {
+ m.Load("userNS", &x.userNS)
+ m.Load("semaphores", &x.semaphores)
+ m.Load("shms", &x.shms)
+}
+
+func (x *Kernel) beforeSave() {}
+func (x *Kernel) save(m state.Map) {
+ x.beforeSave()
+ var danglingEndpoints []tcpip.Endpoint = x.saveDanglingEndpoints()
+ m.SaveValue("danglingEndpoints", danglingEndpoints)
+ var deviceRegistry *device.Registry = x.saveDeviceRegistry()
+ m.SaveValue("deviceRegistry", deviceRegistry)
+ m.Save("featureSet", &x.featureSet)
+ m.Save("timekeeper", &x.timekeeper)
+ m.Save("tasks", &x.tasks)
+ m.Save("rootUserNamespace", &x.rootUserNamespace)
+ m.Save("applicationCores", &x.applicationCores)
+ m.Save("useHostCores", &x.useHostCores)
+ m.Save("extraAuxv", &x.extraAuxv)
+ m.Save("vdso", &x.vdso)
+ m.Save("rootUTSNamespace", &x.rootUTSNamespace)
+ m.Save("rootIPCNamespace", &x.rootIPCNamespace)
+ m.Save("rootAbstractSocketNamespace", &x.rootAbstractSocketNamespace)
+ m.Save("futexes", &x.futexes)
+ m.Save("globalInit", &x.globalInit)
+ m.Save("realtimeClock", &x.realtimeClock)
+ m.Save("monotonicClock", &x.monotonicClock)
+ m.Save("syslog", &x.syslog)
+ m.Save("cpuClock", &x.cpuClock)
+ m.Save("fdMapUids", &x.fdMapUids)
+ m.Save("uniqueID", &x.uniqueID)
+ m.Save("nextInotifyCookie", &x.nextInotifyCookie)
+ m.Save("netlinkPorts", &x.netlinkPorts)
+ m.Save("sockets", &x.sockets)
+ m.Save("nextSocketEntry", &x.nextSocketEntry)
+ m.Save("DirentCacheLimiter", &x.DirentCacheLimiter)
+}
+
+func (x *Kernel) afterLoad() {}
+func (x *Kernel) load(m state.Map) {
+ m.Load("featureSet", &x.featureSet)
+ m.Load("timekeeper", &x.timekeeper)
+ m.Load("tasks", &x.tasks)
+ m.Load("rootUserNamespace", &x.rootUserNamespace)
+ m.Load("applicationCores", &x.applicationCores)
+ m.Load("useHostCores", &x.useHostCores)
+ m.Load("extraAuxv", &x.extraAuxv)
+ m.Load("vdso", &x.vdso)
+ m.Load("rootUTSNamespace", &x.rootUTSNamespace)
+ m.Load("rootIPCNamespace", &x.rootIPCNamespace)
+ m.Load("rootAbstractSocketNamespace", &x.rootAbstractSocketNamespace)
+ m.Load("futexes", &x.futexes)
+ m.Load("globalInit", &x.globalInit)
+ m.Load("realtimeClock", &x.realtimeClock)
+ m.Load("monotonicClock", &x.monotonicClock)
+ m.Load("syslog", &x.syslog)
+ m.Load("cpuClock", &x.cpuClock)
+ m.Load("fdMapUids", &x.fdMapUids)
+ m.Load("uniqueID", &x.uniqueID)
+ m.Load("nextInotifyCookie", &x.nextInotifyCookie)
+ m.Load("netlinkPorts", &x.netlinkPorts)
+ m.Load("sockets", &x.sockets)
+ m.Load("nextSocketEntry", &x.nextSocketEntry)
+ m.Load("DirentCacheLimiter", &x.DirentCacheLimiter)
+ m.LoadValue("danglingEndpoints", new([]tcpip.Endpoint), func(y interface{}) { x.loadDanglingEndpoints(y.([]tcpip.Endpoint)) })
+ m.LoadValue("deviceRegistry", new(*device.Registry), func(y interface{}) { x.loadDeviceRegistry(y.(*device.Registry)) })
+}
+
+func (x *SocketEntry) beforeSave() {}
+func (x *SocketEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("socketEntry", &x.socketEntry)
+ m.Save("k", &x.k)
+ m.Save("Sock", &x.Sock)
+ m.Save("ID", &x.ID)
+}
+
+func (x *SocketEntry) afterLoad() {}
+func (x *SocketEntry) load(m state.Map) {
+ m.Load("socketEntry", &x.socketEntry)
+ m.Load("k", &x.k)
+ m.Load("Sock", &x.Sock)
+ m.Load("ID", &x.ID)
+}
+
+func (x *pendingSignals) beforeSave() {}
+func (x *pendingSignals) save(m state.Map) {
+ x.beforeSave()
+ var signals []savedPendingSignal = x.saveSignals()
+ m.SaveValue("signals", signals)
+}
+
+func (x *pendingSignals) afterLoad() {}
+func (x *pendingSignals) load(m state.Map) {
+ m.LoadValue("signals", new([]savedPendingSignal), func(y interface{}) { x.loadSignals(y.([]savedPendingSignal)) })
+}
+
+func (x *pendingSignalQueue) beforeSave() {}
+func (x *pendingSignalQueue) save(m state.Map) {
+ x.beforeSave()
+ m.Save("pendingSignalList", &x.pendingSignalList)
+ m.Save("length", &x.length)
+}
+
+func (x *pendingSignalQueue) afterLoad() {}
+func (x *pendingSignalQueue) load(m state.Map) {
+ m.Load("pendingSignalList", &x.pendingSignalList)
+ m.Load("length", &x.length)
+}
+
+func (x *pendingSignal) beforeSave() {}
+func (x *pendingSignal) save(m state.Map) {
+ x.beforeSave()
+ m.Save("pendingSignalEntry", &x.pendingSignalEntry)
+ m.Save("SignalInfo", &x.SignalInfo)
+ m.Save("timer", &x.timer)
+}
+
+func (x *pendingSignal) afterLoad() {}
+func (x *pendingSignal) load(m state.Map) {
+ m.Load("pendingSignalEntry", &x.pendingSignalEntry)
+ m.Load("SignalInfo", &x.SignalInfo)
+ m.Load("timer", &x.timer)
+}
+
+func (x *pendingSignalList) beforeSave() {}
+func (x *pendingSignalList) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *pendingSignalList) afterLoad() {}
+func (x *pendingSignalList) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *pendingSignalEntry) beforeSave() {}
+func (x *pendingSignalEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *pendingSignalEntry) afterLoad() {}
+func (x *pendingSignalEntry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func (x *savedPendingSignal) beforeSave() {}
+func (x *savedPendingSignal) save(m state.Map) {
+ x.beforeSave()
+ m.Save("si", &x.si)
+ m.Save("timer", &x.timer)
+}
+
+func (x *savedPendingSignal) afterLoad() {}
+func (x *savedPendingSignal) load(m state.Map) {
+ m.Load("si", &x.si)
+ m.Load("timer", &x.timer)
+}
+
+func (x *IntervalTimer) beforeSave() {}
+func (x *IntervalTimer) save(m state.Map) {
+ x.beforeSave()
+ m.Save("timer", &x.timer)
+ m.Save("target", &x.target)
+ m.Save("signo", &x.signo)
+ m.Save("id", &x.id)
+ m.Save("sigval", &x.sigval)
+ m.Save("group", &x.group)
+ m.Save("sigpending", &x.sigpending)
+ m.Save("sigorphan", &x.sigorphan)
+ m.Save("overrunCur", &x.overrunCur)
+ m.Save("overrunLast", &x.overrunLast)
+}
+
+func (x *IntervalTimer) afterLoad() {}
+func (x *IntervalTimer) load(m state.Map) {
+ m.Load("timer", &x.timer)
+ m.Load("target", &x.target)
+ m.Load("signo", &x.signo)
+ m.Load("id", &x.id)
+ m.Load("sigval", &x.sigval)
+ m.Load("group", &x.group)
+ m.Load("sigpending", &x.sigpending)
+ m.Load("sigorphan", &x.sigorphan)
+ m.Load("overrunCur", &x.overrunCur)
+ m.Load("overrunLast", &x.overrunLast)
+}
+
+func (x *processGroupList) beforeSave() {}
+func (x *processGroupList) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *processGroupList) afterLoad() {}
+func (x *processGroupList) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *processGroupEntry) beforeSave() {}
+func (x *processGroupEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *processGroupEntry) afterLoad() {}
+func (x *processGroupEntry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func (x *ptraceOptions) beforeSave() {}
+func (x *ptraceOptions) save(m state.Map) {
+ x.beforeSave()
+ m.Save("ExitKill", &x.ExitKill)
+ m.Save("SysGood", &x.SysGood)
+ m.Save("TraceClone", &x.TraceClone)
+ m.Save("TraceExec", &x.TraceExec)
+ m.Save("TraceExit", &x.TraceExit)
+ m.Save("TraceFork", &x.TraceFork)
+ m.Save("TraceSeccomp", &x.TraceSeccomp)
+ m.Save("TraceVfork", &x.TraceVfork)
+ m.Save("TraceVforkDone", &x.TraceVforkDone)
+}
+
+func (x *ptraceOptions) afterLoad() {}
+func (x *ptraceOptions) load(m state.Map) {
+ m.Load("ExitKill", &x.ExitKill)
+ m.Load("SysGood", &x.SysGood)
+ m.Load("TraceClone", &x.TraceClone)
+ m.Load("TraceExec", &x.TraceExec)
+ m.Load("TraceExit", &x.TraceExit)
+ m.Load("TraceFork", &x.TraceFork)
+ m.Load("TraceSeccomp", &x.TraceSeccomp)
+ m.Load("TraceVfork", &x.TraceVfork)
+ m.Load("TraceVforkDone", &x.TraceVforkDone)
+}
+
+func (x *ptraceStop) beforeSave() {}
+func (x *ptraceStop) save(m state.Map) {
+ x.beforeSave()
+ m.Save("frozen", &x.frozen)
+ m.Save("listen", &x.listen)
+}
+
+func (x *ptraceStop) afterLoad() {}
+func (x *ptraceStop) load(m state.Map) {
+ m.Load("frozen", &x.frozen)
+ m.Load("listen", &x.listen)
+}
+
+func (x *RSEQCriticalRegion) beforeSave() {}
+func (x *RSEQCriticalRegion) save(m state.Map) {
+ x.beforeSave()
+ m.Save("CriticalSection", &x.CriticalSection)
+ m.Save("Restart", &x.Restart)
+}
+
+func (x *RSEQCriticalRegion) afterLoad() {}
+func (x *RSEQCriticalRegion) load(m state.Map) {
+ m.Load("CriticalSection", &x.CriticalSection)
+ m.Load("Restart", &x.Restart)
+}
+
+func (x *sessionList) beforeSave() {}
+func (x *sessionList) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *sessionList) afterLoad() {}
+func (x *sessionList) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *sessionEntry) beforeSave() {}
+func (x *sessionEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *sessionEntry) afterLoad() {}
+func (x *sessionEntry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func (x *Session) beforeSave() {}
+func (x *Session) save(m state.Map) {
+ x.beforeSave()
+ m.Save("refs", &x.refs)
+ m.Save("leader", &x.leader)
+ m.Save("id", &x.id)
+ m.Save("processGroups", &x.processGroups)
+ m.Save("sessionEntry", &x.sessionEntry)
+}
+
+func (x *Session) afterLoad() {}
+func (x *Session) load(m state.Map) {
+ m.Load("refs", &x.refs)
+ m.Load("leader", &x.leader)
+ m.Load("id", &x.id)
+ m.Load("processGroups", &x.processGroups)
+ m.Load("sessionEntry", &x.sessionEntry)
+}
+
+func (x *ProcessGroup) beforeSave() {}
+func (x *ProcessGroup) save(m state.Map) {
+ x.beforeSave()
+ m.Save("refs", &x.refs)
+ m.Save("originator", &x.originator)
+ m.Save("id", &x.id)
+ m.Save("session", &x.session)
+ m.Save("ancestors", &x.ancestors)
+ m.Save("processGroupEntry", &x.processGroupEntry)
+}
+
+func (x *ProcessGroup) afterLoad() {}
+func (x *ProcessGroup) load(m state.Map) {
+ m.Load("refs", &x.refs)
+ m.Load("originator", &x.originator)
+ m.Load("id", &x.id)
+ m.Load("session", &x.session)
+ m.Load("ancestors", &x.ancestors)
+ m.Load("processGroupEntry", &x.processGroupEntry)
+}
+
+func (x *SignalHandlers) beforeSave() {}
+func (x *SignalHandlers) save(m state.Map) {
+ x.beforeSave()
+ m.Save("actions", &x.actions)
+}
+
+func (x *SignalHandlers) afterLoad() {}
+func (x *SignalHandlers) load(m state.Map) {
+ m.Load("actions", &x.actions)
+}
+
+func (x *socketList) beforeSave() {}
+func (x *socketList) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *socketList) afterLoad() {}
+func (x *socketList) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *socketEntry) beforeSave() {}
+func (x *socketEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *socketEntry) afterLoad() {}
+func (x *socketEntry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func (x *SyscallTable) beforeSave() {}
+func (x *SyscallTable) save(m state.Map) {
+ x.beforeSave()
+ m.Save("OS", &x.OS)
+ m.Save("Arch", &x.Arch)
+}
+
+func (x *SyscallTable) load(m state.Map) {
+ m.LoadWait("OS", &x.OS)
+ m.LoadWait("Arch", &x.Arch)
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *syslog) beforeSave() {}
+func (x *syslog) save(m state.Map) {
+ x.beforeSave()
+ m.Save("msg", &x.msg)
+}
+
+func (x *syslog) afterLoad() {}
+func (x *syslog) load(m state.Map) {
+ m.Load("msg", &x.msg)
+}
+
+func (x *Task) beforeSave() {}
+func (x *Task) save(m state.Map) {
+ x.beforeSave()
+ var ptraceTracer *Task = x.savePtraceTracer()
+ m.SaveValue("ptraceTracer", ptraceTracer)
+ var logPrefix string = x.saveLogPrefix()
+ m.SaveValue("logPrefix", logPrefix)
+ var syscallFilters []bpf.Program = x.saveSyscallFilters()
+ m.SaveValue("syscallFilters", syscallFilters)
+ m.Save("taskNode", &x.taskNode)
+ m.Save("runState", &x.runState)
+ m.Save("haveSyscallReturn", &x.haveSyscallReturn)
+ m.Save("gosched", &x.gosched)
+ m.Save("yieldCount", &x.yieldCount)
+ m.Save("pendingSignals", &x.pendingSignals)
+ m.Save("signalMask", &x.signalMask)
+ m.Save("realSignalMask", &x.realSignalMask)
+ m.Save("haveSavedSignalMask", &x.haveSavedSignalMask)
+ m.Save("savedSignalMask", &x.savedSignalMask)
+ m.Save("signalStack", &x.signalStack)
+ m.Save("groupStopPending", &x.groupStopPending)
+ m.Save("groupStopAcknowledged", &x.groupStopAcknowledged)
+ m.Save("trapStopPending", &x.trapStopPending)
+ m.Save("trapNotifyPending", &x.trapNotifyPending)
+ m.Save("stop", &x.stop)
+ m.Save("exitStatus", &x.exitStatus)
+ m.Save("syscallRestartBlock", &x.syscallRestartBlock)
+ m.Save("k", &x.k)
+ m.Save("containerID", &x.containerID)
+ m.Save("tc", &x.tc)
+ m.Save("fsContext", &x.fsContext)
+ m.Save("fdTable", &x.fdTable)
+ m.Save("vforkParent", &x.vforkParent)
+ m.Save("exitState", &x.exitState)
+ m.Save("exitTracerNotified", &x.exitTracerNotified)
+ m.Save("exitTracerAcked", &x.exitTracerAcked)
+ m.Save("exitParentNotified", &x.exitParentNotified)
+ m.Save("exitParentAcked", &x.exitParentAcked)
+ m.Save("ptraceTracees", &x.ptraceTracees)
+ m.Save("ptraceSeized", &x.ptraceSeized)
+ m.Save("ptraceOpts", &x.ptraceOpts)
+ m.Save("ptraceSyscallMode", &x.ptraceSyscallMode)
+ m.Save("ptraceSinglestep", &x.ptraceSinglestep)
+ m.Save("ptraceCode", &x.ptraceCode)
+ m.Save("ptraceSiginfo", &x.ptraceSiginfo)
+ m.Save("ptraceEventMsg", &x.ptraceEventMsg)
+ m.Save("ioUsage", &x.ioUsage)
+ m.Save("creds", &x.creds)
+ m.Save("utsns", &x.utsns)
+ m.Save("ipcns", &x.ipcns)
+ m.Save("abstractSockets", &x.abstractSockets)
+ m.Save("parentDeathSignal", &x.parentDeathSignal)
+ m.Save("cleartid", &x.cleartid)
+ m.Save("allowedCPUMask", &x.allowedCPUMask)
+ m.Save("cpu", &x.cpu)
+ m.Save("niceness", &x.niceness)
+ m.Save("numaPolicy", &x.numaPolicy)
+ m.Save("numaNodeMask", &x.numaNodeMask)
+ m.Save("netns", &x.netns)
+ m.Save("rseqCPUAddr", &x.rseqCPUAddr)
+ m.Save("rseqCPU", &x.rseqCPU)
+ m.Save("startTime", &x.startTime)
+}
+
+func (x *Task) load(m state.Map) {
+ m.Load("taskNode", &x.taskNode)
+ m.Load("runState", &x.runState)
+ m.Load("haveSyscallReturn", &x.haveSyscallReturn)
+ m.Load("gosched", &x.gosched)
+ m.Load("yieldCount", &x.yieldCount)
+ m.Load("pendingSignals", &x.pendingSignals)
+ m.Load("signalMask", &x.signalMask)
+ m.Load("realSignalMask", &x.realSignalMask)
+ m.Load("haveSavedSignalMask", &x.haveSavedSignalMask)
+ m.Load("savedSignalMask", &x.savedSignalMask)
+ m.Load("signalStack", &x.signalStack)
+ m.Load("groupStopPending", &x.groupStopPending)
+ m.Load("groupStopAcknowledged", &x.groupStopAcknowledged)
+ m.Load("trapStopPending", &x.trapStopPending)
+ m.Load("trapNotifyPending", &x.trapNotifyPending)
+ m.Load("stop", &x.stop)
+ m.Load("exitStatus", &x.exitStatus)
+ m.Load("syscallRestartBlock", &x.syscallRestartBlock)
+ m.Load("k", &x.k)
+ m.Load("containerID", &x.containerID)
+ m.Load("tc", &x.tc)
+ m.Load("fsContext", &x.fsContext)
+ m.Load("fdTable", &x.fdTable)
+ m.Load("vforkParent", &x.vforkParent)
+ m.Load("exitState", &x.exitState)
+ m.Load("exitTracerNotified", &x.exitTracerNotified)
+ m.Load("exitTracerAcked", &x.exitTracerAcked)
+ m.Load("exitParentNotified", &x.exitParentNotified)
+ m.Load("exitParentAcked", &x.exitParentAcked)
+ m.Load("ptraceTracees", &x.ptraceTracees)
+ m.Load("ptraceSeized", &x.ptraceSeized)
+ m.Load("ptraceOpts", &x.ptraceOpts)
+ m.Load("ptraceSyscallMode", &x.ptraceSyscallMode)
+ m.Load("ptraceSinglestep", &x.ptraceSinglestep)
+ m.Load("ptraceCode", &x.ptraceCode)
+ m.Load("ptraceSiginfo", &x.ptraceSiginfo)
+ m.Load("ptraceEventMsg", &x.ptraceEventMsg)
+ m.Load("ioUsage", &x.ioUsage)
+ m.Load("creds", &x.creds)
+ m.Load("utsns", &x.utsns)
+ m.Load("ipcns", &x.ipcns)
+ m.Load("abstractSockets", &x.abstractSockets)
+ m.Load("parentDeathSignal", &x.parentDeathSignal)
+ m.Load("cleartid", &x.cleartid)
+ m.Load("allowedCPUMask", &x.allowedCPUMask)
+ m.Load("cpu", &x.cpu)
+ m.Load("niceness", &x.niceness)
+ m.Load("numaPolicy", &x.numaPolicy)
+ m.Load("numaNodeMask", &x.numaNodeMask)
+ m.Load("netns", &x.netns)
+ m.Load("rseqCPUAddr", &x.rseqCPUAddr)
+ m.Load("rseqCPU", &x.rseqCPU)
+ m.Load("startTime", &x.startTime)
+ m.LoadValue("ptraceTracer", new(*Task), func(y interface{}) { x.loadPtraceTracer(y.(*Task)) })
+ m.LoadValue("logPrefix", new(string), func(y interface{}) { x.loadLogPrefix(y.(string)) })
+ m.LoadValue("syscallFilters", new([]bpf.Program), func(y interface{}) { x.loadSyscallFilters(y.([]bpf.Program)) })
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *runSyscallAfterPtraceEventClone) beforeSave() {}
+func (x *runSyscallAfterPtraceEventClone) save(m state.Map) {
+ x.beforeSave()
+ m.Save("vforkChild", &x.vforkChild)
+ m.Save("vforkChildTID", &x.vforkChildTID)
+}
+
+func (x *runSyscallAfterPtraceEventClone) afterLoad() {}
+func (x *runSyscallAfterPtraceEventClone) load(m state.Map) {
+ m.Load("vforkChild", &x.vforkChild)
+ m.Load("vforkChildTID", &x.vforkChildTID)
+}
+
+func (x *runSyscallAfterVforkStop) beforeSave() {}
+func (x *runSyscallAfterVforkStop) save(m state.Map) {
+ x.beforeSave()
+ m.Save("childTID", &x.childTID)
+}
+
+func (x *runSyscallAfterVforkStop) afterLoad() {}
+func (x *runSyscallAfterVforkStop) load(m state.Map) {
+ m.Load("childTID", &x.childTID)
+}
+
+func (x *vforkStop) beforeSave() {}
+func (x *vforkStop) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *vforkStop) afterLoad() {}
+func (x *vforkStop) load(m state.Map) {
+}
+
+func (x *TaskContext) beforeSave() {}
+func (x *TaskContext) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Name", &x.Name)
+ m.Save("Arch", &x.Arch)
+ m.Save("MemoryManager", &x.MemoryManager)
+ m.Save("fu", &x.fu)
+ m.Save("st", &x.st)
+}
+
+func (x *TaskContext) afterLoad() {}
+func (x *TaskContext) load(m state.Map) {
+ m.Load("Name", &x.Name)
+ m.Load("Arch", &x.Arch)
+ m.Load("MemoryManager", &x.MemoryManager)
+ m.Load("fu", &x.fu)
+ m.Load("st", &x.st)
+}
+
+func (x *execStop) beforeSave() {}
+func (x *execStop) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *execStop) afterLoad() {}
+func (x *execStop) load(m state.Map) {
+}
+
+func (x *runSyscallAfterExecStop) beforeSave() {}
+func (x *runSyscallAfterExecStop) save(m state.Map) {
+ x.beforeSave()
+ m.Save("tc", &x.tc)
+}
+
+func (x *runSyscallAfterExecStop) afterLoad() {}
+func (x *runSyscallAfterExecStop) load(m state.Map) {
+ m.Load("tc", &x.tc)
+}
+
+func (x *ExitStatus) beforeSave() {}
+func (x *ExitStatus) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Code", &x.Code)
+ m.Save("Signo", &x.Signo)
+}
+
+func (x *ExitStatus) afterLoad() {}
+func (x *ExitStatus) load(m state.Map) {
+ m.Load("Code", &x.Code)
+ m.Load("Signo", &x.Signo)
+}
+
+func (x *runExit) beforeSave() {}
+func (x *runExit) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *runExit) afterLoad() {}
+func (x *runExit) load(m state.Map) {
+}
+
+func (x *runExitMain) beforeSave() {}
+func (x *runExitMain) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *runExitMain) afterLoad() {}
+func (x *runExitMain) load(m state.Map) {
+}
+
+func (x *runExitNotify) beforeSave() {}
+func (x *runExitNotify) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *runExitNotify) afterLoad() {}
+func (x *runExitNotify) load(m state.Map) {
+}
+
+func (x *taskList) beforeSave() {}
+func (x *taskList) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *taskList) afterLoad() {}
+func (x *taskList) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *taskEntry) beforeSave() {}
+func (x *taskEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *taskEntry) afterLoad() {}
+func (x *taskEntry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func (x *runApp) beforeSave() {}
+func (x *runApp) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *runApp) afterLoad() {}
+func (x *runApp) load(m state.Map) {
+}
+
+func (x *TaskGoroutineSchedInfo) beforeSave() {}
+func (x *TaskGoroutineSchedInfo) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Timestamp", &x.Timestamp)
+ m.Save("State", &x.State)
+ m.Save("UserTicks", &x.UserTicks)
+ m.Save("SysTicks", &x.SysTicks)
+}
+
+func (x *TaskGoroutineSchedInfo) afterLoad() {}
+func (x *TaskGoroutineSchedInfo) load(m state.Map) {
+ m.Load("Timestamp", &x.Timestamp)
+ m.Load("State", &x.State)
+ m.Load("UserTicks", &x.UserTicks)
+ m.Load("SysTicks", &x.SysTicks)
+}
+
+func (x *taskClock) beforeSave() {}
+func (x *taskClock) save(m state.Map) {
+ x.beforeSave()
+ m.Save("t", &x.t)
+ m.Save("includeSys", &x.includeSys)
+}
+
+func (x *taskClock) afterLoad() {}
+func (x *taskClock) load(m state.Map) {
+ m.Load("t", &x.t)
+ m.Load("includeSys", &x.includeSys)
+}
+
+func (x *tgClock) beforeSave() {}
+func (x *tgClock) save(m state.Map) {
+ x.beforeSave()
+ m.Save("tg", &x.tg)
+ m.Save("includeSys", &x.includeSys)
+}
+
+func (x *tgClock) afterLoad() {}
+func (x *tgClock) load(m state.Map) {
+ m.Load("tg", &x.tg)
+ m.Load("includeSys", &x.includeSys)
+}
+
+func (x *groupStop) beforeSave() {}
+func (x *groupStop) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *groupStop) afterLoad() {}
+func (x *groupStop) load(m state.Map) {
+}
+
+func (x *runInterrupt) beforeSave() {}
+func (x *runInterrupt) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *runInterrupt) afterLoad() {}
+func (x *runInterrupt) load(m state.Map) {
+}
+
+func (x *runInterruptAfterSignalDeliveryStop) beforeSave() {}
+func (x *runInterruptAfterSignalDeliveryStop) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *runInterruptAfterSignalDeliveryStop) afterLoad() {}
+func (x *runInterruptAfterSignalDeliveryStop) load(m state.Map) {
+}
+
+func (x *runSyscallAfterSyscallEnterStop) beforeSave() {}
+func (x *runSyscallAfterSyscallEnterStop) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *runSyscallAfterSyscallEnterStop) afterLoad() {}
+func (x *runSyscallAfterSyscallEnterStop) load(m state.Map) {
+}
+
+func (x *runSyscallAfterSysemuStop) beforeSave() {}
+func (x *runSyscallAfterSysemuStop) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *runSyscallAfterSysemuStop) afterLoad() {}
+func (x *runSyscallAfterSysemuStop) load(m state.Map) {
+}
+
+func (x *runSyscallReinvoke) beforeSave() {}
+func (x *runSyscallReinvoke) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *runSyscallReinvoke) afterLoad() {}
+func (x *runSyscallReinvoke) load(m state.Map) {
+}
+
+func (x *runSyscallExit) beforeSave() {}
+func (x *runSyscallExit) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *runSyscallExit) afterLoad() {}
+func (x *runSyscallExit) load(m state.Map) {
+}
+
+func (x *ThreadGroup) beforeSave() {}
+func (x *ThreadGroup) save(m state.Map) {
+ x.beforeSave()
+ var rscr *RSEQCriticalRegion = x.saveRscr()
+ m.SaveValue("rscr", rscr)
+ m.Save("threadGroupNode", &x.threadGroupNode)
+ m.Save("signalHandlers", &x.signalHandlers)
+ m.Save("pendingSignals", &x.pendingSignals)
+ m.Save("groupStopDequeued", &x.groupStopDequeued)
+ m.Save("groupStopSignal", &x.groupStopSignal)
+ m.Save("groupStopPendingCount", &x.groupStopPendingCount)
+ m.Save("groupStopComplete", &x.groupStopComplete)
+ m.Save("groupStopWaitable", &x.groupStopWaitable)
+ m.Save("groupContNotify", &x.groupContNotify)
+ m.Save("groupContInterrupted", &x.groupContInterrupted)
+ m.Save("groupContWaitable", &x.groupContWaitable)
+ m.Save("exiting", &x.exiting)
+ m.Save("exitStatus", &x.exitStatus)
+ m.Save("terminationSignal", &x.terminationSignal)
+ m.Save("itimerRealTimer", &x.itimerRealTimer)
+ m.Save("itimerVirtSetting", &x.itimerVirtSetting)
+ m.Save("itimerProfSetting", &x.itimerProfSetting)
+ m.Save("rlimitCPUSoftSetting", &x.rlimitCPUSoftSetting)
+ m.Save("cpuTimersEnabled", &x.cpuTimersEnabled)
+ m.Save("timers", &x.timers)
+ m.Save("nextTimerID", &x.nextTimerID)
+ m.Save("exitedCPUStats", &x.exitedCPUStats)
+ m.Save("childCPUStats", &x.childCPUStats)
+ m.Save("ioUsage", &x.ioUsage)
+ m.Save("maxRSS", &x.maxRSS)
+ m.Save("childMaxRSS", &x.childMaxRSS)
+ m.Save("limits", &x.limits)
+ m.Save("processGroup", &x.processGroup)
+ m.Save("execed", &x.execed)
+ m.Save("mounts", &x.mounts)
+}
+
+func (x *ThreadGroup) afterLoad() {}
+func (x *ThreadGroup) load(m state.Map) {
+ m.Load("threadGroupNode", &x.threadGroupNode)
+ m.Load("signalHandlers", &x.signalHandlers)
+ m.Load("pendingSignals", &x.pendingSignals)
+ m.Load("groupStopDequeued", &x.groupStopDequeued)
+ m.Load("groupStopSignal", &x.groupStopSignal)
+ m.Load("groupStopPendingCount", &x.groupStopPendingCount)
+ m.Load("groupStopComplete", &x.groupStopComplete)
+ m.Load("groupStopWaitable", &x.groupStopWaitable)
+ m.Load("groupContNotify", &x.groupContNotify)
+ m.Load("groupContInterrupted", &x.groupContInterrupted)
+ m.Load("groupContWaitable", &x.groupContWaitable)
+ m.Load("exiting", &x.exiting)
+ m.Load("exitStatus", &x.exitStatus)
+ m.Load("terminationSignal", &x.terminationSignal)
+ m.Load("itimerRealTimer", &x.itimerRealTimer)
+ m.Load("itimerVirtSetting", &x.itimerVirtSetting)
+ m.Load("itimerProfSetting", &x.itimerProfSetting)
+ m.Load("rlimitCPUSoftSetting", &x.rlimitCPUSoftSetting)
+ m.Load("cpuTimersEnabled", &x.cpuTimersEnabled)
+ m.Load("timers", &x.timers)
+ m.Load("nextTimerID", &x.nextTimerID)
+ m.Load("exitedCPUStats", &x.exitedCPUStats)
+ m.Load("childCPUStats", &x.childCPUStats)
+ m.Load("ioUsage", &x.ioUsage)
+ m.Load("maxRSS", &x.maxRSS)
+ m.Load("childMaxRSS", &x.childMaxRSS)
+ m.Load("limits", &x.limits)
+ m.Load("processGroup", &x.processGroup)
+ m.Load("execed", &x.execed)
+ m.Load("mounts", &x.mounts)
+ m.LoadValue("rscr", new(*RSEQCriticalRegion), func(y interface{}) { x.loadRscr(y.(*RSEQCriticalRegion)) })
+}
+
+func (x *itimerRealListener) beforeSave() {}
+func (x *itimerRealListener) save(m state.Map) {
+ x.beforeSave()
+ m.Save("tg", &x.tg)
+}
+
+func (x *itimerRealListener) afterLoad() {}
+func (x *itimerRealListener) load(m state.Map) {
+ m.Load("tg", &x.tg)
+}
+
+func (x *TaskSet) beforeSave() {}
+func (x *TaskSet) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Root", &x.Root)
+ m.Save("sessions", &x.sessions)
+}
+
+func (x *TaskSet) afterLoad() {}
+func (x *TaskSet) load(m state.Map) {
+ m.Load("Root", &x.Root)
+ m.Load("sessions", &x.sessions)
+}
+
+func (x *PIDNamespace) beforeSave() {}
+func (x *PIDNamespace) save(m state.Map) {
+ x.beforeSave()
+ m.Save("owner", &x.owner)
+ m.Save("parent", &x.parent)
+ m.Save("userns", &x.userns)
+ m.Save("last", &x.last)
+ m.Save("tasks", &x.tasks)
+ m.Save("tids", &x.tids)
+ m.Save("tgids", &x.tgids)
+ m.Save("sessions", &x.sessions)
+ m.Save("sids", &x.sids)
+ m.Save("processGroups", &x.processGroups)
+ m.Save("pgids", &x.pgids)
+ m.Save("exiting", &x.exiting)
+}
+
+func (x *PIDNamespace) afterLoad() {}
+func (x *PIDNamespace) load(m state.Map) {
+ m.Load("owner", &x.owner)
+ m.Load("parent", &x.parent)
+ m.Load("userns", &x.userns)
+ m.Load("last", &x.last)
+ m.Load("tasks", &x.tasks)
+ m.Load("tids", &x.tids)
+ m.Load("tgids", &x.tgids)
+ m.Load("sessions", &x.sessions)
+ m.Load("sids", &x.sids)
+ m.Load("processGroups", &x.processGroups)
+ m.Load("pgids", &x.pgids)
+ m.Load("exiting", &x.exiting)
+}
+
+func (x *threadGroupNode) beforeSave() {}
+func (x *threadGroupNode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("pidns", &x.pidns)
+ m.Save("leader", &x.leader)
+ m.Save("execing", &x.execing)
+ m.Save("tasks", &x.tasks)
+ m.Save("tasksCount", &x.tasksCount)
+ m.Save("liveTasks", &x.liveTasks)
+ m.Save("activeTasks", &x.activeTasks)
+}
+
+func (x *threadGroupNode) afterLoad() {}
+func (x *threadGroupNode) load(m state.Map) {
+ m.Load("pidns", &x.pidns)
+ m.Load("leader", &x.leader)
+ m.Load("execing", &x.execing)
+ m.Load("tasks", &x.tasks)
+ m.Load("tasksCount", &x.tasksCount)
+ m.Load("liveTasks", &x.liveTasks)
+ m.Load("activeTasks", &x.activeTasks)
+}
+
+func (x *taskNode) beforeSave() {}
+func (x *taskNode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("tg", &x.tg)
+ m.Save("taskEntry", &x.taskEntry)
+ m.Save("parent", &x.parent)
+ m.Save("children", &x.children)
+ m.Save("childPIDNamespace", &x.childPIDNamespace)
+}
+
+func (x *taskNode) afterLoad() {}
+func (x *taskNode) load(m state.Map) {
+ m.LoadWait("tg", &x.tg)
+ m.Load("taskEntry", &x.taskEntry)
+ m.Load("parent", &x.parent)
+ m.Load("children", &x.children)
+ m.Load("childPIDNamespace", &x.childPIDNamespace)
+}
+
+func (x *Timekeeper) save(m state.Map) {
+ x.beforeSave()
+ m.Save("bootTime", &x.bootTime)
+ m.Save("saveMonotonic", &x.saveMonotonic)
+ m.Save("saveRealtime", &x.saveRealtime)
+ m.Save("params", &x.params)
+}
+
+func (x *Timekeeper) load(m state.Map) {
+ m.Load("bootTime", &x.bootTime)
+ m.Load("saveMonotonic", &x.saveMonotonic)
+ m.Load("saveRealtime", &x.saveRealtime)
+ m.Load("params", &x.params)
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *timekeeperClock) beforeSave() {}
+func (x *timekeeperClock) save(m state.Map) {
+ x.beforeSave()
+ m.Save("tk", &x.tk)
+ m.Save("c", &x.c)
+}
+
+func (x *timekeeperClock) afterLoad() {}
+func (x *timekeeperClock) load(m state.Map) {
+ m.Load("tk", &x.tk)
+ m.Load("c", &x.c)
+}
+
+func (x *UTSNamespace) beforeSave() {}
+func (x *UTSNamespace) save(m state.Map) {
+ x.beforeSave()
+ m.Save("hostName", &x.hostName)
+ m.Save("domainName", &x.domainName)
+ m.Save("userns", &x.userns)
+}
+
+func (x *UTSNamespace) afterLoad() {}
+func (x *UTSNamespace) load(m state.Map) {
+ m.Load("hostName", &x.hostName)
+ m.Load("domainName", &x.domainName)
+ m.Load("userns", &x.userns)
+}
+
+func (x *VDSOParamPage) beforeSave() {}
+func (x *VDSOParamPage) save(m state.Map) {
+ x.beforeSave()
+ m.Save("mfp", &x.mfp)
+ m.Save("fr", &x.fr)
+ m.Save("seq", &x.seq)
+}
+
+func (x *VDSOParamPage) afterLoad() {}
+func (x *VDSOParamPage) load(m state.Map) {
+ m.Load("mfp", &x.mfp)
+ m.Load("fr", &x.fr)
+ m.Load("seq", &x.seq)
+}
+
+func init() {
+ state.Register("kernel.abstractEndpoint", (*abstractEndpoint)(nil), state.Fns{Save: (*abstractEndpoint).save, Load: (*abstractEndpoint).load})
+ state.Register("kernel.AbstractSocketNamespace", (*AbstractSocketNamespace)(nil), state.Fns{Save: (*AbstractSocketNamespace).save, Load: (*AbstractSocketNamespace).load})
+ state.Register("kernel.FDFlags", (*FDFlags)(nil), state.Fns{Save: (*FDFlags).save, Load: (*FDFlags).load})
+ state.Register("kernel.descriptor", (*descriptor)(nil), state.Fns{Save: (*descriptor).save, Load: (*descriptor).load})
+ state.Register("kernel.FDTable", (*FDTable)(nil), state.Fns{Save: (*FDTable).save, Load: (*FDTable).load})
+ state.Register("kernel.FSContext", (*FSContext)(nil), state.Fns{Save: (*FSContext).save, Load: (*FSContext).load})
+ state.Register("kernel.IPCNamespace", (*IPCNamespace)(nil), state.Fns{Save: (*IPCNamespace).save, Load: (*IPCNamespace).load})
+ state.Register("kernel.Kernel", (*Kernel)(nil), state.Fns{Save: (*Kernel).save, Load: (*Kernel).load})
+ state.Register("kernel.SocketEntry", (*SocketEntry)(nil), state.Fns{Save: (*SocketEntry).save, Load: (*SocketEntry).load})
+ state.Register("kernel.pendingSignals", (*pendingSignals)(nil), state.Fns{Save: (*pendingSignals).save, Load: (*pendingSignals).load})
+ state.Register("kernel.pendingSignalQueue", (*pendingSignalQueue)(nil), state.Fns{Save: (*pendingSignalQueue).save, Load: (*pendingSignalQueue).load})
+ state.Register("kernel.pendingSignal", (*pendingSignal)(nil), state.Fns{Save: (*pendingSignal).save, Load: (*pendingSignal).load})
+ state.Register("kernel.pendingSignalList", (*pendingSignalList)(nil), state.Fns{Save: (*pendingSignalList).save, Load: (*pendingSignalList).load})
+ state.Register("kernel.pendingSignalEntry", (*pendingSignalEntry)(nil), state.Fns{Save: (*pendingSignalEntry).save, Load: (*pendingSignalEntry).load})
+ state.Register("kernel.savedPendingSignal", (*savedPendingSignal)(nil), state.Fns{Save: (*savedPendingSignal).save, Load: (*savedPendingSignal).load})
+ state.Register("kernel.IntervalTimer", (*IntervalTimer)(nil), state.Fns{Save: (*IntervalTimer).save, Load: (*IntervalTimer).load})
+ state.Register("kernel.processGroupList", (*processGroupList)(nil), state.Fns{Save: (*processGroupList).save, Load: (*processGroupList).load})
+ state.Register("kernel.processGroupEntry", (*processGroupEntry)(nil), state.Fns{Save: (*processGroupEntry).save, Load: (*processGroupEntry).load})
+ state.Register("kernel.ptraceOptions", (*ptraceOptions)(nil), state.Fns{Save: (*ptraceOptions).save, Load: (*ptraceOptions).load})
+ state.Register("kernel.ptraceStop", (*ptraceStop)(nil), state.Fns{Save: (*ptraceStop).save, Load: (*ptraceStop).load})
+ state.Register("kernel.RSEQCriticalRegion", (*RSEQCriticalRegion)(nil), state.Fns{Save: (*RSEQCriticalRegion).save, Load: (*RSEQCriticalRegion).load})
+ state.Register("kernel.sessionList", (*sessionList)(nil), state.Fns{Save: (*sessionList).save, Load: (*sessionList).load})
+ state.Register("kernel.sessionEntry", (*sessionEntry)(nil), state.Fns{Save: (*sessionEntry).save, Load: (*sessionEntry).load})
+ state.Register("kernel.Session", (*Session)(nil), state.Fns{Save: (*Session).save, Load: (*Session).load})
+ state.Register("kernel.ProcessGroup", (*ProcessGroup)(nil), state.Fns{Save: (*ProcessGroup).save, Load: (*ProcessGroup).load})
+ state.Register("kernel.SignalHandlers", (*SignalHandlers)(nil), state.Fns{Save: (*SignalHandlers).save, Load: (*SignalHandlers).load})
+ state.Register("kernel.socketList", (*socketList)(nil), state.Fns{Save: (*socketList).save, Load: (*socketList).load})
+ state.Register("kernel.socketEntry", (*socketEntry)(nil), state.Fns{Save: (*socketEntry).save, Load: (*socketEntry).load})
+ state.Register("kernel.SyscallTable", (*SyscallTable)(nil), state.Fns{Save: (*SyscallTable).save, Load: (*SyscallTable).load})
+ state.Register("kernel.syslog", (*syslog)(nil), state.Fns{Save: (*syslog).save, Load: (*syslog).load})
+ state.Register("kernel.Task", (*Task)(nil), state.Fns{Save: (*Task).save, Load: (*Task).load})
+ state.Register("kernel.runSyscallAfterPtraceEventClone", (*runSyscallAfterPtraceEventClone)(nil), state.Fns{Save: (*runSyscallAfterPtraceEventClone).save, Load: (*runSyscallAfterPtraceEventClone).load})
+ state.Register("kernel.runSyscallAfterVforkStop", (*runSyscallAfterVforkStop)(nil), state.Fns{Save: (*runSyscallAfterVforkStop).save, Load: (*runSyscallAfterVforkStop).load})
+ state.Register("kernel.vforkStop", (*vforkStop)(nil), state.Fns{Save: (*vforkStop).save, Load: (*vforkStop).load})
+ state.Register("kernel.TaskContext", (*TaskContext)(nil), state.Fns{Save: (*TaskContext).save, Load: (*TaskContext).load})
+ state.Register("kernel.execStop", (*execStop)(nil), state.Fns{Save: (*execStop).save, Load: (*execStop).load})
+ state.Register("kernel.runSyscallAfterExecStop", (*runSyscallAfterExecStop)(nil), state.Fns{Save: (*runSyscallAfterExecStop).save, Load: (*runSyscallAfterExecStop).load})
+ state.Register("kernel.ExitStatus", (*ExitStatus)(nil), state.Fns{Save: (*ExitStatus).save, Load: (*ExitStatus).load})
+ state.Register("kernel.runExit", (*runExit)(nil), state.Fns{Save: (*runExit).save, Load: (*runExit).load})
+ state.Register("kernel.runExitMain", (*runExitMain)(nil), state.Fns{Save: (*runExitMain).save, Load: (*runExitMain).load})
+ state.Register("kernel.runExitNotify", (*runExitNotify)(nil), state.Fns{Save: (*runExitNotify).save, Load: (*runExitNotify).load})
+ state.Register("kernel.taskList", (*taskList)(nil), state.Fns{Save: (*taskList).save, Load: (*taskList).load})
+ state.Register("kernel.taskEntry", (*taskEntry)(nil), state.Fns{Save: (*taskEntry).save, Load: (*taskEntry).load})
+ state.Register("kernel.runApp", (*runApp)(nil), state.Fns{Save: (*runApp).save, Load: (*runApp).load})
+ state.Register("kernel.TaskGoroutineSchedInfo", (*TaskGoroutineSchedInfo)(nil), state.Fns{Save: (*TaskGoroutineSchedInfo).save, Load: (*TaskGoroutineSchedInfo).load})
+ state.Register("kernel.taskClock", (*taskClock)(nil), state.Fns{Save: (*taskClock).save, Load: (*taskClock).load})
+ state.Register("kernel.tgClock", (*tgClock)(nil), state.Fns{Save: (*tgClock).save, Load: (*tgClock).load})
+ state.Register("kernel.groupStop", (*groupStop)(nil), state.Fns{Save: (*groupStop).save, Load: (*groupStop).load})
+ state.Register("kernel.runInterrupt", (*runInterrupt)(nil), state.Fns{Save: (*runInterrupt).save, Load: (*runInterrupt).load})
+ state.Register("kernel.runInterruptAfterSignalDeliveryStop", (*runInterruptAfterSignalDeliveryStop)(nil), state.Fns{Save: (*runInterruptAfterSignalDeliveryStop).save, Load: (*runInterruptAfterSignalDeliveryStop).load})
+ state.Register("kernel.runSyscallAfterSyscallEnterStop", (*runSyscallAfterSyscallEnterStop)(nil), state.Fns{Save: (*runSyscallAfterSyscallEnterStop).save, Load: (*runSyscallAfterSyscallEnterStop).load})
+ state.Register("kernel.runSyscallAfterSysemuStop", (*runSyscallAfterSysemuStop)(nil), state.Fns{Save: (*runSyscallAfterSysemuStop).save, Load: (*runSyscallAfterSysemuStop).load})
+ state.Register("kernel.runSyscallReinvoke", (*runSyscallReinvoke)(nil), state.Fns{Save: (*runSyscallReinvoke).save, Load: (*runSyscallReinvoke).load})
+ state.Register("kernel.runSyscallExit", (*runSyscallExit)(nil), state.Fns{Save: (*runSyscallExit).save, Load: (*runSyscallExit).load})
+ state.Register("kernel.ThreadGroup", (*ThreadGroup)(nil), state.Fns{Save: (*ThreadGroup).save, Load: (*ThreadGroup).load})
+ state.Register("kernel.itimerRealListener", (*itimerRealListener)(nil), state.Fns{Save: (*itimerRealListener).save, Load: (*itimerRealListener).load})
+ state.Register("kernel.TaskSet", (*TaskSet)(nil), state.Fns{Save: (*TaskSet).save, Load: (*TaskSet).load})
+ state.Register("kernel.PIDNamespace", (*PIDNamespace)(nil), state.Fns{Save: (*PIDNamespace).save, Load: (*PIDNamespace).load})
+ state.Register("kernel.threadGroupNode", (*threadGroupNode)(nil), state.Fns{Save: (*threadGroupNode).save, Load: (*threadGroupNode).load})
+ state.Register("kernel.taskNode", (*taskNode)(nil), state.Fns{Save: (*taskNode).save, Load: (*taskNode).load})
+ state.Register("kernel.Timekeeper", (*Timekeeper)(nil), state.Fns{Save: (*Timekeeper).save, Load: (*Timekeeper).load})
+ state.Register("kernel.timekeeperClock", (*timekeeperClock)(nil), state.Fns{Save: (*timekeeperClock).save, Load: (*timekeeperClock).load})
+ state.Register("kernel.UTSNamespace", (*UTSNamespace)(nil), state.Fns{Save: (*UTSNamespace).save, Load: (*UTSNamespace).load})
+ state.Register("kernel.VDSOParamPage", (*VDSOParamPage)(nil), state.Fns{Save: (*VDSOParamPage).save, Load: (*VDSOParamPage).load})
+}
diff --git a/pkg/sentry/kernel/memevent/BUILD b/pkg/sentry/kernel/memevent/BUILD
deleted file mode 100644
index ebcfaa619..000000000
--- a/pkg/sentry/kernel/memevent/BUILD
+++ /dev/null
@@ -1,32 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "memevent",
- srcs = ["memory_events.go"],
- importpath = "gvisor.dev/gvisor/pkg/sentry/kernel/memevent",
- visibility = ["//:sandbox"],
- deps = [
- ":memory_events_go_proto",
- "//pkg/eventchannel",
- "//pkg/log",
- "//pkg/metric",
- "//pkg/sentry/kernel",
- "//pkg/sentry/usage",
- ],
-)
-
-proto_library(
- name = "memory_events_proto",
- srcs = ["memory_events.proto"],
- visibility = ["//visibility:public"],
-)
-
-go_proto_library(
- name = "memory_events_go_proto",
- importpath = "gvisor.dev/gvisor/pkg/sentry/kernel/memevent/memory_events_go_proto",
- proto = ":memory_events_proto",
- visibility = ["//visibility:public"],
-)
diff --git a/pkg/sentry/kernel/memevent/memory_events.go b/pkg/sentry/kernel/memevent/memory_events.go
deleted file mode 100644
index b0d98e7f0..000000000
--- a/pkg/sentry/kernel/memevent/memory_events.go
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package memevent implements the memory usage events controller, which
-// periodically emits events via the eventchannel.
-package memevent
-
-import (
- "sync"
- "time"
-
- "gvisor.dev/gvisor/pkg/eventchannel"
- "gvisor.dev/gvisor/pkg/log"
- "gvisor.dev/gvisor/pkg/metric"
- "gvisor.dev/gvisor/pkg/sentry/kernel"
- pb "gvisor.dev/gvisor/pkg/sentry/kernel/memevent/memory_events_go_proto"
- "gvisor.dev/gvisor/pkg/sentry/usage"
-)
-
-var totalTicks = metric.MustCreateNewUint64Metric("/memory_events/ticks", false /*sync*/, "Total number of memory event periods that have elapsed since startup.")
-var totalEvents = metric.MustCreateNewUint64Metric("/memory_events/events", false /*sync*/, "Total number of memory events emitted.")
-
-// MemoryEvents describes the configuration for the global memory event emitter.
-type MemoryEvents struct {
- k *kernel.Kernel
-
- // The period is how often to emit an event. The memory events goroutine
- // will ensure a minimum of one event is emitted per this period, regardless
- // how of much memory usage has changed.
- period time.Duration
-
- // Writing to this channel indicates the memory goroutine should stop.
- stop chan struct{}
-
- // done is used to signal when the memory event goroutine has exited.
- done sync.WaitGroup
-}
-
-// New creates a new MemoryEvents.
-func New(k *kernel.Kernel, period time.Duration) *MemoryEvents {
- return &MemoryEvents{
- k: k,
- period: period,
- stop: make(chan struct{}),
- }
-}
-
-// Stop stops the memory usage events emitter goroutine. Stop must not be called
-// concurrently with Start and may only be called once.
-func (m *MemoryEvents) Stop() {
- close(m.stop)
- m.done.Wait()
-}
-
-// Start starts the memory usage events emitter goroutine. Start must not be
-// called concurrently with Stop and may only be called once.
-func (m *MemoryEvents) Start() {
- if m.period == 0 {
- return
- }
- m.done.Add(1)
- go m.run() // S/R-SAFE: doesn't interact with saved state.
-}
-
-func (m *MemoryEvents) run() {
- defer m.done.Done()
-
- // Emit the first event immediately on startup.
- totalTicks.Increment()
- m.emit()
-
- ticker := time.NewTicker(m.period)
- defer ticker.Stop()
-
- for {
- select {
- case <-m.stop:
- return
- case <-ticker.C:
- totalTicks.Increment()
- m.emit()
- }
- }
-}
-
-func (m *MemoryEvents) emit() {
- totalPlatform, err := m.k.MemoryFile().TotalUsage()
- if err != nil {
- log.Warningf("Failed to fetch memory usage for memory events: %v", err)
- return
- }
- snapshot, _ := usage.MemoryAccounting.Copy()
- total := totalPlatform + snapshot.Mapped
-
- totalEvents.Increment()
- eventchannel.Emit(&pb.MemoryUsageEvent{
- Mapped: snapshot.Mapped,
- Total: total,
- })
-}
diff --git a/pkg/sentry/kernel/memevent/memory_events.proto b/pkg/sentry/kernel/memevent/memory_events.proto
deleted file mode 100644
index bf8029ff5..000000000
--- a/pkg/sentry/kernel/memevent/memory_events.proto
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto3";
-
-package gvisor;
-
-// MemoryUsageEvent describes the memory usage of the sandbox at a single
-// instant in time. These messages are emitted periodically on the eventchannel.
-message MemoryUsageEvent {
- // The total memory usage of the sandboxed application in bytes, calculated
- // using the 'fast' method.
- uint64 total = 1;
-
- // Memory used to back memory-mapped regions for files in the application, in
- // bytes. This corresponds to the usage.MemoryKind.Mapped memory type.
- uint64 mapped = 2;
-}
diff --git a/pkg/sentry/kernel/pending_signals_list.go b/pkg/sentry/kernel/pending_signals_list.go
new file mode 100755
index 000000000..a3499371a
--- /dev/null
+++ b/pkg/sentry/kernel/pending_signals_list.go
@@ -0,0 +1,173 @@
+package kernel
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type pendingSignalElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (pendingSignalElementMapper) linkerFor(elem *pendingSignal) *pendingSignal { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type pendingSignalList struct {
+ head *pendingSignal
+ tail *pendingSignal
+}
+
+// Reset resets list l to the empty state.
+func (l *pendingSignalList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *pendingSignalList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *pendingSignalList) Front() *pendingSignal {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *pendingSignalList) Back() *pendingSignal {
+ return l.tail
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *pendingSignalList) PushFront(e *pendingSignal) {
+ pendingSignalElementMapper{}.linkerFor(e).SetNext(l.head)
+ pendingSignalElementMapper{}.linkerFor(e).SetPrev(nil)
+
+ if l.head != nil {
+ pendingSignalElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *pendingSignalList) PushBack(e *pendingSignal) {
+ pendingSignalElementMapper{}.linkerFor(e).SetNext(nil)
+ pendingSignalElementMapper{}.linkerFor(e).SetPrev(l.tail)
+
+ if l.tail != nil {
+ pendingSignalElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *pendingSignalList) PushBackList(m *pendingSignalList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ pendingSignalElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ pendingSignalElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *pendingSignalList) InsertAfter(b, e *pendingSignal) {
+ a := pendingSignalElementMapper{}.linkerFor(b).Next()
+ pendingSignalElementMapper{}.linkerFor(e).SetNext(a)
+ pendingSignalElementMapper{}.linkerFor(e).SetPrev(b)
+ pendingSignalElementMapper{}.linkerFor(b).SetNext(e)
+
+ if a != nil {
+ pendingSignalElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *pendingSignalList) InsertBefore(a, e *pendingSignal) {
+ b := pendingSignalElementMapper{}.linkerFor(a).Prev()
+ pendingSignalElementMapper{}.linkerFor(e).SetNext(a)
+ pendingSignalElementMapper{}.linkerFor(e).SetPrev(b)
+ pendingSignalElementMapper{}.linkerFor(a).SetPrev(e)
+
+ if b != nil {
+ pendingSignalElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *pendingSignalList) Remove(e *pendingSignal) {
+ prev := pendingSignalElementMapper{}.linkerFor(e).Prev()
+ next := pendingSignalElementMapper{}.linkerFor(e).Next()
+
+ if prev != nil {
+ pendingSignalElementMapper{}.linkerFor(prev).SetNext(next)
+ } else {
+ l.head = next
+ }
+
+ if next != nil {
+ pendingSignalElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else {
+ l.tail = prev
+ }
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type pendingSignalEntry struct {
+ next *pendingSignal
+ prev *pendingSignal
+}
+
+// Next returns the entry that follows e in the list.
+func (e *pendingSignalEntry) Next() *pendingSignal {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *pendingSignalEntry) Prev() *pendingSignal {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *pendingSignalEntry) SetNext(elem *pendingSignal) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *pendingSignalEntry) SetPrev(elem *pendingSignal) {
+ e.prev = elem
+}
diff --git a/pkg/sentry/kernel/pipe/BUILD b/pkg/sentry/kernel/pipe/BUILD
deleted file mode 100644
index 2ce8952e2..000000000
--- a/pkg/sentry/kernel/pipe/BUILD
+++ /dev/null
@@ -1,66 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_template_instance(
- name = "buffer_list",
- out = "buffer_list.go",
- package = "pipe",
- prefix = "buffer",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*buffer",
- "Linker": "*buffer",
- },
-)
-
-go_library(
- name = "pipe",
- srcs = [
- "buffer.go",
- "buffer_list.go",
- "device.go",
- "node.go",
- "pipe.go",
- "reader.go",
- "reader_writer.go",
- "writer.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/kernel/pipe",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/amutex",
- "//pkg/sentry/arch",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/safemem",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "pipe_test",
- size = "small",
- srcs = [
- "buffer_test.go",
- "node_test.go",
- "pipe_test.go",
- ],
- embed = [":pipe"],
- deps = [
- "//pkg/sentry/context",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/fs",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/kernel/pipe/buffer_list.go b/pkg/sentry/kernel/pipe/buffer_list.go
new file mode 100755
index 000000000..42ec78788
--- /dev/null
+++ b/pkg/sentry/kernel/pipe/buffer_list.go
@@ -0,0 +1,173 @@
+package pipe
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type bufferElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (bufferElementMapper) linkerFor(elem *buffer) *buffer { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type bufferList struct {
+ head *buffer
+ tail *buffer
+}
+
+// Reset resets list l to the empty state.
+func (l *bufferList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *bufferList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *bufferList) Front() *buffer {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *bufferList) Back() *buffer {
+ return l.tail
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *bufferList) PushFront(e *buffer) {
+ bufferElementMapper{}.linkerFor(e).SetNext(l.head)
+ bufferElementMapper{}.linkerFor(e).SetPrev(nil)
+
+ if l.head != nil {
+ bufferElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *bufferList) PushBack(e *buffer) {
+ bufferElementMapper{}.linkerFor(e).SetNext(nil)
+ bufferElementMapper{}.linkerFor(e).SetPrev(l.tail)
+
+ if l.tail != nil {
+ bufferElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *bufferList) PushBackList(m *bufferList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ bufferElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ bufferElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *bufferList) InsertAfter(b, e *buffer) {
+ a := bufferElementMapper{}.linkerFor(b).Next()
+ bufferElementMapper{}.linkerFor(e).SetNext(a)
+ bufferElementMapper{}.linkerFor(e).SetPrev(b)
+ bufferElementMapper{}.linkerFor(b).SetNext(e)
+
+ if a != nil {
+ bufferElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *bufferList) InsertBefore(a, e *buffer) {
+ b := bufferElementMapper{}.linkerFor(a).Prev()
+ bufferElementMapper{}.linkerFor(e).SetNext(a)
+ bufferElementMapper{}.linkerFor(e).SetPrev(b)
+ bufferElementMapper{}.linkerFor(a).SetPrev(e)
+
+ if b != nil {
+ bufferElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *bufferList) Remove(e *buffer) {
+ prev := bufferElementMapper{}.linkerFor(e).Prev()
+ next := bufferElementMapper{}.linkerFor(e).Next()
+
+ if prev != nil {
+ bufferElementMapper{}.linkerFor(prev).SetNext(next)
+ } else {
+ l.head = next
+ }
+
+ if next != nil {
+ bufferElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else {
+ l.tail = prev
+ }
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type bufferEntry struct {
+ next *buffer
+ prev *buffer
+}
+
+// Next returns the entry that follows e in the list.
+func (e *bufferEntry) Next() *buffer {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *bufferEntry) Prev() *buffer {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *bufferEntry) SetNext(elem *buffer) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *bufferEntry) SetPrev(elem *buffer) {
+ e.prev = elem
+}
diff --git a/pkg/sentry/kernel/pipe/buffer_test.go b/pkg/sentry/kernel/pipe/buffer_test.go
deleted file mode 100644
index ee1b90115..000000000
--- a/pkg/sentry/kernel/pipe/buffer_test.go
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package pipe
-
-import (
- "testing"
- "unsafe"
-
- "gvisor.dev/gvisor/pkg/sentry/usermem"
-)
-
-func TestBufferSize(t *testing.T) {
- bufferSize := unsafe.Sizeof(buffer{})
- if bufferSize < usermem.PageSize {
- t.Errorf("buffer is less than a page")
- }
- if bufferSize > (2 * usermem.PageSize) {
- t.Errorf("buffer is greater than two pages")
- }
-}
diff --git a/pkg/sentry/kernel/pipe/node_test.go b/pkg/sentry/kernel/pipe/node_test.go
deleted file mode 100644
index adbad7764..000000000
--- a/pkg/sentry/kernel/pipe/node_test.go
+++ /dev/null
@@ -1,320 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package pipe
-
-import (
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-type sleeper struct {
- context.Context
- ch chan struct{}
-}
-
-func newSleeperContext(t *testing.T) context.Context {
- return &sleeper{
- Context: contexttest.Context(t),
- ch: make(chan struct{}),
- }
-}
-
-func (s *sleeper) SleepStart() <-chan struct{} {
- return s.ch
-}
-
-func (s *sleeper) SleepFinish(bool) {
-}
-
-func (s *sleeper) Cancel() {
- s.ch <- struct{}{}
-}
-
-func (s *sleeper) Interrupted() bool {
- return len(s.ch) != 0
-}
-
-type openResult struct {
- *fs.File
- error
-}
-
-var perms fs.FilePermissions = fs.FilePermissions{
- User: fs.PermMask{Read: true, Write: true},
-}
-
-func testOpenOrDie(ctx context.Context, t *testing.T, n fs.InodeOperations, flags fs.FileFlags, doneChan chan<- struct{}) (*fs.File, error) {
- inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{Type: fs.Pipe})
- d := fs.NewDirent(ctx, inode, "pipe")
- file, err := n.GetFile(ctx, d, flags)
- if err != nil {
- t.Fatalf("open with flags %+v failed: %v", flags, err)
- }
- if doneChan != nil {
- doneChan <- struct{}{}
- }
- return file, err
-}
-
-func testOpen(ctx context.Context, t *testing.T, n fs.InodeOperations, flags fs.FileFlags, resChan chan<- openResult) (*fs.File, error) {
- inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{Type: fs.Pipe})
- d := fs.NewDirent(ctx, inode, "pipe")
- file, err := n.GetFile(ctx, d, flags)
- if resChan != nil {
- resChan <- openResult{file, err}
- }
- return file, err
-}
-
-func newNamedPipe(t *testing.T) *Pipe {
- return NewPipe(contexttest.Context(t), true, DefaultPipeSize, usermem.PageSize)
-}
-
-func newAnonPipe(t *testing.T) *Pipe {
- return NewPipe(contexttest.Context(t), false, DefaultPipeSize, usermem.PageSize)
-}
-
-// assertRecvBlocks ensures that a recv attempt on c blocks for at least
-// blockDuration. This is useful for checking that a goroutine that is supposed
-// to be executing a blocking operation is actually blocking.
-func assertRecvBlocks(t *testing.T, c <-chan struct{}, blockDuration time.Duration, failMsg string) {
- select {
- case <-c:
- t.Fatalf(failMsg)
- case <-time.After(blockDuration):
- // Ok, blocked for the required duration.
- }
-}
-
-func TestReadOpenBlocksForWriteOpen(t *testing.T) {
- ctx := newSleeperContext(t)
- f := NewInodeOperations(ctx, perms, newNamedPipe(t))
-
- rDone := make(chan struct{})
- go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone)
-
- // Verify that the open for read is blocking.
- assertRecvBlocks(t, rDone, time.Millisecond*100,
- "open for read not blocking with no writers")
-
- wDone := make(chan struct{})
- go testOpenOrDie(ctx, t, f, fs.FileFlags{Write: true}, wDone)
-
- <-wDone
- <-rDone
-}
-
-func TestWriteOpenBlocksForReadOpen(t *testing.T) {
- ctx := newSleeperContext(t)
- f := NewInodeOperations(ctx, perms, newNamedPipe(t))
-
- wDone := make(chan struct{})
- go testOpenOrDie(ctx, t, f, fs.FileFlags{Write: true}, wDone)
-
- // Verify that the open for write is blocking
- assertRecvBlocks(t, wDone, time.Millisecond*100,
- "open for write not blocking with no readers")
-
- rDone := make(chan struct{})
- go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone)
-
- <-rDone
- <-wDone
-}
-
-func TestMultipleWriteOpenDoesntCountAsReadOpen(t *testing.T) {
- ctx := newSleeperContext(t)
- f := NewInodeOperations(ctx, perms, newNamedPipe(t))
-
- rDone1 := make(chan struct{})
- rDone2 := make(chan struct{})
- go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone1)
- go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone2)
-
- assertRecvBlocks(t, rDone1, time.Millisecond*100,
- "open for read didn't block with no writers")
- assertRecvBlocks(t, rDone2, time.Millisecond*100,
- "open for read didn't block with no writers")
-
- wDone := make(chan struct{})
- go testOpenOrDie(ctx, t, f, fs.FileFlags{Write: true}, wDone)
-
- <-wDone
- <-rDone2
- <-rDone1
-}
-
-func TestClosedReaderBlocksWriteOpen(t *testing.T) {
- ctx := newSleeperContext(t)
- f := NewInodeOperations(ctx, perms, newNamedPipe(t))
-
- rFile, _ := testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true, NonBlocking: true}, nil)
- rFile.DecRef()
-
- wDone := make(chan struct{})
- // This open for write should block because the reader is now gone.
- go testOpenOrDie(ctx, t, f, fs.FileFlags{Write: true}, wDone)
- assertRecvBlocks(t, wDone, time.Millisecond*100,
- "open for write didn't block with no concurrent readers")
-
- // Open for read again. This should unblock the open for write.
- rDone := make(chan struct{})
- go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone)
-
- <-rDone
- <-wDone
-}
-
-func TestReadWriteOpenNeverBlocks(t *testing.T) {
- ctx := newSleeperContext(t)
- f := NewInodeOperations(ctx, perms, newNamedPipe(t))
-
- rwDone := make(chan struct{})
- // Open for read-write never wait for a reader or writer, even if the
- // nonblocking flag is not set.
- go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true, Write: true, NonBlocking: false}, rwDone)
- <-rwDone
-}
-
-func TestReadWriteOpenUnblocksReadOpen(t *testing.T) {
- ctx := newSleeperContext(t)
- f := NewInodeOperations(ctx, perms, newNamedPipe(t))
-
- rDone := make(chan struct{})
- go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone)
-
- rwDone := make(chan struct{})
- go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true, Write: true}, rwDone)
-
- <-rwDone
- <-rDone
-}
-
-func TestReadWriteOpenUnblocksWriteOpen(t *testing.T) {
- ctx := newSleeperContext(t)
- f := NewInodeOperations(ctx, perms, newNamedPipe(t))
-
- wDone := make(chan struct{})
- go testOpenOrDie(ctx, t, f, fs.FileFlags{Write: true}, wDone)
-
- rwDone := make(chan struct{})
- go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true, Write: true}, rwDone)
-
- <-rwDone
- <-wDone
-}
-
-func TestBlockedOpenIsCancellable(t *testing.T) {
- ctx := newSleeperContext(t)
- f := NewInodeOperations(ctx, perms, newNamedPipe(t))
-
- done := make(chan openResult)
- go testOpen(ctx, t, f, fs.FileFlags{Read: true}, done)
- select {
- case <-done:
- t.Fatalf("open for read didn't block with no writers")
- case <-time.After(time.Millisecond * 100):
- // Ok.
- }
-
- ctx.(*sleeper).Cancel()
- // If the cancel on the sleeper didn't work, the open for read would never
- // return.
- res := <-done
- if res.error != syserror.ErrInterrupted {
- t.Fatalf("Cancellation didn't cause GetFile to return fs.ErrInterrupted, got %v.",
- res.error)
- }
-}
-
-func TestNonblockingReadOpenFileNoWriters(t *testing.T) {
- ctx := newSleeperContext(t)
- f := NewInodeOperations(ctx, perms, newNamedPipe(t))
-
- if _, err := testOpen(ctx, t, f, fs.FileFlags{Read: true, NonBlocking: true}, nil); err != nil {
- t.Fatalf("Nonblocking open for read failed with error %v.", err)
- }
-}
-
-func TestNonblockingWriteOpenFileNoReaders(t *testing.T) {
- ctx := newSleeperContext(t)
- f := NewInodeOperations(ctx, perms, newNamedPipe(t))
-
- if _, err := testOpen(ctx, t, f, fs.FileFlags{Write: true, NonBlocking: true}, nil); err != syserror.ENXIO {
- t.Fatalf("Nonblocking open for write failed unexpected error %v.", err)
- }
-}
-
-func TestNonBlockingReadOpenWithWriter(t *testing.T) {
- ctx := newSleeperContext(t)
- f := NewInodeOperations(ctx, perms, newNamedPipe(t))
-
- wDone := make(chan struct{})
- go testOpenOrDie(ctx, t, f, fs.FileFlags{Write: true}, wDone)
-
- // Open for write blocks since there are no readers yet.
- assertRecvBlocks(t, wDone, time.Millisecond*100,
- "Open for write didn't block with no reader.")
-
- if _, err := testOpen(ctx, t, f, fs.FileFlags{Read: true, NonBlocking: true}, nil); err != nil {
- t.Fatalf("Nonblocking open for read failed with error %v.", err)
- }
-
- // Open for write should now be unblocked.
- <-wDone
-}
-
-func TestNonBlockingWriteOpenWithReader(t *testing.T) {
- ctx := newSleeperContext(t)
- f := NewInodeOperations(ctx, perms, newNamedPipe(t))
-
- rDone := make(chan struct{})
- go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone)
-
- // Open for write blocked, since no reader yet.
- assertRecvBlocks(t, rDone, time.Millisecond*100,
- "Open for reader didn't block with no writer.")
-
- if _, err := testOpen(ctx, t, f, fs.FileFlags{Write: true, NonBlocking: true}, nil); err != nil {
- t.Fatalf("Nonblocking open for write failed with error %v.", err)
- }
-
- // Open for write should now be unblocked.
- <-rDone
-}
-
-func TestAnonReadOpen(t *testing.T) {
- ctx := newSleeperContext(t)
- f := NewInodeOperations(ctx, perms, newAnonPipe(t))
-
- if _, err := testOpen(ctx, t, f, fs.FileFlags{Read: true}, nil); err != nil {
- t.Fatalf("open anon pipe for read failed: %v", err)
- }
-}
-
-func TestAnonWriteOpen(t *testing.T) {
- ctx := newSleeperContext(t)
- f := NewInodeOperations(ctx, perms, newAnonPipe(t))
-
- if _, err := testOpen(ctx, t, f, fs.FileFlags{Write: true}, nil); err != nil {
- t.Fatalf("open anon pipe for write failed: %v", err)
- }
-}
diff --git a/pkg/sentry/kernel/pipe/pipe_state_autogen.go b/pkg/sentry/kernel/pipe/pipe_state_autogen.go
new file mode 100755
index 000000000..cad035789
--- /dev/null
+++ b/pkg/sentry/kernel/pipe/pipe_state_autogen.go
@@ -0,0 +1,132 @@
+// automatically generated by stateify.
+
+package pipe
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *buffer) beforeSave() {}
+func (x *buffer) save(m state.Map) {
+ x.beforeSave()
+ m.Save("data", &x.data)
+ m.Save("read", &x.read)
+ m.Save("write", &x.write)
+ m.Save("bufferEntry", &x.bufferEntry)
+}
+
+func (x *buffer) afterLoad() {}
+func (x *buffer) load(m state.Map) {
+ m.Load("data", &x.data)
+ m.Load("read", &x.read)
+ m.Load("write", &x.write)
+ m.Load("bufferEntry", &x.bufferEntry)
+}
+
+func (x *bufferList) beforeSave() {}
+func (x *bufferList) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *bufferList) afterLoad() {}
+func (x *bufferList) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *bufferEntry) beforeSave() {}
+func (x *bufferEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *bufferEntry) afterLoad() {}
+func (x *bufferEntry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func (x *inodeOperations) beforeSave() {}
+func (x *inodeOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Save("p", &x.p)
+}
+
+func (x *inodeOperations) afterLoad() {}
+func (x *inodeOperations) load(m state.Map) {
+ m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes)
+ m.Load("p", &x.p)
+}
+
+func (x *Pipe) beforeSave() {}
+func (x *Pipe) save(m state.Map) {
+ x.beforeSave()
+ m.Save("isNamed", &x.isNamed)
+ m.Save("atomicIOBytes", &x.atomicIOBytes)
+ m.Save("readers", &x.readers)
+ m.Save("writers", &x.writers)
+ m.Save("data", &x.data)
+ m.Save("max", &x.max)
+ m.Save("size", &x.size)
+ m.Save("hadWriter", &x.hadWriter)
+}
+
+func (x *Pipe) afterLoad() {}
+func (x *Pipe) load(m state.Map) {
+ m.Load("isNamed", &x.isNamed)
+ m.Load("atomicIOBytes", &x.atomicIOBytes)
+ m.Load("readers", &x.readers)
+ m.Load("writers", &x.writers)
+ m.Load("data", &x.data)
+ m.Load("max", &x.max)
+ m.Load("size", &x.size)
+ m.Load("hadWriter", &x.hadWriter)
+}
+
+func (x *Reader) beforeSave() {}
+func (x *Reader) save(m state.Map) {
+ x.beforeSave()
+ m.Save("ReaderWriter", &x.ReaderWriter)
+}
+
+func (x *Reader) afterLoad() {}
+func (x *Reader) load(m state.Map) {
+ m.Load("ReaderWriter", &x.ReaderWriter)
+}
+
+func (x *ReaderWriter) beforeSave() {}
+func (x *ReaderWriter) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Pipe", &x.Pipe)
+}
+
+func (x *ReaderWriter) afterLoad() {}
+func (x *ReaderWriter) load(m state.Map) {
+ m.Load("Pipe", &x.Pipe)
+}
+
+func (x *Writer) beforeSave() {}
+func (x *Writer) save(m state.Map) {
+ x.beforeSave()
+ m.Save("ReaderWriter", &x.ReaderWriter)
+}
+
+func (x *Writer) afterLoad() {}
+func (x *Writer) load(m state.Map) {
+ m.Load("ReaderWriter", &x.ReaderWriter)
+}
+
+func init() {
+ state.Register("pipe.buffer", (*buffer)(nil), state.Fns{Save: (*buffer).save, Load: (*buffer).load})
+ state.Register("pipe.bufferList", (*bufferList)(nil), state.Fns{Save: (*bufferList).save, Load: (*bufferList).load})
+ state.Register("pipe.bufferEntry", (*bufferEntry)(nil), state.Fns{Save: (*bufferEntry).save, Load: (*bufferEntry).load})
+ state.Register("pipe.inodeOperations", (*inodeOperations)(nil), state.Fns{Save: (*inodeOperations).save, Load: (*inodeOperations).load})
+ state.Register("pipe.Pipe", (*Pipe)(nil), state.Fns{Save: (*Pipe).save, Load: (*Pipe).load})
+ state.Register("pipe.Reader", (*Reader)(nil), state.Fns{Save: (*Reader).save, Load: (*Reader).load})
+ state.Register("pipe.ReaderWriter", (*ReaderWriter)(nil), state.Fns{Save: (*ReaderWriter).save, Load: (*ReaderWriter).load})
+ state.Register("pipe.Writer", (*Writer)(nil), state.Fns{Save: (*Writer).save, Load: (*Writer).load})
+}
diff --git a/pkg/sentry/kernel/pipe/pipe_test.go b/pkg/sentry/kernel/pipe/pipe_test.go
deleted file mode 100644
index e3a14b665..000000000
--- a/pkg/sentry/kernel/pipe/pipe_test.go
+++ /dev/null
@@ -1,139 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package pipe
-
-import (
- "bytes"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/pkg/syserror"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-func TestPipeRW(t *testing.T) {
- ctx := contexttest.Context(t)
- r, w := NewConnectedPipe(ctx, 65536, 4096)
- defer r.DecRef()
- defer w.DecRef()
-
- msg := []byte("here's some bytes")
- wantN := int64(len(msg))
- n, err := w.Writev(ctx, usermem.BytesIOSequence(msg))
- if n != wantN || err != nil {
- t.Fatalf("Writev: got (%d, %v), wanted (%d, nil)", n, err, wantN)
- }
-
- buf := make([]byte, len(msg))
- n, err = r.Readv(ctx, usermem.BytesIOSequence(buf))
- if n != wantN || err != nil || !bytes.Equal(buf, msg) {
- t.Fatalf("Readv: got (%d, %v) %q, wanted (%d, nil) %q", n, err, buf, wantN, msg)
- }
-}
-
-func TestPipeReadBlock(t *testing.T) {
- ctx := contexttest.Context(t)
- r, w := NewConnectedPipe(ctx, 65536, 4096)
- defer r.DecRef()
- defer w.DecRef()
-
- n, err := r.Readv(ctx, usermem.BytesIOSequence(make([]byte, 1)))
- if n != 0 || err != syserror.ErrWouldBlock {
- t.Fatalf("Readv: got (%d, %v), wanted (0, %v)", n, err, syserror.ErrWouldBlock)
- }
-}
-
-func TestPipeWriteBlock(t *testing.T) {
- const atomicIOBytes = 2
- const capacity = MinimumPipeSize
-
- ctx := contexttest.Context(t)
- r, w := NewConnectedPipe(ctx, capacity, atomicIOBytes)
- defer r.DecRef()
- defer w.DecRef()
-
- msg := make([]byte, capacity+1)
- n, err := w.Writev(ctx, usermem.BytesIOSequence(msg))
- if wantN, wantErr := int64(capacity), syserror.ErrWouldBlock; n != wantN || err != wantErr {
- t.Fatalf("Writev: got (%d, %v), wanted (%d, %v)", n, err, wantN, wantErr)
- }
-}
-
-func TestPipeWriteUntilEnd(t *testing.T) {
- const atomicIOBytes = 2
-
- ctx := contexttest.Context(t)
- r, w := NewConnectedPipe(ctx, atomicIOBytes, atomicIOBytes)
- defer r.DecRef()
- defer w.DecRef()
-
- msg := []byte("here's some bytes")
-
- wDone := make(chan struct{}, 0)
- rDone := make(chan struct{}, 0)
- defer func() {
- // Signal the reader to stop and wait until it does so.
- close(wDone)
- <-rDone
- }()
-
- go func() {
- defer close(rDone)
- // Read from r until done is closed.
- ctx := contexttest.Context(t)
- buf := make([]byte, len(msg)+1)
- dst := usermem.BytesIOSequence(buf)
- e, ch := waiter.NewChannelEntry(nil)
- r.EventRegister(&e, waiter.EventIn)
- defer r.EventUnregister(&e)
- for {
- n, err := r.Readv(ctx, dst)
- dst = dst.DropFirst64(n)
- if err == syserror.ErrWouldBlock {
- select {
- case <-ch:
- continue
- case <-wDone:
- // We expect to have 1 byte left in dst since len(buf) ==
- // len(msg)+1.
- if dst.NumBytes() != 1 || !bytes.Equal(buf[:len(msg)], msg) {
- t.Errorf("Reader: got %q (%d bytes remaining), wanted %q", buf, dst.NumBytes(), msg)
- }
- return
- }
- }
- if err != nil {
- t.Fatalf("Readv: got unexpected error %v", err)
- }
- }
- }()
-
- src := usermem.BytesIOSequence(msg)
- e, ch := waiter.NewChannelEntry(nil)
- w.EventRegister(&e, waiter.EventOut)
- defer w.EventUnregister(&e)
- for src.NumBytes() != 0 {
- n, err := w.Writev(ctx, src)
- src = src.DropFirst64(n)
- if err == syserror.ErrWouldBlock {
- <-ch
- continue
- }
- if err != nil {
- t.Fatalf("Writev: got (%d, %v)", n, err)
- }
- }
-}
diff --git a/pkg/sentry/kernel/process_group_list.go b/pkg/sentry/kernel/process_group_list.go
new file mode 100755
index 000000000..853145237
--- /dev/null
+++ b/pkg/sentry/kernel/process_group_list.go
@@ -0,0 +1,173 @@
+package kernel
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type processGroupElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (processGroupElementMapper) linkerFor(elem *ProcessGroup) *ProcessGroup { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type processGroupList struct {
+ head *ProcessGroup
+ tail *ProcessGroup
+}
+
+// Reset resets list l to the empty state.
+func (l *processGroupList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *processGroupList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *processGroupList) Front() *ProcessGroup {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *processGroupList) Back() *ProcessGroup {
+ return l.tail
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *processGroupList) PushFront(e *ProcessGroup) {
+ processGroupElementMapper{}.linkerFor(e).SetNext(l.head)
+ processGroupElementMapper{}.linkerFor(e).SetPrev(nil)
+
+ if l.head != nil {
+ processGroupElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *processGroupList) PushBack(e *ProcessGroup) {
+ processGroupElementMapper{}.linkerFor(e).SetNext(nil)
+ processGroupElementMapper{}.linkerFor(e).SetPrev(l.tail)
+
+ if l.tail != nil {
+ processGroupElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *processGroupList) PushBackList(m *processGroupList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ processGroupElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ processGroupElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *processGroupList) InsertAfter(b, e *ProcessGroup) {
+ a := processGroupElementMapper{}.linkerFor(b).Next()
+ processGroupElementMapper{}.linkerFor(e).SetNext(a)
+ processGroupElementMapper{}.linkerFor(e).SetPrev(b)
+ processGroupElementMapper{}.linkerFor(b).SetNext(e)
+
+ if a != nil {
+ processGroupElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *processGroupList) InsertBefore(a, e *ProcessGroup) {
+ b := processGroupElementMapper{}.linkerFor(a).Prev()
+ processGroupElementMapper{}.linkerFor(e).SetNext(a)
+ processGroupElementMapper{}.linkerFor(e).SetPrev(b)
+ processGroupElementMapper{}.linkerFor(a).SetPrev(e)
+
+ if b != nil {
+ processGroupElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *processGroupList) Remove(e *ProcessGroup) {
+ prev := processGroupElementMapper{}.linkerFor(e).Prev()
+ next := processGroupElementMapper{}.linkerFor(e).Next()
+
+ if prev != nil {
+ processGroupElementMapper{}.linkerFor(prev).SetNext(next)
+ } else {
+ l.head = next
+ }
+
+ if next != nil {
+ processGroupElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else {
+ l.tail = prev
+ }
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type processGroupEntry struct {
+ next *ProcessGroup
+ prev *ProcessGroup
+}
+
+// Next returns the entry that follows e in the list.
+func (e *processGroupEntry) Next() *ProcessGroup {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *processGroupEntry) Prev() *ProcessGroup {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *processGroupEntry) SetNext(elem *ProcessGroup) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *processGroupEntry) SetPrev(elem *ProcessGroup) {
+ e.prev = elem
+}
diff --git a/pkg/sentry/kernel/sched/BUILD b/pkg/sentry/kernel/sched/BUILD
deleted file mode 100644
index 98ea7a0d8..000000000
--- a/pkg/sentry/kernel/sched/BUILD
+++ /dev/null
@@ -1,21 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "sched",
- srcs = [
- "cpuset.go",
- "sched.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/kernel/sched",
- visibility = ["//pkg/sentry:internal"],
-)
-
-go_test(
- name = "sched_test",
- size = "small",
- srcs = ["cpuset_test.go"],
- embed = [":sched"],
-)
diff --git a/pkg/sentry/kernel/sched/cpuset_test.go b/pkg/sentry/kernel/sched/cpuset_test.go
deleted file mode 100644
index 3af9f1197..000000000
--- a/pkg/sentry/kernel/sched/cpuset_test.go
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package sched
-
-import (
- "testing"
-)
-
-func TestNumCPUs(t *testing.T) {
- for i := uint(0); i < 1024; i++ {
- c := NewCPUSet(i)
- for j := uint(0); j < i; j++ {
- c.Set(j)
- }
- n := c.NumCPUs()
- if n != i {
- t.Errorf("got wrong number of cpus %d, want %d", n, i)
- }
- }
-}
-
-func TestClearAbove(t *testing.T) {
- const n = 1024
- c := NewFullCPUSet(n)
- for i := uint(0); i < n; i++ {
- cpu := n - i
- c.ClearAbove(cpu)
- if got := c.NumCPUs(); got != cpu {
- t.Errorf("iteration %d: got %d cpus, wanted %d", i, got, cpu)
- }
- }
-}
diff --git a/pkg/sentry/kernel/sched/sched_state_autogen.go b/pkg/sentry/kernel/sched/sched_state_autogen.go
new file mode 100755
index 000000000..2a482732e
--- /dev/null
+++ b/pkg/sentry/kernel/sched/sched_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package sched
+
diff --git a/pkg/sentry/kernel/semaphore/BUILD b/pkg/sentry/kernel/semaphore/BUILD
deleted file mode 100644
index 80e5e5da3..000000000
--- a/pkg/sentry/kernel/semaphore/BUILD
+++ /dev/null
@@ -1,51 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_template_instance(
- name = "waiter_list",
- out = "waiter_list.go",
- package = "semaphore",
- prefix = "waiter",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*waiter",
- "Linker": "*waiter",
- },
-)
-
-go_library(
- name = "semaphore",
- srcs = [
- "semaphore.go",
- "waiter_list.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/kernel/semaphore",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/log",
- "//pkg/sentry/context",
- "//pkg/sentry/fs",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/kernel/time",
- "//pkg/syserror",
- ],
-)
-
-go_test(
- name = "semaphore_test",
- size = "small",
- srcs = ["semaphore_test.go"],
- embed = [":semaphore"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/sentry/context",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/kernel/auth",
- "//pkg/syserror",
- ],
-)
diff --git a/pkg/sentry/kernel/semaphore/semaphore_state_autogen.go b/pkg/sentry/kernel/semaphore/semaphore_state_autogen.go
new file mode 100755
index 000000000..f225f6f75
--- /dev/null
+++ b/pkg/sentry/kernel/semaphore/semaphore_state_autogen.go
@@ -0,0 +1,115 @@
+// automatically generated by stateify.
+
+package semaphore
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *Registry) beforeSave() {}
+func (x *Registry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("userNS", &x.userNS)
+ m.Save("semaphores", &x.semaphores)
+ m.Save("lastIDUsed", &x.lastIDUsed)
+}
+
+func (x *Registry) afterLoad() {}
+func (x *Registry) load(m state.Map) {
+ m.Load("userNS", &x.userNS)
+ m.Load("semaphores", &x.semaphores)
+ m.Load("lastIDUsed", &x.lastIDUsed)
+}
+
+func (x *Set) beforeSave() {}
+func (x *Set) save(m state.Map) {
+ x.beforeSave()
+ m.Save("registry", &x.registry)
+ m.Save("ID", &x.ID)
+ m.Save("key", &x.key)
+ m.Save("creator", &x.creator)
+ m.Save("owner", &x.owner)
+ m.Save("perms", &x.perms)
+ m.Save("opTime", &x.opTime)
+ m.Save("changeTime", &x.changeTime)
+ m.Save("sems", &x.sems)
+ m.Save("dead", &x.dead)
+}
+
+func (x *Set) afterLoad() {}
+func (x *Set) load(m state.Map) {
+ m.Load("registry", &x.registry)
+ m.Load("ID", &x.ID)
+ m.Load("key", &x.key)
+ m.Load("creator", &x.creator)
+ m.Load("owner", &x.owner)
+ m.Load("perms", &x.perms)
+ m.Load("opTime", &x.opTime)
+ m.Load("changeTime", &x.changeTime)
+ m.Load("sems", &x.sems)
+ m.Load("dead", &x.dead)
+}
+
+func (x *sem) beforeSave() {}
+func (x *sem) save(m state.Map) {
+ x.beforeSave()
+ if !state.IsZeroValue(x.waiters) { m.Failf("waiters is %v, expected zero", x.waiters) }
+ m.Save("value", &x.value)
+ m.Save("pid", &x.pid)
+}
+
+func (x *sem) afterLoad() {}
+func (x *sem) load(m state.Map) {
+ m.Load("value", &x.value)
+ m.Load("pid", &x.pid)
+}
+
+func (x *waiter) beforeSave() {}
+func (x *waiter) save(m state.Map) {
+ x.beforeSave()
+ m.Save("waiterEntry", &x.waiterEntry)
+ m.Save("value", &x.value)
+ m.Save("ch", &x.ch)
+}
+
+func (x *waiter) afterLoad() {}
+func (x *waiter) load(m state.Map) {
+ m.Load("waiterEntry", &x.waiterEntry)
+ m.Load("value", &x.value)
+ m.Load("ch", &x.ch)
+}
+
+func (x *waiterList) beforeSave() {}
+func (x *waiterList) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *waiterList) afterLoad() {}
+func (x *waiterList) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *waiterEntry) beforeSave() {}
+func (x *waiterEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *waiterEntry) afterLoad() {}
+func (x *waiterEntry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func init() {
+ state.Register("semaphore.Registry", (*Registry)(nil), state.Fns{Save: (*Registry).save, Load: (*Registry).load})
+ state.Register("semaphore.Set", (*Set)(nil), state.Fns{Save: (*Set).save, Load: (*Set).load})
+ state.Register("semaphore.sem", (*sem)(nil), state.Fns{Save: (*sem).save, Load: (*sem).load})
+ state.Register("semaphore.waiter", (*waiter)(nil), state.Fns{Save: (*waiter).save, Load: (*waiter).load})
+ state.Register("semaphore.waiterList", (*waiterList)(nil), state.Fns{Save: (*waiterList).save, Load: (*waiterList).load})
+ state.Register("semaphore.waiterEntry", (*waiterEntry)(nil), state.Fns{Save: (*waiterEntry).save, Load: (*waiterEntry).load})
+}
diff --git a/pkg/sentry/kernel/semaphore/semaphore_test.go b/pkg/sentry/kernel/semaphore/semaphore_test.go
deleted file mode 100644
index c235f6ca4..000000000
--- a/pkg/sentry/kernel/semaphore/semaphore_test.go
+++ /dev/null
@@ -1,172 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package semaphore
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-func executeOps(ctx context.Context, t *testing.T, set *Set, ops []linux.Sembuf, block bool) chan struct{} {
- ch, _, err := set.executeOps(ctx, ops, 123)
- if err != nil {
- t.Fatalf("ExecuteOps(ops) failed, err: %v, ops: %+v", err, ops)
- }
- if block {
- if ch == nil {
- t.Fatalf("ExecuteOps(ops) got: nil, expected: !nil, ops: %+v", ops)
- }
- if signalled(ch) {
- t.Fatalf("ExecuteOps(ops) channel should not have been signalled, ops: %+v", ops)
- }
- } else {
- if ch != nil {
- t.Fatalf("ExecuteOps(ops) got: %v, expected: nil, ops: %+v", ch, ops)
- }
- }
- return ch
-}
-
-func signalled(ch chan struct{}) bool {
- select {
- case <-ch:
- return true
- default:
- return false
- }
-}
-
-func TestBasic(t *testing.T) {
- ctx := contexttest.Context(t)
- set := &Set{ID: 123, sems: make([]sem, 1)}
- ops := []linux.Sembuf{
- {SemOp: 1},
- }
- executeOps(ctx, t, set, ops, false)
-
- ops[0].SemOp = -1
- executeOps(ctx, t, set, ops, false)
-
- ops[0].SemOp = -1
- ch1 := executeOps(ctx, t, set, ops, true)
-
- ops[0].SemOp = 1
- executeOps(ctx, t, set, ops, false)
- if !signalled(ch1) {
- t.Fatalf("ExecuteOps(ops) channel should not have been signalled, ops: %+v", ops)
- }
-}
-
-func TestWaitForZero(t *testing.T) {
- ctx := contexttest.Context(t)
- set := &Set{ID: 123, sems: make([]sem, 1)}
- ops := []linux.Sembuf{
- {SemOp: 0},
- }
- executeOps(ctx, t, set, ops, false)
-
- ops[0].SemOp = -2
- ch1 := executeOps(ctx, t, set, ops, true)
-
- ops[0].SemOp = 0
- executeOps(ctx, t, set, ops, false)
-
- ops[0].SemOp = 1
- executeOps(ctx, t, set, ops, false)
-
- ops[0].SemOp = 0
- chZero1 := executeOps(ctx, t, set, ops, true)
-
- ops[0].SemOp = 0
- chZero2 := executeOps(ctx, t, set, ops, true)
-
- ops[0].SemOp = 1
- executeOps(ctx, t, set, ops, false)
- if !signalled(ch1) {
- t.Fatalf("ExecuteOps(ops) channel should have been signalled, ops: %+v, set: %+v", ops, set)
- }
-
- ops[0].SemOp = -2
- executeOps(ctx, t, set, ops, false)
- if !signalled(chZero1) {
- t.Fatalf("ExecuteOps(ops) channel zero 1 should have been signalled, ops: %+v, set: %+v", ops, set)
- }
- if !signalled(chZero2) {
- t.Fatalf("ExecuteOps(ops) channel zero 2 should have been signalled, ops: %+v, set: %+v", ops, set)
- }
-}
-
-func TestNoWait(t *testing.T) {
- ctx := contexttest.Context(t)
- set := &Set{ID: 123, sems: make([]sem, 1)}
- ops := []linux.Sembuf{
- {SemOp: 1},
- }
- executeOps(ctx, t, set, ops, false)
-
- ops[0].SemOp = -2
- ops[0].SemFlg = linux.IPC_NOWAIT
- if _, _, err := set.executeOps(ctx, ops, 123); err != syserror.ErrWouldBlock {
- t.Fatalf("ExecuteOps(ops) wrong result, got: %v, expected: %v", err, syserror.ErrWouldBlock)
- }
-
- ops[0].SemOp = 0
- ops[0].SemFlg = linux.IPC_NOWAIT
- if _, _, err := set.executeOps(ctx, ops, 123); err != syserror.ErrWouldBlock {
- t.Fatalf("ExecuteOps(ops) wrong result, got: %v, expected: %v", err, syserror.ErrWouldBlock)
- }
-}
-
-func TestUnregister(t *testing.T) {
- ctx := contexttest.Context(t)
- r := NewRegistry(auth.NewRootUserNamespace())
- set, err := r.FindOrCreate(ctx, 123, 2, linux.FileMode(0x600), true, true, true)
- if err != nil {
- t.Fatalf("FindOrCreate() failed, err: %v", err)
- }
- if got := r.FindByID(set.ID); got.ID != set.ID {
- t.Fatalf("FindById(%d) failed, got: %+v, expected: %+v", set.ID, got, set)
- }
-
- ops := []linux.Sembuf{
- {SemOp: -1},
- }
- chs := make([]chan struct{}, 0, 5)
- for i := 0; i < 5; i++ {
- ch := executeOps(ctx, t, set, ops, true)
- chs = append(chs, ch)
- }
-
- creds := auth.CredentialsFromContext(ctx)
- if err := r.RemoveID(set.ID, creds); err != nil {
- t.Fatalf("RemoveID(%d) failed, err: %v", set.ID, err)
- }
- if !set.dead {
- t.Fatalf("set is not dead: %+v", set)
- }
- if got := r.FindByID(set.ID); got != nil {
- t.Fatalf("FindById(%d) failed, got: %+v, expected: nil", set.ID, got)
- }
- for i, ch := range chs {
- if !signalled(ch) {
- t.Fatalf("channel %d should have been signalled", i)
- }
- }
-}
diff --git a/pkg/sentry/kernel/semaphore/waiter_list.go b/pkg/sentry/kernel/semaphore/waiter_list.go
new file mode 100755
index 000000000..33e29fb55
--- /dev/null
+++ b/pkg/sentry/kernel/semaphore/waiter_list.go
@@ -0,0 +1,173 @@
+package semaphore
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type waiterElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (waiterElementMapper) linkerFor(elem *waiter) *waiter { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type waiterList struct {
+ head *waiter
+ tail *waiter
+}
+
+// Reset resets list l to the empty state.
+func (l *waiterList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *waiterList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *waiterList) Front() *waiter {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *waiterList) Back() *waiter {
+ return l.tail
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *waiterList) PushFront(e *waiter) {
+ waiterElementMapper{}.linkerFor(e).SetNext(l.head)
+ waiterElementMapper{}.linkerFor(e).SetPrev(nil)
+
+ if l.head != nil {
+ waiterElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *waiterList) PushBack(e *waiter) {
+ waiterElementMapper{}.linkerFor(e).SetNext(nil)
+ waiterElementMapper{}.linkerFor(e).SetPrev(l.tail)
+
+ if l.tail != nil {
+ waiterElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *waiterList) PushBackList(m *waiterList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ waiterElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ waiterElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *waiterList) InsertAfter(b, e *waiter) {
+ a := waiterElementMapper{}.linkerFor(b).Next()
+ waiterElementMapper{}.linkerFor(e).SetNext(a)
+ waiterElementMapper{}.linkerFor(e).SetPrev(b)
+ waiterElementMapper{}.linkerFor(b).SetNext(e)
+
+ if a != nil {
+ waiterElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *waiterList) InsertBefore(a, e *waiter) {
+ b := waiterElementMapper{}.linkerFor(a).Prev()
+ waiterElementMapper{}.linkerFor(e).SetNext(a)
+ waiterElementMapper{}.linkerFor(e).SetPrev(b)
+ waiterElementMapper{}.linkerFor(a).SetPrev(e)
+
+ if b != nil {
+ waiterElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *waiterList) Remove(e *waiter) {
+ prev := waiterElementMapper{}.linkerFor(e).Prev()
+ next := waiterElementMapper{}.linkerFor(e).Next()
+
+ if prev != nil {
+ waiterElementMapper{}.linkerFor(prev).SetNext(next)
+ } else {
+ l.head = next
+ }
+
+ if next != nil {
+ waiterElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else {
+ l.tail = prev
+ }
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type waiterEntry struct {
+ next *waiter
+ prev *waiter
+}
+
+// Next returns the entry that follows e in the list.
+func (e *waiterEntry) Next() *waiter {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *waiterEntry) Prev() *waiter {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *waiterEntry) SetNext(elem *waiter) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *waiterEntry) SetPrev(elem *waiter) {
+ e.prev = elem
+}
diff --git a/third_party/gvsync/seqatomic_unsafe.go b/pkg/sentry/kernel/seqatomic_taskgoroutineschedinfo_unsafe.go
index 382eeed43..be6b07629 100644..100755
--- a/third_party/gvsync/seqatomic_unsafe.go
+++ b/pkg/sentry/kernel/seqatomic_taskgoroutineschedinfo_unsafe.go
@@ -1,44 +1,26 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package template doesn't exist. This file must be instantiated using the
-// go_template_instance rule in tools/go_generics/defs.bzl.
-package template
+package kernel
import (
"fmt"
+ "gvisor.dev/gvisor/third_party/gvsync"
"reflect"
"strings"
"unsafe"
-
- "gvisor.dev/gvisor/third_party/gvsync"
)
-// Value is a required type parameter.
-//
-// Value must not contain any pointers, including interface objects, function
-// objects, slices, maps, channels, unsafe.Pointer, and arrays or structs
-// containing any of the above. An init() function will panic if this property
-// does not hold.
-type Value struct{}
-
// SeqAtomicLoad returns a copy of *ptr, ensuring that the read does not race
// with any writer critical sections in sc.
-func SeqAtomicLoad(sc *gvsync.SeqCount, ptr *Value) Value {
+func SeqAtomicLoadTaskGoroutineSchedInfo(sc *gvsync.SeqCount, ptr *TaskGoroutineSchedInfo) TaskGoroutineSchedInfo {
// This function doesn't use SeqAtomicTryLoad because doing so is
// measurably, significantly (~20%) slower; Go is awful at inlining.
- var val Value
+ var val TaskGoroutineSchedInfo
for {
epoch := sc.BeginRead()
if gvsync.RaceEnabled {
- // runtime.RaceDisable() doesn't actually stop the race detector,
- // so it can't help us here. Instead, call runtime.memmove
- // directly, which is not instrumented by the race detector.
+
gvsync.Memmove(unsafe.Pointer(&val), unsafe.Pointer(ptr), unsafe.Sizeof(val))
} else {
- // This is ~40% faster for short reads than going through memmove.
+
val = *ptr
}
if sc.ReadOk(epoch) {
@@ -52,8 +34,8 @@ func SeqAtomicLoad(sc *gvsync.SeqCount, ptr *Value) Value {
// in sc initiated by a call to sc.BeginRead() that returned epoch. If the read
// would race with a writer critical section, SeqAtomicTryLoad returns
// (unspecified, false).
-func SeqAtomicTryLoad(sc *gvsync.SeqCount, epoch gvsync.SeqCountEpoch, ptr *Value) (Value, bool) {
- var val Value
+func SeqAtomicTryLoadTaskGoroutineSchedInfo(sc *gvsync.SeqCount, epoch gvsync.SeqCountEpoch, ptr *TaskGoroutineSchedInfo) (TaskGoroutineSchedInfo, bool) {
+ var val TaskGoroutineSchedInfo
if gvsync.RaceEnabled {
gvsync.Memmove(unsafe.Pointer(&val), unsafe.Pointer(ptr), unsafe.Sizeof(val))
} else {
@@ -62,8 +44,8 @@ func SeqAtomicTryLoad(sc *gvsync.SeqCount, epoch gvsync.SeqCountEpoch, ptr *Valu
return val, sc.ReadOk(epoch)
}
-func init() {
- var val Value
+func initTaskGoroutineSchedInfo() {
+ var val TaskGoroutineSchedInfo
typ := reflect.TypeOf(val)
name := typ.Name()
if ptrs := gvsync.PointersInType(typ, name); len(ptrs) != 0 {
diff --git a/pkg/sentry/kernel/session_list.go b/pkg/sentry/kernel/session_list.go
new file mode 100755
index 000000000..9ba27b164
--- /dev/null
+++ b/pkg/sentry/kernel/session_list.go
@@ -0,0 +1,173 @@
+package kernel
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type sessionElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (sessionElementMapper) linkerFor(elem *Session) *Session { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type sessionList struct {
+ head *Session
+ tail *Session
+}
+
+// Reset resets list l to the empty state.
+func (l *sessionList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *sessionList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *sessionList) Front() *Session {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *sessionList) Back() *Session {
+ return l.tail
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *sessionList) PushFront(e *Session) {
+ sessionElementMapper{}.linkerFor(e).SetNext(l.head)
+ sessionElementMapper{}.linkerFor(e).SetPrev(nil)
+
+ if l.head != nil {
+ sessionElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *sessionList) PushBack(e *Session) {
+ sessionElementMapper{}.linkerFor(e).SetNext(nil)
+ sessionElementMapper{}.linkerFor(e).SetPrev(l.tail)
+
+ if l.tail != nil {
+ sessionElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *sessionList) PushBackList(m *sessionList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ sessionElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ sessionElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *sessionList) InsertAfter(b, e *Session) {
+ a := sessionElementMapper{}.linkerFor(b).Next()
+ sessionElementMapper{}.linkerFor(e).SetNext(a)
+ sessionElementMapper{}.linkerFor(e).SetPrev(b)
+ sessionElementMapper{}.linkerFor(b).SetNext(e)
+
+ if a != nil {
+ sessionElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *sessionList) InsertBefore(a, e *Session) {
+ b := sessionElementMapper{}.linkerFor(a).Prev()
+ sessionElementMapper{}.linkerFor(e).SetNext(a)
+ sessionElementMapper{}.linkerFor(e).SetPrev(b)
+ sessionElementMapper{}.linkerFor(a).SetPrev(e)
+
+ if b != nil {
+ sessionElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *sessionList) Remove(e *Session) {
+ prev := sessionElementMapper{}.linkerFor(e).Prev()
+ next := sessionElementMapper{}.linkerFor(e).Next()
+
+ if prev != nil {
+ sessionElementMapper{}.linkerFor(prev).SetNext(next)
+ } else {
+ l.head = next
+ }
+
+ if next != nil {
+ sessionElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else {
+ l.tail = prev
+ }
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type sessionEntry struct {
+ next *Session
+ prev *Session
+}
+
+// Next returns the entry that follows e in the list.
+func (e *sessionEntry) Next() *Session {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *sessionEntry) Prev() *Session {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *sessionEntry) SetNext(elem *Session) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *sessionEntry) SetPrev(elem *Session) {
+ e.prev = elem
+}
diff --git a/pkg/sentry/kernel/shm/BUILD b/pkg/sentry/kernel/shm/BUILD
deleted file mode 100644
index aa7471eb6..000000000
--- a/pkg/sentry/kernel/shm/BUILD
+++ /dev/null
@@ -1,29 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "shm",
- srcs = [
- "device.go",
- "shm.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/kernel/shm",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/log",
- "//pkg/refs",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/memmap",
- "//pkg/sentry/pgalloc",
- "//pkg/sentry/platform",
- "//pkg/sentry/usage",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- ],
-)
diff --git a/pkg/sentry/kernel/shm/shm_state_autogen.go b/pkg/sentry/kernel/shm/shm_state_autogen.go
new file mode 100755
index 000000000..4fd44bb6a
--- /dev/null
+++ b/pkg/sentry/kernel/shm/shm_state_autogen.go
@@ -0,0 +1,74 @@
+// automatically generated by stateify.
+
+package shm
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *Registry) beforeSave() {}
+func (x *Registry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("userNS", &x.userNS)
+ m.Save("shms", &x.shms)
+ m.Save("keysToShms", &x.keysToShms)
+ m.Save("totalPages", &x.totalPages)
+ m.Save("lastIDUsed", &x.lastIDUsed)
+}
+
+func (x *Registry) afterLoad() {}
+func (x *Registry) load(m state.Map) {
+ m.Load("userNS", &x.userNS)
+ m.Load("shms", &x.shms)
+ m.Load("keysToShms", &x.keysToShms)
+ m.Load("totalPages", &x.totalPages)
+ m.Load("lastIDUsed", &x.lastIDUsed)
+}
+
+func (x *Shm) beforeSave() {}
+func (x *Shm) save(m state.Map) {
+ x.beforeSave()
+ m.Save("AtomicRefCount", &x.AtomicRefCount)
+ m.Save("mfp", &x.mfp)
+ m.Save("registry", &x.registry)
+ m.Save("ID", &x.ID)
+ m.Save("creator", &x.creator)
+ m.Save("size", &x.size)
+ m.Save("effectiveSize", &x.effectiveSize)
+ m.Save("fr", &x.fr)
+ m.Save("key", &x.key)
+ m.Save("perms", &x.perms)
+ m.Save("owner", &x.owner)
+ m.Save("attachTime", &x.attachTime)
+ m.Save("detachTime", &x.detachTime)
+ m.Save("changeTime", &x.changeTime)
+ m.Save("creatorPID", &x.creatorPID)
+ m.Save("lastAttachDetachPID", &x.lastAttachDetachPID)
+ m.Save("pendingDestruction", &x.pendingDestruction)
+}
+
+func (x *Shm) afterLoad() {}
+func (x *Shm) load(m state.Map) {
+ m.Load("AtomicRefCount", &x.AtomicRefCount)
+ m.Load("mfp", &x.mfp)
+ m.Load("registry", &x.registry)
+ m.Load("ID", &x.ID)
+ m.Load("creator", &x.creator)
+ m.Load("size", &x.size)
+ m.Load("effectiveSize", &x.effectiveSize)
+ m.Load("fr", &x.fr)
+ m.Load("key", &x.key)
+ m.Load("perms", &x.perms)
+ m.Load("owner", &x.owner)
+ m.Load("attachTime", &x.attachTime)
+ m.Load("detachTime", &x.detachTime)
+ m.Load("changeTime", &x.changeTime)
+ m.Load("creatorPID", &x.creatorPID)
+ m.Load("lastAttachDetachPID", &x.lastAttachDetachPID)
+ m.Load("pendingDestruction", &x.pendingDestruction)
+}
+
+func init() {
+ state.Register("shm.Registry", (*Registry)(nil), state.Fns{Save: (*Registry).save, Load: (*Registry).load})
+ state.Register("shm.Shm", (*Shm)(nil), state.Fns{Save: (*Shm).save, Load: (*Shm).load})
+}
diff --git a/pkg/sentry/kernel/socket_list.go b/pkg/sentry/kernel/socket_list.go
new file mode 100755
index 000000000..aed0a555e
--- /dev/null
+++ b/pkg/sentry/kernel/socket_list.go
@@ -0,0 +1,173 @@
+package kernel
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type socketElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (socketElementMapper) linkerFor(elem *SocketEntry) *SocketEntry { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type socketList struct {
+ head *SocketEntry
+ tail *SocketEntry
+}
+
+// Reset resets list l to the empty state.
+func (l *socketList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *socketList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *socketList) Front() *SocketEntry {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *socketList) Back() *SocketEntry {
+ return l.tail
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *socketList) PushFront(e *SocketEntry) {
+ socketElementMapper{}.linkerFor(e).SetNext(l.head)
+ socketElementMapper{}.linkerFor(e).SetPrev(nil)
+
+ if l.head != nil {
+ socketElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *socketList) PushBack(e *SocketEntry) {
+ socketElementMapper{}.linkerFor(e).SetNext(nil)
+ socketElementMapper{}.linkerFor(e).SetPrev(l.tail)
+
+ if l.tail != nil {
+ socketElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *socketList) PushBackList(m *socketList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ socketElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ socketElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *socketList) InsertAfter(b, e *SocketEntry) {
+ a := socketElementMapper{}.linkerFor(b).Next()
+ socketElementMapper{}.linkerFor(e).SetNext(a)
+ socketElementMapper{}.linkerFor(e).SetPrev(b)
+ socketElementMapper{}.linkerFor(b).SetNext(e)
+
+ if a != nil {
+ socketElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *socketList) InsertBefore(a, e *SocketEntry) {
+ b := socketElementMapper{}.linkerFor(a).Prev()
+ socketElementMapper{}.linkerFor(e).SetNext(a)
+ socketElementMapper{}.linkerFor(e).SetPrev(b)
+ socketElementMapper{}.linkerFor(a).SetPrev(e)
+
+ if b != nil {
+ socketElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *socketList) Remove(e *SocketEntry) {
+ prev := socketElementMapper{}.linkerFor(e).Prev()
+ next := socketElementMapper{}.linkerFor(e).Next()
+
+ if prev != nil {
+ socketElementMapper{}.linkerFor(prev).SetNext(next)
+ } else {
+ l.head = next
+ }
+
+ if next != nil {
+ socketElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else {
+ l.tail = prev
+ }
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type socketEntry struct {
+ next *SocketEntry
+ prev *SocketEntry
+}
+
+// Next returns the entry that follows e in the list.
+func (e *socketEntry) Next() *SocketEntry {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *socketEntry) Prev() *SocketEntry {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *socketEntry) SetNext(elem *SocketEntry) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *socketEntry) SetPrev(elem *SocketEntry) {
+ e.prev = elem
+}
diff --git a/pkg/sentry/kernel/table_test.go b/pkg/sentry/kernel/table_test.go
deleted file mode 100644
index 32cf47e05..000000000
--- a/pkg/sentry/kernel/table_test.go
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package kernel
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/abi"
- "gvisor.dev/gvisor/pkg/sentry/arch"
-)
-
-const (
- maxTestSyscall = 1000
-)
-
-func createSyscallTable() *SyscallTable {
- m := make(map[uintptr]Syscall)
- for i := uintptr(0); i <= maxTestSyscall; i++ {
- j := i
- m[i] = Syscall{
- Fn: func(*Task, arch.SyscallArguments) (uintptr, *SyscallControl, error) {
- return j, nil, nil
- },
- }
- }
-
- s := &SyscallTable{
- OS: abi.Linux,
- Arch: arch.AMD64,
- Table: m,
- }
-
- RegisterSyscallTable(s)
- return s
-}
-
-func TestTable(t *testing.T) {
- table := createSyscallTable()
- defer func() {
- // Cleanup registered tables to keep tests separate.
- allSyscallTables = []*SyscallTable{}
- }()
-
- // Go through all functions and check that they return the right value.
- for i := uintptr(0); i < maxTestSyscall; i++ {
- fn := table.Lookup(i)
- if fn == nil {
- t.Errorf("Syscall %v is set to nil", i)
- continue
- }
-
- v, _, _ := fn(nil, arch.SyscallArguments{})
- if v != i {
- t.Errorf("Wrong return value for syscall %v: expected %v, got %v", i, i, v)
- }
- }
-
- // Check that values outside the range return nil.
- for i := uintptr(maxTestSyscall + 1); i < maxTestSyscall+100; i++ {
- fn := table.Lookup(i)
- if fn != nil {
- t.Errorf("Syscall %v is not nil: %v", i, fn)
- continue
- }
- }
-}
-
-func BenchmarkTableLookup(b *testing.B) {
- table := createSyscallTable()
-
- b.ResetTimer()
-
- j := uintptr(0)
- for i := 0; i < b.N; i++ {
- table.Lookup(j)
- j = (j + 1) % 310
- }
-
- b.StopTimer()
- // Cleanup registered tables to keep tests separate.
- allSyscallTables = []*SyscallTable{}
-}
-
-func BenchmarkTableMapLookup(b *testing.B) {
- table := createSyscallTable()
-
- b.ResetTimer()
-
- j := uintptr(0)
- for i := 0; i < b.N; i++ {
- table.mapLookup(j)
- j = (j + 1) % 310
- }
-
- b.StopTimer()
- // Cleanup registered tables to keep tests separate.
- allSyscallTables = []*SyscallTable{}
-}
diff --git a/pkg/sentry/kernel/task_list.go b/pkg/sentry/kernel/task_list.go
new file mode 100755
index 000000000..57d3f098d
--- /dev/null
+++ b/pkg/sentry/kernel/task_list.go
@@ -0,0 +1,173 @@
+package kernel
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type taskElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (taskElementMapper) linkerFor(elem *Task) *Task { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type taskList struct {
+ head *Task
+ tail *Task
+}
+
+// Reset resets list l to the empty state.
+func (l *taskList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *taskList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *taskList) Front() *Task {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *taskList) Back() *Task {
+ return l.tail
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *taskList) PushFront(e *Task) {
+ taskElementMapper{}.linkerFor(e).SetNext(l.head)
+ taskElementMapper{}.linkerFor(e).SetPrev(nil)
+
+ if l.head != nil {
+ taskElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *taskList) PushBack(e *Task) {
+ taskElementMapper{}.linkerFor(e).SetNext(nil)
+ taskElementMapper{}.linkerFor(e).SetPrev(l.tail)
+
+ if l.tail != nil {
+ taskElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *taskList) PushBackList(m *taskList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ taskElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ taskElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *taskList) InsertAfter(b, e *Task) {
+ a := taskElementMapper{}.linkerFor(b).Next()
+ taskElementMapper{}.linkerFor(e).SetNext(a)
+ taskElementMapper{}.linkerFor(e).SetPrev(b)
+ taskElementMapper{}.linkerFor(b).SetNext(e)
+
+ if a != nil {
+ taskElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *taskList) InsertBefore(a, e *Task) {
+ b := taskElementMapper{}.linkerFor(a).Prev()
+ taskElementMapper{}.linkerFor(e).SetNext(a)
+ taskElementMapper{}.linkerFor(e).SetPrev(b)
+ taskElementMapper{}.linkerFor(a).SetPrev(e)
+
+ if b != nil {
+ taskElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *taskList) Remove(e *Task) {
+ prev := taskElementMapper{}.linkerFor(e).Prev()
+ next := taskElementMapper{}.linkerFor(e).Next()
+
+ if prev != nil {
+ taskElementMapper{}.linkerFor(prev).SetNext(next)
+ } else {
+ l.head = next
+ }
+
+ if next != nil {
+ taskElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else {
+ l.tail = prev
+ }
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type taskEntry struct {
+ next *Task
+ prev *Task
+}
+
+// Next returns the entry that follows e in the list.
+func (e *taskEntry) Next() *Task {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *taskEntry) Prev() *Task {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *taskEntry) SetNext(elem *Task) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *taskEntry) SetPrev(elem *Task) {
+ e.prev = elem
+}
diff --git a/pkg/sentry/kernel/task_test.go b/pkg/sentry/kernel/task_test.go
deleted file mode 100644
index cfcde9a7a..000000000
--- a/pkg/sentry/kernel/task_test.go
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package kernel
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/kernel/sched"
-)
-
-func TestTaskCPU(t *testing.T) {
- for _, test := range []struct {
- mask sched.CPUSet
- tid ThreadID
- cpu int32
- }{
- {
- mask: []byte{0xff},
- tid: 1,
- cpu: 0,
- },
- {
- mask: []byte{0xff},
- tid: 10,
- cpu: 1,
- },
- {
- // more than 8 cpus.
- mask: []byte{0xff, 0xff},
- tid: 10,
- cpu: 9,
- },
- {
- // missing the first cpu.
- mask: []byte{0xfe},
- tid: 1,
- cpu: 1,
- },
- {
- mask: []byte{0xfe},
- tid: 10,
- cpu: 3,
- },
- {
- // missing the fifth cpu.
- mask: []byte{0xef},
- tid: 10,
- cpu: 2,
- },
- } {
- assigned := assignCPU(test.mask, test.tid)
- if test.cpu != assigned {
- t.Errorf("assignCPU(%v, %v) got %v, want %v", test.mask, test.tid, assigned, test.cpu)
- }
- }
-
-}
diff --git a/pkg/sentry/kernel/time/BUILD b/pkg/sentry/kernel/time/BUILD
deleted file mode 100644
index 9beae4b31..000000000
--- a/pkg/sentry/kernel/time/BUILD
+++ /dev/null
@@ -1,19 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "time",
- srcs = [
- "context.go",
- "time.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/kernel/time",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/sentry/context",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/kernel/time/time_state_autogen.go b/pkg/sentry/kernel/time/time_state_autogen.go
new file mode 100755
index 000000000..77b676cec
--- /dev/null
+++ b/pkg/sentry/kernel/time/time_state_autogen.go
@@ -0,0 +1,56 @@
+// automatically generated by stateify.
+
+package time
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *Time) beforeSave() {}
+func (x *Time) save(m state.Map) {
+ x.beforeSave()
+ m.Save("ns", &x.ns)
+}
+
+func (x *Time) afterLoad() {}
+func (x *Time) load(m state.Map) {
+ m.Load("ns", &x.ns)
+}
+
+func (x *Setting) beforeSave() {}
+func (x *Setting) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Enabled", &x.Enabled)
+ m.Save("Next", &x.Next)
+ m.Save("Period", &x.Period)
+}
+
+func (x *Setting) afterLoad() {}
+func (x *Setting) load(m state.Map) {
+ m.Load("Enabled", &x.Enabled)
+ m.Load("Next", &x.Next)
+ m.Load("Period", &x.Period)
+}
+
+func (x *Timer) beforeSave() {}
+func (x *Timer) save(m state.Map) {
+ x.beforeSave()
+ m.Save("clock", &x.clock)
+ m.Save("listener", &x.listener)
+ m.Save("setting", &x.setting)
+ m.Save("paused", &x.paused)
+}
+
+func (x *Timer) afterLoad() {}
+func (x *Timer) load(m state.Map) {
+ m.Load("clock", &x.clock)
+ m.Load("listener", &x.listener)
+ m.Load("setting", &x.setting)
+ m.Load("paused", &x.paused)
+}
+
+func init() {
+ state.Register("time.Time", (*Time)(nil), state.Fns{Save: (*Time).save, Load: (*Time).load})
+ state.Register("time.Setting", (*Setting)(nil), state.Fns{Save: (*Setting).save, Load: (*Setting).load})
+ state.Register("time.Timer", (*Timer)(nil), state.Fns{Save: (*Timer).save, Load: (*Timer).load})
+}
diff --git a/pkg/sentry/kernel/timekeeper_test.go b/pkg/sentry/kernel/timekeeper_test.go
deleted file mode 100644
index 849c5b646..000000000
--- a/pkg/sentry/kernel/timekeeper_test.go
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package kernel
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/pgalloc"
- sentrytime "gvisor.dev/gvisor/pkg/sentry/time"
- "gvisor.dev/gvisor/pkg/sentry/usage"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-// mockClocks is a sentrytime.Clocks that simply returns the times in the
-// struct.
-type mockClocks struct {
- monotonic int64
- realtime int64
-}
-
-// Update implements sentrytime.Clocks.Update. It does nothing.
-func (*mockClocks) Update() (monotonicParams sentrytime.Parameters, monotonicOk bool, realtimeParam sentrytime.Parameters, realtimeOk bool) {
- return
-}
-
-// Update implements sentrytime.Clocks.GetTime.
-func (c *mockClocks) GetTime(id sentrytime.ClockID) (int64, error) {
- switch id {
- case sentrytime.Monotonic:
- return c.monotonic, nil
- case sentrytime.Realtime:
- return c.realtime, nil
- default:
- return 0, syserror.EINVAL
- }
-}
-
-// stateTestClocklessTimekeeper returns a test Timekeeper which has not had
-// SetClocks called.
-func stateTestClocklessTimekeeper(tb testing.TB) *Timekeeper {
- ctx := contexttest.Context(tb)
- mfp := pgalloc.MemoryFileProviderFromContext(ctx)
- fr, err := mfp.MemoryFile().Allocate(usermem.PageSize, usage.Anonymous)
- if err != nil {
- tb.Fatalf("failed to allocate memory: %v", err)
- }
- return &Timekeeper{
- params: NewVDSOParamPage(mfp, fr),
- }
-}
-
-func stateTestTimekeeper(tb testing.TB) *Timekeeper {
- t := stateTestClocklessTimekeeper(tb)
- t.SetClocks(sentrytime.NewCalibratedClocks())
- return t
-}
-
-// TestTimekeeperMonotonicZero tests that monotonic time starts at zero.
-func TestTimekeeperMonotonicZero(t *testing.T) {
- c := &mockClocks{
- monotonic: 100000,
- }
-
- tk := stateTestClocklessTimekeeper(t)
- tk.SetClocks(c)
- defer tk.Destroy()
-
- now, err := tk.GetTime(sentrytime.Monotonic)
- if err != nil {
- t.Errorf("GetTime err got %v want nil", err)
- }
- if now != 0 {
- t.Errorf("GetTime got %d want 0", now)
- }
-
- c.monotonic += 10
-
- now, err = tk.GetTime(sentrytime.Monotonic)
- if err != nil {
- t.Errorf("GetTime err got %v want nil", err)
- }
- if now != 10 {
- t.Errorf("GetTime got %d want 10", now)
- }
-}
-
-// TestTimekeeperMonotonicJumpForward tests that monotonic time jumps forward
-// after restore.
-func TestTimekeeperMonotonicForward(t *testing.T) {
- c := &mockClocks{
- monotonic: 900000,
- realtime: 600000,
- }
-
- tk := stateTestClocklessTimekeeper(t)
- tk.restored = make(chan struct{})
- tk.saveMonotonic = 100000
- tk.saveRealtime = 400000
- tk.SetClocks(c)
- defer tk.Destroy()
-
- // The monotonic clock should jump ahead by 200000 to 300000.
- //
- // The new system monotonic time (900000) is irrelevant to what the app
- // sees.
- now, err := tk.GetTime(sentrytime.Monotonic)
- if err != nil {
- t.Errorf("GetTime err got %v want nil", err)
- }
- if now != 300000 {
- t.Errorf("GetTime got %d want 300000", now)
- }
-}
-
-// TestTimekeeperMonotonicJumpBackwards tests that monotonic time does not jump
-// backwards when realtime goes backwards.
-func TestTimekeeperMonotonicJumpBackwards(t *testing.T) {
- c := &mockClocks{
- monotonic: 900000,
- realtime: 400000,
- }
-
- tk := stateTestClocklessTimekeeper(t)
- tk.restored = make(chan struct{})
- tk.saveMonotonic = 100000
- tk.saveRealtime = 600000
- tk.SetClocks(c)
- defer tk.Destroy()
-
- // The monotonic clock should remain at 100000.
- //
- // The new system monotonic time (900000) is irrelevant to what the app
- // sees and we don't want to jump the monotonic clock backwards like
- // realtime did.
- now, err := tk.GetTime(sentrytime.Monotonic)
- if err != nil {
- t.Errorf("GetTime err got %v want nil", err)
- }
- if now != 100000 {
- t.Errorf("GetTime got %d want 100000", now)
- }
-}
diff --git a/pkg/sentry/kernel/uncaught_signal.proto b/pkg/sentry/kernel/uncaught_signal.proto
deleted file mode 100644
index 0bdb062cb..000000000
--- a/pkg/sentry/kernel/uncaught_signal.proto
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto3";
-
-package gvisor;
-
-import "pkg/sentry/arch/registers.proto";
-
-message UncaughtSignal {
- // Thread ID.
- int32 tid = 1;
-
- // Process ID.
- int32 pid = 2;
-
- // Registers at the time of the fault or signal.
- Registers registers = 3;
-
- // Signal number.
- int32 signal_number = 4;
-
- // The memory location which caused the fault (set if applicable, 0
- // otherwise). This will be set for SIGILL, SIGFPE, SIGSEGV, and SIGBUS.
- uint64 fault_addr = 5;
-}
diff --git a/pkg/sentry/kernel/uncaught_signal_go_proto/uncaught_signal.pb.go b/pkg/sentry/kernel/uncaught_signal_go_proto/uncaught_signal.pb.go
new file mode 100755
index 000000000..822e549ab
--- /dev/null
+++ b/pkg/sentry/kernel/uncaught_signal_go_proto/uncaught_signal.pb.go
@@ -0,0 +1,119 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: pkg/sentry/kernel/uncaught_signal.proto
+
+package gvisor
+
+import (
+ fmt "fmt"
+ proto "github.com/golang/protobuf/proto"
+ registers_go_proto "gvisor.dev/gvisor/pkg/sentry/arch/registers_go_proto"
+ math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+type UncaughtSignal struct {
+ Tid int32 `protobuf:"varint,1,opt,name=tid,proto3" json:"tid,omitempty"`
+ Pid int32 `protobuf:"varint,2,opt,name=pid,proto3" json:"pid,omitempty"`
+ Registers *registers_go_proto.Registers `protobuf:"bytes,3,opt,name=registers,proto3" json:"registers,omitempty"`
+ SignalNumber int32 `protobuf:"varint,4,opt,name=signal_number,json=signalNumber,proto3" json:"signal_number,omitempty"`
+ FaultAddr uint64 `protobuf:"varint,5,opt,name=fault_addr,json=faultAddr,proto3" json:"fault_addr,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *UncaughtSignal) Reset() { *m = UncaughtSignal{} }
+func (m *UncaughtSignal) String() string { return proto.CompactTextString(m) }
+func (*UncaughtSignal) ProtoMessage() {}
+func (*UncaughtSignal) Descriptor() ([]byte, []int) {
+ return fileDescriptor_5ca9e03e13704688, []int{0}
+}
+
+func (m *UncaughtSignal) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_UncaughtSignal.Unmarshal(m, b)
+}
+func (m *UncaughtSignal) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_UncaughtSignal.Marshal(b, m, deterministic)
+}
+func (m *UncaughtSignal) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_UncaughtSignal.Merge(m, src)
+}
+func (m *UncaughtSignal) XXX_Size() int {
+ return xxx_messageInfo_UncaughtSignal.Size(m)
+}
+func (m *UncaughtSignal) XXX_DiscardUnknown() {
+ xxx_messageInfo_UncaughtSignal.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_UncaughtSignal proto.InternalMessageInfo
+
+func (m *UncaughtSignal) GetTid() int32 {
+ if m != nil {
+ return m.Tid
+ }
+ return 0
+}
+
+func (m *UncaughtSignal) GetPid() int32 {
+ if m != nil {
+ return m.Pid
+ }
+ return 0
+}
+
+func (m *UncaughtSignal) GetRegisters() *registers_go_proto.Registers {
+ if m != nil {
+ return m.Registers
+ }
+ return nil
+}
+
+func (m *UncaughtSignal) GetSignalNumber() int32 {
+ if m != nil {
+ return m.SignalNumber
+ }
+ return 0
+}
+
+func (m *UncaughtSignal) GetFaultAddr() uint64 {
+ if m != nil {
+ return m.FaultAddr
+ }
+ return 0
+}
+
+func init() {
+ proto.RegisterType((*UncaughtSignal)(nil), "gvisor.UncaughtSignal")
+}
+
+func init() {
+ proto.RegisterFile("pkg/sentry/kernel/uncaught_signal.proto", fileDescriptor_5ca9e03e13704688)
+}
+
+var fileDescriptor_5ca9e03e13704688 = []byte{
+ // 210 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x8e, 0x4d, 0x4a, 0xc6, 0x30,
+ 0x10, 0x86, 0x89, 0xfd, 0x81, 0xc6, 0x1f, 0x34, 0xab, 0x20, 0x88, 0x45, 0x17, 0x76, 0xd5, 0x80,
+ 0x9e, 0xc0, 0x0b, 0xb8, 0x88, 0xb8, 0x2e, 0x69, 0x13, 0xd3, 0xd0, 0x9a, 0x86, 0x49, 0x22, 0x78,
+ 0x24, 0x6f, 0x29, 0x4d, 0xd4, 0xef, 0xdb, 0x0d, 0xcf, 0xbc, 0xf3, 0xcc, 0x8b, 0x1f, 0xdc, 0xa2,
+ 0x99, 0x57, 0x36, 0xc0, 0x17, 0x5b, 0x14, 0x58, 0xb5, 0xb2, 0x68, 0x27, 0x11, 0xf5, 0x1c, 0x06,
+ 0x6f, 0xb4, 0x15, 0x6b, 0xef, 0x60, 0x0b, 0x1b, 0xa9, 0xf5, 0xa7, 0xf1, 0x1b, 0x5c, 0xdf, 0x1e,
+ 0x1d, 0x08, 0x98, 0x66, 0x06, 0x4a, 0x1b, 0x1f, 0x14, 0xf8, 0x1c, 0xbc, 0xfb, 0x46, 0xf8, 0xe2,
+ 0xed, 0x57, 0xf1, 0x9a, 0x0c, 0xe4, 0x12, 0x17, 0xc1, 0x48, 0x8a, 0x5a, 0xd4, 0x55, 0x7c, 0x1f,
+ 0x77, 0xe2, 0x8c, 0xa4, 0x27, 0x99, 0x38, 0x23, 0x09, 0xc3, 0xcd, 0xbf, 0x89, 0x16, 0x2d, 0xea,
+ 0x4e, 0x1f, 0xaf, 0xfa, 0xfc, 0xb3, 0xe7, 0x7f, 0x0b, 0x7e, 0xc8, 0x90, 0x7b, 0x7c, 0x9e, 0x0b,
+ 0x0e, 0x36, 0x7e, 0x8c, 0x0a, 0x68, 0x99, 0x64, 0x67, 0x19, 0xbe, 0x24, 0x46, 0x6e, 0x30, 0x7e,
+ 0x17, 0x71, 0x0d, 0x83, 0x90, 0x12, 0x68, 0xd5, 0xa2, 0xae, 0xe4, 0x4d, 0x22, 0xcf, 0x52, 0xc2,
+ 0x58, 0xa7, 0xca, 0x4f, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xfd, 0x62, 0x54, 0xdf, 0x06, 0x01,
+ 0x00, 0x00,
+}
diff --git a/pkg/sentry/limits/BUILD b/pkg/sentry/limits/BUILD
deleted file mode 100644
index 59649c770..000000000
--- a/pkg/sentry/limits/BUILD
+++ /dev/null
@@ -1,29 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "limits",
- srcs = [
- "context.go",
- "limits.go",
- "linux.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/limits",
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/sentry/context",
- ],
-)
-
-go_test(
- name = "limits_test",
- size = "small",
- srcs = [
- "limits_test.go",
- ],
- embed = [":limits"],
-)
diff --git a/pkg/sentry/limits/limits_state_autogen.go b/pkg/sentry/limits/limits_state_autogen.go
new file mode 100755
index 000000000..912a99cae
--- /dev/null
+++ b/pkg/sentry/limits/limits_state_autogen.go
@@ -0,0 +1,36 @@
+// automatically generated by stateify.
+
+package limits
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *Limit) beforeSave() {}
+func (x *Limit) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Cur", &x.Cur)
+ m.Save("Max", &x.Max)
+}
+
+func (x *Limit) afterLoad() {}
+func (x *Limit) load(m state.Map) {
+ m.Load("Cur", &x.Cur)
+ m.Load("Max", &x.Max)
+}
+
+func (x *LimitSet) beforeSave() {}
+func (x *LimitSet) save(m state.Map) {
+ x.beforeSave()
+ m.Save("data", &x.data)
+}
+
+func (x *LimitSet) afterLoad() {}
+func (x *LimitSet) load(m state.Map) {
+ m.Load("data", &x.data)
+}
+
+func init() {
+ state.Register("limits.Limit", (*Limit)(nil), state.Fns{Save: (*Limit).save, Load: (*Limit).load})
+ state.Register("limits.LimitSet", (*LimitSet)(nil), state.Fns{Save: (*LimitSet).save, Load: (*LimitSet).load})
+}
diff --git a/pkg/sentry/limits/limits_test.go b/pkg/sentry/limits/limits_test.go
deleted file mode 100644
index 658a20f56..000000000
--- a/pkg/sentry/limits/limits_test.go
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package limits
-
-import (
- "syscall"
- "testing"
-)
-
-func TestSet(t *testing.T) {
- testCases := []struct {
- limit Limit
- privileged bool
- expectedErr error
- }{
- {limit: Limit{Cur: 50, Max: 50}, privileged: false, expectedErr: nil},
- {limit: Limit{Cur: 20, Max: 50}, privileged: false, expectedErr: nil},
- {limit: Limit{Cur: 20, Max: 60}, privileged: false, expectedErr: syscall.EPERM},
- {limit: Limit{Cur: 60, Max: 50}, privileged: false, expectedErr: syscall.EINVAL},
- {limit: Limit{Cur: 11, Max: 10}, privileged: false, expectedErr: syscall.EINVAL},
- {limit: Limit{Cur: 20, Max: 60}, privileged: true, expectedErr: nil},
- }
-
- ls := NewLimitSet()
- for _, tc := range testCases {
- if _, err := ls.Set(1, tc.limit, tc.privileged); err != tc.expectedErr {
- t.Fatalf("Tried to set Limit to %+v and privilege %t: got %v, wanted %v", tc.limit, tc.privileged, err, tc.expectedErr)
- }
- }
-
-}
diff --git a/pkg/sentry/loader/BUILD b/pkg/sentry/loader/BUILD
deleted file mode 100644
index 3b322f5f3..000000000
--- a/pkg/sentry/loader/BUILD
+++ /dev/null
@@ -1,51 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_embed_data")
-
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_embed_data(
- name = "vdso_bin",
- src = "//vdso:vdso.so",
- package = "loader",
- var = "vdsoBin",
-)
-
-go_library(
- name = "loader",
- srcs = [
- "elf.go",
- "interpreter.go",
- "loader.go",
- "vdso.go",
- "vdso_state.go",
- ":vdso_bin",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/loader",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi",
- "//pkg/abi/linux",
- "//pkg/binary",
- "//pkg/cpuid",
- "//pkg/log",
- "//pkg/rand",
- "//pkg/sentry/arch",
- "//pkg/sentry/context",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/anon",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/limits",
- "//pkg/sentry/memmap",
- "//pkg/sentry/mm",
- "//pkg/sentry/pgalloc",
- "//pkg/sentry/safemem",
- "//pkg/sentry/uniqueid",
- "//pkg/sentry/usage",
- "//pkg/sentry/usermem",
- "//pkg/syserr",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/loader/loader_state_autogen.go b/pkg/sentry/loader/loader_state_autogen.go
new file mode 100755
index 000000000..4fe3c779d
--- /dev/null
+++ b/pkg/sentry/loader/loader_state_autogen.go
@@ -0,0 +1,57 @@
+// automatically generated by stateify.
+
+package loader
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *VDSO) beforeSave() {}
+func (x *VDSO) save(m state.Map) {
+ x.beforeSave()
+ var phdrs []elfProgHeader = x.savePhdrs()
+ m.SaveValue("phdrs", phdrs)
+ m.Save("ParamPage", &x.ParamPage)
+ m.Save("vdso", &x.vdso)
+ m.Save("os", &x.os)
+ m.Save("arch", &x.arch)
+}
+
+func (x *VDSO) afterLoad() {}
+func (x *VDSO) load(m state.Map) {
+ m.Load("ParamPage", &x.ParamPage)
+ m.Load("vdso", &x.vdso)
+ m.Load("os", &x.os)
+ m.Load("arch", &x.arch)
+ m.LoadValue("phdrs", new([]elfProgHeader), func(y interface{}) { x.loadPhdrs(y.([]elfProgHeader)) })
+}
+
+func (x *elfProgHeader) beforeSave() {}
+func (x *elfProgHeader) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Type", &x.Type)
+ m.Save("Flags", &x.Flags)
+ m.Save("Off", &x.Off)
+ m.Save("Vaddr", &x.Vaddr)
+ m.Save("Paddr", &x.Paddr)
+ m.Save("Filesz", &x.Filesz)
+ m.Save("Memsz", &x.Memsz)
+ m.Save("Align", &x.Align)
+}
+
+func (x *elfProgHeader) afterLoad() {}
+func (x *elfProgHeader) load(m state.Map) {
+ m.Load("Type", &x.Type)
+ m.Load("Flags", &x.Flags)
+ m.Load("Off", &x.Off)
+ m.Load("Vaddr", &x.Vaddr)
+ m.Load("Paddr", &x.Paddr)
+ m.Load("Filesz", &x.Filesz)
+ m.Load("Memsz", &x.Memsz)
+ m.Load("Align", &x.Align)
+}
+
+func init() {
+ state.Register("loader.VDSO", (*VDSO)(nil), state.Fns{Save: (*VDSO).save, Load: (*VDSO).load})
+ state.Register("loader.elfProgHeader", (*elfProgHeader)(nil), state.Fns{Save: (*elfProgHeader).save, Load: (*elfProgHeader).load})
+}
diff --git a/pkg/sentry/loader/vdso_bin.go b/pkg/sentry/loader/vdso_bin.go
new file mode 100755
index 000000000..3c2477b6c
--- /dev/null
+++ b/pkg/sentry/loader/vdso_bin.go
@@ -0,0 +1,5 @@
+// Generated by go_embed_data for //pkg/sentry/loader:vdso_bin. DO NOT EDIT.
+
+package loader
+
+var vdsoBin = []byte("ELF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00\x98\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x008\x00\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00p\xff\xff\xff\xff\xff\x00\x00p\xff\xff\xff\xff\xffC\x00\x00\x00\x00\x00\x00C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00P\x00\x00\x00\x00\x00\x00Pp\xff\xff\xff\xff\xffPp\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00P\xe5td\x00\x00\x00@\x00\x00\x00\x00\x00\x00@p\xff\xff\xff\xff\xff@p\xff\xff\xff\xff\xff<\x00\x00\x00\x00\x00\x00\x00<\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\xc0p\xff\xff\xff\xff\xff\"\x00\x00\x00\x00\x00\x00\x00'\x00\x00\x00\x00 \x000p\xff\xff\xff\xff\xff&\x00\x00\x00\x00\x00\x00\x00<\x00\x00\x00\x00 \x00`p\xff\xff\xff\xff\xff_\x00\x00\x00\x00\x00\x00\x00P\x00\x00\x00\x00 \x00\xf0p\xff\xff\xff\xff\xff\n\x00\x00\x00\x00\x00\x00\x00^\x00\x00\x00\"\x00 \x00\xc0p\xff\xff\xff\xff\xff\"\x00\x00\x00\x00\x00\x00\x00c\x00\x00\x00\"\x00 \x000p\xff\xff\xff\xff\xff&\x00\x00\x00\x00\x00\x00\x00q\x00\x00\x00\"\x00 \x00`p\xff\xff\xff\xff\xff_\x00\x00\x00\x00\x00\x00\x00~\x00\x00\x00\"\x00 \x00\xf0p\xff\xff\xff\xff\xff\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00linux-vdso.so.1\x00LINUX_2.6\x00__vdso_time\x00__vdso_clock_gettime\x00__vdso_gettimeofday\x00__vdso_getcpu\x00time\x00clock_gettime\x00gettimeofday\x00getcpu\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa1\xbf\xee \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6u\xae\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00;<\x00\x00\x00\x00\x00\x00\xf0 \x00\x00X\x00\x00\x00 \x00\x00p\x00\x00\x00\x80 \x00\x00\x98\x00\x00\x00\xb0 \x00\x00\xb8\x00\x00\x00\xc0 \x00\x00\xd0\x00\x00\x00`\x00\x00\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00zR\x00x \x90\x00\x00\x00\x00\x00\x00\x00\x00\x90 \x00\x00&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00\x00\x004\x00\x00\x00\xa8 \x00\x00_\x00\x00\x00\x00BAD0\x83\x8eTAB\x00\x00\x00\\\x00\x00\x00\xe0 \x00\x00\"\x00\x00\x00\x00AD \x83[A\x00\x00\x00\x00|\x00\x00\x00\xf0 \x00\x00\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x94\x00\x00\x00\xe8 \x00\x00\xa0\x00\x00\x00\x00A\x83\x90AM\x00\x00\x00\xb4\x00\x00\x00h \x00\x00\xa3\x00\x00\x00\x00A\x83\x90AP\x00\x00\x00\x00\x00\x00\x00`p\xff\xff\xff\xff\xff \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Pp\xff\xff\xff\xff\xff\n\x00\x00\x00\x00\x00\x00\x00\x85\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 p\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\xff\xffo\x00\x00\x00\x00\xd6p\xff\xff\xff\xff\xff\xfc\xff\xffo\x00\x00\x00\x00\xecp\xff\xff\xff\xff\xff\xfd\xff\xffo\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x83\xfft\x83\xfft \x85\xffuH\x89\xf7\xe9\xba\x00\x00\x00H\x89\xf7\xe9R\x00\x00\xb8\xe4\x00\x00\x00\xc3f.\x84\x00\x00\x00\x00\x00AVSH\x83\xecI\x89\xf6H\x85\xfft:H\x89\xfbH\x8d|$\xe8\x84\x00\x00\x00\x85\xc0u7H\x8bD$H\x89H\xb8\xcf\xf7S㥛\xc4 H\xf7l$H\x89\xd0H\xc1\xe8?H\xc1\xfaH\xc2H\x89S1\xc0M\x85\xf6tI\xc7\x00\x00\x00\x00H\x83\xc4[A^ÐSH\x83\xecH\x89\xfbH\x89\xe7\xe80\x00\x00\x00H\x8b$H\x85\xdbtH\x89H\x83\xc4[\xc3f.\x84\x00\x00\x00\x00\x00@\x00\xb85\x00\x00H\x98Ð\x90\x90\x90\x90\x90SI\x89\xf8H\x8d \xf5\xde\xff\xffH\x8b1f\x90Hc\xdeH\x83\xe3\xfeH\x8by(L\x8bY0L\x8bI8L\x8bQ@\xae\xe81H\x8b1H9\xdeu\xdcH\x85\xfftYH\xc1\xe2 \x89\xc0H \xd01\xc9L)\xd8HM\xc8H\xb8\x00\x00\x00\x00\x00ʚ;1\xd2I\xf7\xf2H\xf7\xe1H\xa4\xc2 I\xd1L\x89\xc8H\xc1\xe8 H\xb9SZ\x9b\xa0/\xb8D\x00H\xf7\xe1H\xc1\xea Hi\xc2\x00ʚ;I)\xc1I\x89M\x89H1\xc0[\xc31\xffL\x89Ƹ\xe4\x00\x00\x00[\xc3SI\x89\xf8H\x8d U\xde\xff\xffH\x8b1f\x90Hc\xdeH\x83\xe3\xfeH\x8byL\x8bYL\x8bIL\x8bQ \xae\xe81H\x8b1H9\xdeu\xdcH\x85\xfftYH\xc1\xe2 \x89\xc0H \xd01\xc9L)\xd8HM\xc8H\xb8\x00\x00\x00\x00\x00ʚ;1\xd2I\xf7\xf2H\xf7\xe1H\xa4\xc2 I\xd1L\x89\xc8H\xc1\xe8 H\xb9SZ\x9b\xa0/\xb8D\x00H\xf7\xe1H\xc1\xea Hi\xc2\x00ʚ;I)\xc1I\x89M\x89H1\xc0[ÿ\x00\x00\x00L\x89Ƹ\xe4\x00\x00\x00[\xc3\x00clang version 9.0.0 (trunk 352865)\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00GNU\x00gold 1.11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\xf1\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00p\xff\xff\xff\xff\xff\xa0\x00\x00\x00\x00\x00\x00\x009\x00\x00\x00\x00 \x00\xa0p\xff\xff\xff\xff\xff\xa3\x00\x00\x00\x00\x00\x00\x00]\x00\x00\x00\x00Pp\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00f\x00\x00\x00\x00\x00\xf1\xff\x00\x00p\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00s\x00\x00\x00\x00\x00\xf1\xff\x00\xf0o\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00{\x00\x00\x00\x00\xf1\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x85\x00\x00\x00\x00 \x00\xc0p\xff\xff\xff\xff\xff\"\x00\x00\x00\x00\x00\x00\x00\x91\x00\x00\x00\x00 \x000p\xff\xff\xff\xff\xff&\x00\x00\x00\x00\x00\x00\x00\xa6\x00\x00\x00\x00 \x00`p\xff\xff\xff\xff\xff_\x00\x00\x00\x00\x00\x00\x00\xba\x00\x00\x00\x00 \x00\xf0p\xff\xff\xff\xff\xff\n\x00\x00\x00\x00\x00\x00\x00\xc8\x00\x00\x00\"\x00 \x00\xc0p\xff\xff\xff\xff\xff\"\x00\x00\x00\x00\x00\x00\x00\xcd\x00\x00\x00\"\x00 \x000p\xff\xff\xff\xff\xff&\x00\x00\x00\x00\x00\x00\x00\xdb\x00\x00\x00\"\x00 \x00`p\xff\xff\xff\xff\xff_\x00\x00\x00\x00\x00\x00\x00\xe8\x00\x00\x00\"\x00 \x00\xf0p\xff\xff\xff\xff\xff\n\x00\x00\x00\x00\x00\x00\x00\x00vdso.cc\x00vdso_time.cc\x00_ZN4vdso13ClockRealtimeEP8timespec\x00_ZN4vdso14ClockMonotonicEP8timespec\x00_DYNAMIC\x00VDSO_PRELINK\x00_params\x00LINUX_2.6\x00__vdso_time\x00__vdso_clock_gettime\x00__vdso_gettimeofday\x00__vdso_getcpu\x00time\x00clock_gettime\x00gettimeofday\x00getcpu\x00\x00.text\x00.comment\x00.dynstr\x00.eh_frame_hdr\x00.gnu.version\x00.dynsym\x00.hash\x00.note\x00.eh_frame\x00.gnu.version_d\x00.dynamic\x00.shstrtab\x00.strtab\x00.symtab\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00;\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 p\xff\xff\xff\xff\xff \x00\x00\x00\x00\x00\x00<\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x003\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`p\xff\xff\xff\xff\xff`\x00\x00\x00\x00\x00\x00\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Pp\xff\xff\xff\xff\xffP\x00\x00\x00\x00\x00\x00\x85\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&\x00\x00\x00\xff\xff\xffo\x00\x00\x00\x00\x00\x00\x00\xd6p\xff\xff\xff\xff\xff\xd6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Q\x00\x00\x00\xfd\xff\xffo\x00\x00\x00\x00\x00\x00\x00\xecp\xff\xff\xff\xff\xff\xec\x00\x00\x00\x00\x00\x008\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@p\xff\xff\xff\xff\xff@\x00\x00\x00\x00\x00\x00<\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80p\xff\xff\xff\xff\xff\x80\x00\x00\x00\x00\x00\x00\xd0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Pp\xff\xff\xff\xff\xffP\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000p\xff\xff\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00C\x00\x00\x00\x00\x00\x00$\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00{\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x88\x00\x00\x00\x00\x00\x00\x98\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00s\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\xef\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00i\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x83\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
diff --git a/pkg/sentry/memmap/BUILD b/pkg/sentry/memmap/BUILD
deleted file mode 100644
index 9687e7e76..000000000
--- a/pkg/sentry/memmap/BUILD
+++ /dev/null
@@ -1,59 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_template_instance(
- name = "mappable_range",
- out = "mappable_range.go",
- package = "memmap",
- prefix = "Mappable",
- template = "//pkg/segment:generic_range",
- types = {
- "T": "uint64",
- },
-)
-
-go_template_instance(
- name = "mapping_set_impl",
- out = "mapping_set_impl.go",
- package = "memmap",
- prefix = "Mapping",
- template = "//pkg/segment:generic_set",
- types = {
- "Key": "uint64",
- "Range": "MappableRange",
- "Value": "MappingsOfRange",
- "Functions": "mappingSetFunctions",
- },
-)
-
-go_library(
- name = "memmap",
- srcs = [
- "mappable_range.go",
- "mapping_set.go",
- "mapping_set_impl.go",
- "memmap.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/memmap",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/log",
- "//pkg/refs",
- "//pkg/sentry/context",
- "//pkg/sentry/platform",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- ],
-)
-
-go_test(
- name = "memmap_test",
- size = "small",
- srcs = ["mapping_set_test.go"],
- embed = [":memmap"],
- deps = ["//pkg/sentry/usermem"],
-)
diff --git a/pkg/sentry/memmap/mappable_range.go b/pkg/sentry/memmap/mappable_range.go
new file mode 100755
index 000000000..6b6c2c685
--- /dev/null
+++ b/pkg/sentry/memmap/mappable_range.go
@@ -0,0 +1,62 @@
+package memmap
+
+// A Range represents a contiguous range of T.
+//
+// +stateify savable
+type MappableRange struct {
+ // Start is the inclusive start of the range.
+ Start uint64
+
+ // End is the exclusive end of the range.
+ End uint64
+}
+
+// WellFormed returns true if r.Start <= r.End. All other methods on a Range
+// require that the Range is well-formed.
+func (r MappableRange) WellFormed() bool {
+ return r.Start <= r.End
+}
+
+// Length returns the length of the range.
+func (r MappableRange) Length() uint64 {
+ return r.End - r.Start
+}
+
+// Contains returns true if r contains x.
+func (r MappableRange) Contains(x uint64) bool {
+ return r.Start <= x && x < r.End
+}
+
+// Overlaps returns true if r and r2 overlap.
+func (r MappableRange) Overlaps(r2 MappableRange) bool {
+ return r.Start < r2.End && r2.Start < r.End
+}
+
+// IsSupersetOf returns true if r is a superset of r2; that is, the range r2 is
+// contained within r.
+func (r MappableRange) IsSupersetOf(r2 MappableRange) bool {
+ return r.Start <= r2.Start && r.End >= r2.End
+}
+
+// Intersect returns a range consisting of the intersection between r and r2.
+// If r and r2 do not overlap, Intersect returns a range with unspecified
+// bounds, but for which Length() == 0.
+func (r MappableRange) Intersect(r2 MappableRange) MappableRange {
+ if r.Start < r2.Start {
+ r.Start = r2.Start
+ }
+ if r.End > r2.End {
+ r.End = r2.End
+ }
+ if r.End < r.Start {
+ r.End = r.Start
+ }
+ return r
+}
+
+// CanSplitAt returns true if it is legal to split a segment spanning the range
+// r at x; that is, splitting at x would produce two ranges, both of which have
+// non-zero length.
+func (r MappableRange) CanSplitAt(x uint64) bool {
+ return r.Contains(x) && r.Start < x
+}
diff --git a/pkg/sentry/memmap/mapping_set_impl.go b/pkg/sentry/memmap/mapping_set_impl.go
new file mode 100755
index 000000000..e632f28a5
--- /dev/null
+++ b/pkg/sentry/memmap/mapping_set_impl.go
@@ -0,0 +1,1270 @@
+package memmap
+
+import (
+ "bytes"
+ "fmt"
+)
+
+const (
+ // minDegree is the minimum degree of an internal node in a Set B-tree.
+ //
+ // - Any non-root node has at least minDegree-1 segments.
+ //
+ // - Any non-root internal (non-leaf) node has at least minDegree children.
+ //
+ // - The root node may have fewer than minDegree-1 segments, but it may
+ // only have 0 segments if the tree is empty.
+ //
+ // Our implementation requires minDegree >= 3. Higher values of minDegree
+ // usually improve performance, but increase memory usage for small sets.
+ MappingminDegree = 3
+
+ MappingmaxDegree = 2 * MappingminDegree
+)
+
+// A Set is a mapping of segments with non-overlapping Range keys. The zero
+// value for a Set is an empty set. Set values are not safely movable nor
+// copyable. Set is thread-compatible.
+//
+// +stateify savable
+type MappingSet struct {
+ root Mappingnode `state:".(*MappingSegmentDataSlices)"`
+}
+
+// IsEmpty returns true if the set contains no segments.
+func (s *MappingSet) IsEmpty() bool {
+ return s.root.nrSegments == 0
+}
+
+// IsEmptyRange returns true iff no segments in the set overlap the given
+// range. This is semantically equivalent to s.SpanRange(r) == 0, but may be
+// more efficient.
+func (s *MappingSet) IsEmptyRange(r MappableRange) bool {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return true
+ }
+ _, gap := s.Find(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ return r.End <= gap.End()
+}
+
+// Span returns the total size of all segments in the set.
+func (s *MappingSet) Span() uint64 {
+ var sz uint64
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sz += seg.Range().Length()
+ }
+ return sz
+}
+
+// SpanRange returns the total size of the intersection of segments in the set
+// with the given range.
+func (s *MappingSet) SpanRange(r MappableRange) uint64 {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return 0
+ }
+ var sz uint64
+ for seg := s.LowerBoundSegment(r.Start); seg.Ok() && seg.Start() < r.End; seg = seg.NextSegment() {
+ sz += seg.Range().Intersect(r).Length()
+ }
+ return sz
+}
+
+// FirstSegment returns the first segment in the set. If the set is empty,
+// FirstSegment returns a terminal iterator.
+func (s *MappingSet) FirstSegment() MappingIterator {
+ if s.root.nrSegments == 0 {
+ return MappingIterator{}
+ }
+ return s.root.firstSegment()
+}
+
+// LastSegment returns the last segment in the set. If the set is empty,
+// LastSegment returns a terminal iterator.
+func (s *MappingSet) LastSegment() MappingIterator {
+ if s.root.nrSegments == 0 {
+ return MappingIterator{}
+ }
+ return s.root.lastSegment()
+}
+
+// FirstGap returns the first gap in the set.
+func (s *MappingSet) FirstGap() MappingGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return MappingGapIterator{n, 0}
+}
+
+// LastGap returns the last gap in the set.
+func (s *MappingSet) LastGap() MappingGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return MappingGapIterator{n, n.nrSegments}
+}
+
+// Find returns the segment or gap whose range contains the given key. If a
+// segment is found, the returned Iterator is non-terminal and the
+// returned GapIterator is terminal. Otherwise, the returned Iterator is
+// terminal and the returned GapIterator is non-terminal.
+func (s *MappingSet) Find(key uint64) (MappingIterator, MappingGapIterator) {
+ n := &s.root
+ for {
+
+ lower := 0
+ upper := n.nrSegments
+ for lower < upper {
+ i := lower + (upper-lower)/2
+ if r := n.keys[i]; key < r.End {
+ if key >= r.Start {
+ return MappingIterator{n, i}, MappingGapIterator{}
+ }
+ upper = i
+ } else {
+ lower = i + 1
+ }
+ }
+ i := lower
+ if !n.hasChildren {
+ return MappingIterator{}, MappingGapIterator{n, i}
+ }
+ n = n.children[i]
+ }
+}
+
+// FindSegment returns the segment whose range contains the given key. If no
+// such segment exists, FindSegment returns a terminal iterator.
+func (s *MappingSet) FindSegment(key uint64) MappingIterator {
+ seg, _ := s.Find(key)
+ return seg
+}
+
+// LowerBoundSegment returns the segment with the lowest range that contains a
+// key greater than or equal to min. If no such segment exists,
+// LowerBoundSegment returns a terminal iterator.
+func (s *MappingSet) LowerBoundSegment(min uint64) MappingIterator {
+ seg, gap := s.Find(min)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.NextSegment()
+}
+
+// UpperBoundSegment returns the segment with the highest range that contains a
+// key less than or equal to max. If no such segment exists, UpperBoundSegment
+// returns a terminal iterator.
+func (s *MappingSet) UpperBoundSegment(max uint64) MappingIterator {
+ seg, gap := s.Find(max)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.PrevSegment()
+}
+
+// FindGap returns the gap containing the given key. If no such gap exists
+// (i.e. the set contains a segment containing that key), FindGap returns a
+// terminal iterator.
+func (s *MappingSet) FindGap(key uint64) MappingGapIterator {
+ _, gap := s.Find(key)
+ return gap
+}
+
+// LowerBoundGap returns the gap with the lowest range that is greater than or
+// equal to min.
+func (s *MappingSet) LowerBoundGap(min uint64) MappingGapIterator {
+ seg, gap := s.Find(min)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.NextGap()
+}
+
+// UpperBoundGap returns the gap with the highest range that is less than or
+// equal to max.
+func (s *MappingSet) UpperBoundGap(max uint64) MappingGapIterator {
+ seg, gap := s.Find(max)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.PrevGap()
+}
+
+// Add inserts the given segment into the set and returns true. If the new
+// segment can be merged with adjacent segments, Add will do so. If the new
+// segment would overlap an existing segment, Add returns false. If Add
+// succeeds, all existing iterators are invalidated.
+func (s *MappingSet) Add(r MappableRange, val MappingsOfRange) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.Insert(gap, r, val)
+ return true
+}
+
+// AddWithoutMerging inserts the given segment into the set and returns true.
+// If it would overlap an existing segment, AddWithoutMerging does nothing and
+// returns false. If AddWithoutMerging succeeds, all existing iterators are
+// invalidated.
+func (s *MappingSet) AddWithoutMerging(r MappableRange, val MappingsOfRange) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.InsertWithoutMergingUnchecked(gap, r, val)
+ return true
+}
+
+// Insert inserts the given segment into the given gap. If the new segment can
+// be merged with adjacent segments, Insert will do so. Insert returns an
+// iterator to the segment containing the inserted value (which may have been
+// merged with other values). All existing iterators (including gap, but not
+// including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid, Insert panics.
+//
+// Insert is semantically equivalent to a InsertWithoutMerging followed by a
+// Merge, but may be more efficient. Note that there is no unchecked variant of
+// Insert since Insert must retrieve and inspect gap's predecessor and
+// successor segments regardless.
+func (s *MappingSet) Insert(gap MappingGapIterator, r MappableRange, val MappingsOfRange) MappingIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ prev, next := gap.PrevSegment(), gap.NextSegment()
+ if prev.Ok() && prev.End() > r.Start {
+ panic(fmt.Sprintf("new segment %v overlaps predecessor %v", r, prev.Range()))
+ }
+ if next.Ok() && next.Start() < r.End {
+ panic(fmt.Sprintf("new segment %v overlaps successor %v", r, next.Range()))
+ }
+ if prev.Ok() && prev.End() == r.Start {
+ if mval, ok := (mappingSetFunctions{}).Merge(prev.Range(), prev.Value(), r, val); ok {
+ prev.SetEndUnchecked(r.End)
+ prev.SetValue(mval)
+ if next.Ok() && next.Start() == r.End {
+ val = mval
+ if mval, ok := (mappingSetFunctions{}).Merge(prev.Range(), val, next.Range(), next.Value()); ok {
+ prev.SetEndUnchecked(next.End())
+ prev.SetValue(mval)
+ return s.Remove(next).PrevSegment()
+ }
+ }
+ return prev
+ }
+ }
+ if next.Ok() && next.Start() == r.End {
+ if mval, ok := (mappingSetFunctions{}).Merge(r, val, next.Range(), next.Value()); ok {
+ next.SetStartUnchecked(r.Start)
+ next.SetValue(mval)
+ return next
+ }
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMerging inserts the given segment into the given gap and
+// returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid,
+// InsertWithoutMerging panics.
+func (s *MappingSet) InsertWithoutMerging(gap MappingGapIterator, r MappableRange, val MappingsOfRange) MappingIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if gr := gap.Range(); !gr.IsSupersetOf(r) {
+ panic(fmt.Sprintf("cannot insert segment range %v into gap range %v", r, gr))
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMergingUnchecked inserts the given segment into the given gap
+// and returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// Preconditions: r.Start >= gap.Start(); r.End <= gap.End().
+func (s *MappingSet) InsertWithoutMergingUnchecked(gap MappingGapIterator, r MappableRange, val MappingsOfRange) MappingIterator {
+ gap = gap.node.rebalanceBeforeInsert(gap)
+ copy(gap.node.keys[gap.index+1:], gap.node.keys[gap.index:gap.node.nrSegments])
+ copy(gap.node.values[gap.index+1:], gap.node.values[gap.index:gap.node.nrSegments])
+ gap.node.keys[gap.index] = r
+ gap.node.values[gap.index] = val
+ gap.node.nrSegments++
+ return MappingIterator{gap.node, gap.index}
+}
+
+// Remove removes the given segment and returns an iterator to the vacated gap.
+// All existing iterators (including seg, but not including the returned
+// iterator) are invalidated.
+func (s *MappingSet) Remove(seg MappingIterator) MappingGapIterator {
+
+ if seg.node.hasChildren {
+
+ victim := seg.PrevSegment()
+
+ seg.SetRangeUnchecked(victim.Range())
+ seg.SetValue(victim.Value())
+ return s.Remove(victim).NextGap()
+ }
+ copy(seg.node.keys[seg.index:], seg.node.keys[seg.index+1:seg.node.nrSegments])
+ copy(seg.node.values[seg.index:], seg.node.values[seg.index+1:seg.node.nrSegments])
+ mappingSetFunctions{}.ClearValue(&seg.node.values[seg.node.nrSegments-1])
+ seg.node.nrSegments--
+ return seg.node.rebalanceAfterRemove(MappingGapIterator{seg.node, seg.index})
+}
+
+// RemoveAll removes all segments from the set. All existing iterators are
+// invalidated.
+func (s *MappingSet) RemoveAll() {
+ s.root = Mappingnode{}
+}
+
+// RemoveRange removes all segments in the given range. An iterator to the
+// newly formed gap is returned, and all existing iterators are invalidated.
+func (s *MappingSet) RemoveRange(r MappableRange) MappingGapIterator {
+ seg, gap := s.Find(r.Start)
+ if seg.Ok() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ for seg = gap.NextSegment(); seg.Ok() && seg.Start() < r.End; seg = gap.NextSegment() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ return gap
+}
+
+// Merge attempts to merge two neighboring segments. If successful, Merge
+// returns an iterator to the merged segment, and all existing iterators are
+// invalidated. Otherwise, Merge returns a terminal iterator.
+//
+// If first is not the predecessor of second, Merge panics.
+func (s *MappingSet) Merge(first, second MappingIterator) MappingIterator {
+ if first.NextSegment() != second {
+ panic(fmt.Sprintf("attempt to merge non-neighboring segments %v, %v", first.Range(), second.Range()))
+ }
+ return s.MergeUnchecked(first, second)
+}
+
+// MergeUnchecked attempts to merge two neighboring segments. If successful,
+// MergeUnchecked returns an iterator to the merged segment, and all existing
+// iterators are invalidated. Otherwise, MergeUnchecked returns a terminal
+// iterator.
+//
+// Precondition: first is the predecessor of second: first.NextSegment() ==
+// second, first == second.PrevSegment().
+func (s *MappingSet) MergeUnchecked(first, second MappingIterator) MappingIterator {
+ if first.End() == second.Start() {
+ if mval, ok := (mappingSetFunctions{}).Merge(first.Range(), first.Value(), second.Range(), second.Value()); ok {
+
+ first.SetEndUnchecked(second.End())
+ first.SetValue(mval)
+ return s.Remove(second).PrevSegment()
+ }
+ }
+ return MappingIterator{}
+}
+
+// MergeAll attempts to merge all adjacent segments in the set. All existing
+// iterators are invalidated.
+func (s *MappingSet) MergeAll() {
+ seg := s.FirstSegment()
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeRange attempts to merge all adjacent segments that contain a key in the
+// specific range. All existing iterators are invalidated.
+func (s *MappingSet) MergeRange(r MappableRange) {
+ seg := s.LowerBoundSegment(r.Start)
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() && next.Range().Start < r.End {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeAdjacent attempts to merge the segment containing r.Start with its
+// predecessor, and the segment containing r.End-1 with its successor.
+func (s *MappingSet) MergeAdjacent(r MappableRange) {
+ first := s.FindSegment(r.Start)
+ if first.Ok() {
+ if prev := first.PrevSegment(); prev.Ok() {
+ s.Merge(prev, first)
+ }
+ }
+ last := s.FindSegment(r.End - 1)
+ if last.Ok() {
+ if next := last.NextSegment(); next.Ok() {
+ s.Merge(last, next)
+ }
+ }
+}
+
+// Split splits the given segment at the given key and returns iterators to the
+// two resulting segments. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+//
+// If the segment cannot be split at split (because split is at the start or
+// end of the segment's range, so splitting would produce a segment with zero
+// length, or because split falls outside the segment's range altogether),
+// Split panics.
+func (s *MappingSet) Split(seg MappingIterator, split uint64) (MappingIterator, MappingIterator) {
+ if !seg.Range().CanSplitAt(split) {
+ panic(fmt.Sprintf("can't split %v at %v", seg.Range(), split))
+ }
+ return s.SplitUnchecked(seg, split)
+}
+
+// SplitUnchecked splits the given segment at the given key and returns
+// iterators to the two resulting segments. All existing iterators (including
+// seg, but not including the returned iterators) are invalidated.
+//
+// Preconditions: seg.Start() < key < seg.End().
+func (s *MappingSet) SplitUnchecked(seg MappingIterator, split uint64) (MappingIterator, MappingIterator) {
+ val1, val2 := (mappingSetFunctions{}).Split(seg.Range(), seg.Value(), split)
+ end2 := seg.End()
+ seg.SetEndUnchecked(split)
+ seg.SetValue(val1)
+ seg2 := s.InsertWithoutMergingUnchecked(seg.NextGap(), MappableRange{split, end2}, val2)
+
+ return seg2.PrevSegment(), seg2
+}
+
+// SplitAt splits the segment straddling split, if one exists. SplitAt returns
+// true if a segment was split and false otherwise. If SplitAt splits a
+// segment, all existing iterators are invalidated.
+func (s *MappingSet) SplitAt(split uint64) bool {
+ if seg := s.FindSegment(split); seg.Ok() && seg.Range().CanSplitAt(split) {
+ s.SplitUnchecked(seg, split)
+ return true
+ }
+ return false
+}
+
+// Isolate ensures that the given segment's range does not escape r by
+// splitting at r.Start and r.End if necessary, and returns an updated iterator
+// to the bounded segment. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+func (s *MappingSet) Isolate(seg MappingIterator, r MappableRange) MappingIterator {
+ if seg.Range().CanSplitAt(r.Start) {
+ _, seg = s.SplitUnchecked(seg, r.Start)
+ }
+ if seg.Range().CanSplitAt(r.End) {
+ seg, _ = s.SplitUnchecked(seg, r.End)
+ }
+ return seg
+}
+
+// ApplyContiguous applies a function to a contiguous range of segments,
+// splitting if necessary. The function is applied until the first gap is
+// encountered, at which point the gap is returned. If the function is applied
+// across the entire range, a terminal gap is returned. All existing iterators
+// are invalidated.
+//
+// N.B. The Iterator must not be invalidated by the function.
+func (s *MappingSet) ApplyContiguous(r MappableRange, fn func(seg MappingIterator)) MappingGapIterator {
+ seg, gap := s.Find(r.Start)
+ if !seg.Ok() {
+ return gap
+ }
+ for {
+ seg = s.Isolate(seg, r)
+ fn(seg)
+ if seg.End() >= r.End {
+ return MappingGapIterator{}
+ }
+ gap = seg.NextGap()
+ if !gap.IsEmpty() {
+ return gap
+ }
+ seg = gap.NextSegment()
+ if !seg.Ok() {
+
+ return MappingGapIterator{}
+ }
+ }
+}
+
+// +stateify savable
+type Mappingnode struct {
+ // An internal binary tree node looks like:
+ //
+ // K
+ // / \
+ // Cl Cr
+ //
+ // where all keys in the subtree rooted by Cl (the left subtree) are less
+ // than K (the key of the parent node), and all keys in the subtree rooted
+ // by Cr (the right subtree) are greater than K.
+ //
+ // An internal B-tree node's indexes work out to look like:
+ //
+ // K0 K1 K2 ... Kn-1
+ // / \/ \/ \ ... / \
+ // C0 C1 C2 C3 ... Cn-1 Cn
+ //
+ // where n is nrSegments.
+ nrSegments int
+
+ // parent is a pointer to this node's parent. If this node is root, parent
+ // is nil.
+ parent *Mappingnode
+
+ // parentIndex is the index of this node in parent.children.
+ parentIndex int
+
+ // Flag for internal nodes that is technically redundant with "children[0]
+ // != nil", but is stored in the first cache line. "hasChildren" rather
+ // than "isLeaf" because false must be the correct value for an empty root.
+ hasChildren bool
+
+ // Nodes store keys and values in separate arrays to maximize locality in
+ // the common case (scanning keys for lookup).
+ keys [MappingmaxDegree - 1]MappableRange
+ values [MappingmaxDegree - 1]MappingsOfRange
+ children [MappingmaxDegree]*Mappingnode
+}
+
+// firstSegment returns the first segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *Mappingnode) firstSegment() MappingIterator {
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return MappingIterator{n, 0}
+}
+
+// lastSegment returns the last segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *Mappingnode) lastSegment() MappingIterator {
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return MappingIterator{n, n.nrSegments - 1}
+}
+
+func (n *Mappingnode) prevSibling() *Mappingnode {
+ if n.parent == nil || n.parentIndex == 0 {
+ return nil
+ }
+ return n.parent.children[n.parentIndex-1]
+}
+
+func (n *Mappingnode) nextSibling() *Mappingnode {
+ if n.parent == nil || n.parentIndex == n.parent.nrSegments {
+ return nil
+ }
+ return n.parent.children[n.parentIndex+1]
+}
+
+// rebalanceBeforeInsert splits n and its ancestors if they are full, as
+// required for insertion, and returns an updated iterator to the position
+// represented by gap.
+func (n *Mappingnode) rebalanceBeforeInsert(gap MappingGapIterator) MappingGapIterator {
+ if n.parent != nil {
+ gap = n.parent.rebalanceBeforeInsert(gap)
+ }
+ if n.nrSegments < MappingmaxDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ left := &Mappingnode{
+ nrSegments: MappingminDegree - 1,
+ parent: n,
+ parentIndex: 0,
+ hasChildren: n.hasChildren,
+ }
+ right := &Mappingnode{
+ nrSegments: MappingminDegree - 1,
+ parent: n,
+ parentIndex: 1,
+ hasChildren: n.hasChildren,
+ }
+ copy(left.keys[:MappingminDegree-1], n.keys[:MappingminDegree-1])
+ copy(left.values[:MappingminDegree-1], n.values[:MappingminDegree-1])
+ copy(right.keys[:MappingminDegree-1], n.keys[MappingminDegree:])
+ copy(right.values[:MappingminDegree-1], n.values[MappingminDegree:])
+ n.keys[0], n.values[0] = n.keys[MappingminDegree-1], n.values[MappingminDegree-1]
+ MappingzeroValueSlice(n.values[1:])
+ if n.hasChildren {
+ copy(left.children[:MappingminDegree], n.children[:MappingminDegree])
+ copy(right.children[:MappingminDegree], n.children[MappingminDegree:])
+ MappingzeroNodeSlice(n.children[2:])
+ for i := 0; i < MappingminDegree; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ right.children[i].parent = right
+ right.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = 1
+ n.hasChildren = true
+ n.children[0] = left
+ n.children[1] = right
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < MappingminDegree {
+ return MappingGapIterator{left, gap.index}
+ }
+ return MappingGapIterator{right, gap.index - MappingminDegree}
+ }
+
+ copy(n.parent.keys[n.parentIndex+1:], n.parent.keys[n.parentIndex:n.parent.nrSegments])
+ copy(n.parent.values[n.parentIndex+1:], n.parent.values[n.parentIndex:n.parent.nrSegments])
+ n.parent.keys[n.parentIndex], n.parent.values[n.parentIndex] = n.keys[MappingminDegree-1], n.values[MappingminDegree-1]
+ copy(n.parent.children[n.parentIndex+2:], n.parent.children[n.parentIndex+1:n.parent.nrSegments+1])
+ for i := n.parentIndex + 2; i < n.parent.nrSegments+2; i++ {
+ n.parent.children[i].parentIndex = i
+ }
+ sibling := &Mappingnode{
+ nrSegments: MappingminDegree - 1,
+ parent: n.parent,
+ parentIndex: n.parentIndex + 1,
+ hasChildren: n.hasChildren,
+ }
+ n.parent.children[n.parentIndex+1] = sibling
+ n.parent.nrSegments++
+ copy(sibling.keys[:MappingminDegree-1], n.keys[MappingminDegree:])
+ copy(sibling.values[:MappingminDegree-1], n.values[MappingminDegree:])
+ MappingzeroValueSlice(n.values[MappingminDegree-1:])
+ if n.hasChildren {
+ copy(sibling.children[:MappingminDegree], n.children[MappingminDegree:])
+ MappingzeroNodeSlice(n.children[MappingminDegree:])
+ for i := 0; i < MappingminDegree; i++ {
+ sibling.children[i].parent = sibling
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = MappingminDegree - 1
+
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < MappingminDegree {
+ return gap
+ }
+ return MappingGapIterator{sibling, gap.index - MappingminDegree}
+}
+
+// rebalanceAfterRemove "unsplits" n and its ancestors if they are deficient
+// (contain fewer segments than required by B-tree invariants), as required for
+// removal, and returns an updated iterator to the position represented by gap.
+//
+// Precondition: n is the only node in the tree that may currently violate a
+// B-tree invariant.
+func (n *Mappingnode) rebalanceAfterRemove(gap MappingGapIterator) MappingGapIterator {
+ for {
+ if n.nrSegments >= MappingminDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ return gap
+ }
+
+ if sibling := n.prevSibling(); sibling != nil && sibling.nrSegments >= MappingminDegree {
+ copy(n.keys[1:], n.keys[:n.nrSegments])
+ copy(n.values[1:], n.values[:n.nrSegments])
+ n.keys[0] = n.parent.keys[n.parentIndex-1]
+ n.values[0] = n.parent.values[n.parentIndex-1]
+ n.parent.keys[n.parentIndex-1] = sibling.keys[sibling.nrSegments-1]
+ n.parent.values[n.parentIndex-1] = sibling.values[sibling.nrSegments-1]
+ mappingSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ copy(n.children[1:], n.children[:n.nrSegments+1])
+ n.children[0] = sibling.children[sibling.nrSegments]
+ sibling.children[sibling.nrSegments] = nil
+ n.children[0].parent = n
+ n.children[0].parentIndex = 0
+ for i := 1; i < n.nrSegments+2; i++ {
+ n.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling && gap.index == sibling.nrSegments {
+ return MappingGapIterator{n, 0}
+ }
+ if gap.node == n {
+ return MappingGapIterator{n, gap.index + 1}
+ }
+ return gap
+ }
+ if sibling := n.nextSibling(); sibling != nil && sibling.nrSegments >= MappingminDegree {
+ n.keys[n.nrSegments] = n.parent.keys[n.parentIndex]
+ n.values[n.nrSegments] = n.parent.values[n.parentIndex]
+ n.parent.keys[n.parentIndex] = sibling.keys[0]
+ n.parent.values[n.parentIndex] = sibling.values[0]
+ copy(sibling.keys[:sibling.nrSegments-1], sibling.keys[1:])
+ copy(sibling.values[:sibling.nrSegments-1], sibling.values[1:])
+ mappingSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ n.children[n.nrSegments+1] = sibling.children[0]
+ copy(sibling.children[:sibling.nrSegments], sibling.children[1:])
+ sibling.children[sibling.nrSegments] = nil
+ n.children[n.nrSegments+1].parent = n
+ n.children[n.nrSegments+1].parentIndex = n.nrSegments + 1
+ for i := 0; i < sibling.nrSegments; i++ {
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling {
+ if gap.index == 0 {
+ return MappingGapIterator{n, n.nrSegments}
+ }
+ return MappingGapIterator{sibling, gap.index - 1}
+ }
+ return gap
+ }
+
+ p := n.parent
+ if p.nrSegments == 1 {
+
+ left, right := p.children[0], p.children[1]
+ p.nrSegments = left.nrSegments + right.nrSegments + 1
+ p.hasChildren = left.hasChildren
+ p.keys[left.nrSegments] = p.keys[0]
+ p.values[left.nrSegments] = p.values[0]
+ copy(p.keys[:left.nrSegments], left.keys[:left.nrSegments])
+ copy(p.values[:left.nrSegments], left.values[:left.nrSegments])
+ copy(p.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(p.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(p.children[:left.nrSegments+1], left.children[:left.nrSegments+1])
+ copy(p.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := 0; i < p.nrSegments+1; i++ {
+ p.children[i].parent = p
+ p.children[i].parentIndex = i
+ }
+ } else {
+ p.children[0] = nil
+ p.children[1] = nil
+ }
+ if gap.node == left {
+ return MappingGapIterator{p, gap.index}
+ }
+ if gap.node == right {
+ return MappingGapIterator{p, gap.index + left.nrSegments + 1}
+ }
+ return gap
+ }
+ // Merge n and either sibling, along with the segment separating the
+ // two, into whichever of the two nodes comes first. This is the
+ // reverse of the non-root splitting case in
+ // node.rebalanceBeforeInsert.
+ var left, right *Mappingnode
+ if n.parentIndex > 0 {
+ left = n.prevSibling()
+ right = n
+ } else {
+ left = n
+ right = n.nextSibling()
+ }
+
+ if gap.node == right {
+ gap = MappingGapIterator{left, gap.index + left.nrSegments + 1}
+ }
+ left.keys[left.nrSegments] = p.keys[left.parentIndex]
+ left.values[left.nrSegments] = p.values[left.parentIndex]
+ copy(left.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(left.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(left.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := left.nrSegments + 1; i < left.nrSegments+right.nrSegments+2; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ }
+ }
+ left.nrSegments += right.nrSegments + 1
+ copy(p.keys[left.parentIndex:], p.keys[left.parentIndex+1:p.nrSegments])
+ copy(p.values[left.parentIndex:], p.values[left.parentIndex+1:p.nrSegments])
+ mappingSetFunctions{}.ClearValue(&p.values[p.nrSegments-1])
+ copy(p.children[left.parentIndex+1:], p.children[left.parentIndex+2:p.nrSegments+1])
+ for i := 0; i < p.nrSegments; i++ {
+ p.children[i].parentIndex = i
+ }
+ p.children[p.nrSegments] = nil
+ p.nrSegments--
+
+ n = p
+ }
+}
+
+// A Iterator is conceptually one of:
+//
+// - A pointer to a segment in a set; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Iterators are copyable values and are meaningfully equality-comparable. The
+// zero value of Iterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type MappingIterator struct {
+ // node is the node containing the iterated segment. If the iterator is
+ // terminal, node is nil.
+ node *Mappingnode
+
+ // index is the index of the segment in node.keys/values.
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (seg MappingIterator) Ok() bool {
+ return seg.node != nil
+}
+
+// Range returns the iterated segment's range key.
+func (seg MappingIterator) Range() MappableRange {
+ return seg.node.keys[seg.index]
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (seg MappingIterator) Start() uint64 {
+ return seg.node.keys[seg.index].Start
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (seg MappingIterator) End() uint64 {
+ return seg.node.keys[seg.index].End
+}
+
+// SetRangeUnchecked mutates the iterated segment's range key. This operation
+// does not invalidate any iterators.
+//
+// Preconditions:
+//
+// - r.Length() > 0.
+//
+// - The new range must not overlap an existing one: If seg.NextSegment().Ok(),
+// then r.end <= seg.NextSegment().Start(); if seg.PrevSegment().Ok(), then
+// r.start >= seg.PrevSegment().End().
+func (seg MappingIterator) SetRangeUnchecked(r MappableRange) {
+ seg.node.keys[seg.index] = r
+}
+
+// SetRange mutates the iterated segment's range key. If the new range would
+// cause the iterated segment to overlap another segment, or if the new range
+// is invalid, SetRange panics. This operation does not invalidate any
+// iterators.
+func (seg MappingIterator) SetRange(r MappableRange) {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && r.Start < prev.End() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, prev.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && r.End > next.Start() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, next.Range()))
+ }
+ seg.SetRangeUnchecked(r)
+}
+
+// SetStartUnchecked mutates the iterated segment's start. This operation does
+// not invalidate any iterators.
+//
+// Preconditions: The new start must be valid: start < seg.End(); if
+// seg.PrevSegment().Ok(), then start >= seg.PrevSegment().End().
+func (seg MappingIterator) SetStartUnchecked(start uint64) {
+ seg.node.keys[seg.index].Start = start
+}
+
+// SetStart mutates the iterated segment's start. If the new start value would
+// cause the iterated segment to overlap another segment, or would result in an
+// invalid range, SetStart panics. This operation does not invalidate any
+// iterators.
+func (seg MappingIterator) SetStart(start uint64) {
+ if start >= seg.End() {
+ panic(fmt.Sprintf("new start %v would invalidate segment range %v", start, seg.Range()))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && start < prev.End() {
+ panic(fmt.Sprintf("new start %v would cause segment range %v to overlap segment range %v", start, seg.Range(), prev.Range()))
+ }
+ seg.SetStartUnchecked(start)
+}
+
+// SetEndUnchecked mutates the iterated segment's end. This operation does not
+// invalidate any iterators.
+//
+// Preconditions: The new end must be valid: end > seg.Start(); if
+// seg.NextSegment().Ok(), then end <= seg.NextSegment().Start().
+func (seg MappingIterator) SetEndUnchecked(end uint64) {
+ seg.node.keys[seg.index].End = end
+}
+
+// SetEnd mutates the iterated segment's end. If the new end value would cause
+// the iterated segment to overlap another segment, or would result in an
+// invalid range, SetEnd panics. This operation does not invalidate any
+// iterators.
+func (seg MappingIterator) SetEnd(end uint64) {
+ if end <= seg.Start() {
+ panic(fmt.Sprintf("new end %v would invalidate segment range %v", end, seg.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && end > next.Start() {
+ panic(fmt.Sprintf("new end %v would cause segment range %v to overlap segment range %v", end, seg.Range(), next.Range()))
+ }
+ seg.SetEndUnchecked(end)
+}
+
+// Value returns a copy of the iterated segment's value.
+func (seg MappingIterator) Value() MappingsOfRange {
+ return seg.node.values[seg.index]
+}
+
+// ValuePtr returns a pointer to the iterated segment's value. The pointer is
+// invalidated if the iterator is invalidated. This operation does not
+// invalidate any iterators.
+func (seg MappingIterator) ValuePtr() *MappingsOfRange {
+ return &seg.node.values[seg.index]
+}
+
+// SetValue mutates the iterated segment's value. This operation does not
+// invalidate any iterators.
+func (seg MappingIterator) SetValue(val MappingsOfRange) {
+ seg.node.values[seg.index] = val
+}
+
+// PrevSegment returns the iterated segment's predecessor. If there is no
+// preceding segment, PrevSegment returns a terminal iterator.
+func (seg MappingIterator) PrevSegment() MappingIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index].lastSegment()
+ }
+ if seg.index > 0 {
+ return MappingIterator{seg.node, seg.index - 1}
+ }
+ if seg.node.parent == nil {
+ return MappingIterator{}
+ }
+ return MappingsegmentBeforePosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// NextSegment returns the iterated segment's successor. If there is no
+// succeeding segment, NextSegment returns a terminal iterator.
+func (seg MappingIterator) NextSegment() MappingIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment()
+ }
+ if seg.index < seg.node.nrSegments-1 {
+ return MappingIterator{seg.node, seg.index + 1}
+ }
+ if seg.node.parent == nil {
+ return MappingIterator{}
+ }
+ return MappingsegmentAfterPosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// PrevGap returns the gap immediately before the iterated segment.
+func (seg MappingIterator) PrevGap() MappingGapIterator {
+ if seg.node.hasChildren {
+
+ return seg.node.children[seg.index].lastSegment().NextGap()
+ }
+ return MappingGapIterator{seg.node, seg.index}
+}
+
+// NextGap returns the gap immediately after the iterated segment.
+func (seg MappingIterator) NextGap() MappingGapIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment().PrevGap()
+ }
+ return MappingGapIterator{seg.node, seg.index + 1}
+}
+
+// PrevNonEmpty returns the iterated segment's predecessor if it is adjacent,
+// or the gap before the iterated segment otherwise. If seg.Start() ==
+// Functions.MinKey(), PrevNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by PrevNonEmpty will be
+// non-terminal.
+func (seg MappingIterator) PrevNonEmpty() (MappingIterator, MappingGapIterator) {
+ gap := seg.PrevGap()
+ if gap.Range().Length() != 0 {
+ return MappingIterator{}, gap
+ }
+ return gap.PrevSegment(), MappingGapIterator{}
+}
+
+// NextNonEmpty returns the iterated segment's successor if it is adjacent, or
+// the gap after the iterated segment otherwise. If seg.End() ==
+// Functions.MaxKey(), NextNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by NextNonEmpty will be
+// non-terminal.
+func (seg MappingIterator) NextNonEmpty() (MappingIterator, MappingGapIterator) {
+ gap := seg.NextGap()
+ if gap.Range().Length() != 0 {
+ return MappingIterator{}, gap
+ }
+ return gap.NextSegment(), MappingGapIterator{}
+}
+
+// A GapIterator is conceptually one of:
+//
+// - A pointer to a position between two segments, before the first segment, or
+// after the last segment in a set, called a *gap*; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Note that the gap between two adjacent segments exists (iterators to it are
+// non-terminal), but has a length of zero. GapIterator.IsEmpty returns true
+// for such gaps. An empty set contains a single gap, spanning the entire range
+// of the set's keys.
+//
+// GapIterators are copyable values and are meaningfully equality-comparable.
+// The zero value of GapIterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type MappingGapIterator struct {
+ // The representation of a GapIterator is identical to that of an Iterator,
+ // except that index corresponds to positions between segments in the same
+ // way as for node.children (see comment for node.nrSegments).
+ node *Mappingnode
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (gap MappingGapIterator) Ok() bool {
+ return gap.node != nil
+}
+
+// Range returns the range spanned by the iterated gap.
+func (gap MappingGapIterator) Range() MappableRange {
+ return MappableRange{gap.Start(), gap.End()}
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (gap MappingGapIterator) Start() uint64 {
+ if ps := gap.PrevSegment(); ps.Ok() {
+ return ps.End()
+ }
+ return mappingSetFunctions{}.MinKey()
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (gap MappingGapIterator) End() uint64 {
+ if ns := gap.NextSegment(); ns.Ok() {
+ return ns.Start()
+ }
+ return mappingSetFunctions{}.MaxKey()
+}
+
+// IsEmpty returns true if the iterated gap is empty (that is, the "gap" is
+// between two adjacent segments.)
+func (gap MappingGapIterator) IsEmpty() bool {
+ return gap.Range().Length() == 0
+}
+
+// PrevSegment returns the segment immediately before the iterated gap. If no
+// such segment exists, PrevSegment returns a terminal iterator.
+func (gap MappingGapIterator) PrevSegment() MappingIterator {
+ return MappingsegmentBeforePosition(gap.node, gap.index)
+}
+
+// NextSegment returns the segment immediately after the iterated gap. If no
+// such segment exists, NextSegment returns a terminal iterator.
+func (gap MappingGapIterator) NextSegment() MappingIterator {
+ return MappingsegmentAfterPosition(gap.node, gap.index)
+}
+
+// PrevGap returns the iterated gap's predecessor. If no such gap exists,
+// PrevGap returns a terminal iterator.
+func (gap MappingGapIterator) PrevGap() MappingGapIterator {
+ seg := gap.PrevSegment()
+ if !seg.Ok() {
+ return MappingGapIterator{}
+ }
+ return seg.PrevGap()
+}
+
+// NextGap returns the iterated gap's successor. If no such gap exists, NextGap
+// returns a terminal iterator.
+func (gap MappingGapIterator) NextGap() MappingGapIterator {
+ seg := gap.NextSegment()
+ if !seg.Ok() {
+ return MappingGapIterator{}
+ }
+ return seg.NextGap()
+}
+
+// segmentBeforePosition returns the predecessor segment of the position given
+// by n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentBeforePosition returns a terminal iterator.
+func MappingsegmentBeforePosition(n *Mappingnode, i int) MappingIterator {
+ for i == 0 {
+ if n.parent == nil {
+ return MappingIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return MappingIterator{n, i - 1}
+}
+
+// segmentAfterPosition returns the successor segment of the position given by
+// n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentAfterPosition returns a terminal iterator.
+func MappingsegmentAfterPosition(n *Mappingnode, i int) MappingIterator {
+ for i == n.nrSegments {
+ if n.parent == nil {
+ return MappingIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return MappingIterator{n, i}
+}
+
+func MappingzeroValueSlice(slice []MappingsOfRange) {
+
+ for i := range slice {
+ mappingSetFunctions{}.ClearValue(&slice[i])
+ }
+}
+
+func MappingzeroNodeSlice(slice []*Mappingnode) {
+ for i := range slice {
+ slice[i] = nil
+ }
+}
+
+// String stringifies a Set for debugging.
+func (s *MappingSet) String() string {
+ return s.root.String()
+}
+
+// String stringifies a node (and all of its children) for debugging.
+func (n *Mappingnode) String() string {
+ var buf bytes.Buffer
+ n.writeDebugString(&buf, "")
+ return buf.String()
+}
+
+func (n *Mappingnode) writeDebugString(buf *bytes.Buffer, prefix string) {
+ if n.hasChildren != (n.nrSegments > 0 && n.children[0] != nil) {
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent value of hasChildren: got %v, want %v\n", n.hasChildren, !n.hasChildren))
+ }
+ for i := 0; i < n.nrSegments; i++ {
+ if child := n.children[i]; child != nil {
+ cprefix := fmt.Sprintf("%s- % 3d ", prefix, i)
+ if child.parent != n || child.parentIndex != i {
+ buf.WriteString(cprefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent linkage to parent: got (%p, %d), want (%p, %d)\n", child.parent, child.parentIndex, n, i))
+ }
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, i))
+ }
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("- % 3d: %v => %v\n", i, n.keys[i], n.values[i]))
+ }
+ if child := n.children[n.nrSegments]; child != nil {
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, n.nrSegments))
+ }
+}
+
+// SegmentDataSlices represents segments from a set as slices of start, end, and
+// values. SegmentDataSlices is primarily used as an intermediate representation
+// for save/restore and the layout here is optimized for that.
+//
+// +stateify savable
+type MappingSegmentDataSlices struct {
+ Start []uint64
+ End []uint64
+ Values []MappingsOfRange
+}
+
+// ExportSortedSlice returns a copy of all segments in the given set, in ascending
+// key order.
+func (s *MappingSet) ExportSortedSlices() *MappingSegmentDataSlices {
+ var sds MappingSegmentDataSlices
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sds.Start = append(sds.Start, seg.Start())
+ sds.End = append(sds.End, seg.End())
+ sds.Values = append(sds.Values, seg.Value())
+ }
+ sds.Start = sds.Start[:len(sds.Start):len(sds.Start)]
+ sds.End = sds.End[:len(sds.End):len(sds.End)]
+ sds.Values = sds.Values[:len(sds.Values):len(sds.Values)]
+ return &sds
+}
+
+// ImportSortedSlice initializes the given set from the given slice.
+//
+// Preconditions: s must be empty. sds must represent a valid set (the segments
+// in sds must have valid lengths that do not overlap). The segments in sds
+// must be sorted in ascending key order.
+func (s *MappingSet) ImportSortedSlices(sds *MappingSegmentDataSlices) error {
+ if !s.IsEmpty() {
+ return fmt.Errorf("cannot import into non-empty set %v", s)
+ }
+ gap := s.FirstGap()
+ for i := range sds.Start {
+ r := MappableRange{sds.Start[i], sds.End[i]}
+ if !gap.Range().IsSupersetOf(r) {
+ return fmt.Errorf("segment overlaps a preceding segment or is incorrectly sorted: [%d, %d) => %v", sds.Start[i], sds.End[i], sds.Values[i])
+ }
+ gap = s.InsertWithoutMerging(gap, r, sds.Values[i]).NextGap()
+ }
+ return nil
+}
+func (s *MappingSet) saveRoot() *MappingSegmentDataSlices {
+ return s.ExportSortedSlices()
+}
+
+func (s *MappingSet) loadRoot(sds *MappingSegmentDataSlices) {
+ if err := s.ImportSortedSlices(sds); err != nil {
+ panic(err)
+ }
+}
diff --git a/pkg/sentry/memmap/mapping_set_test.go b/pkg/sentry/memmap/mapping_set_test.go
deleted file mode 100644
index f9b11a59c..000000000
--- a/pkg/sentry/memmap/mapping_set_test.go
+++ /dev/null
@@ -1,260 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package memmap
-
-import (
- "reflect"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/usermem"
-)
-
-type testMappingSpace struct {
- // Ideally we'd store the full ranges that were invalidated, rather
- // than individual calls to Invalidate, as they are an implementation
- // detail, but this is the simplest way for now.
- inv []usermem.AddrRange
-}
-
-func (n *testMappingSpace) reset() {
- n.inv = []usermem.AddrRange{}
-}
-
-func (n *testMappingSpace) Invalidate(ar usermem.AddrRange, opts InvalidateOpts) {
- n.inv = append(n.inv, ar)
-}
-
-func TestAddRemoveMapping(t *testing.T) {
- set := MappingSet{}
- ms := &testMappingSpace{}
-
- mapped := set.AddMapping(ms, usermem.AddrRange{0x10000, 0x12000}, 0x1000, true)
- if got, want := mapped, []MappableRange{{0x1000, 0x3000}}; !reflect.DeepEqual(got, want) {
- t.Errorf("AddMapping: got %+v, wanted %+v", got, want)
- }
-
- // Mappings (usermem.AddrRanges => memmap.MappableRange):
- // [0x10000, 0x12000) => [0x1000, 0x3000)
- t.Log(&set)
-
- mapped = set.AddMapping(ms, usermem.AddrRange{0x20000, 0x21000}, 0x2000, true)
- if len(mapped) != 0 {
- t.Errorf("AddMapping: got %+v, wanted []", mapped)
- }
-
- // Mappings:
- // [0x10000, 0x11000) => [0x1000, 0x2000)
- // [0x11000, 0x12000) and [0x20000, 0x21000) => [0x2000, 0x3000)
- t.Log(&set)
-
- mapped = set.AddMapping(ms, usermem.AddrRange{0x30000, 0x31000}, 0x4000, true)
- if got, want := mapped, []MappableRange{{0x4000, 0x5000}}; !reflect.DeepEqual(got, want) {
- t.Errorf("AddMapping: got %+v, wanted %+v", got, want)
- }
-
- // Mappings:
- // [0x10000, 0x11000) => [0x1000, 0x2000)
- // [0x11000, 0x12000) and [0x20000, 0x21000) => [0x2000, 0x3000)
- // [0x30000, 0x31000) => [0x4000, 0x5000)
- t.Log(&set)
-
- mapped = set.AddMapping(ms, usermem.AddrRange{0x12000, 0x15000}, 0x3000, true)
- if got, want := mapped, []MappableRange{{0x3000, 0x4000}, {0x5000, 0x6000}}; !reflect.DeepEqual(got, want) {
- t.Errorf("AddMapping: got %+v, wanted %+v", got, want)
- }
-
- // Mappings:
- // [0x10000, 0x11000) => [0x1000, 0x2000)
- // [0x11000, 0x12000) and [0x20000, 0x21000) => [0x2000, 0x3000)
- // [0x12000, 0x13000) => [0x3000, 0x4000)
- // [0x13000, 0x14000) and [0x30000, 0x31000) => [0x4000, 0x5000)
- // [0x14000, 0x15000) => [0x5000, 0x6000)
- t.Log(&set)
-
- unmapped := set.RemoveMapping(ms, usermem.AddrRange{0x10000, 0x11000}, 0x1000, true)
- if got, want := unmapped, []MappableRange{{0x1000, 0x2000}}; !reflect.DeepEqual(got, want) {
- t.Errorf("RemoveMapping: got %+v, wanted %+v", got, want)
- }
-
- // Mappings:
- // [0x11000, 0x12000) and [0x20000, 0x21000) => [0x2000, 0x3000)
- // [0x12000, 0x13000) => [0x3000, 0x4000)
- // [0x13000, 0x14000) and [0x30000, 0x31000) => [0x4000, 0x5000)
- // [0x14000, 0x15000) => [0x5000, 0x6000)
- t.Log(&set)
-
- unmapped = set.RemoveMapping(ms, usermem.AddrRange{0x20000, 0x21000}, 0x2000, true)
- if len(unmapped) != 0 {
- t.Errorf("RemoveMapping: got %+v, wanted []", unmapped)
- }
-
- // Mappings:
- // [0x11000, 0x13000) => [0x2000, 0x4000)
- // [0x13000, 0x14000) and [0x30000, 0x31000) => [0x4000, 0x5000)
- // [0x14000, 0x15000) => [0x5000, 0x6000)
- t.Log(&set)
-
- unmapped = set.RemoveMapping(ms, usermem.AddrRange{0x11000, 0x15000}, 0x2000, true)
- if got, want := unmapped, []MappableRange{{0x2000, 0x4000}, {0x5000, 0x6000}}; !reflect.DeepEqual(got, want) {
- t.Errorf("RemoveMapping: got %+v, wanted %+v", got, want)
- }
-
- // Mappings:
- // [0x30000, 0x31000) => [0x4000, 0x5000)
- t.Log(&set)
-
- unmapped = set.RemoveMapping(ms, usermem.AddrRange{0x30000, 0x31000}, 0x4000, true)
- if got, want := unmapped, []MappableRange{{0x4000, 0x5000}}; !reflect.DeepEqual(got, want) {
- t.Errorf("RemoveMapping: got %+v, wanted %+v", got, want)
- }
-}
-
-func TestInvalidateWholeMapping(t *testing.T) {
- set := MappingSet{}
- ms := &testMappingSpace{}
-
- set.AddMapping(ms, usermem.AddrRange{0x10000, 0x11000}, 0, true)
- // Mappings:
- // [0x10000, 0x11000) => [0, 0x1000)
- t.Log(&set)
- set.Invalidate(MappableRange{0, 0x1000}, InvalidateOpts{})
- if got, want := ms.inv, []usermem.AddrRange{{0x10000, 0x11000}}; !reflect.DeepEqual(got, want) {
- t.Errorf("Invalidate: got %+v, wanted %+v", got, want)
- }
-}
-
-func TestInvalidatePartialMapping(t *testing.T) {
- set := MappingSet{}
- ms := &testMappingSpace{}
-
- set.AddMapping(ms, usermem.AddrRange{0x10000, 0x13000}, 0, true)
- // Mappings:
- // [0x10000, 0x13000) => [0, 0x3000)
- t.Log(&set)
- set.Invalidate(MappableRange{0x1000, 0x2000}, InvalidateOpts{})
- if got, want := ms.inv, []usermem.AddrRange{{0x11000, 0x12000}}; !reflect.DeepEqual(got, want) {
- t.Errorf("Invalidate: got %+v, wanted %+v", got, want)
- }
-}
-
-func TestInvalidateMultipleMappings(t *testing.T) {
- set := MappingSet{}
- ms := &testMappingSpace{}
-
- set.AddMapping(ms, usermem.AddrRange{0x10000, 0x11000}, 0, true)
- set.AddMapping(ms, usermem.AddrRange{0x20000, 0x21000}, 0x2000, true)
- // Mappings:
- // [0x10000, 0x11000) => [0, 0x1000)
- // [0x12000, 0x13000) => [0x2000, 0x3000)
- t.Log(&set)
- set.Invalidate(MappableRange{0, 0x3000}, InvalidateOpts{})
- if got, want := ms.inv, []usermem.AddrRange{{0x10000, 0x11000}, {0x20000, 0x21000}}; !reflect.DeepEqual(got, want) {
- t.Errorf("Invalidate: got %+v, wanted %+v", got, want)
- }
-}
-
-func TestInvalidateOverlappingMappings(t *testing.T) {
- set := MappingSet{}
- ms1 := &testMappingSpace{}
- ms2 := &testMappingSpace{}
-
- set.AddMapping(ms1, usermem.AddrRange{0x10000, 0x12000}, 0, true)
- set.AddMapping(ms2, usermem.AddrRange{0x20000, 0x22000}, 0x1000, true)
- // Mappings:
- // ms1:[0x10000, 0x12000) => [0, 0x2000)
- // ms2:[0x11000, 0x13000) => [0x1000, 0x3000)
- t.Log(&set)
- set.Invalidate(MappableRange{0x1000, 0x2000}, InvalidateOpts{})
- if got, want := ms1.inv, []usermem.AddrRange{{0x11000, 0x12000}}; !reflect.DeepEqual(got, want) {
- t.Errorf("Invalidate: ms1: got %+v, wanted %+v", got, want)
- }
- if got, want := ms2.inv, []usermem.AddrRange{{0x20000, 0x21000}}; !reflect.DeepEqual(got, want) {
- t.Errorf("Invalidate: ms1: got %+v, wanted %+v", got, want)
- }
-}
-
-func TestMixedWritableMappings(t *testing.T) {
- set := MappingSet{}
- ms := &testMappingSpace{}
-
- mapped := set.AddMapping(ms, usermem.AddrRange{0x10000, 0x12000}, 0x1000, true)
- if got, want := mapped, []MappableRange{{0x1000, 0x3000}}; !reflect.DeepEqual(got, want) {
- t.Errorf("AddMapping: got %+v, wanted %+v", got, want)
- }
-
- // Mappings:
- // [0x10000, 0x12000) writable => [0x1000, 0x3000)
- t.Log(&set)
-
- mapped = set.AddMapping(ms, usermem.AddrRange{0x20000, 0x22000}, 0x2000, false)
- if got, want := mapped, []MappableRange{{0x3000, 0x4000}}; !reflect.DeepEqual(got, want) {
- t.Errorf("AddMapping: got %+v, wanted %+v", got, want)
- }
-
- // Mappings:
- // [0x10000, 0x11000) writable => [0x1000, 0x2000)
- // [0x11000, 0x12000) writable and [0x20000, 0x21000) readonly => [0x2000, 0x3000)
- // [0x21000, 0x22000) readonly => [0x3000, 0x4000)
- t.Log(&set)
-
- // Unmap should fail because we specified the readonly map address range, but
- // asked to unmap a writable segment.
- unmapped := set.RemoveMapping(ms, usermem.AddrRange{0x20000, 0x21000}, 0x2000, true)
- if len(unmapped) != 0 {
- t.Errorf("RemoveMapping: got %+v, wanted []", unmapped)
- }
-
- // Readonly mapping removed, but writable mapping still exists in the range,
- // so no mappable range fully unmapped.
- unmapped = set.RemoveMapping(ms, usermem.AddrRange{0x20000, 0x21000}, 0x2000, false)
- if len(unmapped) != 0 {
- t.Errorf("RemoveMapping: got %+v, wanted []", unmapped)
- }
-
- // Mappings:
- // [0x10000, 0x12000) writable => [0x1000, 0x3000)
- // [0x21000, 0x22000) readonly => [0x3000, 0x4000)
- t.Log(&set)
-
- unmapped = set.RemoveMapping(ms, usermem.AddrRange{0x11000, 0x12000}, 0x2000, true)
- if got, want := unmapped, []MappableRange{{0x2000, 0x3000}}; !reflect.DeepEqual(got, want) {
- t.Errorf("RemoveMapping: got %+v, wanted %+v", got, want)
- }
-
- // Mappings:
- // [0x10000, 0x12000) writable => [0x1000, 0x3000)
- // [0x21000, 0x22000) readonly => [0x3000, 0x4000)
- t.Log(&set)
-
- // Unmap should fail since writable bit doesn't match.
- unmapped = set.RemoveMapping(ms, usermem.AddrRange{0x10000, 0x12000}, 0x1000, false)
- if len(unmapped) != 0 {
- t.Errorf("RemoveMapping: got %+v, wanted []", unmapped)
- }
-
- unmapped = set.RemoveMapping(ms, usermem.AddrRange{0x10000, 0x12000}, 0x1000, true)
- if got, want := unmapped, []MappableRange{{0x1000, 0x2000}}; !reflect.DeepEqual(got, want) {
- t.Errorf("RemoveMapping: got %+v, wanted %+v", got, want)
- }
-
- // Mappings:
- // [0x21000, 0x22000) readonly => [0x3000, 0x4000)
- t.Log(&set)
-
- unmapped = set.RemoveMapping(ms, usermem.AddrRange{0x21000, 0x22000}, 0x3000, false)
- if got, want := unmapped, []MappableRange{{0x3000, 0x4000}}; !reflect.DeepEqual(got, want) {
- t.Errorf("RemoveMapping: got %+v, wanted %+v", got, want)
- }
-}
diff --git a/pkg/sentry/memmap/memmap_state_autogen.go b/pkg/sentry/memmap/memmap_state_autogen.go
new file mode 100755
index 000000000..2d0814ab1
--- /dev/null
+++ b/pkg/sentry/memmap/memmap_state_autogen.go
@@ -0,0 +1,93 @@
+// automatically generated by stateify.
+
+package memmap
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *MappableRange) beforeSave() {}
+func (x *MappableRange) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Start", &x.Start)
+ m.Save("End", &x.End)
+}
+
+func (x *MappableRange) afterLoad() {}
+func (x *MappableRange) load(m state.Map) {
+ m.Load("Start", &x.Start)
+ m.Load("End", &x.End)
+}
+
+func (x *MappingOfRange) beforeSave() {}
+func (x *MappingOfRange) save(m state.Map) {
+ x.beforeSave()
+ m.Save("MappingSpace", &x.MappingSpace)
+ m.Save("AddrRange", &x.AddrRange)
+ m.Save("Writable", &x.Writable)
+}
+
+func (x *MappingOfRange) afterLoad() {}
+func (x *MappingOfRange) load(m state.Map) {
+ m.Load("MappingSpace", &x.MappingSpace)
+ m.Load("AddrRange", &x.AddrRange)
+ m.Load("Writable", &x.Writable)
+}
+
+func (x *MappingSet) beforeSave() {}
+func (x *MappingSet) save(m state.Map) {
+ x.beforeSave()
+ var root *MappingSegmentDataSlices = x.saveRoot()
+ m.SaveValue("root", root)
+}
+
+func (x *MappingSet) afterLoad() {}
+func (x *MappingSet) load(m state.Map) {
+ m.LoadValue("root", new(*MappingSegmentDataSlices), func(y interface{}) { x.loadRoot(y.(*MappingSegmentDataSlices)) })
+}
+
+func (x *Mappingnode) beforeSave() {}
+func (x *Mappingnode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("nrSegments", &x.nrSegments)
+ m.Save("parent", &x.parent)
+ m.Save("parentIndex", &x.parentIndex)
+ m.Save("hasChildren", &x.hasChildren)
+ m.Save("keys", &x.keys)
+ m.Save("values", &x.values)
+ m.Save("children", &x.children)
+}
+
+func (x *Mappingnode) afterLoad() {}
+func (x *Mappingnode) load(m state.Map) {
+ m.Load("nrSegments", &x.nrSegments)
+ m.Load("parent", &x.parent)
+ m.Load("parentIndex", &x.parentIndex)
+ m.Load("hasChildren", &x.hasChildren)
+ m.Load("keys", &x.keys)
+ m.Load("values", &x.values)
+ m.Load("children", &x.children)
+}
+
+func (x *MappingSegmentDataSlices) beforeSave() {}
+func (x *MappingSegmentDataSlices) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Start", &x.Start)
+ m.Save("End", &x.End)
+ m.Save("Values", &x.Values)
+}
+
+func (x *MappingSegmentDataSlices) afterLoad() {}
+func (x *MappingSegmentDataSlices) load(m state.Map) {
+ m.Load("Start", &x.Start)
+ m.Load("End", &x.End)
+ m.Load("Values", &x.Values)
+}
+
+func init() {
+ state.Register("memmap.MappableRange", (*MappableRange)(nil), state.Fns{Save: (*MappableRange).save, Load: (*MappableRange).load})
+ state.Register("memmap.MappingOfRange", (*MappingOfRange)(nil), state.Fns{Save: (*MappingOfRange).save, Load: (*MappingOfRange).load})
+ state.Register("memmap.MappingSet", (*MappingSet)(nil), state.Fns{Save: (*MappingSet).save, Load: (*MappingSet).load})
+ state.Register("memmap.Mappingnode", (*Mappingnode)(nil), state.Fns{Save: (*Mappingnode).save, Load: (*Mappingnode).load})
+ state.Register("memmap.MappingSegmentDataSlices", (*MappingSegmentDataSlices)(nil), state.Fns{Save: (*MappingSegmentDataSlices).save, Load: (*MappingSegmentDataSlices).load})
+}
diff --git a/pkg/sentry/mm/BUILD b/pkg/sentry/mm/BUILD
deleted file mode 100644
index b35c8c673..000000000
--- a/pkg/sentry/mm/BUILD
+++ /dev/null
@@ -1,144 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_template_instance(
- name = "file_refcount_set",
- out = "file_refcount_set.go",
- imports = {
- "platform": "gvisor.dev/gvisor/pkg/sentry/platform",
- },
- package = "mm",
- prefix = "fileRefcount",
- template = "//pkg/segment:generic_set",
- types = {
- "Key": "uint64",
- "Range": "platform.FileRange",
- "Value": "int32",
- "Functions": "fileRefcountSetFunctions",
- },
-)
-
-go_template_instance(
- name = "vma_set",
- out = "vma_set.go",
- consts = {
- "minDegree": "8",
- },
- imports = {
- "usermem": "gvisor.dev/gvisor/pkg/sentry/usermem",
- },
- package = "mm",
- prefix = "vma",
- template = "//pkg/segment:generic_set",
- types = {
- "Key": "usermem.Addr",
- "Range": "usermem.AddrRange",
- "Value": "vma",
- "Functions": "vmaSetFunctions",
- },
-)
-
-go_template_instance(
- name = "pma_set",
- out = "pma_set.go",
- consts = {
- "minDegree": "8",
- },
- imports = {
- "usermem": "gvisor.dev/gvisor/pkg/sentry/usermem",
- },
- package = "mm",
- prefix = "pma",
- template = "//pkg/segment:generic_set",
- types = {
- "Key": "usermem.Addr",
- "Range": "usermem.AddrRange",
- "Value": "pma",
- "Functions": "pmaSetFunctions",
- },
-)
-
-go_template_instance(
- name = "io_list",
- out = "io_list.go",
- package = "mm",
- prefix = "io",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*ioResult",
- "Linker": "*ioResult",
- },
-)
-
-go_library(
- name = "mm",
- srcs = [
- "address_space.go",
- "aio_context.go",
- "aio_context_state.go",
- "debug.go",
- "file_refcount_set.go",
- "io.go",
- "io_list.go",
- "lifecycle.go",
- "metadata.go",
- "mm.go",
- "pma.go",
- "pma_set.go",
- "procfs.go",
- "save_restore.go",
- "shm.go",
- "special_mappable.go",
- "syscalls.go",
- "vma.go",
- "vma_set.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/mm",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/atomicbitops",
- "//pkg/log",
- "//pkg/refs",
- "//pkg/sentry/arch",
- "//pkg/sentry/context",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/proc/seqfile",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/kernel/futex",
- "//pkg/sentry/kernel/shm",
- "//pkg/sentry/limits",
- "//pkg/sentry/memmap",
- "//pkg/sentry/pgalloc",
- "//pkg/sentry/platform",
- "//pkg/sentry/platform/safecopy",
- "//pkg/sentry/safemem",
- "//pkg/sentry/usage",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- "//pkg/tcpip/buffer",
- "//third_party/gvsync",
- ],
-)
-
-go_test(
- name = "mm_test",
- size = "small",
- srcs = ["mm_test.go"],
- embed = [":mm"],
- deps = [
- "//pkg/sentry/arch",
- "//pkg/sentry/context",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/limits",
- "//pkg/sentry/memmap",
- "//pkg/sentry/pgalloc",
- "//pkg/sentry/platform",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- ],
-)
diff --git a/pkg/sentry/mm/README.md b/pkg/sentry/mm/README.md
deleted file mode 100644
index e1322e373..000000000
--- a/pkg/sentry/mm/README.md
+++ /dev/null
@@ -1,280 +0,0 @@
-This package provides an emulation of Linux semantics for application virtual
-memory mappings.
-
-For completeness, this document also describes aspects of the memory management
-subsystem defined outside this package.
-
-# Background
-
-We begin by describing semantics for virtual memory in Linux.
-
-A virtual address space is defined as a collection of mappings from virtual
-addresses to physical memory. However, userspace applications do not configure
-mappings to physical memory directly. Instead, applications configure memory
-mappings from virtual addresses to offsets into a file using the `mmap` system
-call.[^mmap-anon] For example, a call to:
-
- mmap(
- /* addr = */ 0x400000,
- /* length = */ 0x1000,
- PROT_READ | PROT_WRITE,
- MAP_SHARED,
- /* fd = */ 3,
- /* offset = */ 0);
-
-creates a mapping of length 0x1000 bytes, starting at virtual address (VA)
-0x400000, to offset 0 in the file represented by file descriptor (FD) 3. Within
-the Linux kernel, virtual memory mappings are represented by *virtual memory
-areas* (VMAs). Supposing that FD 3 represents file /tmp/foo, the state of the
-virtual memory subsystem after the `mmap` call may be depicted as:
-
- VMA: VA:0x400000 -> /tmp/foo:0x0
-
-Establishing a virtual memory area does not necessarily establish a mapping to a
-physical address, because Linux has not necessarily provisioned physical memory
-to store the file's contents. Thus, if the application attempts to read the
-contents of VA 0x400000, it may incur a *page fault*, a CPU exception that
-forces the kernel to create such a mapping to service the read.
-
-For a file, doing so consists of several logical phases:
-
-1. The kernel allocates physical memory to store the contents of the required
- part of the file, and copies file contents to the allocated memory.
- Supposing that the kernel chooses the physical memory at physical address
- (PA) 0x2fb000, the resulting state of the system is:
-
- VMA: VA:0x400000 -> /tmp/foo:0x0
- Filemap: /tmp/foo:0x0 -> PA:0x2fb000
-
- (In Linux the state of the mapping from file offset to physical memory is
- stored in `struct address_space`, but to avoid confusion with other notions
- of address space we will refer to this system as filemap, named after Linux
- kernel source file `mm/filemap.c`.)
-
-2. The kernel stores the effective mapping from virtual to physical address in
- a *page table entry* (PTE) in the application's *page tables*, which are
- used by the CPU's virtual memory hardware to perform address translation.
- The resulting state of the system is:
-
- VMA: VA:0x400000 -> /tmp/foo:0x0
- Filemap: /tmp/foo:0x0 -> PA:0x2fb000
- PTE: VA:0x400000 -----------------> PA:0x2fb000
-
- The PTE is required for the application to actually use the contents of the
- mapped file as virtual memory. However, the PTE is derived from the VMA and
- filemap state, both of which are independently mutable, such that mutations
- to either will affect the PTE. For example:
-
- - The application may remove the VMA using the `munmap` system call. This
- breaks the mapping from VA:0x400000 to /tmp/foo:0x0, and consequently
- the mapping from VA:0x400000 to PA:0x2fb000. However, it does not
- necessarily break the mapping from /tmp/foo:0x0 to PA:0x2fb000, so a
- future mapping of the same file offset may reuse this physical memory.
-
- - The application may invalidate the file's contents by passing a length
- of 0 to the `ftruncate` system call. This breaks the mapping from
- /tmp/foo:0x0 to PA:0x2fb000, and consequently the mapping from
- VA:0x400000 to PA:0x2fb000. However, it does not break the mapping from
- VA:0x400000 to /tmp/foo:0x0, so future changes to the file's contents
- may again be made visible at VA:0x400000 after another page fault
- results in the allocation of a new physical address.
-
- Note that, in order to correctly break the mapping from VA:0x400000 to
- PA:0x2fb000 in the latter case, filemap must also store a *reverse mapping*
- from /tmp/foo:0x0 to VA:0x400000 so that it can locate and remove the PTE.
-
-[^mmap-anon]: Memory mappings to non-files are discussed in later sections.
-
-## Private Mappings
-
-The preceding example considered VMAs created using the `MAP_SHARED` flag, which
-means that PTEs derived from the mapping should always use physical memory that
-represents the current state of the mapped file.[^mmap-dev-zero] Applications
-can alternatively pass the `MAP_PRIVATE` flag to create a *private mapping*.
-Private mappings are *copy-on-write*.
-
-Suppose that the application instead created a private mapping in the previous
-example. In Linux, the state of the system after a read page fault would be:
-
- VMA: VA:0x400000 -> /tmp/foo:0x0 (private)
- Filemap: /tmp/foo:0x0 -> PA:0x2fb000
- PTE: VA:0x400000 -----------------> PA:0x2fb000 (read-only)
-
-Now suppose the application attempts to write to VA:0x400000. For a shared
-mapping, the write would be propagated to PA:0x2fb000, and the kernel would be
-responsible for ensuring that the write is later propagated to the mapped file.
-For a private mapping, the write incurs another page fault since the PTE is
-marked read-only. In response, the kernel allocates physical memory to store the
-mapping's *private copy* of the file's contents, copies file contents to the
-allocated memory, and changes the PTE to map to the private copy. Supposing that
-the kernel chooses the physical memory at physical address (PA) 0x5ea000, the
-resulting state of the system is:
-
- VMA: VA:0x400000 -> /tmp/foo:0x0 (private)
- Filemap: /tmp/foo:0x0 -> PA:0x2fb000
- PTE: VA:0x400000 -----------------> PA:0x5ea000
-
-Note that the filemap mapping from /tmp/foo:0x0 to PA:0x2fb000 may still exist,
-but is now irrelevant to this mapping.
-
-[^mmap-dev-zero]: Modulo files with special mmap semantics such as `/dev/zero`.
-
-## Anonymous Mappings
-
-Instead of passing a file to the `mmap` system call, applications can instead
-request an *anonymous* mapping by passing the `MAP_ANONYMOUS` flag.
-Semantically, an anonymous mapping is essentially a mapping to an ephemeral file
-initially filled with zero bytes. Practically speaking, this is how shared
-anonymous mappings are implemented, but private anonymous mappings do not result
-in the creation of an ephemeral file; since there would be no way to modify the
-contents of the underlying file through a private mapping, all private anonymous
-mappings use a single shared page filled with zero bytes until copy-on-write
-occurs.
-
-# Virtual Memory in the Sentry
-
-The sentry implements application virtual memory atop a host kernel, introducing
-an additional level of indirection to the above.
-
-Consider the same scenario as in the previous section. Since the sentry handles
-application system calls, the effect of an application `mmap` system call is to
-create a VMA in the sentry (as opposed to the host kernel):
-
- Sentry VMA: VA:0x400000 -> /tmp/foo:0x0
-
-When the application first incurs a page fault on this address, the host kernel
-delivers information about the page fault to the sentry in a platform-dependent
-manner, and the sentry handles the fault:
-
-1. The sentry allocates memory to store the contents of the required part of
- the file, and copies file contents to the allocated memory. However, since
- the sentry is implemented atop a host kernel, it does not configure mappings
- to physical memory directly. Instead, mappable "memory" in the sentry is
- represented by a host file descriptor and offset, since (as noted in
- "Background") this is the memory mapping primitive provided by the host
- kernel. In general, memory is allocated from a temporary host file using the
- `pgalloc` package. Supposing that the sentry allocates offset 0x3000 from
- host file "memory-file", the resulting state is:
-
- Sentry VMA: VA:0x400000 -> /tmp/foo:0x0
- Sentry filemap: /tmp/foo:0x0 -> host:memory-file:0x3000
-
-2. The sentry stores the effective mapping from virtual address to host file in
- a host VMA by invoking the `mmap` system call:
-
- Sentry VMA: VA:0x400000 -> /tmp/foo:0x0
- Sentry filemap: /tmp/foo:0x0 -> host:memory-file:0x3000
- Host VMA: VA:0x400000 -----------------> host:memory-file:0x3000
-
-3. The sentry returns control to the application, which immediately incurs the
- page fault again.[^mmap-populate] However, since a host VMA now exists for
- the faulting virtual address, the host kernel now handles the page fault as
- described in "Background":
-
- Sentry VMA: VA:0x400000 -> /tmp/foo:0x0
- Sentry filemap: /tmp/foo:0x0 -> host:memory-file:0x3000
- Host VMA: VA:0x400000 -----------------> host:memory-file:0x3000
- Host filemap: host:memory-file:0x3000 -> PA:0x2fb000
- Host PTE: VA:0x400000 --------------------------------------------> PA:0x2fb000
-
-Thus, from an implementation standpoint, host VMAs serve the same purpose in the
-sentry that PTEs do in Linux. As in Linux, sentry VMA and filemap state is
-independently mutable, and the desired state of host VMAs is derived from that
-state.
-
-[^mmap-populate]: The sentry could force the host kernel to establish PTEs when
- it creates the host VMA by passing the `MAP_POPULATE` flag to
- the `mmap` system call, but usually does not. This is because,
- to reduce the number of page faults that require handling by
- the sentry and (correspondingly) the number of host `mmap`
- system calls, the sentry usually creates host VMAs that are
- much larger than the single faulting page.
-
-## Private Mappings
-
-The sentry implements private mappings consistently with Linux. Before
-copy-on-write, the private mapping example given in the Background results in:
-
- Sentry VMA: VA:0x400000 -> /tmp/foo:0x0 (private)
- Sentry filemap: /tmp/foo:0x0 -> host:memory-file:0x3000
- Host VMA: VA:0x400000 -----------------> host:memory-file:0x3000 (read-only)
- Host filemap: host:memory-file:0x3000 -> PA:0x2fb000
- Host PTE: VA:0x400000 --------------------------------------------> PA:0x2fb000 (read-only)
-
-When the application attempts to write to this address, the host kernel delivers
-information about the resulting page fault to the sentry. Analogous to Linux,
-the sentry allocates memory to store the mapping's private copy of the file's
-contents, copies file contents to the allocated memory, and changes the host VMA
-to map to the private copy. Supposing that the sentry chooses the offset 0x4000
-in host file `memory-file` to store the private copy, the state of the system
-after copy-on-write is:
-
- Sentry VMA: VA:0x400000 -> /tmp/foo:0x0 (private)
- Sentry filemap: /tmp/foo:0x0 -> host:memory-file:0x3000
- Host VMA: VA:0x400000 -----------------> host:memory-file:0x4000
- Host filemap: host:memory-file:0x4000 -> PA:0x5ea000
- Host PTE: VA:0x400000 --------------------------------------------> PA:0x5ea000
-
-However, this highlights an important difference between Linux and the sentry.
-In Linux, page tables are concrete (architecture-dependent) data structures
-owned by the kernel. Conversely, the sentry has the ability to create and
-destroy host VMAs using host system calls, but it does not have direct access to
-their state. Thus, as written, if the application invokes the `munmap` system
-call to remove the sentry VMA, it is non-trivial for the sentry to determine
-that it should deallocate `host:memory-file:0x4000`. This implies that the
-sentry must retain information about the host VMAs that it has created.
-
-## Anonymous Mappings
-
-The sentry implements anonymous mappings consistently with Linux, except that
-there is no shared zero page.
-
-# Implementation Constructs
-
-In Linux:
-
-- A virtual address space is represented by `struct mm_struct`.
-
-- VMAs are represented by `struct vm_area_struct`, stored in `struct
- mm_struct::mmap`.
-
-- Mappings from file offsets to physical memory are stored in `struct
- address_space`.
-
-- Reverse mappings from file offsets to virtual mappings are stored in `struct
- address_space::i_mmap`.
-
-- Physical memory pages are represented by a pointer to `struct page` or an
- index called a *page frame number* (PFN), represented by `pfn_t`.
-
-- PTEs are represented by architecture-dependent type `pte_t`, stored in a
- table hierarchy rooted at `struct mm_struct::pgd`.
-
-In the sentry:
-
-- A virtual address space is represented by type [`mm.MemoryManager`][mm].
-
-- Sentry VMAs are represented by type [`mm.vma`][mm], stored in
- `mm.MemoryManager.vmas`.
-
-- Mappings from sentry file offsets to host file offsets are abstracted
- through interface method [`memmap.Mappable.Translate`][memmap].
-
-- Reverse mappings from sentry file offsets to virtual mappings are abstracted
- through interface methods
- [`memmap.Mappable.AddMapping` and `memmap.Mappable.RemoveMapping`][memmap].
-
-- Host files that may be mapped into host VMAs are represented by type
- [`platform.File`][platform].
-
-- Host VMAs are represented in the sentry by type [`mm.pma`][mm] ("platform
- mapping area"), stored in `mm.MemoryManager.pmas`.
-
-- Creation and destruction of host VMAs is abstracted through interface
- methods
- [`platform.AddressSpace.MapFile` and `platform.AddressSpace.Unmap`][platform].
-
-[memmap]: https://github.com/google/gvisor/blob/master/+/master/pkg/sentry/memmap/memmap.go
-[mm]: https://github.com/google/gvisor/blob/master/+/master/pkg/sentry/mm/mm.go
-[pgalloc]: https://github.com/google/gvisor/blob/master/+/master/pkg/sentry/pgalloc/pgalloc.go
-[platform]: https://github.com/google/gvisor/blob/master/+/master/pkg/sentry/platform/platform.go
diff --git a/pkg/sentry/mm/file_refcount_set.go b/pkg/sentry/mm/file_refcount_set.go
new file mode 100755
index 000000000..6b3081009
--- /dev/null
+++ b/pkg/sentry/mm/file_refcount_set.go
@@ -0,0 +1,1274 @@
+package mm
+
+import (
+ __generics_imported0 "gvisor.dev/gvisor/pkg/sentry/platform"
+)
+
+import (
+ "bytes"
+ "fmt"
+)
+
+const (
+ // minDegree is the minimum degree of an internal node in a Set B-tree.
+ //
+ // - Any non-root node has at least minDegree-1 segments.
+ //
+ // - Any non-root internal (non-leaf) node has at least minDegree children.
+ //
+ // - The root node may have fewer than minDegree-1 segments, but it may
+ // only have 0 segments if the tree is empty.
+ //
+ // Our implementation requires minDegree >= 3. Higher values of minDegree
+ // usually improve performance, but increase memory usage for small sets.
+ fileRefcountminDegree = 3
+
+ fileRefcountmaxDegree = 2 * fileRefcountminDegree
+)
+
+// A Set is a mapping of segments with non-overlapping Range keys. The zero
+// value for a Set is an empty set. Set values are not safely movable nor
+// copyable. Set is thread-compatible.
+//
+// +stateify savable
+type fileRefcountSet struct {
+ root fileRefcountnode `state:".(*fileRefcountSegmentDataSlices)"`
+}
+
+// IsEmpty returns true if the set contains no segments.
+func (s *fileRefcountSet) IsEmpty() bool {
+ return s.root.nrSegments == 0
+}
+
+// IsEmptyRange returns true iff no segments in the set overlap the given
+// range. This is semantically equivalent to s.SpanRange(r) == 0, but may be
+// more efficient.
+func (s *fileRefcountSet) IsEmptyRange(r __generics_imported0.FileRange) bool {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return true
+ }
+ _, gap := s.Find(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ return r.End <= gap.End()
+}
+
+// Span returns the total size of all segments in the set.
+func (s *fileRefcountSet) Span() uint64 {
+ var sz uint64
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sz += seg.Range().Length()
+ }
+ return sz
+}
+
+// SpanRange returns the total size of the intersection of segments in the set
+// with the given range.
+func (s *fileRefcountSet) SpanRange(r __generics_imported0.FileRange) uint64 {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return 0
+ }
+ var sz uint64
+ for seg := s.LowerBoundSegment(r.Start); seg.Ok() && seg.Start() < r.End; seg = seg.NextSegment() {
+ sz += seg.Range().Intersect(r).Length()
+ }
+ return sz
+}
+
+// FirstSegment returns the first segment in the set. If the set is empty,
+// FirstSegment returns a terminal iterator.
+func (s *fileRefcountSet) FirstSegment() fileRefcountIterator {
+ if s.root.nrSegments == 0 {
+ return fileRefcountIterator{}
+ }
+ return s.root.firstSegment()
+}
+
+// LastSegment returns the last segment in the set. If the set is empty,
+// LastSegment returns a terminal iterator.
+func (s *fileRefcountSet) LastSegment() fileRefcountIterator {
+ if s.root.nrSegments == 0 {
+ return fileRefcountIterator{}
+ }
+ return s.root.lastSegment()
+}
+
+// FirstGap returns the first gap in the set.
+func (s *fileRefcountSet) FirstGap() fileRefcountGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return fileRefcountGapIterator{n, 0}
+}
+
+// LastGap returns the last gap in the set.
+func (s *fileRefcountSet) LastGap() fileRefcountGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return fileRefcountGapIterator{n, n.nrSegments}
+}
+
+// Find returns the segment or gap whose range contains the given key. If a
+// segment is found, the returned Iterator is non-terminal and the
+// returned GapIterator is terminal. Otherwise, the returned Iterator is
+// terminal and the returned GapIterator is non-terminal.
+func (s *fileRefcountSet) Find(key uint64) (fileRefcountIterator, fileRefcountGapIterator) {
+ n := &s.root
+ for {
+
+ lower := 0
+ upper := n.nrSegments
+ for lower < upper {
+ i := lower + (upper-lower)/2
+ if r := n.keys[i]; key < r.End {
+ if key >= r.Start {
+ return fileRefcountIterator{n, i}, fileRefcountGapIterator{}
+ }
+ upper = i
+ } else {
+ lower = i + 1
+ }
+ }
+ i := lower
+ if !n.hasChildren {
+ return fileRefcountIterator{}, fileRefcountGapIterator{n, i}
+ }
+ n = n.children[i]
+ }
+}
+
+// FindSegment returns the segment whose range contains the given key. If no
+// such segment exists, FindSegment returns a terminal iterator.
+func (s *fileRefcountSet) FindSegment(key uint64) fileRefcountIterator {
+ seg, _ := s.Find(key)
+ return seg
+}
+
+// LowerBoundSegment returns the segment with the lowest range that contains a
+// key greater than or equal to min. If no such segment exists,
+// LowerBoundSegment returns a terminal iterator.
+func (s *fileRefcountSet) LowerBoundSegment(min uint64) fileRefcountIterator {
+ seg, gap := s.Find(min)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.NextSegment()
+}
+
+// UpperBoundSegment returns the segment with the highest range that contains a
+// key less than or equal to max. If no such segment exists, UpperBoundSegment
+// returns a terminal iterator.
+func (s *fileRefcountSet) UpperBoundSegment(max uint64) fileRefcountIterator {
+ seg, gap := s.Find(max)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.PrevSegment()
+}
+
+// FindGap returns the gap containing the given key. If no such gap exists
+// (i.e. the set contains a segment containing that key), FindGap returns a
+// terminal iterator.
+func (s *fileRefcountSet) FindGap(key uint64) fileRefcountGapIterator {
+ _, gap := s.Find(key)
+ return gap
+}
+
+// LowerBoundGap returns the gap with the lowest range that is greater than or
+// equal to min.
+func (s *fileRefcountSet) LowerBoundGap(min uint64) fileRefcountGapIterator {
+ seg, gap := s.Find(min)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.NextGap()
+}
+
+// UpperBoundGap returns the gap with the highest range that is less than or
+// equal to max.
+func (s *fileRefcountSet) UpperBoundGap(max uint64) fileRefcountGapIterator {
+ seg, gap := s.Find(max)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.PrevGap()
+}
+
+// Add inserts the given segment into the set and returns true. If the new
+// segment can be merged with adjacent segments, Add will do so. If the new
+// segment would overlap an existing segment, Add returns false. If Add
+// succeeds, all existing iterators are invalidated.
+func (s *fileRefcountSet) Add(r __generics_imported0.FileRange, val int32) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.Insert(gap, r, val)
+ return true
+}
+
+// AddWithoutMerging inserts the given segment into the set and returns true.
+// If it would overlap an existing segment, AddWithoutMerging does nothing and
+// returns false. If AddWithoutMerging succeeds, all existing iterators are
+// invalidated.
+func (s *fileRefcountSet) AddWithoutMerging(r __generics_imported0.FileRange, val int32) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.InsertWithoutMergingUnchecked(gap, r, val)
+ return true
+}
+
+// Insert inserts the given segment into the given gap. If the new segment can
+// be merged with adjacent segments, Insert will do so. Insert returns an
+// iterator to the segment containing the inserted value (which may have been
+// merged with other values). All existing iterators (including gap, but not
+// including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid, Insert panics.
+//
+// Insert is semantically equivalent to a InsertWithoutMerging followed by a
+// Merge, but may be more efficient. Note that there is no unchecked variant of
+// Insert since Insert must retrieve and inspect gap's predecessor and
+// successor segments regardless.
+func (s *fileRefcountSet) Insert(gap fileRefcountGapIterator, r __generics_imported0.FileRange, val int32) fileRefcountIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ prev, next := gap.PrevSegment(), gap.NextSegment()
+ if prev.Ok() && prev.End() > r.Start {
+ panic(fmt.Sprintf("new segment %v overlaps predecessor %v", r, prev.Range()))
+ }
+ if next.Ok() && next.Start() < r.End {
+ panic(fmt.Sprintf("new segment %v overlaps successor %v", r, next.Range()))
+ }
+ if prev.Ok() && prev.End() == r.Start {
+ if mval, ok := (fileRefcountSetFunctions{}).Merge(prev.Range(), prev.Value(), r, val); ok {
+ prev.SetEndUnchecked(r.End)
+ prev.SetValue(mval)
+ if next.Ok() && next.Start() == r.End {
+ val = mval
+ if mval, ok := (fileRefcountSetFunctions{}).Merge(prev.Range(), val, next.Range(), next.Value()); ok {
+ prev.SetEndUnchecked(next.End())
+ prev.SetValue(mval)
+ return s.Remove(next).PrevSegment()
+ }
+ }
+ return prev
+ }
+ }
+ if next.Ok() && next.Start() == r.End {
+ if mval, ok := (fileRefcountSetFunctions{}).Merge(r, val, next.Range(), next.Value()); ok {
+ next.SetStartUnchecked(r.Start)
+ next.SetValue(mval)
+ return next
+ }
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMerging inserts the given segment into the given gap and
+// returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid,
+// InsertWithoutMerging panics.
+func (s *fileRefcountSet) InsertWithoutMerging(gap fileRefcountGapIterator, r __generics_imported0.FileRange, val int32) fileRefcountIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if gr := gap.Range(); !gr.IsSupersetOf(r) {
+ panic(fmt.Sprintf("cannot insert segment range %v into gap range %v", r, gr))
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMergingUnchecked inserts the given segment into the given gap
+// and returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// Preconditions: r.Start >= gap.Start(); r.End <= gap.End().
+func (s *fileRefcountSet) InsertWithoutMergingUnchecked(gap fileRefcountGapIterator, r __generics_imported0.FileRange, val int32) fileRefcountIterator {
+ gap = gap.node.rebalanceBeforeInsert(gap)
+ copy(gap.node.keys[gap.index+1:], gap.node.keys[gap.index:gap.node.nrSegments])
+ copy(gap.node.values[gap.index+1:], gap.node.values[gap.index:gap.node.nrSegments])
+ gap.node.keys[gap.index] = r
+ gap.node.values[gap.index] = val
+ gap.node.nrSegments++
+ return fileRefcountIterator{gap.node, gap.index}
+}
+
+// Remove removes the given segment and returns an iterator to the vacated gap.
+// All existing iterators (including seg, but not including the returned
+// iterator) are invalidated.
+func (s *fileRefcountSet) Remove(seg fileRefcountIterator) fileRefcountGapIterator {
+
+ if seg.node.hasChildren {
+
+ victim := seg.PrevSegment()
+
+ seg.SetRangeUnchecked(victim.Range())
+ seg.SetValue(victim.Value())
+ return s.Remove(victim).NextGap()
+ }
+ copy(seg.node.keys[seg.index:], seg.node.keys[seg.index+1:seg.node.nrSegments])
+ copy(seg.node.values[seg.index:], seg.node.values[seg.index+1:seg.node.nrSegments])
+ fileRefcountSetFunctions{}.ClearValue(&seg.node.values[seg.node.nrSegments-1])
+ seg.node.nrSegments--
+ return seg.node.rebalanceAfterRemove(fileRefcountGapIterator{seg.node, seg.index})
+}
+
+// RemoveAll removes all segments from the set. All existing iterators are
+// invalidated.
+func (s *fileRefcountSet) RemoveAll() {
+ s.root = fileRefcountnode{}
+}
+
+// RemoveRange removes all segments in the given range. An iterator to the
+// newly formed gap is returned, and all existing iterators are invalidated.
+func (s *fileRefcountSet) RemoveRange(r __generics_imported0.FileRange) fileRefcountGapIterator {
+ seg, gap := s.Find(r.Start)
+ if seg.Ok() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ for seg = gap.NextSegment(); seg.Ok() && seg.Start() < r.End; seg = gap.NextSegment() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ return gap
+}
+
+// Merge attempts to merge two neighboring segments. If successful, Merge
+// returns an iterator to the merged segment, and all existing iterators are
+// invalidated. Otherwise, Merge returns a terminal iterator.
+//
+// If first is not the predecessor of second, Merge panics.
+func (s *fileRefcountSet) Merge(first, second fileRefcountIterator) fileRefcountIterator {
+ if first.NextSegment() != second {
+ panic(fmt.Sprintf("attempt to merge non-neighboring segments %v, %v", first.Range(), second.Range()))
+ }
+ return s.MergeUnchecked(first, second)
+}
+
+// MergeUnchecked attempts to merge two neighboring segments. If successful,
+// MergeUnchecked returns an iterator to the merged segment, and all existing
+// iterators are invalidated. Otherwise, MergeUnchecked returns a terminal
+// iterator.
+//
+// Precondition: first is the predecessor of second: first.NextSegment() ==
+// second, first == second.PrevSegment().
+func (s *fileRefcountSet) MergeUnchecked(first, second fileRefcountIterator) fileRefcountIterator {
+ if first.End() == second.Start() {
+ if mval, ok := (fileRefcountSetFunctions{}).Merge(first.Range(), first.Value(), second.Range(), second.Value()); ok {
+
+ first.SetEndUnchecked(second.End())
+ first.SetValue(mval)
+ return s.Remove(second).PrevSegment()
+ }
+ }
+ return fileRefcountIterator{}
+}
+
+// MergeAll attempts to merge all adjacent segments in the set. All existing
+// iterators are invalidated.
+func (s *fileRefcountSet) MergeAll() {
+ seg := s.FirstSegment()
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeRange attempts to merge all adjacent segments that contain a key in the
+// specific range. All existing iterators are invalidated.
+func (s *fileRefcountSet) MergeRange(r __generics_imported0.FileRange) {
+ seg := s.LowerBoundSegment(r.Start)
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() && next.Range().Start < r.End {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeAdjacent attempts to merge the segment containing r.Start with its
+// predecessor, and the segment containing r.End-1 with its successor.
+func (s *fileRefcountSet) MergeAdjacent(r __generics_imported0.FileRange) {
+ first := s.FindSegment(r.Start)
+ if first.Ok() {
+ if prev := first.PrevSegment(); prev.Ok() {
+ s.Merge(prev, first)
+ }
+ }
+ last := s.FindSegment(r.End - 1)
+ if last.Ok() {
+ if next := last.NextSegment(); next.Ok() {
+ s.Merge(last, next)
+ }
+ }
+}
+
+// Split splits the given segment at the given key and returns iterators to the
+// two resulting segments. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+//
+// If the segment cannot be split at split (because split is at the start or
+// end of the segment's range, so splitting would produce a segment with zero
+// length, or because split falls outside the segment's range altogether),
+// Split panics.
+func (s *fileRefcountSet) Split(seg fileRefcountIterator, split uint64) (fileRefcountIterator, fileRefcountIterator) {
+ if !seg.Range().CanSplitAt(split) {
+ panic(fmt.Sprintf("can't split %v at %v", seg.Range(), split))
+ }
+ return s.SplitUnchecked(seg, split)
+}
+
+// SplitUnchecked splits the given segment at the given key and returns
+// iterators to the two resulting segments. All existing iterators (including
+// seg, but not including the returned iterators) are invalidated.
+//
+// Preconditions: seg.Start() < key < seg.End().
+func (s *fileRefcountSet) SplitUnchecked(seg fileRefcountIterator, split uint64) (fileRefcountIterator, fileRefcountIterator) {
+ val1, val2 := (fileRefcountSetFunctions{}).Split(seg.Range(), seg.Value(), split)
+ end2 := seg.End()
+ seg.SetEndUnchecked(split)
+ seg.SetValue(val1)
+ seg2 := s.InsertWithoutMergingUnchecked(seg.NextGap(), __generics_imported0.FileRange{split, end2}, val2)
+
+ return seg2.PrevSegment(), seg2
+}
+
+// SplitAt splits the segment straddling split, if one exists. SplitAt returns
+// true if a segment was split and false otherwise. If SplitAt splits a
+// segment, all existing iterators are invalidated.
+func (s *fileRefcountSet) SplitAt(split uint64) bool {
+ if seg := s.FindSegment(split); seg.Ok() && seg.Range().CanSplitAt(split) {
+ s.SplitUnchecked(seg, split)
+ return true
+ }
+ return false
+}
+
+// Isolate ensures that the given segment's range does not escape r by
+// splitting at r.Start and r.End if necessary, and returns an updated iterator
+// to the bounded segment. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+func (s *fileRefcountSet) Isolate(seg fileRefcountIterator, r __generics_imported0.FileRange) fileRefcountIterator {
+ if seg.Range().CanSplitAt(r.Start) {
+ _, seg = s.SplitUnchecked(seg, r.Start)
+ }
+ if seg.Range().CanSplitAt(r.End) {
+ seg, _ = s.SplitUnchecked(seg, r.End)
+ }
+ return seg
+}
+
+// ApplyContiguous applies a function to a contiguous range of segments,
+// splitting if necessary. The function is applied until the first gap is
+// encountered, at which point the gap is returned. If the function is applied
+// across the entire range, a terminal gap is returned. All existing iterators
+// are invalidated.
+//
+// N.B. The Iterator must not be invalidated by the function.
+func (s *fileRefcountSet) ApplyContiguous(r __generics_imported0.FileRange, fn func(seg fileRefcountIterator)) fileRefcountGapIterator {
+ seg, gap := s.Find(r.Start)
+ if !seg.Ok() {
+ return gap
+ }
+ for {
+ seg = s.Isolate(seg, r)
+ fn(seg)
+ if seg.End() >= r.End {
+ return fileRefcountGapIterator{}
+ }
+ gap = seg.NextGap()
+ if !gap.IsEmpty() {
+ return gap
+ }
+ seg = gap.NextSegment()
+ if !seg.Ok() {
+
+ return fileRefcountGapIterator{}
+ }
+ }
+}
+
+// +stateify savable
+type fileRefcountnode struct {
+ // An internal binary tree node looks like:
+ //
+ // K
+ // / \
+ // Cl Cr
+ //
+ // where all keys in the subtree rooted by Cl (the left subtree) are less
+ // than K (the key of the parent node), and all keys in the subtree rooted
+ // by Cr (the right subtree) are greater than K.
+ //
+ // An internal B-tree node's indexes work out to look like:
+ //
+ // K0 K1 K2 ... Kn-1
+ // / \/ \/ \ ... / \
+ // C0 C1 C2 C3 ... Cn-1 Cn
+ //
+ // where n is nrSegments.
+ nrSegments int
+
+ // parent is a pointer to this node's parent. If this node is root, parent
+ // is nil.
+ parent *fileRefcountnode
+
+ // parentIndex is the index of this node in parent.children.
+ parentIndex int
+
+ // Flag for internal nodes that is technically redundant with "children[0]
+ // != nil", but is stored in the first cache line. "hasChildren" rather
+ // than "isLeaf" because false must be the correct value for an empty root.
+ hasChildren bool
+
+ // Nodes store keys and values in separate arrays to maximize locality in
+ // the common case (scanning keys for lookup).
+ keys [fileRefcountmaxDegree - 1]__generics_imported0.FileRange
+ values [fileRefcountmaxDegree - 1]int32
+ children [fileRefcountmaxDegree]*fileRefcountnode
+}
+
+// firstSegment returns the first segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *fileRefcountnode) firstSegment() fileRefcountIterator {
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return fileRefcountIterator{n, 0}
+}
+
+// lastSegment returns the last segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *fileRefcountnode) lastSegment() fileRefcountIterator {
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return fileRefcountIterator{n, n.nrSegments - 1}
+}
+
+func (n *fileRefcountnode) prevSibling() *fileRefcountnode {
+ if n.parent == nil || n.parentIndex == 0 {
+ return nil
+ }
+ return n.parent.children[n.parentIndex-1]
+}
+
+func (n *fileRefcountnode) nextSibling() *fileRefcountnode {
+ if n.parent == nil || n.parentIndex == n.parent.nrSegments {
+ return nil
+ }
+ return n.parent.children[n.parentIndex+1]
+}
+
+// rebalanceBeforeInsert splits n and its ancestors if they are full, as
+// required for insertion, and returns an updated iterator to the position
+// represented by gap.
+func (n *fileRefcountnode) rebalanceBeforeInsert(gap fileRefcountGapIterator) fileRefcountGapIterator {
+ if n.parent != nil {
+ gap = n.parent.rebalanceBeforeInsert(gap)
+ }
+ if n.nrSegments < fileRefcountmaxDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ left := &fileRefcountnode{
+ nrSegments: fileRefcountminDegree - 1,
+ parent: n,
+ parentIndex: 0,
+ hasChildren: n.hasChildren,
+ }
+ right := &fileRefcountnode{
+ nrSegments: fileRefcountminDegree - 1,
+ parent: n,
+ parentIndex: 1,
+ hasChildren: n.hasChildren,
+ }
+ copy(left.keys[:fileRefcountminDegree-1], n.keys[:fileRefcountminDegree-1])
+ copy(left.values[:fileRefcountminDegree-1], n.values[:fileRefcountminDegree-1])
+ copy(right.keys[:fileRefcountminDegree-1], n.keys[fileRefcountminDegree:])
+ copy(right.values[:fileRefcountminDegree-1], n.values[fileRefcountminDegree:])
+ n.keys[0], n.values[0] = n.keys[fileRefcountminDegree-1], n.values[fileRefcountminDegree-1]
+ fileRefcountzeroValueSlice(n.values[1:])
+ if n.hasChildren {
+ copy(left.children[:fileRefcountminDegree], n.children[:fileRefcountminDegree])
+ copy(right.children[:fileRefcountminDegree], n.children[fileRefcountminDegree:])
+ fileRefcountzeroNodeSlice(n.children[2:])
+ for i := 0; i < fileRefcountminDegree; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ right.children[i].parent = right
+ right.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = 1
+ n.hasChildren = true
+ n.children[0] = left
+ n.children[1] = right
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < fileRefcountminDegree {
+ return fileRefcountGapIterator{left, gap.index}
+ }
+ return fileRefcountGapIterator{right, gap.index - fileRefcountminDegree}
+ }
+
+ copy(n.parent.keys[n.parentIndex+1:], n.parent.keys[n.parentIndex:n.parent.nrSegments])
+ copy(n.parent.values[n.parentIndex+1:], n.parent.values[n.parentIndex:n.parent.nrSegments])
+ n.parent.keys[n.parentIndex], n.parent.values[n.parentIndex] = n.keys[fileRefcountminDegree-1], n.values[fileRefcountminDegree-1]
+ copy(n.parent.children[n.parentIndex+2:], n.parent.children[n.parentIndex+1:n.parent.nrSegments+1])
+ for i := n.parentIndex + 2; i < n.parent.nrSegments+2; i++ {
+ n.parent.children[i].parentIndex = i
+ }
+ sibling := &fileRefcountnode{
+ nrSegments: fileRefcountminDegree - 1,
+ parent: n.parent,
+ parentIndex: n.parentIndex + 1,
+ hasChildren: n.hasChildren,
+ }
+ n.parent.children[n.parentIndex+1] = sibling
+ n.parent.nrSegments++
+ copy(sibling.keys[:fileRefcountminDegree-1], n.keys[fileRefcountminDegree:])
+ copy(sibling.values[:fileRefcountminDegree-1], n.values[fileRefcountminDegree:])
+ fileRefcountzeroValueSlice(n.values[fileRefcountminDegree-1:])
+ if n.hasChildren {
+ copy(sibling.children[:fileRefcountminDegree], n.children[fileRefcountminDegree:])
+ fileRefcountzeroNodeSlice(n.children[fileRefcountminDegree:])
+ for i := 0; i < fileRefcountminDegree; i++ {
+ sibling.children[i].parent = sibling
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = fileRefcountminDegree - 1
+
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < fileRefcountminDegree {
+ return gap
+ }
+ return fileRefcountGapIterator{sibling, gap.index - fileRefcountminDegree}
+}
+
+// rebalanceAfterRemove "unsplits" n and its ancestors if they are deficient
+// (contain fewer segments than required by B-tree invariants), as required for
+// removal, and returns an updated iterator to the position represented by gap.
+//
+// Precondition: n is the only node in the tree that may currently violate a
+// B-tree invariant.
+func (n *fileRefcountnode) rebalanceAfterRemove(gap fileRefcountGapIterator) fileRefcountGapIterator {
+ for {
+ if n.nrSegments >= fileRefcountminDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ return gap
+ }
+
+ if sibling := n.prevSibling(); sibling != nil && sibling.nrSegments >= fileRefcountminDegree {
+ copy(n.keys[1:], n.keys[:n.nrSegments])
+ copy(n.values[1:], n.values[:n.nrSegments])
+ n.keys[0] = n.parent.keys[n.parentIndex-1]
+ n.values[0] = n.parent.values[n.parentIndex-1]
+ n.parent.keys[n.parentIndex-1] = sibling.keys[sibling.nrSegments-1]
+ n.parent.values[n.parentIndex-1] = sibling.values[sibling.nrSegments-1]
+ fileRefcountSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ copy(n.children[1:], n.children[:n.nrSegments+1])
+ n.children[0] = sibling.children[sibling.nrSegments]
+ sibling.children[sibling.nrSegments] = nil
+ n.children[0].parent = n
+ n.children[0].parentIndex = 0
+ for i := 1; i < n.nrSegments+2; i++ {
+ n.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling && gap.index == sibling.nrSegments {
+ return fileRefcountGapIterator{n, 0}
+ }
+ if gap.node == n {
+ return fileRefcountGapIterator{n, gap.index + 1}
+ }
+ return gap
+ }
+ if sibling := n.nextSibling(); sibling != nil && sibling.nrSegments >= fileRefcountminDegree {
+ n.keys[n.nrSegments] = n.parent.keys[n.parentIndex]
+ n.values[n.nrSegments] = n.parent.values[n.parentIndex]
+ n.parent.keys[n.parentIndex] = sibling.keys[0]
+ n.parent.values[n.parentIndex] = sibling.values[0]
+ copy(sibling.keys[:sibling.nrSegments-1], sibling.keys[1:])
+ copy(sibling.values[:sibling.nrSegments-1], sibling.values[1:])
+ fileRefcountSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ n.children[n.nrSegments+1] = sibling.children[0]
+ copy(sibling.children[:sibling.nrSegments], sibling.children[1:])
+ sibling.children[sibling.nrSegments] = nil
+ n.children[n.nrSegments+1].parent = n
+ n.children[n.nrSegments+1].parentIndex = n.nrSegments + 1
+ for i := 0; i < sibling.nrSegments; i++ {
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling {
+ if gap.index == 0 {
+ return fileRefcountGapIterator{n, n.nrSegments}
+ }
+ return fileRefcountGapIterator{sibling, gap.index - 1}
+ }
+ return gap
+ }
+
+ p := n.parent
+ if p.nrSegments == 1 {
+
+ left, right := p.children[0], p.children[1]
+ p.nrSegments = left.nrSegments + right.nrSegments + 1
+ p.hasChildren = left.hasChildren
+ p.keys[left.nrSegments] = p.keys[0]
+ p.values[left.nrSegments] = p.values[0]
+ copy(p.keys[:left.nrSegments], left.keys[:left.nrSegments])
+ copy(p.values[:left.nrSegments], left.values[:left.nrSegments])
+ copy(p.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(p.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(p.children[:left.nrSegments+1], left.children[:left.nrSegments+1])
+ copy(p.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := 0; i < p.nrSegments+1; i++ {
+ p.children[i].parent = p
+ p.children[i].parentIndex = i
+ }
+ } else {
+ p.children[0] = nil
+ p.children[1] = nil
+ }
+ if gap.node == left {
+ return fileRefcountGapIterator{p, gap.index}
+ }
+ if gap.node == right {
+ return fileRefcountGapIterator{p, gap.index + left.nrSegments + 1}
+ }
+ return gap
+ }
+ // Merge n and either sibling, along with the segment separating the
+ // two, into whichever of the two nodes comes first. This is the
+ // reverse of the non-root splitting case in
+ // node.rebalanceBeforeInsert.
+ var left, right *fileRefcountnode
+ if n.parentIndex > 0 {
+ left = n.prevSibling()
+ right = n
+ } else {
+ left = n
+ right = n.nextSibling()
+ }
+
+ if gap.node == right {
+ gap = fileRefcountGapIterator{left, gap.index + left.nrSegments + 1}
+ }
+ left.keys[left.nrSegments] = p.keys[left.parentIndex]
+ left.values[left.nrSegments] = p.values[left.parentIndex]
+ copy(left.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(left.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(left.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := left.nrSegments + 1; i < left.nrSegments+right.nrSegments+2; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ }
+ }
+ left.nrSegments += right.nrSegments + 1
+ copy(p.keys[left.parentIndex:], p.keys[left.parentIndex+1:p.nrSegments])
+ copy(p.values[left.parentIndex:], p.values[left.parentIndex+1:p.nrSegments])
+ fileRefcountSetFunctions{}.ClearValue(&p.values[p.nrSegments-1])
+ copy(p.children[left.parentIndex+1:], p.children[left.parentIndex+2:p.nrSegments+1])
+ for i := 0; i < p.nrSegments; i++ {
+ p.children[i].parentIndex = i
+ }
+ p.children[p.nrSegments] = nil
+ p.nrSegments--
+
+ n = p
+ }
+}
+
+// A Iterator is conceptually one of:
+//
+// - A pointer to a segment in a set; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Iterators are copyable values and are meaningfully equality-comparable. The
+// zero value of Iterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type fileRefcountIterator struct {
+ // node is the node containing the iterated segment. If the iterator is
+ // terminal, node is nil.
+ node *fileRefcountnode
+
+ // index is the index of the segment in node.keys/values.
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (seg fileRefcountIterator) Ok() bool {
+ return seg.node != nil
+}
+
+// Range returns the iterated segment's range key.
+func (seg fileRefcountIterator) Range() __generics_imported0.FileRange {
+ return seg.node.keys[seg.index]
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (seg fileRefcountIterator) Start() uint64 {
+ return seg.node.keys[seg.index].Start
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (seg fileRefcountIterator) End() uint64 {
+ return seg.node.keys[seg.index].End
+}
+
+// SetRangeUnchecked mutates the iterated segment's range key. This operation
+// does not invalidate any iterators.
+//
+// Preconditions:
+//
+// - r.Length() > 0.
+//
+// - The new range must not overlap an existing one: If seg.NextSegment().Ok(),
+// then r.end <= seg.NextSegment().Start(); if seg.PrevSegment().Ok(), then
+// r.start >= seg.PrevSegment().End().
+func (seg fileRefcountIterator) SetRangeUnchecked(r __generics_imported0.FileRange) {
+ seg.node.keys[seg.index] = r
+}
+
+// SetRange mutates the iterated segment's range key. If the new range would
+// cause the iterated segment to overlap another segment, or if the new range
+// is invalid, SetRange panics. This operation does not invalidate any
+// iterators.
+func (seg fileRefcountIterator) SetRange(r __generics_imported0.FileRange) {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && r.Start < prev.End() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, prev.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && r.End > next.Start() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, next.Range()))
+ }
+ seg.SetRangeUnchecked(r)
+}
+
+// SetStartUnchecked mutates the iterated segment's start. This operation does
+// not invalidate any iterators.
+//
+// Preconditions: The new start must be valid: start < seg.End(); if
+// seg.PrevSegment().Ok(), then start >= seg.PrevSegment().End().
+func (seg fileRefcountIterator) SetStartUnchecked(start uint64) {
+ seg.node.keys[seg.index].Start = start
+}
+
+// SetStart mutates the iterated segment's start. If the new start value would
+// cause the iterated segment to overlap another segment, or would result in an
+// invalid range, SetStart panics. This operation does not invalidate any
+// iterators.
+func (seg fileRefcountIterator) SetStart(start uint64) {
+ if start >= seg.End() {
+ panic(fmt.Sprintf("new start %v would invalidate segment range %v", start, seg.Range()))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && start < prev.End() {
+ panic(fmt.Sprintf("new start %v would cause segment range %v to overlap segment range %v", start, seg.Range(), prev.Range()))
+ }
+ seg.SetStartUnchecked(start)
+}
+
+// SetEndUnchecked mutates the iterated segment's end. This operation does not
+// invalidate any iterators.
+//
+// Preconditions: The new end must be valid: end > seg.Start(); if
+// seg.NextSegment().Ok(), then end <= seg.NextSegment().Start().
+func (seg fileRefcountIterator) SetEndUnchecked(end uint64) {
+ seg.node.keys[seg.index].End = end
+}
+
+// SetEnd mutates the iterated segment's end. If the new end value would cause
+// the iterated segment to overlap another segment, or would result in an
+// invalid range, SetEnd panics. This operation does not invalidate any
+// iterators.
+func (seg fileRefcountIterator) SetEnd(end uint64) {
+ if end <= seg.Start() {
+ panic(fmt.Sprintf("new end %v would invalidate segment range %v", end, seg.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && end > next.Start() {
+ panic(fmt.Sprintf("new end %v would cause segment range %v to overlap segment range %v", end, seg.Range(), next.Range()))
+ }
+ seg.SetEndUnchecked(end)
+}
+
+// Value returns a copy of the iterated segment's value.
+func (seg fileRefcountIterator) Value() int32 {
+ return seg.node.values[seg.index]
+}
+
+// ValuePtr returns a pointer to the iterated segment's value. The pointer is
+// invalidated if the iterator is invalidated. This operation does not
+// invalidate any iterators.
+func (seg fileRefcountIterator) ValuePtr() *int32 {
+ return &seg.node.values[seg.index]
+}
+
+// SetValue mutates the iterated segment's value. This operation does not
+// invalidate any iterators.
+func (seg fileRefcountIterator) SetValue(val int32) {
+ seg.node.values[seg.index] = val
+}
+
+// PrevSegment returns the iterated segment's predecessor. If there is no
+// preceding segment, PrevSegment returns a terminal iterator.
+func (seg fileRefcountIterator) PrevSegment() fileRefcountIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index].lastSegment()
+ }
+ if seg.index > 0 {
+ return fileRefcountIterator{seg.node, seg.index - 1}
+ }
+ if seg.node.parent == nil {
+ return fileRefcountIterator{}
+ }
+ return fileRefcountsegmentBeforePosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// NextSegment returns the iterated segment's successor. If there is no
+// succeeding segment, NextSegment returns a terminal iterator.
+func (seg fileRefcountIterator) NextSegment() fileRefcountIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment()
+ }
+ if seg.index < seg.node.nrSegments-1 {
+ return fileRefcountIterator{seg.node, seg.index + 1}
+ }
+ if seg.node.parent == nil {
+ return fileRefcountIterator{}
+ }
+ return fileRefcountsegmentAfterPosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// PrevGap returns the gap immediately before the iterated segment.
+func (seg fileRefcountIterator) PrevGap() fileRefcountGapIterator {
+ if seg.node.hasChildren {
+
+ return seg.node.children[seg.index].lastSegment().NextGap()
+ }
+ return fileRefcountGapIterator{seg.node, seg.index}
+}
+
+// NextGap returns the gap immediately after the iterated segment.
+func (seg fileRefcountIterator) NextGap() fileRefcountGapIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment().PrevGap()
+ }
+ return fileRefcountGapIterator{seg.node, seg.index + 1}
+}
+
+// PrevNonEmpty returns the iterated segment's predecessor if it is adjacent,
+// or the gap before the iterated segment otherwise. If seg.Start() ==
+// Functions.MinKey(), PrevNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by PrevNonEmpty will be
+// non-terminal.
+func (seg fileRefcountIterator) PrevNonEmpty() (fileRefcountIterator, fileRefcountGapIterator) {
+ gap := seg.PrevGap()
+ if gap.Range().Length() != 0 {
+ return fileRefcountIterator{}, gap
+ }
+ return gap.PrevSegment(), fileRefcountGapIterator{}
+}
+
+// NextNonEmpty returns the iterated segment's successor if it is adjacent, or
+// the gap after the iterated segment otherwise. If seg.End() ==
+// Functions.MaxKey(), NextNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by NextNonEmpty will be
+// non-terminal.
+func (seg fileRefcountIterator) NextNonEmpty() (fileRefcountIterator, fileRefcountGapIterator) {
+ gap := seg.NextGap()
+ if gap.Range().Length() != 0 {
+ return fileRefcountIterator{}, gap
+ }
+ return gap.NextSegment(), fileRefcountGapIterator{}
+}
+
+// A GapIterator is conceptually one of:
+//
+// - A pointer to a position between two segments, before the first segment, or
+// after the last segment in a set, called a *gap*; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Note that the gap between two adjacent segments exists (iterators to it are
+// non-terminal), but has a length of zero. GapIterator.IsEmpty returns true
+// for such gaps. An empty set contains a single gap, spanning the entire range
+// of the set's keys.
+//
+// GapIterators are copyable values and are meaningfully equality-comparable.
+// The zero value of GapIterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type fileRefcountGapIterator struct {
+ // The representation of a GapIterator is identical to that of an Iterator,
+ // except that index corresponds to positions between segments in the same
+ // way as for node.children (see comment for node.nrSegments).
+ node *fileRefcountnode
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (gap fileRefcountGapIterator) Ok() bool {
+ return gap.node != nil
+}
+
+// Range returns the range spanned by the iterated gap.
+func (gap fileRefcountGapIterator) Range() __generics_imported0.FileRange {
+ return __generics_imported0.FileRange{gap.Start(), gap.End()}
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (gap fileRefcountGapIterator) Start() uint64 {
+ if ps := gap.PrevSegment(); ps.Ok() {
+ return ps.End()
+ }
+ return fileRefcountSetFunctions{}.MinKey()
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (gap fileRefcountGapIterator) End() uint64 {
+ if ns := gap.NextSegment(); ns.Ok() {
+ return ns.Start()
+ }
+ return fileRefcountSetFunctions{}.MaxKey()
+}
+
+// IsEmpty returns true if the iterated gap is empty (that is, the "gap" is
+// between two adjacent segments.)
+func (gap fileRefcountGapIterator) IsEmpty() bool {
+ return gap.Range().Length() == 0
+}
+
+// PrevSegment returns the segment immediately before the iterated gap. If no
+// such segment exists, PrevSegment returns a terminal iterator.
+func (gap fileRefcountGapIterator) PrevSegment() fileRefcountIterator {
+ return fileRefcountsegmentBeforePosition(gap.node, gap.index)
+}
+
+// NextSegment returns the segment immediately after the iterated gap. If no
+// such segment exists, NextSegment returns a terminal iterator.
+func (gap fileRefcountGapIterator) NextSegment() fileRefcountIterator {
+ return fileRefcountsegmentAfterPosition(gap.node, gap.index)
+}
+
+// PrevGap returns the iterated gap's predecessor. If no such gap exists,
+// PrevGap returns a terminal iterator.
+func (gap fileRefcountGapIterator) PrevGap() fileRefcountGapIterator {
+ seg := gap.PrevSegment()
+ if !seg.Ok() {
+ return fileRefcountGapIterator{}
+ }
+ return seg.PrevGap()
+}
+
+// NextGap returns the iterated gap's successor. If no such gap exists, NextGap
+// returns a terminal iterator.
+func (gap fileRefcountGapIterator) NextGap() fileRefcountGapIterator {
+ seg := gap.NextSegment()
+ if !seg.Ok() {
+ return fileRefcountGapIterator{}
+ }
+ return seg.NextGap()
+}
+
+// segmentBeforePosition returns the predecessor segment of the position given
+// by n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentBeforePosition returns a terminal iterator.
+func fileRefcountsegmentBeforePosition(n *fileRefcountnode, i int) fileRefcountIterator {
+ for i == 0 {
+ if n.parent == nil {
+ return fileRefcountIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return fileRefcountIterator{n, i - 1}
+}
+
+// segmentAfterPosition returns the successor segment of the position given by
+// n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentAfterPosition returns a terminal iterator.
+func fileRefcountsegmentAfterPosition(n *fileRefcountnode, i int) fileRefcountIterator {
+ for i == n.nrSegments {
+ if n.parent == nil {
+ return fileRefcountIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return fileRefcountIterator{n, i}
+}
+
+func fileRefcountzeroValueSlice(slice []int32) {
+
+ for i := range slice {
+ fileRefcountSetFunctions{}.ClearValue(&slice[i])
+ }
+}
+
+func fileRefcountzeroNodeSlice(slice []*fileRefcountnode) {
+ for i := range slice {
+ slice[i] = nil
+ }
+}
+
+// String stringifies a Set for debugging.
+func (s *fileRefcountSet) String() string {
+ return s.root.String()
+}
+
+// String stringifies a node (and all of its children) for debugging.
+func (n *fileRefcountnode) String() string {
+ var buf bytes.Buffer
+ n.writeDebugString(&buf, "")
+ return buf.String()
+}
+
+func (n *fileRefcountnode) writeDebugString(buf *bytes.Buffer, prefix string) {
+ if n.hasChildren != (n.nrSegments > 0 && n.children[0] != nil) {
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent value of hasChildren: got %v, want %v\n", n.hasChildren, !n.hasChildren))
+ }
+ for i := 0; i < n.nrSegments; i++ {
+ if child := n.children[i]; child != nil {
+ cprefix := fmt.Sprintf("%s- % 3d ", prefix, i)
+ if child.parent != n || child.parentIndex != i {
+ buf.WriteString(cprefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent linkage to parent: got (%p, %d), want (%p, %d)\n", child.parent, child.parentIndex, n, i))
+ }
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, i))
+ }
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("- % 3d: %v => %v\n", i, n.keys[i], n.values[i]))
+ }
+ if child := n.children[n.nrSegments]; child != nil {
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, n.nrSegments))
+ }
+}
+
+// SegmentDataSlices represents segments from a set as slices of start, end, and
+// values. SegmentDataSlices is primarily used as an intermediate representation
+// for save/restore and the layout here is optimized for that.
+//
+// +stateify savable
+type fileRefcountSegmentDataSlices struct {
+ Start []uint64
+ End []uint64
+ Values []int32
+}
+
+// ExportSortedSlice returns a copy of all segments in the given set, in ascending
+// key order.
+func (s *fileRefcountSet) ExportSortedSlices() *fileRefcountSegmentDataSlices {
+ var sds fileRefcountSegmentDataSlices
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sds.Start = append(sds.Start, seg.Start())
+ sds.End = append(sds.End, seg.End())
+ sds.Values = append(sds.Values, seg.Value())
+ }
+ sds.Start = sds.Start[:len(sds.Start):len(sds.Start)]
+ sds.End = sds.End[:len(sds.End):len(sds.End)]
+ sds.Values = sds.Values[:len(sds.Values):len(sds.Values)]
+ return &sds
+}
+
+// ImportSortedSlice initializes the given set from the given slice.
+//
+// Preconditions: s must be empty. sds must represent a valid set (the segments
+// in sds must have valid lengths that do not overlap). The segments in sds
+// must be sorted in ascending key order.
+func (s *fileRefcountSet) ImportSortedSlices(sds *fileRefcountSegmentDataSlices) error {
+ if !s.IsEmpty() {
+ return fmt.Errorf("cannot import into non-empty set %v", s)
+ }
+ gap := s.FirstGap()
+ for i := range sds.Start {
+ r := __generics_imported0.FileRange{sds.Start[i], sds.End[i]}
+ if !gap.Range().IsSupersetOf(r) {
+ return fmt.Errorf("segment overlaps a preceding segment or is incorrectly sorted: [%d, %d) => %v", sds.Start[i], sds.End[i], sds.Values[i])
+ }
+ gap = s.InsertWithoutMerging(gap, r, sds.Values[i]).NextGap()
+ }
+ return nil
+}
+func (s *fileRefcountSet) saveRoot() *fileRefcountSegmentDataSlices {
+ return s.ExportSortedSlices()
+}
+
+func (s *fileRefcountSet) loadRoot(sds *fileRefcountSegmentDataSlices) {
+ if err := s.ImportSortedSlices(sds); err != nil {
+ panic(err)
+ }
+}
diff --git a/pkg/sentry/mm/io_list.go b/pkg/sentry/mm/io_list.go
new file mode 100755
index 000000000..99c83c4b9
--- /dev/null
+++ b/pkg/sentry/mm/io_list.go
@@ -0,0 +1,173 @@
+package mm
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type ioElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (ioElementMapper) linkerFor(elem *ioResult) *ioResult { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type ioList struct {
+ head *ioResult
+ tail *ioResult
+}
+
+// Reset resets list l to the empty state.
+func (l *ioList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *ioList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *ioList) Front() *ioResult {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *ioList) Back() *ioResult {
+ return l.tail
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *ioList) PushFront(e *ioResult) {
+ ioElementMapper{}.linkerFor(e).SetNext(l.head)
+ ioElementMapper{}.linkerFor(e).SetPrev(nil)
+
+ if l.head != nil {
+ ioElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *ioList) PushBack(e *ioResult) {
+ ioElementMapper{}.linkerFor(e).SetNext(nil)
+ ioElementMapper{}.linkerFor(e).SetPrev(l.tail)
+
+ if l.tail != nil {
+ ioElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *ioList) PushBackList(m *ioList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ ioElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ ioElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *ioList) InsertAfter(b, e *ioResult) {
+ a := ioElementMapper{}.linkerFor(b).Next()
+ ioElementMapper{}.linkerFor(e).SetNext(a)
+ ioElementMapper{}.linkerFor(e).SetPrev(b)
+ ioElementMapper{}.linkerFor(b).SetNext(e)
+
+ if a != nil {
+ ioElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *ioList) InsertBefore(a, e *ioResult) {
+ b := ioElementMapper{}.linkerFor(a).Prev()
+ ioElementMapper{}.linkerFor(e).SetNext(a)
+ ioElementMapper{}.linkerFor(e).SetPrev(b)
+ ioElementMapper{}.linkerFor(a).SetPrev(e)
+
+ if b != nil {
+ ioElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *ioList) Remove(e *ioResult) {
+ prev := ioElementMapper{}.linkerFor(e).Prev()
+ next := ioElementMapper{}.linkerFor(e).Next()
+
+ if prev != nil {
+ ioElementMapper{}.linkerFor(prev).SetNext(next)
+ } else {
+ l.head = next
+ }
+
+ if next != nil {
+ ioElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else {
+ l.tail = prev
+ }
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type ioEntry struct {
+ next *ioResult
+ prev *ioResult
+}
+
+// Next returns the entry that follows e in the list.
+func (e *ioEntry) Next() *ioResult {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *ioEntry) Prev() *ioResult {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *ioEntry) SetNext(elem *ioResult) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *ioEntry) SetPrev(elem *ioResult) {
+ e.prev = elem
+}
diff --git a/pkg/sentry/mm/mm_state_autogen.go b/pkg/sentry/mm/mm_state_autogen.go
new file mode 100755
index 000000000..3b5af2401
--- /dev/null
+++ b/pkg/sentry/mm/mm_state_autogen.go
@@ -0,0 +1,388 @@
+// automatically generated by stateify.
+
+package mm
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *aioManager) beforeSave() {}
+func (x *aioManager) save(m state.Map) {
+ x.beforeSave()
+ m.Save("contexts", &x.contexts)
+}
+
+func (x *aioManager) afterLoad() {}
+func (x *aioManager) load(m state.Map) {
+ m.Load("contexts", &x.contexts)
+}
+
+func (x *ioResult) beforeSave() {}
+func (x *ioResult) save(m state.Map) {
+ x.beforeSave()
+ m.Save("data", &x.data)
+ m.Save("ioEntry", &x.ioEntry)
+}
+
+func (x *ioResult) afterLoad() {}
+func (x *ioResult) load(m state.Map) {
+ m.Load("data", &x.data)
+ m.Load("ioEntry", &x.ioEntry)
+}
+
+func (x *AIOContext) beforeSave() {}
+func (x *AIOContext) save(m state.Map) {
+ x.beforeSave()
+ if !state.IsZeroValue(x.dead) { m.Failf("dead is %v, expected zero", x.dead) }
+ m.Save("results", &x.results)
+ m.Save("maxOutstanding", &x.maxOutstanding)
+ m.Save("outstanding", &x.outstanding)
+}
+
+func (x *AIOContext) load(m state.Map) {
+ m.Load("results", &x.results)
+ m.Load("maxOutstanding", &x.maxOutstanding)
+ m.Load("outstanding", &x.outstanding)
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *aioMappable) beforeSave() {}
+func (x *aioMappable) save(m state.Map) {
+ x.beforeSave()
+ m.Save("AtomicRefCount", &x.AtomicRefCount)
+ m.Save("mfp", &x.mfp)
+ m.Save("fr", &x.fr)
+}
+
+func (x *aioMappable) afterLoad() {}
+func (x *aioMappable) load(m state.Map) {
+ m.Load("AtomicRefCount", &x.AtomicRefCount)
+ m.Load("mfp", &x.mfp)
+ m.Load("fr", &x.fr)
+}
+
+func (x *fileRefcountSet) beforeSave() {}
+func (x *fileRefcountSet) save(m state.Map) {
+ x.beforeSave()
+ var root *fileRefcountSegmentDataSlices = x.saveRoot()
+ m.SaveValue("root", root)
+}
+
+func (x *fileRefcountSet) afterLoad() {}
+func (x *fileRefcountSet) load(m state.Map) {
+ m.LoadValue("root", new(*fileRefcountSegmentDataSlices), func(y interface{}) { x.loadRoot(y.(*fileRefcountSegmentDataSlices)) })
+}
+
+func (x *fileRefcountnode) beforeSave() {}
+func (x *fileRefcountnode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("nrSegments", &x.nrSegments)
+ m.Save("parent", &x.parent)
+ m.Save("parentIndex", &x.parentIndex)
+ m.Save("hasChildren", &x.hasChildren)
+ m.Save("keys", &x.keys)
+ m.Save("values", &x.values)
+ m.Save("children", &x.children)
+}
+
+func (x *fileRefcountnode) afterLoad() {}
+func (x *fileRefcountnode) load(m state.Map) {
+ m.Load("nrSegments", &x.nrSegments)
+ m.Load("parent", &x.parent)
+ m.Load("parentIndex", &x.parentIndex)
+ m.Load("hasChildren", &x.hasChildren)
+ m.Load("keys", &x.keys)
+ m.Load("values", &x.values)
+ m.Load("children", &x.children)
+}
+
+func (x *fileRefcountSegmentDataSlices) beforeSave() {}
+func (x *fileRefcountSegmentDataSlices) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Start", &x.Start)
+ m.Save("End", &x.End)
+ m.Save("Values", &x.Values)
+}
+
+func (x *fileRefcountSegmentDataSlices) afterLoad() {}
+func (x *fileRefcountSegmentDataSlices) load(m state.Map) {
+ m.Load("Start", &x.Start)
+ m.Load("End", &x.End)
+ m.Load("Values", &x.Values)
+}
+
+func (x *ioList) beforeSave() {}
+func (x *ioList) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *ioList) afterLoad() {}
+func (x *ioList) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *ioEntry) beforeSave() {}
+func (x *ioEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *ioEntry) afterLoad() {}
+func (x *ioEntry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func (x *MemoryManager) save(m state.Map) {
+ x.beforeSave()
+ if !state.IsZeroValue(x.active) { m.Failf("active is %v, expected zero", x.active) }
+ if !state.IsZeroValue(x.captureInvalidations) { m.Failf("captureInvalidations is %v, expected zero", x.captureInvalidations) }
+ m.Save("p", &x.p)
+ m.Save("mfp", &x.mfp)
+ m.Save("layout", &x.layout)
+ m.Save("privateRefs", &x.privateRefs)
+ m.Save("users", &x.users)
+ m.Save("vmas", &x.vmas)
+ m.Save("brk", &x.brk)
+ m.Save("usageAS", &x.usageAS)
+ m.Save("lockedAS", &x.lockedAS)
+ m.Save("dataAS", &x.dataAS)
+ m.Save("defMLockMode", &x.defMLockMode)
+ m.Save("pmas", &x.pmas)
+ m.Save("curRSS", &x.curRSS)
+ m.Save("maxRSS", &x.maxRSS)
+ m.Save("argv", &x.argv)
+ m.Save("envv", &x.envv)
+ m.Save("auxv", &x.auxv)
+ m.Save("executable", &x.executable)
+ m.Save("dumpability", &x.dumpability)
+ m.Save("aioManager", &x.aioManager)
+}
+
+func (x *MemoryManager) load(m state.Map) {
+ m.Load("p", &x.p)
+ m.Load("mfp", &x.mfp)
+ m.Load("layout", &x.layout)
+ m.Load("privateRefs", &x.privateRefs)
+ m.Load("users", &x.users)
+ m.Load("vmas", &x.vmas)
+ m.Load("brk", &x.brk)
+ m.Load("usageAS", &x.usageAS)
+ m.Load("lockedAS", &x.lockedAS)
+ m.Load("dataAS", &x.dataAS)
+ m.Load("defMLockMode", &x.defMLockMode)
+ m.Load("pmas", &x.pmas)
+ m.Load("curRSS", &x.curRSS)
+ m.Load("maxRSS", &x.maxRSS)
+ m.Load("argv", &x.argv)
+ m.Load("envv", &x.envv)
+ m.Load("auxv", &x.auxv)
+ m.Load("executable", &x.executable)
+ m.Load("dumpability", &x.dumpability)
+ m.Load("aioManager", &x.aioManager)
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *vma) beforeSave() {}
+func (x *vma) save(m state.Map) {
+ x.beforeSave()
+ var realPerms int = x.saveRealPerms()
+ m.SaveValue("realPerms", realPerms)
+ m.Save("mappable", &x.mappable)
+ m.Save("off", &x.off)
+ m.Save("dontfork", &x.dontfork)
+ m.Save("mlockMode", &x.mlockMode)
+ m.Save("numaPolicy", &x.numaPolicy)
+ m.Save("numaNodemask", &x.numaNodemask)
+ m.Save("id", &x.id)
+ m.Save("hint", &x.hint)
+}
+
+func (x *vma) afterLoad() {}
+func (x *vma) load(m state.Map) {
+ m.Load("mappable", &x.mappable)
+ m.Load("off", &x.off)
+ m.Load("dontfork", &x.dontfork)
+ m.Load("mlockMode", &x.mlockMode)
+ m.Load("numaPolicy", &x.numaPolicy)
+ m.Load("numaNodemask", &x.numaNodemask)
+ m.Load("id", &x.id)
+ m.Load("hint", &x.hint)
+ m.LoadValue("realPerms", new(int), func(y interface{}) { x.loadRealPerms(y.(int)) })
+}
+
+func (x *pma) beforeSave() {}
+func (x *pma) save(m state.Map) {
+ x.beforeSave()
+ m.Save("off", &x.off)
+ m.Save("translatePerms", &x.translatePerms)
+ m.Save("effectivePerms", &x.effectivePerms)
+ m.Save("maxPerms", &x.maxPerms)
+ m.Save("needCOW", &x.needCOW)
+ m.Save("private", &x.private)
+}
+
+func (x *pma) afterLoad() {}
+func (x *pma) load(m state.Map) {
+ m.Load("off", &x.off)
+ m.Load("translatePerms", &x.translatePerms)
+ m.Load("effectivePerms", &x.effectivePerms)
+ m.Load("maxPerms", &x.maxPerms)
+ m.Load("needCOW", &x.needCOW)
+ m.Load("private", &x.private)
+}
+
+func (x *privateRefs) beforeSave() {}
+func (x *privateRefs) save(m state.Map) {
+ x.beforeSave()
+ m.Save("refs", &x.refs)
+}
+
+func (x *privateRefs) afterLoad() {}
+func (x *privateRefs) load(m state.Map) {
+ m.Load("refs", &x.refs)
+}
+
+func (x *pmaSet) beforeSave() {}
+func (x *pmaSet) save(m state.Map) {
+ x.beforeSave()
+ var root *pmaSegmentDataSlices = x.saveRoot()
+ m.SaveValue("root", root)
+}
+
+func (x *pmaSet) afterLoad() {}
+func (x *pmaSet) load(m state.Map) {
+ m.LoadValue("root", new(*pmaSegmentDataSlices), func(y interface{}) { x.loadRoot(y.(*pmaSegmentDataSlices)) })
+}
+
+func (x *pmanode) beforeSave() {}
+func (x *pmanode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("nrSegments", &x.nrSegments)
+ m.Save("parent", &x.parent)
+ m.Save("parentIndex", &x.parentIndex)
+ m.Save("hasChildren", &x.hasChildren)
+ m.Save("keys", &x.keys)
+ m.Save("values", &x.values)
+ m.Save("children", &x.children)
+}
+
+func (x *pmanode) afterLoad() {}
+func (x *pmanode) load(m state.Map) {
+ m.Load("nrSegments", &x.nrSegments)
+ m.Load("parent", &x.parent)
+ m.Load("parentIndex", &x.parentIndex)
+ m.Load("hasChildren", &x.hasChildren)
+ m.Load("keys", &x.keys)
+ m.Load("values", &x.values)
+ m.Load("children", &x.children)
+}
+
+func (x *pmaSegmentDataSlices) beforeSave() {}
+func (x *pmaSegmentDataSlices) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Start", &x.Start)
+ m.Save("End", &x.End)
+ m.Save("Values", &x.Values)
+}
+
+func (x *pmaSegmentDataSlices) afterLoad() {}
+func (x *pmaSegmentDataSlices) load(m state.Map) {
+ m.Load("Start", &x.Start)
+ m.Load("End", &x.End)
+ m.Load("Values", &x.Values)
+}
+
+func (x *SpecialMappable) beforeSave() {}
+func (x *SpecialMappable) save(m state.Map) {
+ x.beforeSave()
+ m.Save("AtomicRefCount", &x.AtomicRefCount)
+ m.Save("mfp", &x.mfp)
+ m.Save("fr", &x.fr)
+ m.Save("name", &x.name)
+}
+
+func (x *SpecialMappable) afterLoad() {}
+func (x *SpecialMappable) load(m state.Map) {
+ m.Load("AtomicRefCount", &x.AtomicRefCount)
+ m.Load("mfp", &x.mfp)
+ m.Load("fr", &x.fr)
+ m.Load("name", &x.name)
+}
+
+func (x *vmaSet) beforeSave() {}
+func (x *vmaSet) save(m state.Map) {
+ x.beforeSave()
+ var root *vmaSegmentDataSlices = x.saveRoot()
+ m.SaveValue("root", root)
+}
+
+func (x *vmaSet) afterLoad() {}
+func (x *vmaSet) load(m state.Map) {
+ m.LoadValue("root", new(*vmaSegmentDataSlices), func(y interface{}) { x.loadRoot(y.(*vmaSegmentDataSlices)) })
+}
+
+func (x *vmanode) beforeSave() {}
+func (x *vmanode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("nrSegments", &x.nrSegments)
+ m.Save("parent", &x.parent)
+ m.Save("parentIndex", &x.parentIndex)
+ m.Save("hasChildren", &x.hasChildren)
+ m.Save("keys", &x.keys)
+ m.Save("values", &x.values)
+ m.Save("children", &x.children)
+}
+
+func (x *vmanode) afterLoad() {}
+func (x *vmanode) load(m state.Map) {
+ m.Load("nrSegments", &x.nrSegments)
+ m.Load("parent", &x.parent)
+ m.Load("parentIndex", &x.parentIndex)
+ m.Load("hasChildren", &x.hasChildren)
+ m.Load("keys", &x.keys)
+ m.Load("values", &x.values)
+ m.Load("children", &x.children)
+}
+
+func (x *vmaSegmentDataSlices) beforeSave() {}
+func (x *vmaSegmentDataSlices) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Start", &x.Start)
+ m.Save("End", &x.End)
+ m.Save("Values", &x.Values)
+}
+
+func (x *vmaSegmentDataSlices) afterLoad() {}
+func (x *vmaSegmentDataSlices) load(m state.Map) {
+ m.Load("Start", &x.Start)
+ m.Load("End", &x.End)
+ m.Load("Values", &x.Values)
+}
+
+func init() {
+ state.Register("mm.aioManager", (*aioManager)(nil), state.Fns{Save: (*aioManager).save, Load: (*aioManager).load})
+ state.Register("mm.ioResult", (*ioResult)(nil), state.Fns{Save: (*ioResult).save, Load: (*ioResult).load})
+ state.Register("mm.AIOContext", (*AIOContext)(nil), state.Fns{Save: (*AIOContext).save, Load: (*AIOContext).load})
+ state.Register("mm.aioMappable", (*aioMappable)(nil), state.Fns{Save: (*aioMappable).save, Load: (*aioMappable).load})
+ state.Register("mm.fileRefcountSet", (*fileRefcountSet)(nil), state.Fns{Save: (*fileRefcountSet).save, Load: (*fileRefcountSet).load})
+ state.Register("mm.fileRefcountnode", (*fileRefcountnode)(nil), state.Fns{Save: (*fileRefcountnode).save, Load: (*fileRefcountnode).load})
+ state.Register("mm.fileRefcountSegmentDataSlices", (*fileRefcountSegmentDataSlices)(nil), state.Fns{Save: (*fileRefcountSegmentDataSlices).save, Load: (*fileRefcountSegmentDataSlices).load})
+ state.Register("mm.ioList", (*ioList)(nil), state.Fns{Save: (*ioList).save, Load: (*ioList).load})
+ state.Register("mm.ioEntry", (*ioEntry)(nil), state.Fns{Save: (*ioEntry).save, Load: (*ioEntry).load})
+ state.Register("mm.MemoryManager", (*MemoryManager)(nil), state.Fns{Save: (*MemoryManager).save, Load: (*MemoryManager).load})
+ state.Register("mm.vma", (*vma)(nil), state.Fns{Save: (*vma).save, Load: (*vma).load})
+ state.Register("mm.pma", (*pma)(nil), state.Fns{Save: (*pma).save, Load: (*pma).load})
+ state.Register("mm.privateRefs", (*privateRefs)(nil), state.Fns{Save: (*privateRefs).save, Load: (*privateRefs).load})
+ state.Register("mm.pmaSet", (*pmaSet)(nil), state.Fns{Save: (*pmaSet).save, Load: (*pmaSet).load})
+ state.Register("mm.pmanode", (*pmanode)(nil), state.Fns{Save: (*pmanode).save, Load: (*pmanode).load})
+ state.Register("mm.pmaSegmentDataSlices", (*pmaSegmentDataSlices)(nil), state.Fns{Save: (*pmaSegmentDataSlices).save, Load: (*pmaSegmentDataSlices).load})
+ state.Register("mm.SpecialMappable", (*SpecialMappable)(nil), state.Fns{Save: (*SpecialMappable).save, Load: (*SpecialMappable).load})
+ state.Register("mm.vmaSet", (*vmaSet)(nil), state.Fns{Save: (*vmaSet).save, Load: (*vmaSet).load})
+ state.Register("mm.vmanode", (*vmanode)(nil), state.Fns{Save: (*vmanode).save, Load: (*vmanode).load})
+ state.Register("mm.vmaSegmentDataSlices", (*vmaSegmentDataSlices)(nil), state.Fns{Save: (*vmaSegmentDataSlices).save, Load: (*vmaSegmentDataSlices).load})
+}
diff --git a/pkg/sentry/mm/mm_test.go b/pkg/sentry/mm/mm_test.go
deleted file mode 100644
index 4d2bfaaed..000000000
--- a/pkg/sentry/mm/mm_test.go
+++ /dev/null
@@ -1,230 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package mm
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/arch"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/limits"
- "gvisor.dev/gvisor/pkg/sentry/memmap"
- "gvisor.dev/gvisor/pkg/sentry/pgalloc"
- "gvisor.dev/gvisor/pkg/sentry/platform"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-func testMemoryManager(ctx context.Context) *MemoryManager {
- p := platform.FromContext(ctx)
- mfp := pgalloc.MemoryFileProviderFromContext(ctx)
- mm := NewMemoryManager(p, mfp)
- mm.layout = arch.MmapLayout{
- MinAddr: p.MinUserAddress(),
- MaxAddr: p.MaxUserAddress(),
- BottomUpBase: p.MinUserAddress(),
- TopDownBase: p.MaxUserAddress(),
- }
- return mm
-}
-
-func (mm *MemoryManager) realUsageAS() uint64 {
- return uint64(mm.vmas.Span())
-}
-
-func TestUsageASUpdates(t *testing.T) {
- ctx := contexttest.Context(t)
- mm := testMemoryManager(ctx)
- defer mm.DecUsers(ctx)
-
- addr, err := mm.MMap(ctx, memmap.MMapOpts{
- Length: 2 * usermem.PageSize,
- })
- if err != nil {
- t.Fatalf("MMap got err %v want nil", err)
- }
- realUsage := mm.realUsageAS()
- if mm.usageAS != realUsage {
- t.Fatalf("usageAS believes %v bytes are mapped; %v bytes are actually mapped", mm.usageAS, realUsage)
- }
-
- mm.MUnmap(ctx, addr, usermem.PageSize)
- realUsage = mm.realUsageAS()
- if mm.usageAS != realUsage {
- t.Fatalf("usageAS believes %v bytes are mapped; %v bytes are actually mapped", mm.usageAS, realUsage)
- }
-}
-
-func (mm *MemoryManager) realDataAS() uint64 {
- var sz uint64
- for seg := mm.vmas.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
- vma := seg.Value()
- if vma.isPrivateDataLocked() {
- sz += uint64(seg.Range().Length())
- }
- }
- return sz
-}
-
-func TestDataASUpdates(t *testing.T) {
- ctx := contexttest.Context(t)
- mm := testMemoryManager(ctx)
- defer mm.DecUsers(ctx)
-
- addr, err := mm.MMap(ctx, memmap.MMapOpts{
- Length: 3 * usermem.PageSize,
- Private: true,
- Perms: usermem.Write,
- MaxPerms: usermem.AnyAccess,
- })
- if err != nil {
- t.Fatalf("MMap got err %v want nil", err)
- }
- if mm.dataAS == 0 {
- t.Fatalf("dataAS is 0, wanted not 0")
- }
- realDataAS := mm.realDataAS()
- if mm.dataAS != realDataAS {
- t.Fatalf("dataAS believes %v bytes are mapped; %v bytes are actually mapped", mm.dataAS, realDataAS)
- }
-
- mm.MUnmap(ctx, addr, usermem.PageSize)
- realDataAS = mm.realDataAS()
- if mm.dataAS != realDataAS {
- t.Fatalf("dataAS believes %v bytes are mapped; %v bytes are actually mapped", mm.dataAS, realDataAS)
- }
-
- mm.MProtect(addr+usermem.PageSize, usermem.PageSize, usermem.Read, false)
- realDataAS = mm.realDataAS()
- if mm.dataAS != realDataAS {
- t.Fatalf("dataAS believes %v bytes are mapped; %v bytes are actually mapped", mm.dataAS, realDataAS)
- }
-
- mm.MRemap(ctx, addr+2*usermem.PageSize, usermem.PageSize, 2*usermem.PageSize, MRemapOpts{
- Move: MRemapMayMove,
- })
- realDataAS = mm.realDataAS()
- if mm.dataAS != realDataAS {
- t.Fatalf("dataAS believes %v bytes are mapped; %v bytes are actually mapped", mm.dataAS, realDataAS)
- }
-}
-
-func TestBrkDataLimitUpdates(t *testing.T) {
- limitSet := limits.NewLimitSet()
- limitSet.Set(limits.Data, limits.Limit{}, true /* privileged */) // zero RLIMIT_DATA
-
- ctx := contexttest.WithLimitSet(contexttest.Context(t), limitSet)
- mm := testMemoryManager(ctx)
- defer mm.DecUsers(ctx)
-
- // Try to extend the brk by one page and expect doing so to fail.
- oldBrk, _ := mm.Brk(ctx, 0)
- if newBrk, _ := mm.Brk(ctx, oldBrk+usermem.PageSize); newBrk != oldBrk {
- t.Errorf("brk() increased data segment above RLIMIT_DATA (old brk = %#x, new brk = %#x", oldBrk, newBrk)
- }
-}
-
-// TestIOAfterUnmap ensures that IO fails after unmap.
-func TestIOAfterUnmap(t *testing.T) {
- ctx := contexttest.Context(t)
- mm := testMemoryManager(ctx)
- defer mm.DecUsers(ctx)
-
- addr, err := mm.MMap(ctx, memmap.MMapOpts{
- Length: usermem.PageSize,
- Private: true,
- Perms: usermem.Read,
- MaxPerms: usermem.AnyAccess,
- })
- if err != nil {
- t.Fatalf("MMap got err %v want nil", err)
- }
-
- // IO works before munmap.
- b := make([]byte, 1)
- n, err := mm.CopyIn(ctx, addr, b, usermem.IOOpts{})
- if err != nil {
- t.Errorf("CopyIn got err %v want nil", err)
- }
- if n != 1 {
- t.Errorf("CopyIn got %d want 1", n)
- }
-
- err = mm.MUnmap(ctx, addr, usermem.PageSize)
- if err != nil {
- t.Fatalf("MUnmap got err %v want nil", err)
- }
-
- n, err = mm.CopyIn(ctx, addr, b, usermem.IOOpts{})
- if err != syserror.EFAULT {
- t.Errorf("CopyIn got err %v want EFAULT", err)
- }
- if n != 0 {
- t.Errorf("CopyIn got %d want 0", n)
- }
-}
-
-// TestIOAfterMProtect tests IO interaction with mprotect permissions.
-func TestIOAfterMProtect(t *testing.T) {
- ctx := contexttest.Context(t)
- mm := testMemoryManager(ctx)
- defer mm.DecUsers(ctx)
-
- addr, err := mm.MMap(ctx, memmap.MMapOpts{
- Length: usermem.PageSize,
- Private: true,
- Perms: usermem.ReadWrite,
- MaxPerms: usermem.AnyAccess,
- })
- if err != nil {
- t.Fatalf("MMap got err %v want nil", err)
- }
-
- // Writing works before mprotect.
- b := make([]byte, 1)
- n, err := mm.CopyOut(ctx, addr, b, usermem.IOOpts{})
- if err != nil {
- t.Errorf("CopyOut got err %v want nil", err)
- }
- if n != 1 {
- t.Errorf("CopyOut got %d want 1", n)
- }
-
- err = mm.MProtect(addr, usermem.PageSize, usermem.Read, false)
- if err != nil {
- t.Errorf("MProtect got err %v want nil", err)
- }
-
- // Without IgnorePermissions, CopyOut should no longer succeed.
- n, err = mm.CopyOut(ctx, addr, b, usermem.IOOpts{})
- if err != syserror.EFAULT {
- t.Errorf("CopyOut got err %v want EFAULT", err)
- }
- if n != 0 {
- t.Errorf("CopyOut got %d want 0", n)
- }
-
- // With IgnorePermissions, CopyOut should succeed despite mprotect.
- n, err = mm.CopyOut(ctx, addr, b, usermem.IOOpts{
- IgnorePermissions: true,
- })
- if err != nil {
- t.Errorf("CopyOut got err %v want nil", err)
- }
- if n != 1 {
- t.Errorf("CopyOut got %d want 1", n)
- }
-}
diff --git a/pkg/sentry/mm/pma_set.go b/pkg/sentry/mm/pma_set.go
new file mode 100755
index 000000000..52e66468c
--- /dev/null
+++ b/pkg/sentry/mm/pma_set.go
@@ -0,0 +1,1274 @@
+package mm
+
+import (
+ __generics_imported0 "gvisor.dev/gvisor/pkg/sentry/usermem"
+)
+
+import (
+ "bytes"
+ "fmt"
+)
+
+const (
+ // minDegree is the minimum degree of an internal node in a Set B-tree.
+ //
+ // - Any non-root node has at least minDegree-1 segments.
+ //
+ // - Any non-root internal (non-leaf) node has at least minDegree children.
+ //
+ // - The root node may have fewer than minDegree-1 segments, but it may
+ // only have 0 segments if the tree is empty.
+ //
+ // Our implementation requires minDegree >= 3. Higher values of minDegree
+ // usually improve performance, but increase memory usage for small sets.
+ pmaminDegree = 8
+
+ pmamaxDegree = 2 * pmaminDegree
+)
+
+// A Set is a mapping of segments with non-overlapping Range keys. The zero
+// value for a Set is an empty set. Set values are not safely movable nor
+// copyable. Set is thread-compatible.
+//
+// +stateify savable
+type pmaSet struct {
+ root pmanode `state:".(*pmaSegmentDataSlices)"`
+}
+
+// IsEmpty returns true if the set contains no segments.
+func (s *pmaSet) IsEmpty() bool {
+ return s.root.nrSegments == 0
+}
+
+// IsEmptyRange returns true iff no segments in the set overlap the given
+// range. This is semantically equivalent to s.SpanRange(r) == 0, but may be
+// more efficient.
+func (s *pmaSet) IsEmptyRange(r __generics_imported0.AddrRange) bool {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return true
+ }
+ _, gap := s.Find(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ return r.End <= gap.End()
+}
+
+// Span returns the total size of all segments in the set.
+func (s *pmaSet) Span() __generics_imported0.Addr {
+ var sz __generics_imported0.Addr
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sz += seg.Range().Length()
+ }
+ return sz
+}
+
+// SpanRange returns the total size of the intersection of segments in the set
+// with the given range.
+func (s *pmaSet) SpanRange(r __generics_imported0.AddrRange) __generics_imported0.Addr {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return 0
+ }
+ var sz __generics_imported0.Addr
+ for seg := s.LowerBoundSegment(r.Start); seg.Ok() && seg.Start() < r.End; seg = seg.NextSegment() {
+ sz += seg.Range().Intersect(r).Length()
+ }
+ return sz
+}
+
+// FirstSegment returns the first segment in the set. If the set is empty,
+// FirstSegment returns a terminal iterator.
+func (s *pmaSet) FirstSegment() pmaIterator {
+ if s.root.nrSegments == 0 {
+ return pmaIterator{}
+ }
+ return s.root.firstSegment()
+}
+
+// LastSegment returns the last segment in the set. If the set is empty,
+// LastSegment returns a terminal iterator.
+func (s *pmaSet) LastSegment() pmaIterator {
+ if s.root.nrSegments == 0 {
+ return pmaIterator{}
+ }
+ return s.root.lastSegment()
+}
+
+// FirstGap returns the first gap in the set.
+func (s *pmaSet) FirstGap() pmaGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return pmaGapIterator{n, 0}
+}
+
+// LastGap returns the last gap in the set.
+func (s *pmaSet) LastGap() pmaGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return pmaGapIterator{n, n.nrSegments}
+}
+
+// Find returns the segment or gap whose range contains the given key. If a
+// segment is found, the returned Iterator is non-terminal and the
+// returned GapIterator is terminal. Otherwise, the returned Iterator is
+// terminal and the returned GapIterator is non-terminal.
+func (s *pmaSet) Find(key __generics_imported0.Addr) (pmaIterator, pmaGapIterator) {
+ n := &s.root
+ for {
+
+ lower := 0
+ upper := n.nrSegments
+ for lower < upper {
+ i := lower + (upper-lower)/2
+ if r := n.keys[i]; key < r.End {
+ if key >= r.Start {
+ return pmaIterator{n, i}, pmaGapIterator{}
+ }
+ upper = i
+ } else {
+ lower = i + 1
+ }
+ }
+ i := lower
+ if !n.hasChildren {
+ return pmaIterator{}, pmaGapIterator{n, i}
+ }
+ n = n.children[i]
+ }
+}
+
+// FindSegment returns the segment whose range contains the given key. If no
+// such segment exists, FindSegment returns a terminal iterator.
+func (s *pmaSet) FindSegment(key __generics_imported0.Addr) pmaIterator {
+ seg, _ := s.Find(key)
+ return seg
+}
+
+// LowerBoundSegment returns the segment with the lowest range that contains a
+// key greater than or equal to min. If no such segment exists,
+// LowerBoundSegment returns a terminal iterator.
+func (s *pmaSet) LowerBoundSegment(min __generics_imported0.Addr) pmaIterator {
+ seg, gap := s.Find(min)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.NextSegment()
+}
+
+// UpperBoundSegment returns the segment with the highest range that contains a
+// key less than or equal to max. If no such segment exists, UpperBoundSegment
+// returns a terminal iterator.
+func (s *pmaSet) UpperBoundSegment(max __generics_imported0.Addr) pmaIterator {
+ seg, gap := s.Find(max)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.PrevSegment()
+}
+
+// FindGap returns the gap containing the given key. If no such gap exists
+// (i.e. the set contains a segment containing that key), FindGap returns a
+// terminal iterator.
+func (s *pmaSet) FindGap(key __generics_imported0.Addr) pmaGapIterator {
+ _, gap := s.Find(key)
+ return gap
+}
+
+// LowerBoundGap returns the gap with the lowest range that is greater than or
+// equal to min.
+func (s *pmaSet) LowerBoundGap(min __generics_imported0.Addr) pmaGapIterator {
+ seg, gap := s.Find(min)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.NextGap()
+}
+
+// UpperBoundGap returns the gap with the highest range that is less than or
+// equal to max.
+func (s *pmaSet) UpperBoundGap(max __generics_imported0.Addr) pmaGapIterator {
+ seg, gap := s.Find(max)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.PrevGap()
+}
+
+// Add inserts the given segment into the set and returns true. If the new
+// segment can be merged with adjacent segments, Add will do so. If the new
+// segment would overlap an existing segment, Add returns false. If Add
+// succeeds, all existing iterators are invalidated.
+func (s *pmaSet) Add(r __generics_imported0.AddrRange, val pma) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.Insert(gap, r, val)
+ return true
+}
+
+// AddWithoutMerging inserts the given segment into the set and returns true.
+// If it would overlap an existing segment, AddWithoutMerging does nothing and
+// returns false. If AddWithoutMerging succeeds, all existing iterators are
+// invalidated.
+func (s *pmaSet) AddWithoutMerging(r __generics_imported0.AddrRange, val pma) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.InsertWithoutMergingUnchecked(gap, r, val)
+ return true
+}
+
+// Insert inserts the given segment into the given gap. If the new segment can
+// be merged with adjacent segments, Insert will do so. Insert returns an
+// iterator to the segment containing the inserted value (which may have been
+// merged with other values). All existing iterators (including gap, but not
+// including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid, Insert panics.
+//
+// Insert is semantically equivalent to a InsertWithoutMerging followed by a
+// Merge, but may be more efficient. Note that there is no unchecked variant of
+// Insert since Insert must retrieve and inspect gap's predecessor and
+// successor segments regardless.
+func (s *pmaSet) Insert(gap pmaGapIterator, r __generics_imported0.AddrRange, val pma) pmaIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ prev, next := gap.PrevSegment(), gap.NextSegment()
+ if prev.Ok() && prev.End() > r.Start {
+ panic(fmt.Sprintf("new segment %v overlaps predecessor %v", r, prev.Range()))
+ }
+ if next.Ok() && next.Start() < r.End {
+ panic(fmt.Sprintf("new segment %v overlaps successor %v", r, next.Range()))
+ }
+ if prev.Ok() && prev.End() == r.Start {
+ if mval, ok := (pmaSetFunctions{}).Merge(prev.Range(), prev.Value(), r, val); ok {
+ prev.SetEndUnchecked(r.End)
+ prev.SetValue(mval)
+ if next.Ok() && next.Start() == r.End {
+ val = mval
+ if mval, ok := (pmaSetFunctions{}).Merge(prev.Range(), val, next.Range(), next.Value()); ok {
+ prev.SetEndUnchecked(next.End())
+ prev.SetValue(mval)
+ return s.Remove(next).PrevSegment()
+ }
+ }
+ return prev
+ }
+ }
+ if next.Ok() && next.Start() == r.End {
+ if mval, ok := (pmaSetFunctions{}).Merge(r, val, next.Range(), next.Value()); ok {
+ next.SetStartUnchecked(r.Start)
+ next.SetValue(mval)
+ return next
+ }
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMerging inserts the given segment into the given gap and
+// returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid,
+// InsertWithoutMerging panics.
+func (s *pmaSet) InsertWithoutMerging(gap pmaGapIterator, r __generics_imported0.AddrRange, val pma) pmaIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if gr := gap.Range(); !gr.IsSupersetOf(r) {
+ panic(fmt.Sprintf("cannot insert segment range %v into gap range %v", r, gr))
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMergingUnchecked inserts the given segment into the given gap
+// and returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// Preconditions: r.Start >= gap.Start(); r.End <= gap.End().
+func (s *pmaSet) InsertWithoutMergingUnchecked(gap pmaGapIterator, r __generics_imported0.AddrRange, val pma) pmaIterator {
+ gap = gap.node.rebalanceBeforeInsert(gap)
+ copy(gap.node.keys[gap.index+1:], gap.node.keys[gap.index:gap.node.nrSegments])
+ copy(gap.node.values[gap.index+1:], gap.node.values[gap.index:gap.node.nrSegments])
+ gap.node.keys[gap.index] = r
+ gap.node.values[gap.index] = val
+ gap.node.nrSegments++
+ return pmaIterator{gap.node, gap.index}
+}
+
+// Remove removes the given segment and returns an iterator to the vacated gap.
+// All existing iterators (including seg, but not including the returned
+// iterator) are invalidated.
+func (s *pmaSet) Remove(seg pmaIterator) pmaGapIterator {
+
+ if seg.node.hasChildren {
+
+ victim := seg.PrevSegment()
+
+ seg.SetRangeUnchecked(victim.Range())
+ seg.SetValue(victim.Value())
+ return s.Remove(victim).NextGap()
+ }
+ copy(seg.node.keys[seg.index:], seg.node.keys[seg.index+1:seg.node.nrSegments])
+ copy(seg.node.values[seg.index:], seg.node.values[seg.index+1:seg.node.nrSegments])
+ pmaSetFunctions{}.ClearValue(&seg.node.values[seg.node.nrSegments-1])
+ seg.node.nrSegments--
+ return seg.node.rebalanceAfterRemove(pmaGapIterator{seg.node, seg.index})
+}
+
+// RemoveAll removes all segments from the set. All existing iterators are
+// invalidated.
+func (s *pmaSet) RemoveAll() {
+ s.root = pmanode{}
+}
+
+// RemoveRange removes all segments in the given range. An iterator to the
+// newly formed gap is returned, and all existing iterators are invalidated.
+func (s *pmaSet) RemoveRange(r __generics_imported0.AddrRange) pmaGapIterator {
+ seg, gap := s.Find(r.Start)
+ if seg.Ok() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ for seg = gap.NextSegment(); seg.Ok() && seg.Start() < r.End; seg = gap.NextSegment() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ return gap
+}
+
+// Merge attempts to merge two neighboring segments. If successful, Merge
+// returns an iterator to the merged segment, and all existing iterators are
+// invalidated. Otherwise, Merge returns a terminal iterator.
+//
+// If first is not the predecessor of second, Merge panics.
+func (s *pmaSet) Merge(first, second pmaIterator) pmaIterator {
+ if first.NextSegment() != second {
+ panic(fmt.Sprintf("attempt to merge non-neighboring segments %v, %v", first.Range(), second.Range()))
+ }
+ return s.MergeUnchecked(first, second)
+}
+
+// MergeUnchecked attempts to merge two neighboring segments. If successful,
+// MergeUnchecked returns an iterator to the merged segment, and all existing
+// iterators are invalidated. Otherwise, MergeUnchecked returns a terminal
+// iterator.
+//
+// Precondition: first is the predecessor of second: first.NextSegment() ==
+// second, first == second.PrevSegment().
+func (s *pmaSet) MergeUnchecked(first, second pmaIterator) pmaIterator {
+ if first.End() == second.Start() {
+ if mval, ok := (pmaSetFunctions{}).Merge(first.Range(), first.Value(), second.Range(), second.Value()); ok {
+
+ first.SetEndUnchecked(second.End())
+ first.SetValue(mval)
+ return s.Remove(second).PrevSegment()
+ }
+ }
+ return pmaIterator{}
+}
+
+// MergeAll attempts to merge all adjacent segments in the set. All existing
+// iterators are invalidated.
+func (s *pmaSet) MergeAll() {
+ seg := s.FirstSegment()
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeRange attempts to merge all adjacent segments that contain a key in the
+// specific range. All existing iterators are invalidated.
+func (s *pmaSet) MergeRange(r __generics_imported0.AddrRange) {
+ seg := s.LowerBoundSegment(r.Start)
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() && next.Range().Start < r.End {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeAdjacent attempts to merge the segment containing r.Start with its
+// predecessor, and the segment containing r.End-1 with its successor.
+func (s *pmaSet) MergeAdjacent(r __generics_imported0.AddrRange) {
+ first := s.FindSegment(r.Start)
+ if first.Ok() {
+ if prev := first.PrevSegment(); prev.Ok() {
+ s.Merge(prev, first)
+ }
+ }
+ last := s.FindSegment(r.End - 1)
+ if last.Ok() {
+ if next := last.NextSegment(); next.Ok() {
+ s.Merge(last, next)
+ }
+ }
+}
+
+// Split splits the given segment at the given key and returns iterators to the
+// two resulting segments. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+//
+// If the segment cannot be split at split (because split is at the start or
+// end of the segment's range, so splitting would produce a segment with zero
+// length, or because split falls outside the segment's range altogether),
+// Split panics.
+func (s *pmaSet) Split(seg pmaIterator, split __generics_imported0.Addr) (pmaIterator, pmaIterator) {
+ if !seg.Range().CanSplitAt(split) {
+ panic(fmt.Sprintf("can't split %v at %v", seg.Range(), split))
+ }
+ return s.SplitUnchecked(seg, split)
+}
+
+// SplitUnchecked splits the given segment at the given key and returns
+// iterators to the two resulting segments. All existing iterators (including
+// seg, but not including the returned iterators) are invalidated.
+//
+// Preconditions: seg.Start() < key < seg.End().
+func (s *pmaSet) SplitUnchecked(seg pmaIterator, split __generics_imported0.Addr) (pmaIterator, pmaIterator) {
+ val1, val2 := (pmaSetFunctions{}).Split(seg.Range(), seg.Value(), split)
+ end2 := seg.End()
+ seg.SetEndUnchecked(split)
+ seg.SetValue(val1)
+ seg2 := s.InsertWithoutMergingUnchecked(seg.NextGap(), __generics_imported0.AddrRange{split, end2}, val2)
+
+ return seg2.PrevSegment(), seg2
+}
+
+// SplitAt splits the segment straddling split, if one exists. SplitAt returns
+// true if a segment was split and false otherwise. If SplitAt splits a
+// segment, all existing iterators are invalidated.
+func (s *pmaSet) SplitAt(split __generics_imported0.Addr) bool {
+ if seg := s.FindSegment(split); seg.Ok() && seg.Range().CanSplitAt(split) {
+ s.SplitUnchecked(seg, split)
+ return true
+ }
+ return false
+}
+
+// Isolate ensures that the given segment's range does not escape r by
+// splitting at r.Start and r.End if necessary, and returns an updated iterator
+// to the bounded segment. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+func (s *pmaSet) Isolate(seg pmaIterator, r __generics_imported0.AddrRange) pmaIterator {
+ if seg.Range().CanSplitAt(r.Start) {
+ _, seg = s.SplitUnchecked(seg, r.Start)
+ }
+ if seg.Range().CanSplitAt(r.End) {
+ seg, _ = s.SplitUnchecked(seg, r.End)
+ }
+ return seg
+}
+
+// ApplyContiguous applies a function to a contiguous range of segments,
+// splitting if necessary. The function is applied until the first gap is
+// encountered, at which point the gap is returned. If the function is applied
+// across the entire range, a terminal gap is returned. All existing iterators
+// are invalidated.
+//
+// N.B. The Iterator must not be invalidated by the function.
+func (s *pmaSet) ApplyContiguous(r __generics_imported0.AddrRange, fn func(seg pmaIterator)) pmaGapIterator {
+ seg, gap := s.Find(r.Start)
+ if !seg.Ok() {
+ return gap
+ }
+ for {
+ seg = s.Isolate(seg, r)
+ fn(seg)
+ if seg.End() >= r.End {
+ return pmaGapIterator{}
+ }
+ gap = seg.NextGap()
+ if !gap.IsEmpty() {
+ return gap
+ }
+ seg = gap.NextSegment()
+ if !seg.Ok() {
+
+ return pmaGapIterator{}
+ }
+ }
+}
+
+// +stateify savable
+type pmanode struct {
+ // An internal binary tree node looks like:
+ //
+ // K
+ // / \
+ // Cl Cr
+ //
+ // where all keys in the subtree rooted by Cl (the left subtree) are less
+ // than K (the key of the parent node), and all keys in the subtree rooted
+ // by Cr (the right subtree) are greater than K.
+ //
+ // An internal B-tree node's indexes work out to look like:
+ //
+ // K0 K1 K2 ... Kn-1
+ // / \/ \/ \ ... / \
+ // C0 C1 C2 C3 ... Cn-1 Cn
+ //
+ // where n is nrSegments.
+ nrSegments int
+
+ // parent is a pointer to this node's parent. If this node is root, parent
+ // is nil.
+ parent *pmanode
+
+ // parentIndex is the index of this node in parent.children.
+ parentIndex int
+
+ // Flag for internal nodes that is technically redundant with "children[0]
+ // != nil", but is stored in the first cache line. "hasChildren" rather
+ // than "isLeaf" because false must be the correct value for an empty root.
+ hasChildren bool
+
+ // Nodes store keys and values in separate arrays to maximize locality in
+ // the common case (scanning keys for lookup).
+ keys [pmamaxDegree - 1]__generics_imported0.AddrRange
+ values [pmamaxDegree - 1]pma
+ children [pmamaxDegree]*pmanode
+}
+
+// firstSegment returns the first segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *pmanode) firstSegment() pmaIterator {
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return pmaIterator{n, 0}
+}
+
+// lastSegment returns the last segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *pmanode) lastSegment() pmaIterator {
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return pmaIterator{n, n.nrSegments - 1}
+}
+
+func (n *pmanode) prevSibling() *pmanode {
+ if n.parent == nil || n.parentIndex == 0 {
+ return nil
+ }
+ return n.parent.children[n.parentIndex-1]
+}
+
+func (n *pmanode) nextSibling() *pmanode {
+ if n.parent == nil || n.parentIndex == n.parent.nrSegments {
+ return nil
+ }
+ return n.parent.children[n.parentIndex+1]
+}
+
+// rebalanceBeforeInsert splits n and its ancestors if they are full, as
+// required for insertion, and returns an updated iterator to the position
+// represented by gap.
+func (n *pmanode) rebalanceBeforeInsert(gap pmaGapIterator) pmaGapIterator {
+ if n.parent != nil {
+ gap = n.parent.rebalanceBeforeInsert(gap)
+ }
+ if n.nrSegments < pmamaxDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ left := &pmanode{
+ nrSegments: pmaminDegree - 1,
+ parent: n,
+ parentIndex: 0,
+ hasChildren: n.hasChildren,
+ }
+ right := &pmanode{
+ nrSegments: pmaminDegree - 1,
+ parent: n,
+ parentIndex: 1,
+ hasChildren: n.hasChildren,
+ }
+ copy(left.keys[:pmaminDegree-1], n.keys[:pmaminDegree-1])
+ copy(left.values[:pmaminDegree-1], n.values[:pmaminDegree-1])
+ copy(right.keys[:pmaminDegree-1], n.keys[pmaminDegree:])
+ copy(right.values[:pmaminDegree-1], n.values[pmaminDegree:])
+ n.keys[0], n.values[0] = n.keys[pmaminDegree-1], n.values[pmaminDegree-1]
+ pmazeroValueSlice(n.values[1:])
+ if n.hasChildren {
+ copy(left.children[:pmaminDegree], n.children[:pmaminDegree])
+ copy(right.children[:pmaminDegree], n.children[pmaminDegree:])
+ pmazeroNodeSlice(n.children[2:])
+ for i := 0; i < pmaminDegree; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ right.children[i].parent = right
+ right.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = 1
+ n.hasChildren = true
+ n.children[0] = left
+ n.children[1] = right
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < pmaminDegree {
+ return pmaGapIterator{left, gap.index}
+ }
+ return pmaGapIterator{right, gap.index - pmaminDegree}
+ }
+
+ copy(n.parent.keys[n.parentIndex+1:], n.parent.keys[n.parentIndex:n.parent.nrSegments])
+ copy(n.parent.values[n.parentIndex+1:], n.parent.values[n.parentIndex:n.parent.nrSegments])
+ n.parent.keys[n.parentIndex], n.parent.values[n.parentIndex] = n.keys[pmaminDegree-1], n.values[pmaminDegree-1]
+ copy(n.parent.children[n.parentIndex+2:], n.parent.children[n.parentIndex+1:n.parent.nrSegments+1])
+ for i := n.parentIndex + 2; i < n.parent.nrSegments+2; i++ {
+ n.parent.children[i].parentIndex = i
+ }
+ sibling := &pmanode{
+ nrSegments: pmaminDegree - 1,
+ parent: n.parent,
+ parentIndex: n.parentIndex + 1,
+ hasChildren: n.hasChildren,
+ }
+ n.parent.children[n.parentIndex+1] = sibling
+ n.parent.nrSegments++
+ copy(sibling.keys[:pmaminDegree-1], n.keys[pmaminDegree:])
+ copy(sibling.values[:pmaminDegree-1], n.values[pmaminDegree:])
+ pmazeroValueSlice(n.values[pmaminDegree-1:])
+ if n.hasChildren {
+ copy(sibling.children[:pmaminDegree], n.children[pmaminDegree:])
+ pmazeroNodeSlice(n.children[pmaminDegree:])
+ for i := 0; i < pmaminDegree; i++ {
+ sibling.children[i].parent = sibling
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = pmaminDegree - 1
+
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < pmaminDegree {
+ return gap
+ }
+ return pmaGapIterator{sibling, gap.index - pmaminDegree}
+}
+
+// rebalanceAfterRemove "unsplits" n and its ancestors if they are deficient
+// (contain fewer segments than required by B-tree invariants), as required for
+// removal, and returns an updated iterator to the position represented by gap.
+//
+// Precondition: n is the only node in the tree that may currently violate a
+// B-tree invariant.
+func (n *pmanode) rebalanceAfterRemove(gap pmaGapIterator) pmaGapIterator {
+ for {
+ if n.nrSegments >= pmaminDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ return gap
+ }
+
+ if sibling := n.prevSibling(); sibling != nil && sibling.nrSegments >= pmaminDegree {
+ copy(n.keys[1:], n.keys[:n.nrSegments])
+ copy(n.values[1:], n.values[:n.nrSegments])
+ n.keys[0] = n.parent.keys[n.parentIndex-1]
+ n.values[0] = n.parent.values[n.parentIndex-1]
+ n.parent.keys[n.parentIndex-1] = sibling.keys[sibling.nrSegments-1]
+ n.parent.values[n.parentIndex-1] = sibling.values[sibling.nrSegments-1]
+ pmaSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ copy(n.children[1:], n.children[:n.nrSegments+1])
+ n.children[0] = sibling.children[sibling.nrSegments]
+ sibling.children[sibling.nrSegments] = nil
+ n.children[0].parent = n
+ n.children[0].parentIndex = 0
+ for i := 1; i < n.nrSegments+2; i++ {
+ n.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling && gap.index == sibling.nrSegments {
+ return pmaGapIterator{n, 0}
+ }
+ if gap.node == n {
+ return pmaGapIterator{n, gap.index + 1}
+ }
+ return gap
+ }
+ if sibling := n.nextSibling(); sibling != nil && sibling.nrSegments >= pmaminDegree {
+ n.keys[n.nrSegments] = n.parent.keys[n.parentIndex]
+ n.values[n.nrSegments] = n.parent.values[n.parentIndex]
+ n.parent.keys[n.parentIndex] = sibling.keys[0]
+ n.parent.values[n.parentIndex] = sibling.values[0]
+ copy(sibling.keys[:sibling.nrSegments-1], sibling.keys[1:])
+ copy(sibling.values[:sibling.nrSegments-1], sibling.values[1:])
+ pmaSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ n.children[n.nrSegments+1] = sibling.children[0]
+ copy(sibling.children[:sibling.nrSegments], sibling.children[1:])
+ sibling.children[sibling.nrSegments] = nil
+ n.children[n.nrSegments+1].parent = n
+ n.children[n.nrSegments+1].parentIndex = n.nrSegments + 1
+ for i := 0; i < sibling.nrSegments; i++ {
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling {
+ if gap.index == 0 {
+ return pmaGapIterator{n, n.nrSegments}
+ }
+ return pmaGapIterator{sibling, gap.index - 1}
+ }
+ return gap
+ }
+
+ p := n.parent
+ if p.nrSegments == 1 {
+
+ left, right := p.children[0], p.children[1]
+ p.nrSegments = left.nrSegments + right.nrSegments + 1
+ p.hasChildren = left.hasChildren
+ p.keys[left.nrSegments] = p.keys[0]
+ p.values[left.nrSegments] = p.values[0]
+ copy(p.keys[:left.nrSegments], left.keys[:left.nrSegments])
+ copy(p.values[:left.nrSegments], left.values[:left.nrSegments])
+ copy(p.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(p.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(p.children[:left.nrSegments+1], left.children[:left.nrSegments+1])
+ copy(p.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := 0; i < p.nrSegments+1; i++ {
+ p.children[i].parent = p
+ p.children[i].parentIndex = i
+ }
+ } else {
+ p.children[0] = nil
+ p.children[1] = nil
+ }
+ if gap.node == left {
+ return pmaGapIterator{p, gap.index}
+ }
+ if gap.node == right {
+ return pmaGapIterator{p, gap.index + left.nrSegments + 1}
+ }
+ return gap
+ }
+ // Merge n and either sibling, along with the segment separating the
+ // two, into whichever of the two nodes comes first. This is the
+ // reverse of the non-root splitting case in
+ // node.rebalanceBeforeInsert.
+ var left, right *pmanode
+ if n.parentIndex > 0 {
+ left = n.prevSibling()
+ right = n
+ } else {
+ left = n
+ right = n.nextSibling()
+ }
+
+ if gap.node == right {
+ gap = pmaGapIterator{left, gap.index + left.nrSegments + 1}
+ }
+ left.keys[left.nrSegments] = p.keys[left.parentIndex]
+ left.values[left.nrSegments] = p.values[left.parentIndex]
+ copy(left.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(left.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(left.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := left.nrSegments + 1; i < left.nrSegments+right.nrSegments+2; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ }
+ }
+ left.nrSegments += right.nrSegments + 1
+ copy(p.keys[left.parentIndex:], p.keys[left.parentIndex+1:p.nrSegments])
+ copy(p.values[left.parentIndex:], p.values[left.parentIndex+1:p.nrSegments])
+ pmaSetFunctions{}.ClearValue(&p.values[p.nrSegments-1])
+ copy(p.children[left.parentIndex+1:], p.children[left.parentIndex+2:p.nrSegments+1])
+ for i := 0; i < p.nrSegments; i++ {
+ p.children[i].parentIndex = i
+ }
+ p.children[p.nrSegments] = nil
+ p.nrSegments--
+
+ n = p
+ }
+}
+
+// A Iterator is conceptually one of:
+//
+// - A pointer to a segment in a set; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Iterators are copyable values and are meaningfully equality-comparable. The
+// zero value of Iterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type pmaIterator struct {
+ // node is the node containing the iterated segment. If the iterator is
+ // terminal, node is nil.
+ node *pmanode
+
+ // index is the index of the segment in node.keys/values.
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (seg pmaIterator) Ok() bool {
+ return seg.node != nil
+}
+
+// Range returns the iterated segment's range key.
+func (seg pmaIterator) Range() __generics_imported0.AddrRange {
+ return seg.node.keys[seg.index]
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (seg pmaIterator) Start() __generics_imported0.Addr {
+ return seg.node.keys[seg.index].Start
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (seg pmaIterator) End() __generics_imported0.Addr {
+ return seg.node.keys[seg.index].End
+}
+
+// SetRangeUnchecked mutates the iterated segment's range key. This operation
+// does not invalidate any iterators.
+//
+// Preconditions:
+//
+// - r.Length() > 0.
+//
+// - The new range must not overlap an existing one: If seg.NextSegment().Ok(),
+// then r.end <= seg.NextSegment().Start(); if seg.PrevSegment().Ok(), then
+// r.start >= seg.PrevSegment().End().
+func (seg pmaIterator) SetRangeUnchecked(r __generics_imported0.AddrRange) {
+ seg.node.keys[seg.index] = r
+}
+
+// SetRange mutates the iterated segment's range key. If the new range would
+// cause the iterated segment to overlap another segment, or if the new range
+// is invalid, SetRange panics. This operation does not invalidate any
+// iterators.
+func (seg pmaIterator) SetRange(r __generics_imported0.AddrRange) {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && r.Start < prev.End() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, prev.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && r.End > next.Start() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, next.Range()))
+ }
+ seg.SetRangeUnchecked(r)
+}
+
+// SetStartUnchecked mutates the iterated segment's start. This operation does
+// not invalidate any iterators.
+//
+// Preconditions: The new start must be valid: start < seg.End(); if
+// seg.PrevSegment().Ok(), then start >= seg.PrevSegment().End().
+func (seg pmaIterator) SetStartUnchecked(start __generics_imported0.Addr) {
+ seg.node.keys[seg.index].Start = start
+}
+
+// SetStart mutates the iterated segment's start. If the new start value would
+// cause the iterated segment to overlap another segment, or would result in an
+// invalid range, SetStart panics. This operation does not invalidate any
+// iterators.
+func (seg pmaIterator) SetStart(start __generics_imported0.Addr) {
+ if start >= seg.End() {
+ panic(fmt.Sprintf("new start %v would invalidate segment range %v", start, seg.Range()))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && start < prev.End() {
+ panic(fmt.Sprintf("new start %v would cause segment range %v to overlap segment range %v", start, seg.Range(), prev.Range()))
+ }
+ seg.SetStartUnchecked(start)
+}
+
+// SetEndUnchecked mutates the iterated segment's end. This operation does not
+// invalidate any iterators.
+//
+// Preconditions: The new end must be valid: end > seg.Start(); if
+// seg.NextSegment().Ok(), then end <= seg.NextSegment().Start().
+func (seg pmaIterator) SetEndUnchecked(end __generics_imported0.Addr) {
+ seg.node.keys[seg.index].End = end
+}
+
+// SetEnd mutates the iterated segment's end. If the new end value would cause
+// the iterated segment to overlap another segment, or would result in an
+// invalid range, SetEnd panics. This operation does not invalidate any
+// iterators.
+func (seg pmaIterator) SetEnd(end __generics_imported0.Addr) {
+ if end <= seg.Start() {
+ panic(fmt.Sprintf("new end %v would invalidate segment range %v", end, seg.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && end > next.Start() {
+ panic(fmt.Sprintf("new end %v would cause segment range %v to overlap segment range %v", end, seg.Range(), next.Range()))
+ }
+ seg.SetEndUnchecked(end)
+}
+
+// Value returns a copy of the iterated segment's value.
+func (seg pmaIterator) Value() pma {
+ return seg.node.values[seg.index]
+}
+
+// ValuePtr returns a pointer to the iterated segment's value. The pointer is
+// invalidated if the iterator is invalidated. This operation does not
+// invalidate any iterators.
+func (seg pmaIterator) ValuePtr() *pma {
+ return &seg.node.values[seg.index]
+}
+
+// SetValue mutates the iterated segment's value. This operation does not
+// invalidate any iterators.
+func (seg pmaIterator) SetValue(val pma) {
+ seg.node.values[seg.index] = val
+}
+
+// PrevSegment returns the iterated segment's predecessor. If there is no
+// preceding segment, PrevSegment returns a terminal iterator.
+func (seg pmaIterator) PrevSegment() pmaIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index].lastSegment()
+ }
+ if seg.index > 0 {
+ return pmaIterator{seg.node, seg.index - 1}
+ }
+ if seg.node.parent == nil {
+ return pmaIterator{}
+ }
+ return pmasegmentBeforePosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// NextSegment returns the iterated segment's successor. If there is no
+// succeeding segment, NextSegment returns a terminal iterator.
+func (seg pmaIterator) NextSegment() pmaIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment()
+ }
+ if seg.index < seg.node.nrSegments-1 {
+ return pmaIterator{seg.node, seg.index + 1}
+ }
+ if seg.node.parent == nil {
+ return pmaIterator{}
+ }
+ return pmasegmentAfterPosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// PrevGap returns the gap immediately before the iterated segment.
+func (seg pmaIterator) PrevGap() pmaGapIterator {
+ if seg.node.hasChildren {
+
+ return seg.node.children[seg.index].lastSegment().NextGap()
+ }
+ return pmaGapIterator{seg.node, seg.index}
+}
+
+// NextGap returns the gap immediately after the iterated segment.
+func (seg pmaIterator) NextGap() pmaGapIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment().PrevGap()
+ }
+ return pmaGapIterator{seg.node, seg.index + 1}
+}
+
+// PrevNonEmpty returns the iterated segment's predecessor if it is adjacent,
+// or the gap before the iterated segment otherwise. If seg.Start() ==
+// Functions.MinKey(), PrevNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by PrevNonEmpty will be
+// non-terminal.
+func (seg pmaIterator) PrevNonEmpty() (pmaIterator, pmaGapIterator) {
+ gap := seg.PrevGap()
+ if gap.Range().Length() != 0 {
+ return pmaIterator{}, gap
+ }
+ return gap.PrevSegment(), pmaGapIterator{}
+}
+
+// NextNonEmpty returns the iterated segment's successor if it is adjacent, or
+// the gap after the iterated segment otherwise. If seg.End() ==
+// Functions.MaxKey(), NextNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by NextNonEmpty will be
+// non-terminal.
+func (seg pmaIterator) NextNonEmpty() (pmaIterator, pmaGapIterator) {
+ gap := seg.NextGap()
+ if gap.Range().Length() != 0 {
+ return pmaIterator{}, gap
+ }
+ return gap.NextSegment(), pmaGapIterator{}
+}
+
+// A GapIterator is conceptually one of:
+//
+// - A pointer to a position between two segments, before the first segment, or
+// after the last segment in a set, called a *gap*; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Note that the gap between two adjacent segments exists (iterators to it are
+// non-terminal), but has a length of zero. GapIterator.IsEmpty returns true
+// for such gaps. An empty set contains a single gap, spanning the entire range
+// of the set's keys.
+//
+// GapIterators are copyable values and are meaningfully equality-comparable.
+// The zero value of GapIterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type pmaGapIterator struct {
+ // The representation of a GapIterator is identical to that of an Iterator,
+ // except that index corresponds to positions between segments in the same
+ // way as for node.children (see comment for node.nrSegments).
+ node *pmanode
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (gap pmaGapIterator) Ok() bool {
+ return gap.node != nil
+}
+
+// Range returns the range spanned by the iterated gap.
+func (gap pmaGapIterator) Range() __generics_imported0.AddrRange {
+ return __generics_imported0.AddrRange{gap.Start(), gap.End()}
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (gap pmaGapIterator) Start() __generics_imported0.Addr {
+ if ps := gap.PrevSegment(); ps.Ok() {
+ return ps.End()
+ }
+ return pmaSetFunctions{}.MinKey()
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (gap pmaGapIterator) End() __generics_imported0.Addr {
+ if ns := gap.NextSegment(); ns.Ok() {
+ return ns.Start()
+ }
+ return pmaSetFunctions{}.MaxKey()
+}
+
+// IsEmpty returns true if the iterated gap is empty (that is, the "gap" is
+// between two adjacent segments.)
+func (gap pmaGapIterator) IsEmpty() bool {
+ return gap.Range().Length() == 0
+}
+
+// PrevSegment returns the segment immediately before the iterated gap. If no
+// such segment exists, PrevSegment returns a terminal iterator.
+func (gap pmaGapIterator) PrevSegment() pmaIterator {
+ return pmasegmentBeforePosition(gap.node, gap.index)
+}
+
+// NextSegment returns the segment immediately after the iterated gap. If no
+// such segment exists, NextSegment returns a terminal iterator.
+func (gap pmaGapIterator) NextSegment() pmaIterator {
+ return pmasegmentAfterPosition(gap.node, gap.index)
+}
+
+// PrevGap returns the iterated gap's predecessor. If no such gap exists,
+// PrevGap returns a terminal iterator.
+func (gap pmaGapIterator) PrevGap() pmaGapIterator {
+ seg := gap.PrevSegment()
+ if !seg.Ok() {
+ return pmaGapIterator{}
+ }
+ return seg.PrevGap()
+}
+
+// NextGap returns the iterated gap's successor. If no such gap exists, NextGap
+// returns a terminal iterator.
+func (gap pmaGapIterator) NextGap() pmaGapIterator {
+ seg := gap.NextSegment()
+ if !seg.Ok() {
+ return pmaGapIterator{}
+ }
+ return seg.NextGap()
+}
+
+// segmentBeforePosition returns the predecessor segment of the position given
+// by n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentBeforePosition returns a terminal iterator.
+func pmasegmentBeforePosition(n *pmanode, i int) pmaIterator {
+ for i == 0 {
+ if n.parent == nil {
+ return pmaIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return pmaIterator{n, i - 1}
+}
+
+// segmentAfterPosition returns the successor segment of the position given by
+// n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentAfterPosition returns a terminal iterator.
+func pmasegmentAfterPosition(n *pmanode, i int) pmaIterator {
+ for i == n.nrSegments {
+ if n.parent == nil {
+ return pmaIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return pmaIterator{n, i}
+}
+
+func pmazeroValueSlice(slice []pma) {
+
+ for i := range slice {
+ pmaSetFunctions{}.ClearValue(&slice[i])
+ }
+}
+
+func pmazeroNodeSlice(slice []*pmanode) {
+ for i := range slice {
+ slice[i] = nil
+ }
+}
+
+// String stringifies a Set for debugging.
+func (s *pmaSet) String() string {
+ return s.root.String()
+}
+
+// String stringifies a node (and all of its children) for debugging.
+func (n *pmanode) String() string {
+ var buf bytes.Buffer
+ n.writeDebugString(&buf, "")
+ return buf.String()
+}
+
+func (n *pmanode) writeDebugString(buf *bytes.Buffer, prefix string) {
+ if n.hasChildren != (n.nrSegments > 0 && n.children[0] != nil) {
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent value of hasChildren: got %v, want %v\n", n.hasChildren, !n.hasChildren))
+ }
+ for i := 0; i < n.nrSegments; i++ {
+ if child := n.children[i]; child != nil {
+ cprefix := fmt.Sprintf("%s- % 3d ", prefix, i)
+ if child.parent != n || child.parentIndex != i {
+ buf.WriteString(cprefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent linkage to parent: got (%p, %d), want (%p, %d)\n", child.parent, child.parentIndex, n, i))
+ }
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, i))
+ }
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("- % 3d: %v => %v\n", i, n.keys[i], n.values[i]))
+ }
+ if child := n.children[n.nrSegments]; child != nil {
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, n.nrSegments))
+ }
+}
+
+// SegmentDataSlices represents segments from a set as slices of start, end, and
+// values. SegmentDataSlices is primarily used as an intermediate representation
+// for save/restore and the layout here is optimized for that.
+//
+// +stateify savable
+type pmaSegmentDataSlices struct {
+ Start []__generics_imported0.Addr
+ End []__generics_imported0.Addr
+ Values []pma
+}
+
+// ExportSortedSlice returns a copy of all segments in the given set, in ascending
+// key order.
+func (s *pmaSet) ExportSortedSlices() *pmaSegmentDataSlices {
+ var sds pmaSegmentDataSlices
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sds.Start = append(sds.Start, seg.Start())
+ sds.End = append(sds.End, seg.End())
+ sds.Values = append(sds.Values, seg.Value())
+ }
+ sds.Start = sds.Start[:len(sds.Start):len(sds.Start)]
+ sds.End = sds.End[:len(sds.End):len(sds.End)]
+ sds.Values = sds.Values[:len(sds.Values):len(sds.Values)]
+ return &sds
+}
+
+// ImportSortedSlice initializes the given set from the given slice.
+//
+// Preconditions: s must be empty. sds must represent a valid set (the segments
+// in sds must have valid lengths that do not overlap). The segments in sds
+// must be sorted in ascending key order.
+func (s *pmaSet) ImportSortedSlices(sds *pmaSegmentDataSlices) error {
+ if !s.IsEmpty() {
+ return fmt.Errorf("cannot import into non-empty set %v", s)
+ }
+ gap := s.FirstGap()
+ for i := range sds.Start {
+ r := __generics_imported0.AddrRange{sds.Start[i], sds.End[i]}
+ if !gap.Range().IsSupersetOf(r) {
+ return fmt.Errorf("segment overlaps a preceding segment or is incorrectly sorted: [%d, %d) => %v", sds.Start[i], sds.End[i], sds.Values[i])
+ }
+ gap = s.InsertWithoutMerging(gap, r, sds.Values[i]).NextGap()
+ }
+ return nil
+}
+func (s *pmaSet) saveRoot() *pmaSegmentDataSlices {
+ return s.ExportSortedSlices()
+}
+
+func (s *pmaSet) loadRoot(sds *pmaSegmentDataSlices) {
+ if err := s.ImportSortedSlices(sds); err != nil {
+ panic(err)
+ }
+}
diff --git a/pkg/sentry/mm/vma_set.go b/pkg/sentry/mm/vma_set.go
new file mode 100755
index 000000000..c3ef24f34
--- /dev/null
+++ b/pkg/sentry/mm/vma_set.go
@@ -0,0 +1,1274 @@
+package mm
+
+import (
+ __generics_imported0 "gvisor.dev/gvisor/pkg/sentry/usermem"
+)
+
+import (
+ "bytes"
+ "fmt"
+)
+
+const (
+ // minDegree is the minimum degree of an internal node in a Set B-tree.
+ //
+ // - Any non-root node has at least minDegree-1 segments.
+ //
+ // - Any non-root internal (non-leaf) node has at least minDegree children.
+ //
+ // - The root node may have fewer than minDegree-1 segments, but it may
+ // only have 0 segments if the tree is empty.
+ //
+ // Our implementation requires minDegree >= 3. Higher values of minDegree
+ // usually improve performance, but increase memory usage for small sets.
+ vmaminDegree = 8
+
+ vmamaxDegree = 2 * vmaminDegree
+)
+
+// A Set is a mapping of segments with non-overlapping Range keys. The zero
+// value for a Set is an empty set. Set values are not safely movable nor
+// copyable. Set is thread-compatible.
+//
+// +stateify savable
+type vmaSet struct {
+ root vmanode `state:".(*vmaSegmentDataSlices)"`
+}
+
+// IsEmpty returns true if the set contains no segments.
+func (s *vmaSet) IsEmpty() bool {
+ return s.root.nrSegments == 0
+}
+
+// IsEmptyRange returns true iff no segments in the set overlap the given
+// range. This is semantically equivalent to s.SpanRange(r) == 0, but may be
+// more efficient.
+func (s *vmaSet) IsEmptyRange(r __generics_imported0.AddrRange) bool {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return true
+ }
+ _, gap := s.Find(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ return r.End <= gap.End()
+}
+
+// Span returns the total size of all segments in the set.
+func (s *vmaSet) Span() __generics_imported0.Addr {
+ var sz __generics_imported0.Addr
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sz += seg.Range().Length()
+ }
+ return sz
+}
+
+// SpanRange returns the total size of the intersection of segments in the set
+// with the given range.
+func (s *vmaSet) SpanRange(r __generics_imported0.AddrRange) __generics_imported0.Addr {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return 0
+ }
+ var sz __generics_imported0.Addr
+ for seg := s.LowerBoundSegment(r.Start); seg.Ok() && seg.Start() < r.End; seg = seg.NextSegment() {
+ sz += seg.Range().Intersect(r).Length()
+ }
+ return sz
+}
+
+// FirstSegment returns the first segment in the set. If the set is empty,
+// FirstSegment returns a terminal iterator.
+func (s *vmaSet) FirstSegment() vmaIterator {
+ if s.root.nrSegments == 0 {
+ return vmaIterator{}
+ }
+ return s.root.firstSegment()
+}
+
+// LastSegment returns the last segment in the set. If the set is empty,
+// LastSegment returns a terminal iterator.
+func (s *vmaSet) LastSegment() vmaIterator {
+ if s.root.nrSegments == 0 {
+ return vmaIterator{}
+ }
+ return s.root.lastSegment()
+}
+
+// FirstGap returns the first gap in the set.
+func (s *vmaSet) FirstGap() vmaGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return vmaGapIterator{n, 0}
+}
+
+// LastGap returns the last gap in the set.
+func (s *vmaSet) LastGap() vmaGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return vmaGapIterator{n, n.nrSegments}
+}
+
+// Find returns the segment or gap whose range contains the given key. If a
+// segment is found, the returned Iterator is non-terminal and the
+// returned GapIterator is terminal. Otherwise, the returned Iterator is
+// terminal and the returned GapIterator is non-terminal.
+func (s *vmaSet) Find(key __generics_imported0.Addr) (vmaIterator, vmaGapIterator) {
+ n := &s.root
+ for {
+
+ lower := 0
+ upper := n.nrSegments
+ for lower < upper {
+ i := lower + (upper-lower)/2
+ if r := n.keys[i]; key < r.End {
+ if key >= r.Start {
+ return vmaIterator{n, i}, vmaGapIterator{}
+ }
+ upper = i
+ } else {
+ lower = i + 1
+ }
+ }
+ i := lower
+ if !n.hasChildren {
+ return vmaIterator{}, vmaGapIterator{n, i}
+ }
+ n = n.children[i]
+ }
+}
+
+// FindSegment returns the segment whose range contains the given key. If no
+// such segment exists, FindSegment returns a terminal iterator.
+func (s *vmaSet) FindSegment(key __generics_imported0.Addr) vmaIterator {
+ seg, _ := s.Find(key)
+ return seg
+}
+
+// LowerBoundSegment returns the segment with the lowest range that contains a
+// key greater than or equal to min. If no such segment exists,
+// LowerBoundSegment returns a terminal iterator.
+func (s *vmaSet) LowerBoundSegment(min __generics_imported0.Addr) vmaIterator {
+ seg, gap := s.Find(min)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.NextSegment()
+}
+
+// UpperBoundSegment returns the segment with the highest range that contains a
+// key less than or equal to max. If no such segment exists, UpperBoundSegment
+// returns a terminal iterator.
+func (s *vmaSet) UpperBoundSegment(max __generics_imported0.Addr) vmaIterator {
+ seg, gap := s.Find(max)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.PrevSegment()
+}
+
+// FindGap returns the gap containing the given key. If no such gap exists
+// (i.e. the set contains a segment containing that key), FindGap returns a
+// terminal iterator.
+func (s *vmaSet) FindGap(key __generics_imported0.Addr) vmaGapIterator {
+ _, gap := s.Find(key)
+ return gap
+}
+
+// LowerBoundGap returns the gap with the lowest range that is greater than or
+// equal to min.
+func (s *vmaSet) LowerBoundGap(min __generics_imported0.Addr) vmaGapIterator {
+ seg, gap := s.Find(min)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.NextGap()
+}
+
+// UpperBoundGap returns the gap with the highest range that is less than or
+// equal to max.
+func (s *vmaSet) UpperBoundGap(max __generics_imported0.Addr) vmaGapIterator {
+ seg, gap := s.Find(max)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.PrevGap()
+}
+
+// Add inserts the given segment into the set and returns true. If the new
+// segment can be merged with adjacent segments, Add will do so. If the new
+// segment would overlap an existing segment, Add returns false. If Add
+// succeeds, all existing iterators are invalidated.
+func (s *vmaSet) Add(r __generics_imported0.AddrRange, val vma) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.Insert(gap, r, val)
+ return true
+}
+
+// AddWithoutMerging inserts the given segment into the set and returns true.
+// If it would overlap an existing segment, AddWithoutMerging does nothing and
+// returns false. If AddWithoutMerging succeeds, all existing iterators are
+// invalidated.
+func (s *vmaSet) AddWithoutMerging(r __generics_imported0.AddrRange, val vma) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.InsertWithoutMergingUnchecked(gap, r, val)
+ return true
+}
+
+// Insert inserts the given segment into the given gap. If the new segment can
+// be merged with adjacent segments, Insert will do so. Insert returns an
+// iterator to the segment containing the inserted value (which may have been
+// merged with other values). All existing iterators (including gap, but not
+// including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid, Insert panics.
+//
+// Insert is semantically equivalent to a InsertWithoutMerging followed by a
+// Merge, but may be more efficient. Note that there is no unchecked variant of
+// Insert since Insert must retrieve and inspect gap's predecessor and
+// successor segments regardless.
+func (s *vmaSet) Insert(gap vmaGapIterator, r __generics_imported0.AddrRange, val vma) vmaIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ prev, next := gap.PrevSegment(), gap.NextSegment()
+ if prev.Ok() && prev.End() > r.Start {
+ panic(fmt.Sprintf("new segment %v overlaps predecessor %v", r, prev.Range()))
+ }
+ if next.Ok() && next.Start() < r.End {
+ panic(fmt.Sprintf("new segment %v overlaps successor %v", r, next.Range()))
+ }
+ if prev.Ok() && prev.End() == r.Start {
+ if mval, ok := (vmaSetFunctions{}).Merge(prev.Range(), prev.Value(), r, val); ok {
+ prev.SetEndUnchecked(r.End)
+ prev.SetValue(mval)
+ if next.Ok() && next.Start() == r.End {
+ val = mval
+ if mval, ok := (vmaSetFunctions{}).Merge(prev.Range(), val, next.Range(), next.Value()); ok {
+ prev.SetEndUnchecked(next.End())
+ prev.SetValue(mval)
+ return s.Remove(next).PrevSegment()
+ }
+ }
+ return prev
+ }
+ }
+ if next.Ok() && next.Start() == r.End {
+ if mval, ok := (vmaSetFunctions{}).Merge(r, val, next.Range(), next.Value()); ok {
+ next.SetStartUnchecked(r.Start)
+ next.SetValue(mval)
+ return next
+ }
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMerging inserts the given segment into the given gap and
+// returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid,
+// InsertWithoutMerging panics.
+func (s *vmaSet) InsertWithoutMerging(gap vmaGapIterator, r __generics_imported0.AddrRange, val vma) vmaIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if gr := gap.Range(); !gr.IsSupersetOf(r) {
+ panic(fmt.Sprintf("cannot insert segment range %v into gap range %v", r, gr))
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMergingUnchecked inserts the given segment into the given gap
+// and returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// Preconditions: r.Start >= gap.Start(); r.End <= gap.End().
+func (s *vmaSet) InsertWithoutMergingUnchecked(gap vmaGapIterator, r __generics_imported0.AddrRange, val vma) vmaIterator {
+ gap = gap.node.rebalanceBeforeInsert(gap)
+ copy(gap.node.keys[gap.index+1:], gap.node.keys[gap.index:gap.node.nrSegments])
+ copy(gap.node.values[gap.index+1:], gap.node.values[gap.index:gap.node.nrSegments])
+ gap.node.keys[gap.index] = r
+ gap.node.values[gap.index] = val
+ gap.node.nrSegments++
+ return vmaIterator{gap.node, gap.index}
+}
+
+// Remove removes the given segment and returns an iterator to the vacated gap.
+// All existing iterators (including seg, but not including the returned
+// iterator) are invalidated.
+func (s *vmaSet) Remove(seg vmaIterator) vmaGapIterator {
+
+ if seg.node.hasChildren {
+
+ victim := seg.PrevSegment()
+
+ seg.SetRangeUnchecked(victim.Range())
+ seg.SetValue(victim.Value())
+ return s.Remove(victim).NextGap()
+ }
+ copy(seg.node.keys[seg.index:], seg.node.keys[seg.index+1:seg.node.nrSegments])
+ copy(seg.node.values[seg.index:], seg.node.values[seg.index+1:seg.node.nrSegments])
+ vmaSetFunctions{}.ClearValue(&seg.node.values[seg.node.nrSegments-1])
+ seg.node.nrSegments--
+ return seg.node.rebalanceAfterRemove(vmaGapIterator{seg.node, seg.index})
+}
+
+// RemoveAll removes all segments from the set. All existing iterators are
+// invalidated.
+func (s *vmaSet) RemoveAll() {
+ s.root = vmanode{}
+}
+
+// RemoveRange removes all segments in the given range. An iterator to the
+// newly formed gap is returned, and all existing iterators are invalidated.
+func (s *vmaSet) RemoveRange(r __generics_imported0.AddrRange) vmaGapIterator {
+ seg, gap := s.Find(r.Start)
+ if seg.Ok() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ for seg = gap.NextSegment(); seg.Ok() && seg.Start() < r.End; seg = gap.NextSegment() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ return gap
+}
+
+// Merge attempts to merge two neighboring segments. If successful, Merge
+// returns an iterator to the merged segment, and all existing iterators are
+// invalidated. Otherwise, Merge returns a terminal iterator.
+//
+// If first is not the predecessor of second, Merge panics.
+func (s *vmaSet) Merge(first, second vmaIterator) vmaIterator {
+ if first.NextSegment() != second {
+ panic(fmt.Sprintf("attempt to merge non-neighboring segments %v, %v", first.Range(), second.Range()))
+ }
+ return s.MergeUnchecked(first, second)
+}
+
+// MergeUnchecked attempts to merge two neighboring segments. If successful,
+// MergeUnchecked returns an iterator to the merged segment, and all existing
+// iterators are invalidated. Otherwise, MergeUnchecked returns a terminal
+// iterator.
+//
+// Precondition: first is the predecessor of second: first.NextSegment() ==
+// second, first == second.PrevSegment().
+func (s *vmaSet) MergeUnchecked(first, second vmaIterator) vmaIterator {
+ if first.End() == second.Start() {
+ if mval, ok := (vmaSetFunctions{}).Merge(first.Range(), first.Value(), second.Range(), second.Value()); ok {
+
+ first.SetEndUnchecked(second.End())
+ first.SetValue(mval)
+ return s.Remove(second).PrevSegment()
+ }
+ }
+ return vmaIterator{}
+}
+
+// MergeAll attempts to merge all adjacent segments in the set. All existing
+// iterators are invalidated.
+func (s *vmaSet) MergeAll() {
+ seg := s.FirstSegment()
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeRange attempts to merge all adjacent segments that contain a key in the
+// specific range. All existing iterators are invalidated.
+func (s *vmaSet) MergeRange(r __generics_imported0.AddrRange) {
+ seg := s.LowerBoundSegment(r.Start)
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() && next.Range().Start < r.End {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeAdjacent attempts to merge the segment containing r.Start with its
+// predecessor, and the segment containing r.End-1 with its successor.
+func (s *vmaSet) MergeAdjacent(r __generics_imported0.AddrRange) {
+ first := s.FindSegment(r.Start)
+ if first.Ok() {
+ if prev := first.PrevSegment(); prev.Ok() {
+ s.Merge(prev, first)
+ }
+ }
+ last := s.FindSegment(r.End - 1)
+ if last.Ok() {
+ if next := last.NextSegment(); next.Ok() {
+ s.Merge(last, next)
+ }
+ }
+}
+
+// Split splits the given segment at the given key and returns iterators to the
+// two resulting segments. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+//
+// If the segment cannot be split at split (because split is at the start or
+// end of the segment's range, so splitting would produce a segment with zero
+// length, or because split falls outside the segment's range altogether),
+// Split panics.
+func (s *vmaSet) Split(seg vmaIterator, split __generics_imported0.Addr) (vmaIterator, vmaIterator) {
+ if !seg.Range().CanSplitAt(split) {
+ panic(fmt.Sprintf("can't split %v at %v", seg.Range(), split))
+ }
+ return s.SplitUnchecked(seg, split)
+}
+
+// SplitUnchecked splits the given segment at the given key and returns
+// iterators to the two resulting segments. All existing iterators (including
+// seg, but not including the returned iterators) are invalidated.
+//
+// Preconditions: seg.Start() < key < seg.End().
+func (s *vmaSet) SplitUnchecked(seg vmaIterator, split __generics_imported0.Addr) (vmaIterator, vmaIterator) {
+ val1, val2 := (vmaSetFunctions{}).Split(seg.Range(), seg.Value(), split)
+ end2 := seg.End()
+ seg.SetEndUnchecked(split)
+ seg.SetValue(val1)
+ seg2 := s.InsertWithoutMergingUnchecked(seg.NextGap(), __generics_imported0.AddrRange{split, end2}, val2)
+
+ return seg2.PrevSegment(), seg2
+}
+
+// SplitAt splits the segment straddling split, if one exists. SplitAt returns
+// true if a segment was split and false otherwise. If SplitAt splits a
+// segment, all existing iterators are invalidated.
+func (s *vmaSet) SplitAt(split __generics_imported0.Addr) bool {
+ if seg := s.FindSegment(split); seg.Ok() && seg.Range().CanSplitAt(split) {
+ s.SplitUnchecked(seg, split)
+ return true
+ }
+ return false
+}
+
+// Isolate ensures that the given segment's range does not escape r by
+// splitting at r.Start and r.End if necessary, and returns an updated iterator
+// to the bounded segment. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+func (s *vmaSet) Isolate(seg vmaIterator, r __generics_imported0.AddrRange) vmaIterator {
+ if seg.Range().CanSplitAt(r.Start) {
+ _, seg = s.SplitUnchecked(seg, r.Start)
+ }
+ if seg.Range().CanSplitAt(r.End) {
+ seg, _ = s.SplitUnchecked(seg, r.End)
+ }
+ return seg
+}
+
+// ApplyContiguous applies a function to a contiguous range of segments,
+// splitting if necessary. The function is applied until the first gap is
+// encountered, at which point the gap is returned. If the function is applied
+// across the entire range, a terminal gap is returned. All existing iterators
+// are invalidated.
+//
+// N.B. The Iterator must not be invalidated by the function.
+func (s *vmaSet) ApplyContiguous(r __generics_imported0.AddrRange, fn func(seg vmaIterator)) vmaGapIterator {
+ seg, gap := s.Find(r.Start)
+ if !seg.Ok() {
+ return gap
+ }
+ for {
+ seg = s.Isolate(seg, r)
+ fn(seg)
+ if seg.End() >= r.End {
+ return vmaGapIterator{}
+ }
+ gap = seg.NextGap()
+ if !gap.IsEmpty() {
+ return gap
+ }
+ seg = gap.NextSegment()
+ if !seg.Ok() {
+
+ return vmaGapIterator{}
+ }
+ }
+}
+
+// +stateify savable
+type vmanode struct {
+ // An internal binary tree node looks like:
+ //
+ // K
+ // / \
+ // Cl Cr
+ //
+ // where all keys in the subtree rooted by Cl (the left subtree) are less
+ // than K (the key of the parent node), and all keys in the subtree rooted
+ // by Cr (the right subtree) are greater than K.
+ //
+ // An internal B-tree node's indexes work out to look like:
+ //
+ // K0 K1 K2 ... Kn-1
+ // / \/ \/ \ ... / \
+ // C0 C1 C2 C3 ... Cn-1 Cn
+ //
+ // where n is nrSegments.
+ nrSegments int
+
+ // parent is a pointer to this node's parent. If this node is root, parent
+ // is nil.
+ parent *vmanode
+
+ // parentIndex is the index of this node in parent.children.
+ parentIndex int
+
+ // Flag for internal nodes that is technically redundant with "children[0]
+ // != nil", but is stored in the first cache line. "hasChildren" rather
+ // than "isLeaf" because false must be the correct value for an empty root.
+ hasChildren bool
+
+ // Nodes store keys and values in separate arrays to maximize locality in
+ // the common case (scanning keys for lookup).
+ keys [vmamaxDegree - 1]__generics_imported0.AddrRange
+ values [vmamaxDegree - 1]vma
+ children [vmamaxDegree]*vmanode
+}
+
+// firstSegment returns the first segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *vmanode) firstSegment() vmaIterator {
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return vmaIterator{n, 0}
+}
+
+// lastSegment returns the last segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *vmanode) lastSegment() vmaIterator {
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return vmaIterator{n, n.nrSegments - 1}
+}
+
+func (n *vmanode) prevSibling() *vmanode {
+ if n.parent == nil || n.parentIndex == 0 {
+ return nil
+ }
+ return n.parent.children[n.parentIndex-1]
+}
+
+func (n *vmanode) nextSibling() *vmanode {
+ if n.parent == nil || n.parentIndex == n.parent.nrSegments {
+ return nil
+ }
+ return n.parent.children[n.parentIndex+1]
+}
+
+// rebalanceBeforeInsert splits n and its ancestors if they are full, as
+// required for insertion, and returns an updated iterator to the position
+// represented by gap.
+func (n *vmanode) rebalanceBeforeInsert(gap vmaGapIterator) vmaGapIterator {
+ if n.parent != nil {
+ gap = n.parent.rebalanceBeforeInsert(gap)
+ }
+ if n.nrSegments < vmamaxDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ left := &vmanode{
+ nrSegments: vmaminDegree - 1,
+ parent: n,
+ parentIndex: 0,
+ hasChildren: n.hasChildren,
+ }
+ right := &vmanode{
+ nrSegments: vmaminDegree - 1,
+ parent: n,
+ parentIndex: 1,
+ hasChildren: n.hasChildren,
+ }
+ copy(left.keys[:vmaminDegree-1], n.keys[:vmaminDegree-1])
+ copy(left.values[:vmaminDegree-1], n.values[:vmaminDegree-1])
+ copy(right.keys[:vmaminDegree-1], n.keys[vmaminDegree:])
+ copy(right.values[:vmaminDegree-1], n.values[vmaminDegree:])
+ n.keys[0], n.values[0] = n.keys[vmaminDegree-1], n.values[vmaminDegree-1]
+ vmazeroValueSlice(n.values[1:])
+ if n.hasChildren {
+ copy(left.children[:vmaminDegree], n.children[:vmaminDegree])
+ copy(right.children[:vmaminDegree], n.children[vmaminDegree:])
+ vmazeroNodeSlice(n.children[2:])
+ for i := 0; i < vmaminDegree; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ right.children[i].parent = right
+ right.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = 1
+ n.hasChildren = true
+ n.children[0] = left
+ n.children[1] = right
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < vmaminDegree {
+ return vmaGapIterator{left, gap.index}
+ }
+ return vmaGapIterator{right, gap.index - vmaminDegree}
+ }
+
+ copy(n.parent.keys[n.parentIndex+1:], n.parent.keys[n.parentIndex:n.parent.nrSegments])
+ copy(n.parent.values[n.parentIndex+1:], n.parent.values[n.parentIndex:n.parent.nrSegments])
+ n.parent.keys[n.parentIndex], n.parent.values[n.parentIndex] = n.keys[vmaminDegree-1], n.values[vmaminDegree-1]
+ copy(n.parent.children[n.parentIndex+2:], n.parent.children[n.parentIndex+1:n.parent.nrSegments+1])
+ for i := n.parentIndex + 2; i < n.parent.nrSegments+2; i++ {
+ n.parent.children[i].parentIndex = i
+ }
+ sibling := &vmanode{
+ nrSegments: vmaminDegree - 1,
+ parent: n.parent,
+ parentIndex: n.parentIndex + 1,
+ hasChildren: n.hasChildren,
+ }
+ n.parent.children[n.parentIndex+1] = sibling
+ n.parent.nrSegments++
+ copy(sibling.keys[:vmaminDegree-1], n.keys[vmaminDegree:])
+ copy(sibling.values[:vmaminDegree-1], n.values[vmaminDegree:])
+ vmazeroValueSlice(n.values[vmaminDegree-1:])
+ if n.hasChildren {
+ copy(sibling.children[:vmaminDegree], n.children[vmaminDegree:])
+ vmazeroNodeSlice(n.children[vmaminDegree:])
+ for i := 0; i < vmaminDegree; i++ {
+ sibling.children[i].parent = sibling
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = vmaminDegree - 1
+
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < vmaminDegree {
+ return gap
+ }
+ return vmaGapIterator{sibling, gap.index - vmaminDegree}
+}
+
+// rebalanceAfterRemove "unsplits" n and its ancestors if they are deficient
+// (contain fewer segments than required by B-tree invariants), as required for
+// removal, and returns an updated iterator to the position represented by gap.
+//
+// Precondition: n is the only node in the tree that may currently violate a
+// B-tree invariant.
+func (n *vmanode) rebalanceAfterRemove(gap vmaGapIterator) vmaGapIterator {
+ for {
+ if n.nrSegments >= vmaminDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ return gap
+ }
+
+ if sibling := n.prevSibling(); sibling != nil && sibling.nrSegments >= vmaminDegree {
+ copy(n.keys[1:], n.keys[:n.nrSegments])
+ copy(n.values[1:], n.values[:n.nrSegments])
+ n.keys[0] = n.parent.keys[n.parentIndex-1]
+ n.values[0] = n.parent.values[n.parentIndex-1]
+ n.parent.keys[n.parentIndex-1] = sibling.keys[sibling.nrSegments-1]
+ n.parent.values[n.parentIndex-1] = sibling.values[sibling.nrSegments-1]
+ vmaSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ copy(n.children[1:], n.children[:n.nrSegments+1])
+ n.children[0] = sibling.children[sibling.nrSegments]
+ sibling.children[sibling.nrSegments] = nil
+ n.children[0].parent = n
+ n.children[0].parentIndex = 0
+ for i := 1; i < n.nrSegments+2; i++ {
+ n.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling && gap.index == sibling.nrSegments {
+ return vmaGapIterator{n, 0}
+ }
+ if gap.node == n {
+ return vmaGapIterator{n, gap.index + 1}
+ }
+ return gap
+ }
+ if sibling := n.nextSibling(); sibling != nil && sibling.nrSegments >= vmaminDegree {
+ n.keys[n.nrSegments] = n.parent.keys[n.parentIndex]
+ n.values[n.nrSegments] = n.parent.values[n.parentIndex]
+ n.parent.keys[n.parentIndex] = sibling.keys[0]
+ n.parent.values[n.parentIndex] = sibling.values[0]
+ copy(sibling.keys[:sibling.nrSegments-1], sibling.keys[1:])
+ copy(sibling.values[:sibling.nrSegments-1], sibling.values[1:])
+ vmaSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ n.children[n.nrSegments+1] = sibling.children[0]
+ copy(sibling.children[:sibling.nrSegments], sibling.children[1:])
+ sibling.children[sibling.nrSegments] = nil
+ n.children[n.nrSegments+1].parent = n
+ n.children[n.nrSegments+1].parentIndex = n.nrSegments + 1
+ for i := 0; i < sibling.nrSegments; i++ {
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling {
+ if gap.index == 0 {
+ return vmaGapIterator{n, n.nrSegments}
+ }
+ return vmaGapIterator{sibling, gap.index - 1}
+ }
+ return gap
+ }
+
+ p := n.parent
+ if p.nrSegments == 1 {
+
+ left, right := p.children[0], p.children[1]
+ p.nrSegments = left.nrSegments + right.nrSegments + 1
+ p.hasChildren = left.hasChildren
+ p.keys[left.nrSegments] = p.keys[0]
+ p.values[left.nrSegments] = p.values[0]
+ copy(p.keys[:left.nrSegments], left.keys[:left.nrSegments])
+ copy(p.values[:left.nrSegments], left.values[:left.nrSegments])
+ copy(p.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(p.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(p.children[:left.nrSegments+1], left.children[:left.nrSegments+1])
+ copy(p.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := 0; i < p.nrSegments+1; i++ {
+ p.children[i].parent = p
+ p.children[i].parentIndex = i
+ }
+ } else {
+ p.children[0] = nil
+ p.children[1] = nil
+ }
+ if gap.node == left {
+ return vmaGapIterator{p, gap.index}
+ }
+ if gap.node == right {
+ return vmaGapIterator{p, gap.index + left.nrSegments + 1}
+ }
+ return gap
+ }
+ // Merge n and either sibling, along with the segment separating the
+ // two, into whichever of the two nodes comes first. This is the
+ // reverse of the non-root splitting case in
+ // node.rebalanceBeforeInsert.
+ var left, right *vmanode
+ if n.parentIndex > 0 {
+ left = n.prevSibling()
+ right = n
+ } else {
+ left = n
+ right = n.nextSibling()
+ }
+
+ if gap.node == right {
+ gap = vmaGapIterator{left, gap.index + left.nrSegments + 1}
+ }
+ left.keys[left.nrSegments] = p.keys[left.parentIndex]
+ left.values[left.nrSegments] = p.values[left.parentIndex]
+ copy(left.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(left.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(left.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := left.nrSegments + 1; i < left.nrSegments+right.nrSegments+2; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ }
+ }
+ left.nrSegments += right.nrSegments + 1
+ copy(p.keys[left.parentIndex:], p.keys[left.parentIndex+1:p.nrSegments])
+ copy(p.values[left.parentIndex:], p.values[left.parentIndex+1:p.nrSegments])
+ vmaSetFunctions{}.ClearValue(&p.values[p.nrSegments-1])
+ copy(p.children[left.parentIndex+1:], p.children[left.parentIndex+2:p.nrSegments+1])
+ for i := 0; i < p.nrSegments; i++ {
+ p.children[i].parentIndex = i
+ }
+ p.children[p.nrSegments] = nil
+ p.nrSegments--
+
+ n = p
+ }
+}
+
+// A Iterator is conceptually one of:
+//
+// - A pointer to a segment in a set; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Iterators are copyable values and are meaningfully equality-comparable. The
+// zero value of Iterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type vmaIterator struct {
+ // node is the node containing the iterated segment. If the iterator is
+ // terminal, node is nil.
+ node *vmanode
+
+ // index is the index of the segment in node.keys/values.
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (seg vmaIterator) Ok() bool {
+ return seg.node != nil
+}
+
+// Range returns the iterated segment's range key.
+func (seg vmaIterator) Range() __generics_imported0.AddrRange {
+ return seg.node.keys[seg.index]
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (seg vmaIterator) Start() __generics_imported0.Addr {
+ return seg.node.keys[seg.index].Start
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (seg vmaIterator) End() __generics_imported0.Addr {
+ return seg.node.keys[seg.index].End
+}
+
+// SetRangeUnchecked mutates the iterated segment's range key. This operation
+// does not invalidate any iterators.
+//
+// Preconditions:
+//
+// - r.Length() > 0.
+//
+// - The new range must not overlap an existing one: If seg.NextSegment().Ok(),
+// then r.end <= seg.NextSegment().Start(); if seg.PrevSegment().Ok(), then
+// r.start >= seg.PrevSegment().End().
+func (seg vmaIterator) SetRangeUnchecked(r __generics_imported0.AddrRange) {
+ seg.node.keys[seg.index] = r
+}
+
+// SetRange mutates the iterated segment's range key. If the new range would
+// cause the iterated segment to overlap another segment, or if the new range
+// is invalid, SetRange panics. This operation does not invalidate any
+// iterators.
+func (seg vmaIterator) SetRange(r __generics_imported0.AddrRange) {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && r.Start < prev.End() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, prev.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && r.End > next.Start() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, next.Range()))
+ }
+ seg.SetRangeUnchecked(r)
+}
+
+// SetStartUnchecked mutates the iterated segment's start. This operation does
+// not invalidate any iterators.
+//
+// Preconditions: The new start must be valid: start < seg.End(); if
+// seg.PrevSegment().Ok(), then start >= seg.PrevSegment().End().
+func (seg vmaIterator) SetStartUnchecked(start __generics_imported0.Addr) {
+ seg.node.keys[seg.index].Start = start
+}
+
+// SetStart mutates the iterated segment's start. If the new start value would
+// cause the iterated segment to overlap another segment, or would result in an
+// invalid range, SetStart panics. This operation does not invalidate any
+// iterators.
+func (seg vmaIterator) SetStart(start __generics_imported0.Addr) {
+ if start >= seg.End() {
+ panic(fmt.Sprintf("new start %v would invalidate segment range %v", start, seg.Range()))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && start < prev.End() {
+ panic(fmt.Sprintf("new start %v would cause segment range %v to overlap segment range %v", start, seg.Range(), prev.Range()))
+ }
+ seg.SetStartUnchecked(start)
+}
+
+// SetEndUnchecked mutates the iterated segment's end. This operation does not
+// invalidate any iterators.
+//
+// Preconditions: The new end must be valid: end > seg.Start(); if
+// seg.NextSegment().Ok(), then end <= seg.NextSegment().Start().
+func (seg vmaIterator) SetEndUnchecked(end __generics_imported0.Addr) {
+ seg.node.keys[seg.index].End = end
+}
+
+// SetEnd mutates the iterated segment's end. If the new end value would cause
+// the iterated segment to overlap another segment, or would result in an
+// invalid range, SetEnd panics. This operation does not invalidate any
+// iterators.
+func (seg vmaIterator) SetEnd(end __generics_imported0.Addr) {
+ if end <= seg.Start() {
+ panic(fmt.Sprintf("new end %v would invalidate segment range %v", end, seg.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && end > next.Start() {
+ panic(fmt.Sprintf("new end %v would cause segment range %v to overlap segment range %v", end, seg.Range(), next.Range()))
+ }
+ seg.SetEndUnchecked(end)
+}
+
+// Value returns a copy of the iterated segment's value.
+func (seg vmaIterator) Value() vma {
+ return seg.node.values[seg.index]
+}
+
+// ValuePtr returns a pointer to the iterated segment's value. The pointer is
+// invalidated if the iterator is invalidated. This operation does not
+// invalidate any iterators.
+func (seg vmaIterator) ValuePtr() *vma {
+ return &seg.node.values[seg.index]
+}
+
+// SetValue mutates the iterated segment's value. This operation does not
+// invalidate any iterators.
+func (seg vmaIterator) SetValue(val vma) {
+ seg.node.values[seg.index] = val
+}
+
+// PrevSegment returns the iterated segment's predecessor. If there is no
+// preceding segment, PrevSegment returns a terminal iterator.
+func (seg vmaIterator) PrevSegment() vmaIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index].lastSegment()
+ }
+ if seg.index > 0 {
+ return vmaIterator{seg.node, seg.index - 1}
+ }
+ if seg.node.parent == nil {
+ return vmaIterator{}
+ }
+ return vmasegmentBeforePosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// NextSegment returns the iterated segment's successor. If there is no
+// succeeding segment, NextSegment returns a terminal iterator.
+func (seg vmaIterator) NextSegment() vmaIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment()
+ }
+ if seg.index < seg.node.nrSegments-1 {
+ return vmaIterator{seg.node, seg.index + 1}
+ }
+ if seg.node.parent == nil {
+ return vmaIterator{}
+ }
+ return vmasegmentAfterPosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// PrevGap returns the gap immediately before the iterated segment.
+func (seg vmaIterator) PrevGap() vmaGapIterator {
+ if seg.node.hasChildren {
+
+ return seg.node.children[seg.index].lastSegment().NextGap()
+ }
+ return vmaGapIterator{seg.node, seg.index}
+}
+
+// NextGap returns the gap immediately after the iterated segment.
+func (seg vmaIterator) NextGap() vmaGapIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment().PrevGap()
+ }
+ return vmaGapIterator{seg.node, seg.index + 1}
+}
+
+// PrevNonEmpty returns the iterated segment's predecessor if it is adjacent,
+// or the gap before the iterated segment otherwise. If seg.Start() ==
+// Functions.MinKey(), PrevNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by PrevNonEmpty will be
+// non-terminal.
+func (seg vmaIterator) PrevNonEmpty() (vmaIterator, vmaGapIterator) {
+ gap := seg.PrevGap()
+ if gap.Range().Length() != 0 {
+ return vmaIterator{}, gap
+ }
+ return gap.PrevSegment(), vmaGapIterator{}
+}
+
+// NextNonEmpty returns the iterated segment's successor if it is adjacent, or
+// the gap after the iterated segment otherwise. If seg.End() ==
+// Functions.MaxKey(), NextNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by NextNonEmpty will be
+// non-terminal.
+func (seg vmaIterator) NextNonEmpty() (vmaIterator, vmaGapIterator) {
+ gap := seg.NextGap()
+ if gap.Range().Length() != 0 {
+ return vmaIterator{}, gap
+ }
+ return gap.NextSegment(), vmaGapIterator{}
+}
+
+// A GapIterator is conceptually one of:
+//
+// - A pointer to a position between two segments, before the first segment, or
+// after the last segment in a set, called a *gap*; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Note that the gap between two adjacent segments exists (iterators to it are
+// non-terminal), but has a length of zero. GapIterator.IsEmpty returns true
+// for such gaps. An empty set contains a single gap, spanning the entire range
+// of the set's keys.
+//
+// GapIterators are copyable values and are meaningfully equality-comparable.
+// The zero value of GapIterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type vmaGapIterator struct {
+ // The representation of a GapIterator is identical to that of an Iterator,
+ // except that index corresponds to positions between segments in the same
+ // way as for node.children (see comment for node.nrSegments).
+ node *vmanode
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (gap vmaGapIterator) Ok() bool {
+ return gap.node != nil
+}
+
+// Range returns the range spanned by the iterated gap.
+func (gap vmaGapIterator) Range() __generics_imported0.AddrRange {
+ return __generics_imported0.AddrRange{gap.Start(), gap.End()}
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (gap vmaGapIterator) Start() __generics_imported0.Addr {
+ if ps := gap.PrevSegment(); ps.Ok() {
+ return ps.End()
+ }
+ return vmaSetFunctions{}.MinKey()
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (gap vmaGapIterator) End() __generics_imported0.Addr {
+ if ns := gap.NextSegment(); ns.Ok() {
+ return ns.Start()
+ }
+ return vmaSetFunctions{}.MaxKey()
+}
+
+// IsEmpty returns true if the iterated gap is empty (that is, the "gap" is
+// between two adjacent segments.)
+func (gap vmaGapIterator) IsEmpty() bool {
+ return gap.Range().Length() == 0
+}
+
+// PrevSegment returns the segment immediately before the iterated gap. If no
+// such segment exists, PrevSegment returns a terminal iterator.
+func (gap vmaGapIterator) PrevSegment() vmaIterator {
+ return vmasegmentBeforePosition(gap.node, gap.index)
+}
+
+// NextSegment returns the segment immediately after the iterated gap. If no
+// such segment exists, NextSegment returns a terminal iterator.
+func (gap vmaGapIterator) NextSegment() vmaIterator {
+ return vmasegmentAfterPosition(gap.node, gap.index)
+}
+
+// PrevGap returns the iterated gap's predecessor. If no such gap exists,
+// PrevGap returns a terminal iterator.
+func (gap vmaGapIterator) PrevGap() vmaGapIterator {
+ seg := gap.PrevSegment()
+ if !seg.Ok() {
+ return vmaGapIterator{}
+ }
+ return seg.PrevGap()
+}
+
+// NextGap returns the iterated gap's successor. If no such gap exists, NextGap
+// returns a terminal iterator.
+func (gap vmaGapIterator) NextGap() vmaGapIterator {
+ seg := gap.NextSegment()
+ if !seg.Ok() {
+ return vmaGapIterator{}
+ }
+ return seg.NextGap()
+}
+
+// segmentBeforePosition returns the predecessor segment of the position given
+// by n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentBeforePosition returns a terminal iterator.
+func vmasegmentBeforePosition(n *vmanode, i int) vmaIterator {
+ for i == 0 {
+ if n.parent == nil {
+ return vmaIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return vmaIterator{n, i - 1}
+}
+
+// segmentAfterPosition returns the successor segment of the position given by
+// n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentAfterPosition returns a terminal iterator.
+func vmasegmentAfterPosition(n *vmanode, i int) vmaIterator {
+ for i == n.nrSegments {
+ if n.parent == nil {
+ return vmaIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return vmaIterator{n, i}
+}
+
+func vmazeroValueSlice(slice []vma) {
+
+ for i := range slice {
+ vmaSetFunctions{}.ClearValue(&slice[i])
+ }
+}
+
+func vmazeroNodeSlice(slice []*vmanode) {
+ for i := range slice {
+ slice[i] = nil
+ }
+}
+
+// String stringifies a Set for debugging.
+func (s *vmaSet) String() string {
+ return s.root.String()
+}
+
+// String stringifies a node (and all of its children) for debugging.
+func (n *vmanode) String() string {
+ var buf bytes.Buffer
+ n.writeDebugString(&buf, "")
+ return buf.String()
+}
+
+func (n *vmanode) writeDebugString(buf *bytes.Buffer, prefix string) {
+ if n.hasChildren != (n.nrSegments > 0 && n.children[0] != nil) {
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent value of hasChildren: got %v, want %v\n", n.hasChildren, !n.hasChildren))
+ }
+ for i := 0; i < n.nrSegments; i++ {
+ if child := n.children[i]; child != nil {
+ cprefix := fmt.Sprintf("%s- % 3d ", prefix, i)
+ if child.parent != n || child.parentIndex != i {
+ buf.WriteString(cprefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent linkage to parent: got (%p, %d), want (%p, %d)\n", child.parent, child.parentIndex, n, i))
+ }
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, i))
+ }
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("- % 3d: %v => %v\n", i, n.keys[i], n.values[i]))
+ }
+ if child := n.children[n.nrSegments]; child != nil {
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, n.nrSegments))
+ }
+}
+
+// SegmentDataSlices represents segments from a set as slices of start, end, and
+// values. SegmentDataSlices is primarily used as an intermediate representation
+// for save/restore and the layout here is optimized for that.
+//
+// +stateify savable
+type vmaSegmentDataSlices struct {
+ Start []__generics_imported0.Addr
+ End []__generics_imported0.Addr
+ Values []vma
+}
+
+// ExportSortedSlice returns a copy of all segments in the given set, in ascending
+// key order.
+func (s *vmaSet) ExportSortedSlices() *vmaSegmentDataSlices {
+ var sds vmaSegmentDataSlices
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sds.Start = append(sds.Start, seg.Start())
+ sds.End = append(sds.End, seg.End())
+ sds.Values = append(sds.Values, seg.Value())
+ }
+ sds.Start = sds.Start[:len(sds.Start):len(sds.Start)]
+ sds.End = sds.End[:len(sds.End):len(sds.End)]
+ sds.Values = sds.Values[:len(sds.Values):len(sds.Values)]
+ return &sds
+}
+
+// ImportSortedSlice initializes the given set from the given slice.
+//
+// Preconditions: s must be empty. sds must represent a valid set (the segments
+// in sds must have valid lengths that do not overlap). The segments in sds
+// must be sorted in ascending key order.
+func (s *vmaSet) ImportSortedSlices(sds *vmaSegmentDataSlices) error {
+ if !s.IsEmpty() {
+ return fmt.Errorf("cannot import into non-empty set %v", s)
+ }
+ gap := s.FirstGap()
+ for i := range sds.Start {
+ r := __generics_imported0.AddrRange{sds.Start[i], sds.End[i]}
+ if !gap.Range().IsSupersetOf(r) {
+ return fmt.Errorf("segment overlaps a preceding segment or is incorrectly sorted: [%d, %d) => %v", sds.Start[i], sds.End[i], sds.Values[i])
+ }
+ gap = s.InsertWithoutMerging(gap, r, sds.Values[i]).NextGap()
+ }
+ return nil
+}
+func (s *vmaSet) saveRoot() *vmaSegmentDataSlices {
+ return s.ExportSortedSlices()
+}
+
+func (s *vmaSet) loadRoot(sds *vmaSegmentDataSlices) {
+ if err := s.ImportSortedSlices(sds); err != nil {
+ panic(err)
+ }
+}
diff --git a/pkg/sentry/pgalloc/BUILD b/pkg/sentry/pgalloc/BUILD
deleted file mode 100644
index 3fd904c67..000000000
--- a/pkg/sentry/pgalloc/BUILD
+++ /dev/null
@@ -1,87 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_template_instance(
- name = "evictable_range",
- out = "evictable_range.go",
- package = "pgalloc",
- prefix = "Evictable",
- template = "//pkg/segment:generic_range",
- types = {
- "T": "uint64",
- },
-)
-
-go_template_instance(
- name = "evictable_range_set",
- out = "evictable_range_set.go",
- package = "pgalloc",
- prefix = "evictableRange",
- template = "//pkg/segment:generic_set",
- types = {
- "Key": "uint64",
- "Range": "EvictableRange",
- "Value": "evictableRangeSetValue",
- "Functions": "evictableRangeSetFunctions",
- },
-)
-
-go_template_instance(
- name = "usage_set",
- out = "usage_set.go",
- consts = {
- "minDegree": "10",
- },
- imports = {
- "platform": "gvisor.dev/gvisor/pkg/sentry/platform",
- },
- package = "pgalloc",
- prefix = "usage",
- template = "//pkg/segment:generic_set",
- types = {
- "Key": "uint64",
- "Range": "platform.FileRange",
- "Value": "usageInfo",
- "Functions": "usageSetFunctions",
- },
-)
-
-go_library(
- name = "pgalloc",
- srcs = [
- "context.go",
- "evictable_range.go",
- "evictable_range_set.go",
- "pgalloc.go",
- "pgalloc_unsafe.go",
- "save_restore.go",
- "usage_set.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/pgalloc",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/log",
- "//pkg/memutil",
- "//pkg/sentry/arch",
- "//pkg/sentry/context",
- "//pkg/sentry/hostmm",
- "//pkg/sentry/platform",
- "//pkg/sentry/safemem",
- "//pkg/sentry/usage",
- "//pkg/sentry/usermem",
- "//pkg/state",
- "//pkg/syserror",
- ],
-)
-
-go_test(
- name = "pgalloc_test",
- size = "small",
- srcs = ["pgalloc_test.go"],
- embed = [":pgalloc"],
- deps = ["//pkg/sentry/usermem"],
-)
diff --git a/pkg/sentry/pgalloc/evictable_range.go b/pkg/sentry/pgalloc/evictable_range.go
new file mode 100755
index 000000000..10ce2ff44
--- /dev/null
+++ b/pkg/sentry/pgalloc/evictable_range.go
@@ -0,0 +1,62 @@
+package pgalloc
+
+// A Range represents a contiguous range of T.
+//
+// +stateify savable
+type EvictableRange struct {
+ // Start is the inclusive start of the range.
+ Start uint64
+
+ // End is the exclusive end of the range.
+ End uint64
+}
+
+// WellFormed returns true if r.Start <= r.End. All other methods on a Range
+// require that the Range is well-formed.
+func (r EvictableRange) WellFormed() bool {
+ return r.Start <= r.End
+}
+
+// Length returns the length of the range.
+func (r EvictableRange) Length() uint64 {
+ return r.End - r.Start
+}
+
+// Contains returns true if r contains x.
+func (r EvictableRange) Contains(x uint64) bool {
+ return r.Start <= x && x < r.End
+}
+
+// Overlaps returns true if r and r2 overlap.
+func (r EvictableRange) Overlaps(r2 EvictableRange) bool {
+ return r.Start < r2.End && r2.Start < r.End
+}
+
+// IsSupersetOf returns true if r is a superset of r2; that is, the range r2 is
+// contained within r.
+func (r EvictableRange) IsSupersetOf(r2 EvictableRange) bool {
+ return r.Start <= r2.Start && r.End >= r2.End
+}
+
+// Intersect returns a range consisting of the intersection between r and r2.
+// If r and r2 do not overlap, Intersect returns a range with unspecified
+// bounds, but for which Length() == 0.
+func (r EvictableRange) Intersect(r2 EvictableRange) EvictableRange {
+ if r.Start < r2.Start {
+ r.Start = r2.Start
+ }
+ if r.End > r2.End {
+ r.End = r2.End
+ }
+ if r.End < r.Start {
+ r.End = r.Start
+ }
+ return r
+}
+
+// CanSplitAt returns true if it is legal to split a segment spanning the range
+// r at x; that is, splitting at x would produce two ranges, both of which have
+// non-zero length.
+func (r EvictableRange) CanSplitAt(x uint64) bool {
+ return r.Contains(x) && r.Start < x
+}
diff --git a/pkg/sentry/pgalloc/evictable_range_set.go b/pkg/sentry/pgalloc/evictable_range_set.go
new file mode 100755
index 000000000..6fbd02434
--- /dev/null
+++ b/pkg/sentry/pgalloc/evictable_range_set.go
@@ -0,0 +1,1270 @@
+package pgalloc
+
+import (
+ "bytes"
+ "fmt"
+)
+
+const (
+ // minDegree is the minimum degree of an internal node in a Set B-tree.
+ //
+ // - Any non-root node has at least minDegree-1 segments.
+ //
+ // - Any non-root internal (non-leaf) node has at least minDegree children.
+ //
+ // - The root node may have fewer than minDegree-1 segments, but it may
+ // only have 0 segments if the tree is empty.
+ //
+ // Our implementation requires minDegree >= 3. Higher values of minDegree
+ // usually improve performance, but increase memory usage for small sets.
+ evictableRangeminDegree = 3
+
+ evictableRangemaxDegree = 2 * evictableRangeminDegree
+)
+
+// A Set is a mapping of segments with non-overlapping Range keys. The zero
+// value for a Set is an empty set. Set values are not safely movable nor
+// copyable. Set is thread-compatible.
+//
+// +stateify savable
+type evictableRangeSet struct {
+ root evictableRangenode `state:".(*evictableRangeSegmentDataSlices)"`
+}
+
+// IsEmpty returns true if the set contains no segments.
+func (s *evictableRangeSet) IsEmpty() bool {
+ return s.root.nrSegments == 0
+}
+
+// IsEmptyRange returns true iff no segments in the set overlap the given
+// range. This is semantically equivalent to s.SpanRange(r) == 0, but may be
+// more efficient.
+func (s *evictableRangeSet) IsEmptyRange(r EvictableRange) bool {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return true
+ }
+ _, gap := s.Find(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ return r.End <= gap.End()
+}
+
+// Span returns the total size of all segments in the set.
+func (s *evictableRangeSet) Span() uint64 {
+ var sz uint64
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sz += seg.Range().Length()
+ }
+ return sz
+}
+
+// SpanRange returns the total size of the intersection of segments in the set
+// with the given range.
+func (s *evictableRangeSet) SpanRange(r EvictableRange) uint64 {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return 0
+ }
+ var sz uint64
+ for seg := s.LowerBoundSegment(r.Start); seg.Ok() && seg.Start() < r.End; seg = seg.NextSegment() {
+ sz += seg.Range().Intersect(r).Length()
+ }
+ return sz
+}
+
+// FirstSegment returns the first segment in the set. If the set is empty,
+// FirstSegment returns a terminal iterator.
+func (s *evictableRangeSet) FirstSegment() evictableRangeIterator {
+ if s.root.nrSegments == 0 {
+ return evictableRangeIterator{}
+ }
+ return s.root.firstSegment()
+}
+
+// LastSegment returns the last segment in the set. If the set is empty,
+// LastSegment returns a terminal iterator.
+func (s *evictableRangeSet) LastSegment() evictableRangeIterator {
+ if s.root.nrSegments == 0 {
+ return evictableRangeIterator{}
+ }
+ return s.root.lastSegment()
+}
+
+// FirstGap returns the first gap in the set.
+func (s *evictableRangeSet) FirstGap() evictableRangeGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return evictableRangeGapIterator{n, 0}
+}
+
+// LastGap returns the last gap in the set.
+func (s *evictableRangeSet) LastGap() evictableRangeGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return evictableRangeGapIterator{n, n.nrSegments}
+}
+
+// Find returns the segment or gap whose range contains the given key. If a
+// segment is found, the returned Iterator is non-terminal and the
+// returned GapIterator is terminal. Otherwise, the returned Iterator is
+// terminal and the returned GapIterator is non-terminal.
+func (s *evictableRangeSet) Find(key uint64) (evictableRangeIterator, evictableRangeGapIterator) {
+ n := &s.root
+ for {
+
+ lower := 0
+ upper := n.nrSegments
+ for lower < upper {
+ i := lower + (upper-lower)/2
+ if r := n.keys[i]; key < r.End {
+ if key >= r.Start {
+ return evictableRangeIterator{n, i}, evictableRangeGapIterator{}
+ }
+ upper = i
+ } else {
+ lower = i + 1
+ }
+ }
+ i := lower
+ if !n.hasChildren {
+ return evictableRangeIterator{}, evictableRangeGapIterator{n, i}
+ }
+ n = n.children[i]
+ }
+}
+
+// FindSegment returns the segment whose range contains the given key. If no
+// such segment exists, FindSegment returns a terminal iterator.
+func (s *evictableRangeSet) FindSegment(key uint64) evictableRangeIterator {
+ seg, _ := s.Find(key)
+ return seg
+}
+
+// LowerBoundSegment returns the segment with the lowest range that contains a
+// key greater than or equal to min. If no such segment exists,
+// LowerBoundSegment returns a terminal iterator.
+func (s *evictableRangeSet) LowerBoundSegment(min uint64) evictableRangeIterator {
+ seg, gap := s.Find(min)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.NextSegment()
+}
+
+// UpperBoundSegment returns the segment with the highest range that contains a
+// key less than or equal to max. If no such segment exists, UpperBoundSegment
+// returns a terminal iterator.
+func (s *evictableRangeSet) UpperBoundSegment(max uint64) evictableRangeIterator {
+ seg, gap := s.Find(max)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.PrevSegment()
+}
+
+// FindGap returns the gap containing the given key. If no such gap exists
+// (i.e. the set contains a segment containing that key), FindGap returns a
+// terminal iterator.
+func (s *evictableRangeSet) FindGap(key uint64) evictableRangeGapIterator {
+ _, gap := s.Find(key)
+ return gap
+}
+
+// LowerBoundGap returns the gap with the lowest range that is greater than or
+// equal to min.
+func (s *evictableRangeSet) LowerBoundGap(min uint64) evictableRangeGapIterator {
+ seg, gap := s.Find(min)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.NextGap()
+}
+
+// UpperBoundGap returns the gap with the highest range that is less than or
+// equal to max.
+func (s *evictableRangeSet) UpperBoundGap(max uint64) evictableRangeGapIterator {
+ seg, gap := s.Find(max)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.PrevGap()
+}
+
+// Add inserts the given segment into the set and returns true. If the new
+// segment can be merged with adjacent segments, Add will do so. If the new
+// segment would overlap an existing segment, Add returns false. If Add
+// succeeds, all existing iterators are invalidated.
+func (s *evictableRangeSet) Add(r EvictableRange, val evictableRangeSetValue) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.Insert(gap, r, val)
+ return true
+}
+
+// AddWithoutMerging inserts the given segment into the set and returns true.
+// If it would overlap an existing segment, AddWithoutMerging does nothing and
+// returns false. If AddWithoutMerging succeeds, all existing iterators are
+// invalidated.
+func (s *evictableRangeSet) AddWithoutMerging(r EvictableRange, val evictableRangeSetValue) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.InsertWithoutMergingUnchecked(gap, r, val)
+ return true
+}
+
+// Insert inserts the given segment into the given gap. If the new segment can
+// be merged with adjacent segments, Insert will do so. Insert returns an
+// iterator to the segment containing the inserted value (which may have been
+// merged with other values). All existing iterators (including gap, but not
+// including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid, Insert panics.
+//
+// Insert is semantically equivalent to a InsertWithoutMerging followed by a
+// Merge, but may be more efficient. Note that there is no unchecked variant of
+// Insert since Insert must retrieve and inspect gap's predecessor and
+// successor segments regardless.
+func (s *evictableRangeSet) Insert(gap evictableRangeGapIterator, r EvictableRange, val evictableRangeSetValue) evictableRangeIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ prev, next := gap.PrevSegment(), gap.NextSegment()
+ if prev.Ok() && prev.End() > r.Start {
+ panic(fmt.Sprintf("new segment %v overlaps predecessor %v", r, prev.Range()))
+ }
+ if next.Ok() && next.Start() < r.End {
+ panic(fmt.Sprintf("new segment %v overlaps successor %v", r, next.Range()))
+ }
+ if prev.Ok() && prev.End() == r.Start {
+ if mval, ok := (evictableRangeSetFunctions{}).Merge(prev.Range(), prev.Value(), r, val); ok {
+ prev.SetEndUnchecked(r.End)
+ prev.SetValue(mval)
+ if next.Ok() && next.Start() == r.End {
+ val = mval
+ if mval, ok := (evictableRangeSetFunctions{}).Merge(prev.Range(), val, next.Range(), next.Value()); ok {
+ prev.SetEndUnchecked(next.End())
+ prev.SetValue(mval)
+ return s.Remove(next).PrevSegment()
+ }
+ }
+ return prev
+ }
+ }
+ if next.Ok() && next.Start() == r.End {
+ if mval, ok := (evictableRangeSetFunctions{}).Merge(r, val, next.Range(), next.Value()); ok {
+ next.SetStartUnchecked(r.Start)
+ next.SetValue(mval)
+ return next
+ }
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMerging inserts the given segment into the given gap and
+// returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid,
+// InsertWithoutMerging panics.
+func (s *evictableRangeSet) InsertWithoutMerging(gap evictableRangeGapIterator, r EvictableRange, val evictableRangeSetValue) evictableRangeIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if gr := gap.Range(); !gr.IsSupersetOf(r) {
+ panic(fmt.Sprintf("cannot insert segment range %v into gap range %v", r, gr))
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMergingUnchecked inserts the given segment into the given gap
+// and returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// Preconditions: r.Start >= gap.Start(); r.End <= gap.End().
+func (s *evictableRangeSet) InsertWithoutMergingUnchecked(gap evictableRangeGapIterator, r EvictableRange, val evictableRangeSetValue) evictableRangeIterator {
+ gap = gap.node.rebalanceBeforeInsert(gap)
+ copy(gap.node.keys[gap.index+1:], gap.node.keys[gap.index:gap.node.nrSegments])
+ copy(gap.node.values[gap.index+1:], gap.node.values[gap.index:gap.node.nrSegments])
+ gap.node.keys[gap.index] = r
+ gap.node.values[gap.index] = val
+ gap.node.nrSegments++
+ return evictableRangeIterator{gap.node, gap.index}
+}
+
+// Remove removes the given segment and returns an iterator to the vacated gap.
+// All existing iterators (including seg, but not including the returned
+// iterator) are invalidated.
+func (s *evictableRangeSet) Remove(seg evictableRangeIterator) evictableRangeGapIterator {
+
+ if seg.node.hasChildren {
+
+ victim := seg.PrevSegment()
+
+ seg.SetRangeUnchecked(victim.Range())
+ seg.SetValue(victim.Value())
+ return s.Remove(victim).NextGap()
+ }
+ copy(seg.node.keys[seg.index:], seg.node.keys[seg.index+1:seg.node.nrSegments])
+ copy(seg.node.values[seg.index:], seg.node.values[seg.index+1:seg.node.nrSegments])
+ evictableRangeSetFunctions{}.ClearValue(&seg.node.values[seg.node.nrSegments-1])
+ seg.node.nrSegments--
+ return seg.node.rebalanceAfterRemove(evictableRangeGapIterator{seg.node, seg.index})
+}
+
+// RemoveAll removes all segments from the set. All existing iterators are
+// invalidated.
+func (s *evictableRangeSet) RemoveAll() {
+ s.root = evictableRangenode{}
+}
+
+// RemoveRange removes all segments in the given range. An iterator to the
+// newly formed gap is returned, and all existing iterators are invalidated.
+func (s *evictableRangeSet) RemoveRange(r EvictableRange) evictableRangeGapIterator {
+ seg, gap := s.Find(r.Start)
+ if seg.Ok() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ for seg = gap.NextSegment(); seg.Ok() && seg.Start() < r.End; seg = gap.NextSegment() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ return gap
+}
+
+// Merge attempts to merge two neighboring segments. If successful, Merge
+// returns an iterator to the merged segment, and all existing iterators are
+// invalidated. Otherwise, Merge returns a terminal iterator.
+//
+// If first is not the predecessor of second, Merge panics.
+func (s *evictableRangeSet) Merge(first, second evictableRangeIterator) evictableRangeIterator {
+ if first.NextSegment() != second {
+ panic(fmt.Sprintf("attempt to merge non-neighboring segments %v, %v", first.Range(), second.Range()))
+ }
+ return s.MergeUnchecked(first, second)
+}
+
+// MergeUnchecked attempts to merge two neighboring segments. If successful,
+// MergeUnchecked returns an iterator to the merged segment, and all existing
+// iterators are invalidated. Otherwise, MergeUnchecked returns a terminal
+// iterator.
+//
+// Precondition: first is the predecessor of second: first.NextSegment() ==
+// second, first == second.PrevSegment().
+func (s *evictableRangeSet) MergeUnchecked(first, second evictableRangeIterator) evictableRangeIterator {
+ if first.End() == second.Start() {
+ if mval, ok := (evictableRangeSetFunctions{}).Merge(first.Range(), first.Value(), second.Range(), second.Value()); ok {
+
+ first.SetEndUnchecked(second.End())
+ first.SetValue(mval)
+ return s.Remove(second).PrevSegment()
+ }
+ }
+ return evictableRangeIterator{}
+}
+
+// MergeAll attempts to merge all adjacent segments in the set. All existing
+// iterators are invalidated.
+func (s *evictableRangeSet) MergeAll() {
+ seg := s.FirstSegment()
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeRange attempts to merge all adjacent segments that contain a key in the
+// specific range. All existing iterators are invalidated.
+func (s *evictableRangeSet) MergeRange(r EvictableRange) {
+ seg := s.LowerBoundSegment(r.Start)
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() && next.Range().Start < r.End {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeAdjacent attempts to merge the segment containing r.Start with its
+// predecessor, and the segment containing r.End-1 with its successor.
+func (s *evictableRangeSet) MergeAdjacent(r EvictableRange) {
+ first := s.FindSegment(r.Start)
+ if first.Ok() {
+ if prev := first.PrevSegment(); prev.Ok() {
+ s.Merge(prev, first)
+ }
+ }
+ last := s.FindSegment(r.End - 1)
+ if last.Ok() {
+ if next := last.NextSegment(); next.Ok() {
+ s.Merge(last, next)
+ }
+ }
+}
+
+// Split splits the given segment at the given key and returns iterators to the
+// two resulting segments. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+//
+// If the segment cannot be split at split (because split is at the start or
+// end of the segment's range, so splitting would produce a segment with zero
+// length, or because split falls outside the segment's range altogether),
+// Split panics.
+func (s *evictableRangeSet) Split(seg evictableRangeIterator, split uint64) (evictableRangeIterator, evictableRangeIterator) {
+ if !seg.Range().CanSplitAt(split) {
+ panic(fmt.Sprintf("can't split %v at %v", seg.Range(), split))
+ }
+ return s.SplitUnchecked(seg, split)
+}
+
+// SplitUnchecked splits the given segment at the given key and returns
+// iterators to the two resulting segments. All existing iterators (including
+// seg, but not including the returned iterators) are invalidated.
+//
+// Preconditions: seg.Start() < key < seg.End().
+func (s *evictableRangeSet) SplitUnchecked(seg evictableRangeIterator, split uint64) (evictableRangeIterator, evictableRangeIterator) {
+ val1, val2 := (evictableRangeSetFunctions{}).Split(seg.Range(), seg.Value(), split)
+ end2 := seg.End()
+ seg.SetEndUnchecked(split)
+ seg.SetValue(val1)
+ seg2 := s.InsertWithoutMergingUnchecked(seg.NextGap(), EvictableRange{split, end2}, val2)
+
+ return seg2.PrevSegment(), seg2
+}
+
+// SplitAt splits the segment straddling split, if one exists. SplitAt returns
+// true if a segment was split and false otherwise. If SplitAt splits a
+// segment, all existing iterators are invalidated.
+func (s *evictableRangeSet) SplitAt(split uint64) bool {
+ if seg := s.FindSegment(split); seg.Ok() && seg.Range().CanSplitAt(split) {
+ s.SplitUnchecked(seg, split)
+ return true
+ }
+ return false
+}
+
+// Isolate ensures that the given segment's range does not escape r by
+// splitting at r.Start and r.End if necessary, and returns an updated iterator
+// to the bounded segment. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+func (s *evictableRangeSet) Isolate(seg evictableRangeIterator, r EvictableRange) evictableRangeIterator {
+ if seg.Range().CanSplitAt(r.Start) {
+ _, seg = s.SplitUnchecked(seg, r.Start)
+ }
+ if seg.Range().CanSplitAt(r.End) {
+ seg, _ = s.SplitUnchecked(seg, r.End)
+ }
+ return seg
+}
+
+// ApplyContiguous applies a function to a contiguous range of segments,
+// splitting if necessary. The function is applied until the first gap is
+// encountered, at which point the gap is returned. If the function is applied
+// across the entire range, a terminal gap is returned. All existing iterators
+// are invalidated.
+//
+// N.B. The Iterator must not be invalidated by the function.
+func (s *evictableRangeSet) ApplyContiguous(r EvictableRange, fn func(seg evictableRangeIterator)) evictableRangeGapIterator {
+ seg, gap := s.Find(r.Start)
+ if !seg.Ok() {
+ return gap
+ }
+ for {
+ seg = s.Isolate(seg, r)
+ fn(seg)
+ if seg.End() >= r.End {
+ return evictableRangeGapIterator{}
+ }
+ gap = seg.NextGap()
+ if !gap.IsEmpty() {
+ return gap
+ }
+ seg = gap.NextSegment()
+ if !seg.Ok() {
+
+ return evictableRangeGapIterator{}
+ }
+ }
+}
+
+// +stateify savable
+type evictableRangenode struct {
+ // An internal binary tree node looks like:
+ //
+ // K
+ // / \
+ // Cl Cr
+ //
+ // where all keys in the subtree rooted by Cl (the left subtree) are less
+ // than K (the key of the parent node), and all keys in the subtree rooted
+ // by Cr (the right subtree) are greater than K.
+ //
+ // An internal B-tree node's indexes work out to look like:
+ //
+ // K0 K1 K2 ... Kn-1
+ // / \/ \/ \ ... / \
+ // C0 C1 C2 C3 ... Cn-1 Cn
+ //
+ // where n is nrSegments.
+ nrSegments int
+
+ // parent is a pointer to this node's parent. If this node is root, parent
+ // is nil.
+ parent *evictableRangenode
+
+ // parentIndex is the index of this node in parent.children.
+ parentIndex int
+
+ // Flag for internal nodes that is technically redundant with "children[0]
+ // != nil", but is stored in the first cache line. "hasChildren" rather
+ // than "isLeaf" because false must be the correct value for an empty root.
+ hasChildren bool
+
+ // Nodes store keys and values in separate arrays to maximize locality in
+ // the common case (scanning keys for lookup).
+ keys [evictableRangemaxDegree - 1]EvictableRange
+ values [evictableRangemaxDegree - 1]evictableRangeSetValue
+ children [evictableRangemaxDegree]*evictableRangenode
+}
+
+// firstSegment returns the first segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *evictableRangenode) firstSegment() evictableRangeIterator {
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return evictableRangeIterator{n, 0}
+}
+
+// lastSegment returns the last segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *evictableRangenode) lastSegment() evictableRangeIterator {
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return evictableRangeIterator{n, n.nrSegments - 1}
+}
+
+func (n *evictableRangenode) prevSibling() *evictableRangenode {
+ if n.parent == nil || n.parentIndex == 0 {
+ return nil
+ }
+ return n.parent.children[n.parentIndex-1]
+}
+
+func (n *evictableRangenode) nextSibling() *evictableRangenode {
+ if n.parent == nil || n.parentIndex == n.parent.nrSegments {
+ return nil
+ }
+ return n.parent.children[n.parentIndex+1]
+}
+
+// rebalanceBeforeInsert splits n and its ancestors if they are full, as
+// required for insertion, and returns an updated iterator to the position
+// represented by gap.
+func (n *evictableRangenode) rebalanceBeforeInsert(gap evictableRangeGapIterator) evictableRangeGapIterator {
+ if n.parent != nil {
+ gap = n.parent.rebalanceBeforeInsert(gap)
+ }
+ if n.nrSegments < evictableRangemaxDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ left := &evictableRangenode{
+ nrSegments: evictableRangeminDegree - 1,
+ parent: n,
+ parentIndex: 0,
+ hasChildren: n.hasChildren,
+ }
+ right := &evictableRangenode{
+ nrSegments: evictableRangeminDegree - 1,
+ parent: n,
+ parentIndex: 1,
+ hasChildren: n.hasChildren,
+ }
+ copy(left.keys[:evictableRangeminDegree-1], n.keys[:evictableRangeminDegree-1])
+ copy(left.values[:evictableRangeminDegree-1], n.values[:evictableRangeminDegree-1])
+ copy(right.keys[:evictableRangeminDegree-1], n.keys[evictableRangeminDegree:])
+ copy(right.values[:evictableRangeminDegree-1], n.values[evictableRangeminDegree:])
+ n.keys[0], n.values[0] = n.keys[evictableRangeminDegree-1], n.values[evictableRangeminDegree-1]
+ evictableRangezeroValueSlice(n.values[1:])
+ if n.hasChildren {
+ copy(left.children[:evictableRangeminDegree], n.children[:evictableRangeminDegree])
+ copy(right.children[:evictableRangeminDegree], n.children[evictableRangeminDegree:])
+ evictableRangezeroNodeSlice(n.children[2:])
+ for i := 0; i < evictableRangeminDegree; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ right.children[i].parent = right
+ right.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = 1
+ n.hasChildren = true
+ n.children[0] = left
+ n.children[1] = right
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < evictableRangeminDegree {
+ return evictableRangeGapIterator{left, gap.index}
+ }
+ return evictableRangeGapIterator{right, gap.index - evictableRangeminDegree}
+ }
+
+ copy(n.parent.keys[n.parentIndex+1:], n.parent.keys[n.parentIndex:n.parent.nrSegments])
+ copy(n.parent.values[n.parentIndex+1:], n.parent.values[n.parentIndex:n.parent.nrSegments])
+ n.parent.keys[n.parentIndex], n.parent.values[n.parentIndex] = n.keys[evictableRangeminDegree-1], n.values[evictableRangeminDegree-1]
+ copy(n.parent.children[n.parentIndex+2:], n.parent.children[n.parentIndex+1:n.parent.nrSegments+1])
+ for i := n.parentIndex + 2; i < n.parent.nrSegments+2; i++ {
+ n.parent.children[i].parentIndex = i
+ }
+ sibling := &evictableRangenode{
+ nrSegments: evictableRangeminDegree - 1,
+ parent: n.parent,
+ parentIndex: n.parentIndex + 1,
+ hasChildren: n.hasChildren,
+ }
+ n.parent.children[n.parentIndex+1] = sibling
+ n.parent.nrSegments++
+ copy(sibling.keys[:evictableRangeminDegree-1], n.keys[evictableRangeminDegree:])
+ copy(sibling.values[:evictableRangeminDegree-1], n.values[evictableRangeminDegree:])
+ evictableRangezeroValueSlice(n.values[evictableRangeminDegree-1:])
+ if n.hasChildren {
+ copy(sibling.children[:evictableRangeminDegree], n.children[evictableRangeminDegree:])
+ evictableRangezeroNodeSlice(n.children[evictableRangeminDegree:])
+ for i := 0; i < evictableRangeminDegree; i++ {
+ sibling.children[i].parent = sibling
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = evictableRangeminDegree - 1
+
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < evictableRangeminDegree {
+ return gap
+ }
+ return evictableRangeGapIterator{sibling, gap.index - evictableRangeminDegree}
+}
+
+// rebalanceAfterRemove "unsplits" n and its ancestors if they are deficient
+// (contain fewer segments than required by B-tree invariants), as required for
+// removal, and returns an updated iterator to the position represented by gap.
+//
+// Precondition: n is the only node in the tree that may currently violate a
+// B-tree invariant.
+func (n *evictableRangenode) rebalanceAfterRemove(gap evictableRangeGapIterator) evictableRangeGapIterator {
+ for {
+ if n.nrSegments >= evictableRangeminDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ return gap
+ }
+
+ if sibling := n.prevSibling(); sibling != nil && sibling.nrSegments >= evictableRangeminDegree {
+ copy(n.keys[1:], n.keys[:n.nrSegments])
+ copy(n.values[1:], n.values[:n.nrSegments])
+ n.keys[0] = n.parent.keys[n.parentIndex-1]
+ n.values[0] = n.parent.values[n.parentIndex-1]
+ n.parent.keys[n.parentIndex-1] = sibling.keys[sibling.nrSegments-1]
+ n.parent.values[n.parentIndex-1] = sibling.values[sibling.nrSegments-1]
+ evictableRangeSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ copy(n.children[1:], n.children[:n.nrSegments+1])
+ n.children[0] = sibling.children[sibling.nrSegments]
+ sibling.children[sibling.nrSegments] = nil
+ n.children[0].parent = n
+ n.children[0].parentIndex = 0
+ for i := 1; i < n.nrSegments+2; i++ {
+ n.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling && gap.index == sibling.nrSegments {
+ return evictableRangeGapIterator{n, 0}
+ }
+ if gap.node == n {
+ return evictableRangeGapIterator{n, gap.index + 1}
+ }
+ return gap
+ }
+ if sibling := n.nextSibling(); sibling != nil && sibling.nrSegments >= evictableRangeminDegree {
+ n.keys[n.nrSegments] = n.parent.keys[n.parentIndex]
+ n.values[n.nrSegments] = n.parent.values[n.parentIndex]
+ n.parent.keys[n.parentIndex] = sibling.keys[0]
+ n.parent.values[n.parentIndex] = sibling.values[0]
+ copy(sibling.keys[:sibling.nrSegments-1], sibling.keys[1:])
+ copy(sibling.values[:sibling.nrSegments-1], sibling.values[1:])
+ evictableRangeSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ n.children[n.nrSegments+1] = sibling.children[0]
+ copy(sibling.children[:sibling.nrSegments], sibling.children[1:])
+ sibling.children[sibling.nrSegments] = nil
+ n.children[n.nrSegments+1].parent = n
+ n.children[n.nrSegments+1].parentIndex = n.nrSegments + 1
+ for i := 0; i < sibling.nrSegments; i++ {
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling {
+ if gap.index == 0 {
+ return evictableRangeGapIterator{n, n.nrSegments}
+ }
+ return evictableRangeGapIterator{sibling, gap.index - 1}
+ }
+ return gap
+ }
+
+ p := n.parent
+ if p.nrSegments == 1 {
+
+ left, right := p.children[0], p.children[1]
+ p.nrSegments = left.nrSegments + right.nrSegments + 1
+ p.hasChildren = left.hasChildren
+ p.keys[left.nrSegments] = p.keys[0]
+ p.values[left.nrSegments] = p.values[0]
+ copy(p.keys[:left.nrSegments], left.keys[:left.nrSegments])
+ copy(p.values[:left.nrSegments], left.values[:left.nrSegments])
+ copy(p.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(p.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(p.children[:left.nrSegments+1], left.children[:left.nrSegments+1])
+ copy(p.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := 0; i < p.nrSegments+1; i++ {
+ p.children[i].parent = p
+ p.children[i].parentIndex = i
+ }
+ } else {
+ p.children[0] = nil
+ p.children[1] = nil
+ }
+ if gap.node == left {
+ return evictableRangeGapIterator{p, gap.index}
+ }
+ if gap.node == right {
+ return evictableRangeGapIterator{p, gap.index + left.nrSegments + 1}
+ }
+ return gap
+ }
+ // Merge n and either sibling, along with the segment separating the
+ // two, into whichever of the two nodes comes first. This is the
+ // reverse of the non-root splitting case in
+ // node.rebalanceBeforeInsert.
+ var left, right *evictableRangenode
+ if n.parentIndex > 0 {
+ left = n.prevSibling()
+ right = n
+ } else {
+ left = n
+ right = n.nextSibling()
+ }
+
+ if gap.node == right {
+ gap = evictableRangeGapIterator{left, gap.index + left.nrSegments + 1}
+ }
+ left.keys[left.nrSegments] = p.keys[left.parentIndex]
+ left.values[left.nrSegments] = p.values[left.parentIndex]
+ copy(left.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(left.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(left.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := left.nrSegments + 1; i < left.nrSegments+right.nrSegments+2; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ }
+ }
+ left.nrSegments += right.nrSegments + 1
+ copy(p.keys[left.parentIndex:], p.keys[left.parentIndex+1:p.nrSegments])
+ copy(p.values[left.parentIndex:], p.values[left.parentIndex+1:p.nrSegments])
+ evictableRangeSetFunctions{}.ClearValue(&p.values[p.nrSegments-1])
+ copy(p.children[left.parentIndex+1:], p.children[left.parentIndex+2:p.nrSegments+1])
+ for i := 0; i < p.nrSegments; i++ {
+ p.children[i].parentIndex = i
+ }
+ p.children[p.nrSegments] = nil
+ p.nrSegments--
+
+ n = p
+ }
+}
+
+// A Iterator is conceptually one of:
+//
+// - A pointer to a segment in a set; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Iterators are copyable values and are meaningfully equality-comparable. The
+// zero value of Iterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type evictableRangeIterator struct {
+ // node is the node containing the iterated segment. If the iterator is
+ // terminal, node is nil.
+ node *evictableRangenode
+
+ // index is the index of the segment in node.keys/values.
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (seg evictableRangeIterator) Ok() bool {
+ return seg.node != nil
+}
+
+// Range returns the iterated segment's range key.
+func (seg evictableRangeIterator) Range() EvictableRange {
+ return seg.node.keys[seg.index]
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (seg evictableRangeIterator) Start() uint64 {
+ return seg.node.keys[seg.index].Start
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (seg evictableRangeIterator) End() uint64 {
+ return seg.node.keys[seg.index].End
+}
+
+// SetRangeUnchecked mutates the iterated segment's range key. This operation
+// does not invalidate any iterators.
+//
+// Preconditions:
+//
+// - r.Length() > 0.
+//
+// - The new range must not overlap an existing one: If seg.NextSegment().Ok(),
+// then r.end <= seg.NextSegment().Start(); if seg.PrevSegment().Ok(), then
+// r.start >= seg.PrevSegment().End().
+func (seg evictableRangeIterator) SetRangeUnchecked(r EvictableRange) {
+ seg.node.keys[seg.index] = r
+}
+
+// SetRange mutates the iterated segment's range key. If the new range would
+// cause the iterated segment to overlap another segment, or if the new range
+// is invalid, SetRange panics. This operation does not invalidate any
+// iterators.
+func (seg evictableRangeIterator) SetRange(r EvictableRange) {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && r.Start < prev.End() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, prev.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && r.End > next.Start() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, next.Range()))
+ }
+ seg.SetRangeUnchecked(r)
+}
+
+// SetStartUnchecked mutates the iterated segment's start. This operation does
+// not invalidate any iterators.
+//
+// Preconditions: The new start must be valid: start < seg.End(); if
+// seg.PrevSegment().Ok(), then start >= seg.PrevSegment().End().
+func (seg evictableRangeIterator) SetStartUnchecked(start uint64) {
+ seg.node.keys[seg.index].Start = start
+}
+
+// SetStart mutates the iterated segment's start. If the new start value would
+// cause the iterated segment to overlap another segment, or would result in an
+// invalid range, SetStart panics. This operation does not invalidate any
+// iterators.
+func (seg evictableRangeIterator) SetStart(start uint64) {
+ if start >= seg.End() {
+ panic(fmt.Sprintf("new start %v would invalidate segment range %v", start, seg.Range()))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && start < prev.End() {
+ panic(fmt.Sprintf("new start %v would cause segment range %v to overlap segment range %v", start, seg.Range(), prev.Range()))
+ }
+ seg.SetStartUnchecked(start)
+}
+
+// SetEndUnchecked mutates the iterated segment's end. This operation does not
+// invalidate any iterators.
+//
+// Preconditions: The new end must be valid: end > seg.Start(); if
+// seg.NextSegment().Ok(), then end <= seg.NextSegment().Start().
+func (seg evictableRangeIterator) SetEndUnchecked(end uint64) {
+ seg.node.keys[seg.index].End = end
+}
+
+// SetEnd mutates the iterated segment's end. If the new end value would cause
+// the iterated segment to overlap another segment, or would result in an
+// invalid range, SetEnd panics. This operation does not invalidate any
+// iterators.
+func (seg evictableRangeIterator) SetEnd(end uint64) {
+ if end <= seg.Start() {
+ panic(fmt.Sprintf("new end %v would invalidate segment range %v", end, seg.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && end > next.Start() {
+ panic(fmt.Sprintf("new end %v would cause segment range %v to overlap segment range %v", end, seg.Range(), next.Range()))
+ }
+ seg.SetEndUnchecked(end)
+}
+
+// Value returns a copy of the iterated segment's value.
+func (seg evictableRangeIterator) Value() evictableRangeSetValue {
+ return seg.node.values[seg.index]
+}
+
+// ValuePtr returns a pointer to the iterated segment's value. The pointer is
+// invalidated if the iterator is invalidated. This operation does not
+// invalidate any iterators.
+func (seg evictableRangeIterator) ValuePtr() *evictableRangeSetValue {
+ return &seg.node.values[seg.index]
+}
+
+// SetValue mutates the iterated segment's value. This operation does not
+// invalidate any iterators.
+func (seg evictableRangeIterator) SetValue(val evictableRangeSetValue) {
+ seg.node.values[seg.index] = val
+}
+
+// PrevSegment returns the iterated segment's predecessor. If there is no
+// preceding segment, PrevSegment returns a terminal iterator.
+func (seg evictableRangeIterator) PrevSegment() evictableRangeIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index].lastSegment()
+ }
+ if seg.index > 0 {
+ return evictableRangeIterator{seg.node, seg.index - 1}
+ }
+ if seg.node.parent == nil {
+ return evictableRangeIterator{}
+ }
+ return evictableRangesegmentBeforePosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// NextSegment returns the iterated segment's successor. If there is no
+// succeeding segment, NextSegment returns a terminal iterator.
+func (seg evictableRangeIterator) NextSegment() evictableRangeIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment()
+ }
+ if seg.index < seg.node.nrSegments-1 {
+ return evictableRangeIterator{seg.node, seg.index + 1}
+ }
+ if seg.node.parent == nil {
+ return evictableRangeIterator{}
+ }
+ return evictableRangesegmentAfterPosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// PrevGap returns the gap immediately before the iterated segment.
+func (seg evictableRangeIterator) PrevGap() evictableRangeGapIterator {
+ if seg.node.hasChildren {
+
+ return seg.node.children[seg.index].lastSegment().NextGap()
+ }
+ return evictableRangeGapIterator{seg.node, seg.index}
+}
+
+// NextGap returns the gap immediately after the iterated segment.
+func (seg evictableRangeIterator) NextGap() evictableRangeGapIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment().PrevGap()
+ }
+ return evictableRangeGapIterator{seg.node, seg.index + 1}
+}
+
+// PrevNonEmpty returns the iterated segment's predecessor if it is adjacent,
+// or the gap before the iterated segment otherwise. If seg.Start() ==
+// Functions.MinKey(), PrevNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by PrevNonEmpty will be
+// non-terminal.
+func (seg evictableRangeIterator) PrevNonEmpty() (evictableRangeIterator, evictableRangeGapIterator) {
+ gap := seg.PrevGap()
+ if gap.Range().Length() != 0 {
+ return evictableRangeIterator{}, gap
+ }
+ return gap.PrevSegment(), evictableRangeGapIterator{}
+}
+
+// NextNonEmpty returns the iterated segment's successor if it is adjacent, or
+// the gap after the iterated segment otherwise. If seg.End() ==
+// Functions.MaxKey(), NextNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by NextNonEmpty will be
+// non-terminal.
+func (seg evictableRangeIterator) NextNonEmpty() (evictableRangeIterator, evictableRangeGapIterator) {
+ gap := seg.NextGap()
+ if gap.Range().Length() != 0 {
+ return evictableRangeIterator{}, gap
+ }
+ return gap.NextSegment(), evictableRangeGapIterator{}
+}
+
+// A GapIterator is conceptually one of:
+//
+// - A pointer to a position between two segments, before the first segment, or
+// after the last segment in a set, called a *gap*; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Note that the gap between two adjacent segments exists (iterators to it are
+// non-terminal), but has a length of zero. GapIterator.IsEmpty returns true
+// for such gaps. An empty set contains a single gap, spanning the entire range
+// of the set's keys.
+//
+// GapIterators are copyable values and are meaningfully equality-comparable.
+// The zero value of GapIterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type evictableRangeGapIterator struct {
+ // The representation of a GapIterator is identical to that of an Iterator,
+ // except that index corresponds to positions between segments in the same
+ // way as for node.children (see comment for node.nrSegments).
+ node *evictableRangenode
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (gap evictableRangeGapIterator) Ok() bool {
+ return gap.node != nil
+}
+
+// Range returns the range spanned by the iterated gap.
+func (gap evictableRangeGapIterator) Range() EvictableRange {
+ return EvictableRange{gap.Start(), gap.End()}
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (gap evictableRangeGapIterator) Start() uint64 {
+ if ps := gap.PrevSegment(); ps.Ok() {
+ return ps.End()
+ }
+ return evictableRangeSetFunctions{}.MinKey()
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (gap evictableRangeGapIterator) End() uint64 {
+ if ns := gap.NextSegment(); ns.Ok() {
+ return ns.Start()
+ }
+ return evictableRangeSetFunctions{}.MaxKey()
+}
+
+// IsEmpty returns true if the iterated gap is empty (that is, the "gap" is
+// between two adjacent segments.)
+func (gap evictableRangeGapIterator) IsEmpty() bool {
+ return gap.Range().Length() == 0
+}
+
+// PrevSegment returns the segment immediately before the iterated gap. If no
+// such segment exists, PrevSegment returns a terminal iterator.
+func (gap evictableRangeGapIterator) PrevSegment() evictableRangeIterator {
+ return evictableRangesegmentBeforePosition(gap.node, gap.index)
+}
+
+// NextSegment returns the segment immediately after the iterated gap. If no
+// such segment exists, NextSegment returns a terminal iterator.
+func (gap evictableRangeGapIterator) NextSegment() evictableRangeIterator {
+ return evictableRangesegmentAfterPosition(gap.node, gap.index)
+}
+
+// PrevGap returns the iterated gap's predecessor. If no such gap exists,
+// PrevGap returns a terminal iterator.
+func (gap evictableRangeGapIterator) PrevGap() evictableRangeGapIterator {
+ seg := gap.PrevSegment()
+ if !seg.Ok() {
+ return evictableRangeGapIterator{}
+ }
+ return seg.PrevGap()
+}
+
+// NextGap returns the iterated gap's successor. If no such gap exists, NextGap
+// returns a terminal iterator.
+func (gap evictableRangeGapIterator) NextGap() evictableRangeGapIterator {
+ seg := gap.NextSegment()
+ if !seg.Ok() {
+ return evictableRangeGapIterator{}
+ }
+ return seg.NextGap()
+}
+
+// segmentBeforePosition returns the predecessor segment of the position given
+// by n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentBeforePosition returns a terminal iterator.
+func evictableRangesegmentBeforePosition(n *evictableRangenode, i int) evictableRangeIterator {
+ for i == 0 {
+ if n.parent == nil {
+ return evictableRangeIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return evictableRangeIterator{n, i - 1}
+}
+
+// segmentAfterPosition returns the successor segment of the position given by
+// n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentAfterPosition returns a terminal iterator.
+func evictableRangesegmentAfterPosition(n *evictableRangenode, i int) evictableRangeIterator {
+ for i == n.nrSegments {
+ if n.parent == nil {
+ return evictableRangeIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return evictableRangeIterator{n, i}
+}
+
+func evictableRangezeroValueSlice(slice []evictableRangeSetValue) {
+
+ for i := range slice {
+ evictableRangeSetFunctions{}.ClearValue(&slice[i])
+ }
+}
+
+func evictableRangezeroNodeSlice(slice []*evictableRangenode) {
+ for i := range slice {
+ slice[i] = nil
+ }
+}
+
+// String stringifies a Set for debugging.
+func (s *evictableRangeSet) String() string {
+ return s.root.String()
+}
+
+// String stringifies a node (and all of its children) for debugging.
+func (n *evictableRangenode) String() string {
+ var buf bytes.Buffer
+ n.writeDebugString(&buf, "")
+ return buf.String()
+}
+
+func (n *evictableRangenode) writeDebugString(buf *bytes.Buffer, prefix string) {
+ if n.hasChildren != (n.nrSegments > 0 && n.children[0] != nil) {
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent value of hasChildren: got %v, want %v\n", n.hasChildren, !n.hasChildren))
+ }
+ for i := 0; i < n.nrSegments; i++ {
+ if child := n.children[i]; child != nil {
+ cprefix := fmt.Sprintf("%s- % 3d ", prefix, i)
+ if child.parent != n || child.parentIndex != i {
+ buf.WriteString(cprefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent linkage to parent: got (%p, %d), want (%p, %d)\n", child.parent, child.parentIndex, n, i))
+ }
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, i))
+ }
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("- % 3d: %v => %v\n", i, n.keys[i], n.values[i]))
+ }
+ if child := n.children[n.nrSegments]; child != nil {
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, n.nrSegments))
+ }
+}
+
+// SegmentDataSlices represents segments from a set as slices of start, end, and
+// values. SegmentDataSlices is primarily used as an intermediate representation
+// for save/restore and the layout here is optimized for that.
+//
+// +stateify savable
+type evictableRangeSegmentDataSlices struct {
+ Start []uint64
+ End []uint64
+ Values []evictableRangeSetValue
+}
+
+// ExportSortedSlice returns a copy of all segments in the given set, in ascending
+// key order.
+func (s *evictableRangeSet) ExportSortedSlices() *evictableRangeSegmentDataSlices {
+ var sds evictableRangeSegmentDataSlices
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sds.Start = append(sds.Start, seg.Start())
+ sds.End = append(sds.End, seg.End())
+ sds.Values = append(sds.Values, seg.Value())
+ }
+ sds.Start = sds.Start[:len(sds.Start):len(sds.Start)]
+ sds.End = sds.End[:len(sds.End):len(sds.End)]
+ sds.Values = sds.Values[:len(sds.Values):len(sds.Values)]
+ return &sds
+}
+
+// ImportSortedSlice initializes the given set from the given slice.
+//
+// Preconditions: s must be empty. sds must represent a valid set (the segments
+// in sds must have valid lengths that do not overlap). The segments in sds
+// must be sorted in ascending key order.
+func (s *evictableRangeSet) ImportSortedSlices(sds *evictableRangeSegmentDataSlices) error {
+ if !s.IsEmpty() {
+ return fmt.Errorf("cannot import into non-empty set %v", s)
+ }
+ gap := s.FirstGap()
+ for i := range sds.Start {
+ r := EvictableRange{sds.Start[i], sds.End[i]}
+ if !gap.Range().IsSupersetOf(r) {
+ return fmt.Errorf("segment overlaps a preceding segment or is incorrectly sorted: [%d, %d) => %v", sds.Start[i], sds.End[i], sds.Values[i])
+ }
+ gap = s.InsertWithoutMerging(gap, r, sds.Values[i]).NextGap()
+ }
+ return nil
+}
+func (s *evictableRangeSet) saveRoot() *evictableRangeSegmentDataSlices {
+ return s.ExportSortedSlices()
+}
+
+func (s *evictableRangeSet) loadRoot(sds *evictableRangeSegmentDataSlices) {
+ if err := s.ImportSortedSlices(sds); err != nil {
+ panic(err)
+ }
+}
diff --git a/pkg/sentry/pgalloc/pgalloc_state_autogen.go b/pkg/sentry/pgalloc/pgalloc_state_autogen.go
new file mode 100755
index 000000000..1cfacc241
--- /dev/null
+++ b/pkg/sentry/pgalloc/pgalloc_state_autogen.go
@@ -0,0 +1,146 @@
+// automatically generated by stateify.
+
+package pgalloc
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *EvictableRange) beforeSave() {}
+func (x *EvictableRange) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Start", &x.Start)
+ m.Save("End", &x.End)
+}
+
+func (x *EvictableRange) afterLoad() {}
+func (x *EvictableRange) load(m state.Map) {
+ m.Load("Start", &x.Start)
+ m.Load("End", &x.End)
+}
+
+func (x *evictableRangeSet) beforeSave() {}
+func (x *evictableRangeSet) save(m state.Map) {
+ x.beforeSave()
+ var root *evictableRangeSegmentDataSlices = x.saveRoot()
+ m.SaveValue("root", root)
+}
+
+func (x *evictableRangeSet) afterLoad() {}
+func (x *evictableRangeSet) load(m state.Map) {
+ m.LoadValue("root", new(*evictableRangeSegmentDataSlices), func(y interface{}) { x.loadRoot(y.(*evictableRangeSegmentDataSlices)) })
+}
+
+func (x *evictableRangenode) beforeSave() {}
+func (x *evictableRangenode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("nrSegments", &x.nrSegments)
+ m.Save("parent", &x.parent)
+ m.Save("parentIndex", &x.parentIndex)
+ m.Save("hasChildren", &x.hasChildren)
+ m.Save("keys", &x.keys)
+ m.Save("values", &x.values)
+ m.Save("children", &x.children)
+}
+
+func (x *evictableRangenode) afterLoad() {}
+func (x *evictableRangenode) load(m state.Map) {
+ m.Load("nrSegments", &x.nrSegments)
+ m.Load("parent", &x.parent)
+ m.Load("parentIndex", &x.parentIndex)
+ m.Load("hasChildren", &x.hasChildren)
+ m.Load("keys", &x.keys)
+ m.Load("values", &x.values)
+ m.Load("children", &x.children)
+}
+
+func (x *evictableRangeSegmentDataSlices) beforeSave() {}
+func (x *evictableRangeSegmentDataSlices) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Start", &x.Start)
+ m.Save("End", &x.End)
+ m.Save("Values", &x.Values)
+}
+
+func (x *evictableRangeSegmentDataSlices) afterLoad() {}
+func (x *evictableRangeSegmentDataSlices) load(m state.Map) {
+ m.Load("Start", &x.Start)
+ m.Load("End", &x.End)
+ m.Load("Values", &x.Values)
+}
+
+func (x *usageInfo) beforeSave() {}
+func (x *usageInfo) save(m state.Map) {
+ x.beforeSave()
+ m.Save("kind", &x.kind)
+ m.Save("knownCommitted", &x.knownCommitted)
+ m.Save("refs", &x.refs)
+}
+
+func (x *usageInfo) afterLoad() {}
+func (x *usageInfo) load(m state.Map) {
+ m.Load("kind", &x.kind)
+ m.Load("knownCommitted", &x.knownCommitted)
+ m.Load("refs", &x.refs)
+}
+
+func (x *usageSet) beforeSave() {}
+func (x *usageSet) save(m state.Map) {
+ x.beforeSave()
+ var root *usageSegmentDataSlices = x.saveRoot()
+ m.SaveValue("root", root)
+}
+
+func (x *usageSet) afterLoad() {}
+func (x *usageSet) load(m state.Map) {
+ m.LoadValue("root", new(*usageSegmentDataSlices), func(y interface{}) { x.loadRoot(y.(*usageSegmentDataSlices)) })
+}
+
+func (x *usagenode) beforeSave() {}
+func (x *usagenode) save(m state.Map) {
+ x.beforeSave()
+ m.Save("nrSegments", &x.nrSegments)
+ m.Save("parent", &x.parent)
+ m.Save("parentIndex", &x.parentIndex)
+ m.Save("hasChildren", &x.hasChildren)
+ m.Save("keys", &x.keys)
+ m.Save("values", &x.values)
+ m.Save("children", &x.children)
+}
+
+func (x *usagenode) afterLoad() {}
+func (x *usagenode) load(m state.Map) {
+ m.Load("nrSegments", &x.nrSegments)
+ m.Load("parent", &x.parent)
+ m.Load("parentIndex", &x.parentIndex)
+ m.Load("hasChildren", &x.hasChildren)
+ m.Load("keys", &x.keys)
+ m.Load("values", &x.values)
+ m.Load("children", &x.children)
+}
+
+func (x *usageSegmentDataSlices) beforeSave() {}
+func (x *usageSegmentDataSlices) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Start", &x.Start)
+ m.Save("End", &x.End)
+ m.Save("Values", &x.Values)
+}
+
+func (x *usageSegmentDataSlices) afterLoad() {}
+func (x *usageSegmentDataSlices) load(m state.Map) {
+ m.Load("Start", &x.Start)
+ m.Load("End", &x.End)
+ m.Load("Values", &x.Values)
+}
+
+func init() {
+ state.Register("pgalloc.EvictableRange", (*EvictableRange)(nil), state.Fns{Save: (*EvictableRange).save, Load: (*EvictableRange).load})
+ state.Register("pgalloc.evictableRangeSet", (*evictableRangeSet)(nil), state.Fns{Save: (*evictableRangeSet).save, Load: (*evictableRangeSet).load})
+ state.Register("pgalloc.evictableRangenode", (*evictableRangenode)(nil), state.Fns{Save: (*evictableRangenode).save, Load: (*evictableRangenode).load})
+ state.Register("pgalloc.evictableRangeSegmentDataSlices", (*evictableRangeSegmentDataSlices)(nil), state.Fns{Save: (*evictableRangeSegmentDataSlices).save, Load: (*evictableRangeSegmentDataSlices).load})
+ state.Register("pgalloc.usageInfo", (*usageInfo)(nil), state.Fns{Save: (*usageInfo).save, Load: (*usageInfo).load})
+ state.Register("pgalloc.usageSet", (*usageSet)(nil), state.Fns{Save: (*usageSet).save, Load: (*usageSet).load})
+ state.Register("pgalloc.usagenode", (*usagenode)(nil), state.Fns{Save: (*usagenode).save, Load: (*usagenode).load})
+ state.Register("pgalloc.usageSegmentDataSlices", (*usageSegmentDataSlices)(nil), state.Fns{Save: (*usageSegmentDataSlices).save, Load: (*usageSegmentDataSlices).load})
+}
diff --git a/pkg/sentry/pgalloc/pgalloc_test.go b/pkg/sentry/pgalloc/pgalloc_test.go
deleted file mode 100644
index 428e6a859..000000000
--- a/pkg/sentry/pgalloc/pgalloc_test.go
+++ /dev/null
@@ -1,168 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package pgalloc
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/usermem"
-)
-
-const (
- page = usermem.PageSize
- hugepage = usermem.HugePageSize
-)
-
-func TestFindUnallocatedRange(t *testing.T) {
- for _, test := range []struct {
- desc string
- usage *usageSegmentDataSlices
- start uint64
- length uint64
- alignment uint64
- unallocated uint64
- minUnallocated uint64
- }{
- {
- desc: "Initial allocation succeeds",
- usage: &usageSegmentDataSlices{},
- start: 0,
- length: page,
- alignment: page,
- unallocated: 0,
- minUnallocated: 0,
- },
- {
- desc: "Allocation begins at start of file",
- usage: &usageSegmentDataSlices{
- Start: []uint64{page},
- End: []uint64{2 * page},
- Values: []usageInfo{{refs: 1}},
- },
- start: 0,
- length: page,
- alignment: page,
- unallocated: 0,
- minUnallocated: 0,
- },
- {
- desc: "In-use frames are not allocatable",
- usage: &usageSegmentDataSlices{
- Start: []uint64{0, page},
- End: []uint64{page, 2 * page},
- Values: []usageInfo{{refs: 1}, {refs: 2}},
- },
- start: 0,
- length: page,
- alignment: page,
- unallocated: 2 * page,
- minUnallocated: 2 * page,
- },
- {
- desc: "Reclaimable frames are not allocatable",
- usage: &usageSegmentDataSlices{
- Start: []uint64{0, page, 2 * page},
- End: []uint64{page, 2 * page, 3 * page},
- Values: []usageInfo{{refs: 1}, {refs: 0}, {refs: 1}},
- },
- start: 0,
- length: page,
- alignment: page,
- unallocated: 3 * page,
- minUnallocated: 3 * page,
- },
- {
- desc: "Gaps between in-use frames are allocatable",
- usage: &usageSegmentDataSlices{
- Start: []uint64{0, 2 * page},
- End: []uint64{page, 3 * page},
- Values: []usageInfo{{refs: 1}, {refs: 1}},
- },
- start: 0,
- length: page,
- alignment: page,
- unallocated: page,
- minUnallocated: page,
- },
- {
- desc: "Inadequately-sized gaps are rejected",
- usage: &usageSegmentDataSlices{
- Start: []uint64{0, 2 * page},
- End: []uint64{page, 3 * page},
- Values: []usageInfo{{refs: 1}, {refs: 1}},
- },
- start: 0,
- length: 2 * page,
- alignment: page,
- unallocated: 3 * page,
- minUnallocated: page,
- },
- {
- desc: "Hugepage alignment is honored",
- usage: &usageSegmentDataSlices{
- Start: []uint64{0, hugepage + page},
- // Hugepage-sized gap here that shouldn't be allocated from
- // since it's incorrectly aligned.
- End: []uint64{page, hugepage + 2*page},
- Values: []usageInfo{{refs: 1}, {refs: 1}},
- },
- start: 0,
- length: hugepage,
- alignment: hugepage,
- unallocated: 2 * hugepage,
- minUnallocated: page,
- },
- {
- desc: "Pages before start ignored",
- usage: &usageSegmentDataSlices{
- Start: []uint64{page, 3 * page},
- End: []uint64{2 * page, 4 * page},
- Values: []usageInfo{{refs: 1}, {refs: 2}},
- },
- start: page,
- length: page,
- alignment: page,
- unallocated: 2 * page,
- minUnallocated: 2 * page,
- },
- {
- desc: "start may be in the middle of segment",
- usage: &usageSegmentDataSlices{
- Start: []uint64{0, 3 * page},
- End: []uint64{2 * page, 4 * page},
- Values: []usageInfo{{refs: 1}, {refs: 2}},
- },
- start: page,
- length: page,
- alignment: page,
- unallocated: 2 * page,
- minUnallocated: 2 * page,
- },
- } {
- t.Run(test.desc, func(t *testing.T) {
- var usage usageSet
- if err := usage.ImportSortedSlices(test.usage); err != nil {
- t.Fatalf("Failed to initialize usage from %v: %v", test.usage, err)
- }
- unallocated, minUnallocated := findUnallocatedRange(&usage, test.start, test.length, test.alignment)
- if unallocated != test.unallocated {
- t.Errorf("findUnallocatedRange(%v, %x, %x, %x): got unallocated %x, wanted %x", test.usage, test.start, test.length, test.alignment, unallocated, test.unallocated)
- }
- if minUnallocated != test.minUnallocated {
- t.Errorf("findUnallocatedRange(%v, %x, %x, %x): got minUnallocated %x, wanted %x", test.usage, test.start, test.length, test.alignment, minUnallocated, test.minUnallocated)
- }
- })
- }
-}
diff --git a/pkg/sentry/pgalloc/usage_set.go b/pkg/sentry/pgalloc/usage_set.go
new file mode 100755
index 000000000..37b9235ca
--- /dev/null
+++ b/pkg/sentry/pgalloc/usage_set.go
@@ -0,0 +1,1274 @@
+package pgalloc
+
+import (
+ __generics_imported0 "gvisor.dev/gvisor/pkg/sentry/platform"
+)
+
+import (
+ "bytes"
+ "fmt"
+)
+
+const (
+ // minDegree is the minimum degree of an internal node in a Set B-tree.
+ //
+ // - Any non-root node has at least minDegree-1 segments.
+ //
+ // - Any non-root internal (non-leaf) node has at least minDegree children.
+ //
+ // - The root node may have fewer than minDegree-1 segments, but it may
+ // only have 0 segments if the tree is empty.
+ //
+ // Our implementation requires minDegree >= 3. Higher values of minDegree
+ // usually improve performance, but increase memory usage for small sets.
+ usageminDegree = 10
+
+ usagemaxDegree = 2 * usageminDegree
+)
+
+// A Set is a mapping of segments with non-overlapping Range keys. The zero
+// value for a Set is an empty set. Set values are not safely movable nor
+// copyable. Set is thread-compatible.
+//
+// +stateify savable
+type usageSet struct {
+ root usagenode `state:".(*usageSegmentDataSlices)"`
+}
+
+// IsEmpty returns true if the set contains no segments.
+func (s *usageSet) IsEmpty() bool {
+ return s.root.nrSegments == 0
+}
+
+// IsEmptyRange returns true iff no segments in the set overlap the given
+// range. This is semantically equivalent to s.SpanRange(r) == 0, but may be
+// more efficient.
+func (s *usageSet) IsEmptyRange(r __generics_imported0.FileRange) bool {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return true
+ }
+ _, gap := s.Find(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ return r.End <= gap.End()
+}
+
+// Span returns the total size of all segments in the set.
+func (s *usageSet) Span() uint64 {
+ var sz uint64
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sz += seg.Range().Length()
+ }
+ return sz
+}
+
+// SpanRange returns the total size of the intersection of segments in the set
+// with the given range.
+func (s *usageSet) SpanRange(r __generics_imported0.FileRange) uint64 {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return 0
+ }
+ var sz uint64
+ for seg := s.LowerBoundSegment(r.Start); seg.Ok() && seg.Start() < r.End; seg = seg.NextSegment() {
+ sz += seg.Range().Intersect(r).Length()
+ }
+ return sz
+}
+
+// FirstSegment returns the first segment in the set. If the set is empty,
+// FirstSegment returns a terminal iterator.
+func (s *usageSet) FirstSegment() usageIterator {
+ if s.root.nrSegments == 0 {
+ return usageIterator{}
+ }
+ return s.root.firstSegment()
+}
+
+// LastSegment returns the last segment in the set. If the set is empty,
+// LastSegment returns a terminal iterator.
+func (s *usageSet) LastSegment() usageIterator {
+ if s.root.nrSegments == 0 {
+ return usageIterator{}
+ }
+ return s.root.lastSegment()
+}
+
+// FirstGap returns the first gap in the set.
+func (s *usageSet) FirstGap() usageGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return usageGapIterator{n, 0}
+}
+
+// LastGap returns the last gap in the set.
+func (s *usageSet) LastGap() usageGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return usageGapIterator{n, n.nrSegments}
+}
+
+// Find returns the segment or gap whose range contains the given key. If a
+// segment is found, the returned Iterator is non-terminal and the
+// returned GapIterator is terminal. Otherwise, the returned Iterator is
+// terminal and the returned GapIterator is non-terminal.
+func (s *usageSet) Find(key uint64) (usageIterator, usageGapIterator) {
+ n := &s.root
+ for {
+
+ lower := 0
+ upper := n.nrSegments
+ for lower < upper {
+ i := lower + (upper-lower)/2
+ if r := n.keys[i]; key < r.End {
+ if key >= r.Start {
+ return usageIterator{n, i}, usageGapIterator{}
+ }
+ upper = i
+ } else {
+ lower = i + 1
+ }
+ }
+ i := lower
+ if !n.hasChildren {
+ return usageIterator{}, usageGapIterator{n, i}
+ }
+ n = n.children[i]
+ }
+}
+
+// FindSegment returns the segment whose range contains the given key. If no
+// such segment exists, FindSegment returns a terminal iterator.
+func (s *usageSet) FindSegment(key uint64) usageIterator {
+ seg, _ := s.Find(key)
+ return seg
+}
+
+// LowerBoundSegment returns the segment with the lowest range that contains a
+// key greater than or equal to min. If no such segment exists,
+// LowerBoundSegment returns a terminal iterator.
+func (s *usageSet) LowerBoundSegment(min uint64) usageIterator {
+ seg, gap := s.Find(min)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.NextSegment()
+}
+
+// UpperBoundSegment returns the segment with the highest range that contains a
+// key less than or equal to max. If no such segment exists, UpperBoundSegment
+// returns a terminal iterator.
+func (s *usageSet) UpperBoundSegment(max uint64) usageIterator {
+ seg, gap := s.Find(max)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.PrevSegment()
+}
+
+// FindGap returns the gap containing the given key. If no such gap exists
+// (i.e. the set contains a segment containing that key), FindGap returns a
+// terminal iterator.
+func (s *usageSet) FindGap(key uint64) usageGapIterator {
+ _, gap := s.Find(key)
+ return gap
+}
+
+// LowerBoundGap returns the gap with the lowest range that is greater than or
+// equal to min.
+func (s *usageSet) LowerBoundGap(min uint64) usageGapIterator {
+ seg, gap := s.Find(min)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.NextGap()
+}
+
+// UpperBoundGap returns the gap with the highest range that is less than or
+// equal to max.
+func (s *usageSet) UpperBoundGap(max uint64) usageGapIterator {
+ seg, gap := s.Find(max)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.PrevGap()
+}
+
+// Add inserts the given segment into the set and returns true. If the new
+// segment can be merged with adjacent segments, Add will do so. If the new
+// segment would overlap an existing segment, Add returns false. If Add
+// succeeds, all existing iterators are invalidated.
+func (s *usageSet) Add(r __generics_imported0.FileRange, val usageInfo) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.Insert(gap, r, val)
+ return true
+}
+
+// AddWithoutMerging inserts the given segment into the set and returns true.
+// If it would overlap an existing segment, AddWithoutMerging does nothing and
+// returns false. If AddWithoutMerging succeeds, all existing iterators are
+// invalidated.
+func (s *usageSet) AddWithoutMerging(r __generics_imported0.FileRange, val usageInfo) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.InsertWithoutMergingUnchecked(gap, r, val)
+ return true
+}
+
+// Insert inserts the given segment into the given gap. If the new segment can
+// be merged with adjacent segments, Insert will do so. Insert returns an
+// iterator to the segment containing the inserted value (which may have been
+// merged with other values). All existing iterators (including gap, but not
+// including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid, Insert panics.
+//
+// Insert is semantically equivalent to a InsertWithoutMerging followed by a
+// Merge, but may be more efficient. Note that there is no unchecked variant of
+// Insert since Insert must retrieve and inspect gap's predecessor and
+// successor segments regardless.
+func (s *usageSet) Insert(gap usageGapIterator, r __generics_imported0.FileRange, val usageInfo) usageIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ prev, next := gap.PrevSegment(), gap.NextSegment()
+ if prev.Ok() && prev.End() > r.Start {
+ panic(fmt.Sprintf("new segment %v overlaps predecessor %v", r, prev.Range()))
+ }
+ if next.Ok() && next.Start() < r.End {
+ panic(fmt.Sprintf("new segment %v overlaps successor %v", r, next.Range()))
+ }
+ if prev.Ok() && prev.End() == r.Start {
+ if mval, ok := (usageSetFunctions{}).Merge(prev.Range(), prev.Value(), r, val); ok {
+ prev.SetEndUnchecked(r.End)
+ prev.SetValue(mval)
+ if next.Ok() && next.Start() == r.End {
+ val = mval
+ if mval, ok := (usageSetFunctions{}).Merge(prev.Range(), val, next.Range(), next.Value()); ok {
+ prev.SetEndUnchecked(next.End())
+ prev.SetValue(mval)
+ return s.Remove(next).PrevSegment()
+ }
+ }
+ return prev
+ }
+ }
+ if next.Ok() && next.Start() == r.End {
+ if mval, ok := (usageSetFunctions{}).Merge(r, val, next.Range(), next.Value()); ok {
+ next.SetStartUnchecked(r.Start)
+ next.SetValue(mval)
+ return next
+ }
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMerging inserts the given segment into the given gap and
+// returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid,
+// InsertWithoutMerging panics.
+func (s *usageSet) InsertWithoutMerging(gap usageGapIterator, r __generics_imported0.FileRange, val usageInfo) usageIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if gr := gap.Range(); !gr.IsSupersetOf(r) {
+ panic(fmt.Sprintf("cannot insert segment range %v into gap range %v", r, gr))
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMergingUnchecked inserts the given segment into the given gap
+// and returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// Preconditions: r.Start >= gap.Start(); r.End <= gap.End().
+func (s *usageSet) InsertWithoutMergingUnchecked(gap usageGapIterator, r __generics_imported0.FileRange, val usageInfo) usageIterator {
+ gap = gap.node.rebalanceBeforeInsert(gap)
+ copy(gap.node.keys[gap.index+1:], gap.node.keys[gap.index:gap.node.nrSegments])
+ copy(gap.node.values[gap.index+1:], gap.node.values[gap.index:gap.node.nrSegments])
+ gap.node.keys[gap.index] = r
+ gap.node.values[gap.index] = val
+ gap.node.nrSegments++
+ return usageIterator{gap.node, gap.index}
+}
+
+// Remove removes the given segment and returns an iterator to the vacated gap.
+// All existing iterators (including seg, but not including the returned
+// iterator) are invalidated.
+func (s *usageSet) Remove(seg usageIterator) usageGapIterator {
+
+ if seg.node.hasChildren {
+
+ victim := seg.PrevSegment()
+
+ seg.SetRangeUnchecked(victim.Range())
+ seg.SetValue(victim.Value())
+ return s.Remove(victim).NextGap()
+ }
+ copy(seg.node.keys[seg.index:], seg.node.keys[seg.index+1:seg.node.nrSegments])
+ copy(seg.node.values[seg.index:], seg.node.values[seg.index+1:seg.node.nrSegments])
+ usageSetFunctions{}.ClearValue(&seg.node.values[seg.node.nrSegments-1])
+ seg.node.nrSegments--
+ return seg.node.rebalanceAfterRemove(usageGapIterator{seg.node, seg.index})
+}
+
+// RemoveAll removes all segments from the set. All existing iterators are
+// invalidated.
+func (s *usageSet) RemoveAll() {
+ s.root = usagenode{}
+}
+
+// RemoveRange removes all segments in the given range. An iterator to the
+// newly formed gap is returned, and all existing iterators are invalidated.
+func (s *usageSet) RemoveRange(r __generics_imported0.FileRange) usageGapIterator {
+ seg, gap := s.Find(r.Start)
+ if seg.Ok() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ for seg = gap.NextSegment(); seg.Ok() && seg.Start() < r.End; seg = gap.NextSegment() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ return gap
+}
+
+// Merge attempts to merge two neighboring segments. If successful, Merge
+// returns an iterator to the merged segment, and all existing iterators are
+// invalidated. Otherwise, Merge returns a terminal iterator.
+//
+// If first is not the predecessor of second, Merge panics.
+func (s *usageSet) Merge(first, second usageIterator) usageIterator {
+ if first.NextSegment() != second {
+ panic(fmt.Sprintf("attempt to merge non-neighboring segments %v, %v", first.Range(), second.Range()))
+ }
+ return s.MergeUnchecked(first, second)
+}
+
+// MergeUnchecked attempts to merge two neighboring segments. If successful,
+// MergeUnchecked returns an iterator to the merged segment, and all existing
+// iterators are invalidated. Otherwise, MergeUnchecked returns a terminal
+// iterator.
+//
+// Precondition: first is the predecessor of second: first.NextSegment() ==
+// second, first == second.PrevSegment().
+func (s *usageSet) MergeUnchecked(first, second usageIterator) usageIterator {
+ if first.End() == second.Start() {
+ if mval, ok := (usageSetFunctions{}).Merge(first.Range(), first.Value(), second.Range(), second.Value()); ok {
+
+ first.SetEndUnchecked(second.End())
+ first.SetValue(mval)
+ return s.Remove(second).PrevSegment()
+ }
+ }
+ return usageIterator{}
+}
+
+// MergeAll attempts to merge all adjacent segments in the set. All existing
+// iterators are invalidated.
+func (s *usageSet) MergeAll() {
+ seg := s.FirstSegment()
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeRange attempts to merge all adjacent segments that contain a key in the
+// specific range. All existing iterators are invalidated.
+func (s *usageSet) MergeRange(r __generics_imported0.FileRange) {
+ seg := s.LowerBoundSegment(r.Start)
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() && next.Range().Start < r.End {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeAdjacent attempts to merge the segment containing r.Start with its
+// predecessor, and the segment containing r.End-1 with its successor.
+func (s *usageSet) MergeAdjacent(r __generics_imported0.FileRange) {
+ first := s.FindSegment(r.Start)
+ if first.Ok() {
+ if prev := first.PrevSegment(); prev.Ok() {
+ s.Merge(prev, first)
+ }
+ }
+ last := s.FindSegment(r.End - 1)
+ if last.Ok() {
+ if next := last.NextSegment(); next.Ok() {
+ s.Merge(last, next)
+ }
+ }
+}
+
+// Split splits the given segment at the given key and returns iterators to the
+// two resulting segments. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+//
+// If the segment cannot be split at split (because split is at the start or
+// end of the segment's range, so splitting would produce a segment with zero
+// length, or because split falls outside the segment's range altogether),
+// Split panics.
+func (s *usageSet) Split(seg usageIterator, split uint64) (usageIterator, usageIterator) {
+ if !seg.Range().CanSplitAt(split) {
+ panic(fmt.Sprintf("can't split %v at %v", seg.Range(), split))
+ }
+ return s.SplitUnchecked(seg, split)
+}
+
+// SplitUnchecked splits the given segment at the given key and returns
+// iterators to the two resulting segments. All existing iterators (including
+// seg, but not including the returned iterators) are invalidated.
+//
+// Preconditions: seg.Start() < key < seg.End().
+func (s *usageSet) SplitUnchecked(seg usageIterator, split uint64) (usageIterator, usageIterator) {
+ val1, val2 := (usageSetFunctions{}).Split(seg.Range(), seg.Value(), split)
+ end2 := seg.End()
+ seg.SetEndUnchecked(split)
+ seg.SetValue(val1)
+ seg2 := s.InsertWithoutMergingUnchecked(seg.NextGap(), __generics_imported0.FileRange{split, end2}, val2)
+
+ return seg2.PrevSegment(), seg2
+}
+
+// SplitAt splits the segment straddling split, if one exists. SplitAt returns
+// true if a segment was split and false otherwise. If SplitAt splits a
+// segment, all existing iterators are invalidated.
+func (s *usageSet) SplitAt(split uint64) bool {
+ if seg := s.FindSegment(split); seg.Ok() && seg.Range().CanSplitAt(split) {
+ s.SplitUnchecked(seg, split)
+ return true
+ }
+ return false
+}
+
+// Isolate ensures that the given segment's range does not escape r by
+// splitting at r.Start and r.End if necessary, and returns an updated iterator
+// to the bounded segment. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+func (s *usageSet) Isolate(seg usageIterator, r __generics_imported0.FileRange) usageIterator {
+ if seg.Range().CanSplitAt(r.Start) {
+ _, seg = s.SplitUnchecked(seg, r.Start)
+ }
+ if seg.Range().CanSplitAt(r.End) {
+ seg, _ = s.SplitUnchecked(seg, r.End)
+ }
+ return seg
+}
+
+// ApplyContiguous applies a function to a contiguous range of segments,
+// splitting if necessary. The function is applied until the first gap is
+// encountered, at which point the gap is returned. If the function is applied
+// across the entire range, a terminal gap is returned. All existing iterators
+// are invalidated.
+//
+// N.B. The Iterator must not be invalidated by the function.
+func (s *usageSet) ApplyContiguous(r __generics_imported0.FileRange, fn func(seg usageIterator)) usageGapIterator {
+ seg, gap := s.Find(r.Start)
+ if !seg.Ok() {
+ return gap
+ }
+ for {
+ seg = s.Isolate(seg, r)
+ fn(seg)
+ if seg.End() >= r.End {
+ return usageGapIterator{}
+ }
+ gap = seg.NextGap()
+ if !gap.IsEmpty() {
+ return gap
+ }
+ seg = gap.NextSegment()
+ if !seg.Ok() {
+
+ return usageGapIterator{}
+ }
+ }
+}
+
+// +stateify savable
+type usagenode struct {
+ // An internal binary tree node looks like:
+ //
+ // K
+ // / \
+ // Cl Cr
+ //
+ // where all keys in the subtree rooted by Cl (the left subtree) are less
+ // than K (the key of the parent node), and all keys in the subtree rooted
+ // by Cr (the right subtree) are greater than K.
+ //
+ // An internal B-tree node's indexes work out to look like:
+ //
+ // K0 K1 K2 ... Kn-1
+ // / \/ \/ \ ... / \
+ // C0 C1 C2 C3 ... Cn-1 Cn
+ //
+ // where n is nrSegments.
+ nrSegments int
+
+ // parent is a pointer to this node's parent. If this node is root, parent
+ // is nil.
+ parent *usagenode
+
+ // parentIndex is the index of this node in parent.children.
+ parentIndex int
+
+ // Flag for internal nodes that is technically redundant with "children[0]
+ // != nil", but is stored in the first cache line. "hasChildren" rather
+ // than "isLeaf" because false must be the correct value for an empty root.
+ hasChildren bool
+
+ // Nodes store keys and values in separate arrays to maximize locality in
+ // the common case (scanning keys for lookup).
+ keys [usagemaxDegree - 1]__generics_imported0.FileRange
+ values [usagemaxDegree - 1]usageInfo
+ children [usagemaxDegree]*usagenode
+}
+
+// firstSegment returns the first segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *usagenode) firstSegment() usageIterator {
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return usageIterator{n, 0}
+}
+
+// lastSegment returns the last segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *usagenode) lastSegment() usageIterator {
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return usageIterator{n, n.nrSegments - 1}
+}
+
+func (n *usagenode) prevSibling() *usagenode {
+ if n.parent == nil || n.parentIndex == 0 {
+ return nil
+ }
+ return n.parent.children[n.parentIndex-1]
+}
+
+func (n *usagenode) nextSibling() *usagenode {
+ if n.parent == nil || n.parentIndex == n.parent.nrSegments {
+ return nil
+ }
+ return n.parent.children[n.parentIndex+1]
+}
+
+// rebalanceBeforeInsert splits n and its ancestors if they are full, as
+// required for insertion, and returns an updated iterator to the position
+// represented by gap.
+func (n *usagenode) rebalanceBeforeInsert(gap usageGapIterator) usageGapIterator {
+ if n.parent != nil {
+ gap = n.parent.rebalanceBeforeInsert(gap)
+ }
+ if n.nrSegments < usagemaxDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ left := &usagenode{
+ nrSegments: usageminDegree - 1,
+ parent: n,
+ parentIndex: 0,
+ hasChildren: n.hasChildren,
+ }
+ right := &usagenode{
+ nrSegments: usageminDegree - 1,
+ parent: n,
+ parentIndex: 1,
+ hasChildren: n.hasChildren,
+ }
+ copy(left.keys[:usageminDegree-1], n.keys[:usageminDegree-1])
+ copy(left.values[:usageminDegree-1], n.values[:usageminDegree-1])
+ copy(right.keys[:usageminDegree-1], n.keys[usageminDegree:])
+ copy(right.values[:usageminDegree-1], n.values[usageminDegree:])
+ n.keys[0], n.values[0] = n.keys[usageminDegree-1], n.values[usageminDegree-1]
+ usagezeroValueSlice(n.values[1:])
+ if n.hasChildren {
+ copy(left.children[:usageminDegree], n.children[:usageminDegree])
+ copy(right.children[:usageminDegree], n.children[usageminDegree:])
+ usagezeroNodeSlice(n.children[2:])
+ for i := 0; i < usageminDegree; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ right.children[i].parent = right
+ right.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = 1
+ n.hasChildren = true
+ n.children[0] = left
+ n.children[1] = right
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < usageminDegree {
+ return usageGapIterator{left, gap.index}
+ }
+ return usageGapIterator{right, gap.index - usageminDegree}
+ }
+
+ copy(n.parent.keys[n.parentIndex+1:], n.parent.keys[n.parentIndex:n.parent.nrSegments])
+ copy(n.parent.values[n.parentIndex+1:], n.parent.values[n.parentIndex:n.parent.nrSegments])
+ n.parent.keys[n.parentIndex], n.parent.values[n.parentIndex] = n.keys[usageminDegree-1], n.values[usageminDegree-1]
+ copy(n.parent.children[n.parentIndex+2:], n.parent.children[n.parentIndex+1:n.parent.nrSegments+1])
+ for i := n.parentIndex + 2; i < n.parent.nrSegments+2; i++ {
+ n.parent.children[i].parentIndex = i
+ }
+ sibling := &usagenode{
+ nrSegments: usageminDegree - 1,
+ parent: n.parent,
+ parentIndex: n.parentIndex + 1,
+ hasChildren: n.hasChildren,
+ }
+ n.parent.children[n.parentIndex+1] = sibling
+ n.parent.nrSegments++
+ copy(sibling.keys[:usageminDegree-1], n.keys[usageminDegree:])
+ copy(sibling.values[:usageminDegree-1], n.values[usageminDegree:])
+ usagezeroValueSlice(n.values[usageminDegree-1:])
+ if n.hasChildren {
+ copy(sibling.children[:usageminDegree], n.children[usageminDegree:])
+ usagezeroNodeSlice(n.children[usageminDegree:])
+ for i := 0; i < usageminDegree; i++ {
+ sibling.children[i].parent = sibling
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = usageminDegree - 1
+
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < usageminDegree {
+ return gap
+ }
+ return usageGapIterator{sibling, gap.index - usageminDegree}
+}
+
+// rebalanceAfterRemove "unsplits" n and its ancestors if they are deficient
+// (contain fewer segments than required by B-tree invariants), as required for
+// removal, and returns an updated iterator to the position represented by gap.
+//
+// Precondition: n is the only node in the tree that may currently violate a
+// B-tree invariant.
+func (n *usagenode) rebalanceAfterRemove(gap usageGapIterator) usageGapIterator {
+ for {
+ if n.nrSegments >= usageminDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ return gap
+ }
+
+ if sibling := n.prevSibling(); sibling != nil && sibling.nrSegments >= usageminDegree {
+ copy(n.keys[1:], n.keys[:n.nrSegments])
+ copy(n.values[1:], n.values[:n.nrSegments])
+ n.keys[0] = n.parent.keys[n.parentIndex-1]
+ n.values[0] = n.parent.values[n.parentIndex-1]
+ n.parent.keys[n.parentIndex-1] = sibling.keys[sibling.nrSegments-1]
+ n.parent.values[n.parentIndex-1] = sibling.values[sibling.nrSegments-1]
+ usageSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ copy(n.children[1:], n.children[:n.nrSegments+1])
+ n.children[0] = sibling.children[sibling.nrSegments]
+ sibling.children[sibling.nrSegments] = nil
+ n.children[0].parent = n
+ n.children[0].parentIndex = 0
+ for i := 1; i < n.nrSegments+2; i++ {
+ n.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling && gap.index == sibling.nrSegments {
+ return usageGapIterator{n, 0}
+ }
+ if gap.node == n {
+ return usageGapIterator{n, gap.index + 1}
+ }
+ return gap
+ }
+ if sibling := n.nextSibling(); sibling != nil && sibling.nrSegments >= usageminDegree {
+ n.keys[n.nrSegments] = n.parent.keys[n.parentIndex]
+ n.values[n.nrSegments] = n.parent.values[n.parentIndex]
+ n.parent.keys[n.parentIndex] = sibling.keys[0]
+ n.parent.values[n.parentIndex] = sibling.values[0]
+ copy(sibling.keys[:sibling.nrSegments-1], sibling.keys[1:])
+ copy(sibling.values[:sibling.nrSegments-1], sibling.values[1:])
+ usageSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ n.children[n.nrSegments+1] = sibling.children[0]
+ copy(sibling.children[:sibling.nrSegments], sibling.children[1:])
+ sibling.children[sibling.nrSegments] = nil
+ n.children[n.nrSegments+1].parent = n
+ n.children[n.nrSegments+1].parentIndex = n.nrSegments + 1
+ for i := 0; i < sibling.nrSegments; i++ {
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling {
+ if gap.index == 0 {
+ return usageGapIterator{n, n.nrSegments}
+ }
+ return usageGapIterator{sibling, gap.index - 1}
+ }
+ return gap
+ }
+
+ p := n.parent
+ if p.nrSegments == 1 {
+
+ left, right := p.children[0], p.children[1]
+ p.nrSegments = left.nrSegments + right.nrSegments + 1
+ p.hasChildren = left.hasChildren
+ p.keys[left.nrSegments] = p.keys[0]
+ p.values[left.nrSegments] = p.values[0]
+ copy(p.keys[:left.nrSegments], left.keys[:left.nrSegments])
+ copy(p.values[:left.nrSegments], left.values[:left.nrSegments])
+ copy(p.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(p.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(p.children[:left.nrSegments+1], left.children[:left.nrSegments+1])
+ copy(p.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := 0; i < p.nrSegments+1; i++ {
+ p.children[i].parent = p
+ p.children[i].parentIndex = i
+ }
+ } else {
+ p.children[0] = nil
+ p.children[1] = nil
+ }
+ if gap.node == left {
+ return usageGapIterator{p, gap.index}
+ }
+ if gap.node == right {
+ return usageGapIterator{p, gap.index + left.nrSegments + 1}
+ }
+ return gap
+ }
+ // Merge n and either sibling, along with the segment separating the
+ // two, into whichever of the two nodes comes first. This is the
+ // reverse of the non-root splitting case in
+ // node.rebalanceBeforeInsert.
+ var left, right *usagenode
+ if n.parentIndex > 0 {
+ left = n.prevSibling()
+ right = n
+ } else {
+ left = n
+ right = n.nextSibling()
+ }
+
+ if gap.node == right {
+ gap = usageGapIterator{left, gap.index + left.nrSegments + 1}
+ }
+ left.keys[left.nrSegments] = p.keys[left.parentIndex]
+ left.values[left.nrSegments] = p.values[left.parentIndex]
+ copy(left.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(left.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(left.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := left.nrSegments + 1; i < left.nrSegments+right.nrSegments+2; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ }
+ }
+ left.nrSegments += right.nrSegments + 1
+ copy(p.keys[left.parentIndex:], p.keys[left.parentIndex+1:p.nrSegments])
+ copy(p.values[left.parentIndex:], p.values[left.parentIndex+1:p.nrSegments])
+ usageSetFunctions{}.ClearValue(&p.values[p.nrSegments-1])
+ copy(p.children[left.parentIndex+1:], p.children[left.parentIndex+2:p.nrSegments+1])
+ for i := 0; i < p.nrSegments; i++ {
+ p.children[i].parentIndex = i
+ }
+ p.children[p.nrSegments] = nil
+ p.nrSegments--
+
+ n = p
+ }
+}
+
+// A Iterator is conceptually one of:
+//
+// - A pointer to a segment in a set; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Iterators are copyable values and are meaningfully equality-comparable. The
+// zero value of Iterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type usageIterator struct {
+ // node is the node containing the iterated segment. If the iterator is
+ // terminal, node is nil.
+ node *usagenode
+
+ // index is the index of the segment in node.keys/values.
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (seg usageIterator) Ok() bool {
+ return seg.node != nil
+}
+
+// Range returns the iterated segment's range key.
+func (seg usageIterator) Range() __generics_imported0.FileRange {
+ return seg.node.keys[seg.index]
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (seg usageIterator) Start() uint64 {
+ return seg.node.keys[seg.index].Start
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (seg usageIterator) End() uint64 {
+ return seg.node.keys[seg.index].End
+}
+
+// SetRangeUnchecked mutates the iterated segment's range key. This operation
+// does not invalidate any iterators.
+//
+// Preconditions:
+//
+// - r.Length() > 0.
+//
+// - The new range must not overlap an existing one: If seg.NextSegment().Ok(),
+// then r.end <= seg.NextSegment().Start(); if seg.PrevSegment().Ok(), then
+// r.start >= seg.PrevSegment().End().
+func (seg usageIterator) SetRangeUnchecked(r __generics_imported0.FileRange) {
+ seg.node.keys[seg.index] = r
+}
+
+// SetRange mutates the iterated segment's range key. If the new range would
+// cause the iterated segment to overlap another segment, or if the new range
+// is invalid, SetRange panics. This operation does not invalidate any
+// iterators.
+func (seg usageIterator) SetRange(r __generics_imported0.FileRange) {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && r.Start < prev.End() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, prev.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && r.End > next.Start() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, next.Range()))
+ }
+ seg.SetRangeUnchecked(r)
+}
+
+// SetStartUnchecked mutates the iterated segment's start. This operation does
+// not invalidate any iterators.
+//
+// Preconditions: The new start must be valid: start < seg.End(); if
+// seg.PrevSegment().Ok(), then start >= seg.PrevSegment().End().
+func (seg usageIterator) SetStartUnchecked(start uint64) {
+ seg.node.keys[seg.index].Start = start
+}
+
+// SetStart mutates the iterated segment's start. If the new start value would
+// cause the iterated segment to overlap another segment, or would result in an
+// invalid range, SetStart panics. This operation does not invalidate any
+// iterators.
+func (seg usageIterator) SetStart(start uint64) {
+ if start >= seg.End() {
+ panic(fmt.Sprintf("new start %v would invalidate segment range %v", start, seg.Range()))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && start < prev.End() {
+ panic(fmt.Sprintf("new start %v would cause segment range %v to overlap segment range %v", start, seg.Range(), prev.Range()))
+ }
+ seg.SetStartUnchecked(start)
+}
+
+// SetEndUnchecked mutates the iterated segment's end. This operation does not
+// invalidate any iterators.
+//
+// Preconditions: The new end must be valid: end > seg.Start(); if
+// seg.NextSegment().Ok(), then end <= seg.NextSegment().Start().
+func (seg usageIterator) SetEndUnchecked(end uint64) {
+ seg.node.keys[seg.index].End = end
+}
+
+// SetEnd mutates the iterated segment's end. If the new end value would cause
+// the iterated segment to overlap another segment, or would result in an
+// invalid range, SetEnd panics. This operation does not invalidate any
+// iterators.
+func (seg usageIterator) SetEnd(end uint64) {
+ if end <= seg.Start() {
+ panic(fmt.Sprintf("new end %v would invalidate segment range %v", end, seg.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && end > next.Start() {
+ panic(fmt.Sprintf("new end %v would cause segment range %v to overlap segment range %v", end, seg.Range(), next.Range()))
+ }
+ seg.SetEndUnchecked(end)
+}
+
+// Value returns a copy of the iterated segment's value.
+func (seg usageIterator) Value() usageInfo {
+ return seg.node.values[seg.index]
+}
+
+// ValuePtr returns a pointer to the iterated segment's value. The pointer is
+// invalidated if the iterator is invalidated. This operation does not
+// invalidate any iterators.
+func (seg usageIterator) ValuePtr() *usageInfo {
+ return &seg.node.values[seg.index]
+}
+
+// SetValue mutates the iterated segment's value. This operation does not
+// invalidate any iterators.
+func (seg usageIterator) SetValue(val usageInfo) {
+ seg.node.values[seg.index] = val
+}
+
+// PrevSegment returns the iterated segment's predecessor. If there is no
+// preceding segment, PrevSegment returns a terminal iterator.
+func (seg usageIterator) PrevSegment() usageIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index].lastSegment()
+ }
+ if seg.index > 0 {
+ return usageIterator{seg.node, seg.index - 1}
+ }
+ if seg.node.parent == nil {
+ return usageIterator{}
+ }
+ return usagesegmentBeforePosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// NextSegment returns the iterated segment's successor. If there is no
+// succeeding segment, NextSegment returns a terminal iterator.
+func (seg usageIterator) NextSegment() usageIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment()
+ }
+ if seg.index < seg.node.nrSegments-1 {
+ return usageIterator{seg.node, seg.index + 1}
+ }
+ if seg.node.parent == nil {
+ return usageIterator{}
+ }
+ return usagesegmentAfterPosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// PrevGap returns the gap immediately before the iterated segment.
+func (seg usageIterator) PrevGap() usageGapIterator {
+ if seg.node.hasChildren {
+
+ return seg.node.children[seg.index].lastSegment().NextGap()
+ }
+ return usageGapIterator{seg.node, seg.index}
+}
+
+// NextGap returns the gap immediately after the iterated segment.
+func (seg usageIterator) NextGap() usageGapIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment().PrevGap()
+ }
+ return usageGapIterator{seg.node, seg.index + 1}
+}
+
+// PrevNonEmpty returns the iterated segment's predecessor if it is adjacent,
+// or the gap before the iterated segment otherwise. If seg.Start() ==
+// Functions.MinKey(), PrevNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by PrevNonEmpty will be
+// non-terminal.
+func (seg usageIterator) PrevNonEmpty() (usageIterator, usageGapIterator) {
+ gap := seg.PrevGap()
+ if gap.Range().Length() != 0 {
+ return usageIterator{}, gap
+ }
+ return gap.PrevSegment(), usageGapIterator{}
+}
+
+// NextNonEmpty returns the iterated segment's successor if it is adjacent, or
+// the gap after the iterated segment otherwise. If seg.End() ==
+// Functions.MaxKey(), NextNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by NextNonEmpty will be
+// non-terminal.
+func (seg usageIterator) NextNonEmpty() (usageIterator, usageGapIterator) {
+ gap := seg.NextGap()
+ if gap.Range().Length() != 0 {
+ return usageIterator{}, gap
+ }
+ return gap.NextSegment(), usageGapIterator{}
+}
+
+// A GapIterator is conceptually one of:
+//
+// - A pointer to a position between two segments, before the first segment, or
+// after the last segment in a set, called a *gap*; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Note that the gap between two adjacent segments exists (iterators to it are
+// non-terminal), but has a length of zero. GapIterator.IsEmpty returns true
+// for such gaps. An empty set contains a single gap, spanning the entire range
+// of the set's keys.
+//
+// GapIterators are copyable values and are meaningfully equality-comparable.
+// The zero value of GapIterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type usageGapIterator struct {
+ // The representation of a GapIterator is identical to that of an Iterator,
+ // except that index corresponds to positions between segments in the same
+ // way as for node.children (see comment for node.nrSegments).
+ node *usagenode
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (gap usageGapIterator) Ok() bool {
+ return gap.node != nil
+}
+
+// Range returns the range spanned by the iterated gap.
+func (gap usageGapIterator) Range() __generics_imported0.FileRange {
+ return __generics_imported0.FileRange{gap.Start(), gap.End()}
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (gap usageGapIterator) Start() uint64 {
+ if ps := gap.PrevSegment(); ps.Ok() {
+ return ps.End()
+ }
+ return usageSetFunctions{}.MinKey()
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (gap usageGapIterator) End() uint64 {
+ if ns := gap.NextSegment(); ns.Ok() {
+ return ns.Start()
+ }
+ return usageSetFunctions{}.MaxKey()
+}
+
+// IsEmpty returns true if the iterated gap is empty (that is, the "gap" is
+// between two adjacent segments.)
+func (gap usageGapIterator) IsEmpty() bool {
+ return gap.Range().Length() == 0
+}
+
+// PrevSegment returns the segment immediately before the iterated gap. If no
+// such segment exists, PrevSegment returns a terminal iterator.
+func (gap usageGapIterator) PrevSegment() usageIterator {
+ return usagesegmentBeforePosition(gap.node, gap.index)
+}
+
+// NextSegment returns the segment immediately after the iterated gap. If no
+// such segment exists, NextSegment returns a terminal iterator.
+func (gap usageGapIterator) NextSegment() usageIterator {
+ return usagesegmentAfterPosition(gap.node, gap.index)
+}
+
+// PrevGap returns the iterated gap's predecessor. If no such gap exists,
+// PrevGap returns a terminal iterator.
+func (gap usageGapIterator) PrevGap() usageGapIterator {
+ seg := gap.PrevSegment()
+ if !seg.Ok() {
+ return usageGapIterator{}
+ }
+ return seg.PrevGap()
+}
+
+// NextGap returns the iterated gap's successor. If no such gap exists, NextGap
+// returns a terminal iterator.
+func (gap usageGapIterator) NextGap() usageGapIterator {
+ seg := gap.NextSegment()
+ if !seg.Ok() {
+ return usageGapIterator{}
+ }
+ return seg.NextGap()
+}
+
+// segmentBeforePosition returns the predecessor segment of the position given
+// by n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentBeforePosition returns a terminal iterator.
+func usagesegmentBeforePosition(n *usagenode, i int) usageIterator {
+ for i == 0 {
+ if n.parent == nil {
+ return usageIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return usageIterator{n, i - 1}
+}
+
+// segmentAfterPosition returns the successor segment of the position given by
+// n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentAfterPosition returns a terminal iterator.
+func usagesegmentAfterPosition(n *usagenode, i int) usageIterator {
+ for i == n.nrSegments {
+ if n.parent == nil {
+ return usageIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return usageIterator{n, i}
+}
+
+func usagezeroValueSlice(slice []usageInfo) {
+
+ for i := range slice {
+ usageSetFunctions{}.ClearValue(&slice[i])
+ }
+}
+
+func usagezeroNodeSlice(slice []*usagenode) {
+ for i := range slice {
+ slice[i] = nil
+ }
+}
+
+// String stringifies a Set for debugging.
+func (s *usageSet) String() string {
+ return s.root.String()
+}
+
+// String stringifies a node (and all of its children) for debugging.
+func (n *usagenode) String() string {
+ var buf bytes.Buffer
+ n.writeDebugString(&buf, "")
+ return buf.String()
+}
+
+func (n *usagenode) writeDebugString(buf *bytes.Buffer, prefix string) {
+ if n.hasChildren != (n.nrSegments > 0 && n.children[0] != nil) {
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent value of hasChildren: got %v, want %v\n", n.hasChildren, !n.hasChildren))
+ }
+ for i := 0; i < n.nrSegments; i++ {
+ if child := n.children[i]; child != nil {
+ cprefix := fmt.Sprintf("%s- % 3d ", prefix, i)
+ if child.parent != n || child.parentIndex != i {
+ buf.WriteString(cprefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent linkage to parent: got (%p, %d), want (%p, %d)\n", child.parent, child.parentIndex, n, i))
+ }
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, i))
+ }
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("- % 3d: %v => %v\n", i, n.keys[i], n.values[i]))
+ }
+ if child := n.children[n.nrSegments]; child != nil {
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, n.nrSegments))
+ }
+}
+
+// SegmentDataSlices represents segments from a set as slices of start, end, and
+// values. SegmentDataSlices is primarily used as an intermediate representation
+// for save/restore and the layout here is optimized for that.
+//
+// +stateify savable
+type usageSegmentDataSlices struct {
+ Start []uint64
+ End []uint64
+ Values []usageInfo
+}
+
+// ExportSortedSlice returns a copy of all segments in the given set, in ascending
+// key order.
+func (s *usageSet) ExportSortedSlices() *usageSegmentDataSlices {
+ var sds usageSegmentDataSlices
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sds.Start = append(sds.Start, seg.Start())
+ sds.End = append(sds.End, seg.End())
+ sds.Values = append(sds.Values, seg.Value())
+ }
+ sds.Start = sds.Start[:len(sds.Start):len(sds.Start)]
+ sds.End = sds.End[:len(sds.End):len(sds.End)]
+ sds.Values = sds.Values[:len(sds.Values):len(sds.Values)]
+ return &sds
+}
+
+// ImportSortedSlice initializes the given set from the given slice.
+//
+// Preconditions: s must be empty. sds must represent a valid set (the segments
+// in sds must have valid lengths that do not overlap). The segments in sds
+// must be sorted in ascending key order.
+func (s *usageSet) ImportSortedSlices(sds *usageSegmentDataSlices) error {
+ if !s.IsEmpty() {
+ return fmt.Errorf("cannot import into non-empty set %v", s)
+ }
+ gap := s.FirstGap()
+ for i := range sds.Start {
+ r := __generics_imported0.FileRange{sds.Start[i], sds.End[i]}
+ if !gap.Range().IsSupersetOf(r) {
+ return fmt.Errorf("segment overlaps a preceding segment or is incorrectly sorted: [%d, %d) => %v", sds.Start[i], sds.End[i], sds.Values[i])
+ }
+ gap = s.InsertWithoutMerging(gap, r, sds.Values[i]).NextGap()
+ }
+ return nil
+}
+func (s *usageSet) saveRoot() *usageSegmentDataSlices {
+ return s.ExportSortedSlices()
+}
+
+func (s *usageSet) loadRoot(sds *usageSegmentDataSlices) {
+ if err := s.ImportSortedSlices(sds); err != nil {
+ panic(err)
+ }
+}
diff --git a/pkg/sentry/platform/BUILD b/pkg/sentry/platform/BUILD
deleted file mode 100644
index 9aa6ec507..000000000
--- a/pkg/sentry/platform/BUILD
+++ /dev/null
@@ -1,40 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_template_instance(
- name = "file_range",
- out = "file_range.go",
- package = "platform",
- prefix = "File",
- template = "//pkg/segment:generic_range",
- types = {
- "T": "uint64",
- },
-)
-
-go_library(
- name = "platform",
- srcs = [
- "context.go",
- "file_range.go",
- "mmap_min_addr.go",
- "platform.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/platform",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/atomicbitops",
- "//pkg/log",
- "//pkg/seccomp",
- "//pkg/sentry/arch",
- "//pkg/sentry/context",
- "//pkg/sentry/platform/safecopy",
- "//pkg/sentry/safemem",
- "//pkg/sentry/usage",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- ],
-)
diff --git a/pkg/sentry/platform/file_range.go b/pkg/sentry/platform/file_range.go
new file mode 100755
index 000000000..685d360e3
--- /dev/null
+++ b/pkg/sentry/platform/file_range.go
@@ -0,0 +1,62 @@
+package platform
+
+// A Range represents a contiguous range of T.
+//
+// +stateify savable
+type FileRange struct {
+ // Start is the inclusive start of the range.
+ Start uint64
+
+ // End is the exclusive end of the range.
+ End uint64
+}
+
+// WellFormed returns true if r.Start <= r.End. All other methods on a Range
+// require that the Range is well-formed.
+func (r FileRange) WellFormed() bool {
+ return r.Start <= r.End
+}
+
+// Length returns the length of the range.
+func (r FileRange) Length() uint64 {
+ return r.End - r.Start
+}
+
+// Contains returns true if r contains x.
+func (r FileRange) Contains(x uint64) bool {
+ return r.Start <= x && x < r.End
+}
+
+// Overlaps returns true if r and r2 overlap.
+func (r FileRange) Overlaps(r2 FileRange) bool {
+ return r.Start < r2.End && r2.Start < r.End
+}
+
+// IsSupersetOf returns true if r is a superset of r2; that is, the range r2 is
+// contained within r.
+func (r FileRange) IsSupersetOf(r2 FileRange) bool {
+ return r.Start <= r2.Start && r.End >= r2.End
+}
+
+// Intersect returns a range consisting of the intersection between r and r2.
+// If r and r2 do not overlap, Intersect returns a range with unspecified
+// bounds, but for which Length() == 0.
+func (r FileRange) Intersect(r2 FileRange) FileRange {
+ if r.Start < r2.Start {
+ r.Start = r2.Start
+ }
+ if r.End > r2.End {
+ r.End = r2.End
+ }
+ if r.End < r.Start {
+ r.End = r.Start
+ }
+ return r
+}
+
+// CanSplitAt returns true if it is legal to split a segment spanning the range
+// r at x; that is, splitting at x would produce two ranges, both of which have
+// non-zero length.
+func (r FileRange) CanSplitAt(x uint64) bool {
+ return r.Contains(x) && r.Start < x
+}
diff --git a/pkg/sentry/platform/interrupt/BUILD b/pkg/sentry/platform/interrupt/BUILD
deleted file mode 100644
index b6d008dbe..000000000
--- a/pkg/sentry/platform/interrupt/BUILD
+++ /dev/null
@@ -1,20 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "interrupt",
- srcs = [
- "interrupt.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/platform/interrupt",
- visibility = ["//pkg/sentry:internal"],
-)
-
-go_test(
- name = "interrupt_test",
- size = "small",
- srcs = ["interrupt_test.go"],
- embed = [":interrupt"],
-)
diff --git a/pkg/sentry/platform/interrupt/interrupt_state_autogen.go b/pkg/sentry/platform/interrupt/interrupt_state_autogen.go
new file mode 100755
index 000000000..15e8bacdf
--- /dev/null
+++ b/pkg/sentry/platform/interrupt/interrupt_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package interrupt
+
diff --git a/pkg/sentry/platform/interrupt/interrupt_test.go b/pkg/sentry/platform/interrupt/interrupt_test.go
deleted file mode 100644
index 0ecdf6e7a..000000000
--- a/pkg/sentry/platform/interrupt/interrupt_test.go
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package interrupt
-
-import (
- "testing"
-)
-
-type countingReceiver struct {
- interrupts int
-}
-
-// NotifyInterrupt implements Receiver.NotifyInterrupt.
-func (r *countingReceiver) NotifyInterrupt() {
- r.interrupts++
-}
-
-func TestSingleInterruptBeforeEnable(t *testing.T) {
- var (
- f Forwarder
- r countingReceiver
- )
- f.NotifyInterrupt()
- // The interrupt should cause the first Enable to fail.
- if f.Enable(&r) {
- f.Disable()
- t.Fatalf("Enable: got true, wanted false")
- }
- // The failing Enable "acknowledges" the interrupt, allowing future Enables
- // to succeed.
- if !f.Enable(&r) {
- t.Fatalf("Enable: got false, wanted true")
- }
- f.Disable()
-}
-
-func TestMultipleInterruptsBeforeEnable(t *testing.T) {
- var (
- f Forwarder
- r countingReceiver
- )
- f.NotifyInterrupt()
- f.NotifyInterrupt()
- // The interrupts should cause the first Enable to fail.
- if f.Enable(&r) {
- f.Disable()
- t.Fatalf("Enable: got true, wanted false")
- }
- // Interrupts are deduplicated while the Forwarder is disabled, so the
- // failing Enable "acknowledges" all interrupts, allowing future Enables to
- // succeed.
- if !f.Enable(&r) {
- t.Fatalf("Enable: got false, wanted true")
- }
- f.Disable()
-}
-
-func TestSingleInterruptAfterEnable(t *testing.T) {
- var (
- f Forwarder
- r countingReceiver
- )
- if !f.Enable(&r) {
- t.Fatalf("Enable: got false, wanted true")
- }
- defer f.Disable()
- f.NotifyInterrupt()
- if r.interrupts != 1 {
- t.Errorf("interrupts: got %d, wanted 1", r.interrupts)
- }
-}
-
-func TestMultipleInterruptsAfterEnable(t *testing.T) {
- var (
- f Forwarder
- r countingReceiver
- )
- if !f.Enable(&r) {
- t.Fatalf("Enable: got false, wanted true")
- }
- defer f.Disable()
- f.NotifyInterrupt()
- f.NotifyInterrupt()
- if r.interrupts != 2 {
- t.Errorf("interrupts: got %d, wanted 2", r.interrupts)
- }
-}
diff --git a/pkg/sentry/platform/kvm/BUILD b/pkg/sentry/platform/kvm/BUILD
deleted file mode 100644
index 31fa48ec5..000000000
--- a/pkg/sentry/platform/kvm/BUILD
+++ /dev/null
@@ -1,70 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "kvm",
- srcs = [
- "address_space.go",
- "allocator.go",
- "bluepill.go",
- "bluepill_amd64.go",
- "bluepill_amd64.s",
- "bluepill_amd64_unsafe.go",
- "bluepill_fault.go",
- "bluepill_unsafe.go",
- "context.go",
- "filters.go",
- "kvm.go",
- "kvm_amd64.go",
- "kvm_amd64_unsafe.go",
- "kvm_const.go",
- "machine.go",
- "machine_amd64.go",
- "machine_amd64_unsafe.go",
- "machine_unsafe.go",
- "physical_map.go",
- "virtual_map.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/platform/kvm",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/atomicbitops",
- "//pkg/cpuid",
- "//pkg/log",
- "//pkg/procid",
- "//pkg/seccomp",
- "//pkg/sentry/arch",
- "//pkg/sentry/platform",
- "//pkg/sentry/platform/interrupt",
- "//pkg/sentry/platform/ring0",
- "//pkg/sentry/platform/ring0/pagetables",
- "//pkg/sentry/platform/safecopy",
- "//pkg/sentry/time",
- "//pkg/sentry/usermem",
- ],
-)
-
-go_test(
- name = "kvm_test",
- srcs = [
- "kvm_test.go",
- "virtual_map_test.go",
- ],
- embed = [":kvm"],
- tags = [
- "manual",
- "nogotsan",
- "requires-kvm",
- ],
- deps = [
- "//pkg/sentry/arch",
- "//pkg/sentry/platform",
- "//pkg/sentry/platform/kvm/testutil",
- "//pkg/sentry/platform/ring0",
- "//pkg/sentry/platform/ring0/pagetables",
- "//pkg/sentry/usermem",
- ],
-)
diff --git a/pkg/sentry/platform/kvm/kvm_state_autogen.go b/pkg/sentry/platform/kvm/kvm_state_autogen.go
new file mode 100755
index 000000000..5ab0e0735
--- /dev/null
+++ b/pkg/sentry/platform/kvm/kvm_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package kvm
+
diff --git a/pkg/sentry/platform/kvm/kvm_test.go b/pkg/sentry/platform/kvm/kvm_test.go
deleted file mode 100644
index 30df725d4..000000000
--- a/pkg/sentry/platform/kvm/kvm_test.go
+++ /dev/null
@@ -1,533 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package kvm
-
-import (
- "math/rand"
- "reflect"
- "sync/atomic"
- "syscall"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/sentry/arch"
- "gvisor.dev/gvisor/pkg/sentry/platform"
- "gvisor.dev/gvisor/pkg/sentry/platform/kvm/testutil"
- "gvisor.dev/gvisor/pkg/sentry/platform/ring0"
- "gvisor.dev/gvisor/pkg/sentry/platform/ring0/pagetables"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
-)
-
-var dummyFPState = (*byte)(arch.NewFloatingPointData())
-
-type testHarness interface {
- Errorf(format string, args ...interface{})
- Fatalf(format string, args ...interface{})
-}
-
-func kvmTest(t testHarness, setup func(*KVM), fn func(*vCPU) bool) {
- // Create the machine.
- deviceFile, err := OpenDevice()
- if err != nil {
- t.Fatalf("error opening device file: %v", err)
- }
- k, err := New(deviceFile)
- if err != nil {
- t.Fatalf("error creating KVM instance: %v", err)
- }
- defer k.machine.Destroy()
-
- // Call additional setup.
- if setup != nil {
- setup(k)
- }
-
- var c *vCPU // For recovery.
- defer func() {
- redpill()
- if c != nil {
- k.machine.Put(c)
- }
- }()
- for {
- c = k.machine.Get()
- if !fn(c) {
- break
- }
-
- // We put the vCPU here and clear the value so that the
- // deferred recovery will not re-put it above.
- k.machine.Put(c)
- c = nil
- }
-}
-
-func bluepillTest(t testHarness, fn func(*vCPU)) {
- kvmTest(t, nil, func(c *vCPU) bool {
- bluepill(c)
- fn(c)
- return false
- })
-}
-
-func TestKernelSyscall(t *testing.T) {
- bluepillTest(t, func(c *vCPU) {
- redpill() // Leave guest mode.
- if got := atomic.LoadUint32(&c.state); got != vCPUUser {
- t.Errorf("vCPU not in ready state: got %v", got)
- }
- })
-}
-
-func hostFault() {
- defer func() {
- recover()
- }()
- var foo *int
- *foo = 0
-}
-
-func TestKernelFault(t *testing.T) {
- hostFault() // Ensure recovery works.
- bluepillTest(t, func(c *vCPU) {
- hostFault()
- if got := atomic.LoadUint32(&c.state); got != vCPUUser {
- t.Errorf("vCPU not in ready state: got %v", got)
- }
- })
-}
-
-func TestKernelFloatingPoint(t *testing.T) {
- bluepillTest(t, func(c *vCPU) {
- if !testutil.FloatingPointWorks() {
- t.Errorf("floating point does not work, and it should!")
- }
- })
-}
-
-func applicationTest(t testHarness, useHostMappings bool, target func(), fn func(*vCPU, *syscall.PtraceRegs, *pagetables.PageTables) bool) {
- // Initialize registers & page tables.
- var (
- regs syscall.PtraceRegs
- pt *pagetables.PageTables
- )
- testutil.SetTestTarget(&regs, target)
-
- kvmTest(t, func(k *KVM) {
- // Create new page tables.
- as, _, err := k.NewAddressSpace(nil /* invalidator */)
- if err != nil {
- t.Fatalf("can't create new address space: %v", err)
- }
- pt = as.(*addressSpace).pageTables
-
- if useHostMappings {
- // Apply the physical mappings to these page tables.
- // (This is normally dangerous, since they point to
- // physical pages that may not exist. This shouldn't be
- // done for regular user code, but is fine for test
- // purposes.)
- applyPhysicalRegions(func(pr physicalRegion) bool {
- pt.Map(usermem.Addr(pr.virtual), pr.length, pagetables.MapOpts{
- AccessType: usermem.AnyAccess,
- User: true,
- }, pr.physical)
- return true // Keep iterating.
- })
- }
- }, func(c *vCPU) bool {
- // Invoke the function with the extra data.
- return fn(c, &regs, pt)
- })
-}
-
-func TestApplicationSyscall(t *testing.T) {
- applicationTest(t, true, testutil.SyscallLoop, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool {
- var si arch.SignalInfo
- if _, err := c.SwitchToUser(ring0.SwitchOpts{
- Registers: regs,
- FloatingPointState: dummyFPState,
- PageTables: pt,
- FullRestore: true,
- }, &si); err == platform.ErrContextInterrupt {
- return true // Retry.
- } else if err != nil {
- t.Errorf("application syscall with full restore failed: %v", err)
- }
- return false
- })
- applicationTest(t, true, testutil.SyscallLoop, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool {
- var si arch.SignalInfo
- if _, err := c.SwitchToUser(ring0.SwitchOpts{
- Registers: regs,
- FloatingPointState: dummyFPState,
- PageTables: pt,
- }, &si); err == platform.ErrContextInterrupt {
- return true // Retry.
- } else if err != nil {
- t.Errorf("application syscall with partial restore failed: %v", err)
- }
- return false
- })
-}
-
-func TestApplicationFault(t *testing.T) {
- applicationTest(t, true, testutil.Touch, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool {
- testutil.SetTouchTarget(regs, nil) // Cause fault.
- var si arch.SignalInfo
- if _, err := c.SwitchToUser(ring0.SwitchOpts{
- Registers: regs,
- FloatingPointState: dummyFPState,
- PageTables: pt,
- FullRestore: true,
- }, &si); err == platform.ErrContextInterrupt {
- return true // Retry.
- } else if err != platform.ErrContextSignal || si.Signo != int32(syscall.SIGSEGV) {
- t.Errorf("application fault with full restore got (%v, %v), expected (%v, SIGSEGV)", err, si, platform.ErrContextSignal)
- }
- return false
- })
- applicationTest(t, true, testutil.Touch, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool {
- testutil.SetTouchTarget(regs, nil) // Cause fault.
- var si arch.SignalInfo
- if _, err := c.SwitchToUser(ring0.SwitchOpts{
- Registers: regs,
- FloatingPointState: dummyFPState,
- PageTables: pt,
- }, &si); err == platform.ErrContextInterrupt {
- return true // Retry.
- } else if err != platform.ErrContextSignal || si.Signo != int32(syscall.SIGSEGV) {
- t.Errorf("application fault with partial restore got (%v, %v), expected (%v, SIGSEGV)", err, si, platform.ErrContextSignal)
- }
- return false
- })
-}
-
-func TestRegistersSyscall(t *testing.T) {
- applicationTest(t, true, testutil.TwiddleRegsSyscall, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool {
- testutil.SetTestRegs(regs) // Fill values for all registers.
- for {
- var si arch.SignalInfo
- if _, err := c.SwitchToUser(ring0.SwitchOpts{
- Registers: regs,
- FloatingPointState: dummyFPState,
- PageTables: pt,
- }, &si); err == platform.ErrContextInterrupt {
- continue // Retry.
- } else if err != nil {
- t.Errorf("application register check with partial restore got unexpected error: %v", err)
- }
- if err := testutil.CheckTestRegs(regs, false); err != nil {
- t.Errorf("application register check with partial restore failed: %v", err)
- }
- break // Done.
- }
- return false
- })
-}
-
-func TestRegistersFault(t *testing.T) {
- applicationTest(t, true, testutil.TwiddleRegsFault, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool {
- testutil.SetTestRegs(regs) // Fill values for all registers.
- for {
- var si arch.SignalInfo
- if _, err := c.SwitchToUser(ring0.SwitchOpts{
- Registers: regs,
- FloatingPointState: dummyFPState,
- PageTables: pt,
- FullRestore: true,
- }, &si); err == platform.ErrContextInterrupt {
- continue // Retry.
- } else if err != platform.ErrContextSignal || si.Signo != int32(syscall.SIGSEGV) {
- t.Errorf("application register check with full restore got unexpected error: %v", err)
- }
- if err := testutil.CheckTestRegs(regs, true); err != nil {
- t.Errorf("application register check with full restore failed: %v", err)
- }
- break // Done.
- }
- return false
- })
-}
-
-func TestSegments(t *testing.T) {
- applicationTest(t, true, testutil.TwiddleSegments, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool {
- testutil.SetTestSegments(regs)
- for {
- var si arch.SignalInfo
- if _, err := c.SwitchToUser(ring0.SwitchOpts{
- Registers: regs,
- FloatingPointState: dummyFPState,
- PageTables: pt,
- FullRestore: true,
- }, &si); err == platform.ErrContextInterrupt {
- continue // Retry.
- } else if err != nil {
- t.Errorf("application segment check with full restore got unexpected error: %v", err)
- }
- if err := testutil.CheckTestSegments(regs); err != nil {
- t.Errorf("application segment check with full restore failed: %v", err)
- }
- break // Done.
- }
- return false
- })
-}
-
-func TestBounce(t *testing.T) {
- applicationTest(t, true, testutil.SpinLoop, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool {
- go func() {
- time.Sleep(time.Millisecond)
- c.BounceToKernel()
- }()
- var si arch.SignalInfo
- if _, err := c.SwitchToUser(ring0.SwitchOpts{
- Registers: regs,
- FloatingPointState: dummyFPState,
- PageTables: pt,
- }, &si); err != platform.ErrContextInterrupt {
- t.Errorf("application partial restore: got %v, wanted %v", err, platform.ErrContextInterrupt)
- }
- return false
- })
- applicationTest(t, true, testutil.SpinLoop, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool {
- go func() {
- time.Sleep(time.Millisecond)
- c.BounceToKernel()
- }()
- var si arch.SignalInfo
- if _, err := c.SwitchToUser(ring0.SwitchOpts{
- Registers: regs,
- FloatingPointState: dummyFPState,
- PageTables: pt,
- FullRestore: true,
- }, &si); err != platform.ErrContextInterrupt {
- t.Errorf("application full restore: got %v, wanted %v", err, platform.ErrContextInterrupt)
- }
- return false
- })
-}
-
-func TestBounceStress(t *testing.T) {
- applicationTest(t, true, testutil.SpinLoop, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool {
- randomSleep := func() {
- // O(hundreds of microseconds) is appropriate to ensure
- // different overlaps and different schedules.
- if n := rand.Intn(1000); n > 100 {
- time.Sleep(time.Duration(n) * time.Microsecond)
- }
- }
- for i := 0; i < 1000; i++ {
- // Start an asynchronously executing goroutine that
- // calls Bounce at pseudo-random point in time.
- // This should wind up calling Bounce when the
- // kernel is in various stages of the switch.
- go func() {
- randomSleep()
- c.BounceToKernel()
- }()
- randomSleep()
- var si arch.SignalInfo
- if _, err := c.SwitchToUser(ring0.SwitchOpts{
- Registers: regs,
- FloatingPointState: dummyFPState,
- PageTables: pt,
- }, &si); err != platform.ErrContextInterrupt {
- t.Errorf("application partial restore: got %v, wanted %v", err, platform.ErrContextInterrupt)
- }
- c.unlock()
- randomSleep()
- c.lock()
- }
- return false
- })
-}
-
-func TestInvalidate(t *testing.T) {
- var data uintptr // Used below.
- applicationTest(t, true, testutil.Touch, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool {
- testutil.SetTouchTarget(regs, &data) // Read legitimate value.
- for {
- var si arch.SignalInfo
- if _, err := c.SwitchToUser(ring0.SwitchOpts{
- Registers: regs,
- FloatingPointState: dummyFPState,
- PageTables: pt,
- }, &si); err == platform.ErrContextInterrupt {
- continue // Retry.
- } else if err != nil {
- t.Errorf("application partial restore: got %v, wanted nil", err)
- }
- break // Done.
- }
- // Unmap the page containing data & invalidate.
- pt.Unmap(usermem.Addr(reflect.ValueOf(&data).Pointer() & ^uintptr(usermem.PageSize-1)), usermem.PageSize)
- for {
- var si arch.SignalInfo
- if _, err := c.SwitchToUser(ring0.SwitchOpts{
- Registers: regs,
- FloatingPointState: dummyFPState,
- PageTables: pt,
- Flush: true,
- }, &si); err == platform.ErrContextInterrupt {
- continue // Retry.
- } else if err != platform.ErrContextSignal {
- t.Errorf("application partial restore: got %v, wanted %v", err, platform.ErrContextSignal)
- }
- break // Success.
- }
- return false
- })
-}
-
-// IsFault returns true iff the given signal represents a fault.
-func IsFault(err error, si *arch.SignalInfo) bool {
- return err == platform.ErrContextSignal && si.Signo == int32(syscall.SIGSEGV)
-}
-
-func TestEmptyAddressSpace(t *testing.T) {
- applicationTest(t, false, testutil.SyscallLoop, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool {
- var si arch.SignalInfo
- if _, err := c.SwitchToUser(ring0.SwitchOpts{
- Registers: regs,
- FloatingPointState: dummyFPState,
- PageTables: pt,
- }, &si); err == platform.ErrContextInterrupt {
- return true // Retry.
- } else if !IsFault(err, &si) {
- t.Errorf("first fault with partial restore failed got %v", err)
- t.Logf("registers: %#v", &regs)
- }
- return false
- })
- applicationTest(t, false, testutil.SyscallLoop, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool {
- var si arch.SignalInfo
- if _, err := c.SwitchToUser(ring0.SwitchOpts{
- Registers: regs,
- FloatingPointState: dummyFPState,
- PageTables: pt,
- FullRestore: true,
- }, &si); err == platform.ErrContextInterrupt {
- return true // Retry.
- } else if !IsFault(err, &si) {
- t.Errorf("first fault with full restore failed got %v", err)
- t.Logf("registers: %#v", &regs)
- }
- return false
- })
-}
-
-func TestWrongVCPU(t *testing.T) {
- kvmTest(t, nil, func(c1 *vCPU) bool {
- kvmTest(t, nil, func(c2 *vCPU) bool {
- // Basic test, one then the other.
- bluepill(c1)
- bluepill(c2)
- if c2.switches == 0 {
- // Don't allow the test to proceed if this fails.
- t.Fatalf("wrong vCPU#2 switches: vCPU1=%+v,vCPU2=%+v", c1, c2)
- }
-
- // Alternate vCPUs; we expect to need to trigger the
- // wrong vCPU path on each switch.
- for i := 0; i < 100; i++ {
- bluepill(c1)
- bluepill(c2)
- }
- if count := c1.switches; count < 90 {
- t.Errorf("wrong vCPU#1 switches: vCPU1=%+v,vCPU2=%+v", c1, c2)
- }
- if count := c2.switches; count < 90 {
- t.Errorf("wrong vCPU#2 switches: vCPU1=%+v,vCPU2=%+v", c1, c2)
- }
- return false
- })
- return false
- })
- kvmTest(t, nil, func(c1 *vCPU) bool {
- kvmTest(t, nil, func(c2 *vCPU) bool {
- bluepill(c1)
- bluepill(c2)
- return false
- })
- return false
- })
-}
-
-func BenchmarkApplicationSyscall(b *testing.B) {
- var (
- i int // Iteration includes machine.Get() / machine.Put().
- a int // Count for ErrContextInterrupt.
- )
- applicationTest(b, true, testutil.SyscallLoop, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool {
- var si arch.SignalInfo
- if _, err := c.SwitchToUser(ring0.SwitchOpts{
- Registers: regs,
- FloatingPointState: dummyFPState,
- PageTables: pt,
- }, &si); err == platform.ErrContextInterrupt {
- a++
- return true // Ignore.
- } else if err != nil {
- b.Fatalf("benchmark failed: %v", err)
- }
- i++
- return i < b.N
- })
- if a != 0 {
- b.Logf("ErrContextInterrupt occurred %d times (in %d iterations).", a, a+i)
- }
-}
-
-func BenchmarkKernelSyscall(b *testing.B) {
- // Note that the target passed here is irrelevant, we never execute SwitchToUser.
- applicationTest(b, true, testutil.Getpid, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool {
- // iteration does not include machine.Get() / machine.Put().
- for i := 0; i < b.N; i++ {
- testutil.Getpid()
- }
- return false
- })
-}
-
-func BenchmarkWorldSwitchToUserRoundtrip(b *testing.B) {
- // see BenchmarkApplicationSyscall.
- var (
- i int
- a int
- )
- applicationTest(b, true, testutil.SyscallLoop, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool {
- var si arch.SignalInfo
- if _, err := c.SwitchToUser(ring0.SwitchOpts{
- Registers: regs,
- FloatingPointState: dummyFPState,
- PageTables: pt,
- }, &si); err == platform.ErrContextInterrupt {
- a++
- return true // Ignore.
- } else if err != nil {
- b.Fatalf("benchmark failed: %v", err)
- }
- // This will intentionally cause the world switch. By executing
- // a host syscall here, we force the transition between guest
- // and host mode.
- testutil.Getpid()
- i++
- return i < b.N
- })
- if a != 0 {
- b.Logf("ErrContextInterrupt occurred %d times (in %d iterations).", a, a+i)
- }
-}
diff --git a/pkg/sentry/platform/kvm/testutil/BUILD b/pkg/sentry/platform/kvm/testutil/BUILD
deleted file mode 100644
index 77a449a8b..000000000
--- a/pkg/sentry/platform/kvm/testutil/BUILD
+++ /dev/null
@@ -1,15 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "testutil",
- testonly = 1,
- srcs = [
- "testutil.go",
- "testutil_amd64.go",
- "testutil_amd64.s",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/platform/kvm/testutil",
- visibility = ["//pkg/sentry/platform/kvm:__pkg__"],
-)
diff --git a/pkg/sentry/platform/kvm/testutil/testutil.go b/pkg/sentry/platform/kvm/testutil/testutil.go
deleted file mode 100644
index 6cf2359a3..000000000
--- a/pkg/sentry/platform/kvm/testutil/testutil.go
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package testutil provides common assembly stubs for testing.
-package testutil
-
-import (
- "fmt"
- "strings"
-)
-
-// Getpid executes a trivial system call.
-func Getpid()
-
-// Touch touches the value in the first register.
-func Touch()
-
-// SyscallLoop executes a syscall and loops.
-func SyscallLoop()
-
-// SpinLoop spins on the CPU.
-func SpinLoop()
-
-// HaltLoop immediately halts and loops.
-func HaltLoop()
-
-// TwiddleRegsFault twiddles registers then faults.
-func TwiddleRegsFault()
-
-// TwiddleRegsSyscall twiddles registers then executes a syscall.
-func TwiddleRegsSyscall()
-
-// TwiddleSegments reads segments into known registers.
-func TwiddleSegments()
-
-// FloatingPointWorks is a floating point test.
-//
-// It returns true or false.
-func FloatingPointWorks() bool
-
-// RegisterMismatchError is used for checking registers.
-type RegisterMismatchError []string
-
-// Error returns a human-readable error.
-func (r RegisterMismatchError) Error() string {
- return strings.Join([]string(r), ";")
-}
-
-// addRegisterMisatch allows simple chaining of register mismatches.
-func addRegisterMismatch(err error, reg string, got, expected interface{}) error {
- errStr := fmt.Sprintf("%s got %08x, expected %08x", reg, got, expected)
- switch r := err.(type) {
- case nil:
- // Return a new register mismatch.
- return RegisterMismatchError{errStr}
- case RegisterMismatchError:
- // Append the error.
- r = append(r, errStr)
- return r
- default:
- // Leave as is.
- return err
- }
-}
diff --git a/pkg/sentry/platform/kvm/testutil/testutil_amd64.go b/pkg/sentry/platform/kvm/testutil/testutil_amd64.go
deleted file mode 100644
index 203d71528..000000000
--- a/pkg/sentry/platform/kvm/testutil/testutil_amd64.go
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// +build amd64
-
-package testutil
-
-import (
- "reflect"
- "syscall"
-)
-
-// SetTestTarget sets the rip appropriately.
-func SetTestTarget(regs *syscall.PtraceRegs, fn func()) {
- regs.Rip = uint64(reflect.ValueOf(fn).Pointer())
-}
-
-// SetTouchTarget sets rax appropriately.
-func SetTouchTarget(regs *syscall.PtraceRegs, target *uintptr) {
- if target != nil {
- regs.Rax = uint64(reflect.ValueOf(target).Pointer())
- } else {
- regs.Rax = 0
- }
-}
-
-// RewindSyscall rewinds a syscall RIP.
-func RewindSyscall(regs *syscall.PtraceRegs) {
- regs.Rip -= 2
-}
-
-// SetTestRegs initializes registers to known values.
-func SetTestRegs(regs *syscall.PtraceRegs) {
- regs.R15 = 0x15
- regs.R14 = 0x14
- regs.R13 = 0x13
- regs.R12 = 0x12
- regs.Rbp = 0xb9
- regs.Rbx = 0xb4
- regs.R11 = 0x11
- regs.R10 = 0x10
- regs.R9 = 0x09
- regs.R8 = 0x08
- regs.Rax = 0x44
- regs.Rcx = 0xc4
- regs.Rdx = 0xd4
- regs.Rsi = 0x51
- regs.Rdi = 0xd1
- regs.Rsp = 0x59
-}
-
-// CheckTestRegs checks that registers were twiddled per TwiddleRegs.
-func CheckTestRegs(regs *syscall.PtraceRegs, full bool) (err error) {
- if need := ^uint64(0x15); regs.R15 != need {
- err = addRegisterMismatch(err, "R15", regs.R15, need)
- }
- if need := ^uint64(0x14); regs.R14 != need {
- err = addRegisterMismatch(err, "R14", regs.R14, need)
- }
- if need := ^uint64(0x13); regs.R13 != need {
- err = addRegisterMismatch(err, "R13", regs.R13, need)
- }
- if need := ^uint64(0x12); regs.R12 != need {
- err = addRegisterMismatch(err, "R12", regs.R12, need)
- }
- if need := ^uint64(0xb9); regs.Rbp != need {
- err = addRegisterMismatch(err, "Rbp", regs.Rbp, need)
- }
- if need := ^uint64(0xb4); regs.Rbx != need {
- err = addRegisterMismatch(err, "Rbx", regs.Rbx, need)
- }
- if need := ^uint64(0x10); regs.R10 != need {
- err = addRegisterMismatch(err, "R10", regs.R10, need)
- }
- if need := ^uint64(0x09); regs.R9 != need {
- err = addRegisterMismatch(err, "R9", regs.R9, need)
- }
- if need := ^uint64(0x08); regs.R8 != need {
- err = addRegisterMismatch(err, "R8", regs.R8, need)
- }
- if need := ^uint64(0x44); regs.Rax != need {
- err = addRegisterMismatch(err, "Rax", regs.Rax, need)
- }
- if need := ^uint64(0xd4); regs.Rdx != need {
- err = addRegisterMismatch(err, "Rdx", regs.Rdx, need)
- }
- if need := ^uint64(0x51); regs.Rsi != need {
- err = addRegisterMismatch(err, "Rsi", regs.Rsi, need)
- }
- if need := ^uint64(0xd1); regs.Rdi != need {
- err = addRegisterMismatch(err, "Rdi", regs.Rdi, need)
- }
- if need := ^uint64(0x59); regs.Rsp != need {
- err = addRegisterMismatch(err, "Rsp", regs.Rsp, need)
- }
- // Rcx & R11 are ignored if !full is set.
- if need := ^uint64(0x11); full && regs.R11 != need {
- err = addRegisterMismatch(err, "R11", regs.R11, need)
- }
- if need := ^uint64(0xc4); full && regs.Rcx != need {
- err = addRegisterMismatch(err, "Rcx", regs.Rcx, need)
- }
- return
-}
-
-var fsData uint64 = 0x55
-var gsData uint64 = 0x85
-
-// SetTestSegments initializes segments to known values.
-func SetTestSegments(regs *syscall.PtraceRegs) {
- regs.Fs_base = uint64(reflect.ValueOf(&fsData).Pointer())
- regs.Gs_base = uint64(reflect.ValueOf(&gsData).Pointer())
-}
-
-// CheckTestSegments checks that registers were twiddled per TwiddleSegments.
-func CheckTestSegments(regs *syscall.PtraceRegs) (err error) {
- if regs.Rax != fsData {
- err = addRegisterMismatch(err, "Rax", regs.Rax, fsData)
- }
- if regs.Rbx != gsData {
- err = addRegisterMismatch(err, "Rbx", regs.Rcx, gsData)
- }
- return
-}
diff --git a/pkg/sentry/platform/kvm/testutil/testutil_amd64.s b/pkg/sentry/platform/kvm/testutil/testutil_amd64.s
deleted file mode 100644
index 491ec0c2a..000000000
--- a/pkg/sentry/platform/kvm/testutil/testutil_amd64.s
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// +build amd64
-
-// test_util_amd64.s provides AMD64 test functions.
-
-#include "funcdata.h"
-#include "textflag.h"
-
-TEXT ·Getpid(SB),NOSPLIT,$0
- NO_LOCAL_POINTERS
- MOVQ $39, AX // getpid
- SYSCALL
- RET
-
-TEXT ·Touch(SB),NOSPLIT,$0
-start:
- MOVQ 0(AX), BX // deref AX
- MOVQ $39, AX // getpid
- SYSCALL
- JMP start
-
-TEXT ·HaltLoop(SB),NOSPLIT,$0
-start:
- HLT
- JMP start
-
-TEXT ·SyscallLoop(SB),NOSPLIT,$0
-start:
- SYSCALL
- JMP start
-
-TEXT ·SpinLoop(SB),NOSPLIT,$0
-start:
- JMP start
-
-TEXT ·FloatingPointWorks(SB),NOSPLIT,$0-8
- NO_LOCAL_POINTERS
- MOVQ $1, AX
- MOVQ AX, X0
- MOVQ $39, AX // getpid
- SYSCALL
- MOVQ X0, AX
- CMPQ AX, $1
- SETEQ ret+0(FP)
- RET
-
-#define TWIDDLE_REGS() \
- NOTQ R15; \
- NOTQ R14; \
- NOTQ R13; \
- NOTQ R12; \
- NOTQ BP; \
- NOTQ BX; \
- NOTQ R11; \
- NOTQ R10; \
- NOTQ R9; \
- NOTQ R8; \
- NOTQ AX; \
- NOTQ CX; \
- NOTQ DX; \
- NOTQ SI; \
- NOTQ DI; \
- NOTQ SP;
-
-TEXT ·TwiddleRegsSyscall(SB),NOSPLIT,$0
- TWIDDLE_REGS()
- SYSCALL
- RET // never reached
-
-TEXT ·TwiddleRegsFault(SB),NOSPLIT,$0
- TWIDDLE_REGS()
- JMP AX // must fault
- RET // never reached
-
-#define READ_FS() BYTE $0x64; BYTE $0x48; BYTE $0x8b; BYTE $0x00;
-#define READ_GS() BYTE $0x65; BYTE $0x48; BYTE $0x8b; BYTE $0x00;
-
-TEXT ·TwiddleSegments(SB),NOSPLIT,$0
- MOVQ $0x0, AX
- READ_GS()
- MOVQ AX, BX
- MOVQ $0x0, AX
- READ_FS()
- SYSCALL
- RET // never reached
diff --git a/pkg/sentry/platform/kvm/virtual_map_test.go b/pkg/sentry/platform/kvm/virtual_map_test.go
deleted file mode 100644
index 6a2f145be..000000000
--- a/pkg/sentry/platform/kvm/virtual_map_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package kvm
-
-import (
- "syscall"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/usermem"
-)
-
-type checker struct {
- ok bool
- accessType usermem.AccessType
-}
-
-func (c *checker) Containing(addr uintptr) func(virtualRegion) {
- c.ok = false // Reset for below calls.
- return func(vr virtualRegion) {
- if vr.virtual <= addr && addr < vr.virtual+vr.length {
- c.ok = true
- c.accessType = vr.accessType
- }
- }
-}
-
-func TestParseMaps(t *testing.T) {
- c := new(checker)
-
- // Simple test.
- if err := applyVirtualRegions(c.Containing(0)); err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
-
- // MMap a new page.
- addr, _, errno := syscall.RawSyscall6(
- syscall.SYS_MMAP, 0, usermem.PageSize,
- syscall.PROT_READ|syscall.PROT_WRITE,
- syscall.MAP_ANONYMOUS|syscall.MAP_PRIVATE, 0, 0)
- if errno != 0 {
- t.Fatalf("unexpected map error: %v", errno)
- }
-
- // Re-parse maps.
- if err := applyVirtualRegions(c.Containing(addr)); err != nil {
- syscall.RawSyscall(syscall.SYS_MUNMAP, addr, usermem.PageSize, 0)
- t.Fatalf("unexpected error: %v", err)
- }
-
- // Assert that it now does contain the region.
- if !c.ok {
- syscall.RawSyscall(syscall.SYS_MUNMAP, addr, usermem.PageSize, 0)
- t.Fatalf("updated map does not contain 0x%08x, expected true", addr)
- }
-
- // Map the region as PROT_NONE.
- newAddr, _, errno := syscall.RawSyscall6(
- syscall.SYS_MMAP, addr, usermem.PageSize,
- syscall.PROT_NONE,
- syscall.MAP_ANONYMOUS|syscall.MAP_FIXED|syscall.MAP_PRIVATE, 0, 0)
- if errno != 0 {
- t.Fatalf("unexpected map error: %v", errno)
- }
- if newAddr != addr {
- t.Fatalf("unable to remap address: got 0x%08x, wanted 0x%08x", newAddr, addr)
- }
-
- // Re-parse maps.
- if err := applyVirtualRegions(c.Containing(addr)); err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- if !c.ok {
- t.Fatalf("final map does not contain 0x%08x, expected true", addr)
- }
- if c.accessType.Read || c.accessType.Write || c.accessType.Execute {
- t.Fatalf("final map has incorrect permissions for 0x%08x", addr)
- }
-
- // Unmap the region.
- syscall.RawSyscall(syscall.SYS_MUNMAP, addr, usermem.PageSize, 0)
-}
diff --git a/pkg/sentry/platform/platform_state_autogen.go b/pkg/sentry/platform/platform_state_autogen.go
new file mode 100755
index 000000000..28dcd1764
--- /dev/null
+++ b/pkg/sentry/platform/platform_state_autogen.go
@@ -0,0 +1,24 @@
+// automatically generated by stateify.
+
+package platform
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *FileRange) beforeSave() {}
+func (x *FileRange) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Start", &x.Start)
+ m.Save("End", &x.End)
+}
+
+func (x *FileRange) afterLoad() {}
+func (x *FileRange) load(m state.Map) {
+ m.Load("Start", &x.Start)
+ m.Load("End", &x.End)
+}
+
+func init() {
+ state.Register("platform.FileRange", (*FileRange)(nil), state.Fns{Save: (*FileRange).save, Load: (*FileRange).load})
+}
diff --git a/pkg/sentry/platform/ptrace/BUILD b/pkg/sentry/platform/ptrace/BUILD
deleted file mode 100644
index ebcc8c098..000000000
--- a/pkg/sentry/platform/ptrace/BUILD
+++ /dev/null
@@ -1,37 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "ptrace",
- srcs = [
- "filters.go",
- "ptrace.go",
- "ptrace_amd64.go",
- "ptrace_arm64.go",
- "ptrace_unsafe.go",
- "stub_amd64.s",
- "stub_arm64.s",
- "stub_unsafe.go",
- "subprocess.go",
- "subprocess_amd64.go",
- "subprocess_arm64.go",
- "subprocess_linux.go",
- "subprocess_linux_unsafe.go",
- "subprocess_unsafe.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/platform/ptrace",
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/log",
- "//pkg/procid",
- "//pkg/seccomp",
- "//pkg/sentry/arch",
- "//pkg/sentry/platform",
- "//pkg/sentry/platform/interrupt",
- "//pkg/sentry/platform/safecopy",
- "//pkg/sentry/usermem",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
diff --git a/pkg/sentry/platform/ptrace/ptrace_state_autogen.go b/pkg/sentry/platform/ptrace/ptrace_state_autogen.go
new file mode 100755
index 000000000..ac83a71e7
--- /dev/null
+++ b/pkg/sentry/platform/ptrace/ptrace_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package ptrace
+
diff --git a/pkg/sentry/platform/ring0/BUILD b/pkg/sentry/platform/ring0/BUILD
deleted file mode 100644
index 8ed6c7652..000000000
--- a/pkg/sentry/platform/ring0/BUILD
+++ /dev/null
@@ -1,53 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template", "go_template_instance")
-
-go_template(
- name = "defs",
- srcs = [
- "defs.go",
- "defs_amd64.go",
- "offsets_amd64.go",
- "x86.go",
- ],
- visibility = [":__subpackages__"],
-)
-
-go_template_instance(
- name = "defs_impl",
- out = "defs_impl.go",
- package = "ring0",
- template = ":defs",
-)
-
-genrule(
- name = "entry_impl_amd64",
- srcs = ["entry_amd64.s"],
- outs = ["entry_impl_amd64.s"],
- cmd = "(echo -e '// build +amd64\\n' && $(location //pkg/sentry/platform/ring0/gen_offsets) && cat $(SRCS)) > $@",
- tools = ["//pkg/sentry/platform/ring0/gen_offsets"],
-)
-
-go_library(
- name = "ring0",
- srcs = [
- "defs_impl.go",
- "entry_amd64.go",
- "entry_impl_amd64.s",
- "kernel.go",
- "kernel_amd64.go",
- "kernel_unsafe.go",
- "lib_amd64.go",
- "lib_amd64.s",
- "ring0.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/platform/ring0",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/cpuid",
- "//pkg/sentry/platform/ring0/pagetables",
- "//pkg/sentry/usermem",
- ],
-)
diff --git a/pkg/sentry/platform/ring0/defs.go b/pkg/sentry/platform/ring0/defs.go
deleted file mode 100644
index 076063f85..000000000
--- a/pkg/sentry/platform/ring0/defs.go
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ring0
-
-import (
- "syscall"
-
- "gvisor.dev/gvisor/pkg/sentry/usermem"
-)
-
-var (
- // UserspaceSize is the total size of userspace.
- UserspaceSize = uintptr(1) << (VirtualAddressBits() - 1)
-
- // MaximumUserAddress is the largest possible user address.
- MaximumUserAddress = (UserspaceSize - 1) & ^uintptr(usermem.PageSize-1)
-
- // KernelStartAddress is the starting kernel address.
- KernelStartAddress = ^uintptr(0) - (UserspaceSize - 1)
-)
-
-// Kernel is a global kernel object.
-//
-// This contains global state, shared by multiple CPUs.
-type Kernel struct {
- KernelArchState
-}
-
-// Hooks are hooks for kernel functions.
-type Hooks interface {
- // KernelSyscall is called for kernel system calls.
- //
- // Return from this call will restore registers and return to the kernel: the
- // registers must be modified directly.
- //
- // If this function is not provided, a kernel exception results in halt.
- //
- // This must be go:nosplit, as this will be on the interrupt stack.
- // Closures are permitted, as the pointer to the closure frame is not
- // passed on the stack.
- KernelSyscall()
-
- // KernelException handles an exception during kernel execution.
- //
- // Return from this call will restore registers and return to the kernel: the
- // registers must be modified directly.
- //
- // If this function is not provided, a kernel exception results in halt.
- //
- // This must be go:nosplit, as this will be on the interrupt stack.
- // Closures are permitted, as the pointer to the closure frame is not
- // passed on the stack.
- KernelException(Vector)
-}
-
-// CPU is the per-CPU struct.
-type CPU struct {
- // self is a self reference.
- //
- // This is always guaranteed to be at offset zero.
- self *CPU
-
- // kernel is reference to the kernel that this CPU was initialized
- // with. This reference is kept for garbage collection purposes: CPU
- // registers may refer to objects within the Kernel object that cannot
- // be safely freed.
- kernel *Kernel
-
- // CPUArchState is architecture-specific state.
- CPUArchState
-
- // registers is a set of registers; these may be used on kernel system
- // calls and exceptions via the Registers function.
- registers syscall.PtraceRegs
-
- // hooks are kernel hooks.
- hooks Hooks
-}
-
-// Registers returns a modifiable-copy of the kernel registers.
-//
-// This is explicitly safe to call during KernelException and KernelSyscall.
-//
-//go:nosplit
-func (c *CPU) Registers() *syscall.PtraceRegs {
- return &c.registers
-}
-
-// SwitchOpts are passed to the Switch function.
-type SwitchOpts struct {
- // Registers are the user register state.
- Registers *syscall.PtraceRegs
-
- // FloatingPointState is a byte pointer where floating point state is
- // saved and restored.
- FloatingPointState *byte
-
- // PageTables are the application page tables.
- PageTables *pagetables.PageTables
-
- // Flush indicates that a TLB flush should be forced on switch.
- Flush bool
-
- // FullRestore indicates that an iret-based restore should be used.
- FullRestore bool
-
- // SwitchArchOpts are architecture-specific options.
- SwitchArchOpts
-}
diff --git a/pkg/sentry/platform/ring0/defs_amd64.go b/pkg/sentry/platform/ring0/defs_amd64.go
deleted file mode 100644
index 7206322b1..000000000
--- a/pkg/sentry/platform/ring0/defs_amd64.go
+++ /dev/null
@@ -1,136 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// +build amd64
-
-package ring0
-
-import (
- "gvisor.dev/gvisor/pkg/sentry/platform/ring0/pagetables"
-)
-
-// Segment indices and Selectors.
-const (
- // Index into GDT array.
- _ = iota // Null descriptor first.
- _ // Reserved (Linux is kernel 32).
- segKcode // Kernel code (64-bit).
- segKdata // Kernel data.
- segUcode32 // User code (32-bit).
- segUdata // User data.
- segUcode64 // User code (64-bit).
- segTss // Task segment descriptor.
- segTssHi // Upper bits for TSS.
- segLast // Last segment (terminal, not included).
-)
-
-// Selectors.
-const (
- Kcode Selector = segKcode << 3
- Kdata Selector = segKdata << 3
- Ucode32 Selector = (segUcode32 << 3) | 3
- Udata Selector = (segUdata << 3) | 3
- Ucode64 Selector = (segUcode64 << 3) | 3
- Tss Selector = segTss << 3
-)
-
-// Standard segments.
-var (
- UserCodeSegment32 SegmentDescriptor
- UserDataSegment SegmentDescriptor
- UserCodeSegment64 SegmentDescriptor
- KernelCodeSegment SegmentDescriptor
- KernelDataSegment SegmentDescriptor
-)
-
-// KernelOpts has initialization options for the kernel.
-type KernelOpts struct {
- // PageTables are the kernel pagetables; this must be provided.
- PageTables *pagetables.PageTables
-}
-
-// KernelArchState contains architecture-specific state.
-type KernelArchState struct {
- KernelOpts
-
- // globalIDT is our set of interrupt gates.
- globalIDT idt64
-}
-
-// CPUArchState contains CPU-specific arch state.
-type CPUArchState struct {
- // stack is the stack used for interrupts on this CPU.
- stack [256]byte
-
- // errorCode is the error code from the last exception.
- errorCode uintptr
-
- // errorType indicates the type of error code here, it is always set
- // along with the errorCode value above.
- //
- // It will either by 1, which indicates a user error, or 0 indicating a
- // kernel error. If the error code below returns false (kernel error),
- // then it cannot provide relevant information about the last
- // exception.
- errorType uintptr
-
- // gdt is the CPU's descriptor table.
- gdt descriptorTable
-
- // tss is the CPU's task state.
- tss TaskState64
-}
-
-// ErrorCode returns the last error code.
-//
-// The returned boolean indicates whether the error code corresponds to the
-// last user error or not. If it does not, then fault information must be
-// ignored. This is generally the result of a kernel fault while servicing a
-// user fault.
-//
-//go:nosplit
-func (c *CPU) ErrorCode() (value uintptr, user bool) {
- return c.errorCode, c.errorType != 0
-}
-
-// ClearErrorCode resets the error code.
-//
-//go:nosplit
-func (c *CPU) ClearErrorCode() {
- c.errorCode = 0 // No code.
- c.errorType = 1 // User mode.
-}
-
-// SwitchArchOpts are embedded in SwitchOpts.
-type SwitchArchOpts struct {
- // UserPCID indicates that the application PCID to be used on switch,
- // assuming that PCIDs are supported.
- //
- // Per pagetables_x86.go, a zero PCID implies a flush.
- UserPCID uint16
-
- // KernelPCID indicates that the kernel PCID to be used on return,
- // assuming that PCIDs are supported.
- //
- // Per pagetables_x86.go, a zero PCID implies a flush.
- KernelPCID uint16
-}
-
-func init() {
- KernelCodeSegment.setCode64(0, 0, 0)
- KernelDataSegment.setData(0, 0xffffffff, 0)
- UserCodeSegment32.setCode64(0, 0, 3)
- UserDataSegment.setData(0, 0xffffffff, 3)
- UserCodeSegment64.setCode64(0, 0, 3)
-}
diff --git a/pkg/sentry/platform/ring0/defs_impl.go b/pkg/sentry/platform/ring0/defs_impl.go
new file mode 100755
index 000000000..a36a17e37
--- /dev/null
+++ b/pkg/sentry/platform/ring0/defs_impl.go
@@ -0,0 +1,538 @@
+package ring0
+
+import (
+ "gvisor.dev/gvisor/pkg/cpuid"
+ "reflect"
+ "syscall"
+
+ "fmt"
+ "gvisor.dev/gvisor/pkg/sentry/platform/ring0/pagetables"
+ "gvisor.dev/gvisor/pkg/sentry/usermem"
+ "io"
+)
+
+var (
+ // UserspaceSize is the total size of userspace.
+ UserspaceSize = uintptr(1) << (VirtualAddressBits() - 1)
+
+ // MaximumUserAddress is the largest possible user address.
+ MaximumUserAddress = (UserspaceSize - 1) & ^uintptr(usermem.PageSize-1)
+
+ // KernelStartAddress is the starting kernel address.
+ KernelStartAddress = ^uintptr(0) - (UserspaceSize - 1)
+)
+
+// Kernel is a global kernel object.
+//
+// This contains global state, shared by multiple CPUs.
+type Kernel struct {
+ KernelArchState
+}
+
+// Hooks are hooks for kernel functions.
+type Hooks interface {
+ // KernelSyscall is called for kernel system calls.
+ //
+ // Return from this call will restore registers and return to the kernel: the
+ // registers must be modified directly.
+ //
+ // If this function is not provided, a kernel exception results in halt.
+ //
+ // This must be go:nosplit, as this will be on the interrupt stack.
+ // Closures are permitted, as the pointer to the closure frame is not
+ // passed on the stack.
+ KernelSyscall()
+
+ // KernelException handles an exception during kernel execution.
+ //
+ // Return from this call will restore registers and return to the kernel: the
+ // registers must be modified directly.
+ //
+ // If this function is not provided, a kernel exception results in halt.
+ //
+ // This must be go:nosplit, as this will be on the interrupt stack.
+ // Closures are permitted, as the pointer to the closure frame is not
+ // passed on the stack.
+ KernelException(Vector)
+}
+
+// CPU is the per-CPU struct.
+type CPU struct {
+ // self is a self reference.
+ //
+ // This is always guaranteed to be at offset zero.
+ self *CPU
+
+ // kernel is reference to the kernel that this CPU was initialized
+ // with. This reference is kept for garbage collection purposes: CPU
+ // registers may refer to objects within the Kernel object that cannot
+ // be safely freed.
+ kernel *Kernel
+
+ // CPUArchState is architecture-specific state.
+ CPUArchState
+
+ // registers is a set of registers; these may be used on kernel system
+ // calls and exceptions via the Registers function.
+ registers syscall.PtraceRegs
+
+ // hooks are kernel hooks.
+ hooks Hooks
+}
+
+// Registers returns a modifiable-copy of the kernel registers.
+//
+// This is explicitly safe to call during KernelException and KernelSyscall.
+//
+//go:nosplit
+func (c *CPU) Registers() *syscall.PtraceRegs {
+ return &c.registers
+}
+
+// SwitchOpts are passed to the Switch function.
+type SwitchOpts struct {
+ // Registers are the user register state.
+ Registers *syscall.PtraceRegs
+
+ // FloatingPointState is a byte pointer where floating point state is
+ // saved and restored.
+ FloatingPointState *byte
+
+ // PageTables are the application page tables.
+ PageTables *pagetables.PageTables
+
+ // Flush indicates that a TLB flush should be forced on switch.
+ Flush bool
+
+ // FullRestore indicates that an iret-based restore should be used.
+ FullRestore bool
+
+ // SwitchArchOpts are architecture-specific options.
+ SwitchArchOpts
+}
+
+// Segment indices and Selectors.
+const (
+ // Index into GDT array.
+ _ = iota // Null descriptor first.
+ _ // Reserved (Linux is kernel 32).
+ segKcode // Kernel code (64-bit).
+ segKdata // Kernel data.
+ segUcode32 // User code (32-bit).
+ segUdata // User data.
+ segUcode64 // User code (64-bit).
+ segTss // Task segment descriptor.
+ segTssHi // Upper bits for TSS.
+ segLast // Last segment (terminal, not included).
+)
+
+// Selectors.
+const (
+ Kcode Selector = segKcode << 3
+ Kdata Selector = segKdata << 3
+ Ucode32 Selector = (segUcode32 << 3) | 3
+ Udata Selector = (segUdata << 3) | 3
+ Ucode64 Selector = (segUcode64 << 3) | 3
+ Tss Selector = segTss << 3
+)
+
+// Standard segments.
+var (
+ UserCodeSegment32 SegmentDescriptor
+ UserDataSegment SegmentDescriptor
+ UserCodeSegment64 SegmentDescriptor
+ KernelCodeSegment SegmentDescriptor
+ KernelDataSegment SegmentDescriptor
+)
+
+// KernelOpts has initialization options for the kernel.
+type KernelOpts struct {
+ // PageTables are the kernel pagetables; this must be provided.
+ PageTables *pagetables.PageTables
+}
+
+// KernelArchState contains architecture-specific state.
+type KernelArchState struct {
+ KernelOpts
+
+ // globalIDT is our set of interrupt gates.
+ globalIDT idt64
+}
+
+// CPUArchState contains CPU-specific arch state.
+type CPUArchState struct {
+ // stack is the stack used for interrupts on this CPU.
+ stack [256]byte
+
+ // errorCode is the error code from the last exception.
+ errorCode uintptr
+
+ // errorType indicates the type of error code here, it is always set
+ // along with the errorCode value above.
+ //
+ // It will either by 1, which indicates a user error, or 0 indicating a
+ // kernel error. If the error code below returns false (kernel error),
+ // then it cannot provide relevant information about the last
+ // exception.
+ errorType uintptr
+
+ // gdt is the CPU's descriptor table.
+ gdt descriptorTable
+
+ // tss is the CPU's task state.
+ tss TaskState64
+}
+
+// ErrorCode returns the last error code.
+//
+// The returned boolean indicates whether the error code corresponds to the
+// last user error or not. If it does not, then fault information must be
+// ignored. This is generally the result of a kernel fault while servicing a
+// user fault.
+//
+//go:nosplit
+func (c *CPU) ErrorCode() (value uintptr, user bool) {
+ return c.errorCode, c.errorType != 0
+}
+
+// ClearErrorCode resets the error code.
+//
+//go:nosplit
+func (c *CPU) ClearErrorCode() {
+ c.errorCode = 0
+ c.errorType = 1
+}
+
+// SwitchArchOpts are embedded in SwitchOpts.
+type SwitchArchOpts struct {
+ // UserPCID indicates that the application PCID to be used on switch,
+ // assuming that PCIDs are supported.
+ //
+ // Per pagetables_x86.go, a zero PCID implies a flush.
+ UserPCID uint16
+
+ // KernelPCID indicates that the kernel PCID to be used on return,
+ // assuming that PCIDs are supported.
+ //
+ // Per pagetables_x86.go, a zero PCID implies a flush.
+ KernelPCID uint16
+}
+
+func init() {
+ KernelCodeSegment.setCode64(0, 0, 0)
+ KernelDataSegment.setData(0, 0xffffffff, 0)
+ UserCodeSegment32.setCode64(0, 0, 3)
+ UserDataSegment.setData(0, 0xffffffff, 3)
+ UserCodeSegment64.setCode64(0, 0, 3)
+}
+
+// Emit prints architecture-specific offsets.
+func Emit(w io.Writer) {
+ fmt.Fprintf(w, "// Automatically generated, do not edit.\n")
+
+ c := &CPU{}
+ fmt.Fprintf(w, "\n// CPU offsets.\n")
+ fmt.Fprintf(w, "#define CPU_SELF 0x%02x\n", reflect.ValueOf(&c.self).Pointer()-reflect.ValueOf(c).Pointer())
+ fmt.Fprintf(w, "#define CPU_REGISTERS 0x%02x\n", reflect.ValueOf(&c.registers).Pointer()-reflect.ValueOf(c).Pointer())
+ fmt.Fprintf(w, "#define CPU_STACK_TOP 0x%02x\n", reflect.ValueOf(&c.stack[0]).Pointer()-reflect.ValueOf(c).Pointer()+uintptr(len(c.stack)))
+ fmt.Fprintf(w, "#define CPU_ERROR_CODE 0x%02x\n", reflect.ValueOf(&c.errorCode).Pointer()-reflect.ValueOf(c).Pointer())
+ fmt.Fprintf(w, "#define CPU_ERROR_TYPE 0x%02x\n", reflect.ValueOf(&c.errorType).Pointer()-reflect.ValueOf(c).Pointer())
+
+ fmt.Fprintf(w, "\n// Bits.\n")
+ fmt.Fprintf(w, "#define _RFLAGS_IF 0x%02x\n", _RFLAGS_IF)
+ fmt.Fprintf(w, "#define _KERNEL_FLAGS 0x%02x\n", KernelFlagsSet)
+
+ fmt.Fprintf(w, "\n// Vectors.\n")
+ fmt.Fprintf(w, "#define DivideByZero 0x%02x\n", DivideByZero)
+ fmt.Fprintf(w, "#define Debug 0x%02x\n", Debug)
+ fmt.Fprintf(w, "#define NMI 0x%02x\n", NMI)
+ fmt.Fprintf(w, "#define Breakpoint 0x%02x\n", Breakpoint)
+ fmt.Fprintf(w, "#define Overflow 0x%02x\n", Overflow)
+ fmt.Fprintf(w, "#define BoundRangeExceeded 0x%02x\n", BoundRangeExceeded)
+ fmt.Fprintf(w, "#define InvalidOpcode 0x%02x\n", InvalidOpcode)
+ fmt.Fprintf(w, "#define DeviceNotAvailable 0x%02x\n", DeviceNotAvailable)
+ fmt.Fprintf(w, "#define DoubleFault 0x%02x\n", DoubleFault)
+ fmt.Fprintf(w, "#define CoprocessorSegmentOverrun 0x%02x\n", CoprocessorSegmentOverrun)
+ fmt.Fprintf(w, "#define InvalidTSS 0x%02x\n", InvalidTSS)
+ fmt.Fprintf(w, "#define SegmentNotPresent 0x%02x\n", SegmentNotPresent)
+ fmt.Fprintf(w, "#define StackSegmentFault 0x%02x\n", StackSegmentFault)
+ fmt.Fprintf(w, "#define GeneralProtectionFault 0x%02x\n", GeneralProtectionFault)
+ fmt.Fprintf(w, "#define PageFault 0x%02x\n", PageFault)
+ fmt.Fprintf(w, "#define X87FloatingPointException 0x%02x\n", X87FloatingPointException)
+ fmt.Fprintf(w, "#define AlignmentCheck 0x%02x\n", AlignmentCheck)
+ fmt.Fprintf(w, "#define MachineCheck 0x%02x\n", MachineCheck)
+ fmt.Fprintf(w, "#define SIMDFloatingPointException 0x%02x\n", SIMDFloatingPointException)
+ fmt.Fprintf(w, "#define VirtualizationException 0x%02x\n", VirtualizationException)
+ fmt.Fprintf(w, "#define SecurityException 0x%02x\n", SecurityException)
+ fmt.Fprintf(w, "#define SyscallInt80 0x%02x\n", SyscallInt80)
+ fmt.Fprintf(w, "#define Syscall 0x%02x\n", Syscall)
+
+ p := &syscall.PtraceRegs{}
+ fmt.Fprintf(w, "\n// Ptrace registers.\n")
+ fmt.Fprintf(w, "#define PTRACE_R15 0x%02x\n", reflect.ValueOf(&p.R15).Pointer()-reflect.ValueOf(p).Pointer())
+ fmt.Fprintf(w, "#define PTRACE_R14 0x%02x\n", reflect.ValueOf(&p.R14).Pointer()-reflect.ValueOf(p).Pointer())
+ fmt.Fprintf(w, "#define PTRACE_R13 0x%02x\n", reflect.ValueOf(&p.R13).Pointer()-reflect.ValueOf(p).Pointer())
+ fmt.Fprintf(w, "#define PTRACE_R12 0x%02x\n", reflect.ValueOf(&p.R12).Pointer()-reflect.ValueOf(p).Pointer())
+ fmt.Fprintf(w, "#define PTRACE_RBP 0x%02x\n", reflect.ValueOf(&p.Rbp).Pointer()-reflect.ValueOf(p).Pointer())
+ fmt.Fprintf(w, "#define PTRACE_RBX 0x%02x\n", reflect.ValueOf(&p.Rbx).Pointer()-reflect.ValueOf(p).Pointer())
+ fmt.Fprintf(w, "#define PTRACE_R11 0x%02x\n", reflect.ValueOf(&p.R11).Pointer()-reflect.ValueOf(p).Pointer())
+ fmt.Fprintf(w, "#define PTRACE_R10 0x%02x\n", reflect.ValueOf(&p.R10).Pointer()-reflect.ValueOf(p).Pointer())
+ fmt.Fprintf(w, "#define PTRACE_R9 0x%02x\n", reflect.ValueOf(&p.R9).Pointer()-reflect.ValueOf(p).Pointer())
+ fmt.Fprintf(w, "#define PTRACE_R8 0x%02x\n", reflect.ValueOf(&p.R8).Pointer()-reflect.ValueOf(p).Pointer())
+ fmt.Fprintf(w, "#define PTRACE_RAX 0x%02x\n", reflect.ValueOf(&p.Rax).Pointer()-reflect.ValueOf(p).Pointer())
+ fmt.Fprintf(w, "#define PTRACE_RCX 0x%02x\n", reflect.ValueOf(&p.Rcx).Pointer()-reflect.ValueOf(p).Pointer())
+ fmt.Fprintf(w, "#define PTRACE_RDX 0x%02x\n", reflect.ValueOf(&p.Rdx).Pointer()-reflect.ValueOf(p).Pointer())
+ fmt.Fprintf(w, "#define PTRACE_RSI 0x%02x\n", reflect.ValueOf(&p.Rsi).Pointer()-reflect.ValueOf(p).Pointer())
+ fmt.Fprintf(w, "#define PTRACE_RDI 0x%02x\n", reflect.ValueOf(&p.Rdi).Pointer()-reflect.ValueOf(p).Pointer())
+ fmt.Fprintf(w, "#define PTRACE_ORIGRAX 0x%02x\n", reflect.ValueOf(&p.Orig_rax).Pointer()-reflect.ValueOf(p).Pointer())
+ fmt.Fprintf(w, "#define PTRACE_RIP 0x%02x\n", reflect.ValueOf(&p.Rip).Pointer()-reflect.ValueOf(p).Pointer())
+ fmt.Fprintf(w, "#define PTRACE_CS 0x%02x\n", reflect.ValueOf(&p.Cs).Pointer()-reflect.ValueOf(p).Pointer())
+ fmt.Fprintf(w, "#define PTRACE_FLAGS 0x%02x\n", reflect.ValueOf(&p.Eflags).Pointer()-reflect.ValueOf(p).Pointer())
+ fmt.Fprintf(w, "#define PTRACE_RSP 0x%02x\n", reflect.ValueOf(&p.Rsp).Pointer()-reflect.ValueOf(p).Pointer())
+ fmt.Fprintf(w, "#define PTRACE_SS 0x%02x\n", reflect.ValueOf(&p.Ss).Pointer()-reflect.ValueOf(p).Pointer())
+ fmt.Fprintf(w, "#define PTRACE_FS 0x%02x\n", reflect.ValueOf(&p.Fs_base).Pointer()-reflect.ValueOf(p).Pointer())
+ fmt.Fprintf(w, "#define PTRACE_GS 0x%02x\n", reflect.ValueOf(&p.Gs_base).Pointer()-reflect.ValueOf(p).Pointer())
+}
+
+// Useful bits.
+const (
+ _CR0_PE = 1 << 0
+ _CR0_ET = 1 << 4
+ _CR0_AM = 1 << 18
+ _CR0_PG = 1 << 31
+
+ _CR4_PSE = 1 << 4
+ _CR4_PAE = 1 << 5
+ _CR4_PGE = 1 << 7
+ _CR4_OSFXSR = 1 << 9
+ _CR4_OSXMMEXCPT = 1 << 10
+ _CR4_FSGSBASE = 1 << 16
+ _CR4_PCIDE = 1 << 17
+ _CR4_OSXSAVE = 1 << 18
+ _CR4_SMEP = 1 << 20
+
+ _RFLAGS_AC = 1 << 18
+ _RFLAGS_NT = 1 << 14
+ _RFLAGS_IOPL = 3 << 12
+ _RFLAGS_DF = 1 << 10
+ _RFLAGS_IF = 1 << 9
+ _RFLAGS_STEP = 1 << 8
+ _RFLAGS_RESERVED = 1 << 1
+
+ _EFER_SCE = 0x001
+ _EFER_LME = 0x100
+ _EFER_LMA = 0x400
+ _EFER_NX = 0x800
+
+ _MSR_STAR = 0xc0000081
+ _MSR_LSTAR = 0xc0000082
+ _MSR_CSTAR = 0xc0000083
+ _MSR_SYSCALL_MASK = 0xc0000084
+ _MSR_PLATFORM_INFO = 0xce
+ _MSR_MISC_FEATURES = 0x140
+
+ _PLATFORM_INFO_CPUID_FAULT = 1 << 31
+
+ _MISC_FEATURE_CPUID_TRAP = 0x1
+)
+
+const (
+ // KernelFlagsSet should always be set in the kernel.
+ KernelFlagsSet = _RFLAGS_RESERVED
+
+ // UserFlagsSet are always set in userspace.
+ UserFlagsSet = _RFLAGS_RESERVED | _RFLAGS_IF
+
+ // KernelFlagsClear should always be clear in the kernel.
+ KernelFlagsClear = _RFLAGS_STEP | _RFLAGS_IF | _RFLAGS_IOPL | _RFLAGS_AC | _RFLAGS_NT
+
+ // UserFlagsClear are always cleared in userspace.
+ UserFlagsClear = _RFLAGS_NT | _RFLAGS_IOPL
+)
+
+// Vector is an exception vector.
+type Vector uintptr
+
+// Exception vectors.
+const (
+ DivideByZero Vector = iota
+ Debug
+ NMI
+ Breakpoint
+ Overflow
+ BoundRangeExceeded
+ InvalidOpcode
+ DeviceNotAvailable
+ DoubleFault
+ CoprocessorSegmentOverrun
+ InvalidTSS
+ SegmentNotPresent
+ StackSegmentFault
+ GeneralProtectionFault
+ PageFault
+ _
+ X87FloatingPointException
+ AlignmentCheck
+ MachineCheck
+ SIMDFloatingPointException
+ VirtualizationException
+ SecurityException = 0x1e
+ SyscallInt80 = 0x80
+ _NR_INTERRUPTS = SyscallInt80 + 1
+)
+
+// System call vectors.
+const (
+ Syscall Vector = _NR_INTERRUPTS
+)
+
+// VirtualAddressBits returns the number bits available for virtual addresses.
+//
+// Note that sign-extension semantics apply to the highest order bit.
+//
+// FIXME(b/69382326): This should use the cpuid passed to Init.
+func VirtualAddressBits() uint32 {
+ ax, _, _, _ := cpuid.HostID(0x80000008, 0)
+ return (ax >> 8) & 0xff
+}
+
+// PhysicalAddressBits returns the number of bits available for physical addresses.
+//
+// FIXME(b/69382326): This should use the cpuid passed to Init.
+func PhysicalAddressBits() uint32 {
+ ax, _, _, _ := cpuid.HostID(0x80000008, 0)
+ return ax & 0xff
+}
+
+// Selector is a segment Selector.
+type Selector uint16
+
+// SegmentDescriptor is a segment descriptor.
+type SegmentDescriptor struct {
+ bits [2]uint32
+}
+
+// descriptorTable is a collection of descriptors.
+type descriptorTable [32]SegmentDescriptor
+
+// SegmentDescriptorFlags are typed flags within a descriptor.
+type SegmentDescriptorFlags uint32
+
+// SegmentDescriptorFlag declarations.
+const (
+ SegmentDescriptorAccess SegmentDescriptorFlags = 1 << 8 // Access bit (always set).
+ SegmentDescriptorWrite = 1 << 9 // Write permission.
+ SegmentDescriptorExpandDown = 1 << 10 // Grows down, not used.
+ SegmentDescriptorExecute = 1 << 11 // Execute permission.
+ SegmentDescriptorSystem = 1 << 12 // Zero => system, 1 => user code/data.
+ SegmentDescriptorPresent = 1 << 15 // Present.
+ SegmentDescriptorAVL = 1 << 20 // Available.
+ SegmentDescriptorLong = 1 << 21 // Long mode.
+ SegmentDescriptorDB = 1 << 22 // 16 or 32-bit.
+ SegmentDescriptorG = 1 << 23 // Granularity: page or byte.
+)
+
+// Base returns the descriptor's base linear address.
+func (d *SegmentDescriptor) Base() uint32 {
+ return d.bits[1]&0xFF000000 | (d.bits[1]&0x000000FF)<<16 | d.bits[0]>>16
+}
+
+// Limit returns the descriptor size.
+func (d *SegmentDescriptor) Limit() uint32 {
+ l := d.bits[0]&0xFFFF | d.bits[1]&0xF0000
+ if d.bits[1]&uint32(SegmentDescriptorG) != 0 {
+ l <<= 12
+ l |= 0xFFF
+ }
+ return l
+}
+
+// Flags returns descriptor flags.
+func (d *SegmentDescriptor) Flags() SegmentDescriptorFlags {
+ return SegmentDescriptorFlags(d.bits[1] & 0x00F09F00)
+}
+
+// DPL returns the descriptor privilege level.
+func (d *SegmentDescriptor) DPL() int {
+ return int((d.bits[1] >> 13) & 3)
+}
+
+func (d *SegmentDescriptor) setNull() {
+ d.bits[0] = 0
+ d.bits[1] = 0
+}
+
+func (d *SegmentDescriptor) set(base, limit uint32, dpl int, flags SegmentDescriptorFlags) {
+ flags |= SegmentDescriptorPresent
+ if limit>>12 != 0 {
+ limit >>= 12
+ flags |= SegmentDescriptorG
+ }
+ d.bits[0] = base<<16 | limit&0xFFFF
+ d.bits[1] = base&0xFF000000 | (base>>16)&0xFF | limit&0x000F0000 | uint32(flags) | uint32(dpl)<<13
+}
+
+func (d *SegmentDescriptor) setCode32(base, limit uint32, dpl int) {
+ d.set(base, limit, dpl,
+ SegmentDescriptorDB|
+ SegmentDescriptorExecute|
+ SegmentDescriptorSystem)
+}
+
+func (d *SegmentDescriptor) setCode64(base, limit uint32, dpl int) {
+ d.set(base, limit, dpl,
+ SegmentDescriptorG|
+ SegmentDescriptorLong|
+ SegmentDescriptorExecute|
+ SegmentDescriptorSystem)
+}
+
+func (d *SegmentDescriptor) setData(base, limit uint32, dpl int) {
+ d.set(base, limit, dpl,
+ SegmentDescriptorWrite|
+ SegmentDescriptorSystem)
+}
+
+// setHi is only used for the TSS segment, which is magically 64-bits.
+func (d *SegmentDescriptor) setHi(base uint32) {
+ d.bits[0] = base
+ d.bits[1] = 0
+}
+
+// Gate64 is a 64-bit task, trap, or interrupt gate.
+type Gate64 struct {
+ bits [4]uint32
+}
+
+// idt64 is a 64-bit interrupt descriptor table.
+type idt64 [_NR_INTERRUPTS]Gate64
+
+func (g *Gate64) setInterrupt(cs Selector, rip uint64, dpl int, ist int) {
+ g.bits[0] = uint32(cs)<<16 | uint32(rip)&0xFFFF
+ g.bits[1] = uint32(rip)&0xFFFF0000 | SegmentDescriptorPresent | uint32(dpl)<<13 | 14<<8 | uint32(ist)&0x7
+ g.bits[2] = uint32(rip >> 32)
+}
+
+func (g *Gate64) setTrap(cs Selector, rip uint64, dpl int, ist int) {
+ g.setInterrupt(cs, rip, dpl, ist)
+ g.bits[1] |= 1 << 8
+}
+
+// TaskState64 is a 64-bit task state structure.
+type TaskState64 struct {
+ _ uint32
+ rsp0Lo, rsp0Hi uint32
+ rsp1Lo, rsp1Hi uint32
+ rsp2Lo, rsp2Hi uint32
+ _ [2]uint32
+ ist1Lo, ist1Hi uint32
+ ist2Lo, ist2Hi uint32
+ ist3Lo, ist3Hi uint32
+ ist4Lo, ist4Hi uint32
+ ist5Lo, ist5Hi uint32
+ ist6Lo, ist6Hi uint32
+ ist7Lo, ist7Hi uint32
+ _ [2]uint32
+ _ uint16
+ ioPerm uint16
+}
diff --git a/pkg/sentry/platform/ring0/entry_amd64.s b/pkg/sentry/platform/ring0/entry_impl_amd64.s
index 02df38331..daba45f9d 100644..100755
--- a/pkg/sentry/platform/ring0/entry_amd64.s
+++ b/pkg/sentry/platform/ring0/entry_impl_amd64.s
@@ -1,3 +1,67 @@
+// build +amd64
+
+// Automatically generated, do not edit.
+
+// CPU offsets.
+#define CPU_SELF 0x00
+#define CPU_REGISTERS 0x288
+#define CPU_STACK_TOP 0x110
+#define CPU_ERROR_CODE 0x110
+#define CPU_ERROR_TYPE 0x118
+
+// Bits.
+#define _RFLAGS_IF 0x200
+#define _KERNEL_FLAGS 0x02
+
+// Vectors.
+#define DivideByZero 0x00
+#define Debug 0x01
+#define NMI 0x02
+#define Breakpoint 0x03
+#define Overflow 0x04
+#define BoundRangeExceeded 0x05
+#define InvalidOpcode 0x06
+#define DeviceNotAvailable 0x07
+#define DoubleFault 0x08
+#define CoprocessorSegmentOverrun 0x09
+#define InvalidTSS 0x0a
+#define SegmentNotPresent 0x0b
+#define StackSegmentFault 0x0c
+#define GeneralProtectionFault 0x0d
+#define PageFault 0x0e
+#define X87FloatingPointException 0x10
+#define AlignmentCheck 0x11
+#define MachineCheck 0x12
+#define SIMDFloatingPointException 0x13
+#define VirtualizationException 0x14
+#define SecurityException 0x1e
+#define SyscallInt80 0x80
+#define Syscall 0x81
+
+// Ptrace registers.
+#define PTRACE_R15 0x00
+#define PTRACE_R14 0x08
+#define PTRACE_R13 0x10
+#define PTRACE_R12 0x18
+#define PTRACE_RBP 0x20
+#define PTRACE_RBX 0x28
+#define PTRACE_R11 0x30
+#define PTRACE_R10 0x38
+#define PTRACE_R9 0x40
+#define PTRACE_R8 0x48
+#define PTRACE_RAX 0x50
+#define PTRACE_RCX 0x58
+#define PTRACE_RDX 0x60
+#define PTRACE_RSI 0x68
+#define PTRACE_RDI 0x70
+#define PTRACE_ORIGRAX 0x78
+#define PTRACE_RIP 0x80
+#define PTRACE_CS 0x88
+#define PTRACE_FLAGS 0x90
+#define PTRACE_RSP 0x98
+#define PTRACE_SS 0xa0
+#define PTRACE_FS 0xa8
+#define PTRACE_GS 0xb0
// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/pkg/sentry/platform/ring0/gen_offsets/BUILD b/pkg/sentry/platform/ring0/gen_offsets/BUILD
deleted file mode 100644
index d7029d5a9..000000000
--- a/pkg/sentry/platform/ring0/gen_offsets/BUILD
+++ /dev/null
@@ -1,26 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-
-go_template_instance(
- name = "defs_impl",
- out = "defs_impl.go",
- package = "main",
- template = "//pkg/sentry/platform/ring0:defs",
-)
-
-go_binary(
- name = "gen_offsets",
- srcs = [
- "defs_impl.go",
- "main.go",
- ],
- visibility = ["//pkg/sentry/platform/ring0:__pkg__"],
- deps = [
- "//pkg/cpuid",
- "//pkg/sentry/platform/ring0/pagetables",
- "//pkg/sentry/usermem",
- ],
-)
diff --git a/pkg/sentry/platform/ring0/gen_offsets/main.go b/pkg/sentry/platform/ring0/gen_offsets/main.go
deleted file mode 100644
index a4927da2f..000000000
--- a/pkg/sentry/platform/ring0/gen_offsets/main.go
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Binary gen_offsets is a helper for generating offset headers.
-package main
-
-import (
- "os"
-)
-
-func main() {
- Emit(os.Stdout)
-}
diff --git a/pkg/sentry/platform/ring0/offsets_amd64.go b/pkg/sentry/platform/ring0/offsets_amd64.go
deleted file mode 100644
index 85cc3fdad..000000000
--- a/pkg/sentry/platform/ring0/offsets_amd64.go
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// +build amd64
-
-package ring0
-
-import (
- "fmt"
- "io"
- "reflect"
- "syscall"
-)
-
-// Emit prints architecture-specific offsets.
-func Emit(w io.Writer) {
- fmt.Fprintf(w, "// Automatically generated, do not edit.\n")
-
- c := &CPU{}
- fmt.Fprintf(w, "\n// CPU offsets.\n")
- fmt.Fprintf(w, "#define CPU_SELF 0x%02x\n", reflect.ValueOf(&c.self).Pointer()-reflect.ValueOf(c).Pointer())
- fmt.Fprintf(w, "#define CPU_REGISTERS 0x%02x\n", reflect.ValueOf(&c.registers).Pointer()-reflect.ValueOf(c).Pointer())
- fmt.Fprintf(w, "#define CPU_STACK_TOP 0x%02x\n", reflect.ValueOf(&c.stack[0]).Pointer()-reflect.ValueOf(c).Pointer()+uintptr(len(c.stack)))
- fmt.Fprintf(w, "#define CPU_ERROR_CODE 0x%02x\n", reflect.ValueOf(&c.errorCode).Pointer()-reflect.ValueOf(c).Pointer())
- fmt.Fprintf(w, "#define CPU_ERROR_TYPE 0x%02x\n", reflect.ValueOf(&c.errorType).Pointer()-reflect.ValueOf(c).Pointer())
-
- fmt.Fprintf(w, "\n// Bits.\n")
- fmt.Fprintf(w, "#define _RFLAGS_IF 0x%02x\n", _RFLAGS_IF)
- fmt.Fprintf(w, "#define _KERNEL_FLAGS 0x%02x\n", KernelFlagsSet)
-
- fmt.Fprintf(w, "\n// Vectors.\n")
- fmt.Fprintf(w, "#define DivideByZero 0x%02x\n", DivideByZero)
- fmt.Fprintf(w, "#define Debug 0x%02x\n", Debug)
- fmt.Fprintf(w, "#define NMI 0x%02x\n", NMI)
- fmt.Fprintf(w, "#define Breakpoint 0x%02x\n", Breakpoint)
- fmt.Fprintf(w, "#define Overflow 0x%02x\n", Overflow)
- fmt.Fprintf(w, "#define BoundRangeExceeded 0x%02x\n", BoundRangeExceeded)
- fmt.Fprintf(w, "#define InvalidOpcode 0x%02x\n", InvalidOpcode)
- fmt.Fprintf(w, "#define DeviceNotAvailable 0x%02x\n", DeviceNotAvailable)
- fmt.Fprintf(w, "#define DoubleFault 0x%02x\n", DoubleFault)
- fmt.Fprintf(w, "#define CoprocessorSegmentOverrun 0x%02x\n", CoprocessorSegmentOverrun)
- fmt.Fprintf(w, "#define InvalidTSS 0x%02x\n", InvalidTSS)
- fmt.Fprintf(w, "#define SegmentNotPresent 0x%02x\n", SegmentNotPresent)
- fmt.Fprintf(w, "#define StackSegmentFault 0x%02x\n", StackSegmentFault)
- fmt.Fprintf(w, "#define GeneralProtectionFault 0x%02x\n", GeneralProtectionFault)
- fmt.Fprintf(w, "#define PageFault 0x%02x\n", PageFault)
- fmt.Fprintf(w, "#define X87FloatingPointException 0x%02x\n", X87FloatingPointException)
- fmt.Fprintf(w, "#define AlignmentCheck 0x%02x\n", AlignmentCheck)
- fmt.Fprintf(w, "#define MachineCheck 0x%02x\n", MachineCheck)
- fmt.Fprintf(w, "#define SIMDFloatingPointException 0x%02x\n", SIMDFloatingPointException)
- fmt.Fprintf(w, "#define VirtualizationException 0x%02x\n", VirtualizationException)
- fmt.Fprintf(w, "#define SecurityException 0x%02x\n", SecurityException)
- fmt.Fprintf(w, "#define SyscallInt80 0x%02x\n", SyscallInt80)
- fmt.Fprintf(w, "#define Syscall 0x%02x\n", Syscall)
-
- p := &syscall.PtraceRegs{}
- fmt.Fprintf(w, "\n// Ptrace registers.\n")
- fmt.Fprintf(w, "#define PTRACE_R15 0x%02x\n", reflect.ValueOf(&p.R15).Pointer()-reflect.ValueOf(p).Pointer())
- fmt.Fprintf(w, "#define PTRACE_R14 0x%02x\n", reflect.ValueOf(&p.R14).Pointer()-reflect.ValueOf(p).Pointer())
- fmt.Fprintf(w, "#define PTRACE_R13 0x%02x\n", reflect.ValueOf(&p.R13).Pointer()-reflect.ValueOf(p).Pointer())
- fmt.Fprintf(w, "#define PTRACE_R12 0x%02x\n", reflect.ValueOf(&p.R12).Pointer()-reflect.ValueOf(p).Pointer())
- fmt.Fprintf(w, "#define PTRACE_RBP 0x%02x\n", reflect.ValueOf(&p.Rbp).Pointer()-reflect.ValueOf(p).Pointer())
- fmt.Fprintf(w, "#define PTRACE_RBX 0x%02x\n", reflect.ValueOf(&p.Rbx).Pointer()-reflect.ValueOf(p).Pointer())
- fmt.Fprintf(w, "#define PTRACE_R11 0x%02x\n", reflect.ValueOf(&p.R11).Pointer()-reflect.ValueOf(p).Pointer())
- fmt.Fprintf(w, "#define PTRACE_R10 0x%02x\n", reflect.ValueOf(&p.R10).Pointer()-reflect.ValueOf(p).Pointer())
- fmt.Fprintf(w, "#define PTRACE_R9 0x%02x\n", reflect.ValueOf(&p.R9).Pointer()-reflect.ValueOf(p).Pointer())
- fmt.Fprintf(w, "#define PTRACE_R8 0x%02x\n", reflect.ValueOf(&p.R8).Pointer()-reflect.ValueOf(p).Pointer())
- fmt.Fprintf(w, "#define PTRACE_RAX 0x%02x\n", reflect.ValueOf(&p.Rax).Pointer()-reflect.ValueOf(p).Pointer())
- fmt.Fprintf(w, "#define PTRACE_RCX 0x%02x\n", reflect.ValueOf(&p.Rcx).Pointer()-reflect.ValueOf(p).Pointer())
- fmt.Fprintf(w, "#define PTRACE_RDX 0x%02x\n", reflect.ValueOf(&p.Rdx).Pointer()-reflect.ValueOf(p).Pointer())
- fmt.Fprintf(w, "#define PTRACE_RSI 0x%02x\n", reflect.ValueOf(&p.Rsi).Pointer()-reflect.ValueOf(p).Pointer())
- fmt.Fprintf(w, "#define PTRACE_RDI 0x%02x\n", reflect.ValueOf(&p.Rdi).Pointer()-reflect.ValueOf(p).Pointer())
- fmt.Fprintf(w, "#define PTRACE_ORIGRAX 0x%02x\n", reflect.ValueOf(&p.Orig_rax).Pointer()-reflect.ValueOf(p).Pointer())
- fmt.Fprintf(w, "#define PTRACE_RIP 0x%02x\n", reflect.ValueOf(&p.Rip).Pointer()-reflect.ValueOf(p).Pointer())
- fmt.Fprintf(w, "#define PTRACE_CS 0x%02x\n", reflect.ValueOf(&p.Cs).Pointer()-reflect.ValueOf(p).Pointer())
- fmt.Fprintf(w, "#define PTRACE_FLAGS 0x%02x\n", reflect.ValueOf(&p.Eflags).Pointer()-reflect.ValueOf(p).Pointer())
- fmt.Fprintf(w, "#define PTRACE_RSP 0x%02x\n", reflect.ValueOf(&p.Rsp).Pointer()-reflect.ValueOf(p).Pointer())
- fmt.Fprintf(w, "#define PTRACE_SS 0x%02x\n", reflect.ValueOf(&p.Ss).Pointer()-reflect.ValueOf(p).Pointer())
- fmt.Fprintf(w, "#define PTRACE_FS 0x%02x\n", reflect.ValueOf(&p.Fs_base).Pointer()-reflect.ValueOf(p).Pointer())
- fmt.Fprintf(w, "#define PTRACE_GS 0x%02x\n", reflect.ValueOf(&p.Gs_base).Pointer()-reflect.ValueOf(p).Pointer())
-}
diff --git a/pkg/sentry/platform/ring0/pagetables/BUILD b/pkg/sentry/platform/ring0/pagetables/BUILD
deleted file mode 100644
index ea090b686..000000000
--- a/pkg/sentry/platform/ring0/pagetables/BUILD
+++ /dev/null
@@ -1,106 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template", "go_template_instance")
-
-go_template(
- name = "generic_walker",
- srcs = [
- "walker_amd64.go",
- ],
- opt_types = [
- "Visitor",
- ],
- visibility = [":__pkg__"],
-)
-
-go_template_instance(
- name = "walker_map",
- out = "walker_map.go",
- package = "pagetables",
- prefix = "map",
- template = ":generic_walker",
- types = {
- "Visitor": "mapVisitor",
- },
-)
-
-go_template_instance(
- name = "walker_unmap",
- out = "walker_unmap.go",
- package = "pagetables",
- prefix = "unmap",
- template = ":generic_walker",
- types = {
- "Visitor": "unmapVisitor",
- },
-)
-
-go_template_instance(
- name = "walker_lookup",
- out = "walker_lookup.go",
- package = "pagetables",
- prefix = "lookup",
- template = ":generic_walker",
- types = {
- "Visitor": "lookupVisitor",
- },
-)
-
-go_template_instance(
- name = "walker_empty",
- out = "walker_empty.go",
- package = "pagetables",
- prefix = "empty",
- template = ":generic_walker",
- types = {
- "Visitor": "emptyVisitor",
- },
-)
-
-go_template_instance(
- name = "walker_check",
- out = "walker_check.go",
- package = "pagetables",
- prefix = "check",
- template = ":generic_walker",
- types = {
- "Visitor": "checkVisitor",
- },
-)
-
-go_library(
- name = "pagetables",
- srcs = [
- "allocator.go",
- "allocator_unsafe.go",
- "pagetables.go",
- "pagetables_amd64.go",
- "pagetables_x86.go",
- "pcids_x86.go",
- "walker_empty.go",
- "walker_lookup.go",
- "walker_map.go",
- "walker_unmap.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/platform/ring0/pagetables",
- visibility = [
- "//pkg/sentry/platform/kvm:__subpackages__",
- "//pkg/sentry/platform/ring0:__subpackages__",
- ],
- deps = ["//pkg/sentry/usermem"],
-)
-
-go_test(
- name = "pagetables_test",
- size = "small",
- srcs = [
- "pagetables_amd64_test.go",
- "pagetables_test.go",
- "walker_check.go",
- ],
- embed = [":pagetables"],
- deps = ["//pkg/sentry/usermem"],
-)
diff --git a/pkg/sentry/platform/ring0/pagetables/pagetables_amd64_test.go b/pkg/sentry/platform/ring0/pagetables/pagetables_amd64_test.go
deleted file mode 100644
index 35e917526..000000000
--- a/pkg/sentry/platform/ring0/pagetables/pagetables_amd64_test.go
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// +build amd64
-
-package pagetables
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/usermem"
-)
-
-func Test2MAnd4K(t *testing.T) {
- pt := New(NewRuntimeAllocator())
-
- // Map a small page and a huge page.
- pt.Map(0x400000, pteSize, MapOpts{AccessType: usermem.ReadWrite}, pteSize*42)
- pt.Map(0x00007f0000000000, pmdSize, MapOpts{AccessType: usermem.Read}, pmdSize*47)
-
- checkMappings(t, pt, []mapping{
- {0x400000, pteSize, pteSize * 42, MapOpts{AccessType: usermem.ReadWrite}},
- {0x00007f0000000000, pmdSize, pmdSize * 47, MapOpts{AccessType: usermem.Read}},
- })
-}
-
-func Test1GAnd4K(t *testing.T) {
- pt := New(NewRuntimeAllocator())
-
- // Map a small page and a super page.
- pt.Map(0x400000, pteSize, MapOpts{AccessType: usermem.ReadWrite}, pteSize*42)
- pt.Map(0x00007f0000000000, pudSize, MapOpts{AccessType: usermem.Read}, pudSize*47)
-
- checkMappings(t, pt, []mapping{
- {0x400000, pteSize, pteSize * 42, MapOpts{AccessType: usermem.ReadWrite}},
- {0x00007f0000000000, pudSize, pudSize * 47, MapOpts{AccessType: usermem.Read}},
- })
-}
-
-func TestSplit1GPage(t *testing.T) {
- pt := New(NewRuntimeAllocator())
-
- // Map a super page and knock out the middle.
- pt.Map(0x00007f0000000000, pudSize, MapOpts{AccessType: usermem.Read}, pudSize*42)
- pt.Unmap(usermem.Addr(0x00007f0000000000+pteSize), pudSize-(2*pteSize))
-
- checkMappings(t, pt, []mapping{
- {0x00007f0000000000, pteSize, pudSize * 42, MapOpts{AccessType: usermem.Read}},
- {0x00007f0000000000 + pudSize - pteSize, pteSize, pudSize*42 + pudSize - pteSize, MapOpts{AccessType: usermem.Read}},
- })
-}
-
-func TestSplit2MPage(t *testing.T) {
- pt := New(NewRuntimeAllocator())
-
- // Map a huge page and knock out the middle.
- pt.Map(0x00007f0000000000, pmdSize, MapOpts{AccessType: usermem.Read}, pmdSize*42)
- pt.Unmap(usermem.Addr(0x00007f0000000000+pteSize), pmdSize-(2*pteSize))
-
- checkMappings(t, pt, []mapping{
- {0x00007f0000000000, pteSize, pmdSize * 42, MapOpts{AccessType: usermem.Read}},
- {0x00007f0000000000 + pmdSize - pteSize, pteSize, pmdSize*42 + pmdSize - pteSize, MapOpts{AccessType: usermem.Read}},
- })
-}
diff --git a/pkg/sentry/platform/ring0/pagetables/pagetables_state_autogen.go b/pkg/sentry/platform/ring0/pagetables/pagetables_state_autogen.go
new file mode 100755
index 000000000..ac1ccf3d3
--- /dev/null
+++ b/pkg/sentry/platform/ring0/pagetables/pagetables_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package pagetables
+
diff --git a/pkg/sentry/platform/ring0/pagetables/pagetables_test.go b/pkg/sentry/platform/ring0/pagetables/pagetables_test.go
deleted file mode 100644
index 6e95ad2b9..000000000
--- a/pkg/sentry/platform/ring0/pagetables/pagetables_test.go
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package pagetables
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/usermem"
-)
-
-type mapping struct {
- start uintptr
- length uintptr
- addr uintptr
- opts MapOpts
-}
-
-type checkVisitor struct {
- expected []mapping // Input.
- current int // Temporary.
- found []mapping // Output.
- failed string // Output.
-}
-
-func (v *checkVisitor) visit(start uintptr, pte *PTE, align uintptr) {
- v.found = append(v.found, mapping{
- start: start,
- length: align + 1,
- addr: pte.Address(),
- opts: pte.Opts(),
- })
- if v.failed != "" {
- // Don't keep looking for errors.
- return
- }
-
- if v.current >= len(v.expected) {
- v.failed = "more mappings than expected"
- } else if v.expected[v.current].start != start {
- v.failed = "start didn't match expected"
- } else if v.expected[v.current].length != (align + 1) {
- v.failed = "end didn't match expected"
- } else if v.expected[v.current].addr != pte.Address() {
- v.failed = "address didn't match expected"
- } else if v.expected[v.current].opts != pte.Opts() {
- v.failed = "opts didn't match"
- }
- v.current++
-}
-
-func (*checkVisitor) requiresAlloc() bool { return false }
-
-func (*checkVisitor) requiresSplit() bool { return false }
-
-func checkMappings(t *testing.T, pt *PageTables, m []mapping) {
- // Iterate over all the mappings.
- w := checkWalker{
- pageTables: pt,
- visitor: checkVisitor{
- expected: m,
- },
- }
- w.iterateRange(0, ^uintptr(0))
-
- // Were we expected additional mappings?
- if w.visitor.failed == "" && w.visitor.current != len(w.visitor.expected) {
- w.visitor.failed = "insufficient mappings found"
- }
-
- // Emit a meaningful error message on failure.
- if w.visitor.failed != "" {
- t.Errorf("%s; got %#v, wanted %#v", w.visitor.failed, w.visitor.found, w.visitor.expected)
- }
-}
-
-func TestUnmap(t *testing.T) {
- pt := New(NewRuntimeAllocator())
-
- // Map and unmap one entry.
- pt.Map(0x400000, pteSize, MapOpts{AccessType: usermem.ReadWrite}, pteSize*42)
- pt.Unmap(0x400000, pteSize)
-
- checkMappings(t, pt, nil)
-}
-
-func TestReadOnly(t *testing.T) {
- pt := New(NewRuntimeAllocator())
-
- // Map one entry.
- pt.Map(0x400000, pteSize, MapOpts{AccessType: usermem.Read}, pteSize*42)
-
- checkMappings(t, pt, []mapping{
- {0x400000, pteSize, pteSize * 42, MapOpts{AccessType: usermem.Read}},
- })
-}
-
-func TestReadWrite(t *testing.T) {
- pt := New(NewRuntimeAllocator())
-
- // Map one entry.
- pt.Map(0x400000, pteSize, MapOpts{AccessType: usermem.ReadWrite}, pteSize*42)
-
- checkMappings(t, pt, []mapping{
- {0x400000, pteSize, pteSize * 42, MapOpts{AccessType: usermem.ReadWrite}},
- })
-}
-
-func TestSerialEntries(t *testing.T) {
- pt := New(NewRuntimeAllocator())
-
- // Map two sequential entries.
- pt.Map(0x400000, pteSize, MapOpts{AccessType: usermem.ReadWrite}, pteSize*42)
- pt.Map(0x401000, pteSize, MapOpts{AccessType: usermem.ReadWrite}, pteSize*47)
-
- checkMappings(t, pt, []mapping{
- {0x400000, pteSize, pteSize * 42, MapOpts{AccessType: usermem.ReadWrite}},
- {0x401000, pteSize, pteSize * 47, MapOpts{AccessType: usermem.ReadWrite}},
- })
-}
-
-func TestSpanningEntries(t *testing.T) {
- pt := New(NewRuntimeAllocator())
-
- // Span a pgd with two pages.
- pt.Map(0x00007efffffff000, 2*pteSize, MapOpts{AccessType: usermem.Read}, pteSize*42)
-
- checkMappings(t, pt, []mapping{
- {0x00007efffffff000, pteSize, pteSize * 42, MapOpts{AccessType: usermem.Read}},
- {0x00007f0000000000, pteSize, pteSize * 43, MapOpts{AccessType: usermem.Read}},
- })
-}
-
-func TestSparseEntries(t *testing.T) {
- pt := New(NewRuntimeAllocator())
-
- // Map two entries in different pgds.
- pt.Map(0x400000, pteSize, MapOpts{AccessType: usermem.ReadWrite}, pteSize*42)
- pt.Map(0x00007f0000000000, pteSize, MapOpts{AccessType: usermem.Read}, pteSize*47)
-
- checkMappings(t, pt, []mapping{
- {0x400000, pteSize, pteSize * 42, MapOpts{AccessType: usermem.ReadWrite}},
- {0x00007f0000000000, pteSize, pteSize * 47, MapOpts{AccessType: usermem.Read}},
- })
-}
diff --git a/pkg/sentry/platform/ring0/pagetables/walker_amd64.go b/pkg/sentry/platform/ring0/pagetables/walker_empty.go
index 8f9dacd93..417784e17 100644..100755
--- a/pkg/sentry/platform/ring0/pagetables/walker_amd64.go
+++ b/pkg/sentry/platform/ring0/pagetables/walker_empty.go
@@ -1,42 +1,12 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// +build amd64
-
package pagetables
-// Visitor is a generic type.
-type Visitor interface {
- // visit is called on each PTE.
- visit(start uintptr, pte *PTE, align uintptr)
-
- // requiresAlloc indicates that new entries should be allocated within
- // the walked range.
- requiresAlloc() bool
-
- // requiresSplit indicates that entries in the given range should be
- // split if they are huge or jumbo pages.
- requiresSplit() bool
-}
-
// Walker walks page tables.
-type Walker struct {
+type emptyWalker struct {
// pageTables are the tables to walk.
pageTables *PageTables
// Visitor is the set of arguments.
- visitor Visitor
+ visitor emptyVisitor
}
// iterateRange iterates over all appropriate levels of page tables for the given range.
@@ -63,7 +33,7 @@ type Walker struct {
// non-canonical ranges. If they do, a panic will result.
//
//go:nosplit
-func (w *Walker) iterateRange(start, end uintptr) {
+func (w *emptyWalker) iterateRange(start, end uintptr) {
if start%pteSize != 0 {
panic("unaligned start")
}
@@ -104,7 +74,7 @@ func (w *Walker) iterateRange(start, end uintptr) {
// next returns the next address quantized by the given size.
//
//go:nosplit
-func next(start uintptr, size uintptr) uintptr {
+func emptynext(start uintptr, size uintptr) uintptr {
start &= ^(size - 1)
start += size
return start
@@ -113,7 +83,7 @@ func next(start uintptr, size uintptr) uintptr {
// iterateRangeCanonical walks a canonical range.
//
//go:nosplit
-func (w *Walker) iterateRangeCanonical(start, end uintptr) {
+func (w *emptyWalker) iterateRangeCanonical(start, end uintptr) {
for pgdIndex := uint16((start & pgdMask) >> pgdShift); start < end && pgdIndex < entriesPerPage; pgdIndex++ {
var (
pgdEntry = &w.pageTables.root[pgdIndex]
@@ -121,19 +91,17 @@ func (w *Walker) iterateRangeCanonical(start, end uintptr) {
)
if !pgdEntry.Valid() {
if !w.visitor.requiresAlloc() {
- // Skip over this entry.
- start = next(start, pgdSize)
+
+ start = emptynext(start, pgdSize)
continue
}
- // Allocate a new pgd.
pudEntries = w.pageTables.Allocator.NewPTEs()
pgdEntry.setPageTable(w.pageTables, pudEntries)
} else {
pudEntries = w.pageTables.Allocator.LookupPTEs(pgdEntry.Address())
}
- // Map the next level.
clearPUDEntries := uint16(0)
for pudIndex := uint16((start & pudMask) >> pudShift); start < end && pudIndex < entriesPerPage; pudIndex++ {
@@ -143,33 +111,28 @@ func (w *Walker) iterateRangeCanonical(start, end uintptr) {
)
if !pudEntry.Valid() {
if !w.visitor.requiresAlloc() {
- // Skip over this entry.
+
clearPUDEntries++
- start = next(start, pudSize)
+ start = emptynext(start, pudSize)
continue
}
- // This level has 1-GB super pages. Is this
- // entire region at least as large as a single
- // PUD entry? If so, we can skip allocating a
- // new page for the pmd.
if start&(pudSize-1) == 0 && end-start >= pudSize {
pudEntry.SetSuper()
w.visitor.visit(uintptr(start), pudEntry, pudSize-1)
if pudEntry.Valid() {
- start = next(start, pudSize)
+ start = emptynext(start, pudSize)
continue
}
}
- // Allocate a new pud.
pmdEntries = w.pageTables.Allocator.NewPTEs()
pudEntry.setPageTable(w.pageTables, pmdEntries)
} else if pudEntry.IsSuper() {
- // Does this page need to be split?
- if w.visitor.requiresSplit() && (start&(pudSize-1) != 0 || end < next(start, pudSize)) {
- // Install the relevant entries.
+
+ if w.visitor.requiresSplit() && (start&(pudSize-1) != 0 || end < emptynext(start, pudSize)) {
+
pmdEntries = w.pageTables.Allocator.NewPTEs()
for index := uint16(0); index < entriesPerPage; index++ {
pmdEntries[index].SetSuper()
@@ -179,23 +142,20 @@ func (w *Walker) iterateRangeCanonical(start, end uintptr) {
}
pudEntry.setPageTable(w.pageTables, pmdEntries)
} else {
- // A super page to be checked directly.
+
w.visitor.visit(uintptr(start), pudEntry, pudSize-1)
- // Might have been cleared.
if !pudEntry.Valid() {
clearPUDEntries++
}
- // Note that the super page was changed.
- start = next(start, pudSize)
+ start = emptynext(start, pudSize)
continue
}
} else {
pmdEntries = w.pageTables.Allocator.LookupPTEs(pudEntry.Address())
}
- // Map the next level, since this is valid.
clearPMDEntries := uint16(0)
for pmdIndex := uint16((start & pmdMask) >> pmdShift); start < end && pmdIndex < entriesPerPage; pmdIndex++ {
@@ -205,32 +165,28 @@ func (w *Walker) iterateRangeCanonical(start, end uintptr) {
)
if !pmdEntry.Valid() {
if !w.visitor.requiresAlloc() {
- // Skip over this entry.
+
clearPMDEntries++
- start = next(start, pmdSize)
+ start = emptynext(start, pmdSize)
continue
}
- // This level has 2-MB huge pages. If this
- // region is contined in a single PMD entry?
- // As above, we can skip allocating a new page.
if start&(pmdSize-1) == 0 && end-start >= pmdSize {
pmdEntry.SetSuper()
w.visitor.visit(uintptr(start), pmdEntry, pmdSize-1)
if pmdEntry.Valid() {
- start = next(start, pmdSize)
+ start = emptynext(start, pmdSize)
continue
}
}
- // Allocate a new pmd.
pteEntries = w.pageTables.Allocator.NewPTEs()
pmdEntry.setPageTable(w.pageTables, pteEntries)
} else if pmdEntry.IsSuper() {
- // Does this page need to be split?
- if w.visitor.requiresSplit() && (start&(pmdSize-1) != 0 || end < next(start, pmdSize)) {
- // Install the relevant entries.
+
+ if w.visitor.requiresSplit() && (start&(pmdSize-1) != 0 || end < emptynext(start, pmdSize)) {
+
pteEntries = w.pageTables.Allocator.NewPTEs()
for index := uint16(0); index < entriesPerPage; index++ {
pteEntries[index].Set(
@@ -239,23 +195,20 @@ func (w *Walker) iterateRangeCanonical(start, end uintptr) {
}
pmdEntry.setPageTable(w.pageTables, pteEntries)
} else {
- // A huge page to be checked directly.
+
w.visitor.visit(uintptr(start), pmdEntry, pmdSize-1)
- // Might have been cleared.
if !pmdEntry.Valid() {
clearPMDEntries++
}
- // Note that the huge page was changed.
- start = next(start, pmdSize)
+ start = emptynext(start, pmdSize)
continue
}
} else {
pteEntries = w.pageTables.Allocator.LookupPTEs(pmdEntry.Address())
}
- // Map the next level, since this is valid.
clearPTEEntries := uint16(0)
for pteIndex := uint16((start & pteMask) >> pteShift); start < end && pteIndex < entriesPerPage; pteIndex++ {
@@ -268,7 +221,6 @@ func (w *Walker) iterateRangeCanonical(start, end uintptr) {
continue
}
- // At this point, we are guaranteed that start%pteSize == 0.
w.visitor.visit(uintptr(start), pteEntry, pteSize-1)
if !pteEntry.Valid() {
if w.visitor.requiresAlloc() {
@@ -277,12 +229,10 @@ func (w *Walker) iterateRangeCanonical(start, end uintptr) {
clearPTEEntries++
}
- // Note that the pte was changed.
start += pteSize
continue
}
- // Check if we no longer need this page.
if clearPTEEntries == entriesPerPage {
pmdEntry.Clear()
w.pageTables.Allocator.FreePTEs(pteEntries)
@@ -290,7 +240,6 @@ func (w *Walker) iterateRangeCanonical(start, end uintptr) {
}
}
- // Check if we no longer need this page.
if clearPMDEntries == entriesPerPage {
pudEntry.Clear()
w.pageTables.Allocator.FreePTEs(pmdEntries)
@@ -298,7 +247,6 @@ func (w *Walker) iterateRangeCanonical(start, end uintptr) {
}
}
- // Check if we no longer need this page.
if clearPUDEntries == entriesPerPage {
pgdEntry.Clear()
w.pageTables.Allocator.FreePTEs(pudEntries)
diff --git a/pkg/sentry/platform/ring0/pagetables/walker_lookup.go b/pkg/sentry/platform/ring0/pagetables/walker_lookup.go
new file mode 100755
index 000000000..906c9c50f
--- /dev/null
+++ b/pkg/sentry/platform/ring0/pagetables/walker_lookup.go
@@ -0,0 +1,255 @@
+package pagetables
+
+// Walker walks page tables.
+type lookupWalker struct {
+ // pageTables are the tables to walk.
+ pageTables *PageTables
+
+ // Visitor is the set of arguments.
+ visitor lookupVisitor
+}
+
+// iterateRange iterates over all appropriate levels of page tables for the given range.
+//
+// If requiresAlloc is true, then Set _must_ be called on all given PTEs. The
+// exception is super pages. If a valid super page (huge or jumbo) cannot be
+// installed, then the walk will continue to individual entries.
+//
+// This algorithm will attempt to maximize the use of super pages whenever
+// possible. Whether a super page is provided will be clear through the range
+// provided in the callback.
+//
+// Note that if requiresAlloc is true, then no gaps will be present. However,
+// if alloc is not set, then the iteration will likely be full of gaps.
+//
+// Note that this function should generally be avoided in favor of Map, Unmap,
+// etc. when not necessary.
+//
+// Precondition: start must be page-aligned.
+//
+// Precondition: start must be less than end.
+//
+// Precondition: If requiresAlloc is true, then start and end should not span
+// non-canonical ranges. If they do, a panic will result.
+//
+//go:nosplit
+func (w *lookupWalker) iterateRange(start, end uintptr) {
+ if start%pteSize != 0 {
+ panic("unaligned start")
+ }
+ if end < start {
+ panic("start > end")
+ }
+ if start < lowerTop {
+ if end <= lowerTop {
+ w.iterateRangeCanonical(start, end)
+ } else if end > lowerTop && end <= upperBottom {
+ if w.visitor.requiresAlloc() {
+ panic("alloc spans non-canonical range")
+ }
+ w.iterateRangeCanonical(start, lowerTop)
+ } else {
+ if w.visitor.requiresAlloc() {
+ panic("alloc spans non-canonical range")
+ }
+ w.iterateRangeCanonical(start, lowerTop)
+ w.iterateRangeCanonical(upperBottom, end)
+ }
+ } else if start < upperBottom {
+ if end <= upperBottom {
+ if w.visitor.requiresAlloc() {
+ panic("alloc spans non-canonical range")
+ }
+ } else {
+ if w.visitor.requiresAlloc() {
+ panic("alloc spans non-canonical range")
+ }
+ w.iterateRangeCanonical(upperBottom, end)
+ }
+ } else {
+ w.iterateRangeCanonical(start, end)
+ }
+}
+
+// next returns the next address quantized by the given size.
+//
+//go:nosplit
+func lookupnext(start uintptr, size uintptr) uintptr {
+ start &= ^(size - 1)
+ start += size
+ return start
+}
+
+// iterateRangeCanonical walks a canonical range.
+//
+//go:nosplit
+func (w *lookupWalker) iterateRangeCanonical(start, end uintptr) {
+ for pgdIndex := uint16((start & pgdMask) >> pgdShift); start < end && pgdIndex < entriesPerPage; pgdIndex++ {
+ var (
+ pgdEntry = &w.pageTables.root[pgdIndex]
+ pudEntries *PTEs
+ )
+ if !pgdEntry.Valid() {
+ if !w.visitor.requiresAlloc() {
+
+ start = lookupnext(start, pgdSize)
+ continue
+ }
+
+ pudEntries = w.pageTables.Allocator.NewPTEs()
+ pgdEntry.setPageTable(w.pageTables, pudEntries)
+ } else {
+ pudEntries = w.pageTables.Allocator.LookupPTEs(pgdEntry.Address())
+ }
+
+ clearPUDEntries := uint16(0)
+
+ for pudIndex := uint16((start & pudMask) >> pudShift); start < end && pudIndex < entriesPerPage; pudIndex++ {
+ var (
+ pudEntry = &pudEntries[pudIndex]
+ pmdEntries *PTEs
+ )
+ if !pudEntry.Valid() {
+ if !w.visitor.requiresAlloc() {
+
+ clearPUDEntries++
+ start = lookupnext(start, pudSize)
+ continue
+ }
+
+ if start&(pudSize-1) == 0 && end-start >= pudSize {
+ pudEntry.SetSuper()
+ w.visitor.visit(uintptr(start), pudEntry, pudSize-1)
+ if pudEntry.Valid() {
+ start = lookupnext(start, pudSize)
+ continue
+ }
+ }
+
+ pmdEntries = w.pageTables.Allocator.NewPTEs()
+ pudEntry.setPageTable(w.pageTables, pmdEntries)
+
+ } else if pudEntry.IsSuper() {
+
+ if w.visitor.requiresSplit() && (start&(pudSize-1) != 0 || end < lookupnext(start, pudSize)) {
+
+ pmdEntries = w.pageTables.Allocator.NewPTEs()
+ for index := uint16(0); index < entriesPerPage; index++ {
+ pmdEntries[index].SetSuper()
+ pmdEntries[index].Set(
+ pudEntry.Address()+(pmdSize*uintptr(index)),
+ pudEntry.Opts())
+ }
+ pudEntry.setPageTable(w.pageTables, pmdEntries)
+ } else {
+
+ w.visitor.visit(uintptr(start), pudEntry, pudSize-1)
+
+ if !pudEntry.Valid() {
+ clearPUDEntries++
+ }
+
+ start = lookupnext(start, pudSize)
+ continue
+ }
+ } else {
+ pmdEntries = w.pageTables.Allocator.LookupPTEs(pudEntry.Address())
+ }
+
+ clearPMDEntries := uint16(0)
+
+ for pmdIndex := uint16((start & pmdMask) >> pmdShift); start < end && pmdIndex < entriesPerPage; pmdIndex++ {
+ var (
+ pmdEntry = &pmdEntries[pmdIndex]
+ pteEntries *PTEs
+ )
+ if !pmdEntry.Valid() {
+ if !w.visitor.requiresAlloc() {
+
+ clearPMDEntries++
+ start = lookupnext(start, pmdSize)
+ continue
+ }
+
+ if start&(pmdSize-1) == 0 && end-start >= pmdSize {
+ pmdEntry.SetSuper()
+ w.visitor.visit(uintptr(start), pmdEntry, pmdSize-1)
+ if pmdEntry.Valid() {
+ start = lookupnext(start, pmdSize)
+ continue
+ }
+ }
+
+ pteEntries = w.pageTables.Allocator.NewPTEs()
+ pmdEntry.setPageTable(w.pageTables, pteEntries)
+
+ } else if pmdEntry.IsSuper() {
+
+ if w.visitor.requiresSplit() && (start&(pmdSize-1) != 0 || end < lookupnext(start, pmdSize)) {
+
+ pteEntries = w.pageTables.Allocator.NewPTEs()
+ for index := uint16(0); index < entriesPerPage; index++ {
+ pteEntries[index].Set(
+ pmdEntry.Address()+(pteSize*uintptr(index)),
+ pmdEntry.Opts())
+ }
+ pmdEntry.setPageTable(w.pageTables, pteEntries)
+ } else {
+
+ w.visitor.visit(uintptr(start), pmdEntry, pmdSize-1)
+
+ if !pmdEntry.Valid() {
+ clearPMDEntries++
+ }
+
+ start = lookupnext(start, pmdSize)
+ continue
+ }
+ } else {
+ pteEntries = w.pageTables.Allocator.LookupPTEs(pmdEntry.Address())
+ }
+
+ clearPTEEntries := uint16(0)
+
+ for pteIndex := uint16((start & pteMask) >> pteShift); start < end && pteIndex < entriesPerPage; pteIndex++ {
+ var (
+ pteEntry = &pteEntries[pteIndex]
+ )
+ if !pteEntry.Valid() && !w.visitor.requiresAlloc() {
+ clearPTEEntries++
+ start += pteSize
+ continue
+ }
+
+ w.visitor.visit(uintptr(start), pteEntry, pteSize-1)
+ if !pteEntry.Valid() {
+ if w.visitor.requiresAlloc() {
+ panic("PTE not set after iteration with requiresAlloc!")
+ }
+ clearPTEEntries++
+ }
+
+ start += pteSize
+ continue
+ }
+
+ if clearPTEEntries == entriesPerPage {
+ pmdEntry.Clear()
+ w.pageTables.Allocator.FreePTEs(pteEntries)
+ clearPMDEntries++
+ }
+ }
+
+ if clearPMDEntries == entriesPerPage {
+ pudEntry.Clear()
+ w.pageTables.Allocator.FreePTEs(pmdEntries)
+ clearPUDEntries++
+ }
+ }
+
+ if clearPUDEntries == entriesPerPage {
+ pgdEntry.Clear()
+ w.pageTables.Allocator.FreePTEs(pudEntries)
+ }
+ }
+}
diff --git a/pkg/sentry/platform/ring0/pagetables/walker_map.go b/pkg/sentry/platform/ring0/pagetables/walker_map.go
new file mode 100755
index 000000000..61ee3c825
--- /dev/null
+++ b/pkg/sentry/platform/ring0/pagetables/walker_map.go
@@ -0,0 +1,255 @@
+package pagetables
+
+// Walker walks page tables.
+type mapWalker struct {
+ // pageTables are the tables to walk.
+ pageTables *PageTables
+
+ // Visitor is the set of arguments.
+ visitor mapVisitor
+}
+
+// iterateRange iterates over all appropriate levels of page tables for the given range.
+//
+// If requiresAlloc is true, then Set _must_ be called on all given PTEs. The
+// exception is super pages. If a valid super page (huge or jumbo) cannot be
+// installed, then the walk will continue to individual entries.
+//
+// This algorithm will attempt to maximize the use of super pages whenever
+// possible. Whether a super page is provided will be clear through the range
+// provided in the callback.
+//
+// Note that if requiresAlloc is true, then no gaps will be present. However,
+// if alloc is not set, then the iteration will likely be full of gaps.
+//
+// Note that this function should generally be avoided in favor of Map, Unmap,
+// etc. when not necessary.
+//
+// Precondition: start must be page-aligned.
+//
+// Precondition: start must be less than end.
+//
+// Precondition: If requiresAlloc is true, then start and end should not span
+// non-canonical ranges. If they do, a panic will result.
+//
+//go:nosplit
+func (w *mapWalker) iterateRange(start, end uintptr) {
+ if start%pteSize != 0 {
+ panic("unaligned start")
+ }
+ if end < start {
+ panic("start > end")
+ }
+ if start < lowerTop {
+ if end <= lowerTop {
+ w.iterateRangeCanonical(start, end)
+ } else if end > lowerTop && end <= upperBottom {
+ if w.visitor.requiresAlloc() {
+ panic("alloc spans non-canonical range")
+ }
+ w.iterateRangeCanonical(start, lowerTop)
+ } else {
+ if w.visitor.requiresAlloc() {
+ panic("alloc spans non-canonical range")
+ }
+ w.iterateRangeCanonical(start, lowerTop)
+ w.iterateRangeCanonical(upperBottom, end)
+ }
+ } else if start < upperBottom {
+ if end <= upperBottom {
+ if w.visitor.requiresAlloc() {
+ panic("alloc spans non-canonical range")
+ }
+ } else {
+ if w.visitor.requiresAlloc() {
+ panic("alloc spans non-canonical range")
+ }
+ w.iterateRangeCanonical(upperBottom, end)
+ }
+ } else {
+ w.iterateRangeCanonical(start, end)
+ }
+}
+
+// next returns the next address quantized by the given size.
+//
+//go:nosplit
+func mapnext(start uintptr, size uintptr) uintptr {
+ start &= ^(size - 1)
+ start += size
+ return start
+}
+
+// iterateRangeCanonical walks a canonical range.
+//
+//go:nosplit
+func (w *mapWalker) iterateRangeCanonical(start, end uintptr) {
+ for pgdIndex := uint16((start & pgdMask) >> pgdShift); start < end && pgdIndex < entriesPerPage; pgdIndex++ {
+ var (
+ pgdEntry = &w.pageTables.root[pgdIndex]
+ pudEntries *PTEs
+ )
+ if !pgdEntry.Valid() {
+ if !w.visitor.requiresAlloc() {
+
+ start = mapnext(start, pgdSize)
+ continue
+ }
+
+ pudEntries = w.pageTables.Allocator.NewPTEs()
+ pgdEntry.setPageTable(w.pageTables, pudEntries)
+ } else {
+ pudEntries = w.pageTables.Allocator.LookupPTEs(pgdEntry.Address())
+ }
+
+ clearPUDEntries := uint16(0)
+
+ for pudIndex := uint16((start & pudMask) >> pudShift); start < end && pudIndex < entriesPerPage; pudIndex++ {
+ var (
+ pudEntry = &pudEntries[pudIndex]
+ pmdEntries *PTEs
+ )
+ if !pudEntry.Valid() {
+ if !w.visitor.requiresAlloc() {
+
+ clearPUDEntries++
+ start = mapnext(start, pudSize)
+ continue
+ }
+
+ if start&(pudSize-1) == 0 && end-start >= pudSize {
+ pudEntry.SetSuper()
+ w.visitor.visit(uintptr(start), pudEntry, pudSize-1)
+ if pudEntry.Valid() {
+ start = mapnext(start, pudSize)
+ continue
+ }
+ }
+
+ pmdEntries = w.pageTables.Allocator.NewPTEs()
+ pudEntry.setPageTable(w.pageTables, pmdEntries)
+
+ } else if pudEntry.IsSuper() {
+
+ if w.visitor.requiresSplit() && (start&(pudSize-1) != 0 || end < mapnext(start, pudSize)) {
+
+ pmdEntries = w.pageTables.Allocator.NewPTEs()
+ for index := uint16(0); index < entriesPerPage; index++ {
+ pmdEntries[index].SetSuper()
+ pmdEntries[index].Set(
+ pudEntry.Address()+(pmdSize*uintptr(index)),
+ pudEntry.Opts())
+ }
+ pudEntry.setPageTable(w.pageTables, pmdEntries)
+ } else {
+
+ w.visitor.visit(uintptr(start), pudEntry, pudSize-1)
+
+ if !pudEntry.Valid() {
+ clearPUDEntries++
+ }
+
+ start = mapnext(start, pudSize)
+ continue
+ }
+ } else {
+ pmdEntries = w.pageTables.Allocator.LookupPTEs(pudEntry.Address())
+ }
+
+ clearPMDEntries := uint16(0)
+
+ for pmdIndex := uint16((start & pmdMask) >> pmdShift); start < end && pmdIndex < entriesPerPage; pmdIndex++ {
+ var (
+ pmdEntry = &pmdEntries[pmdIndex]
+ pteEntries *PTEs
+ )
+ if !pmdEntry.Valid() {
+ if !w.visitor.requiresAlloc() {
+
+ clearPMDEntries++
+ start = mapnext(start, pmdSize)
+ continue
+ }
+
+ if start&(pmdSize-1) == 0 && end-start >= pmdSize {
+ pmdEntry.SetSuper()
+ w.visitor.visit(uintptr(start), pmdEntry, pmdSize-1)
+ if pmdEntry.Valid() {
+ start = mapnext(start, pmdSize)
+ continue
+ }
+ }
+
+ pteEntries = w.pageTables.Allocator.NewPTEs()
+ pmdEntry.setPageTable(w.pageTables, pteEntries)
+
+ } else if pmdEntry.IsSuper() {
+
+ if w.visitor.requiresSplit() && (start&(pmdSize-1) != 0 || end < mapnext(start, pmdSize)) {
+
+ pteEntries = w.pageTables.Allocator.NewPTEs()
+ for index := uint16(0); index < entriesPerPage; index++ {
+ pteEntries[index].Set(
+ pmdEntry.Address()+(pteSize*uintptr(index)),
+ pmdEntry.Opts())
+ }
+ pmdEntry.setPageTable(w.pageTables, pteEntries)
+ } else {
+
+ w.visitor.visit(uintptr(start), pmdEntry, pmdSize-1)
+
+ if !pmdEntry.Valid() {
+ clearPMDEntries++
+ }
+
+ start = mapnext(start, pmdSize)
+ continue
+ }
+ } else {
+ pteEntries = w.pageTables.Allocator.LookupPTEs(pmdEntry.Address())
+ }
+
+ clearPTEEntries := uint16(0)
+
+ for pteIndex := uint16((start & pteMask) >> pteShift); start < end && pteIndex < entriesPerPage; pteIndex++ {
+ var (
+ pteEntry = &pteEntries[pteIndex]
+ )
+ if !pteEntry.Valid() && !w.visitor.requiresAlloc() {
+ clearPTEEntries++
+ start += pteSize
+ continue
+ }
+
+ w.visitor.visit(uintptr(start), pteEntry, pteSize-1)
+ if !pteEntry.Valid() {
+ if w.visitor.requiresAlloc() {
+ panic("PTE not set after iteration with requiresAlloc!")
+ }
+ clearPTEEntries++
+ }
+
+ start += pteSize
+ continue
+ }
+
+ if clearPTEEntries == entriesPerPage {
+ pmdEntry.Clear()
+ w.pageTables.Allocator.FreePTEs(pteEntries)
+ clearPMDEntries++
+ }
+ }
+
+ if clearPMDEntries == entriesPerPage {
+ pudEntry.Clear()
+ w.pageTables.Allocator.FreePTEs(pmdEntries)
+ clearPUDEntries++
+ }
+ }
+
+ if clearPUDEntries == entriesPerPage {
+ pgdEntry.Clear()
+ w.pageTables.Allocator.FreePTEs(pudEntries)
+ }
+ }
+}
diff --git a/pkg/sentry/platform/ring0/pagetables/walker_unmap.go b/pkg/sentry/platform/ring0/pagetables/walker_unmap.go
new file mode 100755
index 000000000..be2aa0ce4
--- /dev/null
+++ b/pkg/sentry/platform/ring0/pagetables/walker_unmap.go
@@ -0,0 +1,255 @@
+package pagetables
+
+// Walker walks page tables.
+type unmapWalker struct {
+ // pageTables are the tables to walk.
+ pageTables *PageTables
+
+ // Visitor is the set of arguments.
+ visitor unmapVisitor
+}
+
+// iterateRange iterates over all appropriate levels of page tables for the given range.
+//
+// If requiresAlloc is true, then Set _must_ be called on all given PTEs. The
+// exception is super pages. If a valid super page (huge or jumbo) cannot be
+// installed, then the walk will continue to individual entries.
+//
+// This algorithm will attempt to maximize the use of super pages whenever
+// possible. Whether a super page is provided will be clear through the range
+// provided in the callback.
+//
+// Note that if requiresAlloc is true, then no gaps will be present. However,
+// if alloc is not set, then the iteration will likely be full of gaps.
+//
+// Note that this function should generally be avoided in favor of Map, Unmap,
+// etc. when not necessary.
+//
+// Precondition: start must be page-aligned.
+//
+// Precondition: start must be less than end.
+//
+// Precondition: If requiresAlloc is true, then start and end should not span
+// non-canonical ranges. If they do, a panic will result.
+//
+//go:nosplit
+func (w *unmapWalker) iterateRange(start, end uintptr) {
+ if start%pteSize != 0 {
+ panic("unaligned start")
+ }
+ if end < start {
+ panic("start > end")
+ }
+ if start < lowerTop {
+ if end <= lowerTop {
+ w.iterateRangeCanonical(start, end)
+ } else if end > lowerTop && end <= upperBottom {
+ if w.visitor.requiresAlloc() {
+ panic("alloc spans non-canonical range")
+ }
+ w.iterateRangeCanonical(start, lowerTop)
+ } else {
+ if w.visitor.requiresAlloc() {
+ panic("alloc spans non-canonical range")
+ }
+ w.iterateRangeCanonical(start, lowerTop)
+ w.iterateRangeCanonical(upperBottom, end)
+ }
+ } else if start < upperBottom {
+ if end <= upperBottom {
+ if w.visitor.requiresAlloc() {
+ panic("alloc spans non-canonical range")
+ }
+ } else {
+ if w.visitor.requiresAlloc() {
+ panic("alloc spans non-canonical range")
+ }
+ w.iterateRangeCanonical(upperBottom, end)
+ }
+ } else {
+ w.iterateRangeCanonical(start, end)
+ }
+}
+
+// next returns the next address quantized by the given size.
+//
+//go:nosplit
+func unmapnext(start uintptr, size uintptr) uintptr {
+ start &= ^(size - 1)
+ start += size
+ return start
+}
+
+// iterateRangeCanonical walks a canonical range.
+//
+//go:nosplit
+func (w *unmapWalker) iterateRangeCanonical(start, end uintptr) {
+ for pgdIndex := uint16((start & pgdMask) >> pgdShift); start < end && pgdIndex < entriesPerPage; pgdIndex++ {
+ var (
+ pgdEntry = &w.pageTables.root[pgdIndex]
+ pudEntries *PTEs
+ )
+ if !pgdEntry.Valid() {
+ if !w.visitor.requiresAlloc() {
+
+ start = unmapnext(start, pgdSize)
+ continue
+ }
+
+ pudEntries = w.pageTables.Allocator.NewPTEs()
+ pgdEntry.setPageTable(w.pageTables, pudEntries)
+ } else {
+ pudEntries = w.pageTables.Allocator.LookupPTEs(pgdEntry.Address())
+ }
+
+ clearPUDEntries := uint16(0)
+
+ for pudIndex := uint16((start & pudMask) >> pudShift); start < end && pudIndex < entriesPerPage; pudIndex++ {
+ var (
+ pudEntry = &pudEntries[pudIndex]
+ pmdEntries *PTEs
+ )
+ if !pudEntry.Valid() {
+ if !w.visitor.requiresAlloc() {
+
+ clearPUDEntries++
+ start = unmapnext(start, pudSize)
+ continue
+ }
+
+ if start&(pudSize-1) == 0 && end-start >= pudSize {
+ pudEntry.SetSuper()
+ w.visitor.visit(uintptr(start), pudEntry, pudSize-1)
+ if pudEntry.Valid() {
+ start = unmapnext(start, pudSize)
+ continue
+ }
+ }
+
+ pmdEntries = w.pageTables.Allocator.NewPTEs()
+ pudEntry.setPageTable(w.pageTables, pmdEntries)
+
+ } else if pudEntry.IsSuper() {
+
+ if w.visitor.requiresSplit() && (start&(pudSize-1) != 0 || end < unmapnext(start, pudSize)) {
+
+ pmdEntries = w.pageTables.Allocator.NewPTEs()
+ for index := uint16(0); index < entriesPerPage; index++ {
+ pmdEntries[index].SetSuper()
+ pmdEntries[index].Set(
+ pudEntry.Address()+(pmdSize*uintptr(index)),
+ pudEntry.Opts())
+ }
+ pudEntry.setPageTable(w.pageTables, pmdEntries)
+ } else {
+
+ w.visitor.visit(uintptr(start), pudEntry, pudSize-1)
+
+ if !pudEntry.Valid() {
+ clearPUDEntries++
+ }
+
+ start = unmapnext(start, pudSize)
+ continue
+ }
+ } else {
+ pmdEntries = w.pageTables.Allocator.LookupPTEs(pudEntry.Address())
+ }
+
+ clearPMDEntries := uint16(0)
+
+ for pmdIndex := uint16((start & pmdMask) >> pmdShift); start < end && pmdIndex < entriesPerPage; pmdIndex++ {
+ var (
+ pmdEntry = &pmdEntries[pmdIndex]
+ pteEntries *PTEs
+ )
+ if !pmdEntry.Valid() {
+ if !w.visitor.requiresAlloc() {
+
+ clearPMDEntries++
+ start = unmapnext(start, pmdSize)
+ continue
+ }
+
+ if start&(pmdSize-1) == 0 && end-start >= pmdSize {
+ pmdEntry.SetSuper()
+ w.visitor.visit(uintptr(start), pmdEntry, pmdSize-1)
+ if pmdEntry.Valid() {
+ start = unmapnext(start, pmdSize)
+ continue
+ }
+ }
+
+ pteEntries = w.pageTables.Allocator.NewPTEs()
+ pmdEntry.setPageTable(w.pageTables, pteEntries)
+
+ } else if pmdEntry.IsSuper() {
+
+ if w.visitor.requiresSplit() && (start&(pmdSize-1) != 0 || end < unmapnext(start, pmdSize)) {
+
+ pteEntries = w.pageTables.Allocator.NewPTEs()
+ for index := uint16(0); index < entriesPerPage; index++ {
+ pteEntries[index].Set(
+ pmdEntry.Address()+(pteSize*uintptr(index)),
+ pmdEntry.Opts())
+ }
+ pmdEntry.setPageTable(w.pageTables, pteEntries)
+ } else {
+
+ w.visitor.visit(uintptr(start), pmdEntry, pmdSize-1)
+
+ if !pmdEntry.Valid() {
+ clearPMDEntries++
+ }
+
+ start = unmapnext(start, pmdSize)
+ continue
+ }
+ } else {
+ pteEntries = w.pageTables.Allocator.LookupPTEs(pmdEntry.Address())
+ }
+
+ clearPTEEntries := uint16(0)
+
+ for pteIndex := uint16((start & pteMask) >> pteShift); start < end && pteIndex < entriesPerPage; pteIndex++ {
+ var (
+ pteEntry = &pteEntries[pteIndex]
+ )
+ if !pteEntry.Valid() && !w.visitor.requiresAlloc() {
+ clearPTEEntries++
+ start += pteSize
+ continue
+ }
+
+ w.visitor.visit(uintptr(start), pteEntry, pteSize-1)
+ if !pteEntry.Valid() {
+ if w.visitor.requiresAlloc() {
+ panic("PTE not set after iteration with requiresAlloc!")
+ }
+ clearPTEEntries++
+ }
+
+ start += pteSize
+ continue
+ }
+
+ if clearPTEEntries == entriesPerPage {
+ pmdEntry.Clear()
+ w.pageTables.Allocator.FreePTEs(pteEntries)
+ clearPMDEntries++
+ }
+ }
+
+ if clearPMDEntries == entriesPerPage {
+ pudEntry.Clear()
+ w.pageTables.Allocator.FreePTEs(pmdEntries)
+ clearPUDEntries++
+ }
+ }
+
+ if clearPUDEntries == entriesPerPage {
+ pgdEntry.Clear()
+ w.pageTables.Allocator.FreePTEs(pudEntries)
+ }
+ }
+}
diff --git a/pkg/sentry/platform/ring0/ring0_state_autogen.go b/pkg/sentry/platform/ring0/ring0_state_autogen.go
new file mode 100755
index 000000000..462f9a446
--- /dev/null
+++ b/pkg/sentry/platform/ring0/ring0_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package ring0
+
diff --git a/pkg/sentry/platform/ring0/x86.go b/pkg/sentry/platform/ring0/x86.go
deleted file mode 100644
index 5f80d64e8..000000000
--- a/pkg/sentry/platform/ring0/x86.go
+++ /dev/null
@@ -1,264 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// +build i386 amd64
-
-package ring0
-
-import (
- "gvisor.dev/gvisor/pkg/cpuid"
-)
-
-// Useful bits.
-const (
- _CR0_PE = 1 << 0
- _CR0_ET = 1 << 4
- _CR0_AM = 1 << 18
- _CR0_PG = 1 << 31
-
- _CR4_PSE = 1 << 4
- _CR4_PAE = 1 << 5
- _CR4_PGE = 1 << 7
- _CR4_OSFXSR = 1 << 9
- _CR4_OSXMMEXCPT = 1 << 10
- _CR4_FSGSBASE = 1 << 16
- _CR4_PCIDE = 1 << 17
- _CR4_OSXSAVE = 1 << 18
- _CR4_SMEP = 1 << 20
-
- _RFLAGS_AC = 1 << 18
- _RFLAGS_NT = 1 << 14
- _RFLAGS_IOPL = 3 << 12
- _RFLAGS_DF = 1 << 10
- _RFLAGS_IF = 1 << 9
- _RFLAGS_STEP = 1 << 8
- _RFLAGS_RESERVED = 1 << 1
-
- _EFER_SCE = 0x001
- _EFER_LME = 0x100
- _EFER_LMA = 0x400
- _EFER_NX = 0x800
-
- _MSR_STAR = 0xc0000081
- _MSR_LSTAR = 0xc0000082
- _MSR_CSTAR = 0xc0000083
- _MSR_SYSCALL_MASK = 0xc0000084
- _MSR_PLATFORM_INFO = 0xce
- _MSR_MISC_FEATURES = 0x140
-
- _PLATFORM_INFO_CPUID_FAULT = 1 << 31
-
- _MISC_FEATURE_CPUID_TRAP = 0x1
-)
-
-const (
- // KernelFlagsSet should always be set in the kernel.
- KernelFlagsSet = _RFLAGS_RESERVED
-
- // UserFlagsSet are always set in userspace.
- UserFlagsSet = _RFLAGS_RESERVED | _RFLAGS_IF
-
- // KernelFlagsClear should always be clear in the kernel.
- KernelFlagsClear = _RFLAGS_STEP | _RFLAGS_IF | _RFLAGS_IOPL | _RFLAGS_AC | _RFLAGS_NT
-
- // UserFlagsClear are always cleared in userspace.
- UserFlagsClear = _RFLAGS_NT | _RFLAGS_IOPL
-)
-
-// Vector is an exception vector.
-type Vector uintptr
-
-// Exception vectors.
-const (
- DivideByZero Vector = iota
- Debug
- NMI
- Breakpoint
- Overflow
- BoundRangeExceeded
- InvalidOpcode
- DeviceNotAvailable
- DoubleFault
- CoprocessorSegmentOverrun
- InvalidTSS
- SegmentNotPresent
- StackSegmentFault
- GeneralProtectionFault
- PageFault
- _
- X87FloatingPointException
- AlignmentCheck
- MachineCheck
- SIMDFloatingPointException
- VirtualizationException
- SecurityException = 0x1e
- SyscallInt80 = 0x80
- _NR_INTERRUPTS = SyscallInt80 + 1
-)
-
-// System call vectors.
-const (
- Syscall Vector = _NR_INTERRUPTS
-)
-
-// VirtualAddressBits returns the number bits available for virtual addresses.
-//
-// Note that sign-extension semantics apply to the highest order bit.
-//
-// FIXME(b/69382326): This should use the cpuid passed to Init.
-func VirtualAddressBits() uint32 {
- ax, _, _, _ := cpuid.HostID(0x80000008, 0)
- return (ax >> 8) & 0xff
-}
-
-// PhysicalAddressBits returns the number of bits available for physical addresses.
-//
-// FIXME(b/69382326): This should use the cpuid passed to Init.
-func PhysicalAddressBits() uint32 {
- ax, _, _, _ := cpuid.HostID(0x80000008, 0)
- return ax & 0xff
-}
-
-// Selector is a segment Selector.
-type Selector uint16
-
-// SegmentDescriptor is a segment descriptor.
-type SegmentDescriptor struct {
- bits [2]uint32
-}
-
-// descriptorTable is a collection of descriptors.
-type descriptorTable [32]SegmentDescriptor
-
-// SegmentDescriptorFlags are typed flags within a descriptor.
-type SegmentDescriptorFlags uint32
-
-// SegmentDescriptorFlag declarations.
-const (
- SegmentDescriptorAccess SegmentDescriptorFlags = 1 << 8 // Access bit (always set).
- SegmentDescriptorWrite = 1 << 9 // Write permission.
- SegmentDescriptorExpandDown = 1 << 10 // Grows down, not used.
- SegmentDescriptorExecute = 1 << 11 // Execute permission.
- SegmentDescriptorSystem = 1 << 12 // Zero => system, 1 => user code/data.
- SegmentDescriptorPresent = 1 << 15 // Present.
- SegmentDescriptorAVL = 1 << 20 // Available.
- SegmentDescriptorLong = 1 << 21 // Long mode.
- SegmentDescriptorDB = 1 << 22 // 16 or 32-bit.
- SegmentDescriptorG = 1 << 23 // Granularity: page or byte.
-)
-
-// Base returns the descriptor's base linear address.
-func (d *SegmentDescriptor) Base() uint32 {
- return d.bits[1]&0xFF000000 | (d.bits[1]&0x000000FF)<<16 | d.bits[0]>>16
-}
-
-// Limit returns the descriptor size.
-func (d *SegmentDescriptor) Limit() uint32 {
- l := d.bits[0]&0xFFFF | d.bits[1]&0xF0000
- if d.bits[1]&uint32(SegmentDescriptorG) != 0 {
- l <<= 12
- l |= 0xFFF
- }
- return l
-}
-
-// Flags returns descriptor flags.
-func (d *SegmentDescriptor) Flags() SegmentDescriptorFlags {
- return SegmentDescriptorFlags(d.bits[1] & 0x00F09F00)
-}
-
-// DPL returns the descriptor privilege level.
-func (d *SegmentDescriptor) DPL() int {
- return int((d.bits[1] >> 13) & 3)
-}
-
-func (d *SegmentDescriptor) setNull() {
- d.bits[0] = 0
- d.bits[1] = 0
-}
-
-func (d *SegmentDescriptor) set(base, limit uint32, dpl int, flags SegmentDescriptorFlags) {
- flags |= SegmentDescriptorPresent
- if limit>>12 != 0 {
- limit >>= 12
- flags |= SegmentDescriptorG
- }
- d.bits[0] = base<<16 | limit&0xFFFF
- d.bits[1] = base&0xFF000000 | (base>>16)&0xFF | limit&0x000F0000 | uint32(flags) | uint32(dpl)<<13
-}
-
-func (d *SegmentDescriptor) setCode32(base, limit uint32, dpl int) {
- d.set(base, limit, dpl,
- SegmentDescriptorDB|
- SegmentDescriptorExecute|
- SegmentDescriptorSystem)
-}
-
-func (d *SegmentDescriptor) setCode64(base, limit uint32, dpl int) {
- d.set(base, limit, dpl,
- SegmentDescriptorG|
- SegmentDescriptorLong|
- SegmentDescriptorExecute|
- SegmentDescriptorSystem)
-}
-
-func (d *SegmentDescriptor) setData(base, limit uint32, dpl int) {
- d.set(base, limit, dpl,
- SegmentDescriptorWrite|
- SegmentDescriptorSystem)
-}
-
-// setHi is only used for the TSS segment, which is magically 64-bits.
-func (d *SegmentDescriptor) setHi(base uint32) {
- d.bits[0] = base
- d.bits[1] = 0
-}
-
-// Gate64 is a 64-bit task, trap, or interrupt gate.
-type Gate64 struct {
- bits [4]uint32
-}
-
-// idt64 is a 64-bit interrupt descriptor table.
-type idt64 [_NR_INTERRUPTS]Gate64
-
-func (g *Gate64) setInterrupt(cs Selector, rip uint64, dpl int, ist int) {
- g.bits[0] = uint32(cs)<<16 | uint32(rip)&0xFFFF
- g.bits[1] = uint32(rip)&0xFFFF0000 | SegmentDescriptorPresent | uint32(dpl)<<13 | 14<<8 | uint32(ist)&0x7
- g.bits[2] = uint32(rip >> 32)
-}
-
-func (g *Gate64) setTrap(cs Selector, rip uint64, dpl int, ist int) {
- g.setInterrupt(cs, rip, dpl, ist)
- g.bits[1] |= 1 << 8
-}
-
-// TaskState64 is a 64-bit task state structure.
-type TaskState64 struct {
- _ uint32
- rsp0Lo, rsp0Hi uint32
- rsp1Lo, rsp1Hi uint32
- rsp2Lo, rsp2Hi uint32
- _ [2]uint32
- ist1Lo, ist1Hi uint32
- ist2Lo, ist2Hi uint32
- ist3Lo, ist3Hi uint32
- ist4Lo, ist4Hi uint32
- ist5Lo, ist5Hi uint32
- ist6Lo, ist6Hi uint32
- ist7Lo, ist7Hi uint32
- _ [2]uint32
- _ uint16
- ioPerm uint16
-}
diff --git a/pkg/sentry/platform/safecopy/BUILD b/pkg/sentry/platform/safecopy/BUILD
deleted file mode 100644
index 6769cd0a5..000000000
--- a/pkg/sentry/platform/safecopy/BUILD
+++ /dev/null
@@ -1,31 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "safecopy",
- srcs = [
- "atomic_amd64.s",
- "atomic_arm64.s",
- "memclr_amd64.s",
- "memclr_arm64.s",
- "memcpy_amd64.s",
- "memcpy_arm64.s",
- "safecopy.go",
- "safecopy_unsafe.go",
- "sighandler_amd64.s",
- "sighandler_arm64.s",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/platform/safecopy",
- visibility = ["//pkg/sentry:internal"],
- deps = ["//pkg/syserror"],
-)
-
-go_test(
- name = "safecopy_test",
- srcs = [
- "safecopy_test.go",
- ],
- embed = [":safecopy"],
-)
diff --git a/pkg/sentry/platform/safecopy/LICENSE b/pkg/sentry/platform/safecopy/LICENSE
deleted file mode 100644
index 6a66aea5e..000000000
--- a/pkg/sentry/platform/safecopy/LICENSE
+++ /dev/null
@@ -1,27 +0,0 @@
-Copyright (c) 2009 The Go Authors. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
- * Redistributions of source code must retain the above copyright
-notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above
-copyright notice, this list of conditions and the following disclaimer
-in the documentation and/or other materials provided with the
-distribution.
- * Neither the name of Google Inc. nor the names of its
-contributors may be used to endorse or promote products derived from
-this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkg/sentry/platform/safecopy/safecopy_state_autogen.go b/pkg/sentry/platform/safecopy/safecopy_state_autogen.go
new file mode 100755
index 000000000..58fd8fbd0
--- /dev/null
+++ b/pkg/sentry/platform/safecopy/safecopy_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package safecopy
+
diff --git a/pkg/sentry/platform/safecopy/safecopy_test.go b/pkg/sentry/platform/safecopy/safecopy_test.go
deleted file mode 100644
index 5818f7f9b..000000000
--- a/pkg/sentry/platform/safecopy/safecopy_test.go
+++ /dev/null
@@ -1,617 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package safecopy
-
-import (
- "bytes"
- "fmt"
- "io/ioutil"
- "math/rand"
- "os"
- "runtime/debug"
- "syscall"
- "testing"
- "unsafe"
-)
-
-// Size of a page in bytes. Cloned from usermem.PageSize to avoid a circular
-// dependency.
-const pageSize = 4096
-
-func initRandom(b []byte) {
- for i := range b {
- b[i] = byte(rand.Intn(256))
- }
-}
-
-func randBuf(size int) []byte {
- b := make([]byte, size)
- initRandom(b)
- return b
-}
-
-func TestCopyInSuccess(t *testing.T) {
- // Test that CopyIn does not return an error when all pages are accessible.
- const bufLen = 8192
- a := randBuf(bufLen)
- b := make([]byte, bufLen)
-
- n, err := CopyIn(b, unsafe.Pointer(&a[0]))
- if n != bufLen {
- t.Errorf("Unexpected copy length, got %v, want %v", n, bufLen)
- }
- if err != nil {
- t.Errorf("Unexpected error: %v", err)
- }
- if !bytes.Equal(a, b) {
- t.Errorf("Buffers are not equal when they should be: %v %v", a, b)
- }
-}
-
-func TestCopyOutSuccess(t *testing.T) {
- // Test that CopyOut does not return an error when all pages are
- // accessible.
- const bufLen = 8192
- a := randBuf(bufLen)
- b := make([]byte, bufLen)
-
- n, err := CopyOut(unsafe.Pointer(&b[0]), a)
- if n != bufLen {
- t.Errorf("Unexpected copy length, got %v, want %v", n, bufLen)
- }
- if err != nil {
- t.Errorf("Unexpected error: %v", err)
- }
- if !bytes.Equal(a, b) {
- t.Errorf("Buffers are not equal when they should be: %v %v", a, b)
- }
-}
-
-func TestCopySuccess(t *testing.T) {
- // Test that Copy does not return an error when all pages are accessible.
- const bufLen = 8192
- a := randBuf(bufLen)
- b := make([]byte, bufLen)
-
- n, err := Copy(unsafe.Pointer(&b[0]), unsafe.Pointer(&a[0]), bufLen)
- if n != bufLen {
- t.Errorf("Unexpected copy length, got %v, want %v", n, bufLen)
- }
- if err != nil {
- t.Errorf("Unexpected error: %v", err)
- }
- if !bytes.Equal(a, b) {
- t.Errorf("Buffers are not equal when they should be: %v %v", a, b)
- }
-}
-
-func TestZeroOutSuccess(t *testing.T) {
- // Test that ZeroOut does not return an error when all pages are
- // accessible.
- const bufLen = 8192
- a := make([]byte, bufLen)
- b := randBuf(bufLen)
-
- n, err := ZeroOut(unsafe.Pointer(&b[0]), bufLen)
- if n != bufLen {
- t.Errorf("Unexpected copy length, got %v, want %v", n, bufLen)
- }
- if err != nil {
- t.Errorf("Unexpected error: %v", err)
- }
- if !bytes.Equal(a, b) {
- t.Errorf("Buffers are not equal when they should be: %v %v", a, b)
- }
-}
-
-func TestSwapUint32Success(t *testing.T) {
- // Test that SwapUint32 does not return an error when the page is
- // accessible.
- before := uint32(rand.Int31())
- after := uint32(rand.Int31())
- val := before
-
- old, err := SwapUint32(unsafe.Pointer(&val), after)
- if err != nil {
- t.Errorf("Unexpected error: %v", err)
- }
- if old != before {
- t.Errorf("Unexpected old value: got %v, want %v", old, before)
- }
- if val != after {
- t.Errorf("Unexpected new value: got %v, want %v", val, after)
- }
-}
-
-func TestSwapUint32AlignmentError(t *testing.T) {
- // Test that SwapUint32 returns an AlignmentError when passed an unaligned
- // address.
- data := new(struct{ val uint64 })
- addr := uintptr(unsafe.Pointer(&data.val)) + 1
- want := AlignmentError{Addr: addr, Alignment: 4}
- if _, err := SwapUint32(unsafe.Pointer(addr), 1); err != want {
- t.Errorf("Unexpected error: got %v, want %v", err, want)
- }
-}
-
-func TestSwapUint64Success(t *testing.T) {
- // Test that SwapUint64 does not return an error when the page is
- // accessible.
- before := uint64(rand.Int63())
- after := uint64(rand.Int63())
- // "The first word in ... an allocated struct or slice can be relied upon
- // to be 64-bit aligned." - sync/atomic docs
- data := new(struct{ val uint64 })
- data.val = before
-
- old, err := SwapUint64(unsafe.Pointer(&data.val), after)
- if err != nil {
- t.Errorf("Unexpected error: %v", err)
- }
- if old != before {
- t.Errorf("Unexpected old value: got %v, want %v", old, before)
- }
- if data.val != after {
- t.Errorf("Unexpected new value: got %v, want %v", data.val, after)
- }
-}
-
-func TestSwapUint64AlignmentError(t *testing.T) {
- // Test that SwapUint64 returns an AlignmentError when passed an unaligned
- // address.
- data := new(struct{ val1, val2 uint64 })
- addr := uintptr(unsafe.Pointer(&data.val1)) + 1
- want := AlignmentError{Addr: addr, Alignment: 8}
- if _, err := SwapUint64(unsafe.Pointer(addr), 1); err != want {
- t.Errorf("Unexpected error: got %v, want %v", err, want)
- }
-}
-
-func TestCompareAndSwapUint32Success(t *testing.T) {
- // Test that CompareAndSwapUint32 does not return an error when the page is
- // accessible.
- before := uint32(rand.Int31())
- after := uint32(rand.Int31())
- val := before
-
- old, err := CompareAndSwapUint32(unsafe.Pointer(&val), before, after)
- if err != nil {
- t.Errorf("Unexpected error: %v", err)
- }
- if old != before {
- t.Errorf("Unexpected old value: got %v, want %v", old, before)
- }
- if val != after {
- t.Errorf("Unexpected new value: got %v, want %v", val, after)
- }
-}
-
-func TestCompareAndSwapUint32AlignmentError(t *testing.T) {
- // Test that CompareAndSwapUint32 returns an AlignmentError when passed an
- // unaligned address.
- data := new(struct{ val uint64 })
- addr := uintptr(unsafe.Pointer(&data.val)) + 1
- want := AlignmentError{Addr: addr, Alignment: 4}
- if _, err := CompareAndSwapUint32(unsafe.Pointer(addr), 0, 1); err != want {
- t.Errorf("Unexpected error: got %v, want %v", err, want)
- }
-}
-
-// withSegvErrorTestMapping calls fn with a two-page mapping. The first page
-// contains random data, and the second page generates SIGSEGV when accessed.
-func withSegvErrorTestMapping(t *testing.T, fn func(m []byte)) {
- mapping, err := syscall.Mmap(-1, 0, 2*pageSize, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_ANONYMOUS|syscall.MAP_PRIVATE)
- if err != nil {
- t.Fatalf("Mmap failed: %v", err)
- }
- defer syscall.Munmap(mapping)
- if err := syscall.Mprotect(mapping[pageSize:], syscall.PROT_NONE); err != nil {
- t.Fatalf("Mprotect failed: %v", err)
- }
- initRandom(mapping[:pageSize])
-
- fn(mapping)
-}
-
-// withBusErrorTestMapping calls fn with a two-page mapping. The first page
-// contains random data, and the second page generates SIGBUS when accessed.
-func withBusErrorTestMapping(t *testing.T, fn func(m []byte)) {
- f, err := ioutil.TempFile("", "sigbus_test")
- if err != nil {
- t.Fatalf("TempFile failed: %v", err)
- }
- defer f.Close()
- if err := f.Truncate(pageSize); err != nil {
- t.Fatalf("Truncate failed: %v", err)
- }
- mapping, err := syscall.Mmap(int(f.Fd()), 0, 2*pageSize, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
- if err != nil {
- t.Fatalf("Mmap failed: %v", err)
- }
- defer syscall.Munmap(mapping)
- initRandom(mapping[:pageSize])
-
- fn(mapping)
-}
-
-func TestCopyInSegvError(t *testing.T) {
- // Test that CopyIn returns a SegvError when reaching a page that signals
- // SIGSEGV.
- for bytesBeforeFault := 0; bytesBeforeFault <= 2*maxRegisterSize; bytesBeforeFault++ {
- t.Run(fmt.Sprintf("starting copy %d bytes before SIGSEGV", bytesBeforeFault), func(t *testing.T) {
- withSegvErrorTestMapping(t, func(mapping []byte) {
- secondPage := uintptr(unsafe.Pointer(&mapping[0])) + pageSize
- src := unsafe.Pointer(secondPage - uintptr(bytesBeforeFault))
- dst := randBuf(pageSize)
- n, err := CopyIn(dst, src)
- if n != bytesBeforeFault {
- t.Errorf("Unexpected copy length: got %v, want %v", n, bytesBeforeFault)
- }
- if want := (SegvError{secondPage}); err != want {
- t.Errorf("Unexpected error: got %v, want %v", err, want)
- }
- if got, want := dst[:bytesBeforeFault], mapping[pageSize-bytesBeforeFault:pageSize]; !bytes.Equal(got, want) {
- t.Errorf("Buffers are not equal when they should be: %v %v", got, want)
- }
- })
- })
- }
-}
-
-func TestCopyInBusError(t *testing.T) {
- // Test that CopyIn returns a BusError when reaching a page that signals
- // SIGBUS.
- for bytesBeforeFault := 0; bytesBeforeFault <= 2*maxRegisterSize; bytesBeforeFault++ {
- t.Run(fmt.Sprintf("starting copy %d bytes before SIGBUS", bytesBeforeFault), func(t *testing.T) {
- withBusErrorTestMapping(t, func(mapping []byte) {
- secondPage := uintptr(unsafe.Pointer(&mapping[0])) + pageSize
- src := unsafe.Pointer(secondPage - uintptr(bytesBeforeFault))
- dst := randBuf(pageSize)
- n, err := CopyIn(dst, src)
- if n != bytesBeforeFault {
- t.Errorf("Unexpected copy length: got %v, want %v", n, bytesBeforeFault)
- }
- if want := (BusError{secondPage}); err != want {
- t.Errorf("Unexpected error: got %v, want %v", err, want)
- }
- if got, want := dst[:bytesBeforeFault], mapping[pageSize-bytesBeforeFault:pageSize]; !bytes.Equal(got, want) {
- t.Errorf("Buffers are not equal when they should be: %v %v", got, want)
- }
- })
- })
- }
-}
-
-func TestCopyOutSegvError(t *testing.T) {
- // Test that CopyOut returns a SegvError when reaching a page that signals
- // SIGSEGV.
- for bytesBeforeFault := 0; bytesBeforeFault <= 2*maxRegisterSize; bytesBeforeFault++ {
- t.Run(fmt.Sprintf("starting copy %d bytes before SIGSEGV", bytesBeforeFault), func(t *testing.T) {
- withSegvErrorTestMapping(t, func(mapping []byte) {
- secondPage := uintptr(unsafe.Pointer(&mapping[0])) + pageSize
- dst := unsafe.Pointer(secondPage - uintptr(bytesBeforeFault))
- src := randBuf(pageSize)
- n, err := CopyOut(dst, src)
- if n != bytesBeforeFault {
- t.Errorf("Unexpected copy length: got %v, want %v", n, bytesBeforeFault)
- }
- if want := (SegvError{secondPage}); err != want {
- t.Errorf("Unexpected error: got %v, want %v", err, want)
- }
- if got, want := mapping[pageSize-bytesBeforeFault:pageSize], src[:bytesBeforeFault]; !bytes.Equal(got, want) {
- t.Errorf("Buffers are not equal when they should be: %v %v", got, want)
- }
- })
- })
- }
-}
-
-func TestCopyOutBusError(t *testing.T) {
- // Test that CopyOut returns a BusError when reaching a page that signals
- // SIGBUS.
- for bytesBeforeFault := 0; bytesBeforeFault <= 2*maxRegisterSize; bytesBeforeFault++ {
- t.Run(fmt.Sprintf("starting copy %d bytes before SIGSEGV", bytesBeforeFault), func(t *testing.T) {
- withBusErrorTestMapping(t, func(mapping []byte) {
- secondPage := uintptr(unsafe.Pointer(&mapping[0])) + pageSize
- dst := unsafe.Pointer(secondPage - uintptr(bytesBeforeFault))
- src := randBuf(pageSize)
- n, err := CopyOut(dst, src)
- if n != bytesBeforeFault {
- t.Errorf("Unexpected copy length: got %v, want %v", n, bytesBeforeFault)
- }
- if want := (BusError{secondPage}); err != want {
- t.Errorf("Unexpected error: got %v, want %v", err, want)
- }
- if got, want := mapping[pageSize-bytesBeforeFault:pageSize], src[:bytesBeforeFault]; !bytes.Equal(got, want) {
- t.Errorf("Buffers are not equal when they should be: %v %v", got, want)
- }
- })
- })
- }
-}
-
-func TestCopySourceSegvError(t *testing.T) {
- // Test that Copy returns a SegvError when copying from a page that signals
- // SIGSEGV.
- for bytesBeforeFault := 0; bytesBeforeFault <= 2*maxRegisterSize; bytesBeforeFault++ {
- t.Run(fmt.Sprintf("starting copy %d bytes before SIGSEGV", bytesBeforeFault), func(t *testing.T) {
- withSegvErrorTestMapping(t, func(mapping []byte) {
- secondPage := uintptr(unsafe.Pointer(&mapping[0])) + pageSize
- src := unsafe.Pointer(secondPage - uintptr(bytesBeforeFault))
- dst := randBuf(pageSize)
- n, err := Copy(unsafe.Pointer(&dst[0]), src, pageSize)
- if n != uintptr(bytesBeforeFault) {
- t.Errorf("Unexpected copy length: got %v, want %v", n, bytesBeforeFault)
- }
- if want := (SegvError{secondPage}); err != want {
- t.Errorf("Unexpected error: got %v, want %v", err, want)
- }
- if got, want := dst[:bytesBeforeFault], mapping[pageSize-bytesBeforeFault:pageSize]; !bytes.Equal(got, want) {
- t.Errorf("Buffers are not equal when they should be: %v %v", got, want)
- }
- })
- })
- }
-}
-
-func TestCopySourceBusError(t *testing.T) {
- // Test that Copy returns a BusError when copying from a page that signals
- // SIGBUS.
- for bytesBeforeFault := 0; bytesBeforeFault <= 2*maxRegisterSize; bytesBeforeFault++ {
- t.Run(fmt.Sprintf("starting copy %d bytes before SIGBUS", bytesBeforeFault), func(t *testing.T) {
- withBusErrorTestMapping(t, func(mapping []byte) {
- secondPage := uintptr(unsafe.Pointer(&mapping[0])) + pageSize
- src := unsafe.Pointer(secondPage - uintptr(bytesBeforeFault))
- dst := randBuf(pageSize)
- n, err := Copy(unsafe.Pointer(&dst[0]), src, pageSize)
- if n != uintptr(bytesBeforeFault) {
- t.Errorf("Unexpected copy length: got %v, want %v", n, bytesBeforeFault)
- }
- if want := (BusError{secondPage}); err != want {
- t.Errorf("Unexpected error: got %v, want %v", err, want)
- }
- if got, want := dst[:bytesBeforeFault], mapping[pageSize-bytesBeforeFault:pageSize]; !bytes.Equal(got, want) {
- t.Errorf("Buffers are not equal when they should be: %v %v", got, want)
- }
- })
- })
- }
-}
-
-func TestCopyDestinationSegvError(t *testing.T) {
- // Test that Copy returns a SegvError when copying to a page that signals
- // SIGSEGV.
- for bytesBeforeFault := 0; bytesBeforeFault <= 2*maxRegisterSize; bytesBeforeFault++ {
- t.Run(fmt.Sprintf("starting copy %d bytes before SIGSEGV", bytesBeforeFault), func(t *testing.T) {
- withSegvErrorTestMapping(t, func(mapping []byte) {
- secondPage := uintptr(unsafe.Pointer(&mapping[0])) + pageSize
- dst := unsafe.Pointer(secondPage - uintptr(bytesBeforeFault))
- src := randBuf(pageSize)
- n, err := Copy(dst, unsafe.Pointer(&src[0]), pageSize)
- if n != uintptr(bytesBeforeFault) {
- t.Errorf("Unexpected copy length: got %v, want %v", n, bytesBeforeFault)
- }
- if want := (SegvError{secondPage}); err != want {
- t.Errorf("Unexpected error: got %v, want %v", err, want)
- }
- if got, want := mapping[pageSize-bytesBeforeFault:pageSize], src[:bytesBeforeFault]; !bytes.Equal(got, want) {
- t.Errorf("Buffers are not equal when they should be: %v %v", got, want)
- }
- })
- })
- }
-}
-
-func TestCopyDestinationBusError(t *testing.T) {
- // Test that Copy returns a BusError when copying to a page that signals
- // SIGBUS.
- for bytesBeforeFault := 0; bytesBeforeFault <= 2*maxRegisterSize; bytesBeforeFault++ {
- t.Run(fmt.Sprintf("starting copy %d bytes before SIGBUS", bytesBeforeFault), func(t *testing.T) {
- withBusErrorTestMapping(t, func(mapping []byte) {
- secondPage := uintptr(unsafe.Pointer(&mapping[0])) + pageSize
- dst := unsafe.Pointer(secondPage - uintptr(bytesBeforeFault))
- src := randBuf(pageSize)
- n, err := Copy(dst, unsafe.Pointer(&src[0]), pageSize)
- if n != uintptr(bytesBeforeFault) {
- t.Errorf("Unexpected copy length: got %v, want %v", n, bytesBeforeFault)
- }
- if want := (BusError{secondPage}); err != want {
- t.Errorf("Unexpected error: got %v, want %v", err, want)
- }
- if got, want := mapping[pageSize-bytesBeforeFault:pageSize], src[:bytesBeforeFault]; !bytes.Equal(got, want) {
- t.Errorf("Buffers are not equal when they should be: %v %v", got, want)
- }
- })
- })
- }
-}
-
-func TestZeroOutSegvError(t *testing.T) {
- // Test that ZeroOut returns a SegvError when reaching a page that signals
- // SIGSEGV.
- for bytesBeforeFault := 0; bytesBeforeFault <= 2*maxRegisterSize; bytesBeforeFault++ {
- t.Run(fmt.Sprintf("starting write %d bytes before SIGSEGV", bytesBeforeFault), func(t *testing.T) {
- withSegvErrorTestMapping(t, func(mapping []byte) {
- secondPage := uintptr(unsafe.Pointer(&mapping[0])) + pageSize
- dst := unsafe.Pointer(secondPage - uintptr(bytesBeforeFault))
- n, err := ZeroOut(dst, pageSize)
- if n != uintptr(bytesBeforeFault) {
- t.Errorf("Unexpected write length: got %v, want %v", n, bytesBeforeFault)
- }
- if want := (SegvError{secondPage}); err != want {
- t.Errorf("Unexpected error: got %v, want %v", err, want)
- }
- if got, want := mapping[pageSize-bytesBeforeFault:pageSize], make([]byte, bytesBeforeFault); !bytes.Equal(got, want) {
- t.Errorf("Non-zero bytes in written part of mapping: %v", got)
- }
- })
- })
- }
-}
-
-func TestZeroOutBusError(t *testing.T) {
- // Test that ZeroOut returns a BusError when reaching a page that signals
- // SIGBUS.
- for bytesBeforeFault := 0; bytesBeforeFault <= 2*maxRegisterSize; bytesBeforeFault++ {
- t.Run(fmt.Sprintf("starting write %d bytes before SIGBUS", bytesBeforeFault), func(t *testing.T) {
- withBusErrorTestMapping(t, func(mapping []byte) {
- secondPage := uintptr(unsafe.Pointer(&mapping[0])) + pageSize
- dst := unsafe.Pointer(secondPage - uintptr(bytesBeforeFault))
- n, err := ZeroOut(dst, pageSize)
- if n != uintptr(bytesBeforeFault) {
- t.Errorf("Unexpected write length: got %v, want %v", n, bytesBeforeFault)
- }
- if want := (BusError{secondPage}); err != want {
- t.Errorf("Unexpected error: got %v, want %v", err, want)
- }
- if got, want := mapping[pageSize-bytesBeforeFault:pageSize], make([]byte, bytesBeforeFault); !bytes.Equal(got, want) {
- t.Errorf("Non-zero bytes in written part of mapping: %v", got)
- }
- })
- })
- }
-}
-
-func TestSwapUint32SegvError(t *testing.T) {
- // Test that SwapUint32 returns a SegvError when reaching a page that
- // signals SIGSEGV.
- withSegvErrorTestMapping(t, func(mapping []byte) {
- secondPage := uintptr(unsafe.Pointer(&mapping[0])) + pageSize
- _, err := SwapUint32(unsafe.Pointer(secondPage), 1)
- if want := (SegvError{secondPage}); err != want {
- t.Errorf("Unexpected error: got %v, want %v", err, want)
- }
- })
-}
-
-func TestSwapUint32BusError(t *testing.T) {
- // Test that SwapUint32 returns a BusError when reaching a page that
- // signals SIGBUS.
- withBusErrorTestMapping(t, func(mapping []byte) {
- secondPage := uintptr(unsafe.Pointer(&mapping[0])) + pageSize
- _, err := SwapUint32(unsafe.Pointer(secondPage), 1)
- if want := (BusError{secondPage}); err != want {
- t.Errorf("Unexpected error: got %v, want %v", err, want)
- }
- })
-}
-
-func TestSwapUint64SegvError(t *testing.T) {
- // Test that SwapUint64 returns a SegvError when reaching a page that
- // signals SIGSEGV.
- withSegvErrorTestMapping(t, func(mapping []byte) {
- secondPage := uintptr(unsafe.Pointer(&mapping[0])) + pageSize
- _, err := SwapUint64(unsafe.Pointer(secondPage), 1)
- if want := (SegvError{secondPage}); err != want {
- t.Errorf("Unexpected error: got %v, want %v", err, want)
- }
- })
-}
-
-func TestSwapUint64BusError(t *testing.T) {
- // Test that SwapUint64 returns a BusError when reaching a page that
- // signals SIGBUS.
- withBusErrorTestMapping(t, func(mapping []byte) {
- secondPage := uintptr(unsafe.Pointer(&mapping[0])) + pageSize
- _, err := SwapUint64(unsafe.Pointer(secondPage), 1)
- if want := (BusError{secondPage}); err != want {
- t.Errorf("Unexpected error: got %v, want %v", err, want)
- }
- })
-}
-
-func TestCompareAndSwapUint32SegvError(t *testing.T) {
- // Test that CompareAndSwapUint32 returns a SegvError when reaching a page
- // that signals SIGSEGV.
- withSegvErrorTestMapping(t, func(mapping []byte) {
- secondPage := uintptr(unsafe.Pointer(&mapping[0])) + pageSize
- _, err := CompareAndSwapUint32(unsafe.Pointer(secondPage), 0, 1)
- if want := (SegvError{secondPage}); err != want {
- t.Errorf("Unexpected error: got %v, want %v", err, want)
- }
- })
-}
-
-func TestCompareAndSwapUint32BusError(t *testing.T) {
- // Test that CompareAndSwapUint32 returns a BusError when reaching a page
- // that signals SIGBUS.
- withBusErrorTestMapping(t, func(mapping []byte) {
- secondPage := uintptr(unsafe.Pointer(&mapping[0])) + pageSize
- _, err := CompareAndSwapUint32(unsafe.Pointer(secondPage), 0, 1)
- if want := (BusError{secondPage}); err != want {
- t.Errorf("Unexpected error: got %v, want %v", err, want)
- }
- })
-}
-
-func testCopy(dst, src []byte) (panicked bool) {
- defer func() {
- if r := recover(); r != nil {
- panicked = true
- }
- }()
- debug.SetPanicOnFault(true)
- copy(dst, src)
- return
-}
-
-func TestSegVOnMemmove(t *testing.T) {
- // Test that SIGSEGVs received by runtime.memmove when *not* doing
- // CopyIn or CopyOut work gets propagated to the runtime.
- const bufLen = pageSize
- a, err := syscall.Mmap(-1, 0, bufLen, syscall.PROT_NONE, syscall.MAP_ANON|syscall.MAP_PRIVATE)
- if err != nil {
- t.Fatalf("Mmap failed: %v", err)
-
- }
- defer syscall.Munmap(a)
- b := randBuf(bufLen)
-
- if !testCopy(b, a) {
- t.Fatalf("testCopy didn't panic when it should have")
- }
-
- if !testCopy(a, b) {
- t.Fatalf("testCopy didn't panic when it should have")
- }
-}
-
-func TestSigbusOnMemmove(t *testing.T) {
- // Test that SIGBUS received by runtime.memmove when *not* doing
- // CopyIn or CopyOut work gets propagated to the runtime.
- const bufLen = pageSize
- f, err := ioutil.TempFile("", "sigbus_test")
- if err != nil {
- t.Fatalf("TempFile failed: %v", err)
- }
- os.Remove(f.Name())
- defer f.Close()
-
- a, err := syscall.Mmap(int(f.Fd()), 0, bufLen, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
- if err != nil {
- t.Fatalf("Mmap failed: %v", err)
-
- }
- defer syscall.Munmap(a)
- b := randBuf(bufLen)
-
- if !testCopy(b, a) {
- t.Fatalf("testCopy didn't panic when it should have")
- }
-
- if !testCopy(a, b) {
- t.Fatalf("testCopy didn't panic when it should have")
- }
-}
diff --git a/pkg/sentry/safemem/BUILD b/pkg/sentry/safemem/BUILD
deleted file mode 100644
index 884020f7b..000000000
--- a/pkg/sentry/safemem/BUILD
+++ /dev/null
@@ -1,29 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "safemem",
- srcs = [
- "block_unsafe.go",
- "io.go",
- "safemem.go",
- "seq_unsafe.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/safemem",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/sentry/platform/safecopy",
- ],
-)
-
-go_test(
- name = "safemem_test",
- size = "small",
- srcs = [
- "io_test.go",
- "seq_test.go",
- ],
- embed = [":safemem"],
-)
diff --git a/pkg/sentry/safemem/io_test.go b/pkg/sentry/safemem/io_test.go
deleted file mode 100644
index 629741bee..000000000
--- a/pkg/sentry/safemem/io_test.go
+++ /dev/null
@@ -1,199 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package safemem
-
-import (
- "bytes"
- "io"
- "testing"
-)
-
-func makeBlocks(slices ...[]byte) []Block {
- blocks := make([]Block, 0, len(slices))
- for _, s := range slices {
- blocks = append(blocks, BlockFromSafeSlice(s))
- }
- return blocks
-}
-
-func TestFromIOReaderFullRead(t *testing.T) {
- r := FromIOReader{bytes.NewBufferString("foobar")}
- dsts := makeBlocks(make([]byte, 3), make([]byte, 3))
- n, err := r.ReadToBlocks(BlockSeqFromSlice(dsts))
- if wantN := uint64(6); n != wantN || err != nil {
- t.Errorf("ReadToBlocks: got (%v, %v), wanted (%v, nil)", n, err, wantN)
- }
- for i, want := range [][]byte{[]byte("foo"), []byte("bar")} {
- if got := dsts[i].ToSlice(); !bytes.Equal(got, want) {
- t.Errorf("dsts[%d]: got %q, wanted %q", i, got, want)
- }
- }
-}
-
-type eofHidingReader struct {
- Reader io.Reader
-}
-
-func (r eofHidingReader) Read(dst []byte) (int, error) {
- n, err := r.Reader.Read(dst)
- if err == io.EOF {
- return n, nil
- }
- return n, err
-}
-
-func TestFromIOReaderPartialRead(t *testing.T) {
- r := FromIOReader{eofHidingReader{bytes.NewBufferString("foob")}}
- dsts := makeBlocks(make([]byte, 3), make([]byte, 3))
- n, err := r.ReadToBlocks(BlockSeqFromSlice(dsts))
- // FromIOReader should stop after the eofHidingReader returns (1, nil)
- // for a 3-byte read.
- if wantN := uint64(4); n != wantN || err != nil {
- t.Errorf("ReadToBlocks: got (%v, %v), wanted (%v, nil)", n, err, wantN)
- }
- for i, want := range [][]byte{[]byte("foo"), []byte("b\x00\x00")} {
- if got := dsts[i].ToSlice(); !bytes.Equal(got, want) {
- t.Errorf("dsts[%d]: got %q, wanted %q", i, got, want)
- }
- }
-}
-
-type singleByteReader struct {
- Reader io.Reader
-}
-
-func (r singleByteReader) Read(dst []byte) (int, error) {
- if len(dst) == 0 {
- return r.Reader.Read(dst)
- }
- return r.Reader.Read(dst[:1])
-}
-
-func TestSingleByteReader(t *testing.T) {
- r := FromIOReader{singleByteReader{bytes.NewBufferString("foobar")}}
- dsts := makeBlocks(make([]byte, 3), make([]byte, 3))
- n, err := r.ReadToBlocks(BlockSeqFromSlice(dsts))
- // FromIOReader should stop after the singleByteReader returns (1, nil)
- // for a 3-byte read.
- if wantN := uint64(1); n != wantN || err != nil {
- t.Errorf("ReadToBlocks: got (%v, %v), wanted (%v, nil)", n, err, wantN)
- }
- for i, want := range [][]byte{[]byte("f\x00\x00"), []byte("\x00\x00\x00")} {
- if got := dsts[i].ToSlice(); !bytes.Equal(got, want) {
- t.Errorf("dsts[%d]: got %q, wanted %q", i, got, want)
- }
- }
-}
-
-func TestReadFullToBlocks(t *testing.T) {
- r := FromIOReader{singleByteReader{bytes.NewBufferString("foobar")}}
- dsts := makeBlocks(make([]byte, 3), make([]byte, 3))
- n, err := ReadFullToBlocks(r, BlockSeqFromSlice(dsts))
- // ReadFullToBlocks should call into FromIOReader => singleByteReader
- // repeatedly until dsts is exhausted.
- if wantN := uint64(6); n != wantN || err != nil {
- t.Errorf("ReadFullToBlocks: got (%v, %v), wanted (%v, nil)", n, err, wantN)
- }
- for i, want := range [][]byte{[]byte("foo"), []byte("bar")} {
- if got := dsts[i].ToSlice(); !bytes.Equal(got, want) {
- t.Errorf("dsts[%d]: got %q, wanted %q", i, got, want)
- }
- }
-}
-
-func TestFromIOWriterFullWrite(t *testing.T) {
- srcs := makeBlocks([]byte("foo"), []byte("bar"))
- var dst bytes.Buffer
- w := FromIOWriter{&dst}
- n, err := w.WriteFromBlocks(BlockSeqFromSlice(srcs))
- if wantN := uint64(6); n != wantN || err != nil {
- t.Errorf("WriteFromBlocks: got (%v, %v), wanted (%v, nil)", n, err, wantN)
- }
- if got, want := dst.Bytes(), []byte("foobar"); !bytes.Equal(got, want) {
- t.Errorf("dst: got %q, wanted %q", got, want)
- }
-}
-
-type limitedWriter struct {
- Writer io.Writer
- Done int
- Limit int
-}
-
-func (w *limitedWriter) Write(src []byte) (int, error) {
- count := len(src)
- if count > (w.Limit - w.Done) {
- count = w.Limit - w.Done
- }
- n, err := w.Writer.Write(src[:count])
- w.Done += n
- return n, err
-}
-
-func TestFromIOWriterPartialWrite(t *testing.T) {
- srcs := makeBlocks([]byte("foo"), []byte("bar"))
- var dst bytes.Buffer
- w := FromIOWriter{&limitedWriter{&dst, 0, 4}}
- n, err := w.WriteFromBlocks(BlockSeqFromSlice(srcs))
- // FromIOWriter should stop after the limitedWriter returns (1, nil) for a
- // 3-byte write.
- if wantN := uint64(4); n != wantN || err != nil {
- t.Errorf("WriteFromBlocks: got (%v, %v), wanted (%v, nil)", n, err, wantN)
- }
- if got, want := dst.Bytes(), []byte("foob"); !bytes.Equal(got, want) {
- t.Errorf("dst: got %q, wanted %q", got, want)
- }
-}
-
-type singleByteWriter struct {
- Writer io.Writer
-}
-
-func (w singleByteWriter) Write(src []byte) (int, error) {
- if len(src) == 0 {
- return w.Writer.Write(src)
- }
- return w.Writer.Write(src[:1])
-}
-
-func TestSingleByteWriter(t *testing.T) {
- srcs := makeBlocks([]byte("foo"), []byte("bar"))
- var dst bytes.Buffer
- w := FromIOWriter{singleByteWriter{&dst}}
- n, err := w.WriteFromBlocks(BlockSeqFromSlice(srcs))
- // FromIOWriter should stop after the singleByteWriter returns (1, nil)
- // for a 3-byte write.
- if wantN := uint64(1); n != wantN || err != nil {
- t.Errorf("WriteFromBlocks: got (%v, %v), wanted (%v, nil)", n, err, wantN)
- }
- if got, want := dst.Bytes(), []byte("f"); !bytes.Equal(got, want) {
- t.Errorf("dst: got %q, wanted %q", got, want)
- }
-}
-
-func TestWriteFullToBlocks(t *testing.T) {
- srcs := makeBlocks([]byte("foo"), []byte("bar"))
- var dst bytes.Buffer
- w := FromIOWriter{singleByteWriter{&dst}}
- n, err := WriteFullFromBlocks(w, BlockSeqFromSlice(srcs))
- // WriteFullToBlocks should call into FromIOWriter => singleByteWriter
- // repeatedly until srcs is exhausted.
- if wantN := uint64(6); n != wantN || err != nil {
- t.Errorf("WriteFullFromBlocks: got (%v, %v), wanted (%v, nil)", n, err, wantN)
- }
- if got, want := dst.Bytes(), []byte("foobar"); !bytes.Equal(got, want) {
- t.Errorf("dst: got %q, wanted %q", got, want)
- }
-}
diff --git a/pkg/sentry/safemem/safemem_state_autogen.go b/pkg/sentry/safemem/safemem_state_autogen.go
new file mode 100755
index 000000000..7264df0b1
--- /dev/null
+++ b/pkg/sentry/safemem/safemem_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package safemem
+
diff --git a/pkg/sentry/safemem/seq_test.go b/pkg/sentry/safemem/seq_test.go
deleted file mode 100644
index eba4bb535..000000000
--- a/pkg/sentry/safemem/seq_test.go
+++ /dev/null
@@ -1,196 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package safemem
-
-import (
- "bytes"
- "reflect"
- "testing"
-)
-
-type blockSeqTest struct {
- desc string
-
- pieces []string
- haveOffset bool
- offset uint64
- haveLimit bool
- limit uint64
-
- want string
-}
-
-func (t blockSeqTest) NonEmptyByteSlices() [][]byte {
- // t is a value, so we can mutate it freely.
- slices := make([][]byte, 0, len(t.pieces))
- for _, str := range t.pieces {
- if t.haveOffset {
- strOff := t.offset
- if strOff > uint64(len(str)) {
- strOff = uint64(len(str))
- }
- str = str[strOff:]
- t.offset -= strOff
- }
- if t.haveLimit {
- strLim := t.limit
- if strLim > uint64(len(str)) {
- strLim = uint64(len(str))
- }
- str = str[:strLim]
- t.limit -= strLim
- }
- if len(str) != 0 {
- slices = append(slices, []byte(str))
- }
- }
- return slices
-}
-
-func (t blockSeqTest) BlockSeq() BlockSeq {
- blocks := make([]Block, 0, len(t.pieces))
- for _, str := range t.pieces {
- blocks = append(blocks, BlockFromSafeSlice([]byte(str)))
- }
- bs := BlockSeqFromSlice(blocks)
- if t.haveOffset {
- bs = bs.DropFirst64(t.offset)
- }
- if t.haveLimit {
- bs = bs.TakeFirst64(t.limit)
- }
- return bs
-}
-
-var blockSeqTests = []blockSeqTest{
- {
- desc: "Empty sequence",
- },
- {
- desc: "Sequence of length 1",
- pieces: []string{"foobar"},
- want: "foobar",
- },
- {
- desc: "Sequence of length 2",
- pieces: []string{"foo", "bar"},
- want: "foobar",
- },
- {
- desc: "Empty Blocks",
- pieces: []string{"", "foo", "", "", "bar", ""},
- want: "foobar",
- },
- {
- desc: "Sequence with non-zero offset",
- pieces: []string{"foo", "bar"},
- haveOffset: true,
- offset: 2,
- want: "obar",
- },
- {
- desc: "Sequence with non-maximal limit",
- pieces: []string{"foo", "bar"},
- haveLimit: true,
- limit: 5,
- want: "fooba",
- },
- {
- desc: "Sequence with offset and limit",
- pieces: []string{"foo", "bar"},
- haveOffset: true,
- offset: 2,
- haveLimit: true,
- limit: 3,
- want: "oba",
- },
-}
-
-func TestBlockSeqNumBytes(t *testing.T) {
- for _, test := range blockSeqTests {
- t.Run(test.desc, func(t *testing.T) {
- if got, want := test.BlockSeq().NumBytes(), uint64(len(test.want)); got != want {
- t.Errorf("NumBytes: got %d, wanted %d", got, want)
- }
- })
- }
-}
-
-func TestBlockSeqIterBlocks(t *testing.T) {
- // Tests BlockSeq iteration using Head/Tail.
- for _, test := range blockSeqTests {
- t.Run(test.desc, func(t *testing.T) {
- srcs := test.BlockSeq()
- // "Note that a non-nil empty slice and a nil slice ... are not
- // deeply equal." - reflect
- slices := make([][]byte, 0, 0)
- for !srcs.IsEmpty() {
- src := srcs.Head()
- slices = append(slices, src.ToSlice())
- nextSrcs := srcs.Tail()
- if got, want := nextSrcs.NumBytes(), srcs.NumBytes()-uint64(src.Len()); got != want {
- t.Fatalf("%v.Tail(): got %v (%d bytes), wanted %d bytes", srcs, nextSrcs, got, want)
- }
- srcs = nextSrcs
- }
- if wantSlices := test.NonEmptyByteSlices(); !reflect.DeepEqual(slices, wantSlices) {
- t.Errorf("Accumulated slices: got %v, wanted %v", slices, wantSlices)
- }
- })
- }
-}
-
-func TestBlockSeqIterBytes(t *testing.T) {
- // Tests BlockSeq iteration using Head/DropFirst.
- for _, test := range blockSeqTests {
- t.Run(test.desc, func(t *testing.T) {
- srcs := test.BlockSeq()
- var dst bytes.Buffer
- for !srcs.IsEmpty() {
- src := srcs.Head()
- var b [1]byte
- n, err := Copy(BlockFromSafeSlice(b[:]), src)
- if n != 1 || err != nil {
- t.Fatalf("Copy: got (%v, %v), wanted (1, nil)", n, err)
- }
- dst.WriteByte(b[0])
- nextSrcs := srcs.DropFirst(1)
- if got, want := nextSrcs.NumBytes(), srcs.NumBytes()-1; got != want {
- t.Fatalf("%v.DropFirst(1): got %v (%d bytes), wanted %d bytes", srcs, nextSrcs, got, want)
- }
- srcs = nextSrcs
- }
- if got := string(dst.Bytes()); got != test.want {
- t.Errorf("Copied string: got %q, wanted %q", got, test.want)
- }
- })
- }
-}
-
-func TestBlockSeqDropBeyondLimit(t *testing.T) {
- blocks := []Block{BlockFromSafeSlice([]byte("123")), BlockFromSafeSlice([]byte("4"))}
- bs := BlockSeqFromSlice(blocks)
- if got, want := bs.NumBytes(), uint64(4); got != want {
- t.Errorf("%v.NumBytes(): got %d, wanted %d", bs, got, want)
- }
- bs = bs.TakeFirst(1)
- if got, want := bs.NumBytes(), uint64(1); got != want {
- t.Errorf("%v.NumBytes(): got %d, wanted %d", bs, got, want)
- }
- bs = bs.DropFirst(2)
- if got, want := bs.NumBytes(), uint64(0); got != want {
- t.Errorf("%v.NumBytes(): got %d, wanted %d", bs, got, want)
- }
-}
diff --git a/pkg/sentry/sighandling/BUILD b/pkg/sentry/sighandling/BUILD
deleted file mode 100644
index f561670c7..000000000
--- a/pkg/sentry/sighandling/BUILD
+++ /dev/null
@@ -1,14 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "sighandling",
- srcs = [
- "sighandling.go",
- "sighandling_unsafe.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/sighandling",
- visibility = ["//pkg/sentry:internal"],
- deps = ["//pkg/abi/linux"],
-)
diff --git a/pkg/sentry/sighandling/sighandling_state_autogen.go b/pkg/sentry/sighandling/sighandling_state_autogen.go
new file mode 100755
index 000000000..dad4bdda2
--- /dev/null
+++ b/pkg/sentry/sighandling/sighandling_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package sighandling
+
diff --git a/pkg/sentry/socket/BUILD b/pkg/sentry/socket/BUILD
deleted file mode 100644
index 3300f9a6b..000000000
--- a/pkg/sentry/socket/BUILD
+++ /dev/null
@@ -1,24 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "socket",
- srcs = ["socket.go"],
- importpath = "gvisor.dev/gvisor/pkg/sentry/socket",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/binary",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/usermem",
- "//pkg/syserr",
- "//pkg/tcpip",
- ],
-)
diff --git a/pkg/sentry/socket/control/BUILD b/pkg/sentry/socket/control/BUILD
deleted file mode 100644
index 81dbd7309..000000000
--- a/pkg/sentry/socket/control/BUILD
+++ /dev/null
@@ -1,24 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "control",
- srcs = ["control.go"],
- importpath = "gvisor.dev/gvisor/pkg/sentry/socket/control",
- imports = [
- "gvisor.dev/gvisor/pkg/sentry/fs",
- ],
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/binary",
- "//pkg/sentry/context",
- "//pkg/sentry/fs",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- ],
-)
diff --git a/pkg/sentry/socket/control/control_state_autogen.go b/pkg/sentry/socket/control/control_state_autogen.go
new file mode 100755
index 000000000..c5ecfe700
--- /dev/null
+++ b/pkg/sentry/socket/control/control_state_autogen.go
@@ -0,0 +1,36 @@
+// automatically generated by stateify.
+
+package control
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+ "gvisor.dev/gvisor/pkg/sentry/fs"
+)
+
+func (x *RightsFiles) save(m state.Map) {
+ m.SaveValue("", ([]*fs.File)(*x))
+}
+
+func (x *RightsFiles) load(m state.Map) {
+ m.LoadValue("", new([]*fs.File), func(y interface{}) { *x = (RightsFiles)(y.([]*fs.File)) })
+}
+
+func (x *scmCredentials) beforeSave() {}
+func (x *scmCredentials) save(m state.Map) {
+ x.beforeSave()
+ m.Save("t", &x.t)
+ m.Save("kuid", &x.kuid)
+ m.Save("kgid", &x.kgid)
+}
+
+func (x *scmCredentials) afterLoad() {}
+func (x *scmCredentials) load(m state.Map) {
+ m.Load("t", &x.t)
+ m.Load("kuid", &x.kuid)
+ m.Load("kgid", &x.kgid)
+}
+
+func init() {
+ state.Register("control.RightsFiles", (*RightsFiles)(nil), state.Fns{Save: (*RightsFiles).save, Load: (*RightsFiles).load})
+ state.Register("control.scmCredentials", (*scmCredentials)(nil), state.Fns{Save: (*scmCredentials).save, Load: (*scmCredentials).load})
+}
diff --git a/pkg/sentry/socket/epsocket/BUILD b/pkg/sentry/socket/epsocket/BUILD
deleted file mode 100644
index e927821e1..000000000
--- a/pkg/sentry/socket/epsocket/BUILD
+++ /dev/null
@@ -1,50 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "epsocket",
- srcs = [
- "device.go",
- "epsocket.go",
- "provider.go",
- "save_restore.go",
- "stack.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/socket/epsocket",
- visibility = [
- "//pkg/sentry:internal",
- ],
- deps = [
- "//pkg/abi/linux",
- "//pkg/binary",
- "//pkg/log",
- "//pkg/metric",
- "//pkg/sentry/arch",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/inet",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/safemem",
- "//pkg/sentry/socket",
- "//pkg/sentry/socket/netfilter",
- "//pkg/sentry/unimpl",
- "//pkg/sentry/usermem",
- "//pkg/syserr",
- "//pkg/syserror",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/iptables",
- "//pkg/tcpip/network/ipv4",
- "//pkg/tcpip/network/ipv6",
- "//pkg/tcpip/stack",
- "//pkg/tcpip/transport/tcp",
- "//pkg/tcpip/transport/udp",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/socket/epsocket/epsocket_state_autogen.go b/pkg/sentry/socket/epsocket/epsocket_state_autogen.go
new file mode 100755
index 000000000..c69c145bd
--- /dev/null
+++ b/pkg/sentry/socket/epsocket/epsocket_state_autogen.go
@@ -0,0 +1,56 @@
+// automatically generated by stateify.
+
+package epsocket
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *SocketOperations) beforeSave() {}
+func (x *SocketOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("SendReceiveTimeout", &x.SendReceiveTimeout)
+ m.Save("Queue", &x.Queue)
+ m.Save("family", &x.family)
+ m.Save("Endpoint", &x.Endpoint)
+ m.Save("skType", &x.skType)
+ m.Save("protocol", &x.protocol)
+ m.Save("readView", &x.readView)
+ m.Save("readCM", &x.readCM)
+ m.Save("sender", &x.sender)
+ m.Save("sockOptTimestamp", &x.sockOptTimestamp)
+ m.Save("timestampValid", &x.timestampValid)
+ m.Save("timestampNS", &x.timestampNS)
+ m.Save("sockOptInq", &x.sockOptInq)
+}
+
+func (x *SocketOperations) afterLoad() {}
+func (x *SocketOperations) load(m state.Map) {
+ m.Load("SendReceiveTimeout", &x.SendReceiveTimeout)
+ m.Load("Queue", &x.Queue)
+ m.Load("family", &x.family)
+ m.Load("Endpoint", &x.Endpoint)
+ m.Load("skType", &x.skType)
+ m.Load("protocol", &x.protocol)
+ m.Load("readView", &x.readView)
+ m.Load("readCM", &x.readCM)
+ m.Load("sender", &x.sender)
+ m.Load("sockOptTimestamp", &x.sockOptTimestamp)
+ m.Load("timestampValid", &x.timestampValid)
+ m.Load("timestampNS", &x.timestampNS)
+ m.Load("sockOptInq", &x.sockOptInq)
+}
+
+func (x *Stack) beforeSave() {}
+func (x *Stack) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *Stack) load(m state.Map) {
+ m.AfterLoad(x.afterLoad)
+}
+
+func init() {
+ state.Register("epsocket.SocketOperations", (*SocketOperations)(nil), state.Fns{Save: (*SocketOperations).save, Load: (*SocketOperations).load})
+ state.Register("epsocket.Stack", (*Stack)(nil), state.Fns{Save: (*Stack).save, Load: (*Stack).load})
+}
diff --git a/pkg/sentry/socket/hostinet/BUILD b/pkg/sentry/socket/hostinet/BUILD
deleted file mode 100644
index a951f1bb0..000000000
--- a/pkg/sentry/socket/hostinet/BUILD
+++ /dev/null
@@ -1,37 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "hostinet",
- srcs = [
- "device.go",
- "hostinet.go",
- "save_restore.go",
- "socket.go",
- "socket_unsafe.go",
- "stack.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/socket/hostinet",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/binary",
- "//pkg/fdnotifier",
- "//pkg/log",
- "//pkg/sentry/arch",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/inet",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/safemem",
- "//pkg/sentry/socket",
- "//pkg/sentry/usermem",
- "//pkg/syserr",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/socket/hostinet/hostinet_state_autogen.go b/pkg/sentry/socket/hostinet/hostinet_state_autogen.go
new file mode 100755
index 000000000..0a5c7cdf3
--- /dev/null
+++ b/pkg/sentry/socket/hostinet/hostinet_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package hostinet
+
diff --git a/pkg/sentry/socket/netfilter/BUILD b/pkg/sentry/socket/netfilter/BUILD
deleted file mode 100644
index 354a0d6ee..000000000
--- a/pkg/sentry/socket/netfilter/BUILD
+++ /dev/null
@@ -1,24 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "netfilter",
- srcs = [
- "netfilter.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/socket/netfilter",
- # This target depends on netstack and should only be used by epsocket,
- # which is allowed to depend on netstack.
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/binary",
- "//pkg/sentry/kernel",
- "//pkg/sentry/usermem",
- "//pkg/syserr",
- "//pkg/tcpip",
- "//pkg/tcpip/iptables",
- "//pkg/tcpip/stack",
- ],
-)
diff --git a/pkg/sentry/socket/netfilter/netfilter_state_autogen.go b/pkg/sentry/socket/netfilter/netfilter_state_autogen.go
new file mode 100755
index 000000000..f3d68dd64
--- /dev/null
+++ b/pkg/sentry/socket/netfilter/netfilter_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package netfilter
+
diff --git a/pkg/sentry/socket/netlink/BUILD b/pkg/sentry/socket/netlink/BUILD
deleted file mode 100644
index 45ebb2a0e..000000000
--- a/pkg/sentry/socket/netlink/BUILD
+++ /dev/null
@@ -1,34 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "netlink",
- srcs = [
- "message.go",
- "provider.go",
- "socket.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/socket/netlink",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/binary",
- "//pkg/sentry/arch",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/socket",
- "//pkg/sentry/socket/netlink/port",
- "//pkg/sentry/socket/unix",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/usermem",
- "//pkg/syserr",
- "//pkg/syserror",
- "//pkg/tcpip",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/socket/netlink/netlink_state_autogen.go b/pkg/sentry/socket/netlink/netlink_state_autogen.go
new file mode 100755
index 000000000..794187ec0
--- /dev/null
+++ b/pkg/sentry/socket/netlink/netlink_state_autogen.go
@@ -0,0 +1,38 @@
+// automatically generated by stateify.
+
+package netlink
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *Socket) beforeSave() {}
+func (x *Socket) save(m state.Map) {
+ x.beforeSave()
+ m.Save("SendReceiveTimeout", &x.SendReceiveTimeout)
+ m.Save("ports", &x.ports)
+ m.Save("protocol", &x.protocol)
+ m.Save("skType", &x.skType)
+ m.Save("ep", &x.ep)
+ m.Save("connection", &x.connection)
+ m.Save("bound", &x.bound)
+ m.Save("portID", &x.portID)
+ m.Save("sendBufferSize", &x.sendBufferSize)
+}
+
+func (x *Socket) afterLoad() {}
+func (x *Socket) load(m state.Map) {
+ m.Load("SendReceiveTimeout", &x.SendReceiveTimeout)
+ m.Load("ports", &x.ports)
+ m.Load("protocol", &x.protocol)
+ m.Load("skType", &x.skType)
+ m.Load("ep", &x.ep)
+ m.Load("connection", &x.connection)
+ m.Load("bound", &x.bound)
+ m.Load("portID", &x.portID)
+ m.Load("sendBufferSize", &x.sendBufferSize)
+}
+
+func init() {
+ state.Register("netlink.Socket", (*Socket)(nil), state.Fns{Save: (*Socket).save, Load: (*Socket).load})
+}
diff --git a/pkg/sentry/socket/netlink/port/BUILD b/pkg/sentry/socket/netlink/port/BUILD
deleted file mode 100644
index 445080aa4..000000000
--- a/pkg/sentry/socket/netlink/port/BUILD
+++ /dev/null
@@ -1,18 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "port",
- srcs = ["port.go"],
- importpath = "gvisor.dev/gvisor/pkg/sentry/socket/netlink/port",
- visibility = ["//pkg/sentry:internal"],
-)
-
-go_test(
- name = "port_test",
- srcs = ["port_test.go"],
- embed = [":port"],
-)
diff --git a/pkg/sentry/socket/netlink/port/port_state_autogen.go b/pkg/sentry/socket/netlink/port/port_state_autogen.go
new file mode 100755
index 000000000..b4b4a6758
--- /dev/null
+++ b/pkg/sentry/socket/netlink/port/port_state_autogen.go
@@ -0,0 +1,22 @@
+// automatically generated by stateify.
+
+package port
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *Manager) beforeSave() {}
+func (x *Manager) save(m state.Map) {
+ x.beforeSave()
+ m.Save("ports", &x.ports)
+}
+
+func (x *Manager) afterLoad() {}
+func (x *Manager) load(m state.Map) {
+ m.Load("ports", &x.ports)
+}
+
+func init() {
+ state.Register("port.Manager", (*Manager)(nil), state.Fns{Save: (*Manager).save, Load: (*Manager).load})
+}
diff --git a/pkg/sentry/socket/netlink/port/port_test.go b/pkg/sentry/socket/netlink/port/port_test.go
deleted file mode 100644
index 516f6cd6c..000000000
--- a/pkg/sentry/socket/netlink/port/port_test.go
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package port
-
-import (
- "testing"
-)
-
-func TestAllocateHint(t *testing.T) {
- m := New()
-
- // We can get the hint port.
- p, ok := m.Allocate(0, 1)
- if !ok {
- t.Errorf("m.Allocate got !ok want ok")
- }
- if p != 1 {
- t.Errorf("m.Allocate(0, 1) got %d want 1", p)
- }
-
- // Hint is taken.
- p, ok = m.Allocate(0, 1)
- if !ok {
- t.Errorf("m.Allocate got !ok want ok")
- }
- if p == 1 {
- t.Errorf("m.Allocate(0, 1) got 1 want anything else")
- }
-
- // Hint is available for a different protocol.
- p, ok = m.Allocate(1, 1)
- if !ok {
- t.Errorf("m.Allocate got !ok want ok")
- }
- if p != 1 {
- t.Errorf("m.Allocate(1, 1) got %d want 1", p)
- }
-
- m.Release(0, 1)
-
- // Hint is available again after release.
- p, ok = m.Allocate(0, 1)
- if !ok {
- t.Errorf("m.Allocate got !ok want ok")
- }
- if p != 1 {
- t.Errorf("m.Allocate(0, 1) got %d want 1", p)
- }
-}
-
-func TestAllocateExhausted(t *testing.T) {
- m := New()
-
- // Fill all ports (0 is already reserved).
- for i := int32(1); i < maxPorts; i++ {
- p, ok := m.Allocate(0, i)
- if !ok {
- t.Fatalf("m.Allocate got !ok want ok")
- }
- if p != i {
- t.Fatalf("m.Allocate(0, %d) got %d want %d", i, p, i)
- }
- }
-
- // Now no more can be allocated.
- p, ok := m.Allocate(0, 1)
- if ok {
- t.Errorf("m.Allocate got %d, ok want !ok", p)
- }
-}
diff --git a/pkg/sentry/socket/netlink/route/BUILD b/pkg/sentry/socket/netlink/route/BUILD
deleted file mode 100644
index 5dc8533ec..000000000
--- a/pkg/sentry/socket/netlink/route/BUILD
+++ /dev/null
@@ -1,19 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "route",
- srcs = ["protocol.go"],
- importpath = "gvisor.dev/gvisor/pkg/sentry/socket/netlink/route",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/sentry/context",
- "//pkg/sentry/inet",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/socket/netlink",
- "//pkg/syserr",
- ],
-)
diff --git a/pkg/sentry/socket/netlink/route/route_state_autogen.go b/pkg/sentry/socket/netlink/route/route_state_autogen.go
new file mode 100755
index 000000000..f4225cb3c
--- /dev/null
+++ b/pkg/sentry/socket/netlink/route/route_state_autogen.go
@@ -0,0 +1,20 @@
+// automatically generated by stateify.
+
+package route
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *Protocol) beforeSave() {}
+func (x *Protocol) save(m state.Map) {
+ x.beforeSave()
+}
+
+func (x *Protocol) afterLoad() {}
+func (x *Protocol) load(m state.Map) {
+}
+
+func init() {
+ state.Register("route.Protocol", (*Protocol)(nil), state.Fns{Save: (*Protocol).save, Load: (*Protocol).load})
+}
diff --git a/pkg/sentry/socket/rpcinet/BUILD b/pkg/sentry/socket/rpcinet/BUILD
deleted file mode 100644
index 5061dcbde..000000000
--- a/pkg/sentry/socket/rpcinet/BUILD
+++ /dev/null
@@ -1,59 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "rpcinet",
- srcs = [
- "device.go",
- "rpcinet.go",
- "socket.go",
- "stack.go",
- "stack_unsafe.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/socket/rpcinet",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- ":syscall_rpc_go_proto",
- "//pkg/abi/linux",
- "//pkg/binary",
- "//pkg/sentry/arch",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/inet",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/socket",
- "//pkg/sentry/socket/hostinet",
- "//pkg/sentry/socket/rpcinet/conn",
- "//pkg/sentry/socket/rpcinet/notifier",
- "//pkg/sentry/unimpl",
- "//pkg/sentry/usermem",
- "//pkg/syserr",
- "//pkg/syserror",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/unet",
- "//pkg/waiter",
- ],
-)
-
-proto_library(
- name = "syscall_rpc_proto",
- srcs = ["syscall_rpc.proto"],
- visibility = [
- "//visibility:public",
- ],
-)
-
-go_proto_library(
- name = "syscall_rpc_go_proto",
- importpath = "gvisor.dev/gvisor/pkg/sentry/socket/rpcinet/syscall_rpc_go_proto",
- proto = ":syscall_rpc_proto",
- visibility = [
- "//visibility:public",
- ],
-)
diff --git a/pkg/sentry/socket/rpcinet/conn/BUILD b/pkg/sentry/socket/rpcinet/conn/BUILD
deleted file mode 100644
index 23eadcb1b..000000000
--- a/pkg/sentry/socket/rpcinet/conn/BUILD
+++ /dev/null
@@ -1,17 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "conn",
- srcs = ["conn.go"],
- importpath = "gvisor.dev/gvisor/pkg/sentry/socket/rpcinet/conn",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/binary",
- "//pkg/sentry/socket/rpcinet:syscall_rpc_go_proto",
- "//pkg/syserr",
- "//pkg/unet",
- "@com_github_golang_protobuf//proto:go_default_library",
- ],
-)
diff --git a/pkg/sentry/socket/rpcinet/conn/conn_state_autogen.go b/pkg/sentry/socket/rpcinet/conn/conn_state_autogen.go
new file mode 100755
index 000000000..f6c927a60
--- /dev/null
+++ b/pkg/sentry/socket/rpcinet/conn/conn_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package conn
+
diff --git a/pkg/sentry/socket/rpcinet/notifier/BUILD b/pkg/sentry/socket/rpcinet/notifier/BUILD
deleted file mode 100644
index a3585e10d..000000000
--- a/pkg/sentry/socket/rpcinet/notifier/BUILD
+++ /dev/null
@@ -1,16 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "notifier",
- srcs = ["notifier.go"],
- importpath = "gvisor.dev/gvisor/pkg/sentry/socket/rpcinet/notifier",
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/sentry/socket/rpcinet:syscall_rpc_go_proto",
- "//pkg/sentry/socket/rpcinet/conn",
- "//pkg/waiter",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
diff --git a/pkg/sentry/socket/rpcinet/notifier/notifier_state_autogen.go b/pkg/sentry/socket/rpcinet/notifier/notifier_state_autogen.go
new file mode 100755
index 000000000..f108d91c1
--- /dev/null
+++ b/pkg/sentry/socket/rpcinet/notifier/notifier_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package notifier
+
diff --git a/pkg/sentry/socket/rpcinet/rpcinet_state_autogen.go b/pkg/sentry/socket/rpcinet/rpcinet_state_autogen.go
new file mode 100755
index 000000000..d3076c7e3
--- /dev/null
+++ b/pkg/sentry/socket/rpcinet/rpcinet_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package rpcinet
+
diff --git a/pkg/sentry/socket/rpcinet/syscall_rpc.proto b/pkg/sentry/socket/rpcinet/syscall_rpc.proto
deleted file mode 100644
index 9586f5923..000000000
--- a/pkg/sentry/socket/rpcinet/syscall_rpc.proto
+++ /dev/null
@@ -1,353 +0,0 @@
-syntax = "proto3";
-
-// package syscall_rpc is a set of networking related system calls that can be
-// forwarded to a socket gofer.
-//
-// TODO(b/77963526): Document individual RPCs.
-package syscall_rpc;
-
-message SendmsgRequest {
- uint32 fd = 1;
- bytes data = 2 [ctype = CORD];
- bytes address = 3;
- bool more = 4;
- bool end_of_record = 5;
-}
-
-message SendmsgResponse {
- oneof result {
- uint32 error_number = 1;
- uint32 length = 2;
- }
-}
-
-message IOCtlRequest {
- uint32 fd = 1;
- uint32 cmd = 2;
- bytes arg = 3;
-}
-
-message IOCtlResponse {
- oneof result {
- uint32 error_number = 1;
- bytes value = 2;
- }
-}
-
-message RecvmsgRequest {
- uint32 fd = 1;
- uint32 length = 2;
- bool sender = 3;
- bool peek = 4;
- bool trunc = 5;
- uint32 cmsg_length = 6;
-}
-
-message OpenRequest {
- bytes path = 1;
- uint32 flags = 2;
- uint32 mode = 3;
-}
-
-message OpenResponse {
- oneof result {
- uint32 error_number = 1;
- uint32 fd = 2;
- }
-}
-
-message ReadRequest {
- uint32 fd = 1;
- uint32 length = 2;
-}
-
-message ReadResponse {
- oneof result {
- uint32 error_number = 1;
- bytes data = 2 [ctype = CORD];
- }
-}
-
-message ReadFileRequest {
- string path = 1;
-}
-
-message ReadFileResponse {
- oneof result {
- uint32 error_number = 1;
- bytes data = 2 [ctype = CORD];
- }
-}
-
-message WriteRequest {
- uint32 fd = 1;
- bytes data = 2 [ctype = CORD];
-}
-
-message WriteResponse {
- oneof result {
- uint32 error_number = 1;
- uint32 length = 2;
- }
-}
-
-message WriteFileRequest {
- string path = 1;
- bytes content = 2;
-}
-
-message WriteFileResponse {
- uint32 error_number = 1;
- uint32 written = 2;
-}
-
-message AddressResponse {
- bytes address = 1;
- uint32 length = 2;
-}
-
-message RecvmsgResponse {
- message ResultPayload {
- bytes data = 1 [ctype = CORD];
- AddressResponse address = 2;
- uint32 length = 3;
- bytes cmsg_data = 4;
- }
- oneof result {
- uint32 error_number = 1;
- ResultPayload payload = 2;
- }
-}
-
-message BindRequest {
- uint32 fd = 1;
- bytes address = 2;
-}
-
-message BindResponse {
- uint32 error_number = 1;
-}
-
-message AcceptRequest {
- uint32 fd = 1;
- bool peer = 2;
- int64 flags = 3;
-}
-
-message AcceptResponse {
- message ResultPayload {
- uint32 fd = 1;
- AddressResponse address = 2;
- }
- oneof result {
- uint32 error_number = 1;
- ResultPayload payload = 2;
- }
-}
-
-message ConnectRequest {
- uint32 fd = 1;
- bytes address = 2;
-}
-
-message ConnectResponse {
- uint32 error_number = 1;
-}
-
-message ListenRequest {
- uint32 fd = 1;
- int64 backlog = 2;
-}
-
-message ListenResponse {
- uint32 error_number = 1;
-}
-
-message ShutdownRequest {
- uint32 fd = 1;
- int64 how = 2;
-}
-
-message ShutdownResponse {
- uint32 error_number = 1;
-}
-
-message CloseRequest {
- uint32 fd = 1;
-}
-
-message CloseResponse {
- uint32 error_number = 1;
-}
-
-message GetSockOptRequest {
- uint32 fd = 1;
- int64 level = 2;
- int64 name = 3;
- uint32 length = 4;
-}
-
-message GetSockOptResponse {
- oneof result {
- uint32 error_number = 1;
- bytes opt = 2;
- }
-}
-
-message SetSockOptRequest {
- uint32 fd = 1;
- int64 level = 2;
- int64 name = 3;
- bytes opt = 4;
-}
-
-message SetSockOptResponse {
- uint32 error_number = 1;
-}
-
-message GetSockNameRequest {
- uint32 fd = 1;
-}
-
-message GetSockNameResponse {
- oneof result {
- uint32 error_number = 1;
- AddressResponse address = 2;
- }
-}
-
-message GetPeerNameRequest {
- uint32 fd = 1;
-}
-
-message GetPeerNameResponse {
- oneof result {
- uint32 error_number = 1;
- AddressResponse address = 2;
- }
-}
-
-message SocketRequest {
- int64 family = 1;
- int64 type = 2;
- int64 protocol = 3;
-}
-
-message SocketResponse {
- oneof result {
- uint32 error_number = 1;
- uint32 fd = 2;
- }
-}
-
-message EpollWaitRequest {
- uint32 fd = 1;
- uint32 num_events = 2;
- sint64 msec = 3;
-}
-
-message EpollEvent {
- uint32 fd = 1;
- uint32 events = 2;
-}
-
-message EpollEvents {
- repeated EpollEvent events = 1;
-}
-
-message EpollWaitResponse {
- oneof result {
- uint32 error_number = 1;
- EpollEvents events = 2;
- }
-}
-
-message EpollCtlRequest {
- uint32 epfd = 1;
- int64 op = 2;
- uint32 fd = 3;
- EpollEvent event = 4;
-}
-
-message EpollCtlResponse {
- uint32 error_number = 1;
-}
-
-message EpollCreate1Request {
- int64 flag = 1;
-}
-
-message EpollCreate1Response {
- oneof result {
- uint32 error_number = 1;
- uint32 fd = 2;
- }
-}
-
-message PollRequest {
- uint32 fd = 1;
- uint32 events = 2;
-}
-
-message PollResponse {
- oneof result {
- uint32 error_number = 1;
- uint32 events = 2;
- }
-}
-
-message SyscallRequest {
- oneof args {
- SocketRequest socket = 1;
- SendmsgRequest sendmsg = 2;
- RecvmsgRequest recvmsg = 3;
- BindRequest bind = 4;
- AcceptRequest accept = 5;
- ConnectRequest connect = 6;
- ListenRequest listen = 7;
- ShutdownRequest shutdown = 8;
- CloseRequest close = 9;
- GetSockOptRequest get_sock_opt = 10;
- SetSockOptRequest set_sock_opt = 11;
- GetSockNameRequest get_sock_name = 12;
- GetPeerNameRequest get_peer_name = 13;
- EpollWaitRequest epoll_wait = 14;
- EpollCtlRequest epoll_ctl = 15;
- EpollCreate1Request epoll_create1 = 16;
- PollRequest poll = 17;
- ReadRequest read = 18;
- WriteRequest write = 19;
- OpenRequest open = 20;
- IOCtlRequest ioctl = 21;
- WriteFileRequest write_file = 22;
- ReadFileRequest read_file = 23;
- }
-}
-
-message SyscallResponse {
- oneof result {
- SocketResponse socket = 1;
- SendmsgResponse sendmsg = 2;
- RecvmsgResponse recvmsg = 3;
- BindResponse bind = 4;
- AcceptResponse accept = 5;
- ConnectResponse connect = 6;
- ListenResponse listen = 7;
- ShutdownResponse shutdown = 8;
- CloseResponse close = 9;
- GetSockOptResponse get_sock_opt = 10;
- SetSockOptResponse set_sock_opt = 11;
- GetSockNameResponse get_sock_name = 12;
- GetPeerNameResponse get_peer_name = 13;
- EpollWaitResponse epoll_wait = 14;
- EpollCtlResponse epoll_ctl = 15;
- EpollCreate1Response epoll_create1 = 16;
- PollResponse poll = 17;
- ReadResponse read = 18;
- WriteResponse write = 19;
- OpenResponse open = 20;
- IOCtlResponse ioctl = 21;
- WriteFileResponse write_file = 22;
- ReadFileResponse read_file = 23;
- }
-}
diff --git a/pkg/sentry/socket/rpcinet/syscall_rpc_go_proto/syscall_rpc.pb.go b/pkg/sentry/socket/rpcinet/syscall_rpc_go_proto/syscall_rpc.pb.go
new file mode 100755
index 000000000..fb68d5294
--- /dev/null
+++ b/pkg/sentry/socket/rpcinet/syscall_rpc_go_proto/syscall_rpc.pb.go
@@ -0,0 +1,3938 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: pkg/sentry/socket/rpcinet/syscall_rpc.proto
+
+package syscall_rpc
+
+import (
+ fmt "fmt"
+ proto "github.com/golang/protobuf/proto"
+ math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+type SendmsgRequest struct {
+ Fd uint32 `protobuf:"varint,1,opt,name=fd,proto3" json:"fd,omitempty"`
+ Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
+ Address []byte `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"`
+ More bool `protobuf:"varint,4,opt,name=more,proto3" json:"more,omitempty"`
+ EndOfRecord bool `protobuf:"varint,5,opt,name=end_of_record,json=endOfRecord,proto3" json:"end_of_record,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *SendmsgRequest) Reset() { *m = SendmsgRequest{} }
+func (m *SendmsgRequest) String() string { return proto.CompactTextString(m) }
+func (*SendmsgRequest) ProtoMessage() {}
+func (*SendmsgRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{0}
+}
+
+func (m *SendmsgRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_SendmsgRequest.Unmarshal(m, b)
+}
+func (m *SendmsgRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_SendmsgRequest.Marshal(b, m, deterministic)
+}
+func (m *SendmsgRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_SendmsgRequest.Merge(m, src)
+}
+func (m *SendmsgRequest) XXX_Size() int {
+ return xxx_messageInfo_SendmsgRequest.Size(m)
+}
+func (m *SendmsgRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_SendmsgRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_SendmsgRequest proto.InternalMessageInfo
+
+func (m *SendmsgRequest) GetFd() uint32 {
+ if m != nil {
+ return m.Fd
+ }
+ return 0
+}
+
+func (m *SendmsgRequest) GetData() []byte {
+ if m != nil {
+ return m.Data
+ }
+ return nil
+}
+
+func (m *SendmsgRequest) GetAddress() []byte {
+ if m != nil {
+ return m.Address
+ }
+ return nil
+}
+
+func (m *SendmsgRequest) GetMore() bool {
+ if m != nil {
+ return m.More
+ }
+ return false
+}
+
+func (m *SendmsgRequest) GetEndOfRecord() bool {
+ if m != nil {
+ return m.EndOfRecord
+ }
+ return false
+}
+
+type SendmsgResponse struct {
+ // Types that are valid to be assigned to Result:
+ // *SendmsgResponse_ErrorNumber
+ // *SendmsgResponse_Length
+ Result isSendmsgResponse_Result `protobuf_oneof:"result"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *SendmsgResponse) Reset() { *m = SendmsgResponse{} }
+func (m *SendmsgResponse) String() string { return proto.CompactTextString(m) }
+func (*SendmsgResponse) ProtoMessage() {}
+func (*SendmsgResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{1}
+}
+
+func (m *SendmsgResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_SendmsgResponse.Unmarshal(m, b)
+}
+func (m *SendmsgResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_SendmsgResponse.Marshal(b, m, deterministic)
+}
+func (m *SendmsgResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_SendmsgResponse.Merge(m, src)
+}
+func (m *SendmsgResponse) XXX_Size() int {
+ return xxx_messageInfo_SendmsgResponse.Size(m)
+}
+func (m *SendmsgResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_SendmsgResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_SendmsgResponse proto.InternalMessageInfo
+
+type isSendmsgResponse_Result interface {
+ isSendmsgResponse_Result()
+}
+
+type SendmsgResponse_ErrorNumber struct {
+ ErrorNumber uint32 `protobuf:"varint,1,opt,name=error_number,json=errorNumber,proto3,oneof"`
+}
+
+type SendmsgResponse_Length struct {
+ Length uint32 `protobuf:"varint,2,opt,name=length,proto3,oneof"`
+}
+
+func (*SendmsgResponse_ErrorNumber) isSendmsgResponse_Result() {}
+
+func (*SendmsgResponse_Length) isSendmsgResponse_Result() {}
+
+func (m *SendmsgResponse) GetResult() isSendmsgResponse_Result {
+ if m != nil {
+ return m.Result
+ }
+ return nil
+}
+
+func (m *SendmsgResponse) GetErrorNumber() uint32 {
+ if x, ok := m.GetResult().(*SendmsgResponse_ErrorNumber); ok {
+ return x.ErrorNumber
+ }
+ return 0
+}
+
+func (m *SendmsgResponse) GetLength() uint32 {
+ if x, ok := m.GetResult().(*SendmsgResponse_Length); ok {
+ return x.Length
+ }
+ return 0
+}
+
+// XXX_OneofWrappers is for the internal use of the proto package.
+func (*SendmsgResponse) XXX_OneofWrappers() []interface{} {
+ return []interface{}{
+ (*SendmsgResponse_ErrorNumber)(nil),
+ (*SendmsgResponse_Length)(nil),
+ }
+}
+
+type IOCtlRequest struct {
+ Fd uint32 `protobuf:"varint,1,opt,name=fd,proto3" json:"fd,omitempty"`
+ Cmd uint32 `protobuf:"varint,2,opt,name=cmd,proto3" json:"cmd,omitempty"`
+ Arg []byte `protobuf:"bytes,3,opt,name=arg,proto3" json:"arg,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *IOCtlRequest) Reset() { *m = IOCtlRequest{} }
+func (m *IOCtlRequest) String() string { return proto.CompactTextString(m) }
+func (*IOCtlRequest) ProtoMessage() {}
+func (*IOCtlRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{2}
+}
+
+func (m *IOCtlRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_IOCtlRequest.Unmarshal(m, b)
+}
+func (m *IOCtlRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_IOCtlRequest.Marshal(b, m, deterministic)
+}
+func (m *IOCtlRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_IOCtlRequest.Merge(m, src)
+}
+func (m *IOCtlRequest) XXX_Size() int {
+ return xxx_messageInfo_IOCtlRequest.Size(m)
+}
+func (m *IOCtlRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_IOCtlRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_IOCtlRequest proto.InternalMessageInfo
+
+func (m *IOCtlRequest) GetFd() uint32 {
+ if m != nil {
+ return m.Fd
+ }
+ return 0
+}
+
+func (m *IOCtlRequest) GetCmd() uint32 {
+ if m != nil {
+ return m.Cmd
+ }
+ return 0
+}
+
+func (m *IOCtlRequest) GetArg() []byte {
+ if m != nil {
+ return m.Arg
+ }
+ return nil
+}
+
+type IOCtlResponse struct {
+ // Types that are valid to be assigned to Result:
+ // *IOCtlResponse_ErrorNumber
+ // *IOCtlResponse_Value
+ Result isIOCtlResponse_Result `protobuf_oneof:"result"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *IOCtlResponse) Reset() { *m = IOCtlResponse{} }
+func (m *IOCtlResponse) String() string { return proto.CompactTextString(m) }
+func (*IOCtlResponse) ProtoMessage() {}
+func (*IOCtlResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{3}
+}
+
+func (m *IOCtlResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_IOCtlResponse.Unmarshal(m, b)
+}
+func (m *IOCtlResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_IOCtlResponse.Marshal(b, m, deterministic)
+}
+func (m *IOCtlResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_IOCtlResponse.Merge(m, src)
+}
+func (m *IOCtlResponse) XXX_Size() int {
+ return xxx_messageInfo_IOCtlResponse.Size(m)
+}
+func (m *IOCtlResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_IOCtlResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_IOCtlResponse proto.InternalMessageInfo
+
+type isIOCtlResponse_Result interface {
+ isIOCtlResponse_Result()
+}
+
+type IOCtlResponse_ErrorNumber struct {
+ ErrorNumber uint32 `protobuf:"varint,1,opt,name=error_number,json=errorNumber,proto3,oneof"`
+}
+
+type IOCtlResponse_Value struct {
+ Value []byte `protobuf:"bytes,2,opt,name=value,proto3,oneof"`
+}
+
+func (*IOCtlResponse_ErrorNumber) isIOCtlResponse_Result() {}
+
+func (*IOCtlResponse_Value) isIOCtlResponse_Result() {}
+
+func (m *IOCtlResponse) GetResult() isIOCtlResponse_Result {
+ if m != nil {
+ return m.Result
+ }
+ return nil
+}
+
+func (m *IOCtlResponse) GetErrorNumber() uint32 {
+ if x, ok := m.GetResult().(*IOCtlResponse_ErrorNumber); ok {
+ return x.ErrorNumber
+ }
+ return 0
+}
+
+func (m *IOCtlResponse) GetValue() []byte {
+ if x, ok := m.GetResult().(*IOCtlResponse_Value); ok {
+ return x.Value
+ }
+ return nil
+}
+
+// XXX_OneofWrappers is for the internal use of the proto package.
+func (*IOCtlResponse) XXX_OneofWrappers() []interface{} {
+ return []interface{}{
+ (*IOCtlResponse_ErrorNumber)(nil),
+ (*IOCtlResponse_Value)(nil),
+ }
+}
+
+type RecvmsgRequest struct {
+ Fd uint32 `protobuf:"varint,1,opt,name=fd,proto3" json:"fd,omitempty"`
+ Length uint32 `protobuf:"varint,2,opt,name=length,proto3" json:"length,omitempty"`
+ Sender bool `protobuf:"varint,3,opt,name=sender,proto3" json:"sender,omitempty"`
+ Peek bool `protobuf:"varint,4,opt,name=peek,proto3" json:"peek,omitempty"`
+ Trunc bool `protobuf:"varint,5,opt,name=trunc,proto3" json:"trunc,omitempty"`
+ CmsgLength uint32 `protobuf:"varint,6,opt,name=cmsg_length,json=cmsgLength,proto3" json:"cmsg_length,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *RecvmsgRequest) Reset() { *m = RecvmsgRequest{} }
+func (m *RecvmsgRequest) String() string { return proto.CompactTextString(m) }
+func (*RecvmsgRequest) ProtoMessage() {}
+func (*RecvmsgRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{4}
+}
+
+func (m *RecvmsgRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_RecvmsgRequest.Unmarshal(m, b)
+}
+func (m *RecvmsgRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_RecvmsgRequest.Marshal(b, m, deterministic)
+}
+func (m *RecvmsgRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_RecvmsgRequest.Merge(m, src)
+}
+func (m *RecvmsgRequest) XXX_Size() int {
+ return xxx_messageInfo_RecvmsgRequest.Size(m)
+}
+func (m *RecvmsgRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_RecvmsgRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_RecvmsgRequest proto.InternalMessageInfo
+
+func (m *RecvmsgRequest) GetFd() uint32 {
+ if m != nil {
+ return m.Fd
+ }
+ return 0
+}
+
+func (m *RecvmsgRequest) GetLength() uint32 {
+ if m != nil {
+ return m.Length
+ }
+ return 0
+}
+
+func (m *RecvmsgRequest) GetSender() bool {
+ if m != nil {
+ return m.Sender
+ }
+ return false
+}
+
+func (m *RecvmsgRequest) GetPeek() bool {
+ if m != nil {
+ return m.Peek
+ }
+ return false
+}
+
+func (m *RecvmsgRequest) GetTrunc() bool {
+ if m != nil {
+ return m.Trunc
+ }
+ return false
+}
+
+func (m *RecvmsgRequest) GetCmsgLength() uint32 {
+ if m != nil {
+ return m.CmsgLength
+ }
+ return 0
+}
+
+type OpenRequest struct {
+ Path []byte `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
+ Flags uint32 `protobuf:"varint,2,opt,name=flags,proto3" json:"flags,omitempty"`
+ Mode uint32 `protobuf:"varint,3,opt,name=mode,proto3" json:"mode,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *OpenRequest) Reset() { *m = OpenRequest{} }
+func (m *OpenRequest) String() string { return proto.CompactTextString(m) }
+func (*OpenRequest) ProtoMessage() {}
+func (*OpenRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{5}
+}
+
+func (m *OpenRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_OpenRequest.Unmarshal(m, b)
+}
+func (m *OpenRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_OpenRequest.Marshal(b, m, deterministic)
+}
+func (m *OpenRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_OpenRequest.Merge(m, src)
+}
+func (m *OpenRequest) XXX_Size() int {
+ return xxx_messageInfo_OpenRequest.Size(m)
+}
+func (m *OpenRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_OpenRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_OpenRequest proto.InternalMessageInfo
+
+func (m *OpenRequest) GetPath() []byte {
+ if m != nil {
+ return m.Path
+ }
+ return nil
+}
+
+func (m *OpenRequest) GetFlags() uint32 {
+ if m != nil {
+ return m.Flags
+ }
+ return 0
+}
+
+func (m *OpenRequest) GetMode() uint32 {
+ if m != nil {
+ return m.Mode
+ }
+ return 0
+}
+
+type OpenResponse struct {
+ // Types that are valid to be assigned to Result:
+ // *OpenResponse_ErrorNumber
+ // *OpenResponse_Fd
+ Result isOpenResponse_Result `protobuf_oneof:"result"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *OpenResponse) Reset() { *m = OpenResponse{} }
+func (m *OpenResponse) String() string { return proto.CompactTextString(m) }
+func (*OpenResponse) ProtoMessage() {}
+func (*OpenResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{6}
+}
+
+func (m *OpenResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_OpenResponse.Unmarshal(m, b)
+}
+func (m *OpenResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_OpenResponse.Marshal(b, m, deterministic)
+}
+func (m *OpenResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_OpenResponse.Merge(m, src)
+}
+func (m *OpenResponse) XXX_Size() int {
+ return xxx_messageInfo_OpenResponse.Size(m)
+}
+func (m *OpenResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_OpenResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_OpenResponse proto.InternalMessageInfo
+
+type isOpenResponse_Result interface {
+ isOpenResponse_Result()
+}
+
+type OpenResponse_ErrorNumber struct {
+ ErrorNumber uint32 `protobuf:"varint,1,opt,name=error_number,json=errorNumber,proto3,oneof"`
+}
+
+type OpenResponse_Fd struct {
+ Fd uint32 `protobuf:"varint,2,opt,name=fd,proto3,oneof"`
+}
+
+func (*OpenResponse_ErrorNumber) isOpenResponse_Result() {}
+
+func (*OpenResponse_Fd) isOpenResponse_Result() {}
+
+func (m *OpenResponse) GetResult() isOpenResponse_Result {
+ if m != nil {
+ return m.Result
+ }
+ return nil
+}
+
+func (m *OpenResponse) GetErrorNumber() uint32 {
+ if x, ok := m.GetResult().(*OpenResponse_ErrorNumber); ok {
+ return x.ErrorNumber
+ }
+ return 0
+}
+
+func (m *OpenResponse) GetFd() uint32 {
+ if x, ok := m.GetResult().(*OpenResponse_Fd); ok {
+ return x.Fd
+ }
+ return 0
+}
+
+// XXX_OneofWrappers is for the internal use of the proto package.
+func (*OpenResponse) XXX_OneofWrappers() []interface{} {
+ return []interface{}{
+ (*OpenResponse_ErrorNumber)(nil),
+ (*OpenResponse_Fd)(nil),
+ }
+}
+
+type ReadRequest struct {
+ Fd uint32 `protobuf:"varint,1,opt,name=fd,proto3" json:"fd,omitempty"`
+ Length uint32 `protobuf:"varint,2,opt,name=length,proto3" json:"length,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *ReadRequest) Reset() { *m = ReadRequest{} }
+func (m *ReadRequest) String() string { return proto.CompactTextString(m) }
+func (*ReadRequest) ProtoMessage() {}
+func (*ReadRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{7}
+}
+
+func (m *ReadRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_ReadRequest.Unmarshal(m, b)
+}
+func (m *ReadRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_ReadRequest.Marshal(b, m, deterministic)
+}
+func (m *ReadRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_ReadRequest.Merge(m, src)
+}
+func (m *ReadRequest) XXX_Size() int {
+ return xxx_messageInfo_ReadRequest.Size(m)
+}
+func (m *ReadRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_ReadRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ReadRequest proto.InternalMessageInfo
+
+func (m *ReadRequest) GetFd() uint32 {
+ if m != nil {
+ return m.Fd
+ }
+ return 0
+}
+
+func (m *ReadRequest) GetLength() uint32 {
+ if m != nil {
+ return m.Length
+ }
+ return 0
+}
+
+type ReadResponse struct {
+ // Types that are valid to be assigned to Result:
+ // *ReadResponse_ErrorNumber
+ // *ReadResponse_Data
+ Result isReadResponse_Result `protobuf_oneof:"result"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *ReadResponse) Reset() { *m = ReadResponse{} }
+func (m *ReadResponse) String() string { return proto.CompactTextString(m) }
+func (*ReadResponse) ProtoMessage() {}
+func (*ReadResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{8}
+}
+
+func (m *ReadResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_ReadResponse.Unmarshal(m, b)
+}
+func (m *ReadResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_ReadResponse.Marshal(b, m, deterministic)
+}
+func (m *ReadResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_ReadResponse.Merge(m, src)
+}
+func (m *ReadResponse) XXX_Size() int {
+ return xxx_messageInfo_ReadResponse.Size(m)
+}
+func (m *ReadResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_ReadResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ReadResponse proto.InternalMessageInfo
+
+type isReadResponse_Result interface {
+ isReadResponse_Result()
+}
+
+type ReadResponse_ErrorNumber struct {
+ ErrorNumber uint32 `protobuf:"varint,1,opt,name=error_number,json=errorNumber,proto3,oneof"`
+}
+
+type ReadResponse_Data struct {
+ Data []byte `protobuf:"bytes,2,opt,name=data,proto3,oneof"`
+}
+
+func (*ReadResponse_ErrorNumber) isReadResponse_Result() {}
+
+func (*ReadResponse_Data) isReadResponse_Result() {}
+
+func (m *ReadResponse) GetResult() isReadResponse_Result {
+ if m != nil {
+ return m.Result
+ }
+ return nil
+}
+
+func (m *ReadResponse) GetErrorNumber() uint32 {
+ if x, ok := m.GetResult().(*ReadResponse_ErrorNumber); ok {
+ return x.ErrorNumber
+ }
+ return 0
+}
+
+func (m *ReadResponse) GetData() []byte {
+ if x, ok := m.GetResult().(*ReadResponse_Data); ok {
+ return x.Data
+ }
+ return nil
+}
+
+// XXX_OneofWrappers is for the internal use of the proto package.
+func (*ReadResponse) XXX_OneofWrappers() []interface{} {
+ return []interface{}{
+ (*ReadResponse_ErrorNumber)(nil),
+ (*ReadResponse_Data)(nil),
+ }
+}
+
+type ReadFileRequest struct {
+ Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *ReadFileRequest) Reset() { *m = ReadFileRequest{} }
+func (m *ReadFileRequest) String() string { return proto.CompactTextString(m) }
+func (*ReadFileRequest) ProtoMessage() {}
+func (*ReadFileRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{9}
+}
+
+func (m *ReadFileRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_ReadFileRequest.Unmarshal(m, b)
+}
+func (m *ReadFileRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_ReadFileRequest.Marshal(b, m, deterministic)
+}
+func (m *ReadFileRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_ReadFileRequest.Merge(m, src)
+}
+func (m *ReadFileRequest) XXX_Size() int {
+ return xxx_messageInfo_ReadFileRequest.Size(m)
+}
+func (m *ReadFileRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_ReadFileRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ReadFileRequest proto.InternalMessageInfo
+
+func (m *ReadFileRequest) GetPath() string {
+ if m != nil {
+ return m.Path
+ }
+ return ""
+}
+
+type ReadFileResponse struct {
+ // Types that are valid to be assigned to Result:
+ // *ReadFileResponse_ErrorNumber
+ // *ReadFileResponse_Data
+ Result isReadFileResponse_Result `protobuf_oneof:"result"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *ReadFileResponse) Reset() { *m = ReadFileResponse{} }
+func (m *ReadFileResponse) String() string { return proto.CompactTextString(m) }
+func (*ReadFileResponse) ProtoMessage() {}
+func (*ReadFileResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{10}
+}
+
+func (m *ReadFileResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_ReadFileResponse.Unmarshal(m, b)
+}
+func (m *ReadFileResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_ReadFileResponse.Marshal(b, m, deterministic)
+}
+func (m *ReadFileResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_ReadFileResponse.Merge(m, src)
+}
+func (m *ReadFileResponse) XXX_Size() int {
+ return xxx_messageInfo_ReadFileResponse.Size(m)
+}
+func (m *ReadFileResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_ReadFileResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ReadFileResponse proto.InternalMessageInfo
+
+type isReadFileResponse_Result interface {
+ isReadFileResponse_Result()
+}
+
+type ReadFileResponse_ErrorNumber struct {
+ ErrorNumber uint32 `protobuf:"varint,1,opt,name=error_number,json=errorNumber,proto3,oneof"`
+}
+
+type ReadFileResponse_Data struct {
+ Data []byte `protobuf:"bytes,2,opt,name=data,proto3,oneof"`
+}
+
+func (*ReadFileResponse_ErrorNumber) isReadFileResponse_Result() {}
+
+func (*ReadFileResponse_Data) isReadFileResponse_Result() {}
+
+func (m *ReadFileResponse) GetResult() isReadFileResponse_Result {
+ if m != nil {
+ return m.Result
+ }
+ return nil
+}
+
+func (m *ReadFileResponse) GetErrorNumber() uint32 {
+ if x, ok := m.GetResult().(*ReadFileResponse_ErrorNumber); ok {
+ return x.ErrorNumber
+ }
+ return 0
+}
+
+func (m *ReadFileResponse) GetData() []byte {
+ if x, ok := m.GetResult().(*ReadFileResponse_Data); ok {
+ return x.Data
+ }
+ return nil
+}
+
+// XXX_OneofWrappers is for the internal use of the proto package.
+func (*ReadFileResponse) XXX_OneofWrappers() []interface{} {
+ return []interface{}{
+ (*ReadFileResponse_ErrorNumber)(nil),
+ (*ReadFileResponse_Data)(nil),
+ }
+}
+
+type WriteRequest struct {
+ Fd uint32 `protobuf:"varint,1,opt,name=fd,proto3" json:"fd,omitempty"`
+ Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *WriteRequest) Reset() { *m = WriteRequest{} }
+func (m *WriteRequest) String() string { return proto.CompactTextString(m) }
+func (*WriteRequest) ProtoMessage() {}
+func (*WriteRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{11}
+}
+
+func (m *WriteRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_WriteRequest.Unmarshal(m, b)
+}
+func (m *WriteRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_WriteRequest.Marshal(b, m, deterministic)
+}
+func (m *WriteRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_WriteRequest.Merge(m, src)
+}
+func (m *WriteRequest) XXX_Size() int {
+ return xxx_messageInfo_WriteRequest.Size(m)
+}
+func (m *WriteRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_WriteRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_WriteRequest proto.InternalMessageInfo
+
+func (m *WriteRequest) GetFd() uint32 {
+ if m != nil {
+ return m.Fd
+ }
+ return 0
+}
+
+func (m *WriteRequest) GetData() []byte {
+ if m != nil {
+ return m.Data
+ }
+ return nil
+}
+
+type WriteResponse struct {
+ // Types that are valid to be assigned to Result:
+ // *WriteResponse_ErrorNumber
+ // *WriteResponse_Length
+ Result isWriteResponse_Result `protobuf_oneof:"result"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *WriteResponse) Reset() { *m = WriteResponse{} }
+func (m *WriteResponse) String() string { return proto.CompactTextString(m) }
+func (*WriteResponse) ProtoMessage() {}
+func (*WriteResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{12}
+}
+
+func (m *WriteResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_WriteResponse.Unmarshal(m, b)
+}
+func (m *WriteResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_WriteResponse.Marshal(b, m, deterministic)
+}
+func (m *WriteResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_WriteResponse.Merge(m, src)
+}
+func (m *WriteResponse) XXX_Size() int {
+ return xxx_messageInfo_WriteResponse.Size(m)
+}
+func (m *WriteResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_WriteResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_WriteResponse proto.InternalMessageInfo
+
+type isWriteResponse_Result interface {
+ isWriteResponse_Result()
+}
+
+type WriteResponse_ErrorNumber struct {
+ ErrorNumber uint32 `protobuf:"varint,1,opt,name=error_number,json=errorNumber,proto3,oneof"`
+}
+
+type WriteResponse_Length struct {
+ Length uint32 `protobuf:"varint,2,opt,name=length,proto3,oneof"`
+}
+
+func (*WriteResponse_ErrorNumber) isWriteResponse_Result() {}
+
+func (*WriteResponse_Length) isWriteResponse_Result() {}
+
+func (m *WriteResponse) GetResult() isWriteResponse_Result {
+ if m != nil {
+ return m.Result
+ }
+ return nil
+}
+
+func (m *WriteResponse) GetErrorNumber() uint32 {
+ if x, ok := m.GetResult().(*WriteResponse_ErrorNumber); ok {
+ return x.ErrorNumber
+ }
+ return 0
+}
+
+func (m *WriteResponse) GetLength() uint32 {
+ if x, ok := m.GetResult().(*WriteResponse_Length); ok {
+ return x.Length
+ }
+ return 0
+}
+
+// XXX_OneofWrappers is for the internal use of the proto package.
+func (*WriteResponse) XXX_OneofWrappers() []interface{} {
+ return []interface{}{
+ (*WriteResponse_ErrorNumber)(nil),
+ (*WriteResponse_Length)(nil),
+ }
+}
+
+type WriteFileRequest struct {
+ Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
+ Content []byte `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *WriteFileRequest) Reset() { *m = WriteFileRequest{} }
+func (m *WriteFileRequest) String() string { return proto.CompactTextString(m) }
+func (*WriteFileRequest) ProtoMessage() {}
+func (*WriteFileRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{13}
+}
+
+func (m *WriteFileRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_WriteFileRequest.Unmarshal(m, b)
+}
+func (m *WriteFileRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_WriteFileRequest.Marshal(b, m, deterministic)
+}
+func (m *WriteFileRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_WriteFileRequest.Merge(m, src)
+}
+func (m *WriteFileRequest) XXX_Size() int {
+ return xxx_messageInfo_WriteFileRequest.Size(m)
+}
+func (m *WriteFileRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_WriteFileRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_WriteFileRequest proto.InternalMessageInfo
+
+func (m *WriteFileRequest) GetPath() string {
+ if m != nil {
+ return m.Path
+ }
+ return ""
+}
+
+func (m *WriteFileRequest) GetContent() []byte {
+ if m != nil {
+ return m.Content
+ }
+ return nil
+}
+
+type WriteFileResponse struct {
+ ErrorNumber uint32 `protobuf:"varint,1,opt,name=error_number,json=errorNumber,proto3" json:"error_number,omitempty"`
+ Written uint32 `protobuf:"varint,2,opt,name=written,proto3" json:"written,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *WriteFileResponse) Reset() { *m = WriteFileResponse{} }
+func (m *WriteFileResponse) String() string { return proto.CompactTextString(m) }
+func (*WriteFileResponse) ProtoMessage() {}
+func (*WriteFileResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{14}
+}
+
+func (m *WriteFileResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_WriteFileResponse.Unmarshal(m, b)
+}
+func (m *WriteFileResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_WriteFileResponse.Marshal(b, m, deterministic)
+}
+func (m *WriteFileResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_WriteFileResponse.Merge(m, src)
+}
+func (m *WriteFileResponse) XXX_Size() int {
+ return xxx_messageInfo_WriteFileResponse.Size(m)
+}
+func (m *WriteFileResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_WriteFileResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_WriteFileResponse proto.InternalMessageInfo
+
+func (m *WriteFileResponse) GetErrorNumber() uint32 {
+ if m != nil {
+ return m.ErrorNumber
+ }
+ return 0
+}
+
+func (m *WriteFileResponse) GetWritten() uint32 {
+ if m != nil {
+ return m.Written
+ }
+ return 0
+}
+
+type AddressResponse struct {
+ Address []byte `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
+ Length uint32 `protobuf:"varint,2,opt,name=length,proto3" json:"length,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *AddressResponse) Reset() { *m = AddressResponse{} }
+func (m *AddressResponse) String() string { return proto.CompactTextString(m) }
+func (*AddressResponse) ProtoMessage() {}
+func (*AddressResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{15}
+}
+
+func (m *AddressResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_AddressResponse.Unmarshal(m, b)
+}
+func (m *AddressResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_AddressResponse.Marshal(b, m, deterministic)
+}
+func (m *AddressResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_AddressResponse.Merge(m, src)
+}
+func (m *AddressResponse) XXX_Size() int {
+ return xxx_messageInfo_AddressResponse.Size(m)
+}
+func (m *AddressResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_AddressResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_AddressResponse proto.InternalMessageInfo
+
+func (m *AddressResponse) GetAddress() []byte {
+ if m != nil {
+ return m.Address
+ }
+ return nil
+}
+
+func (m *AddressResponse) GetLength() uint32 {
+ if m != nil {
+ return m.Length
+ }
+ return 0
+}
+
+type RecvmsgResponse struct {
+ // Types that are valid to be assigned to Result:
+ // *RecvmsgResponse_ErrorNumber
+ // *RecvmsgResponse_Payload
+ Result isRecvmsgResponse_Result `protobuf_oneof:"result"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *RecvmsgResponse) Reset() { *m = RecvmsgResponse{} }
+func (m *RecvmsgResponse) String() string { return proto.CompactTextString(m) }
+func (*RecvmsgResponse) ProtoMessage() {}
+func (*RecvmsgResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{16}
+}
+
+func (m *RecvmsgResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_RecvmsgResponse.Unmarshal(m, b)
+}
+func (m *RecvmsgResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_RecvmsgResponse.Marshal(b, m, deterministic)
+}
+func (m *RecvmsgResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_RecvmsgResponse.Merge(m, src)
+}
+func (m *RecvmsgResponse) XXX_Size() int {
+ return xxx_messageInfo_RecvmsgResponse.Size(m)
+}
+func (m *RecvmsgResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_RecvmsgResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_RecvmsgResponse proto.InternalMessageInfo
+
+type isRecvmsgResponse_Result interface {
+ isRecvmsgResponse_Result()
+}
+
+type RecvmsgResponse_ErrorNumber struct {
+ ErrorNumber uint32 `protobuf:"varint,1,opt,name=error_number,json=errorNumber,proto3,oneof"`
+}
+
+type RecvmsgResponse_Payload struct {
+ Payload *RecvmsgResponse_ResultPayload `protobuf:"bytes,2,opt,name=payload,proto3,oneof"`
+}
+
+func (*RecvmsgResponse_ErrorNumber) isRecvmsgResponse_Result() {}
+
+func (*RecvmsgResponse_Payload) isRecvmsgResponse_Result() {}
+
+func (m *RecvmsgResponse) GetResult() isRecvmsgResponse_Result {
+ if m != nil {
+ return m.Result
+ }
+ return nil
+}
+
+func (m *RecvmsgResponse) GetErrorNumber() uint32 {
+ if x, ok := m.GetResult().(*RecvmsgResponse_ErrorNumber); ok {
+ return x.ErrorNumber
+ }
+ return 0
+}
+
+func (m *RecvmsgResponse) GetPayload() *RecvmsgResponse_ResultPayload {
+ if x, ok := m.GetResult().(*RecvmsgResponse_Payload); ok {
+ return x.Payload
+ }
+ return nil
+}
+
+// XXX_OneofWrappers is for the internal use of the proto package.
+func (*RecvmsgResponse) XXX_OneofWrappers() []interface{} {
+ return []interface{}{
+ (*RecvmsgResponse_ErrorNumber)(nil),
+ (*RecvmsgResponse_Payload)(nil),
+ }
+}
+
+type RecvmsgResponse_ResultPayload struct {
+ Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
+ Address *AddressResponse `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"`
+ Length uint32 `protobuf:"varint,3,opt,name=length,proto3" json:"length,omitempty"`
+ CmsgData []byte `protobuf:"bytes,4,opt,name=cmsg_data,json=cmsgData,proto3" json:"cmsg_data,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *RecvmsgResponse_ResultPayload) Reset() { *m = RecvmsgResponse_ResultPayload{} }
+func (m *RecvmsgResponse_ResultPayload) String() string { return proto.CompactTextString(m) }
+func (*RecvmsgResponse_ResultPayload) ProtoMessage() {}
+func (*RecvmsgResponse_ResultPayload) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{16, 0}
+}
+
+func (m *RecvmsgResponse_ResultPayload) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_RecvmsgResponse_ResultPayload.Unmarshal(m, b)
+}
+func (m *RecvmsgResponse_ResultPayload) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_RecvmsgResponse_ResultPayload.Marshal(b, m, deterministic)
+}
+func (m *RecvmsgResponse_ResultPayload) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_RecvmsgResponse_ResultPayload.Merge(m, src)
+}
+func (m *RecvmsgResponse_ResultPayload) XXX_Size() int {
+ return xxx_messageInfo_RecvmsgResponse_ResultPayload.Size(m)
+}
+func (m *RecvmsgResponse_ResultPayload) XXX_DiscardUnknown() {
+ xxx_messageInfo_RecvmsgResponse_ResultPayload.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_RecvmsgResponse_ResultPayload proto.InternalMessageInfo
+
+func (m *RecvmsgResponse_ResultPayload) GetData() []byte {
+ if m != nil {
+ return m.Data
+ }
+ return nil
+}
+
+func (m *RecvmsgResponse_ResultPayload) GetAddress() *AddressResponse {
+ if m != nil {
+ return m.Address
+ }
+ return nil
+}
+
+func (m *RecvmsgResponse_ResultPayload) GetLength() uint32 {
+ if m != nil {
+ return m.Length
+ }
+ return 0
+}
+
+func (m *RecvmsgResponse_ResultPayload) GetCmsgData() []byte {
+ if m != nil {
+ return m.CmsgData
+ }
+ return nil
+}
+
+type BindRequest struct {
+ Fd uint32 `protobuf:"varint,1,opt,name=fd,proto3" json:"fd,omitempty"`
+ Address []byte `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *BindRequest) Reset() { *m = BindRequest{} }
+func (m *BindRequest) String() string { return proto.CompactTextString(m) }
+func (*BindRequest) ProtoMessage() {}
+func (*BindRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{17}
+}
+
+func (m *BindRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_BindRequest.Unmarshal(m, b)
+}
+func (m *BindRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_BindRequest.Marshal(b, m, deterministic)
+}
+func (m *BindRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_BindRequest.Merge(m, src)
+}
+func (m *BindRequest) XXX_Size() int {
+ return xxx_messageInfo_BindRequest.Size(m)
+}
+func (m *BindRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_BindRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_BindRequest proto.InternalMessageInfo
+
+func (m *BindRequest) GetFd() uint32 {
+ if m != nil {
+ return m.Fd
+ }
+ return 0
+}
+
+func (m *BindRequest) GetAddress() []byte {
+ if m != nil {
+ return m.Address
+ }
+ return nil
+}
+
+type BindResponse struct {
+ ErrorNumber uint32 `protobuf:"varint,1,opt,name=error_number,json=errorNumber,proto3" json:"error_number,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *BindResponse) Reset() { *m = BindResponse{} }
+func (m *BindResponse) String() string { return proto.CompactTextString(m) }
+func (*BindResponse) ProtoMessage() {}
+func (*BindResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{18}
+}
+
+func (m *BindResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_BindResponse.Unmarshal(m, b)
+}
+func (m *BindResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_BindResponse.Marshal(b, m, deterministic)
+}
+func (m *BindResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_BindResponse.Merge(m, src)
+}
+func (m *BindResponse) XXX_Size() int {
+ return xxx_messageInfo_BindResponse.Size(m)
+}
+func (m *BindResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_BindResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_BindResponse proto.InternalMessageInfo
+
+func (m *BindResponse) GetErrorNumber() uint32 {
+ if m != nil {
+ return m.ErrorNumber
+ }
+ return 0
+}
+
+type AcceptRequest struct {
+ Fd uint32 `protobuf:"varint,1,opt,name=fd,proto3" json:"fd,omitempty"`
+ Peer bool `protobuf:"varint,2,opt,name=peer,proto3" json:"peer,omitempty"`
+ Flags int64 `protobuf:"varint,3,opt,name=flags,proto3" json:"flags,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *AcceptRequest) Reset() { *m = AcceptRequest{} }
+func (m *AcceptRequest) String() string { return proto.CompactTextString(m) }
+func (*AcceptRequest) ProtoMessage() {}
+func (*AcceptRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{19}
+}
+
+func (m *AcceptRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_AcceptRequest.Unmarshal(m, b)
+}
+func (m *AcceptRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_AcceptRequest.Marshal(b, m, deterministic)
+}
+func (m *AcceptRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_AcceptRequest.Merge(m, src)
+}
+func (m *AcceptRequest) XXX_Size() int {
+ return xxx_messageInfo_AcceptRequest.Size(m)
+}
+func (m *AcceptRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_AcceptRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_AcceptRequest proto.InternalMessageInfo
+
+func (m *AcceptRequest) GetFd() uint32 {
+ if m != nil {
+ return m.Fd
+ }
+ return 0
+}
+
+func (m *AcceptRequest) GetPeer() bool {
+ if m != nil {
+ return m.Peer
+ }
+ return false
+}
+
+func (m *AcceptRequest) GetFlags() int64 {
+ if m != nil {
+ return m.Flags
+ }
+ return 0
+}
+
+type AcceptResponse struct {
+ // Types that are valid to be assigned to Result:
+ // *AcceptResponse_ErrorNumber
+ // *AcceptResponse_Payload
+ Result isAcceptResponse_Result `protobuf_oneof:"result"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *AcceptResponse) Reset() { *m = AcceptResponse{} }
+func (m *AcceptResponse) String() string { return proto.CompactTextString(m) }
+func (*AcceptResponse) ProtoMessage() {}
+func (*AcceptResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{20}
+}
+
+func (m *AcceptResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_AcceptResponse.Unmarshal(m, b)
+}
+func (m *AcceptResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_AcceptResponse.Marshal(b, m, deterministic)
+}
+func (m *AcceptResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_AcceptResponse.Merge(m, src)
+}
+func (m *AcceptResponse) XXX_Size() int {
+ return xxx_messageInfo_AcceptResponse.Size(m)
+}
+func (m *AcceptResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_AcceptResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_AcceptResponse proto.InternalMessageInfo
+
+type isAcceptResponse_Result interface {
+ isAcceptResponse_Result()
+}
+
+type AcceptResponse_ErrorNumber struct {
+ ErrorNumber uint32 `protobuf:"varint,1,opt,name=error_number,json=errorNumber,proto3,oneof"`
+}
+
+type AcceptResponse_Payload struct {
+ Payload *AcceptResponse_ResultPayload `protobuf:"bytes,2,opt,name=payload,proto3,oneof"`
+}
+
+func (*AcceptResponse_ErrorNumber) isAcceptResponse_Result() {}
+
+func (*AcceptResponse_Payload) isAcceptResponse_Result() {}
+
+func (m *AcceptResponse) GetResult() isAcceptResponse_Result {
+ if m != nil {
+ return m.Result
+ }
+ return nil
+}
+
+func (m *AcceptResponse) GetErrorNumber() uint32 {
+ if x, ok := m.GetResult().(*AcceptResponse_ErrorNumber); ok {
+ return x.ErrorNumber
+ }
+ return 0
+}
+
+func (m *AcceptResponse) GetPayload() *AcceptResponse_ResultPayload {
+ if x, ok := m.GetResult().(*AcceptResponse_Payload); ok {
+ return x.Payload
+ }
+ return nil
+}
+
+// XXX_OneofWrappers is for the internal use of the proto package.
+func (*AcceptResponse) XXX_OneofWrappers() []interface{} {
+ return []interface{}{
+ (*AcceptResponse_ErrorNumber)(nil),
+ (*AcceptResponse_Payload)(nil),
+ }
+}
+
+type AcceptResponse_ResultPayload struct {
+ Fd uint32 `protobuf:"varint,1,opt,name=fd,proto3" json:"fd,omitempty"`
+ Address *AddressResponse `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *AcceptResponse_ResultPayload) Reset() { *m = AcceptResponse_ResultPayload{} }
+func (m *AcceptResponse_ResultPayload) String() string { return proto.CompactTextString(m) }
+func (*AcceptResponse_ResultPayload) ProtoMessage() {}
+func (*AcceptResponse_ResultPayload) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{20, 0}
+}
+
+func (m *AcceptResponse_ResultPayload) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_AcceptResponse_ResultPayload.Unmarshal(m, b)
+}
+func (m *AcceptResponse_ResultPayload) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_AcceptResponse_ResultPayload.Marshal(b, m, deterministic)
+}
+func (m *AcceptResponse_ResultPayload) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_AcceptResponse_ResultPayload.Merge(m, src)
+}
+func (m *AcceptResponse_ResultPayload) XXX_Size() int {
+ return xxx_messageInfo_AcceptResponse_ResultPayload.Size(m)
+}
+func (m *AcceptResponse_ResultPayload) XXX_DiscardUnknown() {
+ xxx_messageInfo_AcceptResponse_ResultPayload.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_AcceptResponse_ResultPayload proto.InternalMessageInfo
+
+func (m *AcceptResponse_ResultPayload) GetFd() uint32 {
+ if m != nil {
+ return m.Fd
+ }
+ return 0
+}
+
+func (m *AcceptResponse_ResultPayload) GetAddress() *AddressResponse {
+ if m != nil {
+ return m.Address
+ }
+ return nil
+}
+
+type ConnectRequest struct {
+ Fd uint32 `protobuf:"varint,1,opt,name=fd,proto3" json:"fd,omitempty"`
+ Address []byte `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *ConnectRequest) Reset() { *m = ConnectRequest{} }
+func (m *ConnectRequest) String() string { return proto.CompactTextString(m) }
+func (*ConnectRequest) ProtoMessage() {}
+func (*ConnectRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{21}
+}
+
+func (m *ConnectRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_ConnectRequest.Unmarshal(m, b)
+}
+func (m *ConnectRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_ConnectRequest.Marshal(b, m, deterministic)
+}
+func (m *ConnectRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_ConnectRequest.Merge(m, src)
+}
+func (m *ConnectRequest) XXX_Size() int {
+ return xxx_messageInfo_ConnectRequest.Size(m)
+}
+func (m *ConnectRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_ConnectRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ConnectRequest proto.InternalMessageInfo
+
+func (m *ConnectRequest) GetFd() uint32 {
+ if m != nil {
+ return m.Fd
+ }
+ return 0
+}
+
+func (m *ConnectRequest) GetAddress() []byte {
+ if m != nil {
+ return m.Address
+ }
+ return nil
+}
+
+type ConnectResponse struct {
+ ErrorNumber uint32 `protobuf:"varint,1,opt,name=error_number,json=errorNumber,proto3" json:"error_number,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *ConnectResponse) Reset() { *m = ConnectResponse{} }
+func (m *ConnectResponse) String() string { return proto.CompactTextString(m) }
+func (*ConnectResponse) ProtoMessage() {}
+func (*ConnectResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{22}
+}
+
+func (m *ConnectResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_ConnectResponse.Unmarshal(m, b)
+}
+func (m *ConnectResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_ConnectResponse.Marshal(b, m, deterministic)
+}
+func (m *ConnectResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_ConnectResponse.Merge(m, src)
+}
+func (m *ConnectResponse) XXX_Size() int {
+ return xxx_messageInfo_ConnectResponse.Size(m)
+}
+func (m *ConnectResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_ConnectResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ConnectResponse proto.InternalMessageInfo
+
+func (m *ConnectResponse) GetErrorNumber() uint32 {
+ if m != nil {
+ return m.ErrorNumber
+ }
+ return 0
+}
+
+type ListenRequest struct {
+ Fd uint32 `protobuf:"varint,1,opt,name=fd,proto3" json:"fd,omitempty"`
+ Backlog int64 `protobuf:"varint,2,opt,name=backlog,proto3" json:"backlog,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *ListenRequest) Reset() { *m = ListenRequest{} }
+func (m *ListenRequest) String() string { return proto.CompactTextString(m) }
+func (*ListenRequest) ProtoMessage() {}
+func (*ListenRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{23}
+}
+
+func (m *ListenRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_ListenRequest.Unmarshal(m, b)
+}
+func (m *ListenRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_ListenRequest.Marshal(b, m, deterministic)
+}
+func (m *ListenRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_ListenRequest.Merge(m, src)
+}
+func (m *ListenRequest) XXX_Size() int {
+ return xxx_messageInfo_ListenRequest.Size(m)
+}
+func (m *ListenRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_ListenRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ListenRequest proto.InternalMessageInfo
+
+func (m *ListenRequest) GetFd() uint32 {
+ if m != nil {
+ return m.Fd
+ }
+ return 0
+}
+
+func (m *ListenRequest) GetBacklog() int64 {
+ if m != nil {
+ return m.Backlog
+ }
+ return 0
+}
+
+type ListenResponse struct {
+ ErrorNumber uint32 `protobuf:"varint,1,opt,name=error_number,json=errorNumber,proto3" json:"error_number,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *ListenResponse) Reset() { *m = ListenResponse{} }
+func (m *ListenResponse) String() string { return proto.CompactTextString(m) }
+func (*ListenResponse) ProtoMessage() {}
+func (*ListenResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{24}
+}
+
+func (m *ListenResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_ListenResponse.Unmarshal(m, b)
+}
+func (m *ListenResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_ListenResponse.Marshal(b, m, deterministic)
+}
+func (m *ListenResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_ListenResponse.Merge(m, src)
+}
+func (m *ListenResponse) XXX_Size() int {
+ return xxx_messageInfo_ListenResponse.Size(m)
+}
+func (m *ListenResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_ListenResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ListenResponse proto.InternalMessageInfo
+
+func (m *ListenResponse) GetErrorNumber() uint32 {
+ if m != nil {
+ return m.ErrorNumber
+ }
+ return 0
+}
+
+type ShutdownRequest struct {
+ Fd uint32 `protobuf:"varint,1,opt,name=fd,proto3" json:"fd,omitempty"`
+ How int64 `protobuf:"varint,2,opt,name=how,proto3" json:"how,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *ShutdownRequest) Reset() { *m = ShutdownRequest{} }
+func (m *ShutdownRequest) String() string { return proto.CompactTextString(m) }
+func (*ShutdownRequest) ProtoMessage() {}
+func (*ShutdownRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{25}
+}
+
+func (m *ShutdownRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_ShutdownRequest.Unmarshal(m, b)
+}
+func (m *ShutdownRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_ShutdownRequest.Marshal(b, m, deterministic)
+}
+func (m *ShutdownRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_ShutdownRequest.Merge(m, src)
+}
+func (m *ShutdownRequest) XXX_Size() int {
+ return xxx_messageInfo_ShutdownRequest.Size(m)
+}
+func (m *ShutdownRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_ShutdownRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ShutdownRequest proto.InternalMessageInfo
+
+func (m *ShutdownRequest) GetFd() uint32 {
+ if m != nil {
+ return m.Fd
+ }
+ return 0
+}
+
+func (m *ShutdownRequest) GetHow() int64 {
+ if m != nil {
+ return m.How
+ }
+ return 0
+}
+
+type ShutdownResponse struct {
+ ErrorNumber uint32 `protobuf:"varint,1,opt,name=error_number,json=errorNumber,proto3" json:"error_number,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *ShutdownResponse) Reset() { *m = ShutdownResponse{} }
+func (m *ShutdownResponse) String() string { return proto.CompactTextString(m) }
+func (*ShutdownResponse) ProtoMessage() {}
+func (*ShutdownResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{26}
+}
+
+func (m *ShutdownResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_ShutdownResponse.Unmarshal(m, b)
+}
+func (m *ShutdownResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_ShutdownResponse.Marshal(b, m, deterministic)
+}
+func (m *ShutdownResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_ShutdownResponse.Merge(m, src)
+}
+func (m *ShutdownResponse) XXX_Size() int {
+ return xxx_messageInfo_ShutdownResponse.Size(m)
+}
+func (m *ShutdownResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_ShutdownResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ShutdownResponse proto.InternalMessageInfo
+
+func (m *ShutdownResponse) GetErrorNumber() uint32 {
+ if m != nil {
+ return m.ErrorNumber
+ }
+ return 0
+}
+
+type CloseRequest struct {
+ Fd uint32 `protobuf:"varint,1,opt,name=fd,proto3" json:"fd,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *CloseRequest) Reset() { *m = CloseRequest{} }
+func (m *CloseRequest) String() string { return proto.CompactTextString(m) }
+func (*CloseRequest) ProtoMessage() {}
+func (*CloseRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{27}
+}
+
+func (m *CloseRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_CloseRequest.Unmarshal(m, b)
+}
+func (m *CloseRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_CloseRequest.Marshal(b, m, deterministic)
+}
+func (m *CloseRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_CloseRequest.Merge(m, src)
+}
+func (m *CloseRequest) XXX_Size() int {
+ return xxx_messageInfo_CloseRequest.Size(m)
+}
+func (m *CloseRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_CloseRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CloseRequest proto.InternalMessageInfo
+
+func (m *CloseRequest) GetFd() uint32 {
+ if m != nil {
+ return m.Fd
+ }
+ return 0
+}
+
+type CloseResponse struct {
+ ErrorNumber uint32 `protobuf:"varint,1,opt,name=error_number,json=errorNumber,proto3" json:"error_number,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *CloseResponse) Reset() { *m = CloseResponse{} }
+func (m *CloseResponse) String() string { return proto.CompactTextString(m) }
+func (*CloseResponse) ProtoMessage() {}
+func (*CloseResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{28}
+}
+
+func (m *CloseResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_CloseResponse.Unmarshal(m, b)
+}
+func (m *CloseResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_CloseResponse.Marshal(b, m, deterministic)
+}
+func (m *CloseResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_CloseResponse.Merge(m, src)
+}
+func (m *CloseResponse) XXX_Size() int {
+ return xxx_messageInfo_CloseResponse.Size(m)
+}
+func (m *CloseResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_CloseResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CloseResponse proto.InternalMessageInfo
+
+func (m *CloseResponse) GetErrorNumber() uint32 {
+ if m != nil {
+ return m.ErrorNumber
+ }
+ return 0
+}
+
+type GetSockOptRequest struct {
+ Fd uint32 `protobuf:"varint,1,opt,name=fd,proto3" json:"fd,omitempty"`
+ Level int64 `protobuf:"varint,2,opt,name=level,proto3" json:"level,omitempty"`
+ Name int64 `protobuf:"varint,3,opt,name=name,proto3" json:"name,omitempty"`
+ Length uint32 `protobuf:"varint,4,opt,name=length,proto3" json:"length,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *GetSockOptRequest) Reset() { *m = GetSockOptRequest{} }
+func (m *GetSockOptRequest) String() string { return proto.CompactTextString(m) }
+func (*GetSockOptRequest) ProtoMessage() {}
+func (*GetSockOptRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{29}
+}
+
+func (m *GetSockOptRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_GetSockOptRequest.Unmarshal(m, b)
+}
+func (m *GetSockOptRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_GetSockOptRequest.Marshal(b, m, deterministic)
+}
+func (m *GetSockOptRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_GetSockOptRequest.Merge(m, src)
+}
+func (m *GetSockOptRequest) XXX_Size() int {
+ return xxx_messageInfo_GetSockOptRequest.Size(m)
+}
+func (m *GetSockOptRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_GetSockOptRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_GetSockOptRequest proto.InternalMessageInfo
+
+func (m *GetSockOptRequest) GetFd() uint32 {
+ if m != nil {
+ return m.Fd
+ }
+ return 0
+}
+
+func (m *GetSockOptRequest) GetLevel() int64 {
+ if m != nil {
+ return m.Level
+ }
+ return 0
+}
+
+func (m *GetSockOptRequest) GetName() int64 {
+ if m != nil {
+ return m.Name
+ }
+ return 0
+}
+
+func (m *GetSockOptRequest) GetLength() uint32 {
+ if m != nil {
+ return m.Length
+ }
+ return 0
+}
+
+type GetSockOptResponse struct {
+ // Types that are valid to be assigned to Result:
+ // *GetSockOptResponse_ErrorNumber
+ // *GetSockOptResponse_Opt
+ Result isGetSockOptResponse_Result `protobuf_oneof:"result"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *GetSockOptResponse) Reset() { *m = GetSockOptResponse{} }
+func (m *GetSockOptResponse) String() string { return proto.CompactTextString(m) }
+func (*GetSockOptResponse) ProtoMessage() {}
+func (*GetSockOptResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{30}
+}
+
+func (m *GetSockOptResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_GetSockOptResponse.Unmarshal(m, b)
+}
+func (m *GetSockOptResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_GetSockOptResponse.Marshal(b, m, deterministic)
+}
+func (m *GetSockOptResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_GetSockOptResponse.Merge(m, src)
+}
+func (m *GetSockOptResponse) XXX_Size() int {
+ return xxx_messageInfo_GetSockOptResponse.Size(m)
+}
+func (m *GetSockOptResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_GetSockOptResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_GetSockOptResponse proto.InternalMessageInfo
+
+type isGetSockOptResponse_Result interface {
+ isGetSockOptResponse_Result()
+}
+
+type GetSockOptResponse_ErrorNumber struct {
+ ErrorNumber uint32 `protobuf:"varint,1,opt,name=error_number,json=errorNumber,proto3,oneof"`
+}
+
+type GetSockOptResponse_Opt struct {
+ Opt []byte `protobuf:"bytes,2,opt,name=opt,proto3,oneof"`
+}
+
+func (*GetSockOptResponse_ErrorNumber) isGetSockOptResponse_Result() {}
+
+func (*GetSockOptResponse_Opt) isGetSockOptResponse_Result() {}
+
+func (m *GetSockOptResponse) GetResult() isGetSockOptResponse_Result {
+ if m != nil {
+ return m.Result
+ }
+ return nil
+}
+
+func (m *GetSockOptResponse) GetErrorNumber() uint32 {
+ if x, ok := m.GetResult().(*GetSockOptResponse_ErrorNumber); ok {
+ return x.ErrorNumber
+ }
+ return 0
+}
+
+func (m *GetSockOptResponse) GetOpt() []byte {
+ if x, ok := m.GetResult().(*GetSockOptResponse_Opt); ok {
+ return x.Opt
+ }
+ return nil
+}
+
+// XXX_OneofWrappers is for the internal use of the proto package.
+func (*GetSockOptResponse) XXX_OneofWrappers() []interface{} {
+ return []interface{}{
+ (*GetSockOptResponse_ErrorNumber)(nil),
+ (*GetSockOptResponse_Opt)(nil),
+ }
+}
+
+type SetSockOptRequest struct {
+ Fd uint32 `protobuf:"varint,1,opt,name=fd,proto3" json:"fd,omitempty"`
+ Level int64 `protobuf:"varint,2,opt,name=level,proto3" json:"level,omitempty"`
+ Name int64 `protobuf:"varint,3,opt,name=name,proto3" json:"name,omitempty"`
+ Opt []byte `protobuf:"bytes,4,opt,name=opt,proto3" json:"opt,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *SetSockOptRequest) Reset() { *m = SetSockOptRequest{} }
+func (m *SetSockOptRequest) String() string { return proto.CompactTextString(m) }
+func (*SetSockOptRequest) ProtoMessage() {}
+func (*SetSockOptRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{31}
+}
+
+func (m *SetSockOptRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_SetSockOptRequest.Unmarshal(m, b)
+}
+func (m *SetSockOptRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_SetSockOptRequest.Marshal(b, m, deterministic)
+}
+func (m *SetSockOptRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_SetSockOptRequest.Merge(m, src)
+}
+func (m *SetSockOptRequest) XXX_Size() int {
+ return xxx_messageInfo_SetSockOptRequest.Size(m)
+}
+func (m *SetSockOptRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_SetSockOptRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_SetSockOptRequest proto.InternalMessageInfo
+
+func (m *SetSockOptRequest) GetFd() uint32 {
+ if m != nil {
+ return m.Fd
+ }
+ return 0
+}
+
+func (m *SetSockOptRequest) GetLevel() int64 {
+ if m != nil {
+ return m.Level
+ }
+ return 0
+}
+
+func (m *SetSockOptRequest) GetName() int64 {
+ if m != nil {
+ return m.Name
+ }
+ return 0
+}
+
+func (m *SetSockOptRequest) GetOpt() []byte {
+ if m != nil {
+ return m.Opt
+ }
+ return nil
+}
+
+type SetSockOptResponse struct {
+ ErrorNumber uint32 `protobuf:"varint,1,opt,name=error_number,json=errorNumber,proto3" json:"error_number,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *SetSockOptResponse) Reset() { *m = SetSockOptResponse{} }
+func (m *SetSockOptResponse) String() string { return proto.CompactTextString(m) }
+func (*SetSockOptResponse) ProtoMessage() {}
+func (*SetSockOptResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{32}
+}
+
+func (m *SetSockOptResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_SetSockOptResponse.Unmarshal(m, b)
+}
+func (m *SetSockOptResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_SetSockOptResponse.Marshal(b, m, deterministic)
+}
+func (m *SetSockOptResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_SetSockOptResponse.Merge(m, src)
+}
+func (m *SetSockOptResponse) XXX_Size() int {
+ return xxx_messageInfo_SetSockOptResponse.Size(m)
+}
+func (m *SetSockOptResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_SetSockOptResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_SetSockOptResponse proto.InternalMessageInfo
+
+func (m *SetSockOptResponse) GetErrorNumber() uint32 {
+ if m != nil {
+ return m.ErrorNumber
+ }
+ return 0
+}
+
+type GetSockNameRequest struct {
+ Fd uint32 `protobuf:"varint,1,opt,name=fd,proto3" json:"fd,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *GetSockNameRequest) Reset() { *m = GetSockNameRequest{} }
+func (m *GetSockNameRequest) String() string { return proto.CompactTextString(m) }
+func (*GetSockNameRequest) ProtoMessage() {}
+func (*GetSockNameRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{33}
+}
+
+func (m *GetSockNameRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_GetSockNameRequest.Unmarshal(m, b)
+}
+func (m *GetSockNameRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_GetSockNameRequest.Marshal(b, m, deterministic)
+}
+func (m *GetSockNameRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_GetSockNameRequest.Merge(m, src)
+}
+func (m *GetSockNameRequest) XXX_Size() int {
+ return xxx_messageInfo_GetSockNameRequest.Size(m)
+}
+func (m *GetSockNameRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_GetSockNameRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_GetSockNameRequest proto.InternalMessageInfo
+
+func (m *GetSockNameRequest) GetFd() uint32 {
+ if m != nil {
+ return m.Fd
+ }
+ return 0
+}
+
+type GetSockNameResponse struct {
+ // Types that are valid to be assigned to Result:
+ // *GetSockNameResponse_ErrorNumber
+ // *GetSockNameResponse_Address
+ Result isGetSockNameResponse_Result `protobuf_oneof:"result"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *GetSockNameResponse) Reset() { *m = GetSockNameResponse{} }
+func (m *GetSockNameResponse) String() string { return proto.CompactTextString(m) }
+func (*GetSockNameResponse) ProtoMessage() {}
+func (*GetSockNameResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{34}
+}
+
+func (m *GetSockNameResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_GetSockNameResponse.Unmarshal(m, b)
+}
+func (m *GetSockNameResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_GetSockNameResponse.Marshal(b, m, deterministic)
+}
+func (m *GetSockNameResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_GetSockNameResponse.Merge(m, src)
+}
+func (m *GetSockNameResponse) XXX_Size() int {
+ return xxx_messageInfo_GetSockNameResponse.Size(m)
+}
+func (m *GetSockNameResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_GetSockNameResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_GetSockNameResponse proto.InternalMessageInfo
+
+type isGetSockNameResponse_Result interface {
+ isGetSockNameResponse_Result()
+}
+
+type GetSockNameResponse_ErrorNumber struct {
+ ErrorNumber uint32 `protobuf:"varint,1,opt,name=error_number,json=errorNumber,proto3,oneof"`
+}
+
+type GetSockNameResponse_Address struct {
+ Address *AddressResponse `protobuf:"bytes,2,opt,name=address,proto3,oneof"`
+}
+
+func (*GetSockNameResponse_ErrorNumber) isGetSockNameResponse_Result() {}
+
+func (*GetSockNameResponse_Address) isGetSockNameResponse_Result() {}
+
+func (m *GetSockNameResponse) GetResult() isGetSockNameResponse_Result {
+ if m != nil {
+ return m.Result
+ }
+ return nil
+}
+
+func (m *GetSockNameResponse) GetErrorNumber() uint32 {
+ if x, ok := m.GetResult().(*GetSockNameResponse_ErrorNumber); ok {
+ return x.ErrorNumber
+ }
+ return 0
+}
+
+func (m *GetSockNameResponse) GetAddress() *AddressResponse {
+ if x, ok := m.GetResult().(*GetSockNameResponse_Address); ok {
+ return x.Address
+ }
+ return nil
+}
+
+// XXX_OneofWrappers is for the internal use of the proto package.
+func (*GetSockNameResponse) XXX_OneofWrappers() []interface{} {
+ return []interface{}{
+ (*GetSockNameResponse_ErrorNumber)(nil),
+ (*GetSockNameResponse_Address)(nil),
+ }
+}
+
+type GetPeerNameRequest struct {
+ Fd uint32 `protobuf:"varint,1,opt,name=fd,proto3" json:"fd,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *GetPeerNameRequest) Reset() { *m = GetPeerNameRequest{} }
+func (m *GetPeerNameRequest) String() string { return proto.CompactTextString(m) }
+func (*GetPeerNameRequest) ProtoMessage() {}
+func (*GetPeerNameRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{35}
+}
+
+func (m *GetPeerNameRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_GetPeerNameRequest.Unmarshal(m, b)
+}
+func (m *GetPeerNameRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_GetPeerNameRequest.Marshal(b, m, deterministic)
+}
+func (m *GetPeerNameRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_GetPeerNameRequest.Merge(m, src)
+}
+func (m *GetPeerNameRequest) XXX_Size() int {
+ return xxx_messageInfo_GetPeerNameRequest.Size(m)
+}
+func (m *GetPeerNameRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_GetPeerNameRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_GetPeerNameRequest proto.InternalMessageInfo
+
+func (m *GetPeerNameRequest) GetFd() uint32 {
+ if m != nil {
+ return m.Fd
+ }
+ return 0
+}
+
+type GetPeerNameResponse struct {
+ // Types that are valid to be assigned to Result:
+ // *GetPeerNameResponse_ErrorNumber
+ // *GetPeerNameResponse_Address
+ Result isGetPeerNameResponse_Result `protobuf_oneof:"result"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *GetPeerNameResponse) Reset() { *m = GetPeerNameResponse{} }
+func (m *GetPeerNameResponse) String() string { return proto.CompactTextString(m) }
+func (*GetPeerNameResponse) ProtoMessage() {}
+func (*GetPeerNameResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{36}
+}
+
+func (m *GetPeerNameResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_GetPeerNameResponse.Unmarshal(m, b)
+}
+func (m *GetPeerNameResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_GetPeerNameResponse.Marshal(b, m, deterministic)
+}
+func (m *GetPeerNameResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_GetPeerNameResponse.Merge(m, src)
+}
+func (m *GetPeerNameResponse) XXX_Size() int {
+ return xxx_messageInfo_GetPeerNameResponse.Size(m)
+}
+func (m *GetPeerNameResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_GetPeerNameResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_GetPeerNameResponse proto.InternalMessageInfo
+
+type isGetPeerNameResponse_Result interface {
+ isGetPeerNameResponse_Result()
+}
+
+type GetPeerNameResponse_ErrorNumber struct {
+ ErrorNumber uint32 `protobuf:"varint,1,opt,name=error_number,json=errorNumber,proto3,oneof"`
+}
+
+type GetPeerNameResponse_Address struct {
+ Address *AddressResponse `protobuf:"bytes,2,opt,name=address,proto3,oneof"`
+}
+
+func (*GetPeerNameResponse_ErrorNumber) isGetPeerNameResponse_Result() {}
+
+func (*GetPeerNameResponse_Address) isGetPeerNameResponse_Result() {}
+
+func (m *GetPeerNameResponse) GetResult() isGetPeerNameResponse_Result {
+ if m != nil {
+ return m.Result
+ }
+ return nil
+}
+
+func (m *GetPeerNameResponse) GetErrorNumber() uint32 {
+ if x, ok := m.GetResult().(*GetPeerNameResponse_ErrorNumber); ok {
+ return x.ErrorNumber
+ }
+ return 0
+}
+
+func (m *GetPeerNameResponse) GetAddress() *AddressResponse {
+ if x, ok := m.GetResult().(*GetPeerNameResponse_Address); ok {
+ return x.Address
+ }
+ return nil
+}
+
+// XXX_OneofWrappers is for the internal use of the proto package.
+func (*GetPeerNameResponse) XXX_OneofWrappers() []interface{} {
+ return []interface{}{
+ (*GetPeerNameResponse_ErrorNumber)(nil),
+ (*GetPeerNameResponse_Address)(nil),
+ }
+}
+
+type SocketRequest struct {
+ Family int64 `protobuf:"varint,1,opt,name=family,proto3" json:"family,omitempty"`
+ Type int64 `protobuf:"varint,2,opt,name=type,proto3" json:"type,omitempty"`
+ Protocol int64 `protobuf:"varint,3,opt,name=protocol,proto3" json:"protocol,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *SocketRequest) Reset() { *m = SocketRequest{} }
+func (m *SocketRequest) String() string { return proto.CompactTextString(m) }
+func (*SocketRequest) ProtoMessage() {}
+func (*SocketRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{37}
+}
+
+func (m *SocketRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_SocketRequest.Unmarshal(m, b)
+}
+func (m *SocketRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_SocketRequest.Marshal(b, m, deterministic)
+}
+func (m *SocketRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_SocketRequest.Merge(m, src)
+}
+func (m *SocketRequest) XXX_Size() int {
+ return xxx_messageInfo_SocketRequest.Size(m)
+}
+func (m *SocketRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_SocketRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_SocketRequest proto.InternalMessageInfo
+
+func (m *SocketRequest) GetFamily() int64 {
+ if m != nil {
+ return m.Family
+ }
+ return 0
+}
+
+func (m *SocketRequest) GetType() int64 {
+ if m != nil {
+ return m.Type
+ }
+ return 0
+}
+
+func (m *SocketRequest) GetProtocol() int64 {
+ if m != nil {
+ return m.Protocol
+ }
+ return 0
+}
+
+type SocketResponse struct {
+ // Types that are valid to be assigned to Result:
+ // *SocketResponse_ErrorNumber
+ // *SocketResponse_Fd
+ Result isSocketResponse_Result `protobuf_oneof:"result"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *SocketResponse) Reset() { *m = SocketResponse{} }
+func (m *SocketResponse) String() string { return proto.CompactTextString(m) }
+func (*SocketResponse) ProtoMessage() {}
+func (*SocketResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{38}
+}
+
+func (m *SocketResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_SocketResponse.Unmarshal(m, b)
+}
+func (m *SocketResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_SocketResponse.Marshal(b, m, deterministic)
+}
+func (m *SocketResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_SocketResponse.Merge(m, src)
+}
+func (m *SocketResponse) XXX_Size() int {
+ return xxx_messageInfo_SocketResponse.Size(m)
+}
+func (m *SocketResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_SocketResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_SocketResponse proto.InternalMessageInfo
+
+type isSocketResponse_Result interface {
+ isSocketResponse_Result()
+}
+
+type SocketResponse_ErrorNumber struct {
+ ErrorNumber uint32 `protobuf:"varint,1,opt,name=error_number,json=errorNumber,proto3,oneof"`
+}
+
+type SocketResponse_Fd struct {
+ Fd uint32 `protobuf:"varint,2,opt,name=fd,proto3,oneof"`
+}
+
+func (*SocketResponse_ErrorNumber) isSocketResponse_Result() {}
+
+func (*SocketResponse_Fd) isSocketResponse_Result() {}
+
+func (m *SocketResponse) GetResult() isSocketResponse_Result {
+ if m != nil {
+ return m.Result
+ }
+ return nil
+}
+
+func (m *SocketResponse) GetErrorNumber() uint32 {
+ if x, ok := m.GetResult().(*SocketResponse_ErrorNumber); ok {
+ return x.ErrorNumber
+ }
+ return 0
+}
+
+func (m *SocketResponse) GetFd() uint32 {
+ if x, ok := m.GetResult().(*SocketResponse_Fd); ok {
+ return x.Fd
+ }
+ return 0
+}
+
+// XXX_OneofWrappers is for the internal use of the proto package.
+func (*SocketResponse) XXX_OneofWrappers() []interface{} {
+ return []interface{}{
+ (*SocketResponse_ErrorNumber)(nil),
+ (*SocketResponse_Fd)(nil),
+ }
+}
+
+type EpollWaitRequest struct {
+ Fd uint32 `protobuf:"varint,1,opt,name=fd,proto3" json:"fd,omitempty"`
+ NumEvents uint32 `protobuf:"varint,2,opt,name=num_events,json=numEvents,proto3" json:"num_events,omitempty"`
+ Msec int64 `protobuf:"zigzag64,3,opt,name=msec,proto3" json:"msec,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *EpollWaitRequest) Reset() { *m = EpollWaitRequest{} }
+func (m *EpollWaitRequest) String() string { return proto.CompactTextString(m) }
+func (*EpollWaitRequest) ProtoMessage() {}
+func (*EpollWaitRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{39}
+}
+
+func (m *EpollWaitRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_EpollWaitRequest.Unmarshal(m, b)
+}
+func (m *EpollWaitRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_EpollWaitRequest.Marshal(b, m, deterministic)
+}
+func (m *EpollWaitRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_EpollWaitRequest.Merge(m, src)
+}
+func (m *EpollWaitRequest) XXX_Size() int {
+ return xxx_messageInfo_EpollWaitRequest.Size(m)
+}
+func (m *EpollWaitRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_EpollWaitRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_EpollWaitRequest proto.InternalMessageInfo
+
+func (m *EpollWaitRequest) GetFd() uint32 {
+ if m != nil {
+ return m.Fd
+ }
+ return 0
+}
+
+func (m *EpollWaitRequest) GetNumEvents() uint32 {
+ if m != nil {
+ return m.NumEvents
+ }
+ return 0
+}
+
+func (m *EpollWaitRequest) GetMsec() int64 {
+ if m != nil {
+ return m.Msec
+ }
+ return 0
+}
+
+type EpollEvent struct {
+ Fd uint32 `protobuf:"varint,1,opt,name=fd,proto3" json:"fd,omitempty"`
+ Events uint32 `protobuf:"varint,2,opt,name=events,proto3" json:"events,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *EpollEvent) Reset() { *m = EpollEvent{} }
+func (m *EpollEvent) String() string { return proto.CompactTextString(m) }
+func (*EpollEvent) ProtoMessage() {}
+func (*EpollEvent) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{40}
+}
+
+func (m *EpollEvent) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_EpollEvent.Unmarshal(m, b)
+}
+func (m *EpollEvent) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_EpollEvent.Marshal(b, m, deterministic)
+}
+func (m *EpollEvent) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_EpollEvent.Merge(m, src)
+}
+func (m *EpollEvent) XXX_Size() int {
+ return xxx_messageInfo_EpollEvent.Size(m)
+}
+func (m *EpollEvent) XXX_DiscardUnknown() {
+ xxx_messageInfo_EpollEvent.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_EpollEvent proto.InternalMessageInfo
+
+func (m *EpollEvent) GetFd() uint32 {
+ if m != nil {
+ return m.Fd
+ }
+ return 0
+}
+
+func (m *EpollEvent) GetEvents() uint32 {
+ if m != nil {
+ return m.Events
+ }
+ return 0
+}
+
+type EpollEvents struct {
+ Events []*EpollEvent `protobuf:"bytes,1,rep,name=events,proto3" json:"events,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *EpollEvents) Reset() { *m = EpollEvents{} }
+func (m *EpollEvents) String() string { return proto.CompactTextString(m) }
+func (*EpollEvents) ProtoMessage() {}
+func (*EpollEvents) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{41}
+}
+
+func (m *EpollEvents) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_EpollEvents.Unmarshal(m, b)
+}
+func (m *EpollEvents) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_EpollEvents.Marshal(b, m, deterministic)
+}
+func (m *EpollEvents) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_EpollEvents.Merge(m, src)
+}
+func (m *EpollEvents) XXX_Size() int {
+ return xxx_messageInfo_EpollEvents.Size(m)
+}
+func (m *EpollEvents) XXX_DiscardUnknown() {
+ xxx_messageInfo_EpollEvents.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_EpollEvents proto.InternalMessageInfo
+
+func (m *EpollEvents) GetEvents() []*EpollEvent {
+ if m != nil {
+ return m.Events
+ }
+ return nil
+}
+
+type EpollWaitResponse struct {
+ // Types that are valid to be assigned to Result:
+ // *EpollWaitResponse_ErrorNumber
+ // *EpollWaitResponse_Events
+ Result isEpollWaitResponse_Result `protobuf_oneof:"result"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *EpollWaitResponse) Reset() { *m = EpollWaitResponse{} }
+func (m *EpollWaitResponse) String() string { return proto.CompactTextString(m) }
+func (*EpollWaitResponse) ProtoMessage() {}
+func (*EpollWaitResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{42}
+}
+
+func (m *EpollWaitResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_EpollWaitResponse.Unmarshal(m, b)
+}
+func (m *EpollWaitResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_EpollWaitResponse.Marshal(b, m, deterministic)
+}
+func (m *EpollWaitResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_EpollWaitResponse.Merge(m, src)
+}
+func (m *EpollWaitResponse) XXX_Size() int {
+ return xxx_messageInfo_EpollWaitResponse.Size(m)
+}
+func (m *EpollWaitResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_EpollWaitResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_EpollWaitResponse proto.InternalMessageInfo
+
+type isEpollWaitResponse_Result interface {
+ isEpollWaitResponse_Result()
+}
+
+type EpollWaitResponse_ErrorNumber struct {
+ ErrorNumber uint32 `protobuf:"varint,1,opt,name=error_number,json=errorNumber,proto3,oneof"`
+}
+
+type EpollWaitResponse_Events struct {
+ Events *EpollEvents `protobuf:"bytes,2,opt,name=events,proto3,oneof"`
+}
+
+func (*EpollWaitResponse_ErrorNumber) isEpollWaitResponse_Result() {}
+
+func (*EpollWaitResponse_Events) isEpollWaitResponse_Result() {}
+
+func (m *EpollWaitResponse) GetResult() isEpollWaitResponse_Result {
+ if m != nil {
+ return m.Result
+ }
+ return nil
+}
+
+func (m *EpollWaitResponse) GetErrorNumber() uint32 {
+ if x, ok := m.GetResult().(*EpollWaitResponse_ErrorNumber); ok {
+ return x.ErrorNumber
+ }
+ return 0
+}
+
+func (m *EpollWaitResponse) GetEvents() *EpollEvents {
+ if x, ok := m.GetResult().(*EpollWaitResponse_Events); ok {
+ return x.Events
+ }
+ return nil
+}
+
+// XXX_OneofWrappers is for the internal use of the proto package.
+func (*EpollWaitResponse) XXX_OneofWrappers() []interface{} {
+ return []interface{}{
+ (*EpollWaitResponse_ErrorNumber)(nil),
+ (*EpollWaitResponse_Events)(nil),
+ }
+}
+
+type EpollCtlRequest struct {
+ Epfd uint32 `protobuf:"varint,1,opt,name=epfd,proto3" json:"epfd,omitempty"`
+ Op int64 `protobuf:"varint,2,opt,name=op,proto3" json:"op,omitempty"`
+ Fd uint32 `protobuf:"varint,3,opt,name=fd,proto3" json:"fd,omitempty"`
+ Event *EpollEvent `protobuf:"bytes,4,opt,name=event,proto3" json:"event,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *EpollCtlRequest) Reset() { *m = EpollCtlRequest{} }
+func (m *EpollCtlRequest) String() string { return proto.CompactTextString(m) }
+func (*EpollCtlRequest) ProtoMessage() {}
+func (*EpollCtlRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{43}
+}
+
+func (m *EpollCtlRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_EpollCtlRequest.Unmarshal(m, b)
+}
+func (m *EpollCtlRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_EpollCtlRequest.Marshal(b, m, deterministic)
+}
+func (m *EpollCtlRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_EpollCtlRequest.Merge(m, src)
+}
+func (m *EpollCtlRequest) XXX_Size() int {
+ return xxx_messageInfo_EpollCtlRequest.Size(m)
+}
+func (m *EpollCtlRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_EpollCtlRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_EpollCtlRequest proto.InternalMessageInfo
+
+func (m *EpollCtlRequest) GetEpfd() uint32 {
+ if m != nil {
+ return m.Epfd
+ }
+ return 0
+}
+
+func (m *EpollCtlRequest) GetOp() int64 {
+ if m != nil {
+ return m.Op
+ }
+ return 0
+}
+
+func (m *EpollCtlRequest) GetFd() uint32 {
+ if m != nil {
+ return m.Fd
+ }
+ return 0
+}
+
+func (m *EpollCtlRequest) GetEvent() *EpollEvent {
+ if m != nil {
+ return m.Event
+ }
+ return nil
+}
+
+type EpollCtlResponse struct {
+ ErrorNumber uint32 `protobuf:"varint,1,opt,name=error_number,json=errorNumber,proto3" json:"error_number,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *EpollCtlResponse) Reset() { *m = EpollCtlResponse{} }
+func (m *EpollCtlResponse) String() string { return proto.CompactTextString(m) }
+func (*EpollCtlResponse) ProtoMessage() {}
+func (*EpollCtlResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{44}
+}
+
+func (m *EpollCtlResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_EpollCtlResponse.Unmarshal(m, b)
+}
+func (m *EpollCtlResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_EpollCtlResponse.Marshal(b, m, deterministic)
+}
+func (m *EpollCtlResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_EpollCtlResponse.Merge(m, src)
+}
+func (m *EpollCtlResponse) XXX_Size() int {
+ return xxx_messageInfo_EpollCtlResponse.Size(m)
+}
+func (m *EpollCtlResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_EpollCtlResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_EpollCtlResponse proto.InternalMessageInfo
+
+func (m *EpollCtlResponse) GetErrorNumber() uint32 {
+ if m != nil {
+ return m.ErrorNumber
+ }
+ return 0
+}
+
+type EpollCreate1Request struct {
+ Flag int64 `protobuf:"varint,1,opt,name=flag,proto3" json:"flag,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *EpollCreate1Request) Reset() { *m = EpollCreate1Request{} }
+func (m *EpollCreate1Request) String() string { return proto.CompactTextString(m) }
+func (*EpollCreate1Request) ProtoMessage() {}
+func (*EpollCreate1Request) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{45}
+}
+
+func (m *EpollCreate1Request) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_EpollCreate1Request.Unmarshal(m, b)
+}
+func (m *EpollCreate1Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_EpollCreate1Request.Marshal(b, m, deterministic)
+}
+func (m *EpollCreate1Request) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_EpollCreate1Request.Merge(m, src)
+}
+func (m *EpollCreate1Request) XXX_Size() int {
+ return xxx_messageInfo_EpollCreate1Request.Size(m)
+}
+func (m *EpollCreate1Request) XXX_DiscardUnknown() {
+ xxx_messageInfo_EpollCreate1Request.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_EpollCreate1Request proto.InternalMessageInfo
+
+func (m *EpollCreate1Request) GetFlag() int64 {
+ if m != nil {
+ return m.Flag
+ }
+ return 0
+}
+
+type EpollCreate1Response struct {
+ // Types that are valid to be assigned to Result:
+ // *EpollCreate1Response_ErrorNumber
+ // *EpollCreate1Response_Fd
+ Result isEpollCreate1Response_Result `protobuf_oneof:"result"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *EpollCreate1Response) Reset() { *m = EpollCreate1Response{} }
+func (m *EpollCreate1Response) String() string { return proto.CompactTextString(m) }
+func (*EpollCreate1Response) ProtoMessage() {}
+func (*EpollCreate1Response) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{46}
+}
+
+func (m *EpollCreate1Response) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_EpollCreate1Response.Unmarshal(m, b)
+}
+func (m *EpollCreate1Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_EpollCreate1Response.Marshal(b, m, deterministic)
+}
+func (m *EpollCreate1Response) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_EpollCreate1Response.Merge(m, src)
+}
+func (m *EpollCreate1Response) XXX_Size() int {
+ return xxx_messageInfo_EpollCreate1Response.Size(m)
+}
+func (m *EpollCreate1Response) XXX_DiscardUnknown() {
+ xxx_messageInfo_EpollCreate1Response.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_EpollCreate1Response proto.InternalMessageInfo
+
+type isEpollCreate1Response_Result interface {
+ isEpollCreate1Response_Result()
+}
+
+type EpollCreate1Response_ErrorNumber struct {
+ ErrorNumber uint32 `protobuf:"varint,1,opt,name=error_number,json=errorNumber,proto3,oneof"`
+}
+
+type EpollCreate1Response_Fd struct {
+ Fd uint32 `protobuf:"varint,2,opt,name=fd,proto3,oneof"`
+}
+
+func (*EpollCreate1Response_ErrorNumber) isEpollCreate1Response_Result() {}
+
+func (*EpollCreate1Response_Fd) isEpollCreate1Response_Result() {}
+
+func (m *EpollCreate1Response) GetResult() isEpollCreate1Response_Result {
+ if m != nil {
+ return m.Result
+ }
+ return nil
+}
+
+func (m *EpollCreate1Response) GetErrorNumber() uint32 {
+ if x, ok := m.GetResult().(*EpollCreate1Response_ErrorNumber); ok {
+ return x.ErrorNumber
+ }
+ return 0
+}
+
+func (m *EpollCreate1Response) GetFd() uint32 {
+ if x, ok := m.GetResult().(*EpollCreate1Response_Fd); ok {
+ return x.Fd
+ }
+ return 0
+}
+
+// XXX_OneofWrappers is for the internal use of the proto package.
+func (*EpollCreate1Response) XXX_OneofWrappers() []interface{} {
+ return []interface{}{
+ (*EpollCreate1Response_ErrorNumber)(nil),
+ (*EpollCreate1Response_Fd)(nil),
+ }
+}
+
+type PollRequest struct {
+ Fd uint32 `protobuf:"varint,1,opt,name=fd,proto3" json:"fd,omitempty"`
+ Events uint32 `protobuf:"varint,2,opt,name=events,proto3" json:"events,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *PollRequest) Reset() { *m = PollRequest{} }
+func (m *PollRequest) String() string { return proto.CompactTextString(m) }
+func (*PollRequest) ProtoMessage() {}
+func (*PollRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{47}
+}
+
+func (m *PollRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_PollRequest.Unmarshal(m, b)
+}
+func (m *PollRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_PollRequest.Marshal(b, m, deterministic)
+}
+func (m *PollRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_PollRequest.Merge(m, src)
+}
+func (m *PollRequest) XXX_Size() int {
+ return xxx_messageInfo_PollRequest.Size(m)
+}
+func (m *PollRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_PollRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_PollRequest proto.InternalMessageInfo
+
+func (m *PollRequest) GetFd() uint32 {
+ if m != nil {
+ return m.Fd
+ }
+ return 0
+}
+
+func (m *PollRequest) GetEvents() uint32 {
+ if m != nil {
+ return m.Events
+ }
+ return 0
+}
+
+type PollResponse struct {
+ // Types that are valid to be assigned to Result:
+ // *PollResponse_ErrorNumber
+ // *PollResponse_Events
+ Result isPollResponse_Result `protobuf_oneof:"result"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *PollResponse) Reset() { *m = PollResponse{} }
+func (m *PollResponse) String() string { return proto.CompactTextString(m) }
+func (*PollResponse) ProtoMessage() {}
+func (*PollResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{48}
+}
+
+func (m *PollResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_PollResponse.Unmarshal(m, b)
+}
+func (m *PollResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_PollResponse.Marshal(b, m, deterministic)
+}
+func (m *PollResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_PollResponse.Merge(m, src)
+}
+func (m *PollResponse) XXX_Size() int {
+ return xxx_messageInfo_PollResponse.Size(m)
+}
+func (m *PollResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_PollResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_PollResponse proto.InternalMessageInfo
+
+type isPollResponse_Result interface {
+ isPollResponse_Result()
+}
+
+type PollResponse_ErrorNumber struct {
+ ErrorNumber uint32 `protobuf:"varint,1,opt,name=error_number,json=errorNumber,proto3,oneof"`
+}
+
+type PollResponse_Events struct {
+ Events uint32 `protobuf:"varint,2,opt,name=events,proto3,oneof"`
+}
+
+func (*PollResponse_ErrorNumber) isPollResponse_Result() {}
+
+func (*PollResponse_Events) isPollResponse_Result() {}
+
+func (m *PollResponse) GetResult() isPollResponse_Result {
+ if m != nil {
+ return m.Result
+ }
+ return nil
+}
+
+func (m *PollResponse) GetErrorNumber() uint32 {
+ if x, ok := m.GetResult().(*PollResponse_ErrorNumber); ok {
+ return x.ErrorNumber
+ }
+ return 0
+}
+
+func (m *PollResponse) GetEvents() uint32 {
+ if x, ok := m.GetResult().(*PollResponse_Events); ok {
+ return x.Events
+ }
+ return 0
+}
+
+// XXX_OneofWrappers is for the internal use of the proto package.
+func (*PollResponse) XXX_OneofWrappers() []interface{} {
+ return []interface{}{
+ (*PollResponse_ErrorNumber)(nil),
+ (*PollResponse_Events)(nil),
+ }
+}
+
+type SyscallRequest struct {
+ // Types that are valid to be assigned to Args:
+ // *SyscallRequest_Socket
+ // *SyscallRequest_Sendmsg
+ // *SyscallRequest_Recvmsg
+ // *SyscallRequest_Bind
+ // *SyscallRequest_Accept
+ // *SyscallRequest_Connect
+ // *SyscallRequest_Listen
+ // *SyscallRequest_Shutdown
+ // *SyscallRequest_Close
+ // *SyscallRequest_GetSockOpt
+ // *SyscallRequest_SetSockOpt
+ // *SyscallRequest_GetSockName
+ // *SyscallRequest_GetPeerName
+ // *SyscallRequest_EpollWait
+ // *SyscallRequest_EpollCtl
+ // *SyscallRequest_EpollCreate1
+ // *SyscallRequest_Poll
+ // *SyscallRequest_Read
+ // *SyscallRequest_Write
+ // *SyscallRequest_Open
+ // *SyscallRequest_Ioctl
+ // *SyscallRequest_WriteFile
+ // *SyscallRequest_ReadFile
+ Args isSyscallRequest_Args `protobuf_oneof:"args"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *SyscallRequest) Reset() { *m = SyscallRequest{} }
+func (m *SyscallRequest) String() string { return proto.CompactTextString(m) }
+func (*SyscallRequest) ProtoMessage() {}
+func (*SyscallRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{49}
+}
+
+func (m *SyscallRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_SyscallRequest.Unmarshal(m, b)
+}
+func (m *SyscallRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_SyscallRequest.Marshal(b, m, deterministic)
+}
+func (m *SyscallRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_SyscallRequest.Merge(m, src)
+}
+func (m *SyscallRequest) XXX_Size() int {
+ return xxx_messageInfo_SyscallRequest.Size(m)
+}
+func (m *SyscallRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_SyscallRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_SyscallRequest proto.InternalMessageInfo
+
+type isSyscallRequest_Args interface {
+ isSyscallRequest_Args()
+}
+
+type SyscallRequest_Socket struct {
+ Socket *SocketRequest `protobuf:"bytes,1,opt,name=socket,proto3,oneof"`
+}
+
+type SyscallRequest_Sendmsg struct {
+ Sendmsg *SendmsgRequest `protobuf:"bytes,2,opt,name=sendmsg,proto3,oneof"`
+}
+
+type SyscallRequest_Recvmsg struct {
+ Recvmsg *RecvmsgRequest `protobuf:"bytes,3,opt,name=recvmsg,proto3,oneof"`
+}
+
+type SyscallRequest_Bind struct {
+ Bind *BindRequest `protobuf:"bytes,4,opt,name=bind,proto3,oneof"`
+}
+
+type SyscallRequest_Accept struct {
+ Accept *AcceptRequest `protobuf:"bytes,5,opt,name=accept,proto3,oneof"`
+}
+
+type SyscallRequest_Connect struct {
+ Connect *ConnectRequest `protobuf:"bytes,6,opt,name=connect,proto3,oneof"`
+}
+
+type SyscallRequest_Listen struct {
+ Listen *ListenRequest `protobuf:"bytes,7,opt,name=listen,proto3,oneof"`
+}
+
+type SyscallRequest_Shutdown struct {
+ Shutdown *ShutdownRequest `protobuf:"bytes,8,opt,name=shutdown,proto3,oneof"`
+}
+
+type SyscallRequest_Close struct {
+ Close *CloseRequest `protobuf:"bytes,9,opt,name=close,proto3,oneof"`
+}
+
+type SyscallRequest_GetSockOpt struct {
+ GetSockOpt *GetSockOptRequest `protobuf:"bytes,10,opt,name=get_sock_opt,json=getSockOpt,proto3,oneof"`
+}
+
+type SyscallRequest_SetSockOpt struct {
+ SetSockOpt *SetSockOptRequest `protobuf:"bytes,11,opt,name=set_sock_opt,json=setSockOpt,proto3,oneof"`
+}
+
+type SyscallRequest_GetSockName struct {
+ GetSockName *GetSockNameRequest `protobuf:"bytes,12,opt,name=get_sock_name,json=getSockName,proto3,oneof"`
+}
+
+type SyscallRequest_GetPeerName struct {
+ GetPeerName *GetPeerNameRequest `protobuf:"bytes,13,opt,name=get_peer_name,json=getPeerName,proto3,oneof"`
+}
+
+type SyscallRequest_EpollWait struct {
+ EpollWait *EpollWaitRequest `protobuf:"bytes,14,opt,name=epoll_wait,json=epollWait,proto3,oneof"`
+}
+
+type SyscallRequest_EpollCtl struct {
+ EpollCtl *EpollCtlRequest `protobuf:"bytes,15,opt,name=epoll_ctl,json=epollCtl,proto3,oneof"`
+}
+
+type SyscallRequest_EpollCreate1 struct {
+ EpollCreate1 *EpollCreate1Request `protobuf:"bytes,16,opt,name=epoll_create1,json=epollCreate1,proto3,oneof"`
+}
+
+type SyscallRequest_Poll struct {
+ Poll *PollRequest `protobuf:"bytes,17,opt,name=poll,proto3,oneof"`
+}
+
+type SyscallRequest_Read struct {
+ Read *ReadRequest `protobuf:"bytes,18,opt,name=read,proto3,oneof"`
+}
+
+type SyscallRequest_Write struct {
+ Write *WriteRequest `protobuf:"bytes,19,opt,name=write,proto3,oneof"`
+}
+
+type SyscallRequest_Open struct {
+ Open *OpenRequest `protobuf:"bytes,20,opt,name=open,proto3,oneof"`
+}
+
+type SyscallRequest_Ioctl struct {
+ Ioctl *IOCtlRequest `protobuf:"bytes,21,opt,name=ioctl,proto3,oneof"`
+}
+
+type SyscallRequest_WriteFile struct {
+ WriteFile *WriteFileRequest `protobuf:"bytes,22,opt,name=write_file,json=writeFile,proto3,oneof"`
+}
+
+type SyscallRequest_ReadFile struct {
+ ReadFile *ReadFileRequest `protobuf:"bytes,23,opt,name=read_file,json=readFile,proto3,oneof"`
+}
+
+func (*SyscallRequest_Socket) isSyscallRequest_Args() {}
+
+func (*SyscallRequest_Sendmsg) isSyscallRequest_Args() {}
+
+func (*SyscallRequest_Recvmsg) isSyscallRequest_Args() {}
+
+func (*SyscallRequest_Bind) isSyscallRequest_Args() {}
+
+func (*SyscallRequest_Accept) isSyscallRequest_Args() {}
+
+func (*SyscallRequest_Connect) isSyscallRequest_Args() {}
+
+func (*SyscallRequest_Listen) isSyscallRequest_Args() {}
+
+func (*SyscallRequest_Shutdown) isSyscallRequest_Args() {}
+
+func (*SyscallRequest_Close) isSyscallRequest_Args() {}
+
+func (*SyscallRequest_GetSockOpt) isSyscallRequest_Args() {}
+
+func (*SyscallRequest_SetSockOpt) isSyscallRequest_Args() {}
+
+func (*SyscallRequest_GetSockName) isSyscallRequest_Args() {}
+
+func (*SyscallRequest_GetPeerName) isSyscallRequest_Args() {}
+
+func (*SyscallRequest_EpollWait) isSyscallRequest_Args() {}
+
+func (*SyscallRequest_EpollCtl) isSyscallRequest_Args() {}
+
+func (*SyscallRequest_EpollCreate1) isSyscallRequest_Args() {}
+
+func (*SyscallRequest_Poll) isSyscallRequest_Args() {}
+
+func (*SyscallRequest_Read) isSyscallRequest_Args() {}
+
+func (*SyscallRequest_Write) isSyscallRequest_Args() {}
+
+func (*SyscallRequest_Open) isSyscallRequest_Args() {}
+
+func (*SyscallRequest_Ioctl) isSyscallRequest_Args() {}
+
+func (*SyscallRequest_WriteFile) isSyscallRequest_Args() {}
+
+func (*SyscallRequest_ReadFile) isSyscallRequest_Args() {}
+
+func (m *SyscallRequest) GetArgs() isSyscallRequest_Args {
+ if m != nil {
+ return m.Args
+ }
+ return nil
+}
+
+func (m *SyscallRequest) GetSocket() *SocketRequest {
+ if x, ok := m.GetArgs().(*SyscallRequest_Socket); ok {
+ return x.Socket
+ }
+ return nil
+}
+
+func (m *SyscallRequest) GetSendmsg() *SendmsgRequest {
+ if x, ok := m.GetArgs().(*SyscallRequest_Sendmsg); ok {
+ return x.Sendmsg
+ }
+ return nil
+}
+
+func (m *SyscallRequest) GetRecvmsg() *RecvmsgRequest {
+ if x, ok := m.GetArgs().(*SyscallRequest_Recvmsg); ok {
+ return x.Recvmsg
+ }
+ return nil
+}
+
+func (m *SyscallRequest) GetBind() *BindRequest {
+ if x, ok := m.GetArgs().(*SyscallRequest_Bind); ok {
+ return x.Bind
+ }
+ return nil
+}
+
+func (m *SyscallRequest) GetAccept() *AcceptRequest {
+ if x, ok := m.GetArgs().(*SyscallRequest_Accept); ok {
+ return x.Accept
+ }
+ return nil
+}
+
+func (m *SyscallRequest) GetConnect() *ConnectRequest {
+ if x, ok := m.GetArgs().(*SyscallRequest_Connect); ok {
+ return x.Connect
+ }
+ return nil
+}
+
+func (m *SyscallRequest) GetListen() *ListenRequest {
+ if x, ok := m.GetArgs().(*SyscallRequest_Listen); ok {
+ return x.Listen
+ }
+ return nil
+}
+
+func (m *SyscallRequest) GetShutdown() *ShutdownRequest {
+ if x, ok := m.GetArgs().(*SyscallRequest_Shutdown); ok {
+ return x.Shutdown
+ }
+ return nil
+}
+
+func (m *SyscallRequest) GetClose() *CloseRequest {
+ if x, ok := m.GetArgs().(*SyscallRequest_Close); ok {
+ return x.Close
+ }
+ return nil
+}
+
+func (m *SyscallRequest) GetGetSockOpt() *GetSockOptRequest {
+ if x, ok := m.GetArgs().(*SyscallRequest_GetSockOpt); ok {
+ return x.GetSockOpt
+ }
+ return nil
+}
+
+func (m *SyscallRequest) GetSetSockOpt() *SetSockOptRequest {
+ if x, ok := m.GetArgs().(*SyscallRequest_SetSockOpt); ok {
+ return x.SetSockOpt
+ }
+ return nil
+}
+
+func (m *SyscallRequest) GetGetSockName() *GetSockNameRequest {
+ if x, ok := m.GetArgs().(*SyscallRequest_GetSockName); ok {
+ return x.GetSockName
+ }
+ return nil
+}
+
+func (m *SyscallRequest) GetGetPeerName() *GetPeerNameRequest {
+ if x, ok := m.GetArgs().(*SyscallRequest_GetPeerName); ok {
+ return x.GetPeerName
+ }
+ return nil
+}
+
+func (m *SyscallRequest) GetEpollWait() *EpollWaitRequest {
+ if x, ok := m.GetArgs().(*SyscallRequest_EpollWait); ok {
+ return x.EpollWait
+ }
+ return nil
+}
+
+func (m *SyscallRequest) GetEpollCtl() *EpollCtlRequest {
+ if x, ok := m.GetArgs().(*SyscallRequest_EpollCtl); ok {
+ return x.EpollCtl
+ }
+ return nil
+}
+
+func (m *SyscallRequest) GetEpollCreate1() *EpollCreate1Request {
+ if x, ok := m.GetArgs().(*SyscallRequest_EpollCreate1); ok {
+ return x.EpollCreate1
+ }
+ return nil
+}
+
+func (m *SyscallRequest) GetPoll() *PollRequest {
+ if x, ok := m.GetArgs().(*SyscallRequest_Poll); ok {
+ return x.Poll
+ }
+ return nil
+}
+
+func (m *SyscallRequest) GetRead() *ReadRequest {
+ if x, ok := m.GetArgs().(*SyscallRequest_Read); ok {
+ return x.Read
+ }
+ return nil
+}
+
+func (m *SyscallRequest) GetWrite() *WriteRequest {
+ if x, ok := m.GetArgs().(*SyscallRequest_Write); ok {
+ return x.Write
+ }
+ return nil
+}
+
+func (m *SyscallRequest) GetOpen() *OpenRequest {
+ if x, ok := m.GetArgs().(*SyscallRequest_Open); ok {
+ return x.Open
+ }
+ return nil
+}
+
+func (m *SyscallRequest) GetIoctl() *IOCtlRequest {
+ if x, ok := m.GetArgs().(*SyscallRequest_Ioctl); ok {
+ return x.Ioctl
+ }
+ return nil
+}
+
+func (m *SyscallRequest) GetWriteFile() *WriteFileRequest {
+ if x, ok := m.GetArgs().(*SyscallRequest_WriteFile); ok {
+ return x.WriteFile
+ }
+ return nil
+}
+
+func (m *SyscallRequest) GetReadFile() *ReadFileRequest {
+ if x, ok := m.GetArgs().(*SyscallRequest_ReadFile); ok {
+ return x.ReadFile
+ }
+ return nil
+}
+
+// XXX_OneofWrappers is for the internal use of the proto package.
+func (*SyscallRequest) XXX_OneofWrappers() []interface{} {
+ return []interface{}{
+ (*SyscallRequest_Socket)(nil),
+ (*SyscallRequest_Sendmsg)(nil),
+ (*SyscallRequest_Recvmsg)(nil),
+ (*SyscallRequest_Bind)(nil),
+ (*SyscallRequest_Accept)(nil),
+ (*SyscallRequest_Connect)(nil),
+ (*SyscallRequest_Listen)(nil),
+ (*SyscallRequest_Shutdown)(nil),
+ (*SyscallRequest_Close)(nil),
+ (*SyscallRequest_GetSockOpt)(nil),
+ (*SyscallRequest_SetSockOpt)(nil),
+ (*SyscallRequest_GetSockName)(nil),
+ (*SyscallRequest_GetPeerName)(nil),
+ (*SyscallRequest_EpollWait)(nil),
+ (*SyscallRequest_EpollCtl)(nil),
+ (*SyscallRequest_EpollCreate1)(nil),
+ (*SyscallRequest_Poll)(nil),
+ (*SyscallRequest_Read)(nil),
+ (*SyscallRequest_Write)(nil),
+ (*SyscallRequest_Open)(nil),
+ (*SyscallRequest_Ioctl)(nil),
+ (*SyscallRequest_WriteFile)(nil),
+ (*SyscallRequest_ReadFile)(nil),
+ }
+}
+
+type SyscallResponse struct {
+ // Types that are valid to be assigned to Result:
+ // *SyscallResponse_Socket
+ // *SyscallResponse_Sendmsg
+ // *SyscallResponse_Recvmsg
+ // *SyscallResponse_Bind
+ // *SyscallResponse_Accept
+ // *SyscallResponse_Connect
+ // *SyscallResponse_Listen
+ // *SyscallResponse_Shutdown
+ // *SyscallResponse_Close
+ // *SyscallResponse_GetSockOpt
+ // *SyscallResponse_SetSockOpt
+ // *SyscallResponse_GetSockName
+ // *SyscallResponse_GetPeerName
+ // *SyscallResponse_EpollWait
+ // *SyscallResponse_EpollCtl
+ // *SyscallResponse_EpollCreate1
+ // *SyscallResponse_Poll
+ // *SyscallResponse_Read
+ // *SyscallResponse_Write
+ // *SyscallResponse_Open
+ // *SyscallResponse_Ioctl
+ // *SyscallResponse_WriteFile
+ // *SyscallResponse_ReadFile
+ Result isSyscallResponse_Result `protobuf_oneof:"result"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *SyscallResponse) Reset() { *m = SyscallResponse{} }
+func (m *SyscallResponse) String() string { return proto.CompactTextString(m) }
+func (*SyscallResponse) ProtoMessage() {}
+func (*SyscallResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_dd04f3a8f0c5288b, []int{50}
+}
+
+func (m *SyscallResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_SyscallResponse.Unmarshal(m, b)
+}
+func (m *SyscallResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_SyscallResponse.Marshal(b, m, deterministic)
+}
+func (m *SyscallResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_SyscallResponse.Merge(m, src)
+}
+func (m *SyscallResponse) XXX_Size() int {
+ return xxx_messageInfo_SyscallResponse.Size(m)
+}
+func (m *SyscallResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_SyscallResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_SyscallResponse proto.InternalMessageInfo
+
+type isSyscallResponse_Result interface {
+ isSyscallResponse_Result()
+}
+
+type SyscallResponse_Socket struct {
+ Socket *SocketResponse `protobuf:"bytes,1,opt,name=socket,proto3,oneof"`
+}
+
+type SyscallResponse_Sendmsg struct {
+ Sendmsg *SendmsgResponse `protobuf:"bytes,2,opt,name=sendmsg,proto3,oneof"`
+}
+
+type SyscallResponse_Recvmsg struct {
+ Recvmsg *RecvmsgResponse `protobuf:"bytes,3,opt,name=recvmsg,proto3,oneof"`
+}
+
+type SyscallResponse_Bind struct {
+ Bind *BindResponse `protobuf:"bytes,4,opt,name=bind,proto3,oneof"`
+}
+
+type SyscallResponse_Accept struct {
+ Accept *AcceptResponse `protobuf:"bytes,5,opt,name=accept,proto3,oneof"`
+}
+
+type SyscallResponse_Connect struct {
+ Connect *ConnectResponse `protobuf:"bytes,6,opt,name=connect,proto3,oneof"`
+}
+
+type SyscallResponse_Listen struct {
+ Listen *ListenResponse `protobuf:"bytes,7,opt,name=listen,proto3,oneof"`
+}
+
+type SyscallResponse_Shutdown struct {
+ Shutdown *ShutdownResponse `protobuf:"bytes,8,opt,name=shutdown,proto3,oneof"`
+}
+
+type SyscallResponse_Close struct {
+ Close *CloseResponse `protobuf:"bytes,9,opt,name=close,proto3,oneof"`
+}
+
+type SyscallResponse_GetSockOpt struct {
+ GetSockOpt *GetSockOptResponse `protobuf:"bytes,10,opt,name=get_sock_opt,json=getSockOpt,proto3,oneof"`
+}
+
+type SyscallResponse_SetSockOpt struct {
+ SetSockOpt *SetSockOptResponse `protobuf:"bytes,11,opt,name=set_sock_opt,json=setSockOpt,proto3,oneof"`
+}
+
+type SyscallResponse_GetSockName struct {
+ GetSockName *GetSockNameResponse `protobuf:"bytes,12,opt,name=get_sock_name,json=getSockName,proto3,oneof"`
+}
+
+type SyscallResponse_GetPeerName struct {
+ GetPeerName *GetPeerNameResponse `protobuf:"bytes,13,opt,name=get_peer_name,json=getPeerName,proto3,oneof"`
+}
+
+type SyscallResponse_EpollWait struct {
+ EpollWait *EpollWaitResponse `protobuf:"bytes,14,opt,name=epoll_wait,json=epollWait,proto3,oneof"`
+}
+
+type SyscallResponse_EpollCtl struct {
+ EpollCtl *EpollCtlResponse `protobuf:"bytes,15,opt,name=epoll_ctl,json=epollCtl,proto3,oneof"`
+}
+
+type SyscallResponse_EpollCreate1 struct {
+ EpollCreate1 *EpollCreate1Response `protobuf:"bytes,16,opt,name=epoll_create1,json=epollCreate1,proto3,oneof"`
+}
+
+type SyscallResponse_Poll struct {
+ Poll *PollResponse `protobuf:"bytes,17,opt,name=poll,proto3,oneof"`
+}
+
+type SyscallResponse_Read struct {
+ Read *ReadResponse `protobuf:"bytes,18,opt,name=read,proto3,oneof"`
+}
+
+type SyscallResponse_Write struct {
+ Write *WriteResponse `protobuf:"bytes,19,opt,name=write,proto3,oneof"`
+}
+
+type SyscallResponse_Open struct {
+ Open *OpenResponse `protobuf:"bytes,20,opt,name=open,proto3,oneof"`
+}
+
+type SyscallResponse_Ioctl struct {
+ Ioctl *IOCtlResponse `protobuf:"bytes,21,opt,name=ioctl,proto3,oneof"`
+}
+
+type SyscallResponse_WriteFile struct {
+ WriteFile *WriteFileResponse `protobuf:"bytes,22,opt,name=write_file,json=writeFile,proto3,oneof"`
+}
+
+type SyscallResponse_ReadFile struct {
+ ReadFile *ReadFileResponse `protobuf:"bytes,23,opt,name=read_file,json=readFile,proto3,oneof"`
+}
+
+func (*SyscallResponse_Socket) isSyscallResponse_Result() {}
+
+func (*SyscallResponse_Sendmsg) isSyscallResponse_Result() {}
+
+func (*SyscallResponse_Recvmsg) isSyscallResponse_Result() {}
+
+func (*SyscallResponse_Bind) isSyscallResponse_Result() {}
+
+func (*SyscallResponse_Accept) isSyscallResponse_Result() {}
+
+func (*SyscallResponse_Connect) isSyscallResponse_Result() {}
+
+func (*SyscallResponse_Listen) isSyscallResponse_Result() {}
+
+func (*SyscallResponse_Shutdown) isSyscallResponse_Result() {}
+
+func (*SyscallResponse_Close) isSyscallResponse_Result() {}
+
+func (*SyscallResponse_GetSockOpt) isSyscallResponse_Result() {}
+
+func (*SyscallResponse_SetSockOpt) isSyscallResponse_Result() {}
+
+func (*SyscallResponse_GetSockName) isSyscallResponse_Result() {}
+
+func (*SyscallResponse_GetPeerName) isSyscallResponse_Result() {}
+
+func (*SyscallResponse_EpollWait) isSyscallResponse_Result() {}
+
+func (*SyscallResponse_EpollCtl) isSyscallResponse_Result() {}
+
+func (*SyscallResponse_EpollCreate1) isSyscallResponse_Result() {}
+
+func (*SyscallResponse_Poll) isSyscallResponse_Result() {}
+
+func (*SyscallResponse_Read) isSyscallResponse_Result() {}
+
+func (*SyscallResponse_Write) isSyscallResponse_Result() {}
+
+func (*SyscallResponse_Open) isSyscallResponse_Result() {}
+
+func (*SyscallResponse_Ioctl) isSyscallResponse_Result() {}
+
+func (*SyscallResponse_WriteFile) isSyscallResponse_Result() {}
+
+func (*SyscallResponse_ReadFile) isSyscallResponse_Result() {}
+
+func (m *SyscallResponse) GetResult() isSyscallResponse_Result {
+ if m != nil {
+ return m.Result
+ }
+ return nil
+}
+
+func (m *SyscallResponse) GetSocket() *SocketResponse {
+ if x, ok := m.GetResult().(*SyscallResponse_Socket); ok {
+ return x.Socket
+ }
+ return nil
+}
+
+func (m *SyscallResponse) GetSendmsg() *SendmsgResponse {
+ if x, ok := m.GetResult().(*SyscallResponse_Sendmsg); ok {
+ return x.Sendmsg
+ }
+ return nil
+}
+
+func (m *SyscallResponse) GetRecvmsg() *RecvmsgResponse {
+ if x, ok := m.GetResult().(*SyscallResponse_Recvmsg); ok {
+ return x.Recvmsg
+ }
+ return nil
+}
+
+func (m *SyscallResponse) GetBind() *BindResponse {
+ if x, ok := m.GetResult().(*SyscallResponse_Bind); ok {
+ return x.Bind
+ }
+ return nil
+}
+
+func (m *SyscallResponse) GetAccept() *AcceptResponse {
+ if x, ok := m.GetResult().(*SyscallResponse_Accept); ok {
+ return x.Accept
+ }
+ return nil
+}
+
+func (m *SyscallResponse) GetConnect() *ConnectResponse {
+ if x, ok := m.GetResult().(*SyscallResponse_Connect); ok {
+ return x.Connect
+ }
+ return nil
+}
+
+func (m *SyscallResponse) GetListen() *ListenResponse {
+ if x, ok := m.GetResult().(*SyscallResponse_Listen); ok {
+ return x.Listen
+ }
+ return nil
+}
+
+func (m *SyscallResponse) GetShutdown() *ShutdownResponse {
+ if x, ok := m.GetResult().(*SyscallResponse_Shutdown); ok {
+ return x.Shutdown
+ }
+ return nil
+}
+
+func (m *SyscallResponse) GetClose() *CloseResponse {
+ if x, ok := m.GetResult().(*SyscallResponse_Close); ok {
+ return x.Close
+ }
+ return nil
+}
+
+func (m *SyscallResponse) GetGetSockOpt() *GetSockOptResponse {
+ if x, ok := m.GetResult().(*SyscallResponse_GetSockOpt); ok {
+ return x.GetSockOpt
+ }
+ return nil
+}
+
+func (m *SyscallResponse) GetSetSockOpt() *SetSockOptResponse {
+ if x, ok := m.GetResult().(*SyscallResponse_SetSockOpt); ok {
+ return x.SetSockOpt
+ }
+ return nil
+}
+
+func (m *SyscallResponse) GetGetSockName() *GetSockNameResponse {
+ if x, ok := m.GetResult().(*SyscallResponse_GetSockName); ok {
+ return x.GetSockName
+ }
+ return nil
+}
+
+func (m *SyscallResponse) GetGetPeerName() *GetPeerNameResponse {
+ if x, ok := m.GetResult().(*SyscallResponse_GetPeerName); ok {
+ return x.GetPeerName
+ }
+ return nil
+}
+
+func (m *SyscallResponse) GetEpollWait() *EpollWaitResponse {
+ if x, ok := m.GetResult().(*SyscallResponse_EpollWait); ok {
+ return x.EpollWait
+ }
+ return nil
+}
+
+func (m *SyscallResponse) GetEpollCtl() *EpollCtlResponse {
+ if x, ok := m.GetResult().(*SyscallResponse_EpollCtl); ok {
+ return x.EpollCtl
+ }
+ return nil
+}
+
+func (m *SyscallResponse) GetEpollCreate1() *EpollCreate1Response {
+ if x, ok := m.GetResult().(*SyscallResponse_EpollCreate1); ok {
+ return x.EpollCreate1
+ }
+ return nil
+}
+
+func (m *SyscallResponse) GetPoll() *PollResponse {
+ if x, ok := m.GetResult().(*SyscallResponse_Poll); ok {
+ return x.Poll
+ }
+ return nil
+}
+
+func (m *SyscallResponse) GetRead() *ReadResponse {
+ if x, ok := m.GetResult().(*SyscallResponse_Read); ok {
+ return x.Read
+ }
+ return nil
+}
+
+func (m *SyscallResponse) GetWrite() *WriteResponse {
+ if x, ok := m.GetResult().(*SyscallResponse_Write); ok {
+ return x.Write
+ }
+ return nil
+}
+
+func (m *SyscallResponse) GetOpen() *OpenResponse {
+ if x, ok := m.GetResult().(*SyscallResponse_Open); ok {
+ return x.Open
+ }
+ return nil
+}
+
+func (m *SyscallResponse) GetIoctl() *IOCtlResponse {
+ if x, ok := m.GetResult().(*SyscallResponse_Ioctl); ok {
+ return x.Ioctl
+ }
+ return nil
+}
+
+func (m *SyscallResponse) GetWriteFile() *WriteFileResponse {
+ if x, ok := m.GetResult().(*SyscallResponse_WriteFile); ok {
+ return x.WriteFile
+ }
+ return nil
+}
+
+func (m *SyscallResponse) GetReadFile() *ReadFileResponse {
+ if x, ok := m.GetResult().(*SyscallResponse_ReadFile); ok {
+ return x.ReadFile
+ }
+ return nil
+}
+
+// XXX_OneofWrappers is for the internal use of the proto package.
+func (*SyscallResponse) XXX_OneofWrappers() []interface{} {
+ return []interface{}{
+ (*SyscallResponse_Socket)(nil),
+ (*SyscallResponse_Sendmsg)(nil),
+ (*SyscallResponse_Recvmsg)(nil),
+ (*SyscallResponse_Bind)(nil),
+ (*SyscallResponse_Accept)(nil),
+ (*SyscallResponse_Connect)(nil),
+ (*SyscallResponse_Listen)(nil),
+ (*SyscallResponse_Shutdown)(nil),
+ (*SyscallResponse_Close)(nil),
+ (*SyscallResponse_GetSockOpt)(nil),
+ (*SyscallResponse_SetSockOpt)(nil),
+ (*SyscallResponse_GetSockName)(nil),
+ (*SyscallResponse_GetPeerName)(nil),
+ (*SyscallResponse_EpollWait)(nil),
+ (*SyscallResponse_EpollCtl)(nil),
+ (*SyscallResponse_EpollCreate1)(nil),
+ (*SyscallResponse_Poll)(nil),
+ (*SyscallResponse_Read)(nil),
+ (*SyscallResponse_Write)(nil),
+ (*SyscallResponse_Open)(nil),
+ (*SyscallResponse_Ioctl)(nil),
+ (*SyscallResponse_WriteFile)(nil),
+ (*SyscallResponse_ReadFile)(nil),
+ }
+}
+
+func init() {
+ proto.RegisterType((*SendmsgRequest)(nil), "syscall_rpc.SendmsgRequest")
+ proto.RegisterType((*SendmsgResponse)(nil), "syscall_rpc.SendmsgResponse")
+ proto.RegisterType((*IOCtlRequest)(nil), "syscall_rpc.IOCtlRequest")
+ proto.RegisterType((*IOCtlResponse)(nil), "syscall_rpc.IOCtlResponse")
+ proto.RegisterType((*RecvmsgRequest)(nil), "syscall_rpc.RecvmsgRequest")
+ proto.RegisterType((*OpenRequest)(nil), "syscall_rpc.OpenRequest")
+ proto.RegisterType((*OpenResponse)(nil), "syscall_rpc.OpenResponse")
+ proto.RegisterType((*ReadRequest)(nil), "syscall_rpc.ReadRequest")
+ proto.RegisterType((*ReadResponse)(nil), "syscall_rpc.ReadResponse")
+ proto.RegisterType((*ReadFileRequest)(nil), "syscall_rpc.ReadFileRequest")
+ proto.RegisterType((*ReadFileResponse)(nil), "syscall_rpc.ReadFileResponse")
+ proto.RegisterType((*WriteRequest)(nil), "syscall_rpc.WriteRequest")
+ proto.RegisterType((*WriteResponse)(nil), "syscall_rpc.WriteResponse")
+ proto.RegisterType((*WriteFileRequest)(nil), "syscall_rpc.WriteFileRequest")
+ proto.RegisterType((*WriteFileResponse)(nil), "syscall_rpc.WriteFileResponse")
+ proto.RegisterType((*AddressResponse)(nil), "syscall_rpc.AddressResponse")
+ proto.RegisterType((*RecvmsgResponse)(nil), "syscall_rpc.RecvmsgResponse")
+ proto.RegisterType((*RecvmsgResponse_ResultPayload)(nil), "syscall_rpc.RecvmsgResponse.ResultPayload")
+ proto.RegisterType((*BindRequest)(nil), "syscall_rpc.BindRequest")
+ proto.RegisterType((*BindResponse)(nil), "syscall_rpc.BindResponse")
+ proto.RegisterType((*AcceptRequest)(nil), "syscall_rpc.AcceptRequest")
+ proto.RegisterType((*AcceptResponse)(nil), "syscall_rpc.AcceptResponse")
+ proto.RegisterType((*AcceptResponse_ResultPayload)(nil), "syscall_rpc.AcceptResponse.ResultPayload")
+ proto.RegisterType((*ConnectRequest)(nil), "syscall_rpc.ConnectRequest")
+ proto.RegisterType((*ConnectResponse)(nil), "syscall_rpc.ConnectResponse")
+ proto.RegisterType((*ListenRequest)(nil), "syscall_rpc.ListenRequest")
+ proto.RegisterType((*ListenResponse)(nil), "syscall_rpc.ListenResponse")
+ proto.RegisterType((*ShutdownRequest)(nil), "syscall_rpc.ShutdownRequest")
+ proto.RegisterType((*ShutdownResponse)(nil), "syscall_rpc.ShutdownResponse")
+ proto.RegisterType((*CloseRequest)(nil), "syscall_rpc.CloseRequest")
+ proto.RegisterType((*CloseResponse)(nil), "syscall_rpc.CloseResponse")
+ proto.RegisterType((*GetSockOptRequest)(nil), "syscall_rpc.GetSockOptRequest")
+ proto.RegisterType((*GetSockOptResponse)(nil), "syscall_rpc.GetSockOptResponse")
+ proto.RegisterType((*SetSockOptRequest)(nil), "syscall_rpc.SetSockOptRequest")
+ proto.RegisterType((*SetSockOptResponse)(nil), "syscall_rpc.SetSockOptResponse")
+ proto.RegisterType((*GetSockNameRequest)(nil), "syscall_rpc.GetSockNameRequest")
+ proto.RegisterType((*GetSockNameResponse)(nil), "syscall_rpc.GetSockNameResponse")
+ proto.RegisterType((*GetPeerNameRequest)(nil), "syscall_rpc.GetPeerNameRequest")
+ proto.RegisterType((*GetPeerNameResponse)(nil), "syscall_rpc.GetPeerNameResponse")
+ proto.RegisterType((*SocketRequest)(nil), "syscall_rpc.SocketRequest")
+ proto.RegisterType((*SocketResponse)(nil), "syscall_rpc.SocketResponse")
+ proto.RegisterType((*EpollWaitRequest)(nil), "syscall_rpc.EpollWaitRequest")
+ proto.RegisterType((*EpollEvent)(nil), "syscall_rpc.EpollEvent")
+ proto.RegisterType((*EpollEvents)(nil), "syscall_rpc.EpollEvents")
+ proto.RegisterType((*EpollWaitResponse)(nil), "syscall_rpc.EpollWaitResponse")
+ proto.RegisterType((*EpollCtlRequest)(nil), "syscall_rpc.EpollCtlRequest")
+ proto.RegisterType((*EpollCtlResponse)(nil), "syscall_rpc.EpollCtlResponse")
+ proto.RegisterType((*EpollCreate1Request)(nil), "syscall_rpc.EpollCreate1Request")
+ proto.RegisterType((*EpollCreate1Response)(nil), "syscall_rpc.EpollCreate1Response")
+ proto.RegisterType((*PollRequest)(nil), "syscall_rpc.PollRequest")
+ proto.RegisterType((*PollResponse)(nil), "syscall_rpc.PollResponse")
+ proto.RegisterType((*SyscallRequest)(nil), "syscall_rpc.SyscallRequest")
+ proto.RegisterType((*SyscallResponse)(nil), "syscall_rpc.SyscallResponse")
+}
+
+func init() {
+ proto.RegisterFile("pkg/sentry/socket/rpcinet/syscall_rpc.proto", fileDescriptor_dd04f3a8f0c5288b)
+}
+
+var fileDescriptor_dd04f3a8f0c5288b = []byte{
+ // 1838 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x98, 0xdd, 0x52, 0xe3, 0xc8,
+ 0x15, 0x80, 0x6d, 0x6c, 0xc0, 0x1c, 0xff, 0xa2, 0x21, 0xac, 0xf8, 0x67, 0x95, 0xa4, 0x8a, 0x4d,
+ 0x2a, 0xb8, 0xc6, 0x33, 0xec, 0x90, 0xdd, 0xad, 0xdd, 0x2c, 0x04, 0xd6, 0x53, 0x99, 0x1a, 0x88,
+ 0x5c, 0x09, 0xa9, 0xe4, 0xc2, 0x25, 0xa4, 0xb6, 0x71, 0x21, 0x4b, 0x8a, 0x24, 0x43, 0x71, 0x93,
+ 0x07, 0xc8, 0x75, 0xee, 0x72, 0x91, 0x87, 0xca, 0x03, 0xe4, 0x09, 0xf2, 0x0e, 0xa9, 0xd3, 0xdd,
+ 0x92, 0xba, 0x45, 0x8b, 0xc1, 0x29, 0x6a, 0xef, 0xd4, 0xad, 0xf3, 0xd7, 0xe7, 0x74, 0x7f, 0x3a,
+ 0x2d, 0xf8, 0x65, 0x70, 0x3b, 0xee, 0x46, 0xc4, 0x8b, 0xc3, 0x87, 0x6e, 0xe4, 0xdb, 0xb7, 0x24,
+ 0xee, 0x86, 0x81, 0x3d, 0xf1, 0x48, 0xdc, 0x8d, 0x1e, 0x22, 0xdb, 0x72, 0xdd, 0x61, 0x18, 0xd8,
+ 0x87, 0x41, 0xe8, 0xc7, 0xbe, 0x56, 0x17, 0xa6, 0x8c, 0xbf, 0x97, 0xa1, 0x35, 0x20, 0x9e, 0x33,
+ 0x8d, 0xc6, 0x26, 0xf9, 0xeb, 0x8c, 0x44, 0xb1, 0xd6, 0x82, 0x85, 0x91, 0xa3, 0x97, 0xf7, 0xcb,
+ 0x07, 0x4d, 0x73, 0x61, 0xe4, 0x68, 0xeb, 0x50, 0x75, 0xac, 0xd8, 0xd2, 0x17, 0xf6, 0xcb, 0x07,
+ 0x8d, 0x93, 0x85, 0x5a, 0xd9, 0xa4, 0x63, 0x4d, 0x87, 0x65, 0xcb, 0x71, 0x42, 0x12, 0x45, 0x7a,
+ 0x05, 0x5f, 0x99, 0xc9, 0x50, 0xd3, 0xa0, 0x3a, 0xf5, 0x43, 0xa2, 0x57, 0xf7, 0xcb, 0x07, 0x35,
+ 0x93, 0x3e, 0x6b, 0x06, 0x34, 0x89, 0xe7, 0x0c, 0xfd, 0xd1, 0x30, 0x24, 0xb6, 0x1f, 0x3a, 0xfa,
+ 0x22, 0x7d, 0x59, 0x27, 0x9e, 0x73, 0x31, 0x32, 0xe9, 0x94, 0xf1, 0x67, 0x68, 0xa7, 0xb1, 0x44,
+ 0x81, 0xef, 0x45, 0x44, 0xfb, 0x29, 0x34, 0x48, 0x18, 0xfa, 0xe1, 0xd0, 0x9b, 0x4d, 0xaf, 0x49,
+ 0xc8, 0xc2, 0xea, 0x97, 0xcc, 0x3a, 0x9d, 0xfd, 0x48, 0x27, 0x35, 0x1d, 0x96, 0x5c, 0xe2, 0x8d,
+ 0xe3, 0x1b, 0x1a, 0x23, 0xbe, 0xe6, 0xe3, 0x93, 0x1a, 0x2c, 0x85, 0x24, 0x9a, 0xb9, 0xb1, 0x71,
+ 0x02, 0x8d, 0xf7, 0x17, 0xa7, 0xb1, 0x5b, 0xb4, 0xca, 0x0e, 0x54, 0xec, 0xa9, 0xc3, 0x0c, 0x98,
+ 0xf8, 0x88, 0x33, 0x56, 0x38, 0xe6, 0x6b, 0xc3, 0x47, 0xe3, 0x8f, 0xd0, 0xe4, 0x36, 0xe6, 0x89,
+ 0x6e, 0x1d, 0x16, 0xef, 0x2c, 0x77, 0x46, 0x58, 0x02, 0xfb, 0x25, 0x93, 0x0d, 0x85, 0xd8, 0xfe,
+ 0x59, 0x86, 0x96, 0x49, 0xec, 0xbb, 0x27, 0x8b, 0x20, 0x2d, 0x31, 0x59, 0x20, 0xce, 0x47, 0xc4,
+ 0x73, 0x48, 0x48, 0xe3, 0xac, 0x99, 0x7c, 0x84, 0x25, 0x08, 0x08, 0xb9, 0x4d, 0x4a, 0x80, 0xcf,
+ 0xda, 0x1a, 0x2c, 0xc6, 0xe1, 0xcc, 0xb3, 0x79, 0xea, 0xd9, 0x40, 0xdb, 0x83, 0xba, 0x3d, 0x8d,
+ 0xc6, 0x43, 0x6e, 0x7e, 0x89, 0x9a, 0x07, 0x9c, 0xfa, 0x40, 0x67, 0x8c, 0xdf, 0x41, 0xfd, 0x22,
+ 0x20, 0x5e, 0x12, 0x19, 0x5a, 0xb6, 0xe2, 0x1b, 0x1a, 0x5b, 0xc3, 0xa4, 0xcf, 0x68, 0x79, 0xe4,
+ 0x5a, 0xe3, 0x88, 0x07, 0xc7, 0x06, 0x6c, 0x1b, 0x38, 0x84, 0x46, 0xd6, 0x34, 0xe9, 0xb3, 0x71,
+ 0x01, 0x0d, 0x66, 0x6c, 0x9e, 0x0c, 0x76, 0x68, 0x32, 0x92, 0xda, 0x2e, 0x8c, 0x1c, 0x21, 0x77,
+ 0x47, 0x50, 0x37, 0x89, 0xe5, 0xcc, 0x99, 0x37, 0xe3, 0x0a, 0x1a, 0x4c, 0x6d, 0xbe, 0x7d, 0x96,
+ 0x3b, 0x09, 0xfd, 0x12, 0x3b, 0x0b, 0x42, 0x3c, 0x3f, 0x87, 0x36, 0x1a, 0x3e, 0x9f, 0xb8, 0x44,
+ 0x95, 0xb1, 0x15, 0x96, 0x31, 0xe3, 0x2f, 0xd0, 0xc9, 0xc4, 0x5e, 0x3a, 0x86, 0x2f, 0xa1, 0x71,
+ 0x15, 0x4e, 0x62, 0x32, 0xe7, 0x89, 0x36, 0xfe, 0x04, 0x4d, 0xae, 0xf7, 0xd2, 0xa7, 0xef, 0x37,
+ 0xd0, 0xa1, 0x96, 0x3f, 0x91, 0x16, 0x64, 0x8a, 0xed, 0x7b, 0x31, 0xf1, 0x62, 0x16, 0x9c, 0x99,
+ 0x0c, 0x8d, 0x4b, 0x58, 0x15, 0x2c, 0xf0, 0xf8, 0x3e, 0x57, 0xc5, 0x97, 0x8f, 0x6e, 0xf9, 0x3e,
+ 0x9c, 0xc4, 0x31, 0xf1, 0xf8, 0x0e, 0x48, 0x86, 0xc6, 0x29, 0xb4, 0xbf, 0x67, 0xc0, 0x4a, 0xed,
+ 0x09, 0x48, 0x2b, 0xcb, 0x48, 0x2b, 0xda, 0x47, 0xff, 0x5a, 0xc0, 0x7a, 0xf3, 0xa3, 0x3b, 0x4f,
+ 0xd6, 0xce, 0x61, 0x39, 0xb0, 0x1e, 0x5c, 0xdf, 0x62, 0x1b, 0xbb, 0xde, 0xfb, 0xc5, 0xa1, 0x88,
+ 0xea, 0x9c, 0xcd, 0x43, 0x93, 0xe6, 0xf1, 0x92, 0x69, 0xf4, 0x4b, 0x66, 0xa2, 0xbc, 0xf9, 0x8f,
+ 0x32, 0x34, 0xa5, 0x97, 0x69, 0x75, 0xcb, 0x39, 0x5e, 0x7f, 0x99, 0x2d, 0x8e, 0x79, 0xdc, 0x96,
+ 0x3c, 0xe6, 0x72, 0xa1, 0x5a, 0x7a, 0x45, 0x42, 0xcf, 0x16, 0xac, 0x50, 0x70, 0x50, 0x67, 0x55,
+ 0x9a, 0xae, 0x1a, 0x4e, 0xfc, 0x56, 0xde, 0x8c, 0xef, 0xa0, 0x7e, 0x32, 0xf1, 0x0a, 0x0f, 0xa8,
+ 0x2e, 0x47, 0x95, 0xa5, 0xdc, 0x78, 0x0d, 0x0d, 0xa6, 0xf8, 0xec, 0x62, 0x1b, 0xef, 0xa1, 0xf9,
+ 0xbd, 0x6d, 0x93, 0x20, 0x2e, 0xf2, 0xc6, 0xb0, 0x18, 0x52, 0x57, 0x0c, 0x8b, 0x61, 0x06, 0x2f,
+ 0x5c, 0x5e, 0x85, 0xc3, 0xcb, 0xf8, 0x4f, 0x19, 0x5a, 0x89, 0xad, 0x79, 0xea, 0x7a, 0x96, 0xaf,
+ 0xeb, 0x17, 0x72, 0x96, 0x25, 0x93, 0xc5, 0x65, 0xbd, 0xca, 0x57, 0x35, 0xbf, 0x92, 0xff, 0xb3,
+ 0x9a, 0x42, 0x61, 0xbe, 0x82, 0xd6, 0xa9, 0xef, 0x79, 0xc4, 0x8e, 0xe7, 0xaf, 0xcd, 0x5b, 0x68,
+ 0xa7, 0xba, 0xcf, 0x2f, 0xcf, 0xaf, 0xa1, 0xf9, 0x61, 0x12, 0xc5, 0xd9, 0xb7, 0x44, 0xe1, 0xf0,
+ 0xda, 0xb2, 0x6f, 0x5d, 0x7f, 0x4c, 0x1d, 0x56, 0xcc, 0x64, 0x68, 0xbc, 0x81, 0x56, 0xa2, 0xfa,
+ 0x7c, 0x7f, 0x6f, 0xa0, 0x3d, 0xb8, 0x99, 0xc5, 0x8e, 0x7f, 0xef, 0x3d, 0xf1, 0xd9, 0xbf, 0xf1,
+ 0xef, 0xb9, 0x37, 0x7c, 0x34, 0x8e, 0xa0, 0x93, 0x29, 0x3d, 0xdf, 0xd7, 0x2e, 0x34, 0x4e, 0x5d,
+ 0x3f, 0x2a, 0x62, 0xae, 0xd1, 0x83, 0x26, 0x7f, 0xff, 0x7c, 0x9b, 0x04, 0x56, 0x7f, 0x20, 0xf1,
+ 0xc0, 0xb7, 0x6f, 0x2f, 0x8a, 0xb7, 0xf4, 0x1a, 0x2c, 0xba, 0xe4, 0x8e, 0xb8, 0x7c, 0x0d, 0x6c,
+ 0x80, 0x1b, 0xdd, 0xb3, 0xa6, 0x84, 0xef, 0x69, 0xfa, 0x2c, 0x1c, 0xe4, 0x6a, 0xee, 0x5b, 0xa8,
+ 0x89, 0x6e, 0xe6, 0xd9, 0xed, 0x1a, 0x54, 0xfc, 0x20, 0x4e, 0x3b, 0x1b, 0x1c, 0x08, 0x3b, 0x6c,
+ 0x08, 0xab, 0x83, 0x17, 0x8c, 0xbf, 0xc3, 0x9c, 0x31, 0xd4, 0xe0, 0xa3, 0xf1, 0x0e, 0xb4, 0xc1,
+ 0xe3, 0xc8, 0x9f, 0x91, 0xd9, 0x9f, 0xa5, 0x4b, 0xfe, 0x68, 0x4d, 0x0b, 0x6b, 0xf6, 0x37, 0x78,
+ 0x25, 0x49, 0xcd, 0x93, 0x99, 0xe3, 0xb9, 0xce, 0x27, 0x1e, 0xfd, 0xc7, 0x27, 0x94, 0x45, 0x79,
+ 0x49, 0x48, 0xf8, 0xe9, 0x28, 0x33, 0xa9, 0x1f, 0x3b, 0xca, 0x2b, 0x68, 0x0e, 0xe8, 0x9d, 0x23,
+ 0x09, 0x70, 0x1d, 0x96, 0x46, 0xd6, 0x74, 0xe2, 0x3e, 0x50, 0x9f, 0x15, 0x93, 0x8f, 0xb0, 0xa6,
+ 0xf1, 0x43, 0x40, 0x78, 0xa1, 0xe9, 0xb3, 0xb6, 0x09, 0x35, 0x7a, 0x2b, 0xb1, 0x7d, 0x97, 0xd7,
+ 0x3a, 0x1d, 0x1b, 0xbf, 0x87, 0x56, 0x62, 0xf8, 0xa5, 0xba, 0xc5, 0x3f, 0x40, 0xe7, 0x2c, 0xf0,
+ 0x5d, 0xf7, 0xca, 0x9a, 0x14, 0x6e, 0xc8, 0x1d, 0x00, 0x6f, 0x36, 0x1d, 0x92, 0x3b, 0xe2, 0xc5,
+ 0x49, 0x47, 0xbb, 0xe2, 0xcd, 0xa6, 0x67, 0x74, 0x82, 0x76, 0xb5, 0x11, 0xb1, 0x69, 0xb4, 0x9a,
+ 0x49, 0x9f, 0x8d, 0xb7, 0x00, 0xd4, 0x2c, 0x15, 0x51, 0xf5, 0xa0, 0x92, 0x31, 0x3e, 0x32, 0xbe,
+ 0x85, 0x7a, 0xa6, 0x15, 0x69, 0xdd, 0x54, 0xac, 0xbc, 0x5f, 0x39, 0xa8, 0xf7, 0x3e, 0x93, 0x4a,
+ 0x91, 0x49, 0xa6, 0xfa, 0x77, 0xb0, 0x2a, 0x2c, 0x66, 0x9e, 0x14, 0xf5, 0xa4, 0x88, 0xea, 0x3d,
+ 0xbd, 0xc0, 0x55, 0x84, 0xcd, 0x1c, 0x93, 0x14, 0x92, 0x18, 0x43, 0x9b, 0x8a, 0x08, 0xb7, 0x29,
+ 0x0d, 0xaa, 0x24, 0x48, 0x17, 0x4d, 0x9f, 0x31, 0x0d, 0x7e, 0xc0, 0x8b, 0xbd, 0xe0, 0x07, 0x3c,
+ 0x2d, 0x95, 0x34, 0x2d, 0xbf, 0x82, 0x45, 0x6a, 0x9a, 0x1e, 0xe8, 0x27, 0x96, 0xcb, 0xa4, 0x90,
+ 0xcb, 0x99, 0xd7, 0xe7, 0x9f, 0xf4, 0x2f, 0xe0, 0x15, 0x53, 0x0b, 0x89, 0x15, 0x93, 0xd7, 0x42,
+ 0xc0, 0xf8, 0x9d, 0xe7, 0x3b, 0x94, 0x3e, 0x1b, 0x57, 0xb0, 0x26, 0x8b, 0xbe, 0xe0, 0x1d, 0xe5,
+ 0xd2, 0x77, 0xdd, 0x27, 0xee, 0x28, 0xca, 0xfd, 0x71, 0x05, 0x0d, 0xa6, 0x36, 0x67, 0x37, 0x2e,
+ 0x1a, 0x53, 0x16, 0xf0, 0xdf, 0x00, 0xad, 0x01, 0x4b, 0x76, 0x12, 0xd3, 0x5b, 0x58, 0x62, 0x3f,
+ 0x0e, 0xa8, 0xd5, 0x7a, 0x6f, 0x53, 0xaa, 0x86, 0x74, 0xbe, 0xd1, 0x24, 0x93, 0xd5, 0xde, 0xc1,
+ 0x72, 0xc4, 0x2e, 0xec, 0x7c, 0x23, 0x6d, 0xc9, 0x6a, 0xd2, 0x8f, 0x05, 0xa4, 0x07, 0x97, 0x46,
+ 0xc5, 0x90, 0x75, 0xb8, 0x74, 0x43, 0xe4, 0x15, 0xe5, 0xcb, 0x30, 0x2a, 0x72, 0x69, 0xed, 0x10,
+ 0xaa, 0xd7, 0x13, 0xcf, 0xe1, 0x7b, 0x46, 0xde, 0xb7, 0x42, 0x9b, 0x89, 0x97, 0x22, 0x94, 0xc3,
+ 0x75, 0x59, 0xb4, 0xe5, 0xa2, 0x97, 0xde, 0xfc, 0xba, 0xa4, 0x66, 0x11, 0xd7, 0xc5, 0x64, 0x31,
+ 0x3c, 0x9b, 0xb5, 0x37, 0xf4, 0x3e, 0x9c, 0x0f, 0x4f, 0x6e, 0x9b, 0x30, 0x3c, 0x2e, 0x8d, 0xee,
+ 0x5c, 0xda, 0xa6, 0xe8, 0xcb, 0x0a, 0x77, 0x52, 0xf3, 0x43, 0xef, 0x49, 0x74, 0x42, 0xfb, 0x0a,
+ 0x6a, 0x11, 0x6f, 0x39, 0xf4, 0x9a, 0x02, 0xc3, 0xb9, 0x26, 0xa6, 0x5f, 0x32, 0x53, 0x79, 0xed,
+ 0x35, 0x2c, 0xda, 0xd8, 0x57, 0xe8, 0x2b, 0x54, 0x71, 0x43, 0x0e, 0x54, 0xe8, 0x48, 0xfa, 0x25,
+ 0x93, 0x49, 0x6a, 0x27, 0xd0, 0x18, 0x93, 0x78, 0x88, 0x35, 0x1c, 0xe2, 0x07, 0x15, 0xa8, 0xe6,
+ 0xae, 0xa4, 0xf9, 0xa8, 0xef, 0xe8, 0x97, 0x4c, 0x18, 0xa7, 0x93, 0x68, 0x23, 0x12, 0x6d, 0xd4,
+ 0x15, 0x36, 0x06, 0x2a, 0x1b, 0x51, 0x66, 0xe3, 0x0c, 0x9a, 0x69, 0x1c, 0xf4, 0x63, 0xdf, 0xa0,
+ 0x46, 0xf6, 0x54, 0x81, 0x08, 0x1f, 0x40, 0xdc, 0xf1, 0xe3, 0x6c, 0x36, 0x31, 0x83, 0xbd, 0x3c,
+ 0x33, 0xd3, 0x54, 0x9b, 0xc9, 0x7d, 0x47, 0xb9, 0x99, 0x64, 0x56, 0xfb, 0x16, 0x80, 0xe0, 0xe9,
+ 0x1f, 0xde, 0x5b, 0x93, 0x58, 0x6f, 0x51, 0x1b, 0x3b, 0x8f, 0x99, 0x24, 0x7c, 0x39, 0xfa, 0x25,
+ 0x73, 0x85, 0x24, 0x73, 0xda, 0xd7, 0xc0, 0x06, 0x43, 0x3b, 0x76, 0xf5, 0xb6, 0xa2, 0x8a, 0x39,
+ 0x66, 0x62, 0x15, 0x09, 0x9f, 0xd2, 0x7e, 0x80, 0x26, 0x57, 0x66, 0xec, 0xd1, 0x3b, 0xd4, 0xc0,
+ 0xbe, 0xc2, 0x80, 0xc4, 0xb1, 0x7e, 0xc9, 0x6c, 0x10, 0x61, 0x1a, 0xcf, 0x07, 0x0e, 0xf5, 0x55,
+ 0xc5, 0xf9, 0x10, 0x18, 0x84, 0xe7, 0x03, 0xe5, 0x50, 0x3e, 0x24, 0x96, 0xa3, 0x6b, 0x0a, 0x79,
+ 0xe1, 0xbf, 0x0a, 0xca, 0xa3, 0x1c, 0x6e, 0x37, 0xbc, 0x3f, 0x13, 0xfd, 0x95, 0x62, 0xbb, 0x89,
+ 0x3f, 0x1d, 0x70, 0xbb, 0x51, 0x49, 0x74, 0xe1, 0x07, 0xc4, 0xd3, 0xd7, 0x14, 0x2e, 0x84, 0x1f,
+ 0x4b, 0xe8, 0x02, 0xe5, 0xd0, 0xc5, 0xc4, 0xc7, 0x24, 0xfe, 0x44, 0xe1, 0x42, 0xfc, 0x87, 0x87,
+ 0x2e, 0xa8, 0x24, 0xd6, 0x8e, 0xfa, 0x1a, 0x8e, 0x26, 0x2e, 0xd1, 0xd7, 0x15, 0xb5, 0xcb, 0xff,
+ 0x7d, 0xc0, 0xda, 0xdd, 0x27, 0x73, 0x58, 0x3b, 0x5c, 0x1d, 0x53, 0xff, 0x4c, 0x51, 0xbb, 0xdc,
+ 0x2f, 0x1d, 0xac, 0x5d, 0xc8, 0xa7, 0x4e, 0x96, 0xa0, 0x6a, 0x85, 0xe3, 0xc8, 0xf8, 0x2f, 0x40,
+ 0x3b, 0xa5, 0x2a, 0x47, 0xf6, 0x51, 0x0e, 0xab, 0x5b, 0x4a, 0xac, 0xa6, 0xdd, 0x55, 0xc2, 0xd5,
+ 0xe3, 0x3c, 0x57, 0xb7, 0xd5, 0x5c, 0xcd, 0xda, 0xb2, 0x04, 0xac, 0xc7, 0x79, 0xb0, 0x6e, 0x3f,
+ 0xf5, 0x5b, 0x41, 0x24, 0x6b, 0x57, 0x22, 0xeb, 0x86, 0x82, 0xac, 0xa9, 0x0e, 0x43, 0xeb, 0x51,
+ 0x0e, 0xad, 0x5b, 0x4f, 0x5c, 0x74, 0x05, 0xb6, 0x1e, 0xe7, 0xd9, 0xba, 0xad, 0x66, 0x6b, 0x16,
+ 0x61, 0x02, 0xd7, 0xa3, 0x1c, 0x5c, 0xb7, 0x94, 0x70, 0xcd, 0x1c, 0x72, 0xba, 0x7e, 0xfd, 0x88,
+ 0xae, 0x3b, 0x05, 0x74, 0x4d, 0x55, 0x33, 0xbc, 0xf6, 0x64, 0xbc, 0x6e, 0xaa, 0xf0, 0x9a, 0xaa,
+ 0x71, 0xbe, 0x9e, 0x2a, 0xf9, 0xba, 0x57, 0xc8, 0xd7, 0x54, 0x5f, 0x04, 0xec, 0xa9, 0x12, 0xb0,
+ 0x7b, 0x85, 0x80, 0xcd, 0x8c, 0x08, 0x84, 0x3d, 0x57, 0x13, 0x76, 0xbf, 0x98, 0xb0, 0xa9, 0x19,
+ 0x09, 0xb1, 0xe7, 0x6a, 0xc4, 0xee, 0x17, 0x23, 0x56, 0xb2, 0x93, 0x32, 0xf6, 0x3b, 0x05, 0x63,
+ 0x77, 0x8b, 0x18, 0x9b, 0x9a, 0x10, 0x20, 0xfb, 0xcd, 0x63, 0xc8, 0xee, 0x14, 0x40, 0x36, 0x2b,
+ 0x66, 0x4a, 0xd9, 0xbe, 0x9a, 0xb2, 0x9f, 0x3f, 0x41, 0xd9, 0xd4, 0x8a, 0x8c, 0xd9, 0xae, 0x84,
+ 0xd9, 0x0d, 0x05, 0x66, 0xb3, 0xc3, 0x42, 0x39, 0xdb, 0x95, 0x38, 0xbb, 0xa1, 0xe0, 0x6c, 0xa6,
+ 0x40, 0x41, 0xdb, 0x93, 0x41, 0xbb, 0xa9, 0x02, 0x6d, 0xb6, 0xf1, 0x18, 0x69, 0xbb, 0x12, 0x69,
+ 0x37, 0x14, 0xa4, 0xcd, 0x9c, 0x50, 0xd4, 0xf6, 0x64, 0xd4, 0x6e, 0xaa, 0x50, 0x9b, 0x39, 0x61,
+ 0xac, 0xfd, 0x4e, 0xc1, 0xda, 0xdd, 0x22, 0xd6, 0x66, 0x35, 0xcc, 0x60, 0xfb, 0xcd, 0x63, 0xd8,
+ 0xee, 0x14, 0xc0, 0x36, 0xab, 0x61, 0x4a, 0xdb, 0xb4, 0x8b, 0xbd, 0x5e, 0xa2, 0x17, 0xc5, 0x37,
+ 0xff, 0x0b, 0x00, 0x00, 0xff, 0xff, 0xd5, 0x22, 0x29, 0x56, 0xfd, 0x1a, 0x00, 0x00,
+}
diff --git a/pkg/sentry/socket/socket_state_autogen.go b/pkg/sentry/socket/socket_state_autogen.go
new file mode 100755
index 000000000..3df219247
--- /dev/null
+++ b/pkg/sentry/socket/socket_state_autogen.go
@@ -0,0 +1,24 @@
+// automatically generated by stateify.
+
+package socket
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *SendReceiveTimeout) beforeSave() {}
+func (x *SendReceiveTimeout) save(m state.Map) {
+ x.beforeSave()
+ m.Save("send", &x.send)
+ m.Save("recv", &x.recv)
+}
+
+func (x *SendReceiveTimeout) afterLoad() {}
+func (x *SendReceiveTimeout) load(m state.Map) {
+ m.Load("send", &x.send)
+ m.Load("recv", &x.recv)
+}
+
+func init() {
+ state.Register("socket.SendReceiveTimeout", (*SendReceiveTimeout)(nil), state.Fns{Save: (*SendReceiveTimeout).save, Load: (*SendReceiveTimeout).load})
+}
diff --git a/pkg/sentry/socket/unix/BUILD b/pkg/sentry/socket/unix/BUILD
deleted file mode 100644
index da9977fde..000000000
--- a/pkg/sentry/socket/unix/BUILD
+++ /dev/null
@@ -1,35 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "unix",
- srcs = [
- "device.go",
- "io.go",
- "unix.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/socket/unix",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/refs",
- "//pkg/sentry/arch",
- "//pkg/sentry/context",
- "//pkg/sentry/device",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/fsutil",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/safemem",
- "//pkg/sentry/socket",
- "//pkg/sentry/socket/control",
- "//pkg/sentry/socket/epsocket",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/usermem",
- "//pkg/syserr",
- "//pkg/syserror",
- "//pkg/tcpip",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/socket/unix/transport/BUILD b/pkg/sentry/socket/unix/transport/BUILD
deleted file mode 100644
index 0b0240336..000000000
--- a/pkg/sentry/socket/unix/transport/BUILD
+++ /dev/null
@@ -1,40 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-
-go_template_instance(
- name = "transport_message_list",
- out = "transport_message_list.go",
- package = "transport",
- prefix = "message",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*message",
- "Linker": "*message",
- },
-)
-
-go_library(
- name = "transport",
- srcs = [
- "connectioned.go",
- "connectioned_state.go",
- "connectionless.go",
- "queue.go",
- "transport_message_list.go",
- "unix.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport",
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/ilist",
- "//pkg/refs",
- "//pkg/sentry/context",
- "//pkg/syserr",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/socket/unix/transport/transport_message_list.go b/pkg/sentry/socket/unix/transport/transport_message_list.go
new file mode 100755
index 000000000..6d394860c
--- /dev/null
+++ b/pkg/sentry/socket/unix/transport/transport_message_list.go
@@ -0,0 +1,173 @@
+package transport
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type messageElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (messageElementMapper) linkerFor(elem *message) *message { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type messageList struct {
+ head *message
+ tail *message
+}
+
+// Reset resets list l to the empty state.
+func (l *messageList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *messageList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *messageList) Front() *message {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *messageList) Back() *message {
+ return l.tail
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *messageList) PushFront(e *message) {
+ messageElementMapper{}.linkerFor(e).SetNext(l.head)
+ messageElementMapper{}.linkerFor(e).SetPrev(nil)
+
+ if l.head != nil {
+ messageElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *messageList) PushBack(e *message) {
+ messageElementMapper{}.linkerFor(e).SetNext(nil)
+ messageElementMapper{}.linkerFor(e).SetPrev(l.tail)
+
+ if l.tail != nil {
+ messageElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *messageList) PushBackList(m *messageList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ messageElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ messageElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *messageList) InsertAfter(b, e *message) {
+ a := messageElementMapper{}.linkerFor(b).Next()
+ messageElementMapper{}.linkerFor(e).SetNext(a)
+ messageElementMapper{}.linkerFor(e).SetPrev(b)
+ messageElementMapper{}.linkerFor(b).SetNext(e)
+
+ if a != nil {
+ messageElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *messageList) InsertBefore(a, e *message) {
+ b := messageElementMapper{}.linkerFor(a).Prev()
+ messageElementMapper{}.linkerFor(e).SetNext(a)
+ messageElementMapper{}.linkerFor(e).SetPrev(b)
+ messageElementMapper{}.linkerFor(a).SetPrev(e)
+
+ if b != nil {
+ messageElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *messageList) Remove(e *message) {
+ prev := messageElementMapper{}.linkerFor(e).Prev()
+ next := messageElementMapper{}.linkerFor(e).Next()
+
+ if prev != nil {
+ messageElementMapper{}.linkerFor(prev).SetNext(next)
+ } else {
+ l.head = next
+ }
+
+ if next != nil {
+ messageElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else {
+ l.tail = prev
+ }
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type messageEntry struct {
+ next *message
+ prev *message
+}
+
+// Next returns the entry that follows e in the list.
+func (e *messageEntry) Next() *message {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *messageEntry) Prev() *message {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *messageEntry) SetNext(elem *message) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *messageEntry) SetPrev(elem *message) {
+ e.prev = elem
+}
diff --git a/pkg/sentry/socket/unix/transport/transport_state_autogen.go b/pkg/sentry/socket/unix/transport/transport_state_autogen.go
new file mode 100755
index 000000000..83eedb711
--- /dev/null
+++ b/pkg/sentry/socket/unix/transport/transport_state_autogen.go
@@ -0,0 +1,191 @@
+// automatically generated by stateify.
+
+package transport
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *connectionedEndpoint) beforeSave() {}
+func (x *connectionedEndpoint) save(m state.Map) {
+ x.beforeSave()
+ var acceptedChan []*connectionedEndpoint = x.saveAcceptedChan()
+ m.SaveValue("acceptedChan", acceptedChan)
+ m.Save("baseEndpoint", &x.baseEndpoint)
+ m.Save("id", &x.id)
+ m.Save("idGenerator", &x.idGenerator)
+ m.Save("stype", &x.stype)
+}
+
+func (x *connectionedEndpoint) afterLoad() {}
+func (x *connectionedEndpoint) load(m state.Map) {
+ m.Load("baseEndpoint", &x.baseEndpoint)
+ m.Load("id", &x.id)
+ m.Load("idGenerator", &x.idGenerator)
+ m.Load("stype", &x.stype)
+ m.LoadValue("acceptedChan", new([]*connectionedEndpoint), func(y interface{}) { x.loadAcceptedChan(y.([]*connectionedEndpoint)) })
+}
+
+func (x *connectionlessEndpoint) beforeSave() {}
+func (x *connectionlessEndpoint) save(m state.Map) {
+ x.beforeSave()
+ m.Save("baseEndpoint", &x.baseEndpoint)
+}
+
+func (x *connectionlessEndpoint) afterLoad() {}
+func (x *connectionlessEndpoint) load(m state.Map) {
+ m.Load("baseEndpoint", &x.baseEndpoint)
+}
+
+func (x *queue) beforeSave() {}
+func (x *queue) save(m state.Map) {
+ x.beforeSave()
+ m.Save("AtomicRefCount", &x.AtomicRefCount)
+ m.Save("ReaderQueue", &x.ReaderQueue)
+ m.Save("WriterQueue", &x.WriterQueue)
+ m.Save("closed", &x.closed)
+ m.Save("used", &x.used)
+ m.Save("limit", &x.limit)
+ m.Save("dataList", &x.dataList)
+}
+
+func (x *queue) afterLoad() {}
+func (x *queue) load(m state.Map) {
+ m.Load("AtomicRefCount", &x.AtomicRefCount)
+ m.Load("ReaderQueue", &x.ReaderQueue)
+ m.Load("WriterQueue", &x.WriterQueue)
+ m.Load("closed", &x.closed)
+ m.Load("used", &x.used)
+ m.Load("limit", &x.limit)
+ m.Load("dataList", &x.dataList)
+}
+
+func (x *messageList) beforeSave() {}
+func (x *messageList) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *messageList) afterLoad() {}
+func (x *messageList) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *messageEntry) beforeSave() {}
+func (x *messageEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *messageEntry) afterLoad() {}
+func (x *messageEntry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func (x *ControlMessages) beforeSave() {}
+func (x *ControlMessages) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Rights", &x.Rights)
+ m.Save("Credentials", &x.Credentials)
+}
+
+func (x *ControlMessages) afterLoad() {}
+func (x *ControlMessages) load(m state.Map) {
+ m.Load("Rights", &x.Rights)
+ m.Load("Credentials", &x.Credentials)
+}
+
+func (x *message) beforeSave() {}
+func (x *message) save(m state.Map) {
+ x.beforeSave()
+ m.Save("messageEntry", &x.messageEntry)
+ m.Save("Data", &x.Data)
+ m.Save("Control", &x.Control)
+ m.Save("Address", &x.Address)
+}
+
+func (x *message) afterLoad() {}
+func (x *message) load(m state.Map) {
+ m.Load("messageEntry", &x.messageEntry)
+ m.Load("Data", &x.Data)
+ m.Load("Control", &x.Control)
+ m.Load("Address", &x.Address)
+}
+
+func (x *queueReceiver) beforeSave() {}
+func (x *queueReceiver) save(m state.Map) {
+ x.beforeSave()
+ m.Save("readQueue", &x.readQueue)
+}
+
+func (x *queueReceiver) afterLoad() {}
+func (x *queueReceiver) load(m state.Map) {
+ m.Load("readQueue", &x.readQueue)
+}
+
+func (x *streamQueueReceiver) beforeSave() {}
+func (x *streamQueueReceiver) save(m state.Map) {
+ x.beforeSave()
+ m.Save("queueReceiver", &x.queueReceiver)
+ m.Save("buffer", &x.buffer)
+ m.Save("control", &x.control)
+ m.Save("addr", &x.addr)
+}
+
+func (x *streamQueueReceiver) afterLoad() {}
+func (x *streamQueueReceiver) load(m state.Map) {
+ m.Load("queueReceiver", &x.queueReceiver)
+ m.Load("buffer", &x.buffer)
+ m.Load("control", &x.control)
+ m.Load("addr", &x.addr)
+}
+
+func (x *connectedEndpoint) beforeSave() {}
+func (x *connectedEndpoint) save(m state.Map) {
+ x.beforeSave()
+ m.Save("endpoint", &x.endpoint)
+ m.Save("writeQueue", &x.writeQueue)
+}
+
+func (x *connectedEndpoint) afterLoad() {}
+func (x *connectedEndpoint) load(m state.Map) {
+ m.Load("endpoint", &x.endpoint)
+ m.Load("writeQueue", &x.writeQueue)
+}
+
+func (x *baseEndpoint) beforeSave() {}
+func (x *baseEndpoint) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Queue", &x.Queue)
+ m.Save("passcred", &x.passcred)
+ m.Save("receiver", &x.receiver)
+ m.Save("connected", &x.connected)
+ m.Save("path", &x.path)
+}
+
+func (x *baseEndpoint) afterLoad() {}
+func (x *baseEndpoint) load(m state.Map) {
+ m.Load("Queue", &x.Queue)
+ m.Load("passcred", &x.passcred)
+ m.Load("receiver", &x.receiver)
+ m.Load("connected", &x.connected)
+ m.Load("path", &x.path)
+}
+
+func init() {
+ state.Register("transport.connectionedEndpoint", (*connectionedEndpoint)(nil), state.Fns{Save: (*connectionedEndpoint).save, Load: (*connectionedEndpoint).load})
+ state.Register("transport.connectionlessEndpoint", (*connectionlessEndpoint)(nil), state.Fns{Save: (*connectionlessEndpoint).save, Load: (*connectionlessEndpoint).load})
+ state.Register("transport.queue", (*queue)(nil), state.Fns{Save: (*queue).save, Load: (*queue).load})
+ state.Register("transport.messageList", (*messageList)(nil), state.Fns{Save: (*messageList).save, Load: (*messageList).load})
+ state.Register("transport.messageEntry", (*messageEntry)(nil), state.Fns{Save: (*messageEntry).save, Load: (*messageEntry).load})
+ state.Register("transport.ControlMessages", (*ControlMessages)(nil), state.Fns{Save: (*ControlMessages).save, Load: (*ControlMessages).load})
+ state.Register("transport.message", (*message)(nil), state.Fns{Save: (*message).save, Load: (*message).load})
+ state.Register("transport.queueReceiver", (*queueReceiver)(nil), state.Fns{Save: (*queueReceiver).save, Load: (*queueReceiver).load})
+ state.Register("transport.streamQueueReceiver", (*streamQueueReceiver)(nil), state.Fns{Save: (*streamQueueReceiver).save, Load: (*streamQueueReceiver).load})
+ state.Register("transport.connectedEndpoint", (*connectedEndpoint)(nil), state.Fns{Save: (*connectedEndpoint).save, Load: (*connectedEndpoint).load})
+ state.Register("transport.baseEndpoint", (*baseEndpoint)(nil), state.Fns{Save: (*baseEndpoint).save, Load: (*baseEndpoint).load})
+}
diff --git a/pkg/sentry/socket/unix/unix_state_autogen.go b/pkg/sentry/socket/unix/unix_state_autogen.go
new file mode 100755
index 000000000..86f734156
--- /dev/null
+++ b/pkg/sentry/socket/unix/unix_state_autogen.go
@@ -0,0 +1,28 @@
+// automatically generated by stateify.
+
+package unix
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *SocketOperations) beforeSave() {}
+func (x *SocketOperations) save(m state.Map) {
+ x.beforeSave()
+ m.Save("AtomicRefCount", &x.AtomicRefCount)
+ m.Save("SendReceiveTimeout", &x.SendReceiveTimeout)
+ m.Save("ep", &x.ep)
+ m.Save("stype", &x.stype)
+}
+
+func (x *SocketOperations) afterLoad() {}
+func (x *SocketOperations) load(m state.Map) {
+ m.Load("AtomicRefCount", &x.AtomicRefCount)
+ m.Load("SendReceiveTimeout", &x.SendReceiveTimeout)
+ m.Load("ep", &x.ep)
+ m.Load("stype", &x.stype)
+}
+
+func init() {
+ state.Register("unix.SocketOperations", (*SocketOperations)(nil), state.Fns{Save: (*SocketOperations).save, Load: (*SocketOperations).load})
+}
diff --git a/pkg/sentry/state/BUILD b/pkg/sentry/state/BUILD
deleted file mode 100644
index 88765f4d6..000000000
--- a/pkg/sentry/state/BUILD
+++ /dev/null
@@ -1,24 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "state",
- srcs = [
- "state.go",
- "state_metadata.go",
- "state_unsafe.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/state",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/log",
- "//pkg/sentry/inet",
- "//pkg/sentry/kernel",
- "//pkg/sentry/time",
- "//pkg/sentry/watchdog",
- "//pkg/state/statefile",
- "//pkg/syserror",
- ],
-)
diff --git a/pkg/sentry/state/state_state_autogen.go b/pkg/sentry/state/state_state_autogen.go
new file mode 100755
index 000000000..6c0d9b7a7
--- /dev/null
+++ b/pkg/sentry/state/state_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package state
+
diff --git a/pkg/sentry/strace/BUILD b/pkg/sentry/strace/BUILD
deleted file mode 100644
index 445d25010..000000000
--- a/pkg/sentry/strace/BUILD
+++ /dev/null
@@ -1,52 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "strace",
- srcs = [
- "capability.go",
- "clone.go",
- "futex.go",
- "linux64.go",
- "open.go",
- "poll.go",
- "ptrace.go",
- "signal.go",
- "socket.go",
- "strace.go",
- "syscalls.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/strace",
- visibility = ["//:sandbox"],
- deps = [
- ":strace_go_proto",
- "//pkg/abi",
- "//pkg/abi/linux",
- "//pkg/binary",
- "//pkg/bits",
- "//pkg/eventchannel",
- "//pkg/seccomp",
- "//pkg/sentry/arch",
- "//pkg/sentry/kernel",
- "//pkg/sentry/socket/control",
- "//pkg/sentry/socket/epsocket",
- "//pkg/sentry/socket/netlink",
- "//pkg/sentry/syscalls/linux",
- "//pkg/sentry/usermem",
- ],
-)
-
-proto_library(
- name = "strace_proto",
- srcs = ["strace.proto"],
- visibility = ["//visibility:public"],
-)
-
-go_proto_library(
- name = "strace_go_proto",
- importpath = "gvisor.dev/gvisor/pkg/sentry/strace/strace_go_proto",
- proto = ":strace_proto",
- visibility = ["//visibility:public"],
-)
diff --git a/pkg/sentry/strace/strace.proto b/pkg/sentry/strace/strace.proto
deleted file mode 100644
index 4b2f73a5f..000000000
--- a/pkg/sentry/strace/strace.proto
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto3";
-
-package gvisor;
-
-message Strace {
- // Process name that made the syscall.
- string process = 1;
-
- // Syscall function name.
- string function = 2;
-
- // List of syscall arguments formatted as strings.
- repeated string args = 3;
-
- oneof info {
- StraceEnter enter = 4;
- StraceExit exit = 5;
- }
-}
-
-message StraceEnter {
-}
-
-message StraceExit {
- // Return value formatted as string.
- string return = 1;
-
- // Formatted error string in case syscall failed.
- string error = 2;
-
- // Value of errno upon syscall exit.
- int64 err_no = 3; // errno is a macro and gets expanded :-(
-
- // Time elapsed between syscall enter and exit.
- int64 elapsed_ns = 4;
-}
diff --git a/pkg/sentry/strace/strace_go_proto/strace.pb.go b/pkg/sentry/strace/strace_go_proto/strace.pb.go
new file mode 100755
index 000000000..ef45661bc
--- /dev/null
+++ b/pkg/sentry/strace/strace_go_proto/strace.pb.go
@@ -0,0 +1,247 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: pkg/sentry/strace/strace.proto
+
+package gvisor
+
+import (
+ fmt "fmt"
+ proto "github.com/golang/protobuf/proto"
+ math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+type Strace struct {
+ Process string `protobuf:"bytes,1,opt,name=process,proto3" json:"process,omitempty"`
+ Function string `protobuf:"bytes,2,opt,name=function,proto3" json:"function,omitempty"`
+ Args []string `protobuf:"bytes,3,rep,name=args,proto3" json:"args,omitempty"`
+ // Types that are valid to be assigned to Info:
+ // *Strace_Enter
+ // *Strace_Exit
+ Info isStrace_Info `protobuf_oneof:"info"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Strace) Reset() { *m = Strace{} }
+func (m *Strace) String() string { return proto.CompactTextString(m) }
+func (*Strace) ProtoMessage() {}
+func (*Strace) Descriptor() ([]byte, []int) {
+ return fileDescriptor_50c4b43677c82b5f, []int{0}
+}
+
+func (m *Strace) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Strace.Unmarshal(m, b)
+}
+func (m *Strace) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Strace.Marshal(b, m, deterministic)
+}
+func (m *Strace) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Strace.Merge(m, src)
+}
+func (m *Strace) XXX_Size() int {
+ return xxx_messageInfo_Strace.Size(m)
+}
+func (m *Strace) XXX_DiscardUnknown() {
+ xxx_messageInfo_Strace.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Strace proto.InternalMessageInfo
+
+func (m *Strace) GetProcess() string {
+ if m != nil {
+ return m.Process
+ }
+ return ""
+}
+
+func (m *Strace) GetFunction() string {
+ if m != nil {
+ return m.Function
+ }
+ return ""
+}
+
+func (m *Strace) GetArgs() []string {
+ if m != nil {
+ return m.Args
+ }
+ return nil
+}
+
+type isStrace_Info interface {
+ isStrace_Info()
+}
+
+type Strace_Enter struct {
+ Enter *StraceEnter `protobuf:"bytes,4,opt,name=enter,proto3,oneof"`
+}
+
+type Strace_Exit struct {
+ Exit *StraceExit `protobuf:"bytes,5,opt,name=exit,proto3,oneof"`
+}
+
+func (*Strace_Enter) isStrace_Info() {}
+
+func (*Strace_Exit) isStrace_Info() {}
+
+func (m *Strace) GetInfo() isStrace_Info {
+ if m != nil {
+ return m.Info
+ }
+ return nil
+}
+
+func (m *Strace) GetEnter() *StraceEnter {
+ if x, ok := m.GetInfo().(*Strace_Enter); ok {
+ return x.Enter
+ }
+ return nil
+}
+
+func (m *Strace) GetExit() *StraceExit {
+ if x, ok := m.GetInfo().(*Strace_Exit); ok {
+ return x.Exit
+ }
+ return nil
+}
+
+// XXX_OneofWrappers is for the internal use of the proto package.
+func (*Strace) XXX_OneofWrappers() []interface{} {
+ return []interface{}{
+ (*Strace_Enter)(nil),
+ (*Strace_Exit)(nil),
+ }
+}
+
+type StraceEnter struct {
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *StraceEnter) Reset() { *m = StraceEnter{} }
+func (m *StraceEnter) String() string { return proto.CompactTextString(m) }
+func (*StraceEnter) ProtoMessage() {}
+func (*StraceEnter) Descriptor() ([]byte, []int) {
+ return fileDescriptor_50c4b43677c82b5f, []int{1}
+}
+
+func (m *StraceEnter) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_StraceEnter.Unmarshal(m, b)
+}
+func (m *StraceEnter) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_StraceEnter.Marshal(b, m, deterministic)
+}
+func (m *StraceEnter) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_StraceEnter.Merge(m, src)
+}
+func (m *StraceEnter) XXX_Size() int {
+ return xxx_messageInfo_StraceEnter.Size(m)
+}
+func (m *StraceEnter) XXX_DiscardUnknown() {
+ xxx_messageInfo_StraceEnter.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_StraceEnter proto.InternalMessageInfo
+
+type StraceExit struct {
+ Return string `protobuf:"bytes,1,opt,name=return,proto3" json:"return,omitempty"`
+ Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"`
+ ErrNo int64 `protobuf:"varint,3,opt,name=err_no,json=errNo,proto3" json:"err_no,omitempty"`
+ ElapsedNs int64 `protobuf:"varint,4,opt,name=elapsed_ns,json=elapsedNs,proto3" json:"elapsed_ns,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *StraceExit) Reset() { *m = StraceExit{} }
+func (m *StraceExit) String() string { return proto.CompactTextString(m) }
+func (*StraceExit) ProtoMessage() {}
+func (*StraceExit) Descriptor() ([]byte, []int) {
+ return fileDescriptor_50c4b43677c82b5f, []int{2}
+}
+
+func (m *StraceExit) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_StraceExit.Unmarshal(m, b)
+}
+func (m *StraceExit) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_StraceExit.Marshal(b, m, deterministic)
+}
+func (m *StraceExit) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_StraceExit.Merge(m, src)
+}
+func (m *StraceExit) XXX_Size() int {
+ return xxx_messageInfo_StraceExit.Size(m)
+}
+func (m *StraceExit) XXX_DiscardUnknown() {
+ xxx_messageInfo_StraceExit.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_StraceExit proto.InternalMessageInfo
+
+func (m *StraceExit) GetReturn() string {
+ if m != nil {
+ return m.Return
+ }
+ return ""
+}
+
+func (m *StraceExit) GetError() string {
+ if m != nil {
+ return m.Error
+ }
+ return ""
+}
+
+func (m *StraceExit) GetErrNo() int64 {
+ if m != nil {
+ return m.ErrNo
+ }
+ return 0
+}
+
+func (m *StraceExit) GetElapsedNs() int64 {
+ if m != nil {
+ return m.ElapsedNs
+ }
+ return 0
+}
+
+func init() {
+ proto.RegisterType((*Strace)(nil), "gvisor.Strace")
+ proto.RegisterType((*StraceEnter)(nil), "gvisor.StraceEnter")
+ proto.RegisterType((*StraceExit)(nil), "gvisor.StraceExit")
+}
+
+func init() { proto.RegisterFile("pkg/sentry/strace/strace.proto", fileDescriptor_50c4b43677c82b5f) }
+
+var fileDescriptor_50c4b43677c82b5f = []byte{
+ // 255 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x90, 0xdd, 0x4a, 0xf4, 0x30,
+ 0x10, 0x86, 0xb7, 0x5f, 0xdb, 0x7c, 0x76, 0x16, 0x4f, 0xc6, 0x1f, 0x82, 0xa0, 0x94, 0x1e, 0x05,
+ 0x84, 0x2e, 0xe8, 0x1d, 0x08, 0xc2, 0x1e, 0xed, 0x41, 0xbc, 0x80, 0xa5, 0xd6, 0xd9, 0x12, 0x94,
+ 0x24, 0x4c, 0xb2, 0xb2, 0x5e, 0x96, 0x77, 0x28, 0xa6, 0xf1, 0x07, 0x8f, 0x92, 0x67, 0xde, 0x87,
+ 0x0c, 0x6f, 0xe0, 0xca, 0x3f, 0x4f, 0xab, 0x40, 0x36, 0xf2, 0xdb, 0x2a, 0x44, 0x1e, 0x46, 0xca,
+ 0x47, 0xef, 0xd9, 0x45, 0x87, 0x62, 0x7a, 0x35, 0xc1, 0x71, 0xf7, 0x5e, 0x80, 0x78, 0x48, 0x01,
+ 0x4a, 0xf8, 0xef, 0xd9, 0x8d, 0x14, 0x82, 0x2c, 0xda, 0x42, 0x35, 0xfa, 0x0b, 0xf1, 0x02, 0x8e,
+ 0x76, 0x7b, 0x3b, 0x46, 0xe3, 0xac, 0xfc, 0x97, 0xa2, 0x6f, 0x46, 0x84, 0x6a, 0xe0, 0x29, 0xc8,
+ 0xb2, 0x2d, 0x55, 0xa3, 0xd3, 0x1d, 0xaf, 0xa1, 0x26, 0x1b, 0x89, 0x65, 0xd5, 0x16, 0x6a, 0x79,
+ 0x73, 0xd2, 0xcf, 0xcb, 0xfa, 0x79, 0xd1, 0xfd, 0x67, 0xb4, 0x5e, 0xe8, 0xd9, 0x41, 0x05, 0x15,
+ 0x1d, 0x4c, 0x94, 0x75, 0x72, 0xf1, 0x8f, 0x7b, 0x30, 0x71, 0xbd, 0xd0, 0xc9, 0xb8, 0x13, 0x50,
+ 0x19, 0xbb, 0x73, 0xdd, 0x31, 0x2c, 0x7f, 0xbd, 0xd4, 0x79, 0x80, 0x1f, 0x19, 0xcf, 0x41, 0x30,
+ 0xc5, 0x3d, 0xdb, 0x5c, 0x22, 0x13, 0x9e, 0x42, 0x4d, 0xcc, 0x8e, 0x73, 0x81, 0x19, 0xf0, 0x0c,
+ 0x04, 0x31, 0x6f, 0xad, 0x93, 0x65, 0x5b, 0xa8, 0x32, 0x8d, 0x37, 0x0e, 0x2f, 0x01, 0xe8, 0x65,
+ 0xf0, 0x81, 0x9e, 0xb6, 0x36, 0xa4, 0x16, 0xa5, 0x6e, 0xf2, 0x64, 0x13, 0x1e, 0x45, 0xfa, 0xc3,
+ 0xdb, 0x8f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x42, 0x9a, 0xbc, 0x81, 0x65, 0x01, 0x00, 0x00,
+}
diff --git a/pkg/sentry/strace/strace_state_autogen.go b/pkg/sentry/strace/strace_state_autogen.go
new file mode 100755
index 000000000..9dc697ed6
--- /dev/null
+++ b/pkg/sentry/strace/strace_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package strace
+
diff --git a/pkg/sentry/syscalls/BUILD b/pkg/sentry/syscalls/BUILD
deleted file mode 100644
index 79d972202..000000000
--- a/pkg/sentry/syscalls/BUILD
+++ /dev/null
@@ -1,22 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "syscalls",
- srcs = [
- "epoll.go",
- "syscalls.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/syscalls",
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/sentry/arch",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/epoll",
- "//pkg/sentry/kernel/time",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/syscalls/linux/BUILD b/pkg/sentry/syscalls/linux/BUILD
deleted file mode 100644
index 33a40b9c6..000000000
--- a/pkg/sentry/syscalls/linux/BUILD
+++ /dev/null
@@ -1,92 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "linux",
- srcs = [
- "error.go",
- "flags.go",
- "linux64.go",
- "sigset.go",
- "sys_aio.go",
- "sys_capability.go",
- "sys_epoll.go",
- "sys_eventfd.go",
- "sys_file.go",
- "sys_futex.go",
- "sys_getdents.go",
- "sys_identity.go",
- "sys_inotify.go",
- "sys_lseek.go",
- "sys_mempolicy.go",
- "sys_mmap.go",
- "sys_mount.go",
- "sys_pipe.go",
- "sys_poll.go",
- "sys_prctl.go",
- "sys_random.go",
- "sys_read.go",
- "sys_rlimit.go",
- "sys_rusage.go",
- "sys_sched.go",
- "sys_seccomp.go",
- "sys_sem.go",
- "sys_shm.go",
- "sys_signal.go",
- "sys_socket.go",
- "sys_splice.go",
- "sys_stat.go",
- "sys_sync.go",
- "sys_sysinfo.go",
- "sys_syslog.go",
- "sys_thread.go",
- "sys_time.go",
- "sys_timer.go",
- "sys_timerfd.go",
- "sys_tls.go",
- "sys_utsname.go",
- "sys_write.go",
- "timespec.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/syscalls/linux",
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/abi",
- "//pkg/abi/linux",
- "//pkg/binary",
- "//pkg/bpf",
- "//pkg/log",
- "//pkg/metric",
- "//pkg/rand",
- "//pkg/sentry/arch",
- "//pkg/sentry/context",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/anon",
- "//pkg/sentry/fs/lock",
- "//pkg/sentry/fs/timerfd",
- "//pkg/sentry/fs/tmpfs",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/kernel/epoll",
- "//pkg/sentry/kernel/eventfd",
- "//pkg/sentry/kernel/fasync",
- "//pkg/sentry/kernel/pipe",
- "//pkg/sentry/kernel/sched",
- "//pkg/sentry/kernel/shm",
- "//pkg/sentry/kernel/time",
- "//pkg/sentry/limits",
- "//pkg/sentry/memmap",
- "//pkg/sentry/mm",
- "//pkg/sentry/safemem",
- "//pkg/sentry/socket",
- "//pkg/sentry/socket/control",
- "//pkg/sentry/socket/unix/transport",
- "//pkg/sentry/syscalls",
- "//pkg/sentry/usage",
- "//pkg/sentry/usermem",
- "//pkg/syserr",
- "//pkg/syserror",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/sentry/syscalls/linux/linux_state_autogen.go b/pkg/sentry/syscalls/linux/linux_state_autogen.go
new file mode 100755
index 000000000..06d1fb23f
--- /dev/null
+++ b/pkg/sentry/syscalls/linux/linux_state_autogen.go
@@ -0,0 +1,80 @@
+// automatically generated by stateify.
+
+package linux
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *ioEvent) beforeSave() {}
+func (x *ioEvent) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Data", &x.Data)
+ m.Save("Obj", &x.Obj)
+ m.Save("Result", &x.Result)
+ m.Save("Result2", &x.Result2)
+}
+
+func (x *ioEvent) afterLoad() {}
+func (x *ioEvent) load(m state.Map) {
+ m.Load("Data", &x.Data)
+ m.Load("Obj", &x.Obj)
+ m.Load("Result", &x.Result)
+ m.Load("Result2", &x.Result2)
+}
+
+func (x *futexWaitRestartBlock) beforeSave() {}
+func (x *futexWaitRestartBlock) save(m state.Map) {
+ x.beforeSave()
+ m.Save("duration", &x.duration)
+ m.Save("addr", &x.addr)
+ m.Save("private", &x.private)
+ m.Save("val", &x.val)
+ m.Save("mask", &x.mask)
+}
+
+func (x *futexWaitRestartBlock) afterLoad() {}
+func (x *futexWaitRestartBlock) load(m state.Map) {
+ m.Load("duration", &x.duration)
+ m.Load("addr", &x.addr)
+ m.Load("private", &x.private)
+ m.Load("val", &x.val)
+ m.Load("mask", &x.mask)
+}
+
+func (x *pollRestartBlock) beforeSave() {}
+func (x *pollRestartBlock) save(m state.Map) {
+ x.beforeSave()
+ m.Save("pfdAddr", &x.pfdAddr)
+ m.Save("nfds", &x.nfds)
+ m.Save("timeout", &x.timeout)
+}
+
+func (x *pollRestartBlock) afterLoad() {}
+func (x *pollRestartBlock) load(m state.Map) {
+ m.Load("pfdAddr", &x.pfdAddr)
+ m.Load("nfds", &x.nfds)
+ m.Load("timeout", &x.timeout)
+}
+
+func (x *clockNanosleepRestartBlock) beforeSave() {}
+func (x *clockNanosleepRestartBlock) save(m state.Map) {
+ x.beforeSave()
+ m.Save("c", &x.c)
+ m.Save("duration", &x.duration)
+ m.Save("rem", &x.rem)
+}
+
+func (x *clockNanosleepRestartBlock) afterLoad() {}
+func (x *clockNanosleepRestartBlock) load(m state.Map) {
+ m.Load("c", &x.c)
+ m.Load("duration", &x.duration)
+ m.Load("rem", &x.rem)
+}
+
+func init() {
+ state.Register("linux.ioEvent", (*ioEvent)(nil), state.Fns{Save: (*ioEvent).save, Load: (*ioEvent).load})
+ state.Register("linux.futexWaitRestartBlock", (*futexWaitRestartBlock)(nil), state.Fns{Save: (*futexWaitRestartBlock).save, Load: (*futexWaitRestartBlock).load})
+ state.Register("linux.pollRestartBlock", (*pollRestartBlock)(nil), state.Fns{Save: (*pollRestartBlock).save, Load: (*pollRestartBlock).load})
+ state.Register("linux.clockNanosleepRestartBlock", (*clockNanosleepRestartBlock)(nil), state.Fns{Save: (*clockNanosleepRestartBlock).save, Load: (*clockNanosleepRestartBlock).load})
+}
diff --git a/pkg/sentry/syscalls/syscalls_state_autogen.go b/pkg/sentry/syscalls/syscalls_state_autogen.go
new file mode 100755
index 000000000..c114e7989
--- /dev/null
+++ b/pkg/sentry/syscalls/syscalls_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package syscalls
+
diff --git a/pkg/sentry/time/BUILD b/pkg/sentry/time/BUILD
deleted file mode 100644
index beb43ba13..000000000
--- a/pkg/sentry/time/BUILD
+++ /dev/null
@@ -1,53 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-
-go_template_instance(
- name = "seqatomic_parameters",
- out = "seqatomic_parameters_unsafe.go",
- package = "time",
- suffix = "Parameters",
- template = "//third_party/gvsync:generic_seqatomic",
- types = {
- "Value": "Parameters",
- },
-)
-
-go_library(
- name = "time",
- srcs = [
- "arith_arm64.go",
- "calibrated_clock.go",
- "clock_id.go",
- "clocks.go",
- "muldiv_amd64.s",
- "muldiv_arm64.s",
- "parameters.go",
- "sampler.go",
- "sampler_unsafe.go",
- "seqatomic_parameters_unsafe.go",
- "tsc_amd64.s",
- "tsc_arm64.s",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/time",
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/log",
- "//pkg/metric",
- "//pkg/syserror",
- "//third_party/gvsync",
- ],
-)
-
-go_test(
- name = "time_test",
- srcs = [
- "calibrated_clock_test.go",
- "parameters_test.go",
- "sampler_test.go",
- ],
- embed = [":time"],
-)
diff --git a/pkg/sentry/time/LICENSE b/pkg/sentry/time/LICENSE
deleted file mode 100644
index 6a66aea5e..000000000
--- a/pkg/sentry/time/LICENSE
+++ /dev/null
@@ -1,27 +0,0 @@
-Copyright (c) 2009 The Go Authors. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
- * Redistributions of source code must retain the above copyright
-notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above
-copyright notice, this list of conditions and the following disclaimer
-in the documentation and/or other materials provided with the
-distribution.
- * Neither the name of Google Inc. nor the names of its
-contributors may be used to endorse or promote products derived from
-this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkg/sentry/time/calibrated_clock_test.go b/pkg/sentry/time/calibrated_clock_test.go
deleted file mode 100644
index d6622bfe2..000000000
--- a/pkg/sentry/time/calibrated_clock_test.go
+++ /dev/null
@@ -1,186 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package time
-
-import (
- "testing"
- "time"
-)
-
-// newTestCalibratedClock returns a CalibratedClock that collects samples from
-// the given sample list and cycle counts from the given cycle list.
-func newTestCalibratedClock(samples []sample, cycles []TSCValue) *CalibratedClock {
- return &CalibratedClock{
- ref: newTestSampler(samples, cycles),
- }
-}
-
-func TestConstantFrequency(t *testing.T) {
- // Perfectly constant frequency.
- samples := []sample{
- {before: 100000, after: 100000 + defaultOverheadCycles, ref: 100},
- {before: 200000, after: 200000 + defaultOverheadCycles, ref: 200},
- {before: 300000, after: 300000 + defaultOverheadCycles, ref: 300},
- {before: 400000, after: 400000 + defaultOverheadCycles, ref: 400},
- {before: 500000, after: 500000 + defaultOverheadCycles, ref: 500},
- {before: 600000, after: 600000 + defaultOverheadCycles, ref: 600},
- {before: 700000, after: 700000 + defaultOverheadCycles, ref: 700},
- }
-
- c := newTestCalibratedClock(samples, nil)
-
- // Update from all samples.
- for range samples {
- c.Update()
- }
-
- c.mu.RLock()
- if !c.ready {
- c.mu.RUnlock()
- t.Fatalf("clock not ready")
- }
- // A bit after the last sample.
- now, ok := c.params.ComputeTime(750000)
- c.mu.RUnlock()
- if !ok {
- t.Fatalf("ComputeTime ok got %v want true", ok)
- }
-
- t.Logf("now: %v", now)
-
- // Time should be between the current sample and where we'd expect the
- // next sample.
- if now < 700 || now > 800 {
- t.Errorf("now got %v want > 700 && < 800", now)
- }
-}
-
-func TestErrorCorrection(t *testing.T) {
- testCases := []struct {
- name string
- samples [5]sample
- projectedTimeStart int64
- projectedTimeEnd int64
- }{
- // Initial calibration should be ~1MHz for each of these, and
- // the reference clock changes in samples[2].
- {
- name: "slow-down",
- samples: [5]sample{
- {before: 1000000, after: 1000001, ref: ReferenceNS(1 * ApproxUpdateInterval.Nanoseconds())},
- {before: 2000000, after: 2000001, ref: ReferenceNS(2 * ApproxUpdateInterval.Nanoseconds())},
- // Reference clock has slowed down, causing 100ms of error.
- {before: 3010000, after: 3010001, ref: ReferenceNS(3 * ApproxUpdateInterval.Nanoseconds())},
- {before: 4020000, after: 4020001, ref: ReferenceNS(4 * ApproxUpdateInterval.Nanoseconds())},
- {before: 5030000, after: 5030001, ref: ReferenceNS(5 * ApproxUpdateInterval.Nanoseconds())},
- },
- projectedTimeStart: 3005 * time.Millisecond.Nanoseconds(),
- projectedTimeEnd: 3015 * time.Millisecond.Nanoseconds(),
- },
- {
- name: "speed-up",
- samples: [5]sample{
- {before: 1000000, after: 1000001, ref: ReferenceNS(1 * ApproxUpdateInterval.Nanoseconds())},
- {before: 2000000, after: 2000001, ref: ReferenceNS(2 * ApproxUpdateInterval.Nanoseconds())},
- // Reference clock has sped up, causing 100ms of error.
- {before: 2990000, after: 2990001, ref: ReferenceNS(3 * ApproxUpdateInterval.Nanoseconds())},
- {before: 3980000, after: 3980001, ref: ReferenceNS(4 * ApproxUpdateInterval.Nanoseconds())},
- {before: 4970000, after: 4970001, ref: ReferenceNS(5 * ApproxUpdateInterval.Nanoseconds())},
- },
- projectedTimeStart: 2985 * time.Millisecond.Nanoseconds(),
- projectedTimeEnd: 2995 * time.Millisecond.Nanoseconds(),
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- c := newTestCalibratedClock(tc.samples[:], nil)
-
- // Initial calibration takes two updates.
- _, ok := c.Update()
- if ok {
- t.Fatalf("Update ready too early")
- }
-
- params, ok := c.Update()
- if !ok {
- t.Fatalf("Update not ready")
- }
-
- // Initial calibration is ~1MHz.
- hz := params.Frequency
- if hz < 990000 || hz > 1010000 {
- t.Fatalf("Frequency got %v want > 990kHz && < 1010kHz", hz)
- }
-
- // Project time at the next update. Given the 1MHz
- // calibration, it is expected to be ~3.1s/2.9s, not
- // the actual 3s.
- //
- // N.B. the next update time is the "after" time above.
- projected, ok := params.ComputeTime(tc.samples[2].after)
- if !ok {
- t.Fatalf("ComputeTime ok got %v want true", ok)
- }
- if projected < tc.projectedTimeStart || projected > tc.projectedTimeEnd {
- t.Fatalf("ComputeTime(%v) got %v want > %v && < %v", tc.samples[2].after, projected, tc.projectedTimeStart, tc.projectedTimeEnd)
- }
-
- // Update again to see the changed reference clock.
- params, ok = c.Update()
- if !ok {
- t.Fatalf("Update not ready")
- }
-
- // We now know that TSC = tc.samples[2].after -> 3s,
- // but with the previous params indicated that TSC
- // tc.samples[2].after -> 3.5s/2.5s. We can't allow the
- // clock to go backwards, and having the clock jump
- // forwards is undesirable. There should be a smooth
- // transition that corrects the clock error over time.
- // Check that the clock is continuous at TSC =
- // tc.samples[2].after.
- newProjected, ok := params.ComputeTime(tc.samples[2].after)
- if !ok {
- t.Fatalf("ComputeTime ok got %v want true", ok)
- }
- if newProjected != projected {
- t.Errorf("Discontinuous time; ComputeTime(%v) got %v want %v", tc.samples[2].after, newProjected, projected)
- }
-
- // As the reference clock stablizes, ensure that the clock error
- // decreases.
- initialErr := c.errorNS
- t.Logf("initial error: %v ns", initialErr)
-
- _, ok = c.Update()
- if !ok {
- t.Fatalf("Update not ready")
- }
- if c.errorNS.Magnitude() > initialErr.Magnitude() {
- t.Errorf("errorNS increased, got %v want |%v| <= |%v|", c.errorNS, c.errorNS, initialErr)
- }
-
- _, ok = c.Update()
- if !ok {
- t.Fatalf("Update not ready")
- }
- if c.errorNS.Magnitude() > initialErr.Magnitude() {
- t.Errorf("errorNS increased, got %v want |%v| <= |%v|", c.errorNS, c.errorNS, initialErr)
- }
-
- t.Logf("final error: %v ns", c.errorNS)
- })
- }
-}
diff --git a/pkg/sentry/time/parameters_test.go b/pkg/sentry/time/parameters_test.go
deleted file mode 100644
index e1b9084ac..000000000
--- a/pkg/sentry/time/parameters_test.go
+++ /dev/null
@@ -1,486 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package time
-
-import (
- "math"
- "testing"
- "time"
-)
-
-func TestParametersComputeTime(t *testing.T) {
- testCases := []struct {
- name string
- params Parameters
- now TSCValue
- want int64
- }{
- {
- // Now is the same as the base cycles.
- name: "base-cycles",
- params: Parameters{
- BaseCycles: 10000,
- BaseRef: ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
- Frequency: 10000,
- },
- now: 10000,
- want: 5000 * time.Millisecond.Nanoseconds(),
- },
- {
- // Now is the behind the base cycles. Time is frozen.
- name: "backwards",
- params: Parameters{
- BaseCycles: 10000,
- BaseRef: ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
- Frequency: 10000,
- },
- now: 9000,
- want: 5000 * time.Millisecond.Nanoseconds(),
- },
- {
- // Now is ahead of the base cycles.
- name: "ahead",
- params: Parameters{
- BaseCycles: 10000,
- BaseRef: ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
- Frequency: 10000,
- },
- now: 15000,
- want: 5500 * time.Millisecond.Nanoseconds(),
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- got, ok := tc.params.ComputeTime(tc.now)
- if !ok {
- t.Errorf("ComputeTime ok got %v want true", got)
- }
- if got != tc.want {
- t.Errorf("ComputeTime got %+v want %+v", got, tc.want)
- }
- })
- }
-}
-
-func TestParametersErrorAdjust(t *testing.T) {
- testCases := []struct {
- name string
- oldParams Parameters
- now TSCValue
- newParams Parameters
- want Parameters
- errorNS ReferenceNS
- wantErr bool
- }{
- {
- // newParams are perfectly continuous with oldParams
- // and don't need adjustment.
- name: "continuous",
- oldParams: Parameters{
- BaseCycles: 0,
- BaseRef: 0,
- Frequency: 10000,
- },
- now: 50000,
- newParams: Parameters{
- BaseCycles: 50000,
- BaseRef: ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
- Frequency: 10000,
- },
- want: Parameters{
- BaseCycles: 50000,
- BaseRef: ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
- Frequency: 10000,
- },
- },
- {
- // Same as "continuous", but with now ahead of
- // newParams.BaseCycles. The result is the same as
- // there is no error to correct.
- name: "continuous-nowdiff",
- oldParams: Parameters{
- BaseCycles: 0,
- BaseRef: 0,
- Frequency: 10000,
- },
- now: 60000,
- newParams: Parameters{
- BaseCycles: 50000,
- BaseRef: ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
- Frequency: 10000,
- },
- want: Parameters{
- BaseCycles: 50000,
- BaseRef: ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
- Frequency: 10000,
- },
- },
- {
- // errorAdjust bails out if the TSC goes backwards.
- name: "tsc-backwards",
- oldParams: Parameters{
- BaseCycles: 10000,
- BaseRef: ReferenceNS(1000 * time.Millisecond.Nanoseconds()),
- Frequency: 10000,
- },
- now: 9000,
- newParams: Parameters{
- BaseCycles: 9000,
- BaseRef: ReferenceNS(1100 * time.Millisecond.Nanoseconds()),
- Frequency: 10000,
- },
- wantErr: true,
- },
- {
- // errorAdjust bails out if new params are from after now.
- name: "params-after-now",
- oldParams: Parameters{
- BaseCycles: 10000,
- BaseRef: ReferenceNS(1000 * time.Millisecond.Nanoseconds()),
- Frequency: 10000,
- },
- now: 11000,
- newParams: Parameters{
- BaseCycles: 12000,
- BaseRef: ReferenceNS(1200 * time.Millisecond.Nanoseconds()),
- Frequency: 10000,
- },
- wantErr: true,
- },
- {
- // Host clock sped up.
- name: "speed-up",
- oldParams: Parameters{
- BaseCycles: 0,
- BaseRef: 0,
- Frequency: 10000,
- },
- now: 45000,
- // Host frequency changed to 9000 immediately after
- // oldParams was returned.
- newParams: Parameters{
- BaseCycles: 45000,
- // From oldParams, we think ref = 4.5s at cycles = 45000.
- BaseRef: ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
- Frequency: 9000,
- },
- want: Parameters{
- BaseCycles: 45000,
- BaseRef: ReferenceNS(4500 * time.Millisecond.Nanoseconds()),
- // We must decrease the new frequency by 50% to
- // correct 0.5s of error in 1s
- // (ApproxUpdateInterval).
- Frequency: 4500,
- },
- errorNS: ReferenceNS(-500 * time.Millisecond.Nanoseconds()),
- },
- {
- // Host clock sped up, with now ahead of newParams.
- name: "speed-up-nowdiff",
- oldParams: Parameters{
- BaseCycles: 0,
- BaseRef: 0,
- Frequency: 10000,
- },
- now: 50000,
- // Host frequency changed to 9000 immediately after
- // oldParams was returned.
- newParams: Parameters{
- BaseCycles: 45000,
- BaseRef: ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
- Frequency: 9000,
- },
- // nextRef = 6000ms
- // nextCycles = 9000 * (6000ms - 5000ms) + 45000
- // nextCycles = 9000 * (1s) + 45000
- // nextCycles = 54000
- // f = (54000 - 50000) / 1s = 4000
- //
- // ref = 5000ms - (50000 - 45000) / 4000
- // ref = 3.75s
- want: Parameters{
- BaseCycles: 45000,
- BaseRef: ReferenceNS(3750 * time.Millisecond.Nanoseconds()),
- Frequency: 4000,
- },
- // oldNow = 50000 * 10000 = 5s
- // newNow = (50000 - 45000) / 9000 + 5s = 5.555s
- errorNS: ReferenceNS((5000*time.Millisecond - 5555555555).Nanoseconds()),
- },
- {
- // Host clock sped up. The new parameters are so far
- // ahead that the next update time already passed.
- name: "speed-up-uncorrectable-baseref",
- oldParams: Parameters{
- BaseCycles: 0,
- BaseRef: 0,
- Frequency: 10000,
- },
- now: 50000,
- // Host frequency changed to 5000 immediately after
- // oldParams was returned.
- newParams: Parameters{
- BaseCycles: 45000,
- BaseRef: ReferenceNS(9000 * time.Millisecond.Nanoseconds()),
- Frequency: 5000,
- },
- // The next update should be at 10s, but newParams
- // already passed 6s. Thus it is impossible to correct
- // the clock by then.
- wantErr: true,
- },
- {
- // Host clock sped up. The new parameters are moving so
- // fast that the next update should be before now.
- name: "speed-up-uncorrectable-frequency",
- oldParams: Parameters{
- BaseCycles: 0,
- BaseRef: 0,
- Frequency: 10000,
- },
- now: 55000,
- // Host frequency changed to 7500 immediately after
- // oldParams was returned.
- newParams: Parameters{
- BaseCycles: 45000,
- BaseRef: ReferenceNS(6000 * time.Millisecond.Nanoseconds()),
- Frequency: 7500,
- },
- // The next update should be at 6.5s, but newParams are
- // so far ahead and fast that they reach 6.5s at cycle
- // 48750, which before now! Thus it is impossible to
- // correct the clock by then.
- wantErr: true,
- },
- {
- // Host clock slowed down.
- name: "slow-down",
- oldParams: Parameters{
- BaseCycles: 0,
- BaseRef: 0,
- Frequency: 10000,
- },
- now: 55000,
- // Host frequency changed to 11000 immediately after
- // oldParams was returned.
- newParams: Parameters{
- BaseCycles: 55000,
- // From oldParams, we think ref = 5.5s at cycles = 55000.
- BaseRef: ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
- Frequency: 11000,
- },
- want: Parameters{
- BaseCycles: 55000,
- BaseRef: ReferenceNS(5500 * time.Millisecond.Nanoseconds()),
- // We must increase the new frequency by 50% to
- // correct 0.5s of error in 1s
- // (ApproxUpdateInterval).
- Frequency: 16500,
- },
- errorNS: ReferenceNS(500 * time.Millisecond.Nanoseconds()),
- },
- {
- // Host clock slowed down, with now ahead of newParams.
- name: "slow-down-nowdiff",
- oldParams: Parameters{
- BaseCycles: 0,
- BaseRef: 0,
- Frequency: 10000,
- },
- now: 60000,
- // Host frequency changed to 11000 immediately after
- // oldParams was returned.
- newParams: Parameters{
- BaseCycles: 55000,
- BaseRef: ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
- Frequency: 11000,
- },
- // nextRef = 7000ms
- // nextCycles = 11000 * (7000ms - 5000ms) + 55000
- // nextCycles = 11000 * (2000ms) + 55000
- // nextCycles = 77000
- // f = (77000 - 60000) / 1s = 17000
- //
- // ref = 6000ms - (60000 - 55000) / 17000
- // ref = 5705882353ns
- want: Parameters{
- BaseCycles: 55000,
- BaseRef: ReferenceNS(5705882353),
- Frequency: 17000,
- },
- // oldNow = 60000 * 10000 = 6s
- // newNow = (60000 - 55000) / 11000 + 5s = 5.4545s
- errorNS: ReferenceNS((6*time.Second - 5454545454).Nanoseconds()),
- },
- {
- // Host time went backwards.
- name: "time-backwards",
- oldParams: Parameters{
- BaseCycles: 50000,
- BaseRef: ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
- Frequency: 10000,
- },
- now: 60000,
- newParams: Parameters{
- BaseCycles: 60000,
- // From oldParams, we think ref = 6s at cycles = 60000.
- BaseRef: ReferenceNS(4000 * time.Millisecond.Nanoseconds()),
- Frequency: 10000,
- },
- want: Parameters{
- BaseCycles: 60000,
- BaseRef: ReferenceNS(6000 * time.Millisecond.Nanoseconds()),
- // We must increase the frequency by 200% to
- // correct 2s of error in 1s
- // (ApproxUpdateInterval).
- Frequency: 30000,
- },
- errorNS: ReferenceNS(2000 * time.Millisecond.Nanoseconds()),
- },
- {
- // Host time went backwards, with now ahead of newParams.
- name: "time-backwards-nowdiff",
- oldParams: Parameters{
- BaseCycles: 50000,
- BaseRef: ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
- Frequency: 10000,
- },
- now: 65000,
- // nextRef = 7500ms
- // nextCycles = 10000 * (7500ms - 4000ms) + 60000
- // nextCycles = 10000 * (3500ms) + 60000
- // nextCycles = 95000
- // f = (95000 - 65000) / 1s = 30000
- //
- // ref = 6500ms - (65000 - 60000) / 30000
- // ref = 6333333333ns
- newParams: Parameters{
- BaseCycles: 60000,
- BaseRef: ReferenceNS(4000 * time.Millisecond.Nanoseconds()),
- Frequency: 10000,
- },
- want: Parameters{
- BaseCycles: 60000,
- BaseRef: ReferenceNS(6333333334),
- Frequency: 30000,
- },
- // oldNow = 65000 * 10000 = 6.5s
- // newNow = (65000 - 60000) / 10000 + 4s = 4.5s
- errorNS: ReferenceNS(2000 * time.Millisecond.Nanoseconds()),
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- got, errorNS, err := errorAdjust(tc.oldParams, tc.newParams, tc.now)
- if err != nil && !tc.wantErr {
- t.Errorf("err got %v want nil", err)
- } else if err == nil && tc.wantErr {
- t.Errorf("err got nil want non-nil")
- }
-
- if got != tc.want {
- t.Errorf("Parameters got %+v want %+v", got, tc.want)
- }
- if errorNS != tc.errorNS {
- t.Errorf("errorNS got %v want %v", errorNS, tc.errorNS)
- }
- })
- }
-}
-
-func testMuldiv(t *testing.T, v uint64) {
- for i := uint64(1); i <= 1000000; i++ {
- mult := uint64(1000000000)
- div := i * mult
- res, ok := muldiv64(v, mult, div)
- if !ok {
- t.Errorf("Result of %v * %v / %v ok got false want true", v, mult, div)
- }
- if want := v / i; res != want {
- t.Errorf("Bad result of %v * %v / %v: got %v, want %v", v, mult, div, res, want)
- }
- }
-}
-
-func TestMulDiv(t *testing.T) {
- testMuldiv(t, math.MaxUint64)
- for i := int64(-10); i <= 10; i++ {
- testMuldiv(t, uint64(i))
- }
-}
-
-func TestMulDivZero(t *testing.T) {
- if r, ok := muldiv64(2, 4, 0); ok {
- t.Errorf("muldiv64(2, 4, 0) got %d, ok want !ok", r)
- }
-
- if r, ok := muldiv64(0, 0, 0); ok {
- t.Errorf("muldiv64(0, 0, 0) got %d, ok want !ok", r)
- }
-}
-
-func TestMulDivOverflow(t *testing.T) {
- testCases := []struct {
- name string
- val uint64
- mult uint64
- div uint64
- ok bool
- ret uint64
- }{
- {
- name: "2^62",
- val: 1 << 63,
- mult: 4,
- div: 8,
- ok: true,
- ret: 1 << 62,
- },
- {
- name: "2^64-1",
- val: 0xffffffffffffffff,
- mult: 1,
- div: 1,
- ok: true,
- ret: 0xffffffffffffffff,
- },
- {
- name: "2^64",
- val: 1 << 63,
- mult: 4,
- div: 2,
- ok: false,
- },
- {
- name: "2^125",
- val: 1 << 63,
- mult: 1 << 63,
- div: 2,
- ok: false,
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- r, ok := muldiv64(tc.val, tc.mult, tc.div)
- if ok != tc.ok {
- t.Errorf("ok got %v want %v", ok, tc.ok)
- }
- if tc.ok && r != tc.ret {
- t.Errorf("ret got %v want %v", r, tc.ret)
- }
- })
- }
-}
diff --git a/pkg/sentry/time/sampler_test.go b/pkg/sentry/time/sampler_test.go
deleted file mode 100644
index 3e70a1134..000000000
--- a/pkg/sentry/time/sampler_test.go
+++ /dev/null
@@ -1,183 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package time
-
-import (
- "errors"
- "testing"
-)
-
-// errNoSamples is returned when testReferenceClocks runs out of samples.
-var errNoSamples = errors.New("no samples available")
-
-// testReferenceClocks returns a preset list of samples and cycle counts.
-type testReferenceClocks struct {
- samples []sample
- cycles []TSCValue
-}
-
-// Sample implements referenceClocks.Sample, returning the next sample in the list.
-func (t *testReferenceClocks) Sample(_ ClockID) (sample, error) {
- if len(t.samples) == 0 {
- return sample{}, errNoSamples
- }
-
- s := t.samples[0]
- if len(t.samples) == 1 {
- t.samples = nil
- } else {
- t.samples = t.samples[1:]
- }
-
- return s, nil
-}
-
-// Cycles implements referenceClocks.Cycles, returning the next TSCValue in the list.
-func (t *testReferenceClocks) Cycles() TSCValue {
- if len(t.cycles) == 0 {
- return 0
- }
-
- c := t.cycles[0]
- if len(t.cycles) == 1 {
- t.cycles = nil
- } else {
- t.cycles = t.cycles[1:]
- }
-
- return c
-}
-
-// newTestSampler returns a sampler that collects samples from
-// the given sample list and cycle counts from the given cycle list.
-func newTestSampler(samples []sample, cycles []TSCValue) *sampler {
- return &sampler{
- clocks: &testReferenceClocks{
- samples: samples,
- cycles: cycles,
- },
- overhead: defaultOverheadCycles,
- }
-}
-
-// generateSamples generates n samples with the given overhead.
-func generateSamples(n int, overhead TSCValue) []sample {
- samples := []sample{{before: 1000000, after: 1000000 + overhead, ref: 100}}
- for i := 0; i < n-1; i++ {
- prev := samples[len(samples)-1]
- samples = append(samples, sample{
- before: prev.before + 1000000,
- after: prev.after + 1000000,
- ref: prev.ref + 100,
- })
- }
- return samples
-}
-
-// TestSample ensures that samples can be collected.
-func TestSample(t *testing.T) {
- testCases := []struct {
- name string
- samples []sample
- err error
- }{
- {
- name: "basic",
- samples: []sample{
- {before: 100000, after: 100000 + defaultOverheadCycles, ref: 100},
- },
- err: nil,
- },
- {
- // Sample with backwards TSC ignored.
- // referenceClock should retry and get errNoSamples.
- name: "backwards-tsc-ignored",
- samples: []sample{
- {before: 100000, after: 90000, ref: 100},
- },
- err: errNoSamples,
- },
- {
- // Sample far above overhead skipped.
- // referenceClock should retry and get errNoSamples.
- name: "reject-overhead",
- samples: []sample{
- {before: 100000, after: 100000 + 5*defaultOverheadCycles, ref: 100},
- },
- err: errNoSamples,
- },
- {
- // Maximum overhead allowed is bounded.
- name: "over-max-overhead",
- // Generate a bunch of samples. The reference clock
- // needs a while to ramp up its expected overhead.
- samples: generateSamples(100, 2*maxOverheadCycles),
- err: errOverheadTooHigh,
- },
- {
- // Overhead at maximum overhead is allowed.
- name: "max-overhead",
- // Generate a bunch of samples. The reference clock
- // needs a while to ramp up its expected overhead.
- samples: generateSamples(100, maxOverheadCycles),
- err: nil,
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- s := newTestSampler(tc.samples, nil)
- err := s.Sample()
- if err != tc.err {
- t.Errorf("Sample err got %v want %v", err, tc.err)
- }
- })
- }
-}
-
-// TestOutliersIgnored tests that referenceClock ignores samples with very high
-// overhead.
-func TestOutliersIgnored(t *testing.T) {
- s := newTestSampler([]sample{
- {before: 100000, after: 100000 + defaultOverheadCycles, ref: 100},
- {before: 200000, after: 200000 + defaultOverheadCycles, ref: 200},
- {before: 300000, after: 300000 + defaultOverheadCycles, ref: 300},
- {before: 400000, after: 400000 + defaultOverheadCycles, ref: 400},
- {before: 500000, after: 500000 + 5*defaultOverheadCycles, ref: 500}, // Ignored
- {before: 600000, after: 600000 + defaultOverheadCycles, ref: 600},
- {before: 700000, after: 700000 + defaultOverheadCycles, ref: 700},
- }, nil)
-
- // Collect 5 samples.
- for i := 0; i < 5; i++ {
- err := s.Sample()
- if err != nil {
- t.Fatalf("Unexpected error while sampling: %v", err)
- }
- }
-
- oldest, newest, ok := s.Range()
- if !ok {
- t.Fatalf("Range not ok")
- }
-
- if oldest.ref != 100 {
- t.Errorf("oldest.ref got %v want %v", oldest.ref, 100)
- }
-
- // We skipped the high-overhead sample.
- if newest.ref != 600 {
- t.Errorf("newest.ref got %v want %v", newest.ref, 600)
- }
-}
diff --git a/pkg/sentry/time/seqatomic_parameters_unsafe.go b/pkg/sentry/time/seqatomic_parameters_unsafe.go
new file mode 100755
index 000000000..b4fb0a7f0
--- /dev/null
+++ b/pkg/sentry/time/seqatomic_parameters_unsafe.go
@@ -0,0 +1,54 @@
+package time
+
+import (
+ "fmt"
+ "gvisor.dev/gvisor/third_party/gvsync"
+ "reflect"
+ "strings"
+ "unsafe"
+)
+
+// SeqAtomicLoad returns a copy of *ptr, ensuring that the read does not race
+// with any writer critical sections in sc.
+func SeqAtomicLoadParameters(sc *gvsync.SeqCount, ptr *Parameters) Parameters {
+ // This function doesn't use SeqAtomicTryLoad because doing so is
+ // measurably, significantly (~20%) slower; Go is awful at inlining.
+ var val Parameters
+ for {
+ epoch := sc.BeginRead()
+ if gvsync.RaceEnabled {
+
+ gvsync.Memmove(unsafe.Pointer(&val), unsafe.Pointer(ptr), unsafe.Sizeof(val))
+ } else {
+
+ val = *ptr
+ }
+ if sc.ReadOk(epoch) {
+ break
+ }
+ }
+ return val
+}
+
+// SeqAtomicTryLoad returns a copy of *ptr while in a reader critical section
+// in sc initiated by a call to sc.BeginRead() that returned epoch. If the read
+// would race with a writer critical section, SeqAtomicTryLoad returns
+// (unspecified, false).
+func SeqAtomicTryLoadParameters(sc *gvsync.SeqCount, epoch gvsync.SeqCountEpoch, ptr *Parameters) (Parameters, bool) {
+ var val Parameters
+ if gvsync.RaceEnabled {
+ gvsync.Memmove(unsafe.Pointer(&val), unsafe.Pointer(ptr), unsafe.Sizeof(val))
+ } else {
+ val = *ptr
+ }
+ return val, sc.ReadOk(epoch)
+}
+
+func initParameters() {
+ var val Parameters
+ typ := reflect.TypeOf(val)
+ name := typ.Name()
+ if ptrs := gvsync.PointersInType(typ, name); len(ptrs) != 0 {
+ panic(fmt.Sprintf("SeqAtomicLoad<%s> is invalid since values %s of type %s contain pointers:\n%s", typ, name, typ, strings.Join(ptrs, "\n")))
+ }
+}
diff --git a/pkg/sentry/time/time_state_autogen.go b/pkg/sentry/time/time_state_autogen.go
new file mode 100755
index 000000000..ea614b056
--- /dev/null
+++ b/pkg/sentry/time/time_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package time
+
diff --git a/pkg/sentry/unimpl/BUILD b/pkg/sentry/unimpl/BUILD
deleted file mode 100644
index b69603da3..000000000
--- a/pkg/sentry/unimpl/BUILD
+++ /dev/null
@@ -1,30 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
-
-package(licenses = ["notice"])
-
-proto_library(
- name = "unimplemented_syscall_proto",
- srcs = ["unimplemented_syscall.proto"],
- visibility = ["//visibility:public"],
- deps = ["//pkg/sentry/arch:registers_proto"],
-)
-
-go_proto_library(
- name = "unimplemented_syscall_go_proto",
- importpath = "gvisor.dev/gvisor/pkg/sentry/unimpl/unimplemented_syscall_go_proto",
- proto = ":unimplemented_syscall_proto",
- visibility = ["//visibility:public"],
- deps = ["//pkg/sentry/arch:registers_go_proto"],
-)
-
-go_library(
- name = "unimpl",
- srcs = ["events.go"],
- importpath = "gvisor.dev/gvisor/pkg/sentry/unimpl",
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/log",
- "//pkg/sentry/context",
- ],
-)
diff --git a/pkg/sentry/unimpl/unimpl_state_autogen.go b/pkg/sentry/unimpl/unimpl_state_autogen.go
new file mode 100755
index 000000000..b9d1116f3
--- /dev/null
+++ b/pkg/sentry/unimpl/unimpl_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package unimpl
+
diff --git a/pkg/sentry/unimpl/unimplemented_syscall.proto b/pkg/sentry/unimpl/unimplemented_syscall.proto
deleted file mode 100644
index 0d7a94be7..000000000
--- a/pkg/sentry/unimpl/unimplemented_syscall.proto
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto3";
-
-package gvisor;
-
-import "pkg/sentry/arch/registers.proto";
-
-message UnimplementedSyscall {
- // Task ID.
- int32 tid = 1;
-
- // Registers at the time of the call.
- Registers registers = 2;
-}
diff --git a/pkg/sentry/unimpl/unimplemented_syscall_go_proto/unimplemented_syscall.pb.go b/pkg/sentry/unimpl/unimplemented_syscall_go_proto/unimplemented_syscall.pb.go
new file mode 100755
index 000000000..4dfb169cc
--- /dev/null
+++ b/pkg/sentry/unimpl/unimplemented_syscall_go_proto/unimplemented_syscall.pb.go
@@ -0,0 +1,91 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: pkg/sentry/unimpl/unimplemented_syscall.proto
+
+package gvisor
+
+import (
+ fmt "fmt"
+ proto "github.com/golang/protobuf/proto"
+ registers_go_proto "gvisor.dev/gvisor/pkg/sentry/arch/registers_go_proto"
+ math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+type UnimplementedSyscall struct {
+ Tid int32 `protobuf:"varint,1,opt,name=tid,proto3" json:"tid,omitempty"`
+ Registers *registers_go_proto.Registers `protobuf:"bytes,2,opt,name=registers,proto3" json:"registers,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *UnimplementedSyscall) Reset() { *m = UnimplementedSyscall{} }
+func (m *UnimplementedSyscall) String() string { return proto.CompactTextString(m) }
+func (*UnimplementedSyscall) ProtoMessage() {}
+func (*UnimplementedSyscall) Descriptor() ([]byte, []int) {
+ return fileDescriptor_ddc2fcd2bea3c75d, []int{0}
+}
+
+func (m *UnimplementedSyscall) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_UnimplementedSyscall.Unmarshal(m, b)
+}
+func (m *UnimplementedSyscall) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_UnimplementedSyscall.Marshal(b, m, deterministic)
+}
+func (m *UnimplementedSyscall) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_UnimplementedSyscall.Merge(m, src)
+}
+func (m *UnimplementedSyscall) XXX_Size() int {
+ return xxx_messageInfo_UnimplementedSyscall.Size(m)
+}
+func (m *UnimplementedSyscall) XXX_DiscardUnknown() {
+ xxx_messageInfo_UnimplementedSyscall.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_UnimplementedSyscall proto.InternalMessageInfo
+
+func (m *UnimplementedSyscall) GetTid() int32 {
+ if m != nil {
+ return m.Tid
+ }
+ return 0
+}
+
+func (m *UnimplementedSyscall) GetRegisters() *registers_go_proto.Registers {
+ if m != nil {
+ return m.Registers
+ }
+ return nil
+}
+
+func init() {
+ proto.RegisterType((*UnimplementedSyscall)(nil), "gvisor.UnimplementedSyscall")
+}
+
+func init() {
+ proto.RegisterFile("pkg/sentry/unimpl/unimplemented_syscall.proto", fileDescriptor_ddc2fcd2bea3c75d)
+}
+
+var fileDescriptor_ddc2fcd2bea3c75d = []byte{
+ // 149 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x2d, 0xc8, 0x4e, 0xd7,
+ 0x2f, 0x4e, 0xcd, 0x2b, 0x29, 0xaa, 0xd4, 0x2f, 0xcd, 0xcb, 0xcc, 0x2d, 0xc8, 0x81, 0x52, 0xa9,
+ 0xb9, 0xa9, 0x79, 0x25, 0xa9, 0x29, 0xf1, 0xc5, 0x95, 0xc5, 0xc9, 0x89, 0x39, 0x39, 0x7a, 0x05,
+ 0x45, 0xf9, 0x25, 0xf9, 0x42, 0x6c, 0xe9, 0x65, 0x99, 0xc5, 0xf9, 0x45, 0x52, 0xf2, 0x48, 0xda,
+ 0x12, 0x8b, 0x92, 0x33, 0xf4, 0x8b, 0x52, 0xd3, 0x33, 0x8b, 0x4b, 0x52, 0x8b, 0x8a, 0x21, 0x0a,
+ 0x95, 0x22, 0xb9, 0x44, 0x42, 0x91, 0xcd, 0x09, 0x86, 0x18, 0x23, 0x24, 0xc0, 0xc5, 0x5c, 0x92,
+ 0x99, 0x22, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x1a, 0x04, 0x62, 0x0a, 0xe9, 0x73, 0x71, 0xc2, 0x35,
+ 0x4b, 0x30, 0x29, 0x30, 0x6a, 0x70, 0x1b, 0x09, 0xea, 0x41, 0xac, 0xd1, 0x0b, 0x82, 0x49, 0x04,
+ 0x21, 0xd4, 0x24, 0xb1, 0x81, 0x6d, 0x30, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x51, 0x4a, 0x47,
+ 0x79, 0xbb, 0x00, 0x00, 0x00,
+}
diff --git a/pkg/sentry/uniqueid/BUILD b/pkg/sentry/uniqueid/BUILD
deleted file mode 100644
index 86a87edd4..000000000
--- a/pkg/sentry/uniqueid/BUILD
+++ /dev/null
@@ -1,14 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "uniqueid",
- srcs = ["context.go"],
- importpath = "gvisor.dev/gvisor/pkg/sentry/uniqueid",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/sentry/context",
- "//pkg/sentry/socket/unix/transport",
- ],
-)
diff --git a/pkg/sentry/uniqueid/uniqueid_state_autogen.go b/pkg/sentry/uniqueid/uniqueid_state_autogen.go
new file mode 100755
index 000000000..09e4327e4
--- /dev/null
+++ b/pkg/sentry/uniqueid/uniqueid_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package uniqueid
+
diff --git a/pkg/sentry/usage/BUILD b/pkg/sentry/usage/BUILD
deleted file mode 100644
index a34c39540..000000000
--- a/pkg/sentry/usage/BUILD
+++ /dev/null
@@ -1,22 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "usage",
- srcs = [
- "cpu.go",
- "io.go",
- "memory.go",
- "memory_unsafe.go",
- "usage.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/usage",
- visibility = [
- "//pkg/sentry:internal",
- ],
- deps = [
- "//pkg/bits",
- "//pkg/memutil",
- ],
-)
diff --git a/pkg/sentry/usage/usage_state_autogen.go b/pkg/sentry/usage/usage_state_autogen.go
new file mode 100755
index 000000000..d98e3e916
--- /dev/null
+++ b/pkg/sentry/usage/usage_state_autogen.go
@@ -0,0 +1,50 @@
+// automatically generated by stateify.
+
+package usage
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *CPUStats) beforeSave() {}
+func (x *CPUStats) save(m state.Map) {
+ x.beforeSave()
+ m.Save("UserTime", &x.UserTime)
+ m.Save("SysTime", &x.SysTime)
+ m.Save("VoluntarySwitches", &x.VoluntarySwitches)
+}
+
+func (x *CPUStats) afterLoad() {}
+func (x *CPUStats) load(m state.Map) {
+ m.Load("UserTime", &x.UserTime)
+ m.Load("SysTime", &x.SysTime)
+ m.Load("VoluntarySwitches", &x.VoluntarySwitches)
+}
+
+func (x *IO) beforeSave() {}
+func (x *IO) save(m state.Map) {
+ x.beforeSave()
+ m.Save("CharsRead", &x.CharsRead)
+ m.Save("CharsWritten", &x.CharsWritten)
+ m.Save("ReadSyscalls", &x.ReadSyscalls)
+ m.Save("WriteSyscalls", &x.WriteSyscalls)
+ m.Save("BytesRead", &x.BytesRead)
+ m.Save("BytesWritten", &x.BytesWritten)
+ m.Save("BytesWriteCancelled", &x.BytesWriteCancelled)
+}
+
+func (x *IO) afterLoad() {}
+func (x *IO) load(m state.Map) {
+ m.Load("CharsRead", &x.CharsRead)
+ m.Load("CharsWritten", &x.CharsWritten)
+ m.Load("ReadSyscalls", &x.ReadSyscalls)
+ m.Load("WriteSyscalls", &x.WriteSyscalls)
+ m.Load("BytesRead", &x.BytesRead)
+ m.Load("BytesWritten", &x.BytesWritten)
+ m.Load("BytesWriteCancelled", &x.BytesWriteCancelled)
+}
+
+func init() {
+ state.Register("usage.CPUStats", (*CPUStats)(nil), state.Fns{Save: (*CPUStats).save, Load: (*CPUStats).load})
+ state.Register("usage.IO", (*IO)(nil), state.Fns{Save: (*IO).save, Load: (*IO).load})
+}
diff --git a/pkg/sentry/usermem/BUILD b/pkg/sentry/usermem/BUILD
deleted file mode 100644
index cc5d25762..000000000
--- a/pkg/sentry/usermem/BUILD
+++ /dev/null
@@ -1,59 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_template_instance(
- name = "addr_range",
- out = "addr_range.go",
- package = "usermem",
- prefix = "Addr",
- template = "//pkg/segment:generic_range",
- types = {
- "T": "Addr",
- },
-)
-
-go_library(
- name = "usermem",
- srcs = [
- "access_type.go",
- "addr.go",
- "addr_range.go",
- "addr_range_seq_unsafe.go",
- "bytes_io.go",
- "bytes_io_unsafe.go",
- "usermem.go",
- "usermem_arm64.go",
- "usermem_unsafe.go",
- "usermem_x86.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/usermem",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/atomicbitops",
- "//pkg/binary",
- "//pkg/log",
- "//pkg/sentry/context",
- "//pkg/sentry/safemem",
- "//pkg/syserror",
- "//pkg/tcpip/buffer",
- ],
-)
-
-go_test(
- name = "usermem_test",
- size = "small",
- srcs = [
- "addr_range_seq_test.go",
- "usermem_test.go",
- ],
- embed = [":usermem"],
- deps = [
- "//pkg/sentry/context",
- "//pkg/sentry/safemem",
- "//pkg/syserror",
- ],
-)
diff --git a/pkg/sentry/usermem/README.md b/pkg/sentry/usermem/README.md
deleted file mode 100644
index f6d2137eb..000000000
--- a/pkg/sentry/usermem/README.md
+++ /dev/null
@@ -1,31 +0,0 @@
-This package defines primitives for sentry access to application memory.
-
-Major types:
-
-- The `IO` interface represents a virtual address space and provides I/O
- methods on that address space. `IO` is the lowest-level primitive. The
- primary implementation of the `IO` interface is `mm.MemoryManager`.
-
-- `IOSequence` represents a collection of individually-contiguous address
- ranges in a `IO` that is operated on sequentially, analogous to Linux's
- `struct iov_iter`.
-
-Major usage patterns:
-
-- Access to a task's virtual memory, subject to the application's memory
- protections and while running on that task's goroutine, from a context that
- is at or above the level of the `kernel` package (e.g. most syscall
- implementations in `syscalls/linux`); use the `kernel.Task.Copy*` wrappers
- defined in `kernel/task_usermem.go`.
-
-- Access to a task's virtual memory, from a context that is at or above the
- level of the `kernel` package, but where any of the above constraints does
- not hold (e.g. `PTRACE_POKEDATA`, which ignores application memory
- protections); obtain the task's `mm.MemoryManager` by calling
- `kernel.Task.MemoryManager`, and call its `IO` methods directly.
-
-- Access to a task's virtual memory, from a context that is below the level of
- the `kernel` package (e.g. filesystem I/O); clients must pass I/O arguments
- from higher layers, usually in the form of an `IOSequence`. The
- `kernel.Task.SingleIOSequence` and `kernel.Task.IovecsIOSequence` functions
- in `kernel/task_usermem.go` are convenience functions for doing so.
diff --git a/pkg/sentry/usermem/addr_range.go b/pkg/sentry/usermem/addr_range.go
new file mode 100755
index 000000000..152ed1434
--- /dev/null
+++ b/pkg/sentry/usermem/addr_range.go
@@ -0,0 +1,62 @@
+package usermem
+
+// A Range represents a contiguous range of T.
+//
+// +stateify savable
+type AddrRange struct {
+ // Start is the inclusive start of the range.
+ Start Addr
+
+ // End is the exclusive end of the range.
+ End Addr
+}
+
+// WellFormed returns true if r.Start <= r.End. All other methods on a Range
+// require that the Range is well-formed.
+func (r AddrRange) WellFormed() bool {
+ return r.Start <= r.End
+}
+
+// Length returns the length of the range.
+func (r AddrRange) Length() Addr {
+ return r.End - r.Start
+}
+
+// Contains returns true if r contains x.
+func (r AddrRange) Contains(x Addr) bool {
+ return r.Start <= x && x < r.End
+}
+
+// Overlaps returns true if r and r2 overlap.
+func (r AddrRange) Overlaps(r2 AddrRange) bool {
+ return r.Start < r2.End && r2.Start < r.End
+}
+
+// IsSupersetOf returns true if r is a superset of r2; that is, the range r2 is
+// contained within r.
+func (r AddrRange) IsSupersetOf(r2 AddrRange) bool {
+ return r.Start <= r2.Start && r.End >= r2.End
+}
+
+// Intersect returns a range consisting of the intersection between r and r2.
+// If r and r2 do not overlap, Intersect returns a range with unspecified
+// bounds, but for which Length() == 0.
+func (r AddrRange) Intersect(r2 AddrRange) AddrRange {
+ if r.Start < r2.Start {
+ r.Start = r2.Start
+ }
+ if r.End > r2.End {
+ r.End = r2.End
+ }
+ if r.End < r.Start {
+ r.End = r.Start
+ }
+ return r
+}
+
+// CanSplitAt returns true if it is legal to split a segment spanning the range
+// r at x; that is, splitting at x would produce two ranges, both of which have
+// non-zero length.
+func (r AddrRange) CanSplitAt(x Addr) bool {
+ return r.Contains(x) && r.Start < x
+}
diff --git a/pkg/sentry/usermem/addr_range_seq_test.go b/pkg/sentry/usermem/addr_range_seq_test.go
deleted file mode 100644
index 82f735026..000000000
--- a/pkg/sentry/usermem/addr_range_seq_test.go
+++ /dev/null
@@ -1,197 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package usermem
-
-import (
- "testing"
-)
-
-var addrRangeSeqTests = []struct {
- desc string
- ranges []AddrRange
-}{
- {
- desc: "Empty sequence",
- },
- {
- desc: "Single empty AddrRange",
- ranges: []AddrRange{
- {0x10, 0x10},
- },
- },
- {
- desc: "Single non-empty AddrRange of length 1",
- ranges: []AddrRange{
- {0x10, 0x11},
- },
- },
- {
- desc: "Single non-empty AddrRange of length 2",
- ranges: []AddrRange{
- {0x10, 0x12},
- },
- },
- {
- desc: "Multiple non-empty AddrRanges",
- ranges: []AddrRange{
- {0x10, 0x11},
- {0x20, 0x22},
- },
- },
- {
- desc: "Multiple AddrRanges including empty AddrRanges",
- ranges: []AddrRange{
- {0x10, 0x10},
- {0x20, 0x20},
- {0x30, 0x33},
- {0x40, 0x44},
- {0x50, 0x50},
- {0x60, 0x60},
- {0x70, 0x77},
- {0x80, 0x88},
- {0x90, 0x90},
- {0xa0, 0xa0},
- },
- },
-}
-
-func testAddrRangeSeqEqualityWithTailIteration(t *testing.T, ars AddrRangeSeq, wantRanges []AddrRange) {
- var wantLen int64
- for _, ar := range wantRanges {
- wantLen += int64(ar.Length())
- }
-
- var i int
- for !ars.IsEmpty() {
- if gotLen := ars.NumBytes(); gotLen != wantLen {
- t.Errorf("Iteration %d: %v.NumBytes(): got %d, wanted %d", i, ars, gotLen, wantLen)
- }
- if gotN, wantN := ars.NumRanges(), len(wantRanges)-i; gotN != wantN {
- t.Errorf("Iteration %d: %v.NumRanges(): got %d, wanted %d", i, ars, gotN, wantN)
- }
- got := ars.Head()
- if i >= len(wantRanges) {
- t.Errorf("Iteration %d: %v.Head(): got %s, wanted <end of sequence>", i, ars, got)
- } else if want := wantRanges[i]; got != want {
- t.Errorf("Iteration %d: %v.Head(): got %s, wanted %s", i, ars, got, want)
- }
- ars = ars.Tail()
- wantLen -= int64(got.Length())
- i++
- }
- if gotLen := ars.NumBytes(); gotLen != 0 || wantLen != 0 {
- t.Errorf("Iteration %d: %v.NumBytes(): got %d, wanted %d (which should be 0)", i, ars, gotLen, wantLen)
- }
- if gotN := ars.NumRanges(); gotN != 0 {
- t.Errorf("Iteration %d: %v.NumRanges(): got %d, wanted 0", i, ars, gotN)
- }
-}
-
-func TestAddrRangeSeqTailIteration(t *testing.T) {
- for _, test := range addrRangeSeqTests {
- t.Run(test.desc, func(t *testing.T) {
- testAddrRangeSeqEqualityWithTailIteration(t, AddrRangeSeqFromSlice(test.ranges), test.ranges)
- })
- }
-}
-
-func TestAddrRangeSeqDropFirstEmpty(t *testing.T) {
- var ars AddrRangeSeq
- if got, want := ars.DropFirst(1), ars; got != want {
- t.Errorf("%v.DropFirst(1): got %v, wanted %v", ars, got, want)
- }
-}
-
-func TestAddrRangeSeqDropSingleByteIteration(t *testing.T) {
- // Tests AddrRangeSeq iteration using Head/DropFirst, simulating
- // I/O-per-AddrRange.
- for _, test := range addrRangeSeqTests {
- t.Run(test.desc, func(t *testing.T) {
- // Figure out what AddrRanges we expect to see.
- var wantLen int64
- var wantRanges []AddrRange
- for _, ar := range test.ranges {
- wantLen += int64(ar.Length())
- wantRanges = append(wantRanges, ar)
- if ar.Length() == 0 {
- // We "do" 0 bytes of I/O and then call DropFirst(0),
- // advancing to the next AddrRange.
- continue
- }
- // Otherwise we "do" 1 byte of I/O and then call DropFirst(1),
- // advancing the AddrRange by 1 byte, or to the next AddrRange
- // if this one is exhausted.
- for ar.Start++; ar.Length() != 0; ar.Start++ {
- wantRanges = append(wantRanges, ar)
- }
- }
- t.Logf("Expected AddrRanges: %s (%d bytes)", wantRanges, wantLen)
-
- ars := AddrRangeSeqFromSlice(test.ranges)
- var i int
- for !ars.IsEmpty() {
- if gotLen := ars.NumBytes(); gotLen != wantLen {
- t.Errorf("Iteration %d: %v.NumBytes(): got %d, wanted %d", i, ars, gotLen, wantLen)
- }
- got := ars.Head()
- if i >= len(wantRanges) {
- t.Errorf("Iteration %d: %v.Head(): got %s, wanted <end of sequence>", i, ars, got)
- } else if want := wantRanges[i]; got != want {
- t.Errorf("Iteration %d: %v.Head(): got %s, wanted %s", i, ars, got, want)
- }
- if got.Length() == 0 {
- ars = ars.DropFirst(0)
- } else {
- ars = ars.DropFirst(1)
- wantLen--
- }
- i++
- }
- if gotLen := ars.NumBytes(); gotLen != 0 || wantLen != 0 {
- t.Errorf("Iteration %d: %v.NumBytes(): got %d, wanted %d (which should be 0)", i, ars, gotLen, wantLen)
- }
- })
- }
-}
-
-func TestAddrRangeSeqTakeFirstEmpty(t *testing.T) {
- var ars AddrRangeSeq
- if got, want := ars.TakeFirst(1), ars; got != want {
- t.Errorf("%v.TakeFirst(1): got %v, wanted %v", ars, got, want)
- }
-}
-
-func TestAddrRangeSeqTakeFirst(t *testing.T) {
- ranges := []AddrRange{
- {0x10, 0x11},
- {0x20, 0x22},
- {0x30, 0x30},
- {0x40, 0x44},
- {0x50, 0x55},
- {0x60, 0x60},
- {0x70, 0x77},
- }
- ars := AddrRangeSeqFromSlice(ranges).TakeFirst(5)
- want := []AddrRange{
- {0x10, 0x11}, // +1 byte (total 1 byte), not truncated
- {0x20, 0x22}, // +2 bytes (total 3 bytes), not truncated
- {0x30, 0x30}, // +0 bytes (total 3 bytes), no change
- {0x40, 0x42}, // +2 bytes (total 5 bytes), partially truncated
- {0x50, 0x50}, // +0 bytes (total 5 bytes), fully truncated
- {0x60, 0x60}, // +0 bytes (total 5 bytes), "fully truncated" (no change)
- {0x70, 0x70}, // +0 bytes (total 5 bytes), fully truncated
- }
- testAddrRangeSeqEqualityWithTailIteration(t, ars, want)
-}
diff --git a/pkg/sentry/usermem/usermem_state_autogen.go b/pkg/sentry/usermem/usermem_state_autogen.go
new file mode 100755
index 000000000..39b92d108
--- /dev/null
+++ b/pkg/sentry/usermem/usermem_state_autogen.go
@@ -0,0 +1,49 @@
+// automatically generated by stateify.
+
+package usermem
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *AccessType) beforeSave() {}
+func (x *AccessType) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Read", &x.Read)
+ m.Save("Write", &x.Write)
+ m.Save("Execute", &x.Execute)
+}
+
+func (x *AccessType) afterLoad() {}
+func (x *AccessType) load(m state.Map) {
+ m.Load("Read", &x.Read)
+ m.Load("Write", &x.Write)
+ m.Load("Execute", &x.Execute)
+}
+
+func (x *Addr) save(m state.Map) {
+ m.SaveValue("", (uintptr)(*x))
+}
+
+func (x *Addr) load(m state.Map) {
+ m.LoadValue("", new(uintptr), func(y interface{}) { *x = (Addr)(y.(uintptr)) })
+}
+
+func (x *AddrRange) beforeSave() {}
+func (x *AddrRange) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Start", &x.Start)
+ m.Save("End", &x.End)
+}
+
+func (x *AddrRange) afterLoad() {}
+func (x *AddrRange) load(m state.Map) {
+ m.Load("Start", &x.Start)
+ m.Load("End", &x.End)
+}
+
+func init() {
+ state.Register("usermem.AccessType", (*AccessType)(nil), state.Fns{Save: (*AccessType).save, Load: (*AccessType).load})
+ state.Register("usermem.Addr", (*Addr)(nil), state.Fns{Save: (*Addr).save, Load: (*Addr).load})
+ state.Register("usermem.AddrRange", (*AddrRange)(nil), state.Fns{Save: (*AddrRange).save, Load: (*AddrRange).load})
+}
diff --git a/pkg/sentry/usermem/usermem_test.go b/pkg/sentry/usermem/usermem_test.go
deleted file mode 100644
index 299f64754..000000000
--- a/pkg/sentry/usermem/usermem_test.go
+++ /dev/null
@@ -1,424 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package usermem
-
-import (
- "bytes"
- "encoding/binary"
- "fmt"
- "reflect"
- "strings"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/safemem"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-// newContext returns a context.Context that we can use in these tests (we
-// can't use contexttest because it depends on usermem).
-func newContext() context.Context {
- return context.Background()
-}
-
-func newBytesIOString(s string) *BytesIO {
- return &BytesIO{[]byte(s)}
-}
-
-func TestBytesIOCopyOutSuccess(t *testing.T) {
- b := newBytesIOString("ABCDE")
- n, err := b.CopyOut(newContext(), 1, []byte("foo"), IOOpts{})
- if wantN := 3; n != wantN || err != nil {
- t.Errorf("CopyOut: got (%v, %v), wanted (%v, nil)", n, err, wantN)
- }
- if got, want := b.Bytes, []byte("AfooE"); !bytes.Equal(got, want) {
- t.Errorf("Bytes: got %q, wanted %q", got, want)
- }
-}
-
-func TestBytesIOCopyOutFailure(t *testing.T) {
- b := newBytesIOString("ABC")
- n, err := b.CopyOut(newContext(), 1, []byte("foo"), IOOpts{})
- if wantN, wantErr := 2, syserror.EFAULT; n != wantN || err != wantErr {
- t.Errorf("CopyOut: got (%v, %v), wanted (%v, %v)", n, err, wantN, wantErr)
- }
- if got, want := b.Bytes, []byte("Afo"); !bytes.Equal(got, want) {
- t.Errorf("Bytes: got %q, wanted %q", got, want)
- }
-}
-
-func TestBytesIOCopyInSuccess(t *testing.T) {
- b := newBytesIOString("AfooE")
- var dst [3]byte
- n, err := b.CopyIn(newContext(), 1, dst[:], IOOpts{})
- if wantN := 3; n != wantN || err != nil {
- t.Errorf("CopyIn: got (%v, %v), wanted (%v, nil)", n, err, wantN)
- }
- if got, want := dst[:], []byte("foo"); !bytes.Equal(got, want) {
- t.Errorf("dst: got %q, wanted %q", got, want)
- }
-}
-
-func TestBytesIOCopyInFailure(t *testing.T) {
- b := newBytesIOString("Afo")
- var dst [3]byte
- n, err := b.CopyIn(newContext(), 1, dst[:], IOOpts{})
- if wantN, wantErr := 2, syserror.EFAULT; n != wantN || err != wantErr {
- t.Errorf("CopyIn: got (%v, %v), wanted (%v, %v)", n, err, wantN, wantErr)
- }
- if got, want := dst[:], []byte("fo\x00"); !bytes.Equal(got, want) {
- t.Errorf("dst: got %q, wanted %q", got, want)
- }
-}
-
-func TestBytesIOZeroOutSuccess(t *testing.T) {
- b := newBytesIOString("ABCD")
- n, err := b.ZeroOut(newContext(), 1, 2, IOOpts{})
- if wantN := int64(2); n != wantN || err != nil {
- t.Errorf("ZeroOut: got (%v, %v), wanted (%v, nil)", n, err, wantN)
- }
- if got, want := b.Bytes, []byte("A\x00\x00D"); !bytes.Equal(got, want) {
- t.Errorf("Bytes: got %q, wanted %q", got, want)
- }
-}
-
-func TestBytesIOZeroOutFailure(t *testing.T) {
- b := newBytesIOString("ABC")
- n, err := b.ZeroOut(newContext(), 1, 3, IOOpts{})
- if wantN, wantErr := int64(2), syserror.EFAULT; n != wantN || err != wantErr {
- t.Errorf("ZeroOut: got (%v, %v), wanted (%v, %v)", n, err, wantN, wantErr)
- }
- if got, want := b.Bytes, []byte("A\x00\x00"); !bytes.Equal(got, want) {
- t.Errorf("Bytes: got %q, wanted %q", got, want)
- }
-}
-
-func TestBytesIOCopyOutFromSuccess(t *testing.T) {
- b := newBytesIOString("ABCDEFGH")
- n, err := b.CopyOutFrom(newContext(), AddrRangeSeqFromSlice([]AddrRange{
- {Start: 4, End: 7},
- {Start: 1, End: 4},
- }), safemem.FromIOReader{bytes.NewBufferString("barfoo")}, IOOpts{})
- if wantN := int64(6); n != wantN || err != nil {
- t.Errorf("CopyOutFrom: got (%v, %v), wanted (%v, nil)", n, err, wantN)
- }
- if got, want := b.Bytes, []byte("AfoobarH"); !bytes.Equal(got, want) {
- t.Errorf("Bytes: got %q, wanted %q", got, want)
- }
-}
-
-func TestBytesIOCopyOutFromFailure(t *testing.T) {
- b := newBytesIOString("ABCDE")
- n, err := b.CopyOutFrom(newContext(), AddrRangeSeqFromSlice([]AddrRange{
- {Start: 1, End: 4},
- {Start: 4, End: 7},
- }), safemem.FromIOReader{bytes.NewBufferString("foobar")}, IOOpts{})
- if wantN, wantErr := int64(4), syserror.EFAULT; n != wantN || err != wantErr {
- t.Errorf("CopyOutFrom: got (%v, %v), wanted (%v, %v)", n, err, wantN, wantErr)
- }
- if got, want := b.Bytes, []byte("Afoob"); !bytes.Equal(got, want) {
- t.Errorf("Bytes: got %q, wanted %q", got, want)
- }
-}
-
-func TestBytesIOCopyInToSuccess(t *testing.T) {
- b := newBytesIOString("AfoobarH")
- var dst bytes.Buffer
- n, err := b.CopyInTo(newContext(), AddrRangeSeqFromSlice([]AddrRange{
- {Start: 4, End: 7},
- {Start: 1, End: 4},
- }), safemem.FromIOWriter{&dst}, IOOpts{})
- if wantN := int64(6); n != wantN || err != nil {
- t.Errorf("CopyInTo: got (%v, %v), wanted (%v, nil)", n, err, wantN)
- }
- if got, want := dst.Bytes(), []byte("barfoo"); !bytes.Equal(got, want) {
- t.Errorf("dst.Bytes(): got %q, wanted %q", got, want)
- }
-}
-
-func TestBytesIOCopyInToFailure(t *testing.T) {
- b := newBytesIOString("Afoob")
- var dst bytes.Buffer
- n, err := b.CopyInTo(newContext(), AddrRangeSeqFromSlice([]AddrRange{
- {Start: 1, End: 4},
- {Start: 4, End: 7},
- }), safemem.FromIOWriter{&dst}, IOOpts{})
- if wantN, wantErr := int64(4), syserror.EFAULT; n != wantN || err != wantErr {
- t.Errorf("CopyOutFrom: got (%v, %v), wanted (%v, %v)", n, err, wantN, wantErr)
- }
- if got, want := dst.Bytes(), []byte("foob"); !bytes.Equal(got, want) {
- t.Errorf("dst.Bytes(): got %q, wanted %q", got, want)
- }
-}
-
-type testStruct struct {
- Int8 int8
- Uint8 uint8
- Int16 int16
- Uint16 uint16
- Int32 int32
- Uint32 uint32
- Int64 int64
- Uint64 uint64
-}
-
-func TestCopyObject(t *testing.T) {
- wantObj := testStruct{1, 2, 3, 4, 5, 6, 7, 8}
- wantN := binary.Size(wantObj)
- b := &BytesIO{make([]byte, wantN)}
- ctx := newContext()
- if n, err := CopyObjectOut(ctx, b, 0, &wantObj, IOOpts{}); n != wantN || err != nil {
- t.Fatalf("CopyObjectOut: got (%v, %v), wanted (%v, nil)", n, err, wantN)
- }
- var gotObj testStruct
- if n, err := CopyObjectIn(ctx, b, 0, &gotObj, IOOpts{}); n != wantN || err != nil {
- t.Errorf("CopyObjectIn: got (%v, %v), wanted (%v, nil)", n, err, wantN)
- }
- if gotObj != wantObj {
- t.Errorf("CopyObject round trip: got %+v, wanted %+v", gotObj, wantObj)
- }
-}
-
-func TestCopyStringInShort(t *testing.T) {
- // Tests for string length <= copyStringIncrement.
- want := strings.Repeat("A", copyStringIncrement-2)
- mem := want + "\x00"
- if got, err := CopyStringIn(newContext(), newBytesIOString(mem), 0, 2*copyStringIncrement, IOOpts{}); got != want || err != nil {
- t.Errorf("CopyStringIn: got (%q, %v), wanted (%q, nil)", got, err, want)
- }
-}
-
-func TestCopyStringInLong(t *testing.T) {
- // Tests for copyStringIncrement < string length <= copyStringMaxInitBufLen
- // (requiring multiple calls to IO.CopyIn()).
- want := strings.Repeat("A", copyStringIncrement*3/4) + strings.Repeat("B", copyStringIncrement*3/4)
- mem := want + "\x00"
- if got, err := CopyStringIn(newContext(), newBytesIOString(mem), 0, 2*copyStringIncrement, IOOpts{}); got != want || err != nil {
- t.Errorf("CopyStringIn: got (%q, %v), wanted (%q, nil)", got, err, want)
- }
-}
-
-func TestCopyStringInVeryLong(t *testing.T) {
- // Tests for string length > copyStringMaxInitBufLen (requiring buffer
- // reallocation).
- want := strings.Repeat("A", copyStringMaxInitBufLen*3/4) + strings.Repeat("B", copyStringMaxInitBufLen*3/4)
- mem := want + "\x00"
- if got, err := CopyStringIn(newContext(), newBytesIOString(mem), 0, 2*copyStringMaxInitBufLen, IOOpts{}); got != want || err != nil {
- t.Errorf("CopyStringIn: got (%q, %v), wanted (%q, nil)", got, err, want)
- }
-}
-
-func TestCopyStringInNoTerminatingZeroByte(t *testing.T) {
- want := strings.Repeat("A", copyStringIncrement-1)
- got, err := CopyStringIn(newContext(), newBytesIOString(want), 0, 2*copyStringIncrement, IOOpts{})
- if wantErr := syserror.EFAULT; got != want || err != wantErr {
- t.Errorf("CopyStringIn: got (%q, %v), wanted (%q, %v)", got, err, want, wantErr)
- }
-}
-
-func TestCopyStringInTruncatedByMaxlen(t *testing.T) {
- got, err := CopyStringIn(newContext(), newBytesIOString(strings.Repeat("A", 10)), 0, 5, IOOpts{})
- if want, wantErr := strings.Repeat("A", 5), syserror.ENAMETOOLONG; got != want || err != wantErr {
- t.Errorf("CopyStringIn: got (%q, %v), wanted (%q, %v)", got, err, want, wantErr)
- }
-}
-
-func TestCopyInt32StringsInVec(t *testing.T) {
- for _, test := range []struct {
- str string
- n int
- initial []int32
- final []int32
- }{
- {
- str: "100 200",
- n: len("100 200"),
- initial: []int32{1, 2},
- final: []int32{100, 200},
- },
- {
- // Fewer values ok
- str: "100",
- n: len("100"),
- initial: []int32{1, 2},
- final: []int32{100, 2},
- },
- {
- // Extra values ok
- str: "100 200 300",
- n: len("100 200 "),
- initial: []int32{1, 2},
- final: []int32{100, 200},
- },
- {
- // Leading and trailing whitespace ok
- str: " 100\t200\n",
- n: len(" 100\t200\n"),
- initial: []int32{1, 2},
- final: []int32{100, 200},
- },
- } {
- t.Run(fmt.Sprintf("%q", test.str), func(t *testing.T) {
- src := BytesIOSequence([]byte(test.str))
- dsts := append([]int32(nil), test.initial...)
- if n, err := CopyInt32StringsInVec(newContext(), src.IO, src.Addrs, dsts, src.Opts); n != int64(test.n) || err != nil {
- t.Errorf("CopyInt32StringsInVec: got (%d, %v), wanted (%d, nil)", n, err, test.n)
- }
- if !reflect.DeepEqual(dsts, test.final) {
- t.Errorf("dsts: got %v, wanted %v", dsts, test.final)
- }
- })
- }
-}
-
-func TestCopyInt32StringsInVecRequiresOneValidValue(t *testing.T) {
- for _, s := range []string{"", "\n", "a123"} {
- t.Run(fmt.Sprintf("%q", s), func(t *testing.T) {
- src := BytesIOSequence([]byte(s))
- initial := []int32{1, 2}
- dsts := append([]int32(nil), initial...)
- if n, err := CopyInt32StringsInVec(newContext(), src.IO, src.Addrs, dsts, src.Opts); err != syserror.EINVAL {
- t.Errorf("CopyInt32StringsInVec: got (%d, %v), wanted (_, %v)", n, err, syserror.EINVAL)
- }
- if !reflect.DeepEqual(dsts, initial) {
- t.Errorf("dsts: got %v, wanted %v", dsts, initial)
- }
- })
- }
-}
-
-func TestIOSequenceCopyOut(t *testing.T) {
- buf := []byte("ABCD")
- s := BytesIOSequence(buf)
-
- // CopyOut limited by len(src).
- n, err := s.CopyOut(newContext(), []byte("fo"))
- if wantN := 2; n != wantN || err != nil {
- t.Errorf("CopyOut: got (%v, %v), wanted (%v, nil)", n, err, wantN)
- }
- if want := []byte("foCD"); !bytes.Equal(buf, want) {
- t.Errorf("buf: got %q, wanted %q", buf, want)
- }
- s = s.DropFirst(2)
- if got, want := s.NumBytes(), int64(2); got != want {
- t.Errorf("NumBytes: got %v, wanted %v", got, want)
- }
-
- // CopyOut limited by s.NumBytes().
- n, err = s.CopyOut(newContext(), []byte("obar"))
- if wantN := 2; n != wantN || err != nil {
- t.Errorf("CopyOut: got (%v, %v), wanted (%v, nil)", n, err, wantN)
- }
- if want := []byte("foob"); !bytes.Equal(buf, want) {
- t.Errorf("buf: got %q, wanted %q", buf, want)
- }
- s = s.DropFirst(2)
- if got, want := s.NumBytes(), int64(0); got != want {
- t.Errorf("NumBytes: got %v, wanted %v", got, want)
- }
-}
-
-func TestIOSequenceCopyIn(t *testing.T) {
- s := BytesIOSequence([]byte("foob"))
- dst := []byte("ABCDEF")
-
- // CopyIn limited by len(dst).
- n, err := s.CopyIn(newContext(), dst[:2])
- if wantN := 2; n != wantN || err != nil {
- t.Errorf("CopyIn: got (%v, %v), wanted (%v, nil)", n, err, wantN)
- }
- if want := []byte("foCDEF"); !bytes.Equal(dst, want) {
- t.Errorf("dst: got %q, wanted %q", dst, want)
- }
- s = s.DropFirst(2)
- if got, want := s.NumBytes(), int64(2); got != want {
- t.Errorf("NumBytes: got %v, wanted %v", got, want)
- }
-
- // CopyIn limited by s.Remaining().
- n, err = s.CopyIn(newContext(), dst[2:])
- if wantN := 2; n != wantN || err != nil {
- t.Errorf("CopyIn: got (%v, %v), wanted (%v, nil)", n, err, wantN)
- }
- if want := []byte("foobEF"); !bytes.Equal(dst, want) {
- t.Errorf("dst: got %q, wanted %q", dst, want)
- }
- s = s.DropFirst(2)
- if got, want := s.NumBytes(), int64(0); got != want {
- t.Errorf("NumBytes: got %v, wanted %v", got, want)
- }
-}
-
-func TestIOSequenceZeroOut(t *testing.T) {
- buf := []byte("ABCD")
- s := BytesIOSequence(buf)
-
- // ZeroOut limited by toZero.
- n, err := s.ZeroOut(newContext(), 2)
- if wantN := int64(2); n != wantN || err != nil {
- t.Errorf("ZeroOut: got (%v, %v), wanted (%v, nil)", n, err, wantN)
- }
- if want := []byte("\x00\x00CD"); !bytes.Equal(buf, want) {
- t.Errorf("buf: got %q, wanted %q", buf, want)
- }
- s = s.DropFirst(2)
- if got, want := s.NumBytes(), int64(2); got != want {
- t.Errorf("NumBytes: got %v, wanted %v", got, want)
- }
-
- // ZeroOut limited by s.NumBytes().
- n, err = s.ZeroOut(newContext(), 4)
- if wantN := int64(2); n != wantN || err != nil {
- t.Errorf("CopyOut: got (%v, %v), wanted (%v, nil)", n, err, wantN)
- }
- if want := []byte("\x00\x00\x00\x00"); !bytes.Equal(buf, want) {
- t.Errorf("buf: got %q, wanted %q", buf, want)
- }
- s = s.DropFirst(2)
- if got, want := s.NumBytes(), int64(0); got != want {
- t.Errorf("NumBytes: got %v, wanted %v", got, want)
- }
-}
-
-func TestIOSequenceTakeFirst(t *testing.T) {
- s := BytesIOSequence([]byte("foobar"))
- if got, want := s.NumBytes(), int64(6); got != want {
- t.Errorf("NumBytes: got %v, wanted %v", got, want)
- }
-
- s = s.TakeFirst(3)
- if got, want := s.NumBytes(), int64(3); got != want {
- t.Errorf("NumBytes: got %v, wanted %v", got, want)
- }
-
- // TakeFirst(n) where n > s.NumBytes() is a no-op.
- s = s.TakeFirst(9)
- if got, want := s.NumBytes(), int64(3); got != want {
- t.Errorf("NumBytes: got %v, wanted %v", got, want)
- }
-
- var dst [3]byte
- n, err := s.CopyIn(newContext(), dst[:])
- if wantN := 3; n != wantN || err != nil {
- t.Errorf("CopyIn: got (%v, %v), wanted (%v, nil)", n, err, wantN)
- }
- if got, want := dst[:], []byte("foo"); !bytes.Equal(got, want) {
- t.Errorf("dst: got %q, wanted %q", got, want)
- }
- s = s.DropFirst(3)
- if got, want := s.NumBytes(), int64(0); got != want {
- t.Errorf("NumBytes: got %v, wanted %v", got, want)
- }
-}
diff --git a/pkg/sentry/vfs/BUILD b/pkg/sentry/vfs/BUILD
deleted file mode 100644
index eff4b44f6..000000000
--- a/pkg/sentry/vfs/BUILD
+++ /dev/null
@@ -1,57 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "vfs",
- srcs = [
- "context.go",
- "debug.go",
- "dentry.go",
- "file_description.go",
- "file_description_impl_util.go",
- "filesystem.go",
- "filesystem_type.go",
- "mount.go",
- "mount_unsafe.go",
- "options.go",
- "permissions.go",
- "resolving_path.go",
- "syscalls.go",
- "testutil.go",
- "vfs.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sentry/vfs",
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/fspath",
- "//pkg/sentry/arch",
- "//pkg/sentry/context",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/memmap",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- "//pkg/waiter",
- "//third_party/gvsync",
- ],
-)
-
-go_test(
- name = "vfs_test",
- size = "small",
- srcs = [
- "file_description_impl_util_test.go",
- "mount_test.go",
- ],
- embed = [":vfs"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/sentry/context",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/usermem",
- "//pkg/syserror",
- ],
-)
diff --git a/pkg/sentry/vfs/README.md b/pkg/sentry/vfs/README.md
deleted file mode 100644
index 7847854bc..000000000
--- a/pkg/sentry/vfs/README.md
+++ /dev/null
@@ -1,197 +0,0 @@
-# The gVisor Virtual Filesystem
-
-THIS PACKAGE IS CURRENTLY EXPERIMENTAL AND NOT READY OR ENABLED FOR PRODUCTION
-USE. For the filesystem implementation currently used by gVisor, see the `fs`
-package.
-
-## Implementation Notes
-
-### Reference Counting
-
-Filesystem, Dentry, Mount, MountNamespace, and FileDescription are all
-reference-counted. Mount and MountNamespace are exclusively VFS-managed; when
-their reference count reaches zero, VFS releases their resources. Filesystem and
-FileDescription management is shared between VFS and filesystem implementations;
-when their reference count reaches zero, VFS notifies the implementation by
-calling `FilesystemImpl.Release()` or `FileDescriptionImpl.Release()`
-respectively and then releases VFS-owned resources. Dentries are exclusively
-managed by filesystem implementations; reference count changes are abstracted
-through DentryImpl, which should release resources when reference count reaches
-zero.
-
-Filesystem references are held by:
-
-- Mount: Each referenced Mount holds a reference on the mounted Filesystem.
-
-Dentry references are held by:
-
-- FileDescription: Each referenced FileDescription holds a reference on the
- Dentry through which it was opened, via `FileDescription.vd.dentry`.
-
-- Mount: Each referenced Mount holds a reference on its mount point and on the
- mounted filesystem root. The mount point is mutable (`mount(MS_MOVE)`).
-
-Mount references are held by:
-
-- FileDescription: Each referenced FileDescription holds a reference on the
- Mount on which it was opened, via `FileDescription.vd.mount`.
-
-- Mount: Each referenced Mount holds a reference on its parent, which is the
- mount containing its mount point.
-
-- VirtualFilesystem: A reference is held on all Mounts that are attached
- (reachable by Mount traversal).
-
-MountNamespace and FileDescription references are held by users of VFS. The
-expectation is that each `kernel.Task` holds a reference on its corresponding
-MountNamespace, and each file descriptor holds a reference on its represented
-FileDescription.
-
-Notes:
-
-- Dentries do not hold a reference on their owning Filesystem. Instead, all
- uses of a Dentry occur in the context of a Mount, which holds a reference on
- the relevant Filesystem (see e.g. the VirtualDentry type). As a corollary,
- when releasing references on both a Dentry and its corresponding Mount, the
- Dentry's reference must be released first (because releasing the Mount's
- reference may release the last reference on the Filesystem, whose state may
- be required to release the Dentry reference).
-
-### The Inheritance Pattern
-
-Filesystem, Dentry, and FileDescription are all concepts featuring both state
-that must be shared between VFS and filesystem implementations, and operations
-that are implementation-defined. To facilitate this, each of these three
-concepts follows the same pattern, shown below for Dentry:
-
-```go
-// Dentry represents a node in a filesystem tree.
-type Dentry struct {
- // VFS-required dentry state.
- parent *Dentry
- // ...
-
- // impl is the DentryImpl associated with this Dentry. impl is immutable.
- // This should be the last field in Dentry.
- impl DentryImpl
-}
-
-// Init must be called before first use of d.
-func (d *Dentry) Init(impl DentryImpl) {
- d.impl = impl
-}
-
-// Impl returns the DentryImpl associated with d.
-func (d *Dentry) Impl() DentryImpl {
- return d.impl
-}
-
-// DentryImpl contains implementation-specific details of a Dentry.
-// Implementations of DentryImpl should contain their associated Dentry by
-// value as their first field.
-type DentryImpl interface {
- // VFS-required implementation-defined dentry operations.
- IncRef()
- // ...
-}
-```
-
-This construction, which is essentially a type-safe analogue to Linux's
-`container_of` pattern, has the following properties:
-
-- VFS works almost exclusively with pointers to Dentry rather than DentryImpl
- interface objects, such as in the type of `Dentry.parent`. This avoids
- interface method calls (which are somewhat expensive to perform, and defeat
- inlining and escape analysis), reduces the size of VFS types (since an
- interface object is two pointers in size), and allows pointers to be loaded
- and stored atomically using `sync/atomic`. Implementation-defined behavior
- is accessed via `Dentry.impl` when required.
-
-- Filesystem implementations can access the implementation-defined state
- associated with objects of VFS types by type-asserting or type-switching
- (e.g. `Dentry.Impl().(*myDentry)`). Type assertions to a concrete type
- require only an equality comparison of the interface object's type pointer
- to a static constant, and are consequently very fast.
-
-- Filesystem implementations can access the VFS state associated with objects
- of implementation-defined types directly.
-
-- VFS and implementation-defined state for a given type occupy the same
- object, minimizing memory allocations and maximizing memory locality. `impl`
- is the last field in `Dentry`, and `Dentry` is the first field in
- `DentryImpl` implementations, for similar reasons: this tends to cause
- fetching of the `Dentry.impl` interface object to also fetch `DentryImpl`
- fields, either because they are in the same cache line or via next-line
- prefetching.
-
-## Future Work
-
-- Most `mount(2)` features, and unmounting, are incomplete.
-
-- VFS1 filesystems are not directly compatible with VFS2. It may be possible
- to implement shims that implement `vfs.FilesystemImpl` for
- `fs.MountNamespace`, `vfs.DentryImpl` for `fs.Dirent`, and
- `vfs.FileDescriptionImpl` for `fs.File`, which may be adequate for
- filesystems that are not performance-critical (e.g. sysfs); however, it is
- not clear that this will be less effort than simply porting the filesystems
- in question. Practically speaking, the following filesystems will probably
- need to be ported or made compatible through a shim to evaluate filesystem
- performance on realistic workloads:
-
- - devfs/procfs/sysfs, which will realistically be necessary to execute
- most applications. (Note that procfs and sysfs do not support hard
- links, so they do not require the complexity of separate inode objects.
- Also note that Linux's /dev is actually a variant of tmpfs called
- devtmpfs.)
-
- - tmpfs. This should be relatively straightforward: copy/paste memfs,
- store regular file contents in pgalloc-allocated memory instead of
- `[]byte`, and add support for file timestamps. (In fact, it probably
- makes more sense to convert memfs to tmpfs and not keep the former.)
-
- - A remote filesystem, either lisafs (if it is ready by the time that
- other benchmarking prerequisites are) or v9fs (aka 9P, aka gofers).
-
- - epoll files.
-
- Filesystems that will need to be ported before switching to VFS2, but can
- probably be skipped for early testing:
-
- - overlayfs, which is needed for (at least) synthetic mount points.
-
- - Support for host ttys.
-
- - timerfd files.
-
- Filesystems that can be probably dropped:
-
- - ashmem, which is far too incomplete to use.
-
- - binder, which is similarly far too incomplete to use.
-
- - whitelistfs, which we are already actively attempting to remove.
-
-- Save/restore. For instance, it is unclear if the current implementation of
- the `state` package supports the inheritance pattern described above.
-
-- Many features that were previously implemented by VFS must now be
- implemented by individual filesystems (though, in most cases, this should
- consist of calls to hooks or libraries provided by `vfs` or other packages).
- This includes, but is not necessarily limited to:
-
- - Block and character device special files
-
- - Inotify
-
- - File locking
-
- - `O_ASYNC`
-
-- Reference counts in the `vfs` package do not use the `refs` package since
- `refs.AtomicRefCount` adds 64 bytes of overhead to each 8-byte reference
- count, resulting in considerable cache bloat. 24 bytes of this overhead is
- for weak reference support, which have poor performance and will not be used
- by VFS2. The remaining 40 bytes is to store a descriptive string and stack
- trace for reference leak checking; we can support reference leak checking
- without incurring this space overhead by including the applicable
- information directly in finalizers for applicable types.
diff --git a/pkg/sentry/vfs/context.go b/pkg/sentry/vfs/context.go
deleted file mode 100644
index 32cf9151b..000000000
--- a/pkg/sentry/vfs/context.go
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package vfs
-
-import (
- "gvisor.dev/gvisor/pkg/sentry/context"
-)
-
-// contextID is this package's type for context.Context.Value keys.
-type contextID int
-
-const (
- // CtxMountNamespace is a Context.Value key for a MountNamespace.
- CtxMountNamespace contextID = iota
-)
-
-// MountNamespaceFromContext returns the MountNamespace used by ctx. It does
-// not take a reference on the returned MountNamespace. If ctx is not
-// associated with a MountNamespace, MountNamespaceFromContext returns nil.
-func MountNamespaceFromContext(ctx context.Context) *MountNamespace {
- if v := ctx.Value(CtxMountNamespace); v != nil {
- return v.(*MountNamespace)
- }
- return nil
-}
diff --git a/pkg/sentry/vfs/debug.go b/pkg/sentry/vfs/debug.go
deleted file mode 100644
index 0ed20f249..000000000
--- a/pkg/sentry/vfs/debug.go
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package vfs
-
-const (
- // If checkInvariants is true, perform runtime checks for invariants
- // expected by the vfs package. This is normally disabled since VFS is
- // often a hot path.
- checkInvariants = false
-)
diff --git a/pkg/sentry/vfs/dentry.go b/pkg/sentry/vfs/dentry.go
deleted file mode 100644
index 45912fc58..000000000
--- a/pkg/sentry/vfs/dentry.go
+++ /dev/null
@@ -1,347 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package vfs
-
-import (
- "fmt"
- "sync/atomic"
-
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-// Dentry represents a node in a Filesystem tree which may represent a file.
-//
-// Dentries are reference-counted. Unless otherwise specified, all Dentry
-// methods require that a reference is held.
-//
-// A Dentry transitions through up to 3 different states through its lifetime:
-//
-// - Dentries are initially "independent". Independent Dentries have no parent,
-// and consequently no name.
-//
-// - Dentry.InsertChild() causes an independent Dentry to become a "child" of
-// another Dentry. A child node has a parent node, and a name in that parent,
-// both of which are mutable by DentryMoveChild(). Each child Dentry's name is
-// unique within its parent.
-//
-// - Dentry.RemoveChild() causes a child Dentry to become "disowned". A
-// disowned Dentry can still refer to its former parent and its former name in
-// said parent, but the disowned Dentry is no longer reachable from its parent,
-// and a new Dentry with the same name may become a child of the parent. (This
-// is analogous to a struct dentry being "unhashed" in Linux.)
-//
-// Dentry is loosely analogous to Linux's struct dentry, but:
-//
-// - VFS does not associate Dentries with inodes. gVisor interacts primarily
-// with filesystems that are accessed through filesystem APIs (as opposed to
-// raw block devices); many such APIs support only paths and file descriptors,
-// and not inodes. Furthermore, when parties outside the scope of VFS can
-// rename inodes on such filesystems, VFS generally cannot "follow" the rename,
-// both due to synchronization issues and because it may not even be able to
-// name the destination path; this implies that it would in fact be *incorrect*
-// for Dentries to be associated with inodes on such filesystems. Consequently,
-// operations that are inode operations in Linux are FilesystemImpl methods
-// and/or FileDescriptionImpl methods in gVisor's VFS. Filesystems that do
-// support inodes may store appropriate state in implementations of DentryImpl.
-//
-// - VFS does not provide synchronization for mutable Dentry fields, other than
-// mount-related ones.
-//
-// - VFS does not require that Dentries are instantiated for all paths accessed
-// through VFS, only those that are tracked beyond the scope of a single
-// Filesystem operation. This includes file descriptions, mount points, mount
-// roots, process working directories, and chroots. This avoids instantiation
-// of Dentries for operations on mutable remote filesystems that can't actually
-// cache any state in the Dentry.
-//
-// - For the reasons above, VFS is not directly responsible for managing Dentry
-// lifetime. Dentry reference counts only indicate the extent to which VFS
-// requires Dentries to exist; Filesystems may elect to cache or discard
-// Dentries with zero references.
-type Dentry struct {
- // parent is this Dentry's parent in this Filesystem. If this Dentry is
- // independent, parent is nil.
- parent *Dentry
-
- // name is this Dentry's name in parent.
- name string
-
- flags uint32
-
- // mounts is the number of Mounts for which this Dentry is Mount.point.
- // mounts is accessed using atomic memory operations.
- mounts uint32
-
- // children are child Dentries.
- children map[string]*Dentry
-
- // impl is the DentryImpl associated with this Dentry. impl is immutable.
- // This should be the last field in Dentry.
- impl DentryImpl
-}
-
-const (
- // dflagsDisownedMask is set in Dentry.flags if the Dentry has been
- // disowned.
- dflagsDisownedMask = 1 << iota
-)
-
-// Init must be called before first use of d.
-func (d *Dentry) Init(impl DentryImpl) {
- d.impl = impl
-}
-
-// Impl returns the DentryImpl associated with d.
-func (d *Dentry) Impl() DentryImpl {
- return d.impl
-}
-
-// DentryImpl contains implementation details for a Dentry. Implementations of
-// DentryImpl should contain their associated Dentry by value as their first
-// field.
-type DentryImpl interface {
- // IncRef increments the Dentry's reference count. A Dentry with a non-zero
- // reference count must remain coherent with the state of the filesystem.
- IncRef(fs *Filesystem)
-
- // TryIncRef increments the Dentry's reference count and returns true. If
- // the Dentry's reference count is zero, TryIncRef may do nothing and
- // return false. (It is also permitted to succeed if it can restore the
- // guarantee that the Dentry is coherent with the state of the filesystem.)
- //
- // TryIncRef does not require that a reference is held on the Dentry.
- TryIncRef(fs *Filesystem) bool
-
- // DecRef decrements the Dentry's reference count.
- DecRef(fs *Filesystem)
-}
-
-// IsDisowned returns true if d is disowned.
-func (d *Dentry) IsDisowned() bool {
- return atomic.LoadUint32(&d.flags)&dflagsDisownedMask != 0
-}
-
-// Preconditions: !d.IsDisowned().
-func (d *Dentry) setDisowned() {
- atomic.AddUint32(&d.flags, dflagsDisownedMask)
-}
-
-func (d *Dentry) isMounted() bool {
- return atomic.LoadUint32(&d.mounts) != 0
-}
-
-func (d *Dentry) incRef(fs *Filesystem) {
- d.impl.IncRef(fs)
-}
-
-func (d *Dentry) tryIncRef(fs *Filesystem) bool {
- return d.impl.TryIncRef(fs)
-}
-
-func (d *Dentry) decRef(fs *Filesystem) {
- d.impl.DecRef(fs)
-}
-
-// These functions are exported so that filesystem implementations can use
-// them. The vfs package, and users of VFS, should not call these functions.
-// Unless otherwise specified, these methods require that there are no
-// concurrent mutators of d.
-
-// Name returns d's name in its parent in its owning Filesystem. If d is
-// independent, Name returns an empty string.
-func (d *Dentry) Name() string {
- return d.name
-}
-
-// Parent returns d's parent in its owning Filesystem. It does not take a
-// reference on the returned Dentry. If d is independent, Parent returns nil.
-func (d *Dentry) Parent() *Dentry {
- return d.parent
-}
-
-// ParentOrSelf is equivalent to Parent, but returns d if d is independent.
-func (d *Dentry) ParentOrSelf() *Dentry {
- if d.parent == nil {
- return d
- }
- return d.parent
-}
-
-// Child returns d's child with the given name in its owning Filesystem. It
-// does not take a reference on the returned Dentry. If no such child exists,
-// Child returns nil.
-func (d *Dentry) Child(name string) *Dentry {
- return d.children[name]
-}
-
-// HasChildren returns true if d has any children.
-func (d *Dentry) HasChildren() bool {
- return len(d.children) != 0
-}
-
-// InsertChild makes child a child of d with the given name.
-//
-// InsertChild is a mutator of d and child.
-//
-// Preconditions: child must be an independent Dentry. d and child must be from
-// the same Filesystem. d must not already have a child with the given name.
-func (d *Dentry) InsertChild(child *Dentry, name string) {
- if checkInvariants {
- if _, ok := d.children[name]; ok {
- panic(fmt.Sprintf("parent already contains a child named %q", name))
- }
- if child.parent != nil || child.name != "" {
- panic(fmt.Sprintf("child is not independent: parent = %v, name = %q", child.parent, child.name))
- }
- }
- if d.children == nil {
- d.children = make(map[string]*Dentry)
- }
- d.children[name] = child
- child.parent = d
- child.name = name
-}
-
-// PrepareDeleteDentry must be called before attempting to delete the file
-// represented by d. If PrepareDeleteDentry succeeds, the caller must call
-// AbortDeleteDentry or CommitDeleteDentry depending on the deletion's outcome.
-//
-// Preconditions: d is a child Dentry.
-func (vfs *VirtualFilesystem) PrepareDeleteDentry(mntns *MountNamespace, d *Dentry) error {
- if checkInvariants {
- if d.parent == nil {
- panic("d is independent")
- }
- if d.IsDisowned() {
- panic("d is already disowned")
- }
- }
- vfs.mountMu.RLock()
- if _, ok := mntns.mountpoints[d]; ok {
- vfs.mountMu.RUnlock()
- return syserror.EBUSY
- }
- // Return with vfs.mountMu locked, which will be unlocked by
- // AbortDeleteDentry or CommitDeleteDentry.
- return nil
-}
-
-// AbortDeleteDentry must be called after PrepareDeleteDentry if the deletion
-// fails.
-func (vfs *VirtualFilesystem) AbortDeleteDentry() {
- vfs.mountMu.RUnlock()
-}
-
-// CommitDeleteDentry must be called after the file represented by d is
-// deleted, and causes d to become disowned.
-//
-// Preconditions: PrepareDeleteDentry was previously called on d.
-func (vfs *VirtualFilesystem) CommitDeleteDentry(d *Dentry) {
- delete(d.parent.children, d.name)
- d.setDisowned()
- // TODO: lazily unmount mounts at d
- vfs.mountMu.RUnlock()
-}
-
-// DeleteDentry combines PrepareDeleteDentry and CommitDeleteDentry, as
-// appropriate for in-memory filesystems that don't need to ensure that some
-// external state change succeeds before committing the deletion.
-func (vfs *VirtualFilesystem) DeleteDentry(mntns *MountNamespace, d *Dentry) error {
- if err := vfs.PrepareDeleteDentry(mntns, d); err != nil {
- return err
- }
- vfs.CommitDeleteDentry(d)
- return nil
-}
-
-// PrepareRenameDentry must be called before attempting to rename the file
-// represented by from. If to is not nil, it represents the file that will be
-// replaced or exchanged by the rename. If PrepareRenameDentry succeeds, the
-// caller must call AbortRenameDentry, CommitRenameReplaceDentry, or
-// CommitRenameExchangeDentry depending on the rename's outcome.
-//
-// Preconditions: from is a child Dentry. If to is not nil, it must be a child
-// Dentry from the same Filesystem.
-func (vfs *VirtualFilesystem) PrepareRenameDentry(mntns *MountNamespace, from, to *Dentry) error {
- if checkInvariants {
- if from.parent == nil {
- panic("from is independent")
- }
- if from.IsDisowned() {
- panic("from is already disowned")
- }
- if to != nil {
- if to.parent == nil {
- panic("to is independent")
- }
- if to.IsDisowned() {
- panic("to is already disowned")
- }
- }
- }
- vfs.mountMu.RLock()
- if _, ok := mntns.mountpoints[from]; ok {
- vfs.mountMu.RUnlock()
- return syserror.EBUSY
- }
- if to != nil {
- if _, ok := mntns.mountpoints[to]; ok {
- vfs.mountMu.RUnlock()
- return syserror.EBUSY
- }
- }
- // Return with vfs.mountMu locked, which will be unlocked by
- // AbortRenameDentry, CommitRenameReplaceDentry, or
- // CommitRenameExchangeDentry.
- return nil
-}
-
-// AbortRenameDentry must be called after PrepareRenameDentry if the rename
-// fails.
-func (vfs *VirtualFilesystem) AbortRenameDentry() {
- vfs.mountMu.RUnlock()
-}
-
-// CommitRenameReplaceDentry must be called after the file represented by from
-// is renamed without RENAME_EXCHANGE. If to is not nil, it represents the file
-// that was replaced by from.
-//
-// Preconditions: PrepareRenameDentry was previously called on from and to.
-// newParent.Child(newName) == to.
-func (vfs *VirtualFilesystem) CommitRenameReplaceDentry(from, newParent *Dentry, newName string, to *Dentry) {
- if to != nil {
- to.setDisowned()
- // TODO: lazily unmount mounts at d
- }
- if newParent.children == nil {
- newParent.children = make(map[string]*Dentry)
- }
- newParent.children[newName] = from
- from.parent = newParent
- from.name = newName
- vfs.mountMu.RUnlock()
-}
-
-// CommitRenameExchangeDentry must be called after the files represented by
-// from and to are exchanged by rename(RENAME_EXCHANGE).
-//
-// Preconditions: PrepareRenameDentry was previously called on from and to.
-func (vfs *VirtualFilesystem) CommitRenameExchangeDentry(from, to *Dentry) {
- from.parent, to.parent = to.parent, from.parent
- from.name, to.name = to.name, from.name
- from.parent.children[from.name] = from
- to.parent.children[to.name] = to
- vfs.mountMu.RUnlock()
-}
diff --git a/pkg/sentry/vfs/file_description.go b/pkg/sentry/vfs/file_description.go
deleted file mode 100644
index 86bde7fb3..000000000
--- a/pkg/sentry/vfs/file_description.go
+++ /dev/null
@@ -1,213 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package vfs
-
-import (
- "sync/atomic"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/arch"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/memmap"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-// A FileDescription represents an open file description, which is the entity
-// referred to by a file descriptor (POSIX.1-2017 3.258 "Open File
-// Description").
-//
-// FileDescriptions are reference-counted. Unless otherwise specified, all
-// FileDescription methods require that a reference is held.
-//
-// FileDescription is analogous to Linux's struct file.
-type FileDescription struct {
- // refs is the reference count. refs is accessed using atomic memory
- // operations.
- refs int64
-
- // vd is the filesystem location at which this FileDescription was opened.
- // A reference is held on vd. vd is immutable.
- vd VirtualDentry
-
- // impl is the FileDescriptionImpl associated with this Filesystem. impl is
- // immutable. This should be the last field in FileDescription.
- impl FileDescriptionImpl
-}
-
-// Init must be called before first use of fd. It takes references on mnt and
-// d.
-func (fd *FileDescription) Init(impl FileDescriptionImpl, mnt *Mount, d *Dentry) {
- fd.refs = 1
- fd.vd = VirtualDentry{
- mount: mnt,
- dentry: d,
- }
- fd.vd.IncRef()
- fd.impl = impl
-}
-
-// Impl returns the FileDescriptionImpl associated with fd.
-func (fd *FileDescription) Impl() FileDescriptionImpl {
- return fd.impl
-}
-
-// VirtualDentry returns the location at which fd was opened. It does not take
-// a reference on the returned VirtualDentry.
-func (fd *FileDescription) VirtualDentry() VirtualDentry {
- return fd.vd
-}
-
-// IncRef increments fd's reference count.
-func (fd *FileDescription) IncRef() {
- atomic.AddInt64(&fd.refs, 1)
-}
-
-// DecRef decrements fd's reference count.
-func (fd *FileDescription) DecRef() {
- if refs := atomic.AddInt64(&fd.refs, -1); refs == 0 {
- fd.impl.Release()
- fd.vd.DecRef()
- } else if refs < 0 {
- panic("FileDescription.DecRef() called without holding a reference")
- }
-}
-
-// FileDescriptionImpl contains implementation details for an FileDescription.
-// Implementations of FileDescriptionImpl should contain their associated
-// FileDescription by value as their first field.
-//
-// For all functions that return linux.Statx, Statx.Uid and Statx.Gid will
-// be interpreted as IDs in the root UserNamespace (i.e. as auth.KUID and
-// auth.KGID respectively).
-//
-// FileDescriptionImpl is analogous to Linux's struct file_operations.
-type FileDescriptionImpl interface {
- // Release is called when the associated FileDescription reaches zero
- // references.
- Release()
-
- // OnClose is called when a file descriptor representing the
- // FileDescription is closed. Note that returning a non-nil error does not
- // prevent the file descriptor from being closed.
- OnClose() error
-
- // StatusFlags returns file description status flags, as for
- // fcntl(F_GETFL).
- StatusFlags(ctx context.Context) (uint32, error)
-
- // SetStatusFlags sets file description status flags, as for
- // fcntl(F_SETFL).
- SetStatusFlags(ctx context.Context, flags uint32) error
-
- // Stat returns metadata for the file represented by the FileDescription.
- Stat(ctx context.Context, opts StatOptions) (linux.Statx, error)
-
- // SetStat updates metadata for the file represented by the
- // FileDescription.
- SetStat(ctx context.Context, opts SetStatOptions) error
-
- // StatFS returns metadata for the filesystem containing the file
- // represented by the FileDescription.
- StatFS(ctx context.Context) (linux.Statfs, error)
-
- // waiter.Waitable methods may be used to poll for I/O events.
- waiter.Waitable
-
- // PRead reads from the file into dst, starting at the given offset, and
- // returns the number of bytes read. PRead is permitted to return partial
- // reads with a nil error.
- PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts ReadOptions) (int64, error)
-
- // Read is similar to PRead, but does not specify an offset.
- //
- // For files with an implicit FileDescription offset (e.g. regular files),
- // Read begins at the FileDescription offset, and advances the offset by
- // the number of bytes read; note that POSIX 2.9.7 "Thread Interactions
- // with Regular File Operations" requires that all operations that may
- // mutate the FileDescription offset are serialized.
- Read(ctx context.Context, dst usermem.IOSequence, opts ReadOptions) (int64, error)
-
- // PWrite writes src to the file, starting at the given offset, and returns
- // the number of bytes written. PWrite is permitted to return partial
- // writes with a nil error.
- //
- // As in Linux (but not POSIX), if O_APPEND is in effect for the
- // FileDescription, PWrite should ignore the offset and append data to the
- // end of the file.
- PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts WriteOptions) (int64, error)
-
- // Write is similar to PWrite, but does not specify an offset, which is
- // implied as for Read.
- //
- // Write is a FileDescriptionImpl method, instead of a wrapper around
- // PWrite that uses a FileDescription offset, to make it possible for
- // remote filesystems to implement O_APPEND correctly (i.e. atomically with
- // respect to writers outside the scope of VFS).
- Write(ctx context.Context, src usermem.IOSequence, opts WriteOptions) (int64, error)
-
- // IterDirents invokes cb on each entry in the directory represented by the
- // FileDescription. If IterDirents has been called since the last call to
- // Seek, it continues iteration from the end of the last call.
- IterDirents(ctx context.Context, cb IterDirentsCallback) error
-
- // Seek changes the FileDescription offset (assuming one exists) and
- // returns its new value.
- //
- // For directories, if whence == SEEK_SET and offset == 0, the caller is
- // rewinddir(), such that Seek "shall also cause the directory stream to
- // refer to the current state of the corresponding directory" -
- // POSIX.1-2017.
- Seek(ctx context.Context, offset int64, whence int32) (int64, error)
-
- // Sync requests that cached state associated with the file represented by
- // the FileDescription is synchronized with persistent storage, and blocks
- // until this is complete.
- Sync(ctx context.Context) error
-
- // ConfigureMMap mutates opts to implement mmap(2) for the file. Most
- // implementations that support memory mapping can call
- // GenericConfigureMMap with the appropriate memmap.Mappable.
- ConfigureMMap(ctx context.Context, opts memmap.MMapOpts) error
-
- // Ioctl implements the ioctl(2) syscall.
- Ioctl(ctx context.Context, uio usermem.IO, args arch.SyscallArguments) (uintptr, error)
-
- // TODO: extended attributes; file locking
-}
-
-// Dirent holds the information contained in struct linux_dirent64.
-type Dirent struct {
- // Name is the filename.
- Name string
-
- // Type is the file type, a linux.DT_* constant.
- Type uint8
-
- // Ino is the inode number.
- Ino uint64
-
- // Off is this Dirent's offset.
- Off int64
-}
-
-// IterDirentsCallback receives Dirents from FileDescriptionImpl.IterDirents.
-type IterDirentsCallback interface {
- // Handle handles the given iterated Dirent. It returns true if iteration
- // should continue, and false if FileDescriptionImpl.IterDirents should
- // terminate now and restart with the same Dirent the next time it is
- // called.
- Handle(dirent Dirent) bool
-}
diff --git a/pkg/sentry/vfs/file_description_impl_util.go b/pkg/sentry/vfs/file_description_impl_util.go
deleted file mode 100644
index ba230da72..000000000
--- a/pkg/sentry/vfs/file_description_impl_util.go
+++ /dev/null
@@ -1,254 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package vfs
-
-import (
- "bytes"
- "io"
- "sync"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/arch"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/memmap"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/pkg/syserror"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-// The following design pattern is strongly recommended for filesystem
-// implementations to adapt:
-// - Have a local fileDescription struct (containing FileDescription) which
-// embeds FileDescriptionDefaultImpl and overrides the default methods
-// which are common to all fd implementations for that for that filesystem
-// like StatusFlags, SetStatusFlags, Stat, SetStat, StatFS, etc.
-// - This should be embedded in all file description implementations as the
-// first field by value.
-// - Directory FDs would also embed DirectoryFileDescriptionDefaultImpl.
-
-// FileDescriptionDefaultImpl may be embedded by implementations of
-// FileDescriptionImpl to obtain implementations of many FileDescriptionImpl
-// methods with default behavior analogous to Linux's.
-type FileDescriptionDefaultImpl struct{}
-
-// OnClose implements FileDescriptionImpl.OnClose analogously to
-// file_operations::flush == NULL in Linux.
-func (FileDescriptionDefaultImpl) OnClose() error {
- return nil
-}
-
-// StatFS implements FileDescriptionImpl.StatFS analogously to
-// super_operations::statfs == NULL in Linux.
-func (FileDescriptionDefaultImpl) StatFS(ctx context.Context) (linux.Statfs, error) {
- return linux.Statfs{}, syserror.ENOSYS
-}
-
-// Readiness implements waiter.Waitable.Readiness analogously to
-// file_operations::poll == NULL in Linux.
-func (FileDescriptionDefaultImpl) Readiness(mask waiter.EventMask) waiter.EventMask {
- // include/linux/poll.h:vfs_poll() => DEFAULT_POLLMASK
- return waiter.EventIn | waiter.EventOut
-}
-
-// EventRegister implements waiter.Waitable.EventRegister analogously to
-// file_operations::poll == NULL in Linux.
-func (FileDescriptionDefaultImpl) EventRegister(e *waiter.Entry, mask waiter.EventMask) {
-}
-
-// EventUnregister implements waiter.Waitable.EventUnregister analogously to
-// file_operations::poll == NULL in Linux.
-func (FileDescriptionDefaultImpl) EventUnregister(e *waiter.Entry) {
-}
-
-// PRead implements FileDescriptionImpl.PRead analogously to
-// file_operations::read == file_operations::read_iter == NULL in Linux.
-func (FileDescriptionDefaultImpl) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts ReadOptions) (int64, error) {
- return 0, syserror.EINVAL
-}
-
-// Read implements FileDescriptionImpl.Read analogously to
-// file_operations::read == file_operations::read_iter == NULL in Linux.
-func (FileDescriptionDefaultImpl) Read(ctx context.Context, dst usermem.IOSequence, opts ReadOptions) (int64, error) {
- return 0, syserror.EINVAL
-}
-
-// PWrite implements FileDescriptionImpl.PWrite analogously to
-// file_operations::write == file_operations::write_iter == NULL in Linux.
-func (FileDescriptionDefaultImpl) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts WriteOptions) (int64, error) {
- return 0, syserror.EINVAL
-}
-
-// Write implements FileDescriptionImpl.Write analogously to
-// file_operations::write == file_operations::write_iter == NULL in Linux.
-func (FileDescriptionDefaultImpl) Write(ctx context.Context, src usermem.IOSequence, opts WriteOptions) (int64, error) {
- return 0, syserror.EINVAL
-}
-
-// IterDirents implements FileDescriptionImpl.IterDirents analogously to
-// file_operations::iterate == file_operations::iterate_shared == NULL in
-// Linux.
-func (FileDescriptionDefaultImpl) IterDirents(ctx context.Context, cb IterDirentsCallback) error {
- return syserror.ENOTDIR
-}
-
-// Seek implements FileDescriptionImpl.Seek analogously to
-// file_operations::llseek == NULL in Linux.
-func (FileDescriptionDefaultImpl) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
- return 0, syserror.ESPIPE
-}
-
-// Sync implements FileDescriptionImpl.Sync analogously to
-// file_operations::fsync == NULL in Linux.
-func (FileDescriptionDefaultImpl) Sync(ctx context.Context) error {
- return syserror.EINVAL
-}
-
-// ConfigureMMap implements FileDescriptionImpl.ConfigureMMap analogously to
-// file_operations::mmap == NULL in Linux.
-func (FileDescriptionDefaultImpl) ConfigureMMap(ctx context.Context, opts memmap.MMapOpts) error {
- return syserror.ENODEV
-}
-
-// Ioctl implements FileDescriptionImpl.Ioctl analogously to
-// file_operations::unlocked_ioctl == NULL in Linux.
-func (FileDescriptionDefaultImpl) Ioctl(ctx context.Context, uio usermem.IO, args arch.SyscallArguments) (uintptr, error) {
- return 0, syserror.ENOTTY
-}
-
-// DirectoryFileDescriptionDefaultImpl may be embedded by implementations of
-// FileDescriptionImpl that always represent directories to obtain
-// implementations of non-directory I/O methods that return EISDIR.
-type DirectoryFileDescriptionDefaultImpl struct{}
-
-// PRead implements FileDescriptionImpl.PRead.
-func (DirectoryFileDescriptionDefaultImpl) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts ReadOptions) (int64, error) {
- return 0, syserror.EISDIR
-}
-
-// Read implements FileDescriptionImpl.Read.
-func (DirectoryFileDescriptionDefaultImpl) Read(ctx context.Context, dst usermem.IOSequence, opts ReadOptions) (int64, error) {
- return 0, syserror.EISDIR
-}
-
-// PWrite implements FileDescriptionImpl.PWrite.
-func (DirectoryFileDescriptionDefaultImpl) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts WriteOptions) (int64, error) {
- return 0, syserror.EISDIR
-}
-
-// Write implements FileDescriptionImpl.Write.
-func (DirectoryFileDescriptionDefaultImpl) Write(ctx context.Context, src usermem.IOSequence, opts WriteOptions) (int64, error) {
- return 0, syserror.EISDIR
-}
-
-// DynamicBytesFileDescriptionImpl may be embedded by implementations of
-// FileDescriptionImpl that represent read-only regular files whose contents
-// are backed by a bytes.Buffer that is regenerated when necessary, consistent
-// with Linux's fs/seq_file.c:single_open().
-//
-// DynamicBytesFileDescriptionImpl.SetDataSource() must be called before first
-// use.
-type DynamicBytesFileDescriptionImpl struct {
- data DynamicBytesSource // immutable
- mu sync.Mutex // protects the following fields
- buf bytes.Buffer
- off int64
- lastRead int64 // offset at which the last Read, PRead, or Seek ended
-}
-
-// DynamicBytesSource represents a data source for a
-// DynamicBytesFileDescriptionImpl.
-type DynamicBytesSource interface {
- // Generate writes the file's contents to buf.
- Generate(ctx context.Context, buf *bytes.Buffer) error
-}
-
-// SetDataSource must be called exactly once on fd before first use.
-func (fd *DynamicBytesFileDescriptionImpl) SetDataSource(data DynamicBytesSource) {
- fd.data = data
-}
-
-// Preconditions: fd.mu must be locked.
-func (fd *DynamicBytesFileDescriptionImpl) preadLocked(ctx context.Context, dst usermem.IOSequence, offset int64, opts *ReadOptions) (int64, error) {
- // Regenerate the buffer if it's empty, or before pread() at a new offset.
- // Compare fs/seq_file.c:seq_read() => traverse().
- switch {
- case offset != fd.lastRead:
- fd.buf.Reset()
- fallthrough
- case fd.buf.Len() == 0:
- if err := fd.data.Generate(ctx, &fd.buf); err != nil {
- fd.buf.Reset()
- // fd.off is not updated in this case.
- fd.lastRead = 0
- return 0, err
- }
- }
- bs := fd.buf.Bytes()
- if offset >= int64(len(bs)) {
- return 0, io.EOF
- }
- n, err := dst.CopyOut(ctx, bs[offset:])
- fd.lastRead = offset + int64(n)
- return int64(n), err
-}
-
-// PRead implements FileDescriptionImpl.PRead.
-func (fd *DynamicBytesFileDescriptionImpl) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts ReadOptions) (int64, error) {
- fd.mu.Lock()
- n, err := fd.preadLocked(ctx, dst, offset, &opts)
- fd.mu.Unlock()
- return n, err
-}
-
-// Read implements FileDescriptionImpl.Read.
-func (fd *DynamicBytesFileDescriptionImpl) Read(ctx context.Context, dst usermem.IOSequence, opts ReadOptions) (int64, error) {
- fd.mu.Lock()
- n, err := fd.preadLocked(ctx, dst, fd.off, &opts)
- fd.off += n
- fd.mu.Unlock()
- return n, err
-}
-
-// Seek implements FileDescriptionImpl.Seek.
-func (fd *DynamicBytesFileDescriptionImpl) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
- fd.mu.Lock()
- defer fd.mu.Unlock()
- switch whence {
- case linux.SEEK_SET:
- // Use offset as given.
- case linux.SEEK_CUR:
- offset += fd.off
- default:
- // fs/seq_file:seq_lseek() rejects SEEK_END etc.
- return 0, syserror.EINVAL
- }
- if offset < 0 {
- return 0, syserror.EINVAL
- }
- if offset != fd.lastRead {
- // Regenerate the file's contents immediately. Compare
- // fs/seq_file.c:seq_lseek() => traverse().
- fd.buf.Reset()
- if err := fd.data.Generate(ctx, &fd.buf); err != nil {
- fd.buf.Reset()
- fd.off = 0
- fd.lastRead = 0
- return 0, err
- }
- fd.lastRead = offset
- }
- fd.off = offset
- return offset, nil
-}
diff --git a/pkg/sentry/vfs/file_description_impl_util_test.go b/pkg/sentry/vfs/file_description_impl_util_test.go
deleted file mode 100644
index 511b829fc..000000000
--- a/pkg/sentry/vfs/file_description_impl_util_test.go
+++ /dev/null
@@ -1,141 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package vfs
-
-import (
- "bytes"
- "fmt"
- "io"
- "sync/atomic"
- "testing"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-// fileDescription is the common fd struct which a filesystem implementation
-// embeds in all of its file description implementations as required.
-type fileDescription struct {
- vfsfd FileDescription
- FileDescriptionDefaultImpl
-}
-
-// genCountFD is a read-only FileDescriptionImpl representing a regular file
-// that contains the number of times its DynamicBytesSource.Generate()
-// implementation has been called.
-type genCountFD struct {
- fileDescription
- DynamicBytesFileDescriptionImpl
-
- count uint64 // accessed using atomic memory ops
-}
-
-func newGenCountFD(mnt *Mount, vfsd *Dentry) *FileDescription {
- var fd genCountFD
- fd.vfsfd.Init(&fd, mnt, vfsd)
- fd.DynamicBytesFileDescriptionImpl.SetDataSource(&fd)
- return &fd.vfsfd
-}
-
-// Release implements FileDescriptionImpl.Release.
-func (fd *genCountFD) Release() {
-}
-
-// StatusFlags implements FileDescriptionImpl.StatusFlags.
-func (fd *genCountFD) StatusFlags(ctx context.Context) (uint32, error) {
- return 0, nil
-}
-
-// SetStatusFlags implements FileDescriptionImpl.SetStatusFlags.
-func (fd *genCountFD) SetStatusFlags(ctx context.Context, flags uint32) error {
- return syserror.EPERM
-}
-
-// Stat implements FileDescriptionImpl.Stat.
-func (fd *genCountFD) Stat(ctx context.Context, opts StatOptions) (linux.Statx, error) {
- // Note that Statx.Mask == 0 in the return value.
- return linux.Statx{}, nil
-}
-
-// SetStat implements FileDescriptionImpl.SetStat.
-func (fd *genCountFD) SetStat(ctx context.Context, opts SetStatOptions) error {
- return syserror.EPERM
-}
-
-// Generate implements DynamicBytesSource.Generate.
-func (fd *genCountFD) Generate(ctx context.Context, buf *bytes.Buffer) error {
- fmt.Fprintf(buf, "%d", atomic.AddUint64(&fd.count, 1))
- return nil
-}
-
-func TestGenCountFD(t *testing.T) {
- ctx := contexttest.Context(t)
- creds := auth.CredentialsFromContext(ctx)
-
- vfsObj := New() // vfs.New()
- vfsObj.MustRegisterFilesystemType("testfs", FDTestFilesystemType{})
- mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "testfs", &NewFilesystemOptions{})
- if err != nil {
- t.Fatalf("failed to create testfs root mount: %v", err)
- }
- vd := mntns.Root()
- defer vd.DecRef()
-
- fd := newGenCountFD(vd.Mount(), vd.Dentry())
- defer fd.DecRef()
-
- // The first read causes Generate to be called to fill the FD's buffer.
- buf := make([]byte, 2)
- ioseq := usermem.BytesIOSequence(buf)
- n, err := fd.Impl().Read(ctx, ioseq, ReadOptions{})
- if n != 1 || (err != nil && err != io.EOF) {
- t.Fatalf("first Read: got (%d, %v), wanted (1, nil or EOF)", n, err)
- }
- if want := byte('1'); buf[0] != want {
- t.Errorf("first Read: got byte %c, wanted %c", buf[0], want)
- }
-
- // A second read without seeking is still at EOF.
- n, err = fd.Impl().Read(ctx, ioseq, ReadOptions{})
- if n != 0 || err != io.EOF {
- t.Fatalf("second Read: got (%d, %v), wanted (0, EOF)", n, err)
- }
-
- // Seeking to the beginning of the file causes it to be regenerated.
- n, err = fd.Impl().Seek(ctx, 0, linux.SEEK_SET)
- if n != 0 || err != nil {
- t.Fatalf("Seek: got (%d, %v), wanted (0, nil)", n, err)
- }
- n, err = fd.Impl().Read(ctx, ioseq, ReadOptions{})
- if n != 1 || (err != nil && err != io.EOF) {
- t.Fatalf("Read after Seek: got (%d, %v), wanted (1, nil or EOF)", n, err)
- }
- if want := byte('2'); buf[0] != want {
- t.Errorf("Read after Seek: got byte %c, wanted %c", buf[0], want)
- }
-
- // PRead at the beginning of the file also causes it to be regenerated.
- n, err = fd.Impl().PRead(ctx, ioseq, 0, ReadOptions{})
- if n != 1 || (err != nil && err != io.EOF) {
- t.Fatalf("PRead: got (%d, %v), wanted (1, nil or EOF)", n, err)
- }
- if want := byte('3'); buf[0] != want {
- t.Errorf("PRead: got byte %c, wanted %c", buf[0], want)
- }
-}
diff --git a/pkg/sentry/vfs/filesystem.go b/pkg/sentry/vfs/filesystem.go
deleted file mode 100644
index 7a074b718..000000000
--- a/pkg/sentry/vfs/filesystem.go
+++ /dev/null
@@ -1,155 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package vfs
-
-import (
- "sync/atomic"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/context"
-)
-
-// A Filesystem is a tree of nodes represented by Dentries, which forms part of
-// a VirtualFilesystem.
-//
-// Filesystems are reference-counted. Unless otherwise specified, all
-// Filesystem methods require that a reference is held.
-//
-// Filesystem is analogous to Linux's struct super_block.
-type Filesystem struct {
- // refs is the reference count. refs is accessed using atomic memory
- // operations.
- refs int64
-
- // impl is the FilesystemImpl associated with this Filesystem. impl is
- // immutable. This should be the last field in Dentry.
- impl FilesystemImpl
-}
-
-// Init must be called before first use of fs.
-func (fs *Filesystem) Init(impl FilesystemImpl) {
- fs.refs = 1
- fs.impl = impl
-}
-
-// Impl returns the FilesystemImpl associated with fs.
-func (fs *Filesystem) Impl() FilesystemImpl {
- return fs.impl
-}
-
-func (fs *Filesystem) incRef() {
- if atomic.AddInt64(&fs.refs, 1) <= 1 {
- panic("Filesystem.incRef() called without holding a reference")
- }
-}
-
-func (fs *Filesystem) decRef() {
- if refs := atomic.AddInt64(&fs.refs, -1); refs == 0 {
- fs.impl.Release()
- } else if refs < 0 {
- panic("Filesystem.decRef() called without holding a reference")
- }
-}
-
-// FilesystemImpl contains implementation details for a Filesystem.
-// Implementations of FilesystemImpl should contain their associated Filesystem
-// by value as their first field.
-//
-// All methods that take a ResolvingPath must resolve the path before
-// performing any other checks, including rejection of the operation if not
-// supported by the FilesystemImpl. This is because the final FilesystemImpl
-// (responsible for actually implementing the operation) isn't known until path
-// resolution is complete.
-//
-// For all methods that take or return linux.Statx, Statx.Uid and Statx.Gid
-// should be interpreted as IDs in the root UserNamespace (i.e. as auth.KUID
-// and auth.KGID respectively).
-//
-// FilesystemImpl combines elements of Linux's struct super_operations and
-// struct inode_operations, for reasons described in the documentation for
-// Dentry.
-type FilesystemImpl interface {
- // Release is called when the associated Filesystem reaches zero
- // references.
- Release()
-
- // Sync "causes all pending modifications to filesystem metadata and cached
- // file data to be written to the underlying [filesystem]", as by syncfs(2).
- Sync(ctx context.Context) error
-
- // GetDentryAt returns a Dentry representing the file at rp. A reference is
- // taken on the returned Dentry.
- //
- // GetDentryAt does not correspond directly to a Linux syscall; it is used
- // in the implementation of:
- //
- // - Syscalls that need to resolve two paths: rename(), renameat(),
- // renameat2(), link(), linkat().
- //
- // - Syscalls that need to refer to a filesystem position outside the
- // context of a file description: chdir(), fchdir(), chroot(), mount(),
- // umount().
- GetDentryAt(ctx context.Context, rp *ResolvingPath, opts GetDentryOptions) (*Dentry, error)
-
- // LinkAt creates a hard link at rp representing the same file as vd. It
- // does not take ownership of references on vd.
- //
- // The implementation is responsible for checking that vd.Mount() ==
- // rp.Mount(), and that vd does not represent a directory.
- LinkAt(ctx context.Context, rp *ResolvingPath, vd VirtualDentry) error
-
- // MkdirAt creates a directory at rp.
- MkdirAt(ctx context.Context, rp *ResolvingPath, opts MkdirOptions) error
-
- // MknodAt creates a regular file, device special file, or named pipe at
- // rp.
- MknodAt(ctx context.Context, rp *ResolvingPath, opts MknodOptions) error
-
- // OpenAt returns an FileDescription providing access to the file at rp. A
- // reference is taken on the returned FileDescription.
- OpenAt(ctx context.Context, rp *ResolvingPath, opts OpenOptions) (*FileDescription, error)
-
- // ReadlinkAt returns the target of the symbolic link at rp.
- ReadlinkAt(ctx context.Context, rp *ResolvingPath) (string, error)
-
- // RenameAt renames the Dentry represented by vd to rp. It does not take
- // ownership of references on vd.
- //
- // The implementation is responsible for checking that vd.Mount() ==
- // rp.Mount().
- RenameAt(ctx context.Context, rp *ResolvingPath, vd VirtualDentry, opts RenameOptions) error
-
- // RmdirAt removes the directory at rp.
- RmdirAt(ctx context.Context, rp *ResolvingPath) error
-
- // SetStatAt updates metadata for the file at the given path.
- SetStatAt(ctx context.Context, rp *ResolvingPath, opts SetStatOptions) error
-
- // StatAt returns metadata for the file at rp.
- StatAt(ctx context.Context, rp *ResolvingPath, opts StatOptions) (linux.Statx, error)
-
- // StatFSAt returns metadata for the filesystem containing the file at rp.
- // (This method takes a path because a FilesystemImpl may consist of any
- // number of constituent filesystems.)
- StatFSAt(ctx context.Context, rp *ResolvingPath) (linux.Statfs, error)
-
- // SymlinkAt creates a symbolic link at rp referring to the given target.
- SymlinkAt(ctx context.Context, rp *ResolvingPath, target string) error
-
- // UnlinkAt removes the non-directory file at rp.
- UnlinkAt(ctx context.Context, rp *ResolvingPath) error
-
- // TODO: d_path(); extended attributes; inotify_add_watch(); bind()
-}
diff --git a/pkg/sentry/vfs/filesystem_type.go b/pkg/sentry/vfs/filesystem_type.go
deleted file mode 100644
index f401ad7f3..000000000
--- a/pkg/sentry/vfs/filesystem_type.go
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package vfs
-
-import (
- "fmt"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
-)
-
-// A FilesystemType constructs filesystems.
-//
-// FilesystemType is analogous to Linux's struct file_system_type.
-type FilesystemType interface {
- // NewFilesystem returns a Filesystem configured by the given options,
- // along with its mount root. A reference is taken on the returned
- // Filesystem and Dentry.
- NewFilesystem(ctx context.Context, creds *auth.Credentials, source string, opts NewFilesystemOptions) (*Filesystem, *Dentry, error)
-}
-
-// NewFilesystemOptions contains options to FilesystemType.NewFilesystem.
-type NewFilesystemOptions struct {
- // Data is the string passed as the 5th argument to mount(2), which is
- // usually a comma-separated list of filesystem-specific mount options.
- Data string
-
- // InternalData holds opaque FilesystemType-specific data. There is
- // intentionally no way for applications to specify InternalData; if it is
- // not nil, the call to NewFilesystem originates from within the sentry.
- InternalData interface{}
-}
-
-// RegisterFilesystemType registers the given FilesystemType in vfs with the
-// given name.
-func (vfs *VirtualFilesystem) RegisterFilesystemType(name string, fsType FilesystemType) error {
- vfs.fsTypesMu.Lock()
- defer vfs.fsTypesMu.Unlock()
- if existing, ok := vfs.fsTypes[name]; ok {
- return fmt.Errorf("name %q is already registered to filesystem type %T", name, existing)
- }
- vfs.fsTypes[name] = fsType
- return nil
-}
-
-// MustRegisterFilesystemType is equivalent to RegisterFilesystemType but
-// panics on failure.
-func (vfs *VirtualFilesystem) MustRegisterFilesystemType(name string, fsType FilesystemType) {
- if err := vfs.RegisterFilesystemType(name, fsType); err != nil {
- panic(fmt.Sprintf("failed to register filesystem type %T: %v", fsType, err))
- }
-}
-
-func (vfs *VirtualFilesystem) getFilesystemType(name string) FilesystemType {
- vfs.fsTypesMu.RLock()
- defer vfs.fsTypesMu.RUnlock()
- return vfs.fsTypes[name]
-}
diff --git a/pkg/sentry/vfs/mount.go b/pkg/sentry/vfs/mount.go
deleted file mode 100644
index 11702f720..000000000
--- a/pkg/sentry/vfs/mount.go
+++ /dev/null
@@ -1,411 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package vfs
-
-import (
- "math"
- "sync/atomic"
-
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-// A Mount is a replacement of a Dentry (Mount.key.point) from one Filesystem
-// (Mount.key.parent.fs) with a Dentry (Mount.root) from another Filesystem
-// (Mount.fs), which applies to path resolution in the context of a particular
-// Mount (Mount.key.parent).
-//
-// Mounts are reference-counted. Unless otherwise specified, all Mount methods
-// require that a reference is held.
-//
-// Mount and Filesystem are distinct types because it's possible for a single
-// Filesystem to be mounted at multiple locations and/or in multiple mount
-// namespaces.
-//
-// Mount is analogous to Linux's struct mount. (gVisor does not distinguish
-// between struct mount and struct vfsmount.)
-type Mount struct {
- // The lower 63 bits of refs are a reference count. The MSB of refs is set
- // if the Mount has been eagerly unmounted, as by umount(2) without the
- // MNT_DETACH flag. refs is accessed using atomic memory operations.
- refs int64
-
- // The lower 63 bits of writers is the number of calls to
- // Mount.CheckBeginWrite() that have not yet been paired with a call to
- // Mount.EndWrite(). The MSB of writers is set if MS_RDONLY is in effect.
- // writers is accessed using atomic memory operations.
- writers int64
-
- // key is protected by VirtualFilesystem.mountMu and
- // VirtualFilesystem.mounts.seq, and may be nil. References are held on
- // key.parent and key.point if they are not nil.
- //
- // Invariant: key.parent != nil iff key.point != nil. key.point belongs to
- // key.parent.fs.
- key mountKey
-
- // fs, root, and ns are immutable. References are held on fs and root (but
- // not ns).
- //
- // Invariant: root belongs to fs.
- fs *Filesystem
- root *Dentry
- ns *MountNamespace
-}
-
-// A MountNamespace is a collection of Mounts.
-//
-// MountNamespaces are reference-counted. Unless otherwise specified, all
-// MountNamespace methods require that a reference is held.
-//
-// MountNamespace is analogous to Linux's struct mnt_namespace.
-type MountNamespace struct {
- refs int64 // accessed using atomic memory operations
-
- // root is the MountNamespace's root mount. root is immutable.
- root *Mount
-
- // mountpoints contains all Dentries which are mount points in this
- // namespace. mountpoints is protected by VirtualFilesystem.mountMu.
- //
- // mountpoints is used to determine if a Dentry can be moved or removed
- // (which requires that the Dentry is not a mount point in the calling
- // namespace).
- //
- // mountpoints is maintained even if there are no references held on the
- // MountNamespace; this is required to ensure that
- // VFS.PrepareDeleteDentry() and VFS.PrepareRemoveDentry() operate
- // correctly on unreferenced MountNamespaces.
- mountpoints map[*Dentry]struct{}
-}
-
-// NewMountNamespace returns a new mount namespace with a root filesystem
-// configured by the given arguments. A reference is taken on the returned
-// MountNamespace.
-func (vfs *VirtualFilesystem) NewMountNamespace(ctx context.Context, creds *auth.Credentials, source, fsTypeName string, opts *NewFilesystemOptions) (*MountNamespace, error) {
- fsType := vfs.getFilesystemType(fsTypeName)
- if fsType == nil {
- return nil, syserror.ENODEV
- }
- fs, root, err := fsType.NewFilesystem(ctx, creds, source, *opts)
- if err != nil {
- return nil, err
- }
- mntns := &MountNamespace{
- refs: 1,
- mountpoints: make(map[*Dentry]struct{}),
- }
- mntns.root = &Mount{
- fs: fs,
- root: root,
- ns: mntns,
- refs: 1,
- }
- return mntns, nil
-}
-
-// NewMount creates and mounts a new Filesystem.
-func (vfs *VirtualFilesystem) NewMount(ctx context.Context, creds *auth.Credentials, source string, target *PathOperation, fsTypeName string, opts *NewFilesystemOptions) error {
- fsType := vfs.getFilesystemType(fsTypeName)
- if fsType == nil {
- return syserror.ENODEV
- }
- fs, root, err := fsType.NewFilesystem(ctx, creds, source, *opts)
- if err != nil {
- return err
- }
- // We can't hold vfs.mountMu while calling FilesystemImpl methods due to
- // lock ordering.
- vd, err := vfs.GetDentryAt(ctx, creds, target, &GetDentryOptions{})
- if err != nil {
- root.decRef(fs)
- fs.decRef()
- return err
- }
- vfs.mountMu.Lock()
- for {
- if vd.dentry.IsDisowned() {
- vfs.mountMu.Unlock()
- vd.DecRef()
- root.decRef(fs)
- fs.decRef()
- return syserror.ENOENT
- }
- // vd might have been mounted over between vfs.GetDentryAt() and
- // vfs.mountMu.Lock().
- if !vd.dentry.isMounted() {
- break
- }
- nextmnt := vfs.mounts.Lookup(vd.mount, vd.dentry)
- if nextmnt == nil {
- break
- }
- nextmnt.incRef()
- nextmnt.root.incRef(nextmnt.fs)
- vd.DecRef()
- vd = VirtualDentry{
- mount: nextmnt,
- dentry: nextmnt.root,
- }
- }
- // TODO: Linux requires that either both the mount point and the mount root
- // are directories, or neither are, and returns ENOTDIR if this is not the
- // case.
- mntns := vd.mount.ns
- mnt := &Mount{
- fs: fs,
- root: root,
- ns: mntns,
- refs: 1,
- }
- mnt.storeKey(vd.mount, vd.dentry)
- atomic.AddUint32(&vd.dentry.mounts, 1)
- mntns.mountpoints[vd.dentry] = struct{}{}
- vfsmpmounts, ok := vfs.mountpoints[vd.dentry]
- if !ok {
- vfsmpmounts = make(map[*Mount]struct{})
- vfs.mountpoints[vd.dentry] = vfsmpmounts
- }
- vfsmpmounts[mnt] = struct{}{}
- vfs.mounts.Insert(mnt)
- vfs.mountMu.Unlock()
- return nil
-}
-
-// getMountAt returns the last Mount in the stack mounted at (mnt, d). It takes
-// a reference on the returned Mount. If (mnt, d) is not a mount point,
-// getMountAt returns nil.
-//
-// getMountAt is analogous to Linux's fs/namei.c:follow_mount().
-//
-// Preconditions: References are held on mnt and d.
-func (vfs *VirtualFilesystem) getMountAt(mnt *Mount, d *Dentry) *Mount {
- // The first mount is special-cased:
- //
- // - The caller is assumed to have checked d.isMounted() already. (This
- // isn't a precondition because it doesn't matter for correctness.)
- //
- // - We return nil, instead of mnt, if there is no mount at (mnt, d).
- //
- // - We don't drop the caller's references on mnt and d.
-retryFirst:
- next := vfs.mounts.Lookup(mnt, d)
- if next == nil {
- return nil
- }
- if !next.tryIncMountedRef() {
- // Raced with umount.
- goto retryFirst
- }
- mnt = next
- d = next.root
- // We don't need to take Dentry refs anywhere in this function because
- // Mounts hold references on Mount.root, which is immutable.
- for d.isMounted() {
- next := vfs.mounts.Lookup(mnt, d)
- if next == nil {
- break
- }
- if !next.tryIncMountedRef() {
- // Raced with umount.
- continue
- }
- mnt.decRef()
- mnt = next
- d = next.root
- }
- return mnt
-}
-
-// getMountpointAt returns the mount point for the stack of Mounts including
-// mnt. It takes a reference on the returned Mount and Dentry. If no such mount
-// point exists (i.e. mnt is a root mount), getMountpointAt returns (nil, nil).
-//
-// Preconditions: References are held on mnt and root. vfsroot is not (mnt,
-// mnt.root).
-func (vfs *VirtualFilesystem) getMountpointAt(mnt *Mount, vfsroot VirtualDentry) (*Mount, *Dentry) {
- // The first mount is special-cased:
- //
- // - The caller must have already checked mnt against vfsroot.
- //
- // - We return nil, instead of mnt, if there is no mount point for mnt.
- //
- // - We don't drop the caller's reference on mnt.
-retryFirst:
- epoch := vfs.mounts.seq.BeginRead()
- parent, point := mnt.loadKey()
- if !vfs.mounts.seq.ReadOk(epoch) {
- goto retryFirst
- }
- if parent == nil {
- return nil, nil
- }
- if !parent.tryIncMountedRef() {
- // Raced with umount.
- goto retryFirst
- }
- if !point.tryIncRef(parent.fs) {
- // Since Mount holds a reference on Mount.key.point, this can only
- // happen due to a racing change to Mount.key.
- parent.decRef()
- goto retryFirst
- }
- mnt = parent
- d := point
- for {
- if mnt == vfsroot.mount && d == vfsroot.dentry {
- break
- }
- if d != mnt.root {
- break
- }
- retryNotFirst:
- epoch := vfs.mounts.seq.BeginRead()
- parent, point := mnt.loadKey()
- if !vfs.mounts.seq.ReadOk(epoch) {
- goto retryNotFirst
- }
- if parent == nil {
- break
- }
- if !parent.tryIncMountedRef() {
- // Raced with umount.
- goto retryNotFirst
- }
- if !point.tryIncRef(parent.fs) {
- // Since Mount holds a reference on Mount.key.point, this can
- // only happen due to a racing change to Mount.key.
- parent.decRef()
- goto retryNotFirst
- }
- if !vfs.mounts.seq.ReadOk(epoch) {
- point.decRef(parent.fs)
- parent.decRef()
- goto retryNotFirst
- }
- d.decRef(mnt.fs)
- mnt.decRef()
- mnt = parent
- d = point
- }
- return mnt, d
-}
-
-// tryIncMountedRef increments mnt's reference count and returns true. If mnt's
-// reference count is already zero, or has been eagerly unmounted,
-// tryIncMountedRef does nothing and returns false.
-//
-// tryIncMountedRef does not require that a reference is held on mnt.
-func (mnt *Mount) tryIncMountedRef() bool {
- for {
- refs := atomic.LoadInt64(&mnt.refs)
- if refs <= 0 { // refs < 0 => MSB set => eagerly unmounted
- return false
- }
- if atomic.CompareAndSwapInt64(&mnt.refs, refs, refs+1) {
- return true
- }
- }
-}
-
-func (mnt *Mount) incRef() {
- // In general, negative values for mnt.refs are valid because the MSB is
- // the eager-unmount bit.
- atomic.AddInt64(&mnt.refs, 1)
-}
-
-func (mnt *Mount) decRef() {
- refs := atomic.AddInt64(&mnt.refs, -1)
- if refs&^math.MinInt64 == 0 { // mask out MSB
- parent, point := mnt.loadKey()
- if point != nil {
- point.decRef(parent.fs)
- parent.decRef()
- }
- mnt.root.decRef(mnt.fs)
- mnt.fs.decRef()
- }
-}
-
-// CheckBeginWrite increments the counter of in-progress write operations on
-// mnt. If mnt is mounted MS_RDONLY, CheckBeginWrite does nothing and returns
-// EROFS.
-//
-// If CheckBeginWrite succeeds, EndWrite must be called when the write
-// operation is finished.
-func (mnt *Mount) CheckBeginWrite() error {
- if atomic.AddInt64(&mnt.writers, 1) < 0 {
- atomic.AddInt64(&mnt.writers, -1)
- return syserror.EROFS
- }
- return nil
-}
-
-// EndWrite indicates that a write operation signaled by a previous successful
-// call to CheckBeginWrite has finished.
-func (mnt *Mount) EndWrite() {
- atomic.AddInt64(&mnt.writers, -1)
-}
-
-// Preconditions: VirtualFilesystem.mountMu must be locked for writing.
-func (mnt *Mount) setReadOnlyLocked(ro bool) error {
- if oldRO := atomic.LoadInt64(&mnt.writers) < 0; oldRO == ro {
- return nil
- }
- if ro {
- if !atomic.CompareAndSwapInt64(&mnt.writers, 0, math.MinInt64) {
- return syserror.EBUSY
- }
- return nil
- }
- // Unset MSB without dropping any temporary increments from failed calls to
- // mnt.CheckBeginWrite().
- atomic.AddInt64(&mnt.writers, math.MinInt64)
- return nil
-}
-
-// Filesystem returns the mounted Filesystem. It does not take a reference on
-// the returned Filesystem.
-func (mnt *Mount) Filesystem() *Filesystem {
- return mnt.fs
-}
-
-// IncRef increments mntns' reference count.
-func (mntns *MountNamespace) IncRef() {
- if atomic.AddInt64(&mntns.refs, 1) <= 1 {
- panic("MountNamespace.IncRef() called without holding a reference")
- }
-}
-
-// DecRef decrements mntns' reference count.
-func (mntns *MountNamespace) DecRef() {
- if refs := atomic.AddInt64(&mntns.refs, 0); refs == 0 {
- // TODO: unmount mntns.root
- } else if refs < 0 {
- panic("MountNamespace.DecRef() called without holding a reference")
- }
-}
-
-// Root returns mntns' root. A reference is taken on the returned
-// VirtualDentry.
-func (mntns *MountNamespace) Root() VirtualDentry {
- vd := VirtualDentry{
- mount: mntns.root,
- dentry: mntns.root.root,
- }
- vd.IncRef()
- return vd
-}
diff --git a/pkg/sentry/vfs/mount_test.go b/pkg/sentry/vfs/mount_test.go
deleted file mode 100644
index f394d7483..000000000
--- a/pkg/sentry/vfs/mount_test.go
+++ /dev/null
@@ -1,465 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package vfs
-
-import (
- "fmt"
- "runtime"
- "sync"
- "testing"
-)
-
-func TestMountTableLookupEmpty(t *testing.T) {
- var mt mountTable
- mt.Init()
-
- parent := &Mount{}
- point := &Dentry{}
- if m := mt.Lookup(parent, point); m != nil {
- t.Errorf("empty mountTable lookup: got %p, wanted nil", m)
- }
-}
-
-func TestMountTableInsertLookup(t *testing.T) {
- var mt mountTable
- mt.Init()
-
- mount := &Mount{}
- mount.storeKey(&Mount{}, &Dentry{})
- mt.Insert(mount)
-
- if m := mt.Lookup(mount.parent(), mount.point()); m != mount {
- t.Errorf("mountTable positive lookup: got %p, wanted %p", m, mount)
- }
-
- otherParent := &Mount{}
- if m := mt.Lookup(otherParent, mount.point()); m != nil {
- t.Errorf("mountTable lookup with wrong mount parent: got %p, wanted nil", m)
- }
- otherPoint := &Dentry{}
- if m := mt.Lookup(mount.parent(), otherPoint); m != nil {
- t.Errorf("mountTable lookup with wrong mount point: got %p, wanted nil", m)
- }
-}
-
-// TODO: concurrent lookup/insertion/removal
-
-// must be powers of 2
-var benchNumMounts = []int{1 << 2, 1 << 5, 1 << 8}
-
-// For all of the following:
-//
-// - BenchmarkMountTableFoo tests usage pattern "Foo" for mountTable.
-//
-// - BenchmarkMountMapFoo tests usage pattern "Foo" for a
-// sync.RWMutex-protected map. (Mutator benchmarks do not use a RWMutex, since
-// mountTable also requires external synchronization between mutators.)
-//
-// - BenchmarkMountSyncMapFoo tests usage pattern "Foo" for a sync.Map.
-//
-// ParallelLookup is by far the most common and performance-sensitive operation
-// for this application. NegativeLookup is also important, but less so (only
-// relevant with multiple mount namespaces and significant differences in
-// mounts between them). Insertion and removal are benchmarked for
-// completeness.
-const enableComparativeBenchmarks = false
-
-func newBenchMount() *Mount {
- mount := &Mount{}
- mount.storeKey(&Mount{}, &Dentry{})
- return mount
-}
-
-func vdkey(mnt *Mount) VirtualDentry {
- parent, point := mnt.loadKey()
- return VirtualDentry{
- mount: parent,
- dentry: point,
- }
-}
-
-func BenchmarkMountTableParallelLookup(b *testing.B) {
- for numG, maxG := 1, runtime.GOMAXPROCS(0); numG >= 0 && numG <= maxG; numG *= 2 {
- for _, numMounts := range benchNumMounts {
- desc := fmt.Sprintf("%dx%d", numG, numMounts)
- b.Run(desc, func(b *testing.B) {
- var mt mountTable
- mt.Init()
- keys := make([]VirtualDentry, 0, numMounts)
- for i := 0; i < numMounts; i++ {
- mount := newBenchMount()
- mt.Insert(mount)
- keys = append(keys, vdkey(mount))
- }
-
- var ready sync.WaitGroup
- begin := make(chan struct{})
- var end sync.WaitGroup
- for g := 0; g < numG; g++ {
- ready.Add(1)
- end.Add(1)
- go func() {
- defer end.Done()
- ready.Done()
- <-begin
- for i := 0; i < b.N; i++ {
- k := keys[i&(numMounts-1)]
- m := mt.Lookup(k.mount, k.dentry)
- if m == nil {
- b.Fatalf("lookup failed")
- }
- if parent := m.parent(); parent != k.mount {
- b.Fatalf("lookup returned mount with parent %p, wanted %p", parent, k.mount)
- }
- if point := m.point(); point != k.dentry {
- b.Fatalf("lookup returned mount with point %p, wanted %p", point, k.dentry)
- }
- }
- }()
- }
-
- ready.Wait()
- b.ResetTimer()
- close(begin)
- end.Wait()
- })
- }
- }
-}
-
-func BenchmarkMountMapParallelLookup(b *testing.B) {
- if !enableComparativeBenchmarks {
- b.Skipf("comparative benchmarks are disabled")
- }
-
- for numG, maxG := 1, runtime.GOMAXPROCS(0); numG >= 0 && numG <= maxG; numG *= 2 {
- for _, numMounts := range benchNumMounts {
- desc := fmt.Sprintf("%dx%d", numG, numMounts)
- b.Run(desc, func(b *testing.B) {
- var mu sync.RWMutex
- ms := make(map[VirtualDentry]*Mount)
- keys := make([]VirtualDentry, 0, numMounts)
- for i := 0; i < numMounts; i++ {
- mount := newBenchMount()
- key := vdkey(mount)
- ms[key] = mount
- keys = append(keys, key)
- }
-
- var ready sync.WaitGroup
- begin := make(chan struct{})
- var end sync.WaitGroup
- for g := 0; g < numG; g++ {
- ready.Add(1)
- end.Add(1)
- go func() {
- defer end.Done()
- ready.Done()
- <-begin
- for i := 0; i < b.N; i++ {
- k := keys[i&(numMounts-1)]
- mu.RLock()
- m := ms[k]
- mu.RUnlock()
- if m == nil {
- b.Fatalf("lookup failed")
- }
- if parent := m.parent(); parent != k.mount {
- b.Fatalf("lookup returned mount with parent %p, wanted %p", parent, k.mount)
- }
- if point := m.point(); point != k.dentry {
- b.Fatalf("lookup returned mount with point %p, wanted %p", point, k.dentry)
- }
- }
- }()
- }
-
- ready.Wait()
- b.ResetTimer()
- close(begin)
- end.Wait()
- })
- }
- }
-}
-
-func BenchmarkMountSyncMapParallelLookup(b *testing.B) {
- if !enableComparativeBenchmarks {
- b.Skipf("comparative benchmarks are disabled")
- }
-
- for numG, maxG := 1, runtime.GOMAXPROCS(0); numG >= 0 && numG <= maxG; numG *= 2 {
- for _, numMounts := range benchNumMounts {
- desc := fmt.Sprintf("%dx%d", numG, numMounts)
- b.Run(desc, func(b *testing.B) {
- var ms sync.Map
- keys := make([]VirtualDentry, 0, numMounts)
- for i := 0; i < numMounts; i++ {
- mount := newBenchMount()
- key := vdkey(mount)
- ms.Store(key, mount)
- keys = append(keys, key)
- }
-
- var ready sync.WaitGroup
- begin := make(chan struct{})
- var end sync.WaitGroup
- for g := 0; g < numG; g++ {
- ready.Add(1)
- end.Add(1)
- go func() {
- defer end.Done()
- ready.Done()
- <-begin
- for i := 0; i < b.N; i++ {
- k := keys[i&(numMounts-1)]
- mi, ok := ms.Load(k)
- if !ok {
- b.Fatalf("lookup failed")
- }
- m := mi.(*Mount)
- if parent := m.parent(); parent != k.mount {
- b.Fatalf("lookup returned mount with parent %p, wanted %p", parent, k.mount)
- }
- if point := m.point(); point != k.dentry {
- b.Fatalf("lookup returned mount with point %p, wanted %p", point, k.dentry)
- }
- }
- }()
- }
-
- ready.Wait()
- b.ResetTimer()
- close(begin)
- end.Wait()
- })
- }
- }
-}
-
-func BenchmarkMountTableNegativeLookup(b *testing.B) {
- for _, numMounts := range benchNumMounts {
- desc := fmt.Sprintf("%d", numMounts)
- b.Run(desc, func(b *testing.B) {
- var mt mountTable
- mt.Init()
- for i := 0; i < numMounts; i++ {
- mt.Insert(newBenchMount())
- }
- negkeys := make([]VirtualDentry, 0, numMounts)
- for i := 0; i < numMounts; i++ {
- negkeys = append(negkeys, VirtualDentry{
- mount: &Mount{},
- dentry: &Dentry{},
- })
- }
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- k := negkeys[i&(numMounts-1)]
- m := mt.Lookup(k.mount, k.dentry)
- if m != nil {
- b.Fatalf("lookup got %p, wanted nil", m)
- }
- }
- })
- }
-}
-
-func BenchmarkMountMapNegativeLookup(b *testing.B) {
- if !enableComparativeBenchmarks {
- b.Skipf("comparative benchmarks are disabled")
- }
-
- for _, numMounts := range benchNumMounts {
- desc := fmt.Sprintf("%d", numMounts)
- b.Run(desc, func(b *testing.B) {
- var mu sync.RWMutex
- ms := make(map[VirtualDentry]*Mount)
- for i := 0; i < numMounts; i++ {
- mount := newBenchMount()
- ms[vdkey(mount)] = mount
- }
- negkeys := make([]VirtualDentry, 0, numMounts)
- for i := 0; i < numMounts; i++ {
- negkeys = append(negkeys, VirtualDentry{
- mount: &Mount{},
- dentry: &Dentry{},
- })
- }
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- k := negkeys[i&(numMounts-1)]
- mu.RLock()
- m := ms[k]
- mu.RUnlock()
- if m != nil {
- b.Fatalf("lookup got %p, wanted nil", m)
- }
- }
- })
- }
-}
-
-func BenchmarkMountSyncMapNegativeLookup(b *testing.B) {
- if !enableComparativeBenchmarks {
- b.Skipf("comparative benchmarks are disabled")
- }
-
- for _, numMounts := range benchNumMounts {
- desc := fmt.Sprintf("%d", numMounts)
- b.Run(desc, func(b *testing.B) {
- var ms sync.Map
- for i := 0; i < numMounts; i++ {
- mount := newBenchMount()
- ms.Store(vdkey(mount), mount)
- }
- negkeys := make([]VirtualDentry, 0, numMounts)
- for i := 0; i < numMounts; i++ {
- negkeys = append(negkeys, VirtualDentry{
- mount: &Mount{},
- dentry: &Dentry{},
- })
- }
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- k := negkeys[i&(numMounts-1)]
- m, _ := ms.Load(k)
- if m != nil {
- b.Fatalf("lookup got %p, wanted nil", m)
- }
- }
- })
- }
-}
-
-func BenchmarkMountTableInsert(b *testing.B) {
- // Preallocate Mounts so that allocation time isn't included in the
- // benchmark.
- mounts := make([]*Mount, 0, b.N)
- for i := 0; i < b.N; i++ {
- mounts = append(mounts, newBenchMount())
- }
-
- var mt mountTable
- mt.Init()
- b.ResetTimer()
- for i := range mounts {
- mt.Insert(mounts[i])
- }
-}
-
-func BenchmarkMountMapInsert(b *testing.B) {
- if !enableComparativeBenchmarks {
- b.Skipf("comparative benchmarks are disabled")
- }
-
- // Preallocate Mounts so that allocation time isn't included in the
- // benchmark.
- mounts := make([]*Mount, 0, b.N)
- for i := 0; i < b.N; i++ {
- mounts = append(mounts, newBenchMount())
- }
-
- ms := make(map[VirtualDentry]*Mount)
- b.ResetTimer()
- for i := range mounts {
- mount := mounts[i]
- ms[vdkey(mount)] = mount
- }
-}
-
-func BenchmarkMountSyncMapInsert(b *testing.B) {
- if !enableComparativeBenchmarks {
- b.Skipf("comparative benchmarks are disabled")
- }
-
- // Preallocate Mounts so that allocation time isn't included in the
- // benchmark.
- mounts := make([]*Mount, 0, b.N)
- for i := 0; i < b.N; i++ {
- mounts = append(mounts, newBenchMount())
- }
-
- var ms sync.Map
- b.ResetTimer()
- for i := range mounts {
- mount := mounts[i]
- ms.Store(vdkey(mount), mount)
- }
-}
-
-func BenchmarkMountTableRemove(b *testing.B) {
- mounts := make([]*Mount, 0, b.N)
- for i := 0; i < b.N; i++ {
- mounts = append(mounts, newBenchMount())
- }
- var mt mountTable
- mt.Init()
- for i := range mounts {
- mt.Insert(mounts[i])
- }
-
- b.ResetTimer()
- for i := range mounts {
- mt.Remove(mounts[i])
- }
-}
-
-func BenchmarkMountMapRemove(b *testing.B) {
- if !enableComparativeBenchmarks {
- b.Skipf("comparative benchmarks are disabled")
- }
-
- mounts := make([]*Mount, 0, b.N)
- for i := 0; i < b.N; i++ {
- mounts = append(mounts, newBenchMount())
- }
- ms := make(map[VirtualDentry]*Mount)
- for i := range mounts {
- mount := mounts[i]
- ms[vdkey(mount)] = mount
- }
-
- b.ResetTimer()
- for i := range mounts {
- mount := mounts[i]
- delete(ms, vdkey(mount))
- }
-}
-
-func BenchmarkMountSyncMapRemove(b *testing.B) {
- if !enableComparativeBenchmarks {
- b.Skipf("comparative benchmarks are disabled")
- }
-
- mounts := make([]*Mount, 0, b.N)
- for i := 0; i < b.N; i++ {
- mounts = append(mounts, newBenchMount())
- }
- var ms sync.Map
- for i := range mounts {
- mount := mounts[i]
- ms.Store(vdkey(mount), mount)
- }
-
- b.ResetTimer()
- for i := range mounts {
- mount := mounts[i]
- ms.Delete(vdkey(mount))
- }
-}
diff --git a/pkg/sentry/vfs/mount_unsafe.go b/pkg/sentry/vfs/mount_unsafe.go
deleted file mode 100644
index b0511aa40..000000000
--- a/pkg/sentry/vfs/mount_unsafe.go
+++ /dev/null
@@ -1,356 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// +build go1.12
-// +build !go1.14
-
-// Check go:linkname function signatures when updating Go version.
-
-package vfs
-
-import (
- "fmt"
- "math/bits"
- "reflect"
- "sync/atomic"
- "unsafe"
-
- "gvisor.dev/gvisor/third_party/gvsync"
-)
-
-// mountKey represents the location at which a Mount is mounted. It is
-// structurally identical to VirtualDentry, but stores its fields as
-// unsafe.Pointer since mutators synchronize with VFS path traversal using
-// seqcounts.
-type mountKey struct {
- parent unsafe.Pointer // *Mount
- point unsafe.Pointer // *Dentry
-}
-
-// Invariant: mnt.key's fields are nil. parent and point are non-nil.
-func (mnt *Mount) storeKey(parent *Mount, point *Dentry) {
- atomic.StorePointer(&mnt.key.parent, unsafe.Pointer(parent))
- atomic.StorePointer(&mnt.key.point, unsafe.Pointer(point))
-}
-
-func (mnt *Mount) loadKey() (*Mount, *Dentry) {
- return (*Mount)(atomic.LoadPointer(&mnt.key.parent)), (*Dentry)(atomic.LoadPointer(&mnt.key.point))
-}
-
-func (mnt *Mount) parent() *Mount {
- return (*Mount)(atomic.LoadPointer(&mnt.key.parent))
-}
-
-func (mnt *Mount) point() *Dentry {
- return (*Dentry)(atomic.LoadPointer(&mnt.key.point))
-}
-
-// mountTable maps (mount parent, mount point) pairs to mounts. It supports
-// efficient concurrent lookup, even in the presence of concurrent mutators
-// (provided mutation is sufficiently uncommon).
-//
-// mountTable.Init() must be called on new mountTables before use.
-type mountTable struct {
- // mountTable is implemented as a seqcount-protected hash table that
- // resolves collisions with linear probing, featuring Robin Hood insertion
- // and backward shift deletion. These minimize probe length variance,
- // significantly improving the performance of linear probing at high load
- // factors. (mountTable doesn't use bucketing, which is the other major
- // technique commonly used in high-performance hash tables; the efficiency
- // of bucketing is largely due to SIMD lookup, and Go lacks both SIMD
- // intrinsics and inline assembly, limiting the performance of this
- // approach.)
-
- seq gvsync.SeqCount
- seed uint32 // for hashing keys
-
- // size holds both length (number of elements) and capacity (number of
- // slots): capacity is stored as its base-2 log (referred to as order) in
- // the least significant bits of size, and length is stored in the
- // remaining bits. Go defines bit shifts >= width of shifted unsigned
- // operand as shifting to 0, which differs from x86's SHL, so the Go
- // compiler inserts a bounds check for each bit shift unless we mask order
- // anyway (cf. runtime.bucketShift()), and length isn't used by lookup;
- // thus this bit packing gets us more bits for the length (vs. storing
- // length and cap in separate uint32s) for ~free.
- size uint64
-
- slots unsafe.Pointer // []mountSlot; never nil after Init
-}
-
-type mountSlot struct {
- // We don't store keys in slots; instead, we just check Mount.parent and
- // Mount.point directly. Any practical use of lookup will need to touch
- // Mounts anyway, and comparing hashes means that false positives are
- // extremely rare, so this isn't an extra cache line touch overall.
- value unsafe.Pointer // *Mount
- hash uintptr
-}
-
-const (
- mtSizeOrderBits = 6 // log2 of pointer size in bits
- mtSizeOrderMask = (1 << mtSizeOrderBits) - 1
- mtSizeOrderOne = 1
- mtSizeLenLSB = mtSizeOrderBits
- mtSizeLenOne = 1 << mtSizeLenLSB
- mtSizeLenNegOne = ^uint64(mtSizeOrderMask) // uint64(-1) << mtSizeLenLSB
-
- mountSlotBytes = unsafe.Sizeof(mountSlot{})
- mountKeyBytes = unsafe.Sizeof(mountKey{})
-
- // Tuning parameters.
- //
- // Essentially every mountTable will contain at least /proc, /sys, and
- // /dev/shm, so there is ~no reason for mtInitCap to be < 4.
- mtInitOrder = 2
- mtInitCap = 1 << mtInitOrder
- mtMaxLoadNum = 13
- mtMaxLoadDen = 16
-)
-
-func init() {
- // We can't just define mtSizeOrderBits as follows because Go doesn't have
- // constexpr.
- if ptrBits := uint(unsafe.Sizeof(uintptr(0)) * 8); mtSizeOrderBits != bits.TrailingZeros(ptrBits) {
- panic(fmt.Sprintf("mtSizeOrderBits (%d) must be %d = log2 of pointer size in bits (%d)", mtSizeOrderBits, bits.TrailingZeros(ptrBits), ptrBits))
- }
- if bits.OnesCount(uint(mountSlotBytes)) != 1 {
- panic(fmt.Sprintf("sizeof(mountSlotBytes) (%d) must be a power of 2 to use bit masking for wraparound", mountSlotBytes))
- }
- if mtInitCap <= 1 {
- panic(fmt.Sprintf("mtInitCap (%d) must be at least 2 since mountTable methods assume that there will always be at least one empty slot", mtInitCap))
- }
- if mtMaxLoadNum >= mtMaxLoadDen {
- panic(fmt.Sprintf("invalid mountTable maximum load factor (%d/%d)", mtMaxLoadNum, mtMaxLoadDen))
- }
-}
-
-// Init must be called exactly once on each mountTable before use.
-func (mt *mountTable) Init() {
- mt.seed = rand32()
- mt.size = mtInitOrder
- mt.slots = newMountTableSlots(mtInitCap)
-}
-
-func newMountTableSlots(cap uintptr) unsafe.Pointer {
- slice := make([]mountSlot, cap, cap)
- hdr := (*reflect.SliceHeader)(unsafe.Pointer(&slice))
- return unsafe.Pointer(hdr.Data)
-}
-
-// Lookup returns the Mount with the given parent, mounted at the given point.
-// If no such Mount exists, Lookup returns nil.
-//
-// Lookup may be called even if there are concurrent mutators of mt.
-func (mt *mountTable) Lookup(parent *Mount, point *Dentry) *Mount {
- key := mountKey{parent: unsafe.Pointer(parent), point: unsafe.Pointer(point)}
- hash := memhash(noescape(unsafe.Pointer(&key)), uintptr(mt.seed), mountKeyBytes)
-
-loop:
- for {
- epoch := mt.seq.BeginRead()
- size := atomic.LoadUint64(&mt.size)
- slots := atomic.LoadPointer(&mt.slots)
- if !mt.seq.ReadOk(epoch) {
- continue
- }
- tcap := uintptr(1) << (size & mtSizeOrderMask)
- mask := tcap - 1
- off := (hash & mask) * mountSlotBytes
- offmask := mask * mountSlotBytes
- for {
- // This avoids bounds checking.
- slot := (*mountSlot)(unsafe.Pointer(uintptr(slots) + off))
- slotValue := atomic.LoadPointer(&slot.value)
- slotHash := atomic.LoadUintptr(&slot.hash)
- if !mt.seq.ReadOk(epoch) {
- // The element we're looking for might have been moved into a
- // slot we've previously checked, so restart entirely.
- continue loop
- }
- if slotValue == nil {
- return nil
- }
- if slotHash == hash {
- mount := (*Mount)(slotValue)
- var mountKey mountKey
- mountKey.parent = atomic.LoadPointer(&mount.key.parent)
- mountKey.point = atomic.LoadPointer(&mount.key.point)
- if !mt.seq.ReadOk(epoch) {
- continue loop
- }
- if key == mountKey {
- return mount
- }
- }
- off = (off + mountSlotBytes) & offmask
- }
- }
-}
-
-// Insert inserts the given mount into mt.
-//
-// Preconditions: There are no concurrent mutators of mt. mt must not already
-// contain a Mount with the same mount point and parent.
-func (mt *mountTable) Insert(mount *Mount) {
- hash := memhash(unsafe.Pointer(&mount.key), uintptr(mt.seed), mountKeyBytes)
-
- // We're under the maximum load factor if:
- //
- // (len+1) / cap <= mtMaxLoadNum / mtMaxLoadDen
- // (len+1) * mtMaxLoadDen <= mtMaxLoadNum * cap
- tlen := mt.size >> mtSizeLenLSB
- order := mt.size & mtSizeOrderMask
- tcap := uintptr(1) << order
- if ((tlen + 1) * mtMaxLoadDen) <= (uint64(mtMaxLoadNum) << order) {
- // Atomically insert the new element into the table.
- mt.seq.BeginWrite()
- atomic.AddUint64(&mt.size, mtSizeLenOne)
- mtInsertLocked(mt.slots, tcap, unsafe.Pointer(mount), hash)
- mt.seq.EndWrite()
- return
- }
-
- // Otherwise, we have to expand. Double the number of slots in the new
- // table.
- newOrder := order + 1
- if newOrder > mtSizeOrderMask {
- panic("mount table size overflow")
- }
- newCap := uintptr(1) << newOrder
- newSlots := newMountTableSlots(newCap)
- // Copy existing elements to the new table.
- oldCur := mt.slots
- // Go does not permit pointers to the end of allocated objects, so we
- // must use a pointer to the last element of the old table. The
- // following expression is equivalent to
- // `slots+(cap-1)*mountSlotBytes` but has a critical path length of 2
- // arithmetic instructions instead of 3.
- oldLast := unsafe.Pointer((uintptr(mt.slots) - mountSlotBytes) + (tcap * mountSlotBytes))
- for {
- oldSlot := (*mountSlot)(oldCur)
- if oldSlot.value != nil {
- // Don't need to lock mt.seq yet since newSlots isn't visible
- // to readers.
- mtInsertLocked(newSlots, newCap, oldSlot.value, oldSlot.hash)
- }
- if oldCur == oldLast {
- break
- }
- oldCur = unsafe.Pointer(uintptr(oldCur) + mountSlotBytes)
- }
- // Insert the new element into the new table.
- mtInsertLocked(newSlots, newCap, unsafe.Pointer(mount), hash)
- // Atomically switch to the new table.
- mt.seq.BeginWrite()
- atomic.AddUint64(&mt.size, mtSizeLenOne|mtSizeOrderOne)
- atomic.StorePointer(&mt.slots, newSlots)
- mt.seq.EndWrite()
-}
-
-// Preconditions: There are no concurrent mutators of the table (slots, cap).
-// If the table is visible to readers, then mt.seq must be in a writer critical
-// section. cap must be a power of 2.
-func mtInsertLocked(slots unsafe.Pointer, cap uintptr, value unsafe.Pointer, hash uintptr) {
- mask := cap - 1
- off := (hash & mask) * mountSlotBytes
- offmask := mask * mountSlotBytes
- disp := uintptr(0)
- for {
- slot := (*mountSlot)(unsafe.Pointer(uintptr(slots) + off))
- slotValue := slot.value
- if slotValue == nil {
- atomic.StorePointer(&slot.value, value)
- atomic.StoreUintptr(&slot.hash, hash)
- return
- }
- // If we've been displaced farther from our first-probed slot than the
- // element stored in this one, swap elements and switch to inserting
- // the replaced one. (This is Robin Hood insertion.)
- slotHash := slot.hash
- slotDisp := ((off / mountSlotBytes) - slotHash) & mask
- if disp > slotDisp {
- atomic.StorePointer(&slot.value, value)
- atomic.StoreUintptr(&slot.hash, hash)
- value = slotValue
- hash = slotHash
- disp = slotDisp
- }
- off = (off + mountSlotBytes) & offmask
- disp++
- }
-}
-
-// Remove removes the given mount from mt.
-//
-// Preconditions: There are no concurrent mutators of mt. mt must contain
-// mount.
-func (mt *mountTable) Remove(mount *Mount) {
- hash := memhash(unsafe.Pointer(&mount.key), uintptr(mt.seed), mountKeyBytes)
- tcap := uintptr(1) << (mt.size & mtSizeOrderMask)
- mask := tcap - 1
- slots := mt.slots
- off := (hash & mask) * mountSlotBytes
- offmask := mask * mountSlotBytes
- for {
- slot := (*mountSlot)(unsafe.Pointer(uintptr(slots) + off))
- slotValue := slot.value
- if slotValue == unsafe.Pointer(mount) {
- // Found the element to remove. Move all subsequent elements
- // backward until we either find an empty slot, or an element that
- // is already in its first-probed slot. (This is backward shift
- // deletion.)
- mt.seq.BeginWrite()
- for {
- nextOff := (off + mountSlotBytes) & offmask
- nextSlot := (*mountSlot)(unsafe.Pointer(uintptr(slots) + nextOff))
- nextSlotValue := nextSlot.value
- if nextSlotValue == nil {
- break
- }
- nextSlotHash := nextSlot.hash
- if (nextOff / mountSlotBytes) == (nextSlotHash & mask) {
- break
- }
- atomic.StorePointer(&slot.value, nextSlotValue)
- atomic.StoreUintptr(&slot.hash, nextSlotHash)
- off = nextOff
- slot = nextSlot
- }
- atomic.StorePointer(&slot.value, nil)
- atomic.AddUint64(&mt.size, mtSizeLenNegOne)
- mt.seq.EndWrite()
- return
- }
- if checkInvariants && slotValue == nil {
- panic(fmt.Sprintf("mountTable.Remove() called on missing Mount %v", mount))
- }
- off = (off + mountSlotBytes) & offmask
- }
-}
-
-//go:linkname memhash runtime.memhash
-func memhash(p unsafe.Pointer, seed, s uintptr) uintptr
-
-//go:linkname rand32 runtime.fastrand
-func rand32() uint32
-
-// This is copy/pasted from runtime.noescape(), and is needed because arguments
-// apparently escape from all functions defined by linkname.
-//
-//go:nosplit
-func noescape(p unsafe.Pointer) unsafe.Pointer {
- x := uintptr(p)
- return unsafe.Pointer(x ^ 0)
-}
diff --git a/pkg/sentry/vfs/options.go b/pkg/sentry/vfs/options.go
deleted file mode 100644
index 187e5410c..000000000
--- a/pkg/sentry/vfs/options.go
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package vfs
-
-import (
- "gvisor.dev/gvisor/pkg/abi/linux"
-)
-
-// GetDentryOptions contains options to VirtualFilesystem.GetDentryAt() and
-// FilesystemImpl.GetDentryAt().
-type GetDentryOptions struct {
- // If CheckSearchable is true, FilesystemImpl.GetDentryAt() must check that
- // the returned Dentry is a directory for which creds has search
- // permission.
- CheckSearchable bool
-}
-
-// MkdirOptions contains options to VirtualFilesystem.MkdirAt() and
-// FilesystemImpl.MkdirAt().
-type MkdirOptions struct {
- // Mode is the file mode bits for the created directory.
- Mode uint16
-}
-
-// MknodOptions contains options to VirtualFilesystem.MknodAt() and
-// FilesystemImpl.MknodAt().
-type MknodOptions struct {
- // Mode is the file type and mode bits for the created file.
- Mode uint16
-
- // If Mode specifies a character or block device special file, DevMajor and
- // DevMinor are the major and minor device numbers for the created device.
- DevMajor uint32
- DevMinor uint32
-}
-
-// OpenOptions contains options to VirtualFilesystem.OpenAt() and
-// FilesystemImpl.OpenAt().
-type OpenOptions struct {
- // Flags contains access mode and flags as specified for open(2).
- //
- // FilesystemImpls is reponsible for implementing the following flags:
- // O_RDONLY, O_WRONLY, O_RDWR, O_APPEND, O_CREAT, O_DIRECT, O_DSYNC,
- // O_EXCL, O_NOATIME, O_NOCTTY, O_NONBLOCK, O_PATH, O_SYNC, O_TMPFILE, and
- // O_TRUNC. VFS is responsible for handling O_DIRECTORY, O_LARGEFILE, and
- // O_NOFOLLOW. VFS users are responsible for handling O_CLOEXEC, since file
- // descriptors are mostly outside the scope of VFS.
- Flags uint32
-
- // If FilesystemImpl.OpenAt() creates a file, Mode is the file mode for the
- // created file.
- Mode uint16
-}
-
-// ReadOptions contains options to FileDescription.PRead(),
-// FileDescriptionImpl.PRead(), FileDescription.Read(), and
-// FileDescriptionImpl.Read().
-type ReadOptions struct {
- // Flags contains flags as specified for preadv2(2).
- Flags uint32
-}
-
-// RenameOptions contains options to VirtualFilesystem.RenameAt() and
-// FilesystemImpl.RenameAt().
-type RenameOptions struct {
- // Flags contains flags as specified for renameat2(2).
- Flags uint32
-}
-
-// SetStatOptions contains options to VirtualFilesystem.SetStatAt(),
-// FilesystemImpl.SetStatAt(), FileDescription.SetStat(), and
-// FileDescriptionImpl.SetStat().
-type SetStatOptions struct {
- // Stat is the metadata that should be set. Only fields indicated by
- // Stat.Mask should be set.
- //
- // If Stat specifies that a timestamp should be set,
- // FilesystemImpl.SetStatAt() and FileDescriptionImpl.SetStat() must
- // special-case StatxTimestamp.Nsec == UTIME_NOW as described by
- // utimensat(2); however, they do not need to check for StatxTimestamp.Nsec
- // == UTIME_OMIT (VFS users must unset the corresponding bit in Stat.Mask
- // instead).
- Stat linux.Statx
-}
-
-// StatOptions contains options to VirtualFilesystem.StatAt(),
-// FilesystemImpl.StatAt(), FileDescription.Stat(), and
-// FileDescriptionImpl.Stat().
-type StatOptions struct {
- // Mask is the set of fields in the returned Statx that the FilesystemImpl
- // or FileDescriptionImpl should provide. Bits are as in linux.Statx.Mask.
- //
- // The FilesystemImpl or FileDescriptionImpl may return fields not
- // requested in Mask, and may fail to return fields requested in Mask that
- // are not supported by the underlying filesystem implementation, without
- // returning an error.
- Mask uint32
-
- // Sync specifies the synchronization required, and is one of
- // linux.AT_STATX_SYNC_AS_STAT (which is 0, and therefore the default),
- // linux.AT_STATX_SYNC_FORCE_SYNC, or linux.AT_STATX_SYNC_DONT_SYNC.
- Sync uint32
-}
-
-// WriteOptions contains options to FileDescription.PWrite(),
-// FileDescriptionImpl.PWrite(), FileDescription.Write(), and
-// FileDescriptionImpl.Write().
-type WriteOptions struct {
- // Flags contains flags as specified for pwritev2(2).
- Flags uint32
-}
diff --git a/pkg/sentry/vfs/permissions.go b/pkg/sentry/vfs/permissions.go
deleted file mode 100644
index f8e74355c..000000000
--- a/pkg/sentry/vfs/permissions.go
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package vfs
-
-import (
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-// AccessTypes is a bitmask of Unix file permissions.
-type AccessTypes uint16
-
-// Bits in AccessTypes.
-const (
- MayRead AccessTypes = 4
- MayWrite = 2
- MayExec = 1
-)
-
-// GenericCheckPermissions checks that creds has the given access rights on a
-// file with the given permissions, UID, and GID, subject to the rules of
-// fs/namei.c:generic_permission(). isDir is true if the file is a directory.
-func GenericCheckPermissions(creds *auth.Credentials, ats AccessTypes, isDir bool, mode uint16, kuid auth.KUID, kgid auth.KGID) error {
- // Check permission bits.
- perms := mode
- if creds.EffectiveKUID == kuid {
- perms >>= 6
- } else if creds.InGroup(kgid) {
- perms >>= 3
- }
- if uint16(ats)&perms == uint16(ats) {
- return nil
- }
-
- // Caller capabilities require that the file's KUID and KGID are mapped in
- // the caller's user namespace; compare
- // kernel/capability.c:privileged_wrt_inode_uidgid().
- if !kuid.In(creds.UserNamespace).Ok() || !kgid.In(creds.UserNamespace).Ok() {
- return syserror.EACCES
- }
- // CAP_DAC_READ_SEARCH allows the caller to read and search arbitrary
- // directories, and read arbitrary non-directory files.
- if (isDir && (ats&MayWrite == 0)) || ats == MayRead {
- if creds.HasCapability(linux.CAP_DAC_READ_SEARCH) {
- return nil
- }
- }
- // CAP_DAC_OVERRIDE allows arbitrary access to directories, read/write
- // access to non-directory files, and execute access to non-directory files
- // for which at least one execute bit is set.
- if isDir || (ats&MayExec == 0) || (mode&0111 != 0) {
- if creds.HasCapability(linux.CAP_DAC_OVERRIDE) {
- return nil
- }
- }
- return syserror.EACCES
-}
-
-// AccessTypesForOpenFlags returns the access types required to open a file
-// with the given OpenOptions.Flags. Note that this is NOT the same thing as
-// the set of accesses permitted for the opened file:
-//
-// - O_TRUNC causes MayWrite to be set in the returned AccessTypes (since it
-// mutates the file), but does not permit the opened to write to the file
-// thereafter.
-//
-// - "Linux reserves the special, nonstandard access mode 3 (binary 11) in
-// flags to mean: check for read and write permission on the file and return a
-// file descriptor that can't be used for reading or writing." - open(2). Thus
-// AccessTypesForOpenFlags returns MayRead|MayWrite in this case, but
-// filesystems are responsible for ensuring that access is denied.
-//
-// Use May{Read,Write}FileWithOpenFlags() for these checks instead.
-func AccessTypesForOpenFlags(flags uint32) AccessTypes {
- switch flags & linux.O_ACCMODE {
- case linux.O_RDONLY:
- if flags&linux.O_TRUNC != 0 {
- return MayRead | MayWrite
- }
- return MayRead
- case linux.O_WRONLY:
- return MayWrite
- default:
- return MayRead | MayWrite
- }
-}
-
-// MayReadFileWithOpenFlags returns true if a file with the given open flags
-// should be readable.
-func MayReadFileWithOpenFlags(flags uint32) bool {
- switch flags & linux.O_ACCMODE {
- case linux.O_RDONLY, linux.O_RDWR:
- return true
- default:
- return false
- }
-}
-
-// MayWriteFileWithOpenFlags returns true if a file with the given open flags
-// should be writable.
-func MayWriteFileWithOpenFlags(flags uint32) bool {
- switch flags & linux.O_ACCMODE {
- case linux.O_WRONLY, linux.O_RDWR:
- return true
- default:
- return false
- }
-}
diff --git a/pkg/sentry/vfs/resolving_path.go b/pkg/sentry/vfs/resolving_path.go
deleted file mode 100644
index 8d05c8583..000000000
--- a/pkg/sentry/vfs/resolving_path.go
+++ /dev/null
@@ -1,453 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package vfs
-
-import (
- "fmt"
- "sync"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/fspath"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-// ResolvingPath represents the state of an in-progress path resolution, shared
-// between VFS and FilesystemImpl methods that take a path.
-//
-// From the perspective of FilesystemImpl methods, a ResolvingPath represents a
-// starting Dentry on the associated Filesystem (on which a reference is
-// already held) and a stream of path components relative to that Dentry.
-//
-// ResolvingPath is loosely analogous to Linux's struct nameidata.
-type ResolvingPath struct {
- vfs *VirtualFilesystem
- root VirtualDentry // refs borrowed from PathOperation
- mount *Mount
- start *Dentry
- pit fspath.Iterator
-
- flags uint16
- mustBeDir bool // final file must be a directory?
- mustBeDirOrig bool
- symlinks uint8 // number of symlinks traversed
- symlinksOrig uint8
- curPart uint8 // index into parts
- numOrigParts uint8
-
- creds *auth.Credentials
-
- // Data associated with resolve*Errors, stored in ResolvingPath so that
- // those errors don't need to allocate.
- nextMount *Mount // ref held if not nil
- nextStart *Dentry // ref held if not nil
- absSymlinkTarget fspath.Path
-
- // ResolvingPath must track up to two relative paths: the "current"
- // relative path, which is updated whenever a relative symlink is
- // encountered, and the "original" relative path, which is updated from the
- // current relative path by handleError() when resolution must change
- // filesystems (due to reaching a mount boundary or absolute symlink) and
- // overwrites the current relative path when Restart() is called.
- parts [1 + linux.MaxSymlinkTraversals]fspath.Iterator
- origParts [1 + linux.MaxSymlinkTraversals]fspath.Iterator
-}
-
-const (
- rpflagsHaveMountRef = 1 << iota // do we hold a reference on mount?
- rpflagsHaveStartRef // do we hold a reference on start?
- rpflagsFollowFinalSymlink // same as PathOperation.FollowFinalSymlink
-)
-
-func init() {
- if maxParts := len(ResolvingPath{}.parts); maxParts > 255 {
- panic(fmt.Sprintf("uint8 is insufficient to accommodate len(ResolvingPath.parts) (%d)", maxParts))
- }
-}
-
-// Error types that communicate state from the FilesystemImpl-caller,
-// VFS-callee side of path resolution (i.e. errors returned by
-// ResolvingPath.Resolve*()) to the VFS-caller, FilesystemImpl-callee side
-// (i.e. VFS methods => ResolvingPath.handleError()). These are empty structs
-// rather than error values because Go doesn't support non-primitive constants,
-// so error "constants" are really mutable vars, necessitating somewhat
-// expensive interface object comparisons.
-
-type resolveMountRootError struct{}
-
-// Error implements error.Error.
-func (resolveMountRootError) Error() string {
- return "resolving mount root"
-}
-
-type resolveMountPointError struct{}
-
-// Error implements error.Error.
-func (resolveMountPointError) Error() string {
- return "resolving mount point"
-}
-
-type resolveAbsSymlinkError struct{}
-
-// Error implements error.Error.
-func (resolveAbsSymlinkError) Error() string {
- return "resolving absolute symlink"
-}
-
-var resolvingPathPool = sync.Pool{
- New: func() interface{} {
- return &ResolvingPath{}
- },
-}
-
-func (vfs *VirtualFilesystem) getResolvingPath(creds *auth.Credentials, pop *PathOperation) (*ResolvingPath, error) {
- path, err := fspath.Parse(pop.Pathname)
- if err != nil {
- return nil, err
- }
- rp := resolvingPathPool.Get().(*ResolvingPath)
- rp.vfs = vfs
- rp.root = pop.Root
- rp.mount = pop.Start.mount
- rp.start = pop.Start.dentry
- rp.pit = path.Begin
- rp.flags = 0
- if pop.FollowFinalSymlink {
- rp.flags |= rpflagsFollowFinalSymlink
- }
- rp.mustBeDir = path.Dir
- rp.mustBeDirOrig = path.Dir
- rp.symlinks = 0
- rp.curPart = 0
- rp.numOrigParts = 1
- rp.creds = creds
- rp.parts[0] = path.Begin
- rp.origParts[0] = path.Begin
- return rp, nil
-}
-
-func (vfs *VirtualFilesystem) putResolvingPath(rp *ResolvingPath) {
- rp.root = VirtualDentry{}
- rp.decRefStartAndMount()
- rp.mount = nil
- rp.start = nil
- rp.releaseErrorState()
- resolvingPathPool.Put(rp)
-}
-
-func (rp *ResolvingPath) decRefStartAndMount() {
- if rp.flags&rpflagsHaveStartRef != 0 {
- rp.start.decRef(rp.mount.fs)
- }
- if rp.flags&rpflagsHaveMountRef != 0 {
- rp.mount.decRef()
- }
-}
-
-func (rp *ResolvingPath) releaseErrorState() {
- if rp.nextStart != nil {
- rp.nextStart.decRef(rp.nextMount.fs)
- rp.nextStart = nil
- }
- if rp.nextMount != nil {
- rp.nextMount.decRef()
- rp.nextMount = nil
- }
-}
-
-// VirtualFilesystem returns the containing VirtualFilesystem.
-func (rp *ResolvingPath) VirtualFilesystem() *VirtualFilesystem {
- return rp.vfs
-}
-
-// Credentials returns the credentials of rp's provider.
-func (rp *ResolvingPath) Credentials() *auth.Credentials {
- return rp.creds
-}
-
-// Mount returns the Mount on which path resolution is currently occurring. It
-// does not take a reference on the returned Mount.
-func (rp *ResolvingPath) Mount() *Mount {
- return rp.mount
-}
-
-// Start returns the starting Dentry represented by rp. It does not take a
-// reference on the returned Dentry.
-func (rp *ResolvingPath) Start() *Dentry {
- return rp.start
-}
-
-// Done returns true if there are no remaining path components in the stream
-// represented by rp.
-func (rp *ResolvingPath) Done() bool {
- // We don't need to check for rp.curPart == 0 because rp.Advance() won't
- // set rp.pit to a terminal iterator otherwise.
- return !rp.pit.Ok()
-}
-
-// Final returns true if there is exactly one remaining path component in the
-// stream represented by rp.
-//
-// Preconditions: !rp.Done().
-func (rp *ResolvingPath) Final() bool {
- return rp.curPart == 0 && !rp.pit.NextOk()
-}
-
-// Component returns the current path component in the stream represented by
-// rp.
-//
-// Preconditions: !rp.Done().
-func (rp *ResolvingPath) Component() string {
- if checkInvariants {
- if !rp.pit.Ok() {
- panic("ResolvingPath.Component() called at end of relative path")
- }
- }
- return rp.pit.String()
-}
-
-// Advance advances the stream of path components represented by rp.
-//
-// Preconditions: !rp.Done().
-func (rp *ResolvingPath) Advance() {
- if checkInvariants {
- if !rp.pit.Ok() {
- panic("ResolvingPath.Advance() called at end of relative path")
- }
- }
- next := rp.pit.Next()
- if next.Ok() || rp.curPart == 0 { // have next component, or at end of path
- rp.pit = next
- } else { // at end of path segment, continue with next one
- rp.curPart--
- rp.pit = rp.parts[rp.curPart-1]
- }
-}
-
-// Restart resets the stream of path components represented by rp to its state
-// on entry to the current FilesystemImpl method.
-func (rp *ResolvingPath) Restart() {
- rp.pit = rp.origParts[rp.numOrigParts-1]
- rp.mustBeDir = rp.mustBeDirOrig
- rp.symlinks = rp.symlinksOrig
- rp.curPart = rp.numOrigParts - 1
- copy(rp.parts[:], rp.origParts[:rp.numOrigParts])
- rp.releaseErrorState()
-}
-
-func (rp *ResolvingPath) relpathCommit() {
- rp.mustBeDirOrig = rp.mustBeDir
- rp.symlinksOrig = rp.symlinks
- rp.numOrigParts = rp.curPart + 1
- copy(rp.origParts[:rp.curPart], rp.parts[:])
- rp.origParts[rp.curPart] = rp.pit
-}
-
-// ResolveParent returns the VFS parent of d. It does not take a reference on
-// the returned Dentry.
-//
-// Preconditions: There are no concurrent mutators of d.
-//
-// Postconditions: If the returned error is nil, then the returned Dentry is
-// not nil.
-func (rp *ResolvingPath) ResolveParent(d *Dentry) (*Dentry, error) {
- var parent *Dentry
- if d == rp.root.dentry && rp.mount == rp.root.mount {
- // At contextual VFS root.
- parent = d
- } else if d == rp.mount.root {
- // At mount root ...
- mnt, mntpt := rp.vfs.getMountpointAt(rp.mount, rp.root)
- if mnt != nil {
- // ... of non-root mount.
- rp.nextMount = mnt
- rp.nextStart = mntpt
- return nil, resolveMountRootError{}
- }
- // ... of root mount.
- parent = d
- } else if d.parent == nil {
- // At filesystem root.
- parent = d
- } else {
- parent = d.parent
- }
- if parent.isMounted() {
- if mnt := rp.vfs.getMountAt(rp.mount, parent); mnt != nil {
- rp.nextMount = mnt
- return nil, resolveMountPointError{}
- }
- }
- return parent, nil
-}
-
-// ResolveChild returns the VFS child of d with the given name. It does not
-// take a reference on the returned Dentry. If no such child exists,
-// ResolveChild returns (nil, nil).
-//
-// Preconditions: There are no concurrent mutators of d.
-func (rp *ResolvingPath) ResolveChild(d *Dentry, name string) (*Dentry, error) {
- child := d.children[name]
- if child == nil {
- return nil, nil
- }
- if child.isMounted() {
- if mnt := rp.vfs.getMountAt(rp.mount, child); mnt != nil {
- rp.nextMount = mnt
- return nil, resolveMountPointError{}
- }
- }
- return child, nil
-}
-
-// ResolveComponent returns the Dentry reached by starting at d and resolving
-// the current path component in the stream represented by rp. It does not
-// advance the stream. It does not take a reference on the returned Dentry. If
-// no such Dentry exists, ResolveComponent returns (nil, nil).
-//
-// Preconditions: !rp.Done(). There are no concurrent mutators of d.
-func (rp *ResolvingPath) ResolveComponent(d *Dentry) (*Dentry, error) {
- switch pc := rp.Component(); pc {
- case ".":
- return d, nil
- case "..":
- return rp.ResolveParent(d)
- default:
- return rp.ResolveChild(d, pc)
- }
-}
-
-// ShouldFollowSymlink returns true if, supposing that the current path
-// component in pcs represents a symbolic link, the symbolic link should be
-// followed.
-//
-// Preconditions: !rp.Done().
-func (rp *ResolvingPath) ShouldFollowSymlink() bool {
- // Non-final symlinks are always followed.
- return rp.flags&rpflagsFollowFinalSymlink != 0 || !rp.Final()
-}
-
-// HandleSymlink is called when the current path component is a symbolic link
-// to the given target. If the calling Filesystem method should continue path
-// traversal, HandleSymlink updates the path component stream to reflect the
-// symlink target and returns nil. Otherwise it returns a non-nil error.
-//
-// Preconditions: !rp.Done().
-func (rp *ResolvingPath) HandleSymlink(target string) error {
- if rp.symlinks >= linux.MaxSymlinkTraversals {
- return syserror.ELOOP
- }
- targetPath, err := fspath.Parse(target)
- if err != nil {
- return err
- }
- rp.symlinks++
- if targetPath.Absolute {
- rp.absSymlinkTarget = targetPath
- return resolveAbsSymlinkError{}
- }
- if !targetPath.Begin.Ok() {
- panic(fmt.Sprintf("symbolic link has non-empty target %q that is both relative and has no path components?", target))
- }
- // Consume the path component that represented the symlink.
- rp.Advance()
- // Prepend the symlink target to the relative path.
- rp.relpathPrepend(targetPath)
- return nil
-}
-
-func (rp *ResolvingPath) relpathPrepend(path fspath.Path) {
- if rp.pit.Ok() {
- rp.parts[rp.curPart] = rp.pit
- rp.pit = path.Begin
- rp.curPart++
- } else {
- // The symlink was the final path component, so now the symlink target
- // is the whole path.
- rp.pit = path.Begin
- // Symlink targets can set rp.mustBeDir (if they end in a trailing /),
- // but can't unset it.
- if path.Dir {
- rp.mustBeDir = true
- }
- }
-}
-
-func (rp *ResolvingPath) handleError(err error) bool {
- switch err.(type) {
- case resolveMountRootError:
- // Switch to the new Mount. We hold references on the Mount and Dentry
- // (from VFS.getMountpointAt()).
- rp.decRefStartAndMount()
- rp.mount = rp.nextMount
- rp.start = rp.nextStart
- rp.flags |= rpflagsHaveMountRef | rpflagsHaveStartRef
- rp.nextMount = nil
- rp.nextStart = nil
- // Commit the previous FileystemImpl's progress through the relative
- // path. (Don't consume the path component that caused us to traverse
- // through the mount root - i.e. the ".." - because we still need to
- // resolve the mount point's parent in the new FilesystemImpl.)
- rp.relpathCommit()
- // Restart path resolution on the new Mount. Don't bother calling
- // rp.releaseErrorState() since we already set nextMount and nextStart
- // to nil above.
- return true
-
- case resolveMountPointError:
- // Switch to the new Mount. We hold a reference on the Mount (from
- // VFS.getMountAt()), but borrow the reference on the mount root from
- // the Mount.
- rp.decRefStartAndMount()
- rp.mount = rp.nextMount
- rp.start = rp.nextMount.root
- rp.flags = rp.flags&^rpflagsHaveStartRef | rpflagsHaveMountRef
- rp.nextMount = nil
- // Consume the path component that represented the mount point.
- rp.Advance()
- // Commit the previous FilesystemImpl's progress through the relative
- // path.
- rp.relpathCommit()
- // Restart path resolution on the new Mount.
- rp.releaseErrorState()
- return true
-
- case resolveAbsSymlinkError:
- // Switch to the new Mount. References are borrowed from rp.root.
- rp.decRefStartAndMount()
- rp.mount = rp.root.mount
- rp.start = rp.root.dentry
- rp.flags &^= rpflagsHaveMountRef | rpflagsHaveStartRef
- // Consume the path component that represented the symlink.
- rp.Advance()
- // Prepend the symlink target to the relative path.
- rp.relpathPrepend(rp.absSymlinkTarget)
- // Commit the previous FilesystemImpl's progress through the relative
- // path, including the symlink target we just prepended.
- rp.relpathCommit()
- // Restart path resolution on the new Mount.
- rp.releaseErrorState()
- return true
-
- default:
- // Not an error we can handle.
- return false
- }
-}
-
-// MustBeDir returns true if the file traversed by rp must be a directory.
-func (rp *ResolvingPath) MustBeDir() bool {
- return rp.mustBeDir
-}
diff --git a/pkg/sentry/vfs/syscalls.go b/pkg/sentry/vfs/syscalls.go
deleted file mode 100644
index 23f2b9e08..000000000
--- a/pkg/sentry/vfs/syscalls.go
+++ /dev/null
@@ -1,217 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package vfs
-
-import (
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-// PathOperation specifies the path operated on by a VFS method.
-//
-// PathOperation is passed to VFS methods by pointer to reduce memory copying:
-// it's somewhat large and should never escape. (Options structs are passed by
-// pointer to VFS and FileDescription methods for the same reason.)
-type PathOperation struct {
- // Root is the VFS root. References on Root are borrowed from the provider
- // of the PathOperation.
- //
- // Invariants: Root.Ok().
- Root VirtualDentry
-
- // Start is the starting point for the path traversal. References on Start
- // are borrowed from the provider of the PathOperation (i.e. the caller of
- // the VFS method to which the PathOperation was passed).
- //
- // Invariants: Start.Ok(). If Pathname.Absolute, then Start == Root.
- Start VirtualDentry
-
- // Path is the pathname traversed by this operation.
- Pathname string
-
- // If FollowFinalSymlink is true, and the Dentry traversed by the final
- // path component represents a symbolic link, the symbolic link should be
- // followed.
- FollowFinalSymlink bool
-}
-
-// GetDentryAt returns a VirtualDentry representing the given path, at which a
-// file must exist. A reference is taken on the returned VirtualDentry.
-func (vfs *VirtualFilesystem) GetDentryAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *GetDentryOptions) (VirtualDentry, error) {
- rp, err := vfs.getResolvingPath(creds, pop)
- if err != nil {
- return VirtualDentry{}, err
- }
- for {
- d, err := rp.mount.fs.impl.GetDentryAt(ctx, rp, *opts)
- if err == nil {
- vd := VirtualDentry{
- mount: rp.mount,
- dentry: d,
- }
- rp.mount.incRef()
- vfs.putResolvingPath(rp)
- return vd, nil
- }
- if !rp.handleError(err) {
- vfs.putResolvingPath(rp)
- return VirtualDentry{}, err
- }
- }
-}
-
-// MkdirAt creates a directory at the given path.
-func (vfs *VirtualFilesystem) MkdirAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *MkdirOptions) error {
- // "Under Linux, apart from the permission bits, the S_ISVTX mode bit is
- // also honored." - mkdir(2)
- opts.Mode &= 01777
- rp, err := vfs.getResolvingPath(creds, pop)
- if err != nil {
- return err
- }
- for {
- err := rp.mount.fs.impl.MkdirAt(ctx, rp, *opts)
- if err == nil {
- vfs.putResolvingPath(rp)
- return nil
- }
- if !rp.handleError(err) {
- vfs.putResolvingPath(rp)
- return err
- }
- }
-}
-
-// OpenAt returns a FileDescription providing access to the file at the given
-// path. A reference is taken on the returned FileDescription.
-func (vfs *VirtualFilesystem) OpenAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *OpenOptions) (*FileDescription, error) {
- // Remove:
- //
- // - O_LARGEFILE, which we always report in FileDescription status flags
- // since only 64-bit architectures are supported at this time.
- //
- // - O_CLOEXEC, which affects file descriptors and therefore must be
- // handled outside of VFS.
- //
- // - Unknown flags.
- opts.Flags &= linux.O_ACCMODE | linux.O_CREAT | linux.O_EXCL | linux.O_NOCTTY | linux.O_TRUNC | linux.O_APPEND | linux.O_NONBLOCK | linux.O_DSYNC | linux.O_ASYNC | linux.O_DIRECT | linux.O_DIRECTORY | linux.O_NOFOLLOW | linux.O_NOATIME | linux.O_SYNC | linux.O_PATH | linux.O_TMPFILE
- // Linux's __O_SYNC (which we call linux.O_SYNC) implies O_DSYNC.
- if opts.Flags&linux.O_SYNC != 0 {
- opts.Flags |= linux.O_DSYNC
- }
- // Linux's __O_TMPFILE (which we call linux.O_TMPFILE) must be specified
- // with O_DIRECTORY and a writable access mode (to ensure that it fails on
- // filesystem implementations that do not support it).
- if opts.Flags&linux.O_TMPFILE != 0 {
- if opts.Flags&linux.O_DIRECTORY == 0 {
- return nil, syserror.EINVAL
- }
- if opts.Flags&linux.O_CREAT != 0 {
- return nil, syserror.EINVAL
- }
- if opts.Flags&linux.O_ACCMODE == linux.O_RDONLY {
- return nil, syserror.EINVAL
- }
- }
- // O_PATH causes most other flags to be ignored.
- if opts.Flags&linux.O_PATH != 0 {
- opts.Flags &= linux.O_DIRECTORY | linux.O_NOFOLLOW | linux.O_PATH
- }
- // "On Linux, the following bits are also honored in mode: [S_ISUID,
- // S_ISGID, S_ISVTX]" - open(2)
- opts.Mode &= 07777
-
- if opts.Flags&linux.O_NOFOLLOW != 0 {
- pop.FollowFinalSymlink = false
- }
- rp, err := vfs.getResolvingPath(creds, pop)
- if err != nil {
- return nil, err
- }
- if opts.Flags&linux.O_DIRECTORY != 0 {
- rp.mustBeDir = true
- rp.mustBeDirOrig = true
- }
- for {
- fd, err := rp.mount.fs.impl.OpenAt(ctx, rp, *opts)
- if err == nil {
- vfs.putResolvingPath(rp)
- return fd, nil
- }
- if !rp.handleError(err) {
- vfs.putResolvingPath(rp)
- return nil, err
- }
- }
-}
-
-// StatAt returns metadata for the file at the given path.
-func (vfs *VirtualFilesystem) StatAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *StatOptions) (linux.Statx, error) {
- rp, err := vfs.getResolvingPath(creds, pop)
- if err != nil {
- return linux.Statx{}, err
- }
- for {
- stat, err := rp.mount.fs.impl.StatAt(ctx, rp, *opts)
- if err == nil {
- vfs.putResolvingPath(rp)
- return stat, nil
- }
- if !rp.handleError(err) {
- vfs.putResolvingPath(rp)
- return linux.Statx{}, err
- }
- }
-}
-
-// StatusFlags returns file description status flags.
-func (fd *FileDescription) StatusFlags(ctx context.Context) (uint32, error) {
- flags, err := fd.impl.StatusFlags(ctx)
- flags |= linux.O_LARGEFILE
- return flags, err
-}
-
-// SetStatusFlags sets file description status flags.
-func (fd *FileDescription) SetStatusFlags(ctx context.Context, flags uint32) error {
- return fd.impl.SetStatusFlags(ctx, flags)
-}
-
-// TODO:
-//
-// - VFS.SyncAllFilesystems() for sync(2)
-//
-// - Something for syncfs(2)
-//
-// - VFS.LinkAt()
-//
-// - VFS.MknodAt()
-//
-// - VFS.ReadlinkAt()
-//
-// - VFS.RenameAt()
-//
-// - VFS.RmdirAt()
-//
-// - VFS.SetStatAt()
-//
-// - VFS.StatFSAt()
-//
-// - VFS.SymlinkAt()
-//
-// - VFS.UnlinkAt()
-//
-// - FileDescription.(almost everything)
diff --git a/pkg/sentry/vfs/testutil.go b/pkg/sentry/vfs/testutil.go
deleted file mode 100644
index 70b192ece..000000000
--- a/pkg/sentry/vfs/testutil.go
+++ /dev/null
@@ -1,139 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package vfs
-
-import (
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/context"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-// FDTestFilesystemType is a test-only FilesystemType that produces Filesystems
-// for which all FilesystemImpl methods taking a path return EPERM. It is used
-// to produce Mounts and Dentries for testing of FileDescriptionImpls that do
-// not depend on their originating Filesystem.
-type FDTestFilesystemType struct{}
-
-// FDTestFilesystem is a test-only FilesystemImpl produced by
-// FDTestFilesystemType.
-type FDTestFilesystem struct {
- vfsfs Filesystem
-}
-
-// NewFilesystem implements FilesystemType.NewFilesystem.
-func (fstype FDTestFilesystemType) NewFilesystem(ctx context.Context, creds *auth.Credentials, source string, opts NewFilesystemOptions) (*Filesystem, *Dentry, error) {
- var fs FDTestFilesystem
- fs.vfsfs.Init(&fs)
- return &fs.vfsfs, fs.NewDentry(), nil
-}
-
-// Release implements FilesystemImpl.Release.
-func (fs *FDTestFilesystem) Release() {
-}
-
-// Sync implements FilesystemImpl.Sync.
-func (fs *FDTestFilesystem) Sync(ctx context.Context) error {
- return nil
-}
-
-// GetDentryAt implements FilesystemImpl.GetDentryAt.
-func (fs *FDTestFilesystem) GetDentryAt(ctx context.Context, rp *ResolvingPath, opts GetDentryOptions) (*Dentry, error) {
- return nil, syserror.EPERM
-}
-
-// LinkAt implements FilesystemImpl.LinkAt.
-func (fs *FDTestFilesystem) LinkAt(ctx context.Context, rp *ResolvingPath, vd VirtualDentry) error {
- return syserror.EPERM
-}
-
-// MkdirAt implements FilesystemImpl.MkdirAt.
-func (fs *FDTestFilesystem) MkdirAt(ctx context.Context, rp *ResolvingPath, opts MkdirOptions) error {
- return syserror.EPERM
-}
-
-// MknodAt implements FilesystemImpl.MknodAt.
-func (fs *FDTestFilesystem) MknodAt(ctx context.Context, rp *ResolvingPath, opts MknodOptions) error {
- return syserror.EPERM
-}
-
-// OpenAt implements FilesystemImpl.OpenAt.
-func (fs *FDTestFilesystem) OpenAt(ctx context.Context, rp *ResolvingPath, opts OpenOptions) (*FileDescription, error) {
- return nil, syserror.EPERM
-}
-
-// ReadlinkAt implements FilesystemImpl.ReadlinkAt.
-func (fs *FDTestFilesystem) ReadlinkAt(ctx context.Context, rp *ResolvingPath) (string, error) {
- return "", syserror.EPERM
-}
-
-// RenameAt implements FilesystemImpl.RenameAt.
-func (fs *FDTestFilesystem) RenameAt(ctx context.Context, rp *ResolvingPath, vd VirtualDentry, opts RenameOptions) error {
- return syserror.EPERM
-}
-
-// RmdirAt implements FilesystemImpl.RmdirAt.
-func (fs *FDTestFilesystem) RmdirAt(ctx context.Context, rp *ResolvingPath) error {
- return syserror.EPERM
-}
-
-// SetStatAt implements FilesystemImpl.SetStatAt.
-func (fs *FDTestFilesystem) SetStatAt(ctx context.Context, rp *ResolvingPath, opts SetStatOptions) error {
- return syserror.EPERM
-}
-
-// StatAt implements FilesystemImpl.StatAt.
-func (fs *FDTestFilesystem) StatAt(ctx context.Context, rp *ResolvingPath, opts StatOptions) (linux.Statx, error) {
- return linux.Statx{}, syserror.EPERM
-}
-
-// StatFSAt implements FilesystemImpl.StatFSAt.
-func (fs *FDTestFilesystem) StatFSAt(ctx context.Context, rp *ResolvingPath) (linux.Statfs, error) {
- return linux.Statfs{}, syserror.EPERM
-}
-
-// SymlinkAt implements FilesystemImpl.SymlinkAt.
-func (fs *FDTestFilesystem) SymlinkAt(ctx context.Context, rp *ResolvingPath, target string) error {
- return syserror.EPERM
-}
-
-// UnlinkAt implements FilesystemImpl.UnlinkAt.
-func (fs *FDTestFilesystem) UnlinkAt(ctx context.Context, rp *ResolvingPath) error {
- return syserror.EPERM
-}
-
-type fdTestDentry struct {
- vfsd Dentry
-}
-
-// NewDentry returns a new Dentry.
-func (fs *FDTestFilesystem) NewDentry() *Dentry {
- var d fdTestDentry
- d.vfsd.Init(&d)
- return &d.vfsd
-}
-
-// IncRef implements DentryImpl.IncRef.
-func (d *fdTestDentry) IncRef(vfsfs *Filesystem) {
-}
-
-// TryIncRef implements DentryImpl.TryIncRef.
-func (d *fdTestDentry) TryIncRef(vfsfs *Filesystem) bool {
- return true
-}
-
-// DecRef implements DentryImpl.DecRef.
-func (d *fdTestDentry) DecRef(vfsfs *Filesystem) {
-}
diff --git a/pkg/sentry/vfs/vfs.go b/pkg/sentry/vfs/vfs.go
deleted file mode 100644
index 4a8a69540..000000000
--- a/pkg/sentry/vfs/vfs.go
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package vfs implements a virtual filesystem layer.
-//
-// Lock order:
-//
-// Filesystem implementation locks
-// VirtualFilesystem.mountMu
-// VirtualFilesystem.fsTypesMu
-package vfs
-
-import (
- "sync"
-)
-
-// A VirtualFilesystem (VFS for short) combines Filesystems in trees of Mounts.
-//
-// There is no analogue to the VirtualFilesystem type in Linux, as the
-// equivalent state in Linux is global.
-type VirtualFilesystem struct {
- // mountMu serializes mount mutations.
- //
- // mountMu is analogous to Linux's namespace_sem.
- mountMu sync.RWMutex
-
- // mounts maps (mount parent, mount point) pairs to mounts. (Since mounts
- // are uniquely namespaced, including mount parent in the key correctly
- // handles both bind mounts and mount namespaces; Linux does the same.)
- // Synchronization between mutators and readers is provided by mounts.seq;
- // synchronization between mutators is provided by mountMu.
- //
- // mounts is used to follow mount points during path traversal. We use a
- // single table rather than per-Dentry tables to reduce size (and therefore
- // cache footprint) for the vast majority of Dentries that are not mount
- // points.
- //
- // mounts is analogous to Linux's mount_hashtable.
- mounts mountTable
-
- // mountpoints maps mount points to mounts at those points in all
- // namespaces. mountpoints is protected by mountMu.
- //
- // mountpoints is used to find mounts that must be unmounted due to
- // removal of a mount point Dentry from another mount namespace. ("A file
- // or directory that is a mount point in one namespace that is not a mount
- // point in another namespace, may be renamed, unlinked, or removed
- // (rmdir(2)) in the mount namespace in which it is not a mount point
- // (subject to the usual permission checks)." - mount_namespaces(7))
- //
- // mountpoints is analogous to Linux's mountpoint_hashtable.
- mountpoints map[*Dentry]map[*Mount]struct{}
-
- // fsTypes contains all FilesystemTypes that are usable in the
- // VirtualFilesystem. fsTypes is protected by fsTypesMu.
- fsTypesMu sync.RWMutex
- fsTypes map[string]FilesystemType
-}
-
-// New returns a new VirtualFilesystem with no mounts or FilesystemTypes.
-func New() *VirtualFilesystem {
- vfs := &VirtualFilesystem{
- mountpoints: make(map[*Dentry]map[*Mount]struct{}),
- fsTypes: make(map[string]FilesystemType),
- }
- vfs.mounts.Init()
- return vfs
-}
-
-// A VirtualDentry represents a node in a VFS tree, by combining a Dentry
-// (which represents a node in a Filesystem's tree) and a Mount (which
-// represents the Filesystem's position in a VFS mount tree).
-//
-// VirtualDentry's semantics are similar to that of a Go interface object
-// representing a pointer: it is a copyable value type that represents
-// references to another entity. The zero value of VirtualDentry is an "empty
-// VirtualDentry", directly analogous to a nil interface object.
-// VirtualDentry.Ok() checks that a VirtualDentry is not zero-valued; unless
-// otherwise specified, all other VirtualDentry methods require
-// VirtualDentry.Ok() == true.
-//
-// Mounts and Dentries are reference-counted, requiring that users call
-// VirtualDentry.{Inc,Dec}Ref() as appropriate. We often colloquially refer to
-// references on the Mount and Dentry referred to by a VirtualDentry as
-// references on the VirtualDentry itself. Unless otherwise specified, all
-// VirtualDentry methods require that a reference is held on the VirtualDentry.
-//
-// VirtualDentry is analogous to Linux's struct path.
-type VirtualDentry struct {
- mount *Mount
- dentry *Dentry
-}
-
-// Ok returns true if vd is not empty. It does not require that a reference is
-// held.
-func (vd VirtualDentry) Ok() bool {
- return vd.mount != nil
-}
-
-// IncRef increments the reference counts on the Mount and Dentry represented
-// by vd.
-func (vd VirtualDentry) IncRef() {
- vd.mount.incRef()
- vd.dentry.incRef(vd.mount.fs)
-}
-
-// DecRef decrements the reference counts on the Mount and Dentry represented
-// by vd.
-func (vd VirtualDentry) DecRef() {
- vd.dentry.decRef(vd.mount.fs)
- vd.mount.decRef()
-}
-
-// Mount returns the Mount associated with vd. It does not take a reference on
-// the returned Mount.
-func (vd VirtualDentry) Mount() *Mount {
- return vd.mount
-}
-
-// Dentry returns the Dentry associated with vd. It does not take a reference
-// on the returned Dentry.
-func (vd VirtualDentry) Dentry() *Dentry {
- return vd.dentry
-}
diff --git a/pkg/sentry/watchdog/BUILD b/pkg/sentry/watchdog/BUILD
deleted file mode 100644
index 4d8435265..000000000
--- a/pkg/sentry/watchdog/BUILD
+++ /dev/null
@@ -1,17 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "watchdog",
- srcs = ["watchdog.go"],
- importpath = "gvisor.dev/gvisor/pkg/sentry/watchdog",
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/log",
- "//pkg/metric",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/time",
- ],
-)
diff --git a/pkg/sentry/watchdog/watchdog_state_autogen.go b/pkg/sentry/watchdog/watchdog_state_autogen.go
new file mode 100755
index 000000000..530ac6a07
--- /dev/null
+++ b/pkg/sentry/watchdog/watchdog_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package watchdog
+
diff --git a/pkg/sleep/BUILD b/pkg/sleep/BUILD
deleted file mode 100644
index bdca80d37..000000000
--- a/pkg/sleep/BUILD
+++ /dev/null
@@ -1,25 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "sleep",
- srcs = [
- "commit_amd64.s",
- "commit_asm.go",
- "commit_noasm.go",
- "sleep_unsafe.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/sleep",
- visibility = ["//:sandbox"],
-)
-
-go_test(
- name = "sleep_test",
- size = "medium",
- srcs = [
- "sleep_test.go",
- ],
- embed = [":sleep"],
-)
diff --git a/pkg/sleep/empty.s b/pkg/sleep/empty.s
deleted file mode 100644
index fb37360ac..000000000
--- a/pkg/sleep/empty.s
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Empty assembly file so empty func definitions work.
diff --git a/pkg/sleep/sleep_state_autogen.go b/pkg/sleep/sleep_state_autogen.go
new file mode 100755
index 000000000..e444aa91a
--- /dev/null
+++ b/pkg/sleep/sleep_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package sleep
+
diff --git a/pkg/sleep/sleep_test.go b/pkg/sleep/sleep_test.go
deleted file mode 100644
index 130806c86..000000000
--- a/pkg/sleep/sleep_test.go
+++ /dev/null
@@ -1,542 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package sleep
-
-import (
- "math/rand"
- "runtime"
- "testing"
- "time"
-)
-
-// ZeroWakerNotAsserted tests that a zero-value waker is in non-asserted state.
-func ZeroWakerNotAsserted(t *testing.T) {
- var w Waker
- if w.IsAsserted() {
- t.Fatalf("Zero waker is asserted")
- }
-
- if w.Clear() {
- t.Fatalf("Zero waker is asserted")
- }
-}
-
-// AssertedWakerAfterAssert tests that a waker properly reports its state as
-// asserted once its Assert() method is called.
-func AssertedWakerAfterAssert(t *testing.T) {
- var w Waker
- w.Assert()
- if !w.IsAsserted() {
- t.Fatalf("Asserted waker is not reported as such")
- }
-
- if !w.Clear() {
- t.Fatalf("Asserted waker is not reported as such")
- }
-}
-
-// AssertedWakerAfterTwoAsserts tests that a waker properly reports its state as
-// asserted once its Assert() method is called twice.
-func AssertedWakerAfterTwoAsserts(t *testing.T) {
- var w Waker
- w.Assert()
- w.Assert()
- if !w.IsAsserted() {
- t.Fatalf("Asserted waker is not reported as such")
- }
-
- if !w.Clear() {
- t.Fatalf("Asserted waker is not reported as such")
- }
-}
-
-// NotAssertedWakerWithSleeper tests that a waker properly reports its state as
-// not asserted after a sleeper is associated with it.
-func NotAssertedWakerWithSleeper(t *testing.T) {
- var w Waker
- var s Sleeper
- s.AddWaker(&w, 0)
- if w.IsAsserted() {
- t.Fatalf("Non-asserted waker is reported as asserted")
- }
-
- if w.Clear() {
- t.Fatalf("Non-asserted waker is reported as asserted")
- }
-}
-
-// NotAssertedWakerAfterWake tests that a waker properly reports its state as
-// not asserted after a previous assert is consumed by a sleeper. That is, tests
-// the "edge-triggered" behavior.
-func NotAssertedWakerAfterWake(t *testing.T) {
- var w Waker
- var s Sleeper
- s.AddWaker(&w, 0)
- w.Assert()
- s.Fetch(true)
- if w.IsAsserted() {
- t.Fatalf("Consumed waker is reported as asserted")
- }
-
- if w.Clear() {
- t.Fatalf("Consumed waker is reported as asserted")
- }
-}
-
-// AssertedWakerBeforeAdd tests that a waker causes a sleeper to not sleep if
-// it's already asserted before being added.
-func AssertedWakerBeforeAdd(t *testing.T) {
- var w Waker
- var s Sleeper
- w.Assert()
- s.AddWaker(&w, 0)
-
- if _, ok := s.Fetch(false); !ok {
- t.Fatalf("Fetch failed even though asserted waker was added")
- }
-}
-
-// ClearedWaker tests that a waker properly reports its state as not asserted
-// after it is cleared.
-func ClearedWaker(t *testing.T) {
- var w Waker
- w.Assert()
- w.Clear()
- if w.IsAsserted() {
- t.Fatalf("Cleared waker is reported as asserted")
- }
-
- if w.Clear() {
- t.Fatalf("Cleared waker is reported as asserted")
- }
-}
-
-// ClearedWakerWithSleeper tests that a waker properly reports its state as
-// not asserted when it is cleared while it has a sleeper associated with it.
-func ClearedWakerWithSleeper(t *testing.T) {
- var w Waker
- var s Sleeper
- s.AddWaker(&w, 0)
- w.Clear()
- if w.IsAsserted() {
- t.Fatalf("Cleared waker is reported as asserted")
- }
-
- if w.Clear() {
- t.Fatalf("Cleared waker is reported as asserted")
- }
-}
-
-// ClearedWakerAssertedWithSleeper tests that a waker properly reports its state
-// as not asserted when it is cleared while it has a sleeper associated with it
-// and has been asserted.
-func ClearedWakerAssertedWithSleeper(t *testing.T) {
- var w Waker
- var s Sleeper
- s.AddWaker(&w, 0)
- w.Assert()
- w.Clear()
- if w.IsAsserted() {
- t.Fatalf("Cleared waker is reported as asserted")
- }
-
- if w.Clear() {
- t.Fatalf("Cleared waker is reported as asserted")
- }
-}
-
-// TestBlock tests that a sleeper actually blocks waiting for the waker to
-// assert its state.
-func TestBlock(t *testing.T) {
- var w Waker
- var s Sleeper
-
- s.AddWaker(&w, 0)
-
- // Assert waker after one second.
- before := time.Now()
- go func() {
- time.Sleep(1 * time.Second)
- w.Assert()
- }()
-
- // Fetch the result and make sure it took at least 500ms.
- if _, ok := s.Fetch(true); !ok {
- t.Fatalf("Fetch failed unexpectedly")
- }
- if d := time.Now().Sub(before); d < 500*time.Millisecond {
- t.Fatalf("Duration was too short: %v", d)
- }
-
- // Check that already-asserted waker completes inline.
- w.Assert()
- if _, ok := s.Fetch(true); !ok {
- t.Fatalf("Fetch failed unexpectedly")
- }
-
- // Check that fetch sleeps if waker had been asserted but was reset
- // before Fetch is called.
- w.Assert()
- w.Clear()
- before = time.Now()
- go func() {
- time.Sleep(1 * time.Second)
- w.Assert()
- }()
- if _, ok := s.Fetch(true); !ok {
- t.Fatalf("Fetch failed unexpectedly")
- }
- if d := time.Now().Sub(before); d < 500*time.Millisecond {
- t.Fatalf("Duration was too short: %v", d)
- }
-}
-
-// TestNonBlock checks that a sleeper won't block if waker isn't asserted.
-func TestNonBlock(t *testing.T) {
- var w Waker
- var s Sleeper
-
- // Don't block when there's no waker.
- if _, ok := s.Fetch(false); ok {
- t.Fatalf("Fetch succeeded when there is no waker")
- }
-
- // Don't block when waker isn't asserted.
- s.AddWaker(&w, 0)
- if _, ok := s.Fetch(false); ok {
- t.Fatalf("Fetch succeeded when waker was not asserted")
- }
-
- // Don't block when waker was asserted, but isn't anymore.
- w.Assert()
- w.Clear()
- if _, ok := s.Fetch(false); ok {
- t.Fatalf("Fetch succeeded when waker was not asserted anymore")
- }
-
- // Don't block when waker was consumed by previous Fetch().
- w.Assert()
- if _, ok := s.Fetch(false); !ok {
- t.Fatalf("Fetch failed even though waker was asserted")
- }
-
- if _, ok := s.Fetch(false); ok {
- t.Fatalf("Fetch succeeded when waker had been consumed")
- }
-}
-
-// TestMultiple checks that a sleeper can wait for and receives notifications
-// from multiple wakers.
-func TestMultiple(t *testing.T) {
- s := Sleeper{}
- w1 := Waker{}
- w2 := Waker{}
-
- s.AddWaker(&w1, 0)
- s.AddWaker(&w2, 1)
-
- w1.Assert()
- w2.Assert()
-
- v, ok := s.Fetch(false)
- if !ok {
- t.Fatalf("Fetch failed when there are asserted wakers")
- }
-
- if v != 0 && v != 1 {
- t.Fatalf("Unexpected waker id: %v", v)
- }
-
- want := 1 - v
- v, ok = s.Fetch(false)
- if !ok {
- t.Fatalf("Fetch failed when there is an asserted waker")
- }
-
- if v != want {
- t.Fatalf("Unexpected waker id, got %v, want %v", v, want)
- }
-}
-
-// TestDoneFunction tests if calling Done() on a sleeper works properly.
-func TestDoneFunction(t *testing.T) {
- // Trivial case of no waker.
- s := Sleeper{}
- s.Done()
-
- // Cases when the sleeper has n wakers, but none are asserted.
- for n := 1; n < 20; n++ {
- s := Sleeper{}
- w := make([]Waker, n)
- for j := 0; j < n; j++ {
- s.AddWaker(&w[j], j)
- }
- s.Done()
- }
-
- // Cases when the sleeper has n wakers, and only the i-th one is
- // asserted.
- for n := 1; n < 20; n++ {
- for i := 0; i < n; i++ {
- s := Sleeper{}
- w := make([]Waker, n)
- for j := 0; j < n; j++ {
- s.AddWaker(&w[j], j)
- }
- w[i].Assert()
- s.Done()
- }
- }
-
- // Cases when the sleeper has n wakers, and the i-th one is asserted
- // and cleared.
- for n := 1; n < 20; n++ {
- for i := 0; i < n; i++ {
- s := Sleeper{}
- w := make([]Waker, n)
- for j := 0; j < n; j++ {
- s.AddWaker(&w[j], j)
- }
- w[i].Assert()
- w[i].Clear()
- s.Done()
- }
- }
-
- // Cases when the sleeper has n wakers, with a random number of them
- // asserted.
- for n := 1; n < 20; n++ {
- for iters := 0; iters < 1000; iters++ {
- s := Sleeper{}
- w := make([]Waker, n)
- for j := 0; j < n; j++ {
- s.AddWaker(&w[j], j)
- }
-
- // Pick the number of asserted elements, then assert
- // random wakers.
- asserted := rand.Int() % (n + 1)
- for j := 0; j < asserted; j++ {
- w[rand.Int()%n].Assert()
- }
- s.Done()
- }
- }
-}
-
-// TestRace tests that multiple wakers can continuously send wake requests to
-// the sleeper.
-func TestRace(t *testing.T) {
- const wakers = 100
- const wakeRequests = 10000
-
- counts := make([]int, wakers)
- w := make([]Waker, wakers)
- s := Sleeper{}
-
- // Associate each waker and start goroutines that will assert them.
- for i := range w {
- s.AddWaker(&w[i], i)
- go func(w *Waker) {
- n := 0
- for n < wakeRequests {
- if !w.IsAsserted() {
- w.Assert()
- n++
- } else {
- runtime.Gosched()
- }
- }
- }(&w[i])
- }
-
- // Wait for all wake up notifications from all wakers.
- for i := 0; i < wakers*wakeRequests; i++ {
- v, _ := s.Fetch(true)
- counts[v]++
- }
-
- // Check that we got the right number for each.
- for i, v := range counts {
- if v != wakeRequests {
- t.Errorf("Waker %v only got %v wakes", i, v)
- }
- }
-}
-
-// BenchmarkSleeperMultiSelect measures how long it takes to fetch a wake up
-// from 4 wakers when at least one is already asserted.
-func BenchmarkSleeperMultiSelect(b *testing.B) {
- const count = 4
- s := Sleeper{}
- w := make([]Waker, count)
- for i := range w {
- s.AddWaker(&w[i], i)
- }
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- w[count-1].Assert()
- s.Fetch(true)
- }
-}
-
-// BenchmarkGoMultiSelect measures how long it takes to fetch a zero-length
-// struct from one of 4 channels when at least one is ready.
-func BenchmarkGoMultiSelect(b *testing.B) {
- const count = 4
- ch := make([]chan struct{}, count)
- for i := range ch {
- ch[i] = make(chan struct{}, 1)
- }
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- ch[count-1] <- struct{}{}
- select {
- case <-ch[0]:
- case <-ch[1]:
- case <-ch[2]:
- case <-ch[3]:
- }
- }
-}
-
-// BenchmarkSleeperSingleSelect measures how long it takes to fetch a wake up
-// from one waker that is already asserted.
-func BenchmarkSleeperSingleSelect(b *testing.B) {
- s := Sleeper{}
- w := Waker{}
- s.AddWaker(&w, 0)
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- w.Assert()
- s.Fetch(true)
- }
-}
-
-// BenchmarkGoSingleSelect measures how long it takes to fetch a zero-length
-// struct from a channel that already has it buffered.
-func BenchmarkGoSingleSelect(b *testing.B) {
- ch := make(chan struct{}, 1)
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- ch <- struct{}{}
- <-ch
- }
-}
-
-// BenchmarkSleeperAssertNonWaiting measures how long it takes to assert a
-// channel that is already asserted.
-func BenchmarkSleeperAssertNonWaiting(b *testing.B) {
- w := Waker{}
- w.Assert()
- for i := 0; i < b.N; i++ {
- w.Assert()
- }
-
-}
-
-// BenchmarkGoAssertNonWaiting measures how long it takes to write to a channel
-// that has already something written to it.
-func BenchmarkGoAssertNonWaiting(b *testing.B) {
- ch := make(chan struct{}, 1)
- ch <- struct{}{}
- for i := 0; i < b.N; i++ {
- select {
- case ch <- struct{}{}:
- default:
- }
- }
-}
-
-// BenchmarkSleeperWaitOnSingleSelect measures how long it takes to wait on one
-// waker channel while another goroutine wakes up the sleeper. This assumes that
-// a new goroutine doesn't run immediately (i.e., the creator of a new goroutine
-// is allowed to go to sleep before the new goroutine has a chance to run).
-func BenchmarkSleeperWaitOnSingleSelect(b *testing.B) {
- s := Sleeper{}
- w := Waker{}
- s.AddWaker(&w, 0)
- for i := 0; i < b.N; i++ {
- go func() {
- w.Assert()
- }()
- s.Fetch(true)
- }
-
-}
-
-// BenchmarkGoWaitOnSingleSelect measures how long it takes to wait on one
-// channel while another goroutine wakes up the sleeper. This assumes that a new
-// goroutine doesn't run immediately (i.e., the creator of a new goroutine is
-// allowed to go to sleep before the new goroutine has a chance to run).
-func BenchmarkGoWaitOnSingleSelect(b *testing.B) {
- ch := make(chan struct{}, 1)
- for i := 0; i < b.N; i++ {
- go func() {
- ch <- struct{}{}
- }()
- <-ch
- }
-}
-
-// BenchmarkSleeperWaitOnMultiSelect measures how long it takes to wait on 4
-// wakers while another goroutine wakes up the sleeper. This assumes that a new
-// goroutine doesn't run immediately (i.e., the creator of a new goroutine is
-// allowed to go to sleep before the new goroutine has a chance to run).
-func BenchmarkSleeperWaitOnMultiSelect(b *testing.B) {
- const count = 4
- s := Sleeper{}
- w := make([]Waker, count)
- for i := range w {
- s.AddWaker(&w[i], i)
- }
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- go func() {
- w[count-1].Assert()
- }()
- s.Fetch(true)
- }
-}
-
-// BenchmarkGoWaitOnMultiSelect measures how long it takes to wait on 4 channels
-// while another goroutine wakes up the sleeper. This assumes that a new
-// goroutine doesn't run immediately (i.e., the creator of a new goroutine is
-// allowed to go to sleep before the new goroutine has a chance to run).
-func BenchmarkGoWaitOnMultiSelect(b *testing.B) {
- const count = 4
- ch := make([]chan struct{}, count)
- for i := range ch {
- ch[i] = make(chan struct{}, 1)
- }
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- go func() {
- ch[count-1] <- struct{}{}
- }()
- select {
- case <-ch[0]:
- case <-ch[1]:
- case <-ch[2]:
- case <-ch[3]:
- }
- }
-}
diff --git a/pkg/state/BUILD b/pkg/state/BUILD
deleted file mode 100644
index 329904457..000000000
--- a/pkg/state/BUILD
+++ /dev/null
@@ -1,79 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-
-go_template_instance(
- name = "addr_range",
- out = "addr_range.go",
- package = "state",
- prefix = "addr",
- template = "//pkg/segment:generic_range",
- types = {
- "T": "uintptr",
- },
-)
-
-go_template_instance(
- name = "addr_set",
- out = "addr_set.go",
- consts = {
- "minDegree": "10",
- },
- imports = {
- "reflect": "reflect",
- },
- package = "state",
- prefix = "addr",
- template = "//pkg/segment:generic_set",
- types = {
- "Key": "uintptr",
- "Range": "addrRange",
- "Value": "reflect.Value",
- "Functions": "addrSetFunctions",
- },
-)
-
-go_library(
- name = "state",
- srcs = [
- "addr_range.go",
- "addr_set.go",
- "decode.go",
- "encode.go",
- "encode_unsafe.go",
- "map.go",
- "printer.go",
- "state.go",
- "stats.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/state",
- visibility = ["//:sandbox"],
- deps = [
- ":object_go_proto",
- "@com_github_golang_protobuf//proto:go_default_library",
- ],
-)
-
-proto_library(
- name = "object_proto",
- srcs = ["object.proto"],
- visibility = ["//:sandbox"],
-)
-
-go_proto_library(
- name = "object_go_proto",
- importpath = "gvisor.dev/gvisor/pkg/state/object_go_proto",
- proto = ":object_proto",
- visibility = ["//:sandbox"],
-)
-
-go_test(
- name = "state_test",
- timeout = "long",
- srcs = ["state_test.go"],
- embed = [":state"],
-)
diff --git a/pkg/state/addr_range.go b/pkg/state/addr_range.go
new file mode 100755
index 000000000..45720c643
--- /dev/null
+++ b/pkg/state/addr_range.go
@@ -0,0 +1,62 @@
+package state
+
+// A Range represents a contiguous range of T.
+//
+// +stateify savable
+type addrRange struct {
+ // Start is the inclusive start of the range.
+ Start uintptr
+
+ // End is the exclusive end of the range.
+ End uintptr
+}
+
+// WellFormed returns true if r.Start <= r.End. All other methods on a Range
+// require that the Range is well-formed.
+func (r addrRange) WellFormed() bool {
+ return r.Start <= r.End
+}
+
+// Length returns the length of the range.
+func (r addrRange) Length() uintptr {
+ return r.End - r.Start
+}
+
+// Contains returns true if r contains x.
+func (r addrRange) Contains(x uintptr) bool {
+ return r.Start <= x && x < r.End
+}
+
+// Overlaps returns true if r and r2 overlap.
+func (r addrRange) Overlaps(r2 addrRange) bool {
+ return r.Start < r2.End && r2.Start < r.End
+}
+
+// IsSupersetOf returns true if r is a superset of r2; that is, the range r2 is
+// contained within r.
+func (r addrRange) IsSupersetOf(r2 addrRange) bool {
+ return r.Start <= r2.Start && r.End >= r2.End
+}
+
+// Intersect returns a range consisting of the intersection between r and r2.
+// If r and r2 do not overlap, Intersect returns a range with unspecified
+// bounds, but for which Length() == 0.
+func (r addrRange) Intersect(r2 addrRange) addrRange {
+ if r.Start < r2.Start {
+ r.Start = r2.Start
+ }
+ if r.End > r2.End {
+ r.End = r2.End
+ }
+ if r.End < r.Start {
+ r.End = r.Start
+ }
+ return r
+}
+
+// CanSplitAt returns true if it is legal to split a segment spanning the range
+// r at x; that is, splitting at x would produce two ranges, both of which have
+// non-zero length.
+func (r addrRange) CanSplitAt(x uintptr) bool {
+ return r.Contains(x) && r.Start < x
+}
diff --git a/pkg/state/addr_set.go b/pkg/state/addr_set.go
new file mode 100755
index 000000000..5261aa488
--- /dev/null
+++ b/pkg/state/addr_set.go
@@ -0,0 +1,1274 @@
+package state
+
+import (
+ __generics_imported0 "reflect"
+)
+
+import (
+ "bytes"
+ "fmt"
+)
+
+const (
+ // minDegree is the minimum degree of an internal node in a Set B-tree.
+ //
+ // - Any non-root node has at least minDegree-1 segments.
+ //
+ // - Any non-root internal (non-leaf) node has at least minDegree children.
+ //
+ // - The root node may have fewer than minDegree-1 segments, but it may
+ // only have 0 segments if the tree is empty.
+ //
+ // Our implementation requires minDegree >= 3. Higher values of minDegree
+ // usually improve performance, but increase memory usage for small sets.
+ addrminDegree = 10
+
+ addrmaxDegree = 2 * addrminDegree
+)
+
+// A Set is a mapping of segments with non-overlapping Range keys. The zero
+// value for a Set is an empty set. Set values are not safely movable nor
+// copyable. Set is thread-compatible.
+//
+// +stateify savable
+type addrSet struct {
+ root addrnode `state:".(*addrSegmentDataSlices)"`
+}
+
+// IsEmpty returns true if the set contains no segments.
+func (s *addrSet) IsEmpty() bool {
+ return s.root.nrSegments == 0
+}
+
+// IsEmptyRange returns true iff no segments in the set overlap the given
+// range. This is semantically equivalent to s.SpanRange(r) == 0, but may be
+// more efficient.
+func (s *addrSet) IsEmptyRange(r addrRange) bool {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return true
+ }
+ _, gap := s.Find(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ return r.End <= gap.End()
+}
+
+// Span returns the total size of all segments in the set.
+func (s *addrSet) Span() uintptr {
+ var sz uintptr
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sz += seg.Range().Length()
+ }
+ return sz
+}
+
+// SpanRange returns the total size of the intersection of segments in the set
+// with the given range.
+func (s *addrSet) SpanRange(r addrRange) uintptr {
+ switch {
+ case r.Length() < 0:
+ panic(fmt.Sprintf("invalid range %v", r))
+ case r.Length() == 0:
+ return 0
+ }
+ var sz uintptr
+ for seg := s.LowerBoundSegment(r.Start); seg.Ok() && seg.Start() < r.End; seg = seg.NextSegment() {
+ sz += seg.Range().Intersect(r).Length()
+ }
+ return sz
+}
+
+// FirstSegment returns the first segment in the set. If the set is empty,
+// FirstSegment returns a terminal iterator.
+func (s *addrSet) FirstSegment() addrIterator {
+ if s.root.nrSegments == 0 {
+ return addrIterator{}
+ }
+ return s.root.firstSegment()
+}
+
+// LastSegment returns the last segment in the set. If the set is empty,
+// LastSegment returns a terminal iterator.
+func (s *addrSet) LastSegment() addrIterator {
+ if s.root.nrSegments == 0 {
+ return addrIterator{}
+ }
+ return s.root.lastSegment()
+}
+
+// FirstGap returns the first gap in the set.
+func (s *addrSet) FirstGap() addrGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return addrGapIterator{n, 0}
+}
+
+// LastGap returns the last gap in the set.
+func (s *addrSet) LastGap() addrGapIterator {
+ n := &s.root
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return addrGapIterator{n, n.nrSegments}
+}
+
+// Find returns the segment or gap whose range contains the given key. If a
+// segment is found, the returned Iterator is non-terminal and the
+// returned GapIterator is terminal. Otherwise, the returned Iterator is
+// terminal and the returned GapIterator is non-terminal.
+func (s *addrSet) Find(key uintptr) (addrIterator, addrGapIterator) {
+ n := &s.root
+ for {
+
+ lower := 0
+ upper := n.nrSegments
+ for lower < upper {
+ i := lower + (upper-lower)/2
+ if r := n.keys[i]; key < r.End {
+ if key >= r.Start {
+ return addrIterator{n, i}, addrGapIterator{}
+ }
+ upper = i
+ } else {
+ lower = i + 1
+ }
+ }
+ i := lower
+ if !n.hasChildren {
+ return addrIterator{}, addrGapIterator{n, i}
+ }
+ n = n.children[i]
+ }
+}
+
+// FindSegment returns the segment whose range contains the given key. If no
+// such segment exists, FindSegment returns a terminal iterator.
+func (s *addrSet) FindSegment(key uintptr) addrIterator {
+ seg, _ := s.Find(key)
+ return seg
+}
+
+// LowerBoundSegment returns the segment with the lowest range that contains a
+// key greater than or equal to min. If no such segment exists,
+// LowerBoundSegment returns a terminal iterator.
+func (s *addrSet) LowerBoundSegment(min uintptr) addrIterator {
+ seg, gap := s.Find(min)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.NextSegment()
+}
+
+// UpperBoundSegment returns the segment with the highest range that contains a
+// key less than or equal to max. If no such segment exists, UpperBoundSegment
+// returns a terminal iterator.
+func (s *addrSet) UpperBoundSegment(max uintptr) addrIterator {
+ seg, gap := s.Find(max)
+ if seg.Ok() {
+ return seg
+ }
+ return gap.PrevSegment()
+}
+
+// FindGap returns the gap containing the given key. If no such gap exists
+// (i.e. the set contains a segment containing that key), FindGap returns a
+// terminal iterator.
+func (s *addrSet) FindGap(key uintptr) addrGapIterator {
+ _, gap := s.Find(key)
+ return gap
+}
+
+// LowerBoundGap returns the gap with the lowest range that is greater than or
+// equal to min.
+func (s *addrSet) LowerBoundGap(min uintptr) addrGapIterator {
+ seg, gap := s.Find(min)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.NextGap()
+}
+
+// UpperBoundGap returns the gap with the highest range that is less than or
+// equal to max.
+func (s *addrSet) UpperBoundGap(max uintptr) addrGapIterator {
+ seg, gap := s.Find(max)
+ if gap.Ok() {
+ return gap
+ }
+ return seg.PrevGap()
+}
+
+// Add inserts the given segment into the set and returns true. If the new
+// segment can be merged with adjacent segments, Add will do so. If the new
+// segment would overlap an existing segment, Add returns false. If Add
+// succeeds, all existing iterators are invalidated.
+func (s *addrSet) Add(r addrRange, val __generics_imported0.Value) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.Insert(gap, r, val)
+ return true
+}
+
+// AddWithoutMerging inserts the given segment into the set and returns true.
+// If it would overlap an existing segment, AddWithoutMerging does nothing and
+// returns false. If AddWithoutMerging succeeds, all existing iterators are
+// invalidated.
+func (s *addrSet) AddWithoutMerging(r addrRange, val __generics_imported0.Value) bool {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ gap := s.FindGap(r.Start)
+ if !gap.Ok() {
+ return false
+ }
+ if r.End > gap.End() {
+ return false
+ }
+ s.InsertWithoutMergingUnchecked(gap, r, val)
+ return true
+}
+
+// Insert inserts the given segment into the given gap. If the new segment can
+// be merged with adjacent segments, Insert will do so. Insert returns an
+// iterator to the segment containing the inserted value (which may have been
+// merged with other values). All existing iterators (including gap, but not
+// including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid, Insert panics.
+//
+// Insert is semantically equivalent to a InsertWithoutMerging followed by a
+// Merge, but may be more efficient. Note that there is no unchecked variant of
+// Insert since Insert must retrieve and inspect gap's predecessor and
+// successor segments regardless.
+func (s *addrSet) Insert(gap addrGapIterator, r addrRange, val __generics_imported0.Value) addrIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ prev, next := gap.PrevSegment(), gap.NextSegment()
+ if prev.Ok() && prev.End() > r.Start {
+ panic(fmt.Sprintf("new segment %v overlaps predecessor %v", r, prev.Range()))
+ }
+ if next.Ok() && next.Start() < r.End {
+ panic(fmt.Sprintf("new segment %v overlaps successor %v", r, next.Range()))
+ }
+ if prev.Ok() && prev.End() == r.Start {
+ if mval, ok := (addrSetFunctions{}).Merge(prev.Range(), prev.Value(), r, val); ok {
+ prev.SetEndUnchecked(r.End)
+ prev.SetValue(mval)
+ if next.Ok() && next.Start() == r.End {
+ val = mval
+ if mval, ok := (addrSetFunctions{}).Merge(prev.Range(), val, next.Range(), next.Value()); ok {
+ prev.SetEndUnchecked(next.End())
+ prev.SetValue(mval)
+ return s.Remove(next).PrevSegment()
+ }
+ }
+ return prev
+ }
+ }
+ if next.Ok() && next.Start() == r.End {
+ if mval, ok := (addrSetFunctions{}).Merge(r, val, next.Range(), next.Value()); ok {
+ next.SetStartUnchecked(r.Start)
+ next.SetValue(mval)
+ return next
+ }
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMerging inserts the given segment into the given gap and
+// returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid,
+// InsertWithoutMerging panics.
+func (s *addrSet) InsertWithoutMerging(gap addrGapIterator, r addrRange, val __generics_imported0.Value) addrIterator {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if gr := gap.Range(); !gr.IsSupersetOf(r) {
+ panic(fmt.Sprintf("cannot insert segment range %v into gap range %v", r, gr))
+ }
+ return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMergingUnchecked inserts the given segment into the given gap
+// and returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// Preconditions: r.Start >= gap.Start(); r.End <= gap.End().
+func (s *addrSet) InsertWithoutMergingUnchecked(gap addrGapIterator, r addrRange, val __generics_imported0.Value) addrIterator {
+ gap = gap.node.rebalanceBeforeInsert(gap)
+ copy(gap.node.keys[gap.index+1:], gap.node.keys[gap.index:gap.node.nrSegments])
+ copy(gap.node.values[gap.index+1:], gap.node.values[gap.index:gap.node.nrSegments])
+ gap.node.keys[gap.index] = r
+ gap.node.values[gap.index] = val
+ gap.node.nrSegments++
+ return addrIterator{gap.node, gap.index}
+}
+
+// Remove removes the given segment and returns an iterator to the vacated gap.
+// All existing iterators (including seg, but not including the returned
+// iterator) are invalidated.
+func (s *addrSet) Remove(seg addrIterator) addrGapIterator {
+
+ if seg.node.hasChildren {
+
+ victim := seg.PrevSegment()
+
+ seg.SetRangeUnchecked(victim.Range())
+ seg.SetValue(victim.Value())
+ return s.Remove(victim).NextGap()
+ }
+ copy(seg.node.keys[seg.index:], seg.node.keys[seg.index+1:seg.node.nrSegments])
+ copy(seg.node.values[seg.index:], seg.node.values[seg.index+1:seg.node.nrSegments])
+ addrSetFunctions{}.ClearValue(&seg.node.values[seg.node.nrSegments-1])
+ seg.node.nrSegments--
+ return seg.node.rebalanceAfterRemove(addrGapIterator{seg.node, seg.index})
+}
+
+// RemoveAll removes all segments from the set. All existing iterators are
+// invalidated.
+func (s *addrSet) RemoveAll() {
+ s.root = addrnode{}
+}
+
+// RemoveRange removes all segments in the given range. An iterator to the
+// newly formed gap is returned, and all existing iterators are invalidated.
+func (s *addrSet) RemoveRange(r addrRange) addrGapIterator {
+ seg, gap := s.Find(r.Start)
+ if seg.Ok() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ for seg = gap.NextSegment(); seg.Ok() && seg.Start() < r.End; seg = gap.NextSegment() {
+ seg = s.Isolate(seg, r)
+ gap = s.Remove(seg)
+ }
+ return gap
+}
+
+// Merge attempts to merge two neighboring segments. If successful, Merge
+// returns an iterator to the merged segment, and all existing iterators are
+// invalidated. Otherwise, Merge returns a terminal iterator.
+//
+// If first is not the predecessor of second, Merge panics.
+func (s *addrSet) Merge(first, second addrIterator) addrIterator {
+ if first.NextSegment() != second {
+ panic(fmt.Sprintf("attempt to merge non-neighboring segments %v, %v", first.Range(), second.Range()))
+ }
+ return s.MergeUnchecked(first, second)
+}
+
+// MergeUnchecked attempts to merge two neighboring segments. If successful,
+// MergeUnchecked returns an iterator to the merged segment, and all existing
+// iterators are invalidated. Otherwise, MergeUnchecked returns a terminal
+// iterator.
+//
+// Precondition: first is the predecessor of second: first.NextSegment() ==
+// second, first == second.PrevSegment().
+func (s *addrSet) MergeUnchecked(first, second addrIterator) addrIterator {
+ if first.End() == second.Start() {
+ if mval, ok := (addrSetFunctions{}).Merge(first.Range(), first.Value(), second.Range(), second.Value()); ok {
+
+ first.SetEndUnchecked(second.End())
+ first.SetValue(mval)
+ return s.Remove(second).PrevSegment()
+ }
+ }
+ return addrIterator{}
+}
+
+// MergeAll attempts to merge all adjacent segments in the set. All existing
+// iterators are invalidated.
+func (s *addrSet) MergeAll() {
+ seg := s.FirstSegment()
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeRange attempts to merge all adjacent segments that contain a key in the
+// specific range. All existing iterators are invalidated.
+func (s *addrSet) MergeRange(r addrRange) {
+ seg := s.LowerBoundSegment(r.Start)
+ if !seg.Ok() {
+ return
+ }
+ next := seg.NextSegment()
+ for next.Ok() && next.Range().Start < r.End {
+ if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+ seg, next = mseg, mseg.NextSegment()
+ } else {
+ seg, next = next, next.NextSegment()
+ }
+ }
+}
+
+// MergeAdjacent attempts to merge the segment containing r.Start with its
+// predecessor, and the segment containing r.End-1 with its successor.
+func (s *addrSet) MergeAdjacent(r addrRange) {
+ first := s.FindSegment(r.Start)
+ if first.Ok() {
+ if prev := first.PrevSegment(); prev.Ok() {
+ s.Merge(prev, first)
+ }
+ }
+ last := s.FindSegment(r.End - 1)
+ if last.Ok() {
+ if next := last.NextSegment(); next.Ok() {
+ s.Merge(last, next)
+ }
+ }
+}
+
+// Split splits the given segment at the given key and returns iterators to the
+// two resulting segments. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+//
+// If the segment cannot be split at split (because split is at the start or
+// end of the segment's range, so splitting would produce a segment with zero
+// length, or because split falls outside the segment's range altogether),
+// Split panics.
+func (s *addrSet) Split(seg addrIterator, split uintptr) (addrIterator, addrIterator) {
+ if !seg.Range().CanSplitAt(split) {
+ panic(fmt.Sprintf("can't split %v at %v", seg.Range(), split))
+ }
+ return s.SplitUnchecked(seg, split)
+}
+
+// SplitUnchecked splits the given segment at the given key and returns
+// iterators to the two resulting segments. All existing iterators (including
+// seg, but not including the returned iterators) are invalidated.
+//
+// Preconditions: seg.Start() < key < seg.End().
+func (s *addrSet) SplitUnchecked(seg addrIterator, split uintptr) (addrIterator, addrIterator) {
+ val1, val2 := (addrSetFunctions{}).Split(seg.Range(), seg.Value(), split)
+ end2 := seg.End()
+ seg.SetEndUnchecked(split)
+ seg.SetValue(val1)
+ seg2 := s.InsertWithoutMergingUnchecked(seg.NextGap(), addrRange{split, end2}, val2)
+
+ return seg2.PrevSegment(), seg2
+}
+
+// SplitAt splits the segment straddling split, if one exists. SplitAt returns
+// true if a segment was split and false otherwise. If SplitAt splits a
+// segment, all existing iterators are invalidated.
+func (s *addrSet) SplitAt(split uintptr) bool {
+ if seg := s.FindSegment(split); seg.Ok() && seg.Range().CanSplitAt(split) {
+ s.SplitUnchecked(seg, split)
+ return true
+ }
+ return false
+}
+
+// Isolate ensures that the given segment's range does not escape r by
+// splitting at r.Start and r.End if necessary, and returns an updated iterator
+// to the bounded segment. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+func (s *addrSet) Isolate(seg addrIterator, r addrRange) addrIterator {
+ if seg.Range().CanSplitAt(r.Start) {
+ _, seg = s.SplitUnchecked(seg, r.Start)
+ }
+ if seg.Range().CanSplitAt(r.End) {
+ seg, _ = s.SplitUnchecked(seg, r.End)
+ }
+ return seg
+}
+
+// ApplyContiguous applies a function to a contiguous range of segments,
+// splitting if necessary. The function is applied until the first gap is
+// encountered, at which point the gap is returned. If the function is applied
+// across the entire range, a terminal gap is returned. All existing iterators
+// are invalidated.
+//
+// N.B. The Iterator must not be invalidated by the function.
+func (s *addrSet) ApplyContiguous(r addrRange, fn func(seg addrIterator)) addrGapIterator {
+ seg, gap := s.Find(r.Start)
+ if !seg.Ok() {
+ return gap
+ }
+ for {
+ seg = s.Isolate(seg, r)
+ fn(seg)
+ if seg.End() >= r.End {
+ return addrGapIterator{}
+ }
+ gap = seg.NextGap()
+ if !gap.IsEmpty() {
+ return gap
+ }
+ seg = gap.NextSegment()
+ if !seg.Ok() {
+
+ return addrGapIterator{}
+ }
+ }
+}
+
+// +stateify savable
+type addrnode struct {
+ // An internal binary tree node looks like:
+ //
+ // K
+ // / \
+ // Cl Cr
+ //
+ // where all keys in the subtree rooted by Cl (the left subtree) are less
+ // than K (the key of the parent node), and all keys in the subtree rooted
+ // by Cr (the right subtree) are greater than K.
+ //
+ // An internal B-tree node's indexes work out to look like:
+ //
+ // K0 K1 K2 ... Kn-1
+ // / \/ \/ \ ... / \
+ // C0 C1 C2 C3 ... Cn-1 Cn
+ //
+ // where n is nrSegments.
+ nrSegments int
+
+ // parent is a pointer to this node's parent. If this node is root, parent
+ // is nil.
+ parent *addrnode
+
+ // parentIndex is the index of this node in parent.children.
+ parentIndex int
+
+ // Flag for internal nodes that is technically redundant with "children[0]
+ // != nil", but is stored in the first cache line. "hasChildren" rather
+ // than "isLeaf" because false must be the correct value for an empty root.
+ hasChildren bool
+
+ // Nodes store keys and values in separate arrays to maximize locality in
+ // the common case (scanning keys for lookup).
+ keys [addrmaxDegree - 1]addrRange
+ values [addrmaxDegree - 1]__generics_imported0.Value
+ children [addrmaxDegree]*addrnode
+}
+
+// firstSegment returns the first segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *addrnode) firstSegment() addrIterator {
+ for n.hasChildren {
+ n = n.children[0]
+ }
+ return addrIterator{n, 0}
+}
+
+// lastSegment returns the last segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *addrnode) lastSegment() addrIterator {
+ for n.hasChildren {
+ n = n.children[n.nrSegments]
+ }
+ return addrIterator{n, n.nrSegments - 1}
+}
+
+func (n *addrnode) prevSibling() *addrnode {
+ if n.parent == nil || n.parentIndex == 0 {
+ return nil
+ }
+ return n.parent.children[n.parentIndex-1]
+}
+
+func (n *addrnode) nextSibling() *addrnode {
+ if n.parent == nil || n.parentIndex == n.parent.nrSegments {
+ return nil
+ }
+ return n.parent.children[n.parentIndex+1]
+}
+
+// rebalanceBeforeInsert splits n and its ancestors if they are full, as
+// required for insertion, and returns an updated iterator to the position
+// represented by gap.
+func (n *addrnode) rebalanceBeforeInsert(gap addrGapIterator) addrGapIterator {
+ if n.parent != nil {
+ gap = n.parent.rebalanceBeforeInsert(gap)
+ }
+ if n.nrSegments < addrmaxDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ left := &addrnode{
+ nrSegments: addrminDegree - 1,
+ parent: n,
+ parentIndex: 0,
+ hasChildren: n.hasChildren,
+ }
+ right := &addrnode{
+ nrSegments: addrminDegree - 1,
+ parent: n,
+ parentIndex: 1,
+ hasChildren: n.hasChildren,
+ }
+ copy(left.keys[:addrminDegree-1], n.keys[:addrminDegree-1])
+ copy(left.values[:addrminDegree-1], n.values[:addrminDegree-1])
+ copy(right.keys[:addrminDegree-1], n.keys[addrminDegree:])
+ copy(right.values[:addrminDegree-1], n.values[addrminDegree:])
+ n.keys[0], n.values[0] = n.keys[addrminDegree-1], n.values[addrminDegree-1]
+ addrzeroValueSlice(n.values[1:])
+ if n.hasChildren {
+ copy(left.children[:addrminDegree], n.children[:addrminDegree])
+ copy(right.children[:addrminDegree], n.children[addrminDegree:])
+ addrzeroNodeSlice(n.children[2:])
+ for i := 0; i < addrminDegree; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ right.children[i].parent = right
+ right.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = 1
+ n.hasChildren = true
+ n.children[0] = left
+ n.children[1] = right
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < addrminDegree {
+ return addrGapIterator{left, gap.index}
+ }
+ return addrGapIterator{right, gap.index - addrminDegree}
+ }
+
+ copy(n.parent.keys[n.parentIndex+1:], n.parent.keys[n.parentIndex:n.parent.nrSegments])
+ copy(n.parent.values[n.parentIndex+1:], n.parent.values[n.parentIndex:n.parent.nrSegments])
+ n.parent.keys[n.parentIndex], n.parent.values[n.parentIndex] = n.keys[addrminDegree-1], n.values[addrminDegree-1]
+ copy(n.parent.children[n.parentIndex+2:], n.parent.children[n.parentIndex+1:n.parent.nrSegments+1])
+ for i := n.parentIndex + 2; i < n.parent.nrSegments+2; i++ {
+ n.parent.children[i].parentIndex = i
+ }
+ sibling := &addrnode{
+ nrSegments: addrminDegree - 1,
+ parent: n.parent,
+ parentIndex: n.parentIndex + 1,
+ hasChildren: n.hasChildren,
+ }
+ n.parent.children[n.parentIndex+1] = sibling
+ n.parent.nrSegments++
+ copy(sibling.keys[:addrminDegree-1], n.keys[addrminDegree:])
+ copy(sibling.values[:addrminDegree-1], n.values[addrminDegree:])
+ addrzeroValueSlice(n.values[addrminDegree-1:])
+ if n.hasChildren {
+ copy(sibling.children[:addrminDegree], n.children[addrminDegree:])
+ addrzeroNodeSlice(n.children[addrminDegree:])
+ for i := 0; i < addrminDegree; i++ {
+ sibling.children[i].parent = sibling
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments = addrminDegree - 1
+
+ if gap.node != n {
+ return gap
+ }
+ if gap.index < addrminDegree {
+ return gap
+ }
+ return addrGapIterator{sibling, gap.index - addrminDegree}
+}
+
+// rebalanceAfterRemove "unsplits" n and its ancestors if they are deficient
+// (contain fewer segments than required by B-tree invariants), as required for
+// removal, and returns an updated iterator to the position represented by gap.
+//
+// Precondition: n is the only node in the tree that may currently violate a
+// B-tree invariant.
+func (n *addrnode) rebalanceAfterRemove(gap addrGapIterator) addrGapIterator {
+ for {
+ if n.nrSegments >= addrminDegree-1 {
+ return gap
+ }
+ if n.parent == nil {
+
+ return gap
+ }
+
+ if sibling := n.prevSibling(); sibling != nil && sibling.nrSegments >= addrminDegree {
+ copy(n.keys[1:], n.keys[:n.nrSegments])
+ copy(n.values[1:], n.values[:n.nrSegments])
+ n.keys[0] = n.parent.keys[n.parentIndex-1]
+ n.values[0] = n.parent.values[n.parentIndex-1]
+ n.parent.keys[n.parentIndex-1] = sibling.keys[sibling.nrSegments-1]
+ n.parent.values[n.parentIndex-1] = sibling.values[sibling.nrSegments-1]
+ addrSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ copy(n.children[1:], n.children[:n.nrSegments+1])
+ n.children[0] = sibling.children[sibling.nrSegments]
+ sibling.children[sibling.nrSegments] = nil
+ n.children[0].parent = n
+ n.children[0].parentIndex = 0
+ for i := 1; i < n.nrSegments+2; i++ {
+ n.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling && gap.index == sibling.nrSegments {
+ return addrGapIterator{n, 0}
+ }
+ if gap.node == n {
+ return addrGapIterator{n, gap.index + 1}
+ }
+ return gap
+ }
+ if sibling := n.nextSibling(); sibling != nil && sibling.nrSegments >= addrminDegree {
+ n.keys[n.nrSegments] = n.parent.keys[n.parentIndex]
+ n.values[n.nrSegments] = n.parent.values[n.parentIndex]
+ n.parent.keys[n.parentIndex] = sibling.keys[0]
+ n.parent.values[n.parentIndex] = sibling.values[0]
+ copy(sibling.keys[:sibling.nrSegments-1], sibling.keys[1:])
+ copy(sibling.values[:sibling.nrSegments-1], sibling.values[1:])
+ addrSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+ if n.hasChildren {
+ n.children[n.nrSegments+1] = sibling.children[0]
+ copy(sibling.children[:sibling.nrSegments], sibling.children[1:])
+ sibling.children[sibling.nrSegments] = nil
+ n.children[n.nrSegments+1].parent = n
+ n.children[n.nrSegments+1].parentIndex = n.nrSegments + 1
+ for i := 0; i < sibling.nrSegments; i++ {
+ sibling.children[i].parentIndex = i
+ }
+ }
+ n.nrSegments++
+ sibling.nrSegments--
+ if gap.node == sibling {
+ if gap.index == 0 {
+ return addrGapIterator{n, n.nrSegments}
+ }
+ return addrGapIterator{sibling, gap.index - 1}
+ }
+ return gap
+ }
+
+ p := n.parent
+ if p.nrSegments == 1 {
+
+ left, right := p.children[0], p.children[1]
+ p.nrSegments = left.nrSegments + right.nrSegments + 1
+ p.hasChildren = left.hasChildren
+ p.keys[left.nrSegments] = p.keys[0]
+ p.values[left.nrSegments] = p.values[0]
+ copy(p.keys[:left.nrSegments], left.keys[:left.nrSegments])
+ copy(p.values[:left.nrSegments], left.values[:left.nrSegments])
+ copy(p.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(p.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(p.children[:left.nrSegments+1], left.children[:left.nrSegments+1])
+ copy(p.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := 0; i < p.nrSegments+1; i++ {
+ p.children[i].parent = p
+ p.children[i].parentIndex = i
+ }
+ } else {
+ p.children[0] = nil
+ p.children[1] = nil
+ }
+ if gap.node == left {
+ return addrGapIterator{p, gap.index}
+ }
+ if gap.node == right {
+ return addrGapIterator{p, gap.index + left.nrSegments + 1}
+ }
+ return gap
+ }
+ // Merge n and either sibling, along with the segment separating the
+ // two, into whichever of the two nodes comes first. This is the
+ // reverse of the non-root splitting case in
+ // node.rebalanceBeforeInsert.
+ var left, right *addrnode
+ if n.parentIndex > 0 {
+ left = n.prevSibling()
+ right = n
+ } else {
+ left = n
+ right = n.nextSibling()
+ }
+
+ if gap.node == right {
+ gap = addrGapIterator{left, gap.index + left.nrSegments + 1}
+ }
+ left.keys[left.nrSegments] = p.keys[left.parentIndex]
+ left.values[left.nrSegments] = p.values[left.parentIndex]
+ copy(left.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+ copy(left.values[left.nrSegments+1:], right.values[:right.nrSegments])
+ if left.hasChildren {
+ copy(left.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+ for i := left.nrSegments + 1; i < left.nrSegments+right.nrSegments+2; i++ {
+ left.children[i].parent = left
+ left.children[i].parentIndex = i
+ }
+ }
+ left.nrSegments += right.nrSegments + 1
+ copy(p.keys[left.parentIndex:], p.keys[left.parentIndex+1:p.nrSegments])
+ copy(p.values[left.parentIndex:], p.values[left.parentIndex+1:p.nrSegments])
+ addrSetFunctions{}.ClearValue(&p.values[p.nrSegments-1])
+ copy(p.children[left.parentIndex+1:], p.children[left.parentIndex+2:p.nrSegments+1])
+ for i := 0; i < p.nrSegments; i++ {
+ p.children[i].parentIndex = i
+ }
+ p.children[p.nrSegments] = nil
+ p.nrSegments--
+
+ n = p
+ }
+}
+
+// A Iterator is conceptually one of:
+//
+// - A pointer to a segment in a set; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Iterators are copyable values and are meaningfully equality-comparable. The
+// zero value of Iterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type addrIterator struct {
+ // node is the node containing the iterated segment. If the iterator is
+ // terminal, node is nil.
+ node *addrnode
+
+ // index is the index of the segment in node.keys/values.
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (seg addrIterator) Ok() bool {
+ return seg.node != nil
+}
+
+// Range returns the iterated segment's range key.
+func (seg addrIterator) Range() addrRange {
+ return seg.node.keys[seg.index]
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (seg addrIterator) Start() uintptr {
+ return seg.node.keys[seg.index].Start
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (seg addrIterator) End() uintptr {
+ return seg.node.keys[seg.index].End
+}
+
+// SetRangeUnchecked mutates the iterated segment's range key. This operation
+// does not invalidate any iterators.
+//
+// Preconditions:
+//
+// - r.Length() > 0.
+//
+// - The new range must not overlap an existing one: If seg.NextSegment().Ok(),
+// then r.end <= seg.NextSegment().Start(); if seg.PrevSegment().Ok(), then
+// r.start >= seg.PrevSegment().End().
+func (seg addrIterator) SetRangeUnchecked(r addrRange) {
+ seg.node.keys[seg.index] = r
+}
+
+// SetRange mutates the iterated segment's range key. If the new range would
+// cause the iterated segment to overlap another segment, or if the new range
+// is invalid, SetRange panics. This operation does not invalidate any
+// iterators.
+func (seg addrIterator) SetRange(r addrRange) {
+ if r.Length() <= 0 {
+ panic(fmt.Sprintf("invalid segment range %v", r))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && r.Start < prev.End() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, prev.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && r.End > next.Start() {
+ panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, next.Range()))
+ }
+ seg.SetRangeUnchecked(r)
+}
+
+// SetStartUnchecked mutates the iterated segment's start. This operation does
+// not invalidate any iterators.
+//
+// Preconditions: The new start must be valid: start < seg.End(); if
+// seg.PrevSegment().Ok(), then start >= seg.PrevSegment().End().
+func (seg addrIterator) SetStartUnchecked(start uintptr) {
+ seg.node.keys[seg.index].Start = start
+}
+
+// SetStart mutates the iterated segment's start. If the new start value would
+// cause the iterated segment to overlap another segment, or would result in an
+// invalid range, SetStart panics. This operation does not invalidate any
+// iterators.
+func (seg addrIterator) SetStart(start uintptr) {
+ if start >= seg.End() {
+ panic(fmt.Sprintf("new start %v would invalidate segment range %v", start, seg.Range()))
+ }
+ if prev := seg.PrevSegment(); prev.Ok() && start < prev.End() {
+ panic(fmt.Sprintf("new start %v would cause segment range %v to overlap segment range %v", start, seg.Range(), prev.Range()))
+ }
+ seg.SetStartUnchecked(start)
+}
+
+// SetEndUnchecked mutates the iterated segment's end. This operation does not
+// invalidate any iterators.
+//
+// Preconditions: The new end must be valid: end > seg.Start(); if
+// seg.NextSegment().Ok(), then end <= seg.NextSegment().Start().
+func (seg addrIterator) SetEndUnchecked(end uintptr) {
+ seg.node.keys[seg.index].End = end
+}
+
+// SetEnd mutates the iterated segment's end. If the new end value would cause
+// the iterated segment to overlap another segment, or would result in an
+// invalid range, SetEnd panics. This operation does not invalidate any
+// iterators.
+func (seg addrIterator) SetEnd(end uintptr) {
+ if end <= seg.Start() {
+ panic(fmt.Sprintf("new end %v would invalidate segment range %v", end, seg.Range()))
+ }
+ if next := seg.NextSegment(); next.Ok() && end > next.Start() {
+ panic(fmt.Sprintf("new end %v would cause segment range %v to overlap segment range %v", end, seg.Range(), next.Range()))
+ }
+ seg.SetEndUnchecked(end)
+}
+
+// Value returns a copy of the iterated segment's value.
+func (seg addrIterator) Value() __generics_imported0.Value {
+ return seg.node.values[seg.index]
+}
+
+// ValuePtr returns a pointer to the iterated segment's value. The pointer is
+// invalidated if the iterator is invalidated. This operation does not
+// invalidate any iterators.
+func (seg addrIterator) ValuePtr() *__generics_imported0.Value {
+ return &seg.node.values[seg.index]
+}
+
+// SetValue mutates the iterated segment's value. This operation does not
+// invalidate any iterators.
+func (seg addrIterator) SetValue(val __generics_imported0.Value) {
+ seg.node.values[seg.index] = val
+}
+
+// PrevSegment returns the iterated segment's predecessor. If there is no
+// preceding segment, PrevSegment returns a terminal iterator.
+func (seg addrIterator) PrevSegment() addrIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index].lastSegment()
+ }
+ if seg.index > 0 {
+ return addrIterator{seg.node, seg.index - 1}
+ }
+ if seg.node.parent == nil {
+ return addrIterator{}
+ }
+ return addrsegmentBeforePosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// NextSegment returns the iterated segment's successor. If there is no
+// succeeding segment, NextSegment returns a terminal iterator.
+func (seg addrIterator) NextSegment() addrIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment()
+ }
+ if seg.index < seg.node.nrSegments-1 {
+ return addrIterator{seg.node, seg.index + 1}
+ }
+ if seg.node.parent == nil {
+ return addrIterator{}
+ }
+ return addrsegmentAfterPosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// PrevGap returns the gap immediately before the iterated segment.
+func (seg addrIterator) PrevGap() addrGapIterator {
+ if seg.node.hasChildren {
+
+ return seg.node.children[seg.index].lastSegment().NextGap()
+ }
+ return addrGapIterator{seg.node, seg.index}
+}
+
+// NextGap returns the gap immediately after the iterated segment.
+func (seg addrIterator) NextGap() addrGapIterator {
+ if seg.node.hasChildren {
+ return seg.node.children[seg.index+1].firstSegment().PrevGap()
+ }
+ return addrGapIterator{seg.node, seg.index + 1}
+}
+
+// PrevNonEmpty returns the iterated segment's predecessor if it is adjacent,
+// or the gap before the iterated segment otherwise. If seg.Start() ==
+// Functions.MinKey(), PrevNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by PrevNonEmpty will be
+// non-terminal.
+func (seg addrIterator) PrevNonEmpty() (addrIterator, addrGapIterator) {
+ gap := seg.PrevGap()
+ if gap.Range().Length() != 0 {
+ return addrIterator{}, gap
+ }
+ return gap.PrevSegment(), addrGapIterator{}
+}
+
+// NextNonEmpty returns the iterated segment's successor if it is adjacent, or
+// the gap after the iterated segment otherwise. If seg.End() ==
+// Functions.MaxKey(), NextNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by NextNonEmpty will be
+// non-terminal.
+func (seg addrIterator) NextNonEmpty() (addrIterator, addrGapIterator) {
+ gap := seg.NextGap()
+ if gap.Range().Length() != 0 {
+ return addrIterator{}, gap
+ }
+ return gap.NextSegment(), addrGapIterator{}
+}
+
+// A GapIterator is conceptually one of:
+//
+// - A pointer to a position between two segments, before the first segment, or
+// after the last segment in a set, called a *gap*; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Note that the gap between two adjacent segments exists (iterators to it are
+// non-terminal), but has a length of zero. GapIterator.IsEmpty returns true
+// for such gaps. An empty set contains a single gap, spanning the entire range
+// of the set's keys.
+//
+// GapIterators are copyable values and are meaningfully equality-comparable.
+// The zero value of GapIterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type addrGapIterator struct {
+ // The representation of a GapIterator is identical to that of an Iterator,
+ // except that index corresponds to positions between segments in the same
+ // way as for node.children (see comment for node.nrSegments).
+ node *addrnode
+ index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (gap addrGapIterator) Ok() bool {
+ return gap.node != nil
+}
+
+// Range returns the range spanned by the iterated gap.
+func (gap addrGapIterator) Range() addrRange {
+ return addrRange{gap.Start(), gap.End()}
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (gap addrGapIterator) Start() uintptr {
+ if ps := gap.PrevSegment(); ps.Ok() {
+ return ps.End()
+ }
+ return addrSetFunctions{}.MinKey()
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (gap addrGapIterator) End() uintptr {
+ if ns := gap.NextSegment(); ns.Ok() {
+ return ns.Start()
+ }
+ return addrSetFunctions{}.MaxKey()
+}
+
+// IsEmpty returns true if the iterated gap is empty (that is, the "gap" is
+// between two adjacent segments.)
+func (gap addrGapIterator) IsEmpty() bool {
+ return gap.Range().Length() == 0
+}
+
+// PrevSegment returns the segment immediately before the iterated gap. If no
+// such segment exists, PrevSegment returns a terminal iterator.
+func (gap addrGapIterator) PrevSegment() addrIterator {
+ return addrsegmentBeforePosition(gap.node, gap.index)
+}
+
+// NextSegment returns the segment immediately after the iterated gap. If no
+// such segment exists, NextSegment returns a terminal iterator.
+func (gap addrGapIterator) NextSegment() addrIterator {
+ return addrsegmentAfterPosition(gap.node, gap.index)
+}
+
+// PrevGap returns the iterated gap's predecessor. If no such gap exists,
+// PrevGap returns a terminal iterator.
+func (gap addrGapIterator) PrevGap() addrGapIterator {
+ seg := gap.PrevSegment()
+ if !seg.Ok() {
+ return addrGapIterator{}
+ }
+ return seg.PrevGap()
+}
+
+// NextGap returns the iterated gap's successor. If no such gap exists, NextGap
+// returns a terminal iterator.
+func (gap addrGapIterator) NextGap() addrGapIterator {
+ seg := gap.NextSegment()
+ if !seg.Ok() {
+ return addrGapIterator{}
+ }
+ return seg.NextGap()
+}
+
+// segmentBeforePosition returns the predecessor segment of the position given
+// by n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentBeforePosition returns a terminal iterator.
+func addrsegmentBeforePosition(n *addrnode, i int) addrIterator {
+ for i == 0 {
+ if n.parent == nil {
+ return addrIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return addrIterator{n, i - 1}
+}
+
+// segmentAfterPosition returns the successor segment of the position given by
+// n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentAfterPosition returns a terminal iterator.
+func addrsegmentAfterPosition(n *addrnode, i int) addrIterator {
+ for i == n.nrSegments {
+ if n.parent == nil {
+ return addrIterator{}
+ }
+ n, i = n.parent, n.parentIndex
+ }
+ return addrIterator{n, i}
+}
+
+func addrzeroValueSlice(slice []__generics_imported0.Value) {
+
+ for i := range slice {
+ addrSetFunctions{}.ClearValue(&slice[i])
+ }
+}
+
+func addrzeroNodeSlice(slice []*addrnode) {
+ for i := range slice {
+ slice[i] = nil
+ }
+}
+
+// String stringifies a Set for debugging.
+func (s *addrSet) String() string {
+ return s.root.String()
+}
+
+// String stringifies a node (and all of its children) for debugging.
+func (n *addrnode) String() string {
+ var buf bytes.Buffer
+ n.writeDebugString(&buf, "")
+ return buf.String()
+}
+
+func (n *addrnode) writeDebugString(buf *bytes.Buffer, prefix string) {
+ if n.hasChildren != (n.nrSegments > 0 && n.children[0] != nil) {
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent value of hasChildren: got %v, want %v\n", n.hasChildren, !n.hasChildren))
+ }
+ for i := 0; i < n.nrSegments; i++ {
+ if child := n.children[i]; child != nil {
+ cprefix := fmt.Sprintf("%s- % 3d ", prefix, i)
+ if child.parent != n || child.parentIndex != i {
+ buf.WriteString(cprefix)
+ buf.WriteString(fmt.Sprintf("WARNING: inconsistent linkage to parent: got (%p, %d), want (%p, %d)\n", child.parent, child.parentIndex, n, i))
+ }
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, i))
+ }
+ buf.WriteString(prefix)
+ buf.WriteString(fmt.Sprintf("- % 3d: %v => %v\n", i, n.keys[i], n.values[i]))
+ }
+ if child := n.children[n.nrSegments]; child != nil {
+ child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, n.nrSegments))
+ }
+}
+
+// SegmentDataSlices represents segments from a set as slices of start, end, and
+// values. SegmentDataSlices is primarily used as an intermediate representation
+// for save/restore and the layout here is optimized for that.
+//
+// +stateify savable
+type addrSegmentDataSlices struct {
+ Start []uintptr
+ End []uintptr
+ Values []__generics_imported0.Value
+}
+
+// ExportSortedSlice returns a copy of all segments in the given set, in ascending
+// key order.
+func (s *addrSet) ExportSortedSlices() *addrSegmentDataSlices {
+ var sds addrSegmentDataSlices
+ for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+ sds.Start = append(sds.Start, seg.Start())
+ sds.End = append(sds.End, seg.End())
+ sds.Values = append(sds.Values, seg.Value())
+ }
+ sds.Start = sds.Start[:len(sds.Start):len(sds.Start)]
+ sds.End = sds.End[:len(sds.End):len(sds.End)]
+ sds.Values = sds.Values[:len(sds.Values):len(sds.Values)]
+ return &sds
+}
+
+// ImportSortedSlice initializes the given set from the given slice.
+//
+// Preconditions: s must be empty. sds must represent a valid set (the segments
+// in sds must have valid lengths that do not overlap). The segments in sds
+// must be sorted in ascending key order.
+func (s *addrSet) ImportSortedSlices(sds *addrSegmentDataSlices) error {
+ if !s.IsEmpty() {
+ return fmt.Errorf("cannot import into non-empty set %v", s)
+ }
+ gap := s.FirstGap()
+ for i := range sds.Start {
+ r := addrRange{sds.Start[i], sds.End[i]}
+ if !gap.Range().IsSupersetOf(r) {
+ return fmt.Errorf("segment overlaps a preceding segment or is incorrectly sorted: [%d, %d) => %v", sds.Start[i], sds.End[i], sds.Values[i])
+ }
+ gap = s.InsertWithoutMerging(gap, r, sds.Values[i]).NextGap()
+ }
+ return nil
+}
+func (s *addrSet) saveRoot() *addrSegmentDataSlices {
+ return s.ExportSortedSlices()
+}
+
+func (s *addrSet) loadRoot(sds *addrSegmentDataSlices) {
+ if err := s.ImportSortedSlices(sds); err != nil {
+ panic(err)
+ }
+}
diff --git a/pkg/state/object.proto b/pkg/state/object.proto
deleted file mode 100644
index 952289069..000000000
--- a/pkg/state/object.proto
+++ /dev/null
@@ -1,140 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto3";
-
-package gvisor.state.statefile;
-
-// Slice is a slice value.
-message Slice {
- uint32 length = 1;
- uint32 capacity = 2;
- uint64 ref_value = 3;
-}
-
-// Array is an array value.
-message Array {
- repeated Object contents = 1;
-}
-
-// Map is a map value.
-message Map {
- repeated Object keys = 1;
- repeated Object values = 2;
-}
-
-// Interface is an interface value.
-message Interface {
- string type = 1;
- Object value = 2;
-}
-
-// Struct is a basic composite value.
-message Struct {
- repeated Field fields = 1;
-}
-
-// Field encodes a single field.
-message Field {
- string name = 1;
- Object value = 2;
-}
-
-// Uint16s encodes an uint16 array. To be used inside oneof structure.
-message Uint16s {
- // There is no 16-bit type in protobuf so we use variable length 32-bit here.
- repeated uint32 values = 1;
-}
-
-// Uint32s encodes an uint32 array. To be used inside oneof structure.
-message Uint32s {
- repeated fixed32 values = 1;
-}
-
-// Uint64s encodes an uint64 array. To be used inside oneof structure.
-message Uint64s {
- repeated fixed64 values = 1;
-}
-
-// Uintptrs encodes an uintptr array. To be used inside oneof structure.
-message Uintptrs {
- repeated fixed64 values = 1;
-}
-
-// Int8s encodes an int8 array. To be used inside oneof structure.
-message Int8s {
- bytes values = 1;
-}
-
-// Int16s encodes an int16 array. To be used inside oneof structure.
-message Int16s {
- // There is no 16-bit type in protobuf so we use variable length 32-bit here.
- repeated int32 values = 1;
-}
-
-// Int32s encodes an int32 array. To be used inside oneof structure.
-message Int32s {
- repeated sfixed32 values = 1;
-}
-
-// Int64s encodes an int64 array. To be used inside oneof structure.
-message Int64s {
- repeated sfixed64 values = 1;
-}
-
-// Bools encodes a boolean array. To be used inside oneof structure.
-message Bools {
- repeated bool values = 1;
-}
-
-// Float64s encodes a float64 array. To be used inside oneof structure.
-message Float64s {
- repeated double values = 1;
-}
-
-// Float32s encodes a float32 array. To be used inside oneof structure.
-message Float32s {
- repeated float values = 1;
-}
-
-// Object are primitive encodings.
-//
-// Note that ref_value references an Object.id, below.
-message Object {
- oneof value {
- bool bool_value = 1;
- bytes string_value = 2;
- int64 int64_value = 3;
- uint64 uint64_value = 4;
- double double_value = 5;
- uint64 ref_value = 6;
- Slice slice_value = 7;
- Array array_value = 8;
- Interface interface_value = 9;
- Struct struct_value = 10;
- Map map_value = 11;
- bytes byte_array_value = 12;
- Uint16s uint16_array_value = 13;
- Uint32s uint32_array_value = 14;
- Uint64s uint64_array_value = 15;
- Uintptrs uintptr_array_value = 16;
- Int8s int8_array_value = 17;
- Int16s int16_array_value = 18;
- Int32s int32_array_value = 19;
- Int64s int64_array_value = 20;
- Bools bool_array_value = 21;
- Float64s float64_array_value = 22;
- Float32s float32_array_value = 23;
- }
-}
diff --git a/pkg/state/object_go_proto/object.pb.go b/pkg/state/object_go_proto/object.pb.go
new file mode 100755
index 000000000..dc5127149
--- /dev/null
+++ b/pkg/state/object_go_proto/object.pb.go
@@ -0,0 +1,1195 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: pkg/state/object.proto
+
+package gvisor_state_statefile
+
+import (
+ fmt "fmt"
+ proto "github.com/golang/protobuf/proto"
+ math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+type Slice struct {
+ Length uint32 `protobuf:"varint,1,opt,name=length,proto3" json:"length,omitempty"`
+ Capacity uint32 `protobuf:"varint,2,opt,name=capacity,proto3" json:"capacity,omitempty"`
+ RefValue uint64 `protobuf:"varint,3,opt,name=ref_value,json=refValue,proto3" json:"ref_value,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Slice) Reset() { *m = Slice{} }
+func (m *Slice) String() string { return proto.CompactTextString(m) }
+func (*Slice) ProtoMessage() {}
+func (*Slice) Descriptor() ([]byte, []int) {
+ return fileDescriptor_3dee2c1912d4d62d, []int{0}
+}
+
+func (m *Slice) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Slice.Unmarshal(m, b)
+}
+func (m *Slice) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Slice.Marshal(b, m, deterministic)
+}
+func (m *Slice) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Slice.Merge(m, src)
+}
+func (m *Slice) XXX_Size() int {
+ return xxx_messageInfo_Slice.Size(m)
+}
+func (m *Slice) XXX_DiscardUnknown() {
+ xxx_messageInfo_Slice.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Slice proto.InternalMessageInfo
+
+func (m *Slice) GetLength() uint32 {
+ if m != nil {
+ return m.Length
+ }
+ return 0
+}
+
+func (m *Slice) GetCapacity() uint32 {
+ if m != nil {
+ return m.Capacity
+ }
+ return 0
+}
+
+func (m *Slice) GetRefValue() uint64 {
+ if m != nil {
+ return m.RefValue
+ }
+ return 0
+}
+
+type Array struct {
+ Contents []*Object `protobuf:"bytes,1,rep,name=contents,proto3" json:"contents,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Array) Reset() { *m = Array{} }
+func (m *Array) String() string { return proto.CompactTextString(m) }
+func (*Array) ProtoMessage() {}
+func (*Array) Descriptor() ([]byte, []int) {
+ return fileDescriptor_3dee2c1912d4d62d, []int{1}
+}
+
+func (m *Array) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Array.Unmarshal(m, b)
+}
+func (m *Array) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Array.Marshal(b, m, deterministic)
+}
+func (m *Array) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Array.Merge(m, src)
+}
+func (m *Array) XXX_Size() int {
+ return xxx_messageInfo_Array.Size(m)
+}
+func (m *Array) XXX_DiscardUnknown() {
+ xxx_messageInfo_Array.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Array proto.InternalMessageInfo
+
+func (m *Array) GetContents() []*Object {
+ if m != nil {
+ return m.Contents
+ }
+ return nil
+}
+
+type Map struct {
+ Keys []*Object `protobuf:"bytes,1,rep,name=keys,proto3" json:"keys,omitempty"`
+ Values []*Object `protobuf:"bytes,2,rep,name=values,proto3" json:"values,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Map) Reset() { *m = Map{} }
+func (m *Map) String() string { return proto.CompactTextString(m) }
+func (*Map) ProtoMessage() {}
+func (*Map) Descriptor() ([]byte, []int) {
+ return fileDescriptor_3dee2c1912d4d62d, []int{2}
+}
+
+func (m *Map) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Map.Unmarshal(m, b)
+}
+func (m *Map) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Map.Marshal(b, m, deterministic)
+}
+func (m *Map) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Map.Merge(m, src)
+}
+func (m *Map) XXX_Size() int {
+ return xxx_messageInfo_Map.Size(m)
+}
+func (m *Map) XXX_DiscardUnknown() {
+ xxx_messageInfo_Map.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Map proto.InternalMessageInfo
+
+func (m *Map) GetKeys() []*Object {
+ if m != nil {
+ return m.Keys
+ }
+ return nil
+}
+
+func (m *Map) GetValues() []*Object {
+ if m != nil {
+ return m.Values
+ }
+ return nil
+}
+
+type Interface struct {
+ Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
+ Value *Object `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Interface) Reset() { *m = Interface{} }
+func (m *Interface) String() string { return proto.CompactTextString(m) }
+func (*Interface) ProtoMessage() {}
+func (*Interface) Descriptor() ([]byte, []int) {
+ return fileDescriptor_3dee2c1912d4d62d, []int{3}
+}
+
+func (m *Interface) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Interface.Unmarshal(m, b)
+}
+func (m *Interface) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Interface.Marshal(b, m, deterministic)
+}
+func (m *Interface) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Interface.Merge(m, src)
+}
+func (m *Interface) XXX_Size() int {
+ return xxx_messageInfo_Interface.Size(m)
+}
+func (m *Interface) XXX_DiscardUnknown() {
+ xxx_messageInfo_Interface.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Interface proto.InternalMessageInfo
+
+func (m *Interface) GetType() string {
+ if m != nil {
+ return m.Type
+ }
+ return ""
+}
+
+func (m *Interface) GetValue() *Object {
+ if m != nil {
+ return m.Value
+ }
+ return nil
+}
+
+type Struct struct {
+ Fields []*Field `protobuf:"bytes,1,rep,name=fields,proto3" json:"fields,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Struct) Reset() { *m = Struct{} }
+func (m *Struct) String() string { return proto.CompactTextString(m) }
+func (*Struct) ProtoMessage() {}
+func (*Struct) Descriptor() ([]byte, []int) {
+ return fileDescriptor_3dee2c1912d4d62d, []int{4}
+}
+
+func (m *Struct) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Struct.Unmarshal(m, b)
+}
+func (m *Struct) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Struct.Marshal(b, m, deterministic)
+}
+func (m *Struct) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Struct.Merge(m, src)
+}
+func (m *Struct) XXX_Size() int {
+ return xxx_messageInfo_Struct.Size(m)
+}
+func (m *Struct) XXX_DiscardUnknown() {
+ xxx_messageInfo_Struct.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Struct proto.InternalMessageInfo
+
+func (m *Struct) GetFields() []*Field {
+ if m != nil {
+ return m.Fields
+ }
+ return nil
+}
+
+type Field struct {
+ Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+ Value *Object `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Field) Reset() { *m = Field{} }
+func (m *Field) String() string { return proto.CompactTextString(m) }
+func (*Field) ProtoMessage() {}
+func (*Field) Descriptor() ([]byte, []int) {
+ return fileDescriptor_3dee2c1912d4d62d, []int{5}
+}
+
+func (m *Field) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Field.Unmarshal(m, b)
+}
+func (m *Field) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Field.Marshal(b, m, deterministic)
+}
+func (m *Field) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Field.Merge(m, src)
+}
+func (m *Field) XXX_Size() int {
+ return xxx_messageInfo_Field.Size(m)
+}
+func (m *Field) XXX_DiscardUnknown() {
+ xxx_messageInfo_Field.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Field proto.InternalMessageInfo
+
+func (m *Field) GetName() string {
+ if m != nil {
+ return m.Name
+ }
+ return ""
+}
+
+func (m *Field) GetValue() *Object {
+ if m != nil {
+ return m.Value
+ }
+ return nil
+}
+
+type Uint16S struct {
+ Values []uint32 `protobuf:"varint,1,rep,packed,name=values,proto3" json:"values,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Uint16S) Reset() { *m = Uint16S{} }
+func (m *Uint16S) String() string { return proto.CompactTextString(m) }
+func (*Uint16S) ProtoMessage() {}
+func (*Uint16S) Descriptor() ([]byte, []int) {
+ return fileDescriptor_3dee2c1912d4d62d, []int{6}
+}
+
+func (m *Uint16S) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Uint16S.Unmarshal(m, b)
+}
+func (m *Uint16S) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Uint16S.Marshal(b, m, deterministic)
+}
+func (m *Uint16S) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Uint16S.Merge(m, src)
+}
+func (m *Uint16S) XXX_Size() int {
+ return xxx_messageInfo_Uint16S.Size(m)
+}
+func (m *Uint16S) XXX_DiscardUnknown() {
+ xxx_messageInfo_Uint16S.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Uint16S proto.InternalMessageInfo
+
+func (m *Uint16S) GetValues() []uint32 {
+ if m != nil {
+ return m.Values
+ }
+ return nil
+}
+
+type Uint32S struct {
+ Values []uint32 `protobuf:"fixed32,1,rep,packed,name=values,proto3" json:"values,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Uint32S) Reset() { *m = Uint32S{} }
+func (m *Uint32S) String() string { return proto.CompactTextString(m) }
+func (*Uint32S) ProtoMessage() {}
+func (*Uint32S) Descriptor() ([]byte, []int) {
+ return fileDescriptor_3dee2c1912d4d62d, []int{7}
+}
+
+func (m *Uint32S) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Uint32S.Unmarshal(m, b)
+}
+func (m *Uint32S) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Uint32S.Marshal(b, m, deterministic)
+}
+func (m *Uint32S) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Uint32S.Merge(m, src)
+}
+func (m *Uint32S) XXX_Size() int {
+ return xxx_messageInfo_Uint32S.Size(m)
+}
+func (m *Uint32S) XXX_DiscardUnknown() {
+ xxx_messageInfo_Uint32S.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Uint32S proto.InternalMessageInfo
+
+func (m *Uint32S) GetValues() []uint32 {
+ if m != nil {
+ return m.Values
+ }
+ return nil
+}
+
+type Uint64S struct {
+ Values []uint64 `protobuf:"fixed64,1,rep,packed,name=values,proto3" json:"values,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Uint64S) Reset() { *m = Uint64S{} }
+func (m *Uint64S) String() string { return proto.CompactTextString(m) }
+func (*Uint64S) ProtoMessage() {}
+func (*Uint64S) Descriptor() ([]byte, []int) {
+ return fileDescriptor_3dee2c1912d4d62d, []int{8}
+}
+
+func (m *Uint64S) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Uint64S.Unmarshal(m, b)
+}
+func (m *Uint64S) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Uint64S.Marshal(b, m, deterministic)
+}
+func (m *Uint64S) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Uint64S.Merge(m, src)
+}
+func (m *Uint64S) XXX_Size() int {
+ return xxx_messageInfo_Uint64S.Size(m)
+}
+func (m *Uint64S) XXX_DiscardUnknown() {
+ xxx_messageInfo_Uint64S.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Uint64S proto.InternalMessageInfo
+
+func (m *Uint64S) GetValues() []uint64 {
+ if m != nil {
+ return m.Values
+ }
+ return nil
+}
+
+type Uintptrs struct {
+ Values []uint64 `protobuf:"fixed64,1,rep,packed,name=values,proto3" json:"values,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Uintptrs) Reset() { *m = Uintptrs{} }
+func (m *Uintptrs) String() string { return proto.CompactTextString(m) }
+func (*Uintptrs) ProtoMessage() {}
+func (*Uintptrs) Descriptor() ([]byte, []int) {
+ return fileDescriptor_3dee2c1912d4d62d, []int{9}
+}
+
+func (m *Uintptrs) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Uintptrs.Unmarshal(m, b)
+}
+func (m *Uintptrs) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Uintptrs.Marshal(b, m, deterministic)
+}
+func (m *Uintptrs) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Uintptrs.Merge(m, src)
+}
+func (m *Uintptrs) XXX_Size() int {
+ return xxx_messageInfo_Uintptrs.Size(m)
+}
+func (m *Uintptrs) XXX_DiscardUnknown() {
+ xxx_messageInfo_Uintptrs.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Uintptrs proto.InternalMessageInfo
+
+func (m *Uintptrs) GetValues() []uint64 {
+ if m != nil {
+ return m.Values
+ }
+ return nil
+}
+
+type Int8S struct {
+ Values []byte `protobuf:"bytes,1,opt,name=values,proto3" json:"values,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Int8S) Reset() { *m = Int8S{} }
+func (m *Int8S) String() string { return proto.CompactTextString(m) }
+func (*Int8S) ProtoMessage() {}
+func (*Int8S) Descriptor() ([]byte, []int) {
+ return fileDescriptor_3dee2c1912d4d62d, []int{10}
+}
+
+func (m *Int8S) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Int8S.Unmarshal(m, b)
+}
+func (m *Int8S) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Int8S.Marshal(b, m, deterministic)
+}
+func (m *Int8S) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Int8S.Merge(m, src)
+}
+func (m *Int8S) XXX_Size() int {
+ return xxx_messageInfo_Int8S.Size(m)
+}
+func (m *Int8S) XXX_DiscardUnknown() {
+ xxx_messageInfo_Int8S.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Int8S proto.InternalMessageInfo
+
+func (m *Int8S) GetValues() []byte {
+ if m != nil {
+ return m.Values
+ }
+ return nil
+}
+
+type Int16S struct {
+ Values []int32 `protobuf:"varint,1,rep,packed,name=values,proto3" json:"values,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Int16S) Reset() { *m = Int16S{} }
+func (m *Int16S) String() string { return proto.CompactTextString(m) }
+func (*Int16S) ProtoMessage() {}
+func (*Int16S) Descriptor() ([]byte, []int) {
+ return fileDescriptor_3dee2c1912d4d62d, []int{11}
+}
+
+func (m *Int16S) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Int16S.Unmarshal(m, b)
+}
+func (m *Int16S) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Int16S.Marshal(b, m, deterministic)
+}
+func (m *Int16S) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Int16S.Merge(m, src)
+}
+func (m *Int16S) XXX_Size() int {
+ return xxx_messageInfo_Int16S.Size(m)
+}
+func (m *Int16S) XXX_DiscardUnknown() {
+ xxx_messageInfo_Int16S.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Int16S proto.InternalMessageInfo
+
+func (m *Int16S) GetValues() []int32 {
+ if m != nil {
+ return m.Values
+ }
+ return nil
+}
+
+type Int32S struct {
+ Values []int32 `protobuf:"fixed32,1,rep,packed,name=values,proto3" json:"values,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Int32S) Reset() { *m = Int32S{} }
+func (m *Int32S) String() string { return proto.CompactTextString(m) }
+func (*Int32S) ProtoMessage() {}
+func (*Int32S) Descriptor() ([]byte, []int) {
+ return fileDescriptor_3dee2c1912d4d62d, []int{12}
+}
+
+func (m *Int32S) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Int32S.Unmarshal(m, b)
+}
+func (m *Int32S) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Int32S.Marshal(b, m, deterministic)
+}
+func (m *Int32S) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Int32S.Merge(m, src)
+}
+func (m *Int32S) XXX_Size() int {
+ return xxx_messageInfo_Int32S.Size(m)
+}
+func (m *Int32S) XXX_DiscardUnknown() {
+ xxx_messageInfo_Int32S.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Int32S proto.InternalMessageInfo
+
+func (m *Int32S) GetValues() []int32 {
+ if m != nil {
+ return m.Values
+ }
+ return nil
+}
+
+type Int64S struct {
+ Values []int64 `protobuf:"fixed64,1,rep,packed,name=values,proto3" json:"values,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Int64S) Reset() { *m = Int64S{} }
+func (m *Int64S) String() string { return proto.CompactTextString(m) }
+func (*Int64S) ProtoMessage() {}
+func (*Int64S) Descriptor() ([]byte, []int) {
+ return fileDescriptor_3dee2c1912d4d62d, []int{13}
+}
+
+func (m *Int64S) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Int64S.Unmarshal(m, b)
+}
+func (m *Int64S) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Int64S.Marshal(b, m, deterministic)
+}
+func (m *Int64S) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Int64S.Merge(m, src)
+}
+func (m *Int64S) XXX_Size() int {
+ return xxx_messageInfo_Int64S.Size(m)
+}
+func (m *Int64S) XXX_DiscardUnknown() {
+ xxx_messageInfo_Int64S.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Int64S proto.InternalMessageInfo
+
+func (m *Int64S) GetValues() []int64 {
+ if m != nil {
+ return m.Values
+ }
+ return nil
+}
+
+type Bools struct {
+ Values []bool `protobuf:"varint,1,rep,packed,name=values,proto3" json:"values,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Bools) Reset() { *m = Bools{} }
+func (m *Bools) String() string { return proto.CompactTextString(m) }
+func (*Bools) ProtoMessage() {}
+func (*Bools) Descriptor() ([]byte, []int) {
+ return fileDescriptor_3dee2c1912d4d62d, []int{14}
+}
+
+func (m *Bools) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Bools.Unmarshal(m, b)
+}
+func (m *Bools) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Bools.Marshal(b, m, deterministic)
+}
+func (m *Bools) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Bools.Merge(m, src)
+}
+func (m *Bools) XXX_Size() int {
+ return xxx_messageInfo_Bools.Size(m)
+}
+func (m *Bools) XXX_DiscardUnknown() {
+ xxx_messageInfo_Bools.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Bools proto.InternalMessageInfo
+
+func (m *Bools) GetValues() []bool {
+ if m != nil {
+ return m.Values
+ }
+ return nil
+}
+
+type Float64S struct {
+ Values []float64 `protobuf:"fixed64,1,rep,packed,name=values,proto3" json:"values,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Float64S) Reset() { *m = Float64S{} }
+func (m *Float64S) String() string { return proto.CompactTextString(m) }
+func (*Float64S) ProtoMessage() {}
+func (*Float64S) Descriptor() ([]byte, []int) {
+ return fileDescriptor_3dee2c1912d4d62d, []int{15}
+}
+
+func (m *Float64S) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Float64S.Unmarshal(m, b)
+}
+func (m *Float64S) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Float64S.Marshal(b, m, deterministic)
+}
+func (m *Float64S) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Float64S.Merge(m, src)
+}
+func (m *Float64S) XXX_Size() int {
+ return xxx_messageInfo_Float64S.Size(m)
+}
+func (m *Float64S) XXX_DiscardUnknown() {
+ xxx_messageInfo_Float64S.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Float64S proto.InternalMessageInfo
+
+func (m *Float64S) GetValues() []float64 {
+ if m != nil {
+ return m.Values
+ }
+ return nil
+}
+
+type Float32S struct {
+ Values []float32 `protobuf:"fixed32,1,rep,packed,name=values,proto3" json:"values,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Float32S) Reset() { *m = Float32S{} }
+func (m *Float32S) String() string { return proto.CompactTextString(m) }
+func (*Float32S) ProtoMessage() {}
+func (*Float32S) Descriptor() ([]byte, []int) {
+ return fileDescriptor_3dee2c1912d4d62d, []int{16}
+}
+
+func (m *Float32S) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Float32S.Unmarshal(m, b)
+}
+func (m *Float32S) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Float32S.Marshal(b, m, deterministic)
+}
+func (m *Float32S) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Float32S.Merge(m, src)
+}
+func (m *Float32S) XXX_Size() int {
+ return xxx_messageInfo_Float32S.Size(m)
+}
+func (m *Float32S) XXX_DiscardUnknown() {
+ xxx_messageInfo_Float32S.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Float32S proto.InternalMessageInfo
+
+func (m *Float32S) GetValues() []float32 {
+ if m != nil {
+ return m.Values
+ }
+ return nil
+}
+
+type Object struct {
+ // Types that are valid to be assigned to Value:
+ // *Object_BoolValue
+ // *Object_StringValue
+ // *Object_Int64Value
+ // *Object_Uint64Value
+ // *Object_DoubleValue
+ // *Object_RefValue
+ // *Object_SliceValue
+ // *Object_ArrayValue
+ // *Object_InterfaceValue
+ // *Object_StructValue
+ // *Object_MapValue
+ // *Object_ByteArrayValue
+ // *Object_Uint16ArrayValue
+ // *Object_Uint32ArrayValue
+ // *Object_Uint64ArrayValue
+ // *Object_UintptrArrayValue
+ // *Object_Int8ArrayValue
+ // *Object_Int16ArrayValue
+ // *Object_Int32ArrayValue
+ // *Object_Int64ArrayValue
+ // *Object_BoolArrayValue
+ // *Object_Float64ArrayValue
+ // *Object_Float32ArrayValue
+ Value isObject_Value `protobuf_oneof:"value"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Object) Reset() { *m = Object{} }
+func (m *Object) String() string { return proto.CompactTextString(m) }
+func (*Object) ProtoMessage() {}
+func (*Object) Descriptor() ([]byte, []int) {
+ return fileDescriptor_3dee2c1912d4d62d, []int{17}
+}
+
+func (m *Object) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Object.Unmarshal(m, b)
+}
+func (m *Object) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Object.Marshal(b, m, deterministic)
+}
+func (m *Object) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Object.Merge(m, src)
+}
+func (m *Object) XXX_Size() int {
+ return xxx_messageInfo_Object.Size(m)
+}
+func (m *Object) XXX_DiscardUnknown() {
+ xxx_messageInfo_Object.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Object proto.InternalMessageInfo
+
+type isObject_Value interface {
+ isObject_Value()
+}
+
+type Object_BoolValue struct {
+ BoolValue bool `protobuf:"varint,1,opt,name=bool_value,json=boolValue,proto3,oneof"`
+}
+
+type Object_StringValue struct {
+ StringValue []byte `protobuf:"bytes,2,opt,name=string_value,json=stringValue,proto3,oneof"`
+}
+
+type Object_Int64Value struct {
+ Int64Value int64 `protobuf:"varint,3,opt,name=int64_value,json=int64Value,proto3,oneof"`
+}
+
+type Object_Uint64Value struct {
+ Uint64Value uint64 `protobuf:"varint,4,opt,name=uint64_value,json=uint64Value,proto3,oneof"`
+}
+
+type Object_DoubleValue struct {
+ DoubleValue float64 `protobuf:"fixed64,5,opt,name=double_value,json=doubleValue,proto3,oneof"`
+}
+
+type Object_RefValue struct {
+ RefValue uint64 `protobuf:"varint,6,opt,name=ref_value,json=refValue,proto3,oneof"`
+}
+
+type Object_SliceValue struct {
+ SliceValue *Slice `protobuf:"bytes,7,opt,name=slice_value,json=sliceValue,proto3,oneof"`
+}
+
+type Object_ArrayValue struct {
+ ArrayValue *Array `protobuf:"bytes,8,opt,name=array_value,json=arrayValue,proto3,oneof"`
+}
+
+type Object_InterfaceValue struct {
+ InterfaceValue *Interface `protobuf:"bytes,9,opt,name=interface_value,json=interfaceValue,proto3,oneof"`
+}
+
+type Object_StructValue struct {
+ StructValue *Struct `protobuf:"bytes,10,opt,name=struct_value,json=structValue,proto3,oneof"`
+}
+
+type Object_MapValue struct {
+ MapValue *Map `protobuf:"bytes,11,opt,name=map_value,json=mapValue,proto3,oneof"`
+}
+
+type Object_ByteArrayValue struct {
+ ByteArrayValue []byte `protobuf:"bytes,12,opt,name=byte_array_value,json=byteArrayValue,proto3,oneof"`
+}
+
+type Object_Uint16ArrayValue struct {
+ Uint16ArrayValue *Uint16S `protobuf:"bytes,13,opt,name=uint16_array_value,json=uint16ArrayValue,proto3,oneof"`
+}
+
+type Object_Uint32ArrayValue struct {
+ Uint32ArrayValue *Uint32S `protobuf:"bytes,14,opt,name=uint32_array_value,json=uint32ArrayValue,proto3,oneof"`
+}
+
+type Object_Uint64ArrayValue struct {
+ Uint64ArrayValue *Uint64S `protobuf:"bytes,15,opt,name=uint64_array_value,json=uint64ArrayValue,proto3,oneof"`
+}
+
+type Object_UintptrArrayValue struct {
+ UintptrArrayValue *Uintptrs `protobuf:"bytes,16,opt,name=uintptr_array_value,json=uintptrArrayValue,proto3,oneof"`
+}
+
+type Object_Int8ArrayValue struct {
+ Int8ArrayValue *Int8S `protobuf:"bytes,17,opt,name=int8_array_value,json=int8ArrayValue,proto3,oneof"`
+}
+
+type Object_Int16ArrayValue struct {
+ Int16ArrayValue *Int16S `protobuf:"bytes,18,opt,name=int16_array_value,json=int16ArrayValue,proto3,oneof"`
+}
+
+type Object_Int32ArrayValue struct {
+ Int32ArrayValue *Int32S `protobuf:"bytes,19,opt,name=int32_array_value,json=int32ArrayValue,proto3,oneof"`
+}
+
+type Object_Int64ArrayValue struct {
+ Int64ArrayValue *Int64S `protobuf:"bytes,20,opt,name=int64_array_value,json=int64ArrayValue,proto3,oneof"`
+}
+
+type Object_BoolArrayValue struct {
+ BoolArrayValue *Bools `protobuf:"bytes,21,opt,name=bool_array_value,json=boolArrayValue,proto3,oneof"`
+}
+
+type Object_Float64ArrayValue struct {
+ Float64ArrayValue *Float64S `protobuf:"bytes,22,opt,name=float64_array_value,json=float64ArrayValue,proto3,oneof"`
+}
+
+type Object_Float32ArrayValue struct {
+ Float32ArrayValue *Float32S `protobuf:"bytes,23,opt,name=float32_array_value,json=float32ArrayValue,proto3,oneof"`
+}
+
+func (*Object_BoolValue) isObject_Value() {}
+
+func (*Object_StringValue) isObject_Value() {}
+
+func (*Object_Int64Value) isObject_Value() {}
+
+func (*Object_Uint64Value) isObject_Value() {}
+
+func (*Object_DoubleValue) isObject_Value() {}
+
+func (*Object_RefValue) isObject_Value() {}
+
+func (*Object_SliceValue) isObject_Value() {}
+
+func (*Object_ArrayValue) isObject_Value() {}
+
+func (*Object_InterfaceValue) isObject_Value() {}
+
+func (*Object_StructValue) isObject_Value() {}
+
+func (*Object_MapValue) isObject_Value() {}
+
+func (*Object_ByteArrayValue) isObject_Value() {}
+
+func (*Object_Uint16ArrayValue) isObject_Value() {}
+
+func (*Object_Uint32ArrayValue) isObject_Value() {}
+
+func (*Object_Uint64ArrayValue) isObject_Value() {}
+
+func (*Object_UintptrArrayValue) isObject_Value() {}
+
+func (*Object_Int8ArrayValue) isObject_Value() {}
+
+func (*Object_Int16ArrayValue) isObject_Value() {}
+
+func (*Object_Int32ArrayValue) isObject_Value() {}
+
+func (*Object_Int64ArrayValue) isObject_Value() {}
+
+func (*Object_BoolArrayValue) isObject_Value() {}
+
+func (*Object_Float64ArrayValue) isObject_Value() {}
+
+func (*Object_Float32ArrayValue) isObject_Value() {}
+
+func (m *Object) GetValue() isObject_Value {
+ if m != nil {
+ return m.Value
+ }
+ return nil
+}
+
+func (m *Object) GetBoolValue() bool {
+ if x, ok := m.GetValue().(*Object_BoolValue); ok {
+ return x.BoolValue
+ }
+ return false
+}
+
+func (m *Object) GetStringValue() []byte {
+ if x, ok := m.GetValue().(*Object_StringValue); ok {
+ return x.StringValue
+ }
+ return nil
+}
+
+func (m *Object) GetInt64Value() int64 {
+ if x, ok := m.GetValue().(*Object_Int64Value); ok {
+ return x.Int64Value
+ }
+ return 0
+}
+
+func (m *Object) GetUint64Value() uint64 {
+ if x, ok := m.GetValue().(*Object_Uint64Value); ok {
+ return x.Uint64Value
+ }
+ return 0
+}
+
+func (m *Object) GetDoubleValue() float64 {
+ if x, ok := m.GetValue().(*Object_DoubleValue); ok {
+ return x.DoubleValue
+ }
+ return 0
+}
+
+func (m *Object) GetRefValue() uint64 {
+ if x, ok := m.GetValue().(*Object_RefValue); ok {
+ return x.RefValue
+ }
+ return 0
+}
+
+func (m *Object) GetSliceValue() *Slice {
+ if x, ok := m.GetValue().(*Object_SliceValue); ok {
+ return x.SliceValue
+ }
+ return nil
+}
+
+func (m *Object) GetArrayValue() *Array {
+ if x, ok := m.GetValue().(*Object_ArrayValue); ok {
+ return x.ArrayValue
+ }
+ return nil
+}
+
+func (m *Object) GetInterfaceValue() *Interface {
+ if x, ok := m.GetValue().(*Object_InterfaceValue); ok {
+ return x.InterfaceValue
+ }
+ return nil
+}
+
+func (m *Object) GetStructValue() *Struct {
+ if x, ok := m.GetValue().(*Object_StructValue); ok {
+ return x.StructValue
+ }
+ return nil
+}
+
+func (m *Object) GetMapValue() *Map {
+ if x, ok := m.GetValue().(*Object_MapValue); ok {
+ return x.MapValue
+ }
+ return nil
+}
+
+func (m *Object) GetByteArrayValue() []byte {
+ if x, ok := m.GetValue().(*Object_ByteArrayValue); ok {
+ return x.ByteArrayValue
+ }
+ return nil
+}
+
+func (m *Object) GetUint16ArrayValue() *Uint16S {
+ if x, ok := m.GetValue().(*Object_Uint16ArrayValue); ok {
+ return x.Uint16ArrayValue
+ }
+ return nil
+}
+
+func (m *Object) GetUint32ArrayValue() *Uint32S {
+ if x, ok := m.GetValue().(*Object_Uint32ArrayValue); ok {
+ return x.Uint32ArrayValue
+ }
+ return nil
+}
+
+func (m *Object) GetUint64ArrayValue() *Uint64S {
+ if x, ok := m.GetValue().(*Object_Uint64ArrayValue); ok {
+ return x.Uint64ArrayValue
+ }
+ return nil
+}
+
+func (m *Object) GetUintptrArrayValue() *Uintptrs {
+ if x, ok := m.GetValue().(*Object_UintptrArrayValue); ok {
+ return x.UintptrArrayValue
+ }
+ return nil
+}
+
+func (m *Object) GetInt8ArrayValue() *Int8S {
+ if x, ok := m.GetValue().(*Object_Int8ArrayValue); ok {
+ return x.Int8ArrayValue
+ }
+ return nil
+}
+
+func (m *Object) GetInt16ArrayValue() *Int16S {
+ if x, ok := m.GetValue().(*Object_Int16ArrayValue); ok {
+ return x.Int16ArrayValue
+ }
+ return nil
+}
+
+func (m *Object) GetInt32ArrayValue() *Int32S {
+ if x, ok := m.GetValue().(*Object_Int32ArrayValue); ok {
+ return x.Int32ArrayValue
+ }
+ return nil
+}
+
+func (m *Object) GetInt64ArrayValue() *Int64S {
+ if x, ok := m.GetValue().(*Object_Int64ArrayValue); ok {
+ return x.Int64ArrayValue
+ }
+ return nil
+}
+
+func (m *Object) GetBoolArrayValue() *Bools {
+ if x, ok := m.GetValue().(*Object_BoolArrayValue); ok {
+ return x.BoolArrayValue
+ }
+ return nil
+}
+
+func (m *Object) GetFloat64ArrayValue() *Float64S {
+ if x, ok := m.GetValue().(*Object_Float64ArrayValue); ok {
+ return x.Float64ArrayValue
+ }
+ return nil
+}
+
+func (m *Object) GetFloat32ArrayValue() *Float32S {
+ if x, ok := m.GetValue().(*Object_Float32ArrayValue); ok {
+ return x.Float32ArrayValue
+ }
+ return nil
+}
+
+// XXX_OneofWrappers is for the internal use of the proto package.
+func (*Object) XXX_OneofWrappers() []interface{} {
+ return []interface{}{
+ (*Object_BoolValue)(nil),
+ (*Object_StringValue)(nil),
+ (*Object_Int64Value)(nil),
+ (*Object_Uint64Value)(nil),
+ (*Object_DoubleValue)(nil),
+ (*Object_RefValue)(nil),
+ (*Object_SliceValue)(nil),
+ (*Object_ArrayValue)(nil),
+ (*Object_InterfaceValue)(nil),
+ (*Object_StructValue)(nil),
+ (*Object_MapValue)(nil),
+ (*Object_ByteArrayValue)(nil),
+ (*Object_Uint16ArrayValue)(nil),
+ (*Object_Uint32ArrayValue)(nil),
+ (*Object_Uint64ArrayValue)(nil),
+ (*Object_UintptrArrayValue)(nil),
+ (*Object_Int8ArrayValue)(nil),
+ (*Object_Int16ArrayValue)(nil),
+ (*Object_Int32ArrayValue)(nil),
+ (*Object_Int64ArrayValue)(nil),
+ (*Object_BoolArrayValue)(nil),
+ (*Object_Float64ArrayValue)(nil),
+ (*Object_Float32ArrayValue)(nil),
+ }
+}
+
+func init() {
+ proto.RegisterType((*Slice)(nil), "gvisor.state.statefile.Slice")
+ proto.RegisterType((*Array)(nil), "gvisor.state.statefile.Array")
+ proto.RegisterType((*Map)(nil), "gvisor.state.statefile.Map")
+ proto.RegisterType((*Interface)(nil), "gvisor.state.statefile.Interface")
+ proto.RegisterType((*Struct)(nil), "gvisor.state.statefile.Struct")
+ proto.RegisterType((*Field)(nil), "gvisor.state.statefile.Field")
+ proto.RegisterType((*Uint16S)(nil), "gvisor.state.statefile.Uint16s")
+ proto.RegisterType((*Uint32S)(nil), "gvisor.state.statefile.Uint32s")
+ proto.RegisterType((*Uint64S)(nil), "gvisor.state.statefile.Uint64s")
+ proto.RegisterType((*Uintptrs)(nil), "gvisor.state.statefile.Uintptrs")
+ proto.RegisterType((*Int8S)(nil), "gvisor.state.statefile.Int8s")
+ proto.RegisterType((*Int16S)(nil), "gvisor.state.statefile.Int16s")
+ proto.RegisterType((*Int32S)(nil), "gvisor.state.statefile.Int32s")
+ proto.RegisterType((*Int64S)(nil), "gvisor.state.statefile.Int64s")
+ proto.RegisterType((*Bools)(nil), "gvisor.state.statefile.Bools")
+ proto.RegisterType((*Float64S)(nil), "gvisor.state.statefile.Float64s")
+ proto.RegisterType((*Float32S)(nil), "gvisor.state.statefile.Float32s")
+ proto.RegisterType((*Object)(nil), "gvisor.state.statefile.Object")
+}
+
+func init() { proto.RegisterFile("pkg/state/object.proto", fileDescriptor_3dee2c1912d4d62d) }
+
+var fileDescriptor_3dee2c1912d4d62d = []byte{
+ // 781 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x96, 0x6f, 0x4f, 0xda, 0x5e,
+ 0x14, 0xc7, 0xa9, 0x40, 0x29, 0x07, 0x14, 0xb8, 0xfe, 0x7e, 0x8c, 0xcc, 0x38, 0xb1, 0x7b, 0x42,
+ 0xf6, 0x00, 0x33, 0x60, 0xc4, 0xf8, 0x64, 0x53, 0x13, 0x03, 0xc9, 0x8c, 0x59, 0x8d, 0xcb, 0x9e,
+ 0x99, 0x52, 0x2f, 0xac, 0xb3, 0xb6, 0x5d, 0x7b, 0x6b, 0xc2, 0xcb, 0xdc, 0x3b, 0x5a, 0xee, 0x1f,
+ 0xae, 0xfd, 0x03, 0xc5, 0xec, 0x89, 0xa1, 0xb7, 0xdf, 0xf3, 0xe1, 0xdc, 0xf3, 0x3d, 0xe7, 0x08,
+ 0xb4, 0xfd, 0xc7, 0xc5, 0x49, 0x48, 0x4c, 0x82, 0x4f, 0xbc, 0xd9, 0x2f, 0x6c, 0x91, 0xbe, 0x1f,
+ 0x78, 0xc4, 0x43, 0xed, 0xc5, 0xb3, 0x1d, 0x7a, 0x41, 0x9f, 0xbd, 0xe2, 0x7f, 0xe7, 0xb6, 0x83,
+ 0xf5, 0x1f, 0x50, 0xbe, 0x75, 0x6c, 0x0b, 0xa3, 0x36, 0xa8, 0x0e, 0x76, 0x17, 0xe4, 0x67, 0x47,
+ 0xe9, 0x2a, 0xbd, 0x5d, 0x43, 0x3c, 0xa1, 0xb7, 0xa0, 0x59, 0xa6, 0x6f, 0x5a, 0x36, 0x59, 0x76,
+ 0x76, 0xd8, 0x1b, 0xf9, 0x8c, 0x0e, 0xa0, 0x1a, 0xe0, 0xf9, 0xfd, 0xb3, 0xe9, 0x44, 0xb8, 0x53,
+ 0xec, 0x2a, 0xbd, 0x92, 0xa1, 0x05, 0x78, 0xfe, 0x9d, 0x3e, 0xeb, 0x97, 0x50, 0x3e, 0x0f, 0x02,
+ 0x73, 0x89, 0xce, 0x40, 0xb3, 0x3c, 0x97, 0x60, 0x97, 0x84, 0x1d, 0xa5, 0x5b, 0xec, 0xd5, 0x06,
+ 0xef, 0xfa, 0xeb, 0xb3, 0xe9, 0xdf, 0xb0, 0x94, 0x0d, 0xa9, 0xd7, 0x7f, 0x43, 0xf1, 0xda, 0xf4,
+ 0xd1, 0x00, 0x4a, 0x8f, 0x78, 0xf9, 0xda, 0x70, 0xa6, 0x45, 0x63, 0x50, 0x59, 0x62, 0x61, 0x67,
+ 0xe7, 0x55, 0x51, 0x42, 0xad, 0xdf, 0x41, 0x75, 0xea, 0x12, 0x1c, 0xcc, 0x4d, 0x0b, 0x23, 0x04,
+ 0x25, 0xb2, 0xf4, 0x31, 0xab, 0x49, 0xd5, 0x60, 0x9f, 0xd1, 0x08, 0xca, 0xfc, 0xc6, 0xb4, 0x1c,
+ 0xdb, 0xb9, 0x5c, 0xac, 0x7f, 0x06, 0xf5, 0x96, 0x04, 0x91, 0x45, 0xd0, 0x27, 0x50, 0xe7, 0x36,
+ 0x76, 0x1e, 0x56, 0xd7, 0x39, 0xdc, 0x04, 0xb8, 0xa2, 0x2a, 0x43, 0x88, 0xf5, 0x6f, 0x50, 0x66,
+ 0x07, 0x34, 0x27, 0xd7, 0x7c, 0x92, 0x39, 0xd1, 0xcf, 0xff, 0x98, 0xd3, 0x31, 0x54, 0xee, 0x6c,
+ 0x97, 0x7c, 0x1c, 0x87, 0xd4, 0x7e, 0x51, 0x2d, 0x9a, 0xd4, 0xae, 0xac, 0x86, 0x90, 0x0c, 0x07,
+ 0x69, 0x49, 0x25, 0x2d, 0x19, 0x8f, 0xd2, 0x12, 0x55, 0x4a, 0x74, 0xd0, 0xa8, 0xc4, 0x27, 0xc1,
+ 0x66, 0xcd, 0x11, 0x94, 0xa7, 0x2e, 0x39, 0x4d, 0x0a, 0x94, 0x5e, 0x5d, 0x0a, 0xba, 0xa0, 0x4e,
+ 0xd7, 0x25, 0x5b, 0x4e, 0x29, 0xb2, 0xb9, 0x36, 0x52, 0x8a, 0x6c, 0xaa, 0xcd, 0x78, 0x1a, 0x17,
+ 0x9e, 0xe7, 0xa4, 0x05, 0x5a, 0xfc, 0x2e, 0x57, 0x8e, 0x67, 0xae, 0x81, 0x28, 0x19, 0x4d, 0x36,
+ 0x95, 0x1d, 0xa9, 0xf9, 0x53, 0x03, 0x95, 0xdb, 0x81, 0x8e, 0x00, 0x66, 0x9e, 0xe7, 0x88, 0x41,
+ 0xa2, 0xb7, 0xd6, 0x26, 0x05, 0xa3, 0x4a, 0xcf, 0xd8, 0x2c, 0xa1, 0xf7, 0x50, 0x0f, 0x49, 0x60,
+ 0xbb, 0x8b, 0xfb, 0x17, 0x97, 0xeb, 0x93, 0x82, 0x51, 0xe3, 0xa7, 0x5c, 0x74, 0x0c, 0x35, 0x66,
+ 0x43, 0x6c, 0x1e, 0x8b, 0x93, 0x82, 0x01, 0xec, 0x50, 0x72, 0xa2, 0xb8, 0xa6, 0x44, 0x67, 0x96,
+ 0x72, 0xa2, 0xa4, 0xe8, 0xc1, 0x8b, 0x66, 0x0e, 0x16, 0xa2, 0x72, 0x57, 0xe9, 0x29, 0x54, 0xc4,
+ 0x4f, 0xb9, 0xe8, 0x30, 0x3e, 0xfa, 0xaa, 0xc0, 0xc8, 0xe1, 0x47, 0x5f, 0xa0, 0x16, 0xd2, 0xb5,
+ 0x22, 0x04, 0x15, 0xd6, 0x95, 0x1b, 0x1b, 0x9d, 0x6d, 0x20, 0x9a, 0x2a, 0x8b, 0x91, 0x04, 0x93,
+ 0xae, 0x0f, 0x41, 0xd0, 0xf2, 0x09, 0x6c, 0xd3, 0x50, 0x02, 0x8b, 0xe1, 0x84, 0xaf, 0xd0, 0xb0,
+ 0x57, 0x83, 0x2c, 0x28, 0x55, 0x46, 0x39, 0xde, 0x44, 0x91, 0x73, 0x3f, 0x29, 0x18, 0x7b, 0x32,
+ 0x96, 0xd3, 0x2e, 0x99, 0x05, 0x91, 0x45, 0x04, 0x0a, 0xf2, 0x07, 0x8d, 0xcf, 0xba, 0xb0, 0x28,
+ 0xb2, 0x08, 0x87, 0x9c, 0x41, 0xf5, 0xc9, 0xf4, 0x05, 0xa1, 0xc6, 0x08, 0x07, 0x9b, 0x08, 0xd7,
+ 0xa6, 0x4f, 0x4b, 0xfa, 0x64, 0xfa, 0x3c, 0xf6, 0x03, 0x34, 0x67, 0x4b, 0x82, 0xef, 0xe3, 0x55,
+ 0xa9, 0x8b, 0x3e, 0xd8, 0xa3, 0x6f, 0xce, 0x5f, 0xae, 0x7e, 0x03, 0x28, 0x62, 0x83, 0x9d, 0x50,
+ 0xef, 0xb2, 0x2f, 0x3c, 0xda, 0xf4, 0x85, 0x62, 0x15, 0x4c, 0x0a, 0x46, 0x93, 0x07, 0x67, 0x81,
+ 0xc3, 0x41, 0x02, 0xb8, 0xb7, 0x1d, 0x38, 0x1c, 0x48, 0xe0, 0x70, 0x90, 0x05, 0x8e, 0x47, 0x09,
+ 0x60, 0x63, 0x3b, 0x70, 0x3c, 0x92, 0xc0, 0xf1, 0x28, 0x06, 0x34, 0x60, 0x3f, 0xe2, 0x2b, 0x26,
+ 0x41, 0x6c, 0x32, 0x62, 0x37, 0x8f, 0x48, 0xb7, 0xd2, 0xa4, 0x60, 0xb4, 0x44, 0x78, 0x8c, 0x39,
+ 0x85, 0xa6, 0xed, 0x92, 0xd3, 0x04, 0xb0, 0x95, 0xdf, 0x88, 0x6c, 0x85, 0x89, 0xf6, 0x39, 0x3d,
+ 0x8f, 0x37, 0x63, 0x2b, 0x6b, 0x08, 0xca, 0xef, 0xa1, 0xe9, 0xca, 0x8f, 0x46, 0xda, 0x0e, 0x4e,
+ 0x4b, 0xb9, 0xb1, 0xbf, 0x95, 0xc6, 0xcd, 0x68, 0xa4, 0xbd, 0xe0, 0xb4, 0x94, 0x15, 0xff, 0x6d,
+ 0xa5, 0x71, 0x27, 0x1a, 0x69, 0x23, 0xa6, 0xd0, 0x64, 0xcb, 0x2c, 0x0e, 0xfb, 0x3f, 0xbf, 0x68,
+ 0x6c, 0xe1, 0xb2, 0x36, 0xf6, 0x3c, 0x27, 0xe9, 0xe9, 0x9c, 0xaf, 0xda, 0x04, 0xad, 0x9d, 0xef,
+ 0xe9, 0x6a, 0x3b, 0x53, 0x4f, 0x45, 0xf8, 0x1a, 0x66, 0xaa, 0x78, 0x6f, 0x5e, 0xc1, 0xe4, 0xe5,
+ 0x6b, 0x89, 0xf0, 0x17, 0xe6, 0x45, 0x45, 0xfc, 0xf7, 0x9d, 0xa9, 0xec, 0xc7, 0xd6, 0xf0, 0x6f,
+ 0x00, 0x00, 0x00, 0xff, 0xff, 0x84, 0x69, 0xc9, 0x45, 0x86, 0x09, 0x00, 0x00,
+}
diff --git a/pkg/state/state_test.go b/pkg/state/state_test.go
deleted file mode 100644
index 7c24bbcda..000000000
--- a/pkg/state/state_test.go
+++ /dev/null
@@ -1,720 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package state
-
-import (
- "bytes"
- "io/ioutil"
- "math"
- "reflect"
- "testing"
-)
-
-// TestCase is used to define a single success/failure testcase of
-// serialization of a set of objects.
-type TestCase struct {
- // Name is the name of the test case.
- Name string
-
- // Objects is the list of values to serialize.
- Objects []interface{}
-
- // Fail is whether the test case is supposed to fail or not.
- Fail bool
-}
-
-// runTest runs all testcases.
-func runTest(t *testing.T, tests []TestCase) {
- for _, test := range tests {
- t.Logf("TEST %s:", test.Name)
- for i, root := range test.Objects {
- t.Logf(" case#%d: %#v", i, root)
-
- // Save the passed object.
- saveBuffer := &bytes.Buffer{}
- saveObjectPtr := reflect.New(reflect.TypeOf(root))
- saveObjectPtr.Elem().Set(reflect.ValueOf(root))
- if err := Save(saveBuffer, saveObjectPtr.Interface(), nil); err != nil && !test.Fail {
- t.Errorf(" FAIL: Save failed unexpectedly: %v", err)
- continue
- } else if err != nil {
- t.Logf(" PASS: Save failed as expected: %v", err)
- continue
- }
-
- // Load a new copy of the object.
- loadObjectPtr := reflect.New(reflect.TypeOf(root))
- if err := Load(bytes.NewReader(saveBuffer.Bytes()), loadObjectPtr.Interface(), nil); err != nil && !test.Fail {
- t.Errorf(" FAIL: Load failed unexpectedly: %v", err)
- continue
- } else if err != nil {
- t.Logf(" PASS: Load failed as expected: %v", err)
- continue
- }
-
- // Compare the values.
- loadedValue := loadObjectPtr.Elem().Interface()
- if eq := reflect.DeepEqual(root, loadedValue); !eq && !test.Fail {
- t.Errorf(" FAIL: Objects differs; got %#v", loadedValue)
- continue
- } else if !eq {
- t.Logf(" PASS: Object different as expected.")
- continue
- }
-
- // Everything went okay. Is that good?
- if test.Fail {
- t.Errorf(" FAIL: Unexpected success.")
- } else {
- t.Logf(" PASS: Success.")
- }
- }
- }
-}
-
-// dumbStruct is a struct which does not implement the loader/saver interface.
-// We expect that serialization of this struct will fail.
-type dumbStruct struct {
- A int
- B int
-}
-
-// smartStruct is a struct which does implement the loader/saver interface.
-// We expect that serialization of this struct will succeed.
-type smartStruct struct {
- A int
- B int
-}
-
-func (s *smartStruct) save(m Map) {
- m.Save("A", &s.A)
- m.Save("B", &s.B)
-}
-
-func (s *smartStruct) load(m Map) {
- m.Load("A", &s.A)
- m.Load("B", &s.B)
-}
-
-// valueLoadStruct uses a value load.
-type valueLoadStruct struct {
- v int
-}
-
-func (v *valueLoadStruct) save(m Map) {
- m.SaveValue("v", v.v)
-}
-
-func (v *valueLoadStruct) load(m Map) {
- m.LoadValue("v", new(int), func(value interface{}) {
- v.v = value.(int)
- })
-}
-
-// afterLoadStruct has an AfterLoad function.
-type afterLoadStruct struct {
- v int
-}
-
-func (a *afterLoadStruct) save(m Map) {
-}
-
-func (a *afterLoadStruct) load(m Map) {
- m.AfterLoad(func() {
- a.v++
- })
-}
-
-// genericContainer is a generic dispatcher.
-type genericContainer struct {
- v interface{}
-}
-
-func (g *genericContainer) save(m Map) {
- m.Save("v", &g.v)
-}
-
-func (g *genericContainer) load(m Map) {
- m.Load("v", &g.v)
-}
-
-// sliceContainer is a generic slice.
-type sliceContainer struct {
- v []interface{}
-}
-
-func (s *sliceContainer) save(m Map) {
- m.Save("v", &s.v)
-}
-
-func (s *sliceContainer) load(m Map) {
- m.Load("v", &s.v)
-}
-
-// mapContainer is a generic map.
-type mapContainer struct {
- v map[int]interface{}
-}
-
-func (mc *mapContainer) save(m Map) {
- m.Save("v", &mc.v)
-}
-
-func (mc *mapContainer) load(m Map) {
- // Some of the test cases below assume legacy behavior wherein maps
- // will automatically inherit dependencies.
- m.LoadWait("v", &mc.v)
-}
-
-// dumbMap is a map which does not implement the loader/saver interface.
-// Serialization of this map will default to the standard encode/decode logic.
-type dumbMap map[string]int
-
-// pointerStruct contains various pointers, shared and non-shared, and pointers
-// to pointers. We expect that serialization will respect the structure.
-type pointerStruct struct {
- A *int
- B *int
- C *int
- D *int
-
- AA **int
- BB **int
-}
-
-func (p *pointerStruct) save(m Map) {
- m.Save("A", &p.A)
- m.Save("B", &p.B)
- m.Save("C", &p.C)
- m.Save("D", &p.D)
- m.Save("AA", &p.AA)
- m.Save("BB", &p.BB)
-}
-
-func (p *pointerStruct) load(m Map) {
- m.Load("A", &p.A)
- m.Load("B", &p.B)
- m.Load("C", &p.C)
- m.Load("D", &p.D)
- m.Load("AA", &p.AA)
- m.Load("BB", &p.BB)
-}
-
-// testInterface is a trivial interface example.
-type testInterface interface {
- Foo()
-}
-
-// testImpl is a trivial implementation of testInterface.
-type testImpl struct {
-}
-
-// Foo satisfies testInterface.
-func (t *testImpl) Foo() {
-}
-
-// testImpl is trivially serializable.
-func (t *testImpl) save(m Map) {
-}
-
-// testImpl is trivially serializable.
-func (t *testImpl) load(m Map) {
-}
-
-// testI demonstrates interface dispatching.
-type testI struct {
- I testInterface
-}
-
-func (t *testI) save(m Map) {
- m.Save("I", &t.I)
-}
-
-func (t *testI) load(m Map) {
- m.Load("I", &t.I)
-}
-
-// cycleStruct is used to implement basic cycles.
-type cycleStruct struct {
- c *cycleStruct
-}
-
-func (c *cycleStruct) save(m Map) {
- m.Save("c", &c.c)
-}
-
-func (c *cycleStruct) load(m Map) {
- m.Load("c", &c.c)
-}
-
-// badCycleStruct actually has deadlocking dependencies.
-//
-// This should pass if b.b = {nil|b} and fail otherwise.
-type badCycleStruct struct {
- b *badCycleStruct
-}
-
-func (b *badCycleStruct) save(m Map) {
- m.Save("b", &b.b)
-}
-
-func (b *badCycleStruct) load(m Map) {
- m.LoadWait("b", &b.b)
- m.AfterLoad(func() {
- // This is not executable, since AfterLoad requires that the
- // object and all dependencies are complete. This should cause
- // a deadlock error during load.
- })
-}
-
-// emptyStructPointer points to an empty struct.
-type emptyStructPointer struct {
- nothing *struct{}
-}
-
-func (e *emptyStructPointer) save(m Map) {
- m.Save("nothing", &e.nothing)
-}
-
-func (e *emptyStructPointer) load(m Map) {
- m.Load("nothing", &e.nothing)
-}
-
-// truncateInteger truncates an integer.
-type truncateInteger struct {
- v int64
- v2 int32
-}
-
-func (t *truncateInteger) save(m Map) {
- t.v2 = int32(t.v)
- m.Save("v", &t.v)
-}
-
-func (t *truncateInteger) load(m Map) {
- m.Load("v", &t.v2)
- t.v = int64(t.v2)
-}
-
-// truncateUnsignedInteger truncates an unsigned integer.
-type truncateUnsignedInteger struct {
- v uint64
- v2 uint32
-}
-
-func (t *truncateUnsignedInteger) save(m Map) {
- t.v2 = uint32(t.v)
- m.Save("v", &t.v)
-}
-
-func (t *truncateUnsignedInteger) load(m Map) {
- m.Load("v", &t.v2)
- t.v = uint64(t.v2)
-}
-
-// truncateFloat truncates a floating point number.
-type truncateFloat struct {
- v float64
- v2 float32
-}
-
-func (t *truncateFloat) save(m Map) {
- t.v2 = float32(t.v)
- m.Save("v", &t.v)
-}
-
-func (t *truncateFloat) load(m Map) {
- m.Load("v", &t.v2)
- t.v = float64(t.v2)
-}
-
-func TestTypes(t *testing.T) {
- // x and y are basic integers, while xp points to x.
- x := 1
- y := 2
- xp := &x
-
- // cs is a single object cycle.
- cs := cycleStruct{nil}
- cs.c = &cs
-
- // cs1 and cs2 are in a two object cycle.
- cs1 := cycleStruct{nil}
- cs2 := cycleStruct{nil}
- cs1.c = &cs2
- cs2.c = &cs1
-
- // bs is a single object cycle.
- bs := badCycleStruct{nil}
- bs.b = &bs
-
- // bs2 and bs2 are in a deadlocking cycle.
- bs1 := badCycleStruct{nil}
- bs2 := badCycleStruct{nil}
- bs1.b = &bs2
- bs2.b = &bs1
-
- // regular nils.
- var (
- nilmap dumbMap
- nilslice []byte
- )
-
- // embed points to embedded fields.
- embed1 := pointerStruct{}
- embed1.AA = &embed1.A
- embed2 := pointerStruct{}
- embed2.BB = &embed2.B
-
- // es1 contains two structs pointing to the same empty struct.
- es := emptyStructPointer{new(struct{})}
- es1 := []emptyStructPointer{es, es}
-
- tests := []TestCase{
- {
- Name: "bool",
- Objects: []interface{}{
- true,
- false,
- },
- },
- {
- Name: "integers",
- Objects: []interface{}{
- int(0),
- int(1),
- int(-1),
- int8(0),
- int8(1),
- int8(-1),
- int16(0),
- int16(1),
- int16(-1),
- int32(0),
- int32(1),
- int32(-1),
- int64(0),
- int64(1),
- int64(-1),
- },
- },
- {
- Name: "unsigned integers",
- Objects: []interface{}{
- uint(0),
- uint(1),
- uint8(0),
- uint8(1),
- uint16(0),
- uint16(1),
- uint32(1),
- uint64(0),
- uint64(1),
- },
- },
- {
- Name: "strings",
- Objects: []interface{}{
- "",
- "foo",
- "bar",
- "\xa0",
- },
- },
- {
- Name: "slices",
- Objects: []interface{}{
- []int{-1, 0, 1},
- []*int{&x, &x, &x},
- []int{1, 2, 3}[0:1],
- []int{1, 2, 3}[1:2],
- make([]byte, 32),
- make([]byte, 32)[:16],
- make([]byte, 32)[:16:20],
- nilslice,
- },
- },
- {
- Name: "arrays",
- Objects: []interface{}{
- &[1048576]bool{false, true, false, true},
- &[1048576]uint8{0, 1, 2, 3},
- &[1048576]byte{0, 1, 2, 3},
- &[1048576]uint16{0, 1, 2, 3},
- &[1048576]uint{0, 1, 2, 3},
- &[1048576]uint32{0, 1, 2, 3},
- &[1048576]uint64{0, 1, 2, 3},
- &[1048576]uintptr{0, 1, 2, 3},
- &[1048576]int8{0, -1, -2, -3},
- &[1048576]int16{0, -1, -2, -3},
- &[1048576]int32{0, -1, -2, -3},
- &[1048576]int64{0, -1, -2, -3},
- &[1048576]float32{0, 1.1, 2.2, 3.3},
- &[1048576]float64{0, 1.1, 2.2, 3.3},
- },
- },
- {
- Name: "pointers",
- Objects: []interface{}{
- &pointerStruct{A: &x, B: &x, C: &y, D: &y, AA: &xp, BB: &xp},
- &pointerStruct{},
- },
- },
- {
- Name: "empty struct",
- Objects: []interface{}{
- struct{}{},
- },
- },
- {
- Name: "unenlightened structs",
- Objects: []interface{}{
- &dumbStruct{A: 1, B: 2},
- },
- Fail: true,
- },
- {
- Name: "enlightened structs",
- Objects: []interface{}{
- &smartStruct{A: 1, B: 2},
- },
- },
- {
- Name: "load-hooks",
- Objects: []interface{}{
- &afterLoadStruct{v: 1},
- &valueLoadStruct{v: 1},
- &genericContainer{v: &afterLoadStruct{v: 1}},
- &genericContainer{v: &valueLoadStruct{v: 1}},
- &sliceContainer{v: []interface{}{&afterLoadStruct{v: 1}}},
- &sliceContainer{v: []interface{}{&valueLoadStruct{v: 1}}},
- &mapContainer{v: map[int]interface{}{0: &afterLoadStruct{v: 1}}},
- &mapContainer{v: map[int]interface{}{0: &valueLoadStruct{v: 1}}},
- },
- },
- {
- Name: "maps",
- Objects: []interface{}{
- dumbMap{"a": -1, "b": 0, "c": 1},
- map[smartStruct]int{{}: 0, {A: 1}: 1},
- nilmap,
- &mapContainer{v: map[int]interface{}{0: &smartStruct{A: 1}}},
- },
- },
- {
- Name: "interfaces",
- Objects: []interface{}{
- &testI{&testImpl{}},
- &testI{nil},
- &testI{(*testImpl)(nil)},
- },
- },
- {
- Name: "unregistered-interfaces",
- Objects: []interface{}{
- &genericContainer{v: afterLoadStruct{v: 1}},
- &genericContainer{v: valueLoadStruct{v: 1}},
- &sliceContainer{v: []interface{}{afterLoadStruct{v: 1}}},
- &sliceContainer{v: []interface{}{valueLoadStruct{v: 1}}},
- &mapContainer{v: map[int]interface{}{0: afterLoadStruct{v: 1}}},
- &mapContainer{v: map[int]interface{}{0: valueLoadStruct{v: 1}}},
- },
- Fail: true,
- },
- {
- Name: "cycles",
- Objects: []interface{}{
- &cs,
- &cs1,
- &cycleStruct{&cs1},
- &cycleStruct{&cs},
- &badCycleStruct{nil},
- &bs,
- },
- },
- {
- Name: "deadlock",
- Objects: []interface{}{
- &bs1,
- },
- Fail: true,
- },
- {
- Name: "embed",
- Objects: []interface{}{
- &embed1,
- &embed2,
- },
- Fail: true,
- },
- {
- Name: "empty structs",
- Objects: []interface{}{
- new(struct{}),
- es,
- es1,
- },
- },
- {
- Name: "truncated okay",
- Objects: []interface{}{
- &truncateInteger{v: 1},
- &truncateUnsignedInteger{v: 1},
- &truncateFloat{v: 1.0},
- },
- },
- {
- Name: "truncated bad",
- Objects: []interface{}{
- &truncateInteger{v: math.MaxInt32 + 1},
- &truncateUnsignedInteger{v: math.MaxUint32 + 1},
- &truncateFloat{v: math.MaxFloat32 * 2},
- },
- Fail: true,
- },
- }
-
- runTest(t, tests)
-}
-
-// benchStruct is used for benchmarking.
-type benchStruct struct {
- b *benchStruct
-
- // Dummy data is included to ensure that these objects are large.
- // This is to detect possible regression when registering objects.
- _ [4096]byte
-}
-
-func (b *benchStruct) save(m Map) {
- m.Save("b", &b.b)
-}
-
-func (b *benchStruct) load(m Map) {
- m.LoadWait("b", &b.b)
- m.AfterLoad(b.afterLoad)
-}
-
-func (b *benchStruct) afterLoad() {
- // Do nothing, just force scheduling.
-}
-
-// buildObject builds a benchmark object.
-func buildObject(n int) (b *benchStruct) {
- for i := 0; i < n; i++ {
- b = &benchStruct{b: b}
- }
- return
-}
-
-func BenchmarkEncoding(b *testing.B) {
- b.StopTimer()
- bs := buildObject(b.N)
- var stats Stats
- b.StartTimer()
- if err := Save(ioutil.Discard, bs, &stats); err != nil {
- b.Errorf("save failed: %v", err)
- }
- b.StopTimer()
- if b.N > 1000 {
- b.Logf("breakdown (n=%d): %s", b.N, &stats)
- }
-}
-
-func BenchmarkDecoding(b *testing.B) {
- b.StopTimer()
- bs := buildObject(b.N)
- var newBS benchStruct
- buf := &bytes.Buffer{}
- if err := Save(buf, bs, nil); err != nil {
- b.Errorf("save failed: %v", err)
- }
- var stats Stats
- b.StartTimer()
- if err := Load(buf, &newBS, &stats); err != nil {
- b.Errorf("load failed: %v", err)
- }
- b.StopTimer()
- if b.N > 1000 {
- b.Logf("breakdown (n=%d): %s", b.N, &stats)
- }
-}
-
-func init() {
- Register("stateTest.smartStruct", (*smartStruct)(nil), Fns{
- Save: (*smartStruct).save,
- Load: (*smartStruct).load,
- })
- Register("stateTest.afterLoadStruct", (*afterLoadStruct)(nil), Fns{
- Save: (*afterLoadStruct).save,
- Load: (*afterLoadStruct).load,
- })
- Register("stateTest.valueLoadStruct", (*valueLoadStruct)(nil), Fns{
- Save: (*valueLoadStruct).save,
- Load: (*valueLoadStruct).load,
- })
- Register("stateTest.genericContainer", (*genericContainer)(nil), Fns{
- Save: (*genericContainer).save,
- Load: (*genericContainer).load,
- })
- Register("stateTest.sliceContainer", (*sliceContainer)(nil), Fns{
- Save: (*sliceContainer).save,
- Load: (*sliceContainer).load,
- })
- Register("stateTest.mapContainer", (*mapContainer)(nil), Fns{
- Save: (*mapContainer).save,
- Load: (*mapContainer).load,
- })
- Register("stateTest.pointerStruct", (*pointerStruct)(nil), Fns{
- Save: (*pointerStruct).save,
- Load: (*pointerStruct).load,
- })
- Register("stateTest.testImpl", (*testImpl)(nil), Fns{
- Save: (*testImpl).save,
- Load: (*testImpl).load,
- })
- Register("stateTest.testI", (*testI)(nil), Fns{
- Save: (*testI).save,
- Load: (*testI).load,
- })
- Register("stateTest.cycleStruct", (*cycleStruct)(nil), Fns{
- Save: (*cycleStruct).save,
- Load: (*cycleStruct).load,
- })
- Register("stateTest.badCycleStruct", (*badCycleStruct)(nil), Fns{
- Save: (*badCycleStruct).save,
- Load: (*badCycleStruct).load,
- })
- Register("stateTest.emptyStructPointer", (*emptyStructPointer)(nil), Fns{
- Save: (*emptyStructPointer).save,
- Load: (*emptyStructPointer).load,
- })
- Register("stateTest.truncateInteger", (*truncateInteger)(nil), Fns{
- Save: (*truncateInteger).save,
- Load: (*truncateInteger).load,
- })
- Register("stateTest.truncateUnsignedInteger", (*truncateUnsignedInteger)(nil), Fns{
- Save: (*truncateUnsignedInteger).save,
- Load: (*truncateUnsignedInteger).load,
- })
- Register("stateTest.truncateFloat", (*truncateFloat)(nil), Fns{
- Save: (*truncateFloat).save,
- Load: (*truncateFloat).load,
- })
- Register("stateTest.benchStruct", (*benchStruct)(nil), Fns{
- Save: (*benchStruct).save,
- Load: (*benchStruct).load,
- })
-}
diff --git a/pkg/state/statefile/BUILD b/pkg/state/statefile/BUILD
deleted file mode 100644
index 8a865d229..000000000
--- a/pkg/state/statefile/BUILD
+++ /dev/null
@@ -1,23 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "statefile",
- srcs = ["statefile.go"],
- importpath = "gvisor.dev/gvisor/pkg/state/statefile",
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/binary",
- "//pkg/compressio",
- ],
-)
-
-go_test(
- name = "statefile_test",
- size = "small",
- srcs = ["statefile_test.go"],
- embed = [":statefile"],
- deps = ["//pkg/compressio"],
-)
diff --git a/pkg/state/statefile/statefile_state_autogen.go b/pkg/state/statefile/statefile_state_autogen.go
new file mode 100755
index 000000000..438c485ca
--- /dev/null
+++ b/pkg/state/statefile/statefile_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package statefile
+
diff --git a/pkg/state/statefile/statefile_test.go b/pkg/state/statefile/statefile_test.go
deleted file mode 100644
index 0b470fdec..000000000
--- a/pkg/state/statefile/statefile_test.go
+++ /dev/null
@@ -1,290 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package statefile
-
-import (
- "bytes"
- crand "crypto/rand"
- "encoding/base64"
- "io"
- "math/rand"
- "runtime"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/compressio"
-)
-
-func randomKey() ([]byte, error) {
- r := make([]byte, base64.RawStdEncoding.DecodedLen(keySize))
- if _, err := io.ReadFull(crand.Reader, r); err != nil {
- return nil, err
- }
- key := make([]byte, keySize)
- base64.RawStdEncoding.Encode(key, r)
- return key, nil
-}
-
-type testCase struct {
- name string
- data []byte
- metadata map[string]string
-}
-
-func TestStatefile(t *testing.T) {
- rand.Seed(time.Now().Unix())
-
- cases := []testCase{
- // Various data sizes.
- {"nil", nil, nil},
- {"empty", []byte(""), nil},
- {"some", []byte("_"), nil},
- {"one", []byte("0"), nil},
- {"two", []byte("01"), nil},
- {"three", []byte("012"), nil},
- {"four", []byte("0123"), nil},
- {"five", []byte("01234"), nil},
- {"six", []byte("012356"), nil},
- {"seven", []byte("0123567"), nil},
- {"eight", []byte("01235678"), nil},
-
- // Make sure we have one longer than the hash length.
- {"longer than hash", []byte("012356asdjflkasjlk3jlk23j4lkjaso0d789f0aujw3lkjlkxsdf78asdful2kj3ljka78"), nil},
-
- // Make sure we have one longer than the chunk size.
- {"chunks", make([]byte, 3*compressionChunkSize), nil},
- {"large", make([]byte, 30*compressionChunkSize), nil},
-
- // Different metadata.
- {"one metadata", []byte("data"), map[string]string{"foo": "bar"}},
- {"two metadata", []byte("data"), map[string]string{"foo": "bar", "one": "two"}},
- }
-
- for _, c := range cases {
- // Generate a key.
- integrityKey, err := randomKey()
- if err != nil {
- t.Errorf("can't generate key: got %v, excepted nil", err)
- continue
- }
-
- t.Run(c.name, func(t *testing.T) {
- for _, key := range [][]byte{nil, integrityKey} {
- t.Run("key="+string(key), func(t *testing.T) {
- // Encoding happens via a buffer.
- var bufEncoded bytes.Buffer
- var bufDecoded bytes.Buffer
-
- // Do all the writing.
- w, err := NewWriter(&bufEncoded, key, c.metadata)
- if err != nil {
- t.Fatalf("error creating writer: got %v, expected nil", err)
- }
- if _, err := io.Copy(w, bytes.NewBuffer(c.data)); err != nil {
- t.Fatalf("error during write: got %v, expected nil", err)
- }
-
- // Finish the sum.
- if err := w.Close(); err != nil {
- t.Fatalf("error during close: got %v, expected nil", err)
- }
-
- t.Logf("original data: %d bytes, encoded: %d bytes.",
- len(c.data), len(bufEncoded.Bytes()))
-
- // Do all the reading.
- r, metadata, err := NewReader(bytes.NewReader(bufEncoded.Bytes()), key)
- if err != nil {
- t.Fatalf("error creating reader: got %v, expected nil", err)
- }
- if _, err := io.Copy(&bufDecoded, r); err != nil {
- t.Fatalf("error during read: got %v, expected nil", err)
- }
-
- // Check that the data matches.
- if !bytes.Equal(c.data, bufDecoded.Bytes()) {
- t.Fatalf("data didn't match (%d vs %d bytes)", len(bufDecoded.Bytes()), len(c.data))
- }
-
- // Check that the metadata matches.
- for k, v := range c.metadata {
- nv, ok := metadata[k]
- if !ok {
- t.Fatalf("missing metadata: %s", k)
- }
- if v != nv {
- t.Fatalf("mismatched metdata for %s: got %s, expected %s", k, nv, v)
- }
- }
-
- // Change the data and verify that it fails.
- if key != nil {
- b := append([]byte(nil), bufEncoded.Bytes()...)
- b[rand.Intn(len(b))]++
- bufDecoded.Reset()
- r, _, err = NewReader(bytes.NewReader(b), key)
- if err == nil {
- _, err = io.Copy(&bufDecoded, r)
- }
- if err == nil {
- t.Error("got no error: expected error on data corruption")
- }
- }
-
- // Change the key and verify that it fails.
- newKey := integrityKey
- if len(key) > 0 {
- newKey = append([]byte{}, key...)
- newKey[rand.Intn(len(newKey))]++
- }
- bufDecoded.Reset()
- r, _, err = NewReader(bytes.NewReader(bufEncoded.Bytes()), newKey)
- if err == nil {
- _, err = io.Copy(&bufDecoded, r)
- }
- if err != compressio.ErrHashMismatch {
- t.Errorf("got error: %v, expected ErrHashMismatch on key mismatch", err)
- }
- })
- }
- })
- }
-}
-
-const benchmarkDataSize = 100 * 1024 * 1024
-
-func benchmark(b *testing.B, size int, write bool, compressible bool) {
- b.StopTimer()
- b.SetBytes(benchmarkDataSize)
-
- // Generate source data.
- var source []byte
- if compressible {
- // For compressible data, we use essentially all zeros.
- source = make([]byte, benchmarkDataSize)
- } else {
- // For non-compressible data, we use random base64 data (to
- // make it marginally compressible, a ratio of 75%).
- var sourceBuf bytes.Buffer
- bufW := base64.NewEncoder(base64.RawStdEncoding, &sourceBuf)
- bufR := rand.New(rand.NewSource(0))
- if _, err := io.CopyN(bufW, bufR, benchmarkDataSize); err != nil {
- b.Fatalf("unable to seed random data: %v", err)
- }
- source = sourceBuf.Bytes()
- }
-
- // Generate a random key for integrity check.
- key, err := randomKey()
- if err != nil {
- b.Fatalf("error generating key: %v", err)
- }
-
- // Define our benchmark functions. Prior to running the readState
- // function here, you must execute the writeState function at least
- // once (done below).
- var stateBuf bytes.Buffer
- writeState := func() {
- stateBuf.Reset()
- w, err := NewWriter(&stateBuf, key, nil)
- if err != nil {
- b.Fatalf("error creating writer: %v", err)
- }
- for done := 0; done < len(source); {
- chunk := size // limit size.
- if done+chunk > len(source) {
- chunk = len(source) - done
- }
- n, err := w.Write(source[done : done+chunk])
- done += n
- if n == 0 && err != nil {
- b.Fatalf("error during write: %v", err)
- }
- }
- if err := w.Close(); err != nil {
- b.Fatalf("error closing writer: %v", err)
- }
- }
- readState := func() {
- tmpBuf := bytes.NewBuffer(stateBuf.Bytes())
- r, _, err := NewReader(tmpBuf, key)
- if err != nil {
- b.Fatalf("error creating reader: %v", err)
- }
- for done := 0; done < len(source); {
- chunk := size // limit size.
- if done+chunk > len(source) {
- chunk = len(source) - done
- }
- n, err := r.Read(source[done : done+chunk])
- done += n
- if n == 0 && err != nil {
- b.Fatalf("error during read: %v", err)
- }
- }
- }
- // Generate the state once without timing to ensure that buffers have
- // been appropriately allocated.
- writeState()
- if write {
- b.StartTimer()
- for i := 0; i < b.N; i++ {
- writeState()
- }
- b.StopTimer()
- } else {
- b.StartTimer()
- for i := 0; i < b.N; i++ {
- readState()
- }
- b.StopTimer()
- }
-}
-
-func BenchmarkWrite4KCompressible(b *testing.B) {
- benchmark(b, 4096, true, true)
-}
-
-func BenchmarkWrite4KNoncompressible(b *testing.B) {
- benchmark(b, 4096, true, false)
-}
-
-func BenchmarkWrite1MCompressible(b *testing.B) {
- benchmark(b, 1024*1024, true, true)
-}
-
-func BenchmarkWrite1MNoncompressible(b *testing.B) {
- benchmark(b, 1024*1024, true, false)
-}
-
-func BenchmarkRead4KCompressible(b *testing.B) {
- benchmark(b, 4096, false, true)
-}
-
-func BenchmarkRead4KNoncompressible(b *testing.B) {
- benchmark(b, 4096, false, false)
-}
-
-func BenchmarkRead1MCompressible(b *testing.B) {
- benchmark(b, 1024*1024, false, true)
-}
-
-func BenchmarkRead1MNoncompressible(b *testing.B) {
- benchmark(b, 1024*1024, false, false)
-}
-
-func init() {
- runtime.GOMAXPROCS(runtime.NumCPU())
-}
diff --git a/pkg/syserr/BUILD b/pkg/syserr/BUILD
deleted file mode 100644
index 5665ad4ee..000000000
--- a/pkg/syserr/BUILD
+++ /dev/null
@@ -1,19 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "syserr",
- srcs = [
- "host_linux.go",
- "netstack.go",
- "syserr.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/syserr",
- visibility = ["//visibility:public"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/syserror",
- "//pkg/tcpip",
- ],
-)
diff --git a/pkg/syserr/syserr_state_autogen.go b/pkg/syserr/syserr_state_autogen.go
new file mode 100755
index 000000000..f34cb096b
--- /dev/null
+++ b/pkg/syserr/syserr_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package syserr
+
diff --git a/pkg/syserror/BUILD b/pkg/syserror/BUILD
deleted file mode 100644
index bd3f9fd28..000000000
--- a/pkg/syserror/BUILD
+++ /dev/null
@@ -1,19 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "syserror",
- srcs = ["syserror.go"],
- importpath = "gvisor.dev/gvisor/pkg/syserror",
- visibility = ["//visibility:public"],
-)
-
-go_test(
- name = "syserror_test",
- srcs = ["syserror_test.go"],
- deps = [
- ":syserror",
- ],
-)
diff --git a/pkg/syserror/syserror_state_autogen.go b/pkg/syserror/syserror_state_autogen.go
new file mode 100755
index 000000000..5691e4f5e
--- /dev/null
+++ b/pkg/syserror/syserror_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package syserror
+
diff --git a/pkg/syserror/syserror_test.go b/pkg/syserror/syserror_test.go
deleted file mode 100644
index 29719752e..000000000
--- a/pkg/syserror/syserror_test.go
+++ /dev/null
@@ -1,136 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package syserror_test
-
-import (
- "errors"
- "syscall"
- "testing"
-
- "gvisor.dev/gvisor/pkg/syserror"
-)
-
-var globalError error
-
-func returnErrnoAsError() error {
- return syscall.EINVAL
-}
-
-func returnError() error {
- return syserror.EINVAL
-}
-
-func BenchmarkReturnErrnoAsError(b *testing.B) {
- for i := b.N; i > 0; i-- {
- returnErrnoAsError()
- }
-}
-
-func BenchmarkReturnError(b *testing.B) {
- for i := b.N; i > 0; i-- {
- returnError()
- }
-}
-
-func BenchmarkCompareErrno(b *testing.B) {
- j := 0
- for i := b.N; i > 0; i-- {
- if globalError == syscall.EINVAL {
- j++
- }
- }
-}
-
-func BenchmarkCompareError(b *testing.B) {
- j := 0
- for i := b.N; i > 0; i-- {
- if globalError == syserror.EINVAL {
- j++
- }
- }
-}
-
-func BenchmarkSwitchErrno(b *testing.B) {
- j := 0
- for i := b.N; i > 0; i-- {
- switch globalError {
- case syscall.EINVAL:
- j += 1
- case syscall.EINTR:
- j += 2
- case syscall.EAGAIN:
- j += 3
- }
- }
-}
-
-func BenchmarkSwitchError(b *testing.B) {
- j := 0
- for i := b.N; i > 0; i-- {
- switch globalError {
- case syserror.EINVAL:
- j += 1
- case syserror.EINTR:
- j += 2
- case syserror.EAGAIN:
- j += 3
- }
- }
-}
-
-type translationTestTable struct {
- fn string
- errIn error
- syscallErrorIn syscall.Errno
- expectedBool bool
- expectedTranslation syscall.Errno
-}
-
-func TestErrorTranslation(t *testing.T) {
- myError := errors.New("My test error")
- myError2 := errors.New("Another test error")
- testTable := []translationTestTable{
- {"TranslateError", myError, 0, false, 0},
- {"TranslateError", myError2, 0, false, 0},
- {"AddErrorTranslation", myError, syscall.EAGAIN, true, 0},
- {"AddErrorTranslation", myError, syscall.EAGAIN, false, 0},
- {"AddErrorTranslation", myError, syscall.EPERM, false, 0},
- {"TranslateError", myError, 0, true, syscall.EAGAIN},
- {"TranslateError", myError2, 0, false, 0},
- {"AddErrorTranslation", myError2, syscall.EPERM, true, 0},
- {"AddErrorTranslation", myError2, syscall.EPERM, false, 0},
- {"AddErrorTranslation", myError2, syscall.EAGAIN, false, 0},
- {"TranslateError", myError, 0, true, syscall.EAGAIN},
- {"TranslateError", myError2, 0, true, syscall.EPERM},
- }
- for _, tt := range testTable {
- switch tt.fn {
- case "TranslateError":
- err, ok := syserror.TranslateError(tt.errIn)
- if ok != tt.expectedBool {
- t.Fatalf("%v(%v) => %v expected %v", tt.fn, tt.errIn, ok, tt.expectedBool)
- } else if err != tt.expectedTranslation {
- t.Fatalf("%v(%v) (error) => %v expected %v", tt.fn, tt.errIn, err, tt.expectedTranslation)
- }
- case "AddErrorTranslation":
- ok := syserror.AddErrorTranslation(tt.errIn, tt.syscallErrorIn)
- if ok != tt.expectedBool {
- t.Fatalf("%v(%v) => %v expected %v", tt.fn, tt.errIn, ok, tt.expectedBool)
- }
- default:
- t.Fatalf("Unknown function %v", tt.fn)
- }
- }
-}
diff --git a/pkg/tcpip/BUILD b/pkg/tcpip/BUILD
deleted file mode 100644
index 3fd9e3134..000000000
--- a/pkg/tcpip/BUILD
+++ /dev/null
@@ -1,27 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "tcpip",
- srcs = [
- "tcpip.go",
- "time_unsafe.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/tcpip",
- visibility = ["//visibility:public"],
- deps = [
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/iptables",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "tcpip_test",
- size = "small",
- srcs = ["tcpip_test.go"],
- embed = [":tcpip"],
-)
diff --git a/pkg/tcpip/adapters/gonet/BUILD b/pkg/tcpip/adapters/gonet/BUILD
deleted file mode 100644
index 78df5a0b1..000000000
--- a/pkg/tcpip/adapters/gonet/BUILD
+++ /dev/null
@@ -1,38 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "gonet",
- srcs = ["gonet.go"],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet",
- visibility = ["//visibility:public"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/stack",
- "//pkg/tcpip/transport/tcp",
- "//pkg/tcpip/transport/udp",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "gonet_test",
- size = "small",
- srcs = ["gonet_test.go"],
- embed = [":gonet"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/header",
- "//pkg/tcpip/link/loopback",
- "//pkg/tcpip/network/ipv4",
- "//pkg/tcpip/network/ipv6",
- "//pkg/tcpip/stack",
- "//pkg/tcpip/transport/tcp",
- "//pkg/tcpip/transport/udp",
- "//pkg/waiter",
- "@org_golang_x_net//nettest:go_default_library",
- ],
-)
diff --git a/pkg/tcpip/adapters/gonet/gonet.go b/pkg/tcpip/adapters/gonet/gonet.go
deleted file mode 100644
index cd6ce930a..000000000
--- a/pkg/tcpip/adapters/gonet/gonet.go
+++ /dev/null
@@ -1,722 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package gonet provides a Go net package compatible wrapper for a tcpip stack.
-package gonet
-
-import (
- "context"
- "errors"
- "io"
- "net"
- "sync"
- "time"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
- "gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
- "gvisor.dev/gvisor/pkg/tcpip/transport/udp"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-var (
- errCanceled = errors.New("operation canceled")
- errWouldBlock = errors.New("operation would block")
-)
-
-// timeoutError is how the net package reports timeouts.
-type timeoutError struct{}
-
-func (e *timeoutError) Error() string { return "i/o timeout" }
-func (e *timeoutError) Timeout() bool { return true }
-func (e *timeoutError) Temporary() bool { return true }
-
-// A Listener is a wrapper around a tcpip endpoint that implements
-// net.Listener.
-type Listener struct {
- stack *stack.Stack
- ep tcpip.Endpoint
- wq *waiter.Queue
- cancel chan struct{}
-}
-
-// NewListener creates a new Listener.
-func NewListener(s *stack.Stack, addr tcpip.FullAddress, network tcpip.NetworkProtocolNumber) (*Listener, error) {
- // Create TCP endpoint, bind it, then start listening.
- var wq waiter.Queue
- ep, err := s.NewEndpoint(tcp.ProtocolNumber, network, &wq)
- if err != nil {
- return nil, errors.New(err.String())
- }
-
- if err := ep.Bind(addr); err != nil {
- ep.Close()
- return nil, &net.OpError{
- Op: "bind",
- Net: "tcp",
- Addr: fullToTCPAddr(addr),
- Err: errors.New(err.String()),
- }
- }
-
- if err := ep.Listen(10); err != nil {
- ep.Close()
- return nil, &net.OpError{
- Op: "listen",
- Net: "tcp",
- Addr: fullToTCPAddr(addr),
- Err: errors.New(err.String()),
- }
- }
-
- return &Listener{
- stack: s,
- ep: ep,
- wq: &wq,
- cancel: make(chan struct{}),
- }, nil
-}
-
-// Close implements net.Listener.Close.
-func (l *Listener) Close() error {
- l.ep.Close()
- return nil
-}
-
-// Shutdown stops the HTTP server.
-func (l *Listener) Shutdown() {
- l.ep.Shutdown(tcpip.ShutdownWrite | tcpip.ShutdownRead)
- close(l.cancel) // broadcast cancellation
-}
-
-// Addr implements net.Listener.Addr.
-func (l *Listener) Addr() net.Addr {
- a, err := l.ep.GetLocalAddress()
- if err != nil {
- return nil
- }
- return fullToTCPAddr(a)
-}
-
-type deadlineTimer struct {
- // mu protects the fields below.
- mu sync.Mutex
-
- readTimer *time.Timer
- readCancelCh chan struct{}
- writeTimer *time.Timer
- writeCancelCh chan struct{}
-}
-
-func (d *deadlineTimer) init() {
- d.readCancelCh = make(chan struct{})
- d.writeCancelCh = make(chan struct{})
-}
-
-func (d *deadlineTimer) readCancel() <-chan struct{} {
- d.mu.Lock()
- c := d.readCancelCh
- d.mu.Unlock()
- return c
-}
-func (d *deadlineTimer) writeCancel() <-chan struct{} {
- d.mu.Lock()
- c := d.writeCancelCh
- d.mu.Unlock()
- return c
-}
-
-// setDeadline contains the shared logic for setting a deadline.
-//
-// cancelCh and timer must be pointers to deadlineTimer.readCancelCh and
-// deadlineTimer.readTimer or deadlineTimer.writeCancelCh and
-// deadlineTimer.writeTimer.
-//
-// setDeadline must only be called while holding d.mu.
-func (d *deadlineTimer) setDeadline(cancelCh *chan struct{}, timer **time.Timer, t time.Time) {
- if *timer != nil && !(*timer).Stop() {
- *cancelCh = make(chan struct{})
- }
-
- // Create a new channel if we already closed it due to setting an already
- // expired time. We won't race with the timer because we already handled
- // that above.
- select {
- case <-*cancelCh:
- *cancelCh = make(chan struct{})
- default:
- }
-
- // "A zero value for t means I/O operations will not time out."
- // - net.Conn.SetDeadline
- if t.IsZero() {
- return
- }
-
- timeout := t.Sub(time.Now())
- if timeout <= 0 {
- close(*cancelCh)
- return
- }
-
- // Timer.Stop returns whether or not the AfterFunc has started, but
- // does not indicate whether or not it has completed. Make a copy of
- // the cancel channel to prevent this code from racing with the next
- // call of setDeadline replacing *cancelCh.
- ch := *cancelCh
- *timer = time.AfterFunc(timeout, func() {
- close(ch)
- })
-}
-
-// SetReadDeadline implements net.Conn.SetReadDeadline and
-// net.PacketConn.SetReadDeadline.
-func (d *deadlineTimer) SetReadDeadline(t time.Time) error {
- d.mu.Lock()
- d.setDeadline(&d.readCancelCh, &d.readTimer, t)
- d.mu.Unlock()
- return nil
-}
-
-// SetWriteDeadline implements net.Conn.SetWriteDeadline and
-// net.PacketConn.SetWriteDeadline.
-func (d *deadlineTimer) SetWriteDeadline(t time.Time) error {
- d.mu.Lock()
- d.setDeadline(&d.writeCancelCh, &d.writeTimer, t)
- d.mu.Unlock()
- return nil
-}
-
-// SetDeadline implements net.Conn.SetDeadline and net.PacketConn.SetDeadline.
-func (d *deadlineTimer) SetDeadline(t time.Time) error {
- d.mu.Lock()
- d.setDeadline(&d.readCancelCh, &d.readTimer, t)
- d.setDeadline(&d.writeCancelCh, &d.writeTimer, t)
- d.mu.Unlock()
- return nil
-}
-
-// A Conn is a wrapper around a tcpip.Endpoint that implements the net.Conn
-// interface.
-type Conn struct {
- deadlineTimer
-
- wq *waiter.Queue
- ep tcpip.Endpoint
-
- // readMu serializes reads and implicitly protects read.
- //
- // Lock ordering:
- // If both readMu and deadlineTimer.mu are to be used in a single
- // request, readMu must be acquired before deadlineTimer.mu.
- readMu sync.Mutex
-
- // read contains bytes that have been read from the endpoint,
- // but haven't yet been returned.
- read buffer.View
-}
-
-// NewConn creates a new Conn.
-func NewConn(wq *waiter.Queue, ep tcpip.Endpoint) *Conn {
- c := &Conn{
- wq: wq,
- ep: ep,
- }
- c.deadlineTimer.init()
- return c
-}
-
-// Accept implements net.Conn.Accept.
-func (l *Listener) Accept() (net.Conn, error) {
- n, wq, err := l.ep.Accept()
-
- if err == tcpip.ErrWouldBlock {
- // Create wait queue entry that notifies a channel.
- waitEntry, notifyCh := waiter.NewChannelEntry(nil)
- l.wq.EventRegister(&waitEntry, waiter.EventIn)
- defer l.wq.EventUnregister(&waitEntry)
-
- for {
- n, wq, err = l.ep.Accept()
-
- if err != tcpip.ErrWouldBlock {
- break
- }
-
- select {
- case <-l.cancel:
- return nil, errCanceled
- case <-notifyCh:
- }
- }
- }
-
- if err != nil {
- return nil, &net.OpError{
- Op: "accept",
- Net: "tcp",
- Addr: l.Addr(),
- Err: errors.New(err.String()),
- }
- }
-
- return NewConn(wq, n), nil
-}
-
-type opErrorer interface {
- newOpError(op string, err error) *net.OpError
-}
-
-// commonRead implements the common logic between net.Conn.Read and
-// net.PacketConn.ReadFrom.
-func commonRead(ep tcpip.Endpoint, wq *waiter.Queue, deadline <-chan struct{}, addr *tcpip.FullAddress, errorer opErrorer, dontWait bool) ([]byte, error) {
- select {
- case <-deadline:
- return nil, errorer.newOpError("read", &timeoutError{})
- default:
- }
-
- read, _, err := ep.Read(addr)
-
- if err == tcpip.ErrWouldBlock {
- if dontWait {
- return nil, errWouldBlock
- }
- // Create wait queue entry that notifies a channel.
- waitEntry, notifyCh := waiter.NewChannelEntry(nil)
- wq.EventRegister(&waitEntry, waiter.EventIn)
- defer wq.EventUnregister(&waitEntry)
- for {
- read, _, err = ep.Read(addr)
- if err != tcpip.ErrWouldBlock {
- break
- }
- select {
- case <-deadline:
- return nil, errorer.newOpError("read", &timeoutError{})
- case <-notifyCh:
- }
- }
- }
-
- if err == tcpip.ErrClosedForReceive {
- return nil, io.EOF
- }
-
- if err != nil {
- return nil, errorer.newOpError("read", errors.New(err.String()))
- }
-
- return read, nil
-}
-
-// Read implements net.Conn.Read.
-func (c *Conn) Read(b []byte) (int, error) {
- c.readMu.Lock()
- defer c.readMu.Unlock()
-
- deadline := c.readCancel()
-
- numRead := 0
- for numRead != len(b) {
- if len(c.read) == 0 {
- var err error
- c.read, err = commonRead(c.ep, c.wq, deadline, nil, c, numRead != 0)
- if err != nil {
- if numRead != 0 {
- return numRead, nil
- }
- return numRead, err
- }
- }
- n := copy(b[numRead:], c.read)
- c.read.TrimFront(n)
- numRead += n
- if len(c.read) == 0 {
- c.read = nil
- }
- }
- return numRead, nil
-}
-
-// Write implements net.Conn.Write.
-func (c *Conn) Write(b []byte) (int, error) {
- deadline := c.writeCancel()
-
- // Check if deadlineTimer has already expired.
- select {
- case <-deadline:
- return 0, c.newOpError("write", &timeoutError{})
- default:
- }
-
- v := buffer.NewViewFromBytes(b)
-
- // We must handle two soft failure conditions simultaneously:
- // 1. Write may write nothing and return tcpip.ErrWouldBlock.
- // If this happens, we need to register for notifications if we have
- // not already and wait to try again.
- // 2. Write may write fewer than the full number of bytes and return
- // without error. In this case we need to try writing the remaining
- // bytes again. I do not need to register for notifications.
- //
- // What is more, these two soft failure conditions can be interspersed.
- // There is no guarantee that all of the condition #1s will occur before
- // all of the condition #2s or visa-versa.
- var (
- err *tcpip.Error
- nbytes int
- reg bool
- notifyCh chan struct{}
- )
- for nbytes < len(b) && (err == tcpip.ErrWouldBlock || err == nil) {
- if err == tcpip.ErrWouldBlock {
- if !reg {
- // Only register once.
- reg = true
-
- // Create wait queue entry that notifies a channel.
- var waitEntry waiter.Entry
- waitEntry, notifyCh = waiter.NewChannelEntry(nil)
- c.wq.EventRegister(&waitEntry, waiter.EventOut)
- defer c.wq.EventUnregister(&waitEntry)
- } else {
- // Don't wait immediately after registration in case more data
- // became available between when we last checked and when we setup
- // the notification.
- select {
- case <-deadline:
- return nbytes, c.newOpError("write", &timeoutError{})
- case <-notifyCh:
- }
- }
- }
-
- var n int64
- var resCh <-chan struct{}
- n, resCh, err = c.ep.Write(tcpip.SlicePayload(v), tcpip.WriteOptions{})
- nbytes += int(n)
- v.TrimFront(int(n))
-
- if resCh != nil {
- select {
- case <-deadline:
- return nbytes, c.newOpError("write", &timeoutError{})
- case <-resCh:
- }
-
- n, _, err = c.ep.Write(tcpip.SlicePayload(v), tcpip.WriteOptions{})
- nbytes += int(n)
- v.TrimFront(int(n))
- }
- }
-
- if err == nil {
- return nbytes, nil
- }
-
- return nbytes, c.newOpError("write", errors.New(err.String()))
-}
-
-// Close implements net.Conn.Close.
-func (c *Conn) Close() error {
- c.ep.Close()
- return nil
-}
-
-// CloseRead shuts down the reading side of the TCP connection. Most callers
-// should just use Close.
-//
-// A TCP Half-Close is performed the same as CloseRead for *net.TCPConn.
-func (c *Conn) CloseRead() error {
- if terr := c.ep.Shutdown(tcpip.ShutdownRead); terr != nil {
- return c.newOpError("close", errors.New(terr.String()))
- }
- return nil
-}
-
-// CloseWrite shuts down the writing side of the TCP connection. Most callers
-// should just use Close.
-//
-// A TCP Half-Close is performed the same as CloseWrite for *net.TCPConn.
-func (c *Conn) CloseWrite() error {
- if terr := c.ep.Shutdown(tcpip.ShutdownWrite); terr != nil {
- return c.newOpError("close", errors.New(terr.String()))
- }
- return nil
-}
-
-// LocalAddr implements net.Conn.LocalAddr.
-func (c *Conn) LocalAddr() net.Addr {
- a, err := c.ep.GetLocalAddress()
- if err != nil {
- return nil
- }
- return fullToTCPAddr(a)
-}
-
-// RemoteAddr implements net.Conn.RemoteAddr.
-func (c *Conn) RemoteAddr() net.Addr {
- a, err := c.ep.GetRemoteAddress()
- if err != nil {
- return nil
- }
- return fullToTCPAddr(a)
-}
-
-func (c *Conn) newOpError(op string, err error) *net.OpError {
- return &net.OpError{
- Op: op,
- Net: "tcp",
- Source: c.LocalAddr(),
- Addr: c.RemoteAddr(),
- Err: err,
- }
-}
-
-func fullToTCPAddr(addr tcpip.FullAddress) *net.TCPAddr {
- return &net.TCPAddr{IP: net.IP(addr.Addr), Port: int(addr.Port)}
-}
-
-func fullToUDPAddr(addr tcpip.FullAddress) *net.UDPAddr {
- return &net.UDPAddr{IP: net.IP(addr.Addr), Port: int(addr.Port)}
-}
-
-// DialTCP creates a new TCP Conn connected to the specified address.
-func DialTCP(s *stack.Stack, addr tcpip.FullAddress, network tcpip.NetworkProtocolNumber) (*Conn, error) {
- return DialContextTCP(context.Background(), s, addr, network)
-}
-
-// DialContextTCP creates a new TCP Conn connected to the specified address
-// with the option of adding cancellation and timeouts.
-func DialContextTCP(ctx context.Context, s *stack.Stack, addr tcpip.FullAddress, network tcpip.NetworkProtocolNumber) (*Conn, error) {
- // Create TCP endpoint, then connect.
- var wq waiter.Queue
- ep, err := s.NewEndpoint(tcp.ProtocolNumber, network, &wq)
- if err != nil {
- return nil, errors.New(err.String())
- }
-
- // Create wait queue entry that notifies a channel.
- //
- // We do this unconditionally as Connect will always return an error.
- waitEntry, notifyCh := waiter.NewChannelEntry(nil)
- wq.EventRegister(&waitEntry, waiter.EventOut)
- defer wq.EventUnregister(&waitEntry)
-
- select {
- case <-ctx.Done():
- return nil, ctx.Err()
- default:
- }
-
- err = ep.Connect(addr)
- if err == tcpip.ErrConnectStarted {
- select {
- case <-ctx.Done():
- ep.Close()
- return nil, ctx.Err()
- case <-notifyCh:
- }
-
- err = ep.GetSockOpt(tcpip.ErrorOption{})
- }
- if err != nil {
- ep.Close()
- return nil, &net.OpError{
- Op: "connect",
- Net: "tcp",
- Addr: fullToTCPAddr(addr),
- Err: errors.New(err.String()),
- }
- }
-
- return NewConn(&wq, ep), nil
-}
-
-// A PacketConn is a wrapper around a tcpip endpoint that implements
-// net.PacketConn.
-type PacketConn struct {
- deadlineTimer
-
- stack *stack.Stack
- ep tcpip.Endpoint
- wq *waiter.Queue
-}
-
-// DialUDP creates a new PacketConn.
-//
-// If laddr is nil, a local address is automatically chosen.
-//
-// If raddr is nil, the PacketConn is left unconnected.
-func DialUDP(s *stack.Stack, laddr, raddr *tcpip.FullAddress, network tcpip.NetworkProtocolNumber) (*PacketConn, error) {
- var wq waiter.Queue
- ep, err := s.NewEndpoint(udp.ProtocolNumber, network, &wq)
- if err != nil {
- return nil, errors.New(err.String())
- }
-
- if laddr != nil {
- if err := ep.Bind(*laddr); err != nil {
- ep.Close()
- return nil, &net.OpError{
- Op: "bind",
- Net: "udp",
- Addr: fullToUDPAddr(*laddr),
- Err: errors.New(err.String()),
- }
- }
- }
-
- c := PacketConn{
- stack: s,
- ep: ep,
- wq: &wq,
- }
- c.deadlineTimer.init()
-
- if raddr != nil {
- if err := c.ep.Connect(*raddr); err != nil {
- c.ep.Close()
- return nil, &net.OpError{
- Op: "connect",
- Net: "udp",
- Addr: fullToUDPAddr(*raddr),
- Err: errors.New(err.String()),
- }
- }
- }
-
- return &c, nil
-}
-
-func (c *PacketConn) newOpError(op string, err error) *net.OpError {
- return c.newRemoteOpError(op, nil, err)
-}
-
-func (c *PacketConn) newRemoteOpError(op string, remote net.Addr, err error) *net.OpError {
- return &net.OpError{
- Op: op,
- Net: "udp",
- Source: c.LocalAddr(),
- Addr: remote,
- Err: err,
- }
-}
-
-// RemoteAddr implements net.Conn.RemoteAddr.
-func (c *PacketConn) RemoteAddr() net.Addr {
- a, err := c.ep.GetRemoteAddress()
- if err != nil {
- return nil
- }
- return fullToTCPAddr(a)
-}
-
-// Read implements net.Conn.Read
-func (c *PacketConn) Read(b []byte) (int, error) {
- bytesRead, _, err := c.ReadFrom(b)
- return bytesRead, err
-}
-
-// ReadFrom implements net.PacketConn.ReadFrom.
-func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
- deadline := c.readCancel()
-
- var addr tcpip.FullAddress
- read, err := commonRead(c.ep, c.wq, deadline, &addr, c, false)
- if err != nil {
- return 0, nil, err
- }
-
- return copy(b, read), fullToUDPAddr(addr), nil
-}
-
-func (c *PacketConn) Write(b []byte) (int, error) {
- return c.WriteTo(b, nil)
-}
-
-// WriteTo implements net.PacketConn.WriteTo.
-func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
- deadline := c.writeCancel()
-
- // Check if deadline has already expired.
- select {
- case <-deadline:
- return 0, c.newRemoteOpError("write", addr, &timeoutError{})
- default:
- }
-
- // If we're being called by Write, there is no addr
- wopts := tcpip.WriteOptions{}
- if addr != nil {
- ua := addr.(*net.UDPAddr)
- wopts.To = &tcpip.FullAddress{Addr: tcpip.Address(ua.IP), Port: uint16(ua.Port)}
- }
-
- v := buffer.NewView(len(b))
- copy(v, b)
-
- n, resCh, err := c.ep.Write(tcpip.SlicePayload(v), wopts)
- if resCh != nil {
- select {
- case <-deadline:
- return int(n), c.newRemoteOpError("write", addr, &timeoutError{})
- case <-resCh:
- }
-
- n, _, err = c.ep.Write(tcpip.SlicePayload(v), wopts)
- }
-
- if err == tcpip.ErrWouldBlock {
- // Create wait queue entry that notifies a channel.
- waitEntry, notifyCh := waiter.NewChannelEntry(nil)
- c.wq.EventRegister(&waitEntry, waiter.EventOut)
- defer c.wq.EventUnregister(&waitEntry)
- for {
- select {
- case <-deadline:
- return int(n), c.newRemoteOpError("write", addr, &timeoutError{})
- case <-notifyCh:
- }
-
- n, _, err = c.ep.Write(tcpip.SlicePayload(v), wopts)
- if err != tcpip.ErrWouldBlock {
- break
- }
- }
- }
-
- if err == nil {
- return int(n), nil
- }
-
- return int(n), c.newRemoteOpError("write", addr, errors.New(err.String()))
-}
-
-// Close implements net.PacketConn.Close.
-func (c *PacketConn) Close() error {
- c.ep.Close()
- return nil
-}
-
-// LocalAddr implements net.PacketConn.LocalAddr.
-func (c *PacketConn) LocalAddr() net.Addr {
- a, err := c.ep.GetLocalAddress()
- if err != nil {
- return nil
- }
- return fullToUDPAddr(a)
-}
diff --git a/pkg/tcpip/adapters/gonet/gonet_test.go b/pkg/tcpip/adapters/gonet/gonet_test.go
deleted file mode 100644
index 672f026b2..000000000
--- a/pkg/tcpip/adapters/gonet/gonet_test.go
+++ /dev/null
@@ -1,684 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package gonet
-
-import (
- "context"
- "fmt"
- "io"
- "net"
- "reflect"
- "strings"
- "testing"
- "time"
-
- "golang.org/x/net/nettest"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/link/loopback"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
- "gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
- "gvisor.dev/gvisor/pkg/tcpip/transport/udp"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-const (
- NICID = 1
-)
-
-func TestTimeouts(t *testing.T) {
- nc := NewConn(nil, nil)
- dlfs := []struct {
- name string
- f func(time.Time) error
- }{
- {"SetDeadline", nc.SetDeadline},
- {"SetReadDeadline", nc.SetReadDeadline},
- {"SetWriteDeadline", nc.SetWriteDeadline},
- }
-
- for _, dlf := range dlfs {
- if err := dlf.f(time.Time{}); err != nil {
- t.Errorf("got %s(time.Time{}) = %v, want = %v", dlf.name, err, nil)
- }
- }
-}
-
-func newLoopbackStack() (*stack.Stack, *tcpip.Error) {
- // Create the stack and add a NIC.
- s := stack.New([]string{ipv4.ProtocolName, ipv6.ProtocolName}, []string{tcp.ProtocolName, udp.ProtocolName}, stack.Options{})
-
- if err := s.CreateNIC(NICID, loopback.New()); err != nil {
- return nil, err
- }
-
- // Add default route.
- s.SetRouteTable([]tcpip.Route{
- // IPv4
- {
- Destination: header.IPv4EmptySubnet,
- NIC: NICID,
- },
-
- // IPv6
- {
- Destination: header.IPv6EmptySubnet,
- NIC: NICID,
- },
- })
-
- return s, nil
-}
-
-type testConnection struct {
- wq *waiter.Queue
- e *waiter.Entry
- ch chan struct{}
- ep tcpip.Endpoint
-}
-
-func connect(s *stack.Stack, addr tcpip.FullAddress) (*testConnection, *tcpip.Error) {
- wq := &waiter.Queue{}
- ep, err := s.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq)
-
- entry, ch := waiter.NewChannelEntry(nil)
- wq.EventRegister(&entry, waiter.EventOut)
-
- err = ep.Connect(addr)
- if err == tcpip.ErrConnectStarted {
- <-ch
- err = ep.GetSockOpt(tcpip.ErrorOption{})
- }
- if err != nil {
- return nil, err
- }
-
- wq.EventUnregister(&entry)
- wq.EventRegister(&entry, waiter.EventIn)
-
- return &testConnection{wq, &entry, ch, ep}, nil
-}
-
-func (c *testConnection) close() {
- c.wq.EventUnregister(c.e)
- c.ep.Close()
-}
-
-// TestCloseReader tests that Conn.Close() causes Conn.Read() to unblock.
-func TestCloseReader(t *testing.T) {
- s, err := newLoopbackStack()
- if err != nil {
- t.Fatalf("newLoopbackStack() = %v", err)
- }
-
- addr := tcpip.FullAddress{NICID, tcpip.Address(net.IPv4(169, 254, 10, 1).To4()), 11211}
-
- s.AddAddress(NICID, ipv4.ProtocolNumber, addr.Addr)
-
- l, e := NewListener(s, addr, ipv4.ProtocolNumber)
- if e != nil {
- t.Fatalf("NewListener() = %v", e)
- }
- done := make(chan struct{})
- go func() {
- defer close(done)
- c, err := l.Accept()
- if err != nil {
- t.Fatalf("l.Accept() = %v", err)
- }
-
- // Give c.Read() a chance to block before closing the connection.
- time.AfterFunc(time.Millisecond*50, func() {
- c.Close()
- })
-
- buf := make([]byte, 256)
- n, err := c.Read(buf)
- got, ok := err.(*net.OpError)
- want := tcpip.ErrConnectionAborted
- if n != 0 || !ok || got.Err.Error() != want.String() {
- t.Errorf("c.Read() = (%d, %v), want (0, OpError(%v))", n, err, want)
- }
- }()
- sender, err := connect(s, addr)
- if err != nil {
- t.Fatalf("connect() = %v", err)
- }
-
- select {
- case <-done:
- case <-time.After(5 * time.Second):
- t.Errorf("c.Read() didn't unblock")
- }
- sender.close()
-}
-
-// TestCloseReaderWithForwarder tests that Conn.Close() wakes Conn.Read() when
-// using tcp.Forwarder.
-func TestCloseReaderWithForwarder(t *testing.T) {
- s, err := newLoopbackStack()
- if err != nil {
- t.Fatalf("newLoopbackStack() = %v", err)
- }
-
- addr := tcpip.FullAddress{NICID, tcpip.Address(net.IPv4(169, 254, 10, 1).To4()), 11211}
- s.AddAddress(NICID, ipv4.ProtocolNumber, addr.Addr)
-
- done := make(chan struct{})
-
- fwd := tcp.NewForwarder(s, 30000, 10, func(r *tcp.ForwarderRequest) {
- defer close(done)
-
- var wq waiter.Queue
- ep, err := r.CreateEndpoint(&wq)
- if err != nil {
- t.Fatalf("r.CreateEndpoint() = %v", err)
- }
- defer ep.Close()
- r.Complete(false)
-
- c := NewConn(&wq, ep)
-
- // Give c.Read() a chance to block before closing the connection.
- time.AfterFunc(time.Millisecond*50, func() {
- c.Close()
- })
-
- buf := make([]byte, 256)
- n, e := c.Read(buf)
- got, ok := e.(*net.OpError)
- want := tcpip.ErrConnectionAborted
- if n != 0 || !ok || got.Err.Error() != want.String() {
- t.Errorf("c.Read() = (%d, %v), want (0, OpError(%v))", n, e, want)
- }
- })
- s.SetTransportProtocolHandler(tcp.ProtocolNumber, fwd.HandlePacket)
-
- sender, err := connect(s, addr)
- if err != nil {
- t.Fatalf("connect() = %v", err)
- }
-
- select {
- case <-done:
- case <-time.After(5 * time.Second):
- t.Errorf("c.Read() didn't unblock")
- }
- sender.close()
-}
-
-func TestCloseRead(t *testing.T) {
- s, terr := newLoopbackStack()
- if terr != nil {
- t.Fatalf("newLoopbackStack() = %v", terr)
- }
-
- addr := tcpip.FullAddress{NICID, tcpip.Address(net.IPv4(169, 254, 10, 1).To4()), 11211}
- s.AddAddress(NICID, ipv4.ProtocolNumber, addr.Addr)
-
- fwd := tcp.NewForwarder(s, 30000, 10, func(r *tcp.ForwarderRequest) {
- var wq waiter.Queue
- ep, err := r.CreateEndpoint(&wq)
- if err != nil {
- t.Fatalf("r.CreateEndpoint() = %v", err)
- }
- defer ep.Close()
- r.Complete(false)
-
- c := NewConn(&wq, ep)
-
- buf := make([]byte, 256)
- n, e := c.Read(buf)
- if e != nil || string(buf[:n]) != "abc123" {
- t.Fatalf("c.Read() = (%d, %v), want (6, nil)", n, e)
- }
-
- if n, e = c.Write([]byte("abc123")); e != nil {
- t.Errorf("c.Write() = (%d, %v), want (6, nil)", n, e)
- }
- })
-
- s.SetTransportProtocolHandler(tcp.ProtocolNumber, fwd.HandlePacket)
-
- tc, terr := connect(s, addr)
- if terr != nil {
- t.Fatalf("connect() = %v", terr)
- }
- c := NewConn(tc.wq, tc.ep)
-
- if err := c.CloseRead(); err != nil {
- t.Errorf("c.CloseRead() = %v", err)
- }
-
- buf := make([]byte, 256)
- if n, err := c.Read(buf); err != io.EOF {
- t.Errorf("c.Read() = (%d, %v), want (0, io.EOF)", n, err)
- }
-
- if n, err := c.Write([]byte("abc123")); n != 6 || err != nil {
- t.Errorf("c.Write() = (%d, %v), want (6, nil)", n, err)
- }
-}
-
-func TestCloseWrite(t *testing.T) {
- s, terr := newLoopbackStack()
- if terr != nil {
- t.Fatalf("newLoopbackStack() = %v", terr)
- }
-
- addr := tcpip.FullAddress{NICID, tcpip.Address(net.IPv4(169, 254, 10, 1).To4()), 11211}
- s.AddAddress(NICID, ipv4.ProtocolNumber, addr.Addr)
-
- fwd := tcp.NewForwarder(s, 30000, 10, func(r *tcp.ForwarderRequest) {
- var wq waiter.Queue
- ep, err := r.CreateEndpoint(&wq)
- if err != nil {
- t.Fatalf("r.CreateEndpoint() = %v", err)
- }
- defer ep.Close()
- r.Complete(false)
-
- c := NewConn(&wq, ep)
-
- n, e := c.Read(make([]byte, 256))
- if n != 0 || e != io.EOF {
- t.Errorf("c.Read() = (%d, %v), want (0, io.EOF)", n, e)
- }
-
- if n, e = c.Write([]byte("abc123")); n != 6 || e != nil {
- t.Errorf("c.Write() = (%d, %v), want (6, nil)", n, e)
- }
- })
-
- s.SetTransportProtocolHandler(tcp.ProtocolNumber, fwd.HandlePacket)
-
- tc, terr := connect(s, addr)
- if terr != nil {
- t.Fatalf("connect() = %v", terr)
- }
- c := NewConn(tc.wq, tc.ep)
-
- if err := c.CloseWrite(); err != nil {
- t.Errorf("c.CloseWrite() = %v", err)
- }
-
- buf := make([]byte, 256)
- n, err := c.Read(buf)
- if err != nil || string(buf[:n]) != "abc123" {
- t.Fatalf("c.Read() = (%d, %v), want (6, nil)", n, err)
- }
-
- n, err = c.Write([]byte("abc123"))
- got, ok := err.(*net.OpError)
- want := "endpoint is closed for send"
- if n != 0 || !ok || got.Op != "write" || got.Err == nil || !strings.HasSuffix(got.Err.Error(), want) {
- t.Errorf("c.Write() = (%d, %v), want (0, OpError(Op: write, Err: %s))", n, err, want)
- }
-}
-
-func TestUDPForwarder(t *testing.T) {
- s, terr := newLoopbackStack()
- if terr != nil {
- t.Fatalf("newLoopbackStack() = %v", terr)
- }
-
- ip1 := tcpip.Address(net.IPv4(169, 254, 10, 1).To4())
- addr1 := tcpip.FullAddress{NICID, ip1, 11211}
- s.AddAddress(NICID, ipv4.ProtocolNumber, ip1)
- ip2 := tcpip.Address(net.IPv4(169, 254, 10, 2).To4())
- addr2 := tcpip.FullAddress{NICID, ip2, 11311}
- s.AddAddress(NICID, ipv4.ProtocolNumber, ip2)
-
- done := make(chan struct{})
- fwd := udp.NewForwarder(s, func(r *udp.ForwarderRequest) {
- defer close(done)
-
- var wq waiter.Queue
- ep, err := r.CreateEndpoint(&wq)
- if err != nil {
- t.Fatalf("r.CreateEndpoint() = %v", err)
- }
- defer ep.Close()
-
- c := NewConn(&wq, ep)
-
- buf := make([]byte, 256)
- n, e := c.Read(buf)
- if e != nil {
- t.Errorf("c.Read() = %v", e)
- }
-
- if _, e := c.Write(buf[:n]); e != nil {
- t.Errorf("c.Write() = %v", e)
- }
- })
- s.SetTransportProtocolHandler(udp.ProtocolNumber, fwd.HandlePacket)
-
- c2, err := DialUDP(s, &addr2, nil, ipv4.ProtocolNumber)
- if err != nil {
- t.Fatal("DialUDP(bind port 5):", err)
- }
-
- sent := "abc123"
- sendAddr := fullToUDPAddr(addr1)
- if n, err := c2.WriteTo([]byte(sent), sendAddr); err != nil || n != len(sent) {
- t.Errorf("c1.WriteTo(%q, %v) = %d, %v, want = %d, %v", sent, sendAddr, n, err, len(sent), nil)
- }
-
- buf := make([]byte, 256)
- n, recvAddr, err := c2.ReadFrom(buf)
- if err != nil || recvAddr.String() != sendAddr.String() {
- t.Errorf("c1.ReadFrom() = %d, %v, %v, want = %d, %v, %v", n, recvAddr, err, len(sent), sendAddr, nil)
- }
-}
-
-// TestDeadlineChange tests that changing the deadline affects currently blocked reads.
-func TestDeadlineChange(t *testing.T) {
- s, err := newLoopbackStack()
- if err != nil {
- t.Fatalf("newLoopbackStack() = %v", err)
- }
-
- addr := tcpip.FullAddress{NICID, tcpip.Address(net.IPv4(169, 254, 10, 1).To4()), 11211}
-
- s.AddAddress(NICID, ipv4.ProtocolNumber, addr.Addr)
-
- l, e := NewListener(s, addr, ipv4.ProtocolNumber)
- if e != nil {
- t.Fatalf("NewListener() = %v", e)
- }
- done := make(chan struct{})
- go func() {
- defer close(done)
- c, err := l.Accept()
- if err != nil {
- t.Fatalf("l.Accept() = %v", err)
- }
-
- c.SetDeadline(time.Now().Add(time.Minute))
- // Give c.Read() a chance to block before closing the connection.
- time.AfterFunc(time.Millisecond*50, func() {
- c.SetDeadline(time.Now().Add(time.Millisecond * 10))
- })
-
- buf := make([]byte, 256)
- n, err := c.Read(buf)
- got, ok := err.(*net.OpError)
- want := "i/o timeout"
- if n != 0 || !ok || got.Err == nil || got.Err.Error() != want {
- t.Errorf("c.Read() = (%d, %v), want (0, OpError(%s))", n, err, want)
- }
- }()
- sender, err := connect(s, addr)
- if err != nil {
- t.Fatalf("connect() = %v", err)
- }
-
- select {
- case <-done:
- case <-time.After(time.Millisecond * 500):
- t.Errorf("c.Read() didn't unblock")
- }
- sender.close()
-}
-
-func TestPacketConnTransfer(t *testing.T) {
- s, e := newLoopbackStack()
- if e != nil {
- t.Fatalf("newLoopbackStack() = %v", e)
- }
-
- ip1 := tcpip.Address(net.IPv4(169, 254, 10, 1).To4())
- addr1 := tcpip.FullAddress{NICID, ip1, 11211}
- s.AddAddress(NICID, ipv4.ProtocolNumber, ip1)
- ip2 := tcpip.Address(net.IPv4(169, 254, 10, 2).To4())
- addr2 := tcpip.FullAddress{NICID, ip2, 11311}
- s.AddAddress(NICID, ipv4.ProtocolNumber, ip2)
-
- c1, err := DialUDP(s, &addr1, nil, ipv4.ProtocolNumber)
- if err != nil {
- t.Fatal("DialUDP(bind port 4):", err)
- }
- c2, err := DialUDP(s, &addr2, nil, ipv4.ProtocolNumber)
- if err != nil {
- t.Fatal("DialUDP(bind port 5):", err)
- }
-
- c1.SetDeadline(time.Now().Add(time.Second))
- c2.SetDeadline(time.Now().Add(time.Second))
-
- sent := "abc123"
- sendAddr := fullToUDPAddr(addr2)
- if n, err := c1.WriteTo([]byte(sent), sendAddr); err != nil || n != len(sent) {
- t.Errorf("got c1.WriteTo(%q, %v) = %d, %v, want = %d, %v", sent, sendAddr, n, err, len(sent), nil)
- }
- recv := make([]byte, len(sent))
- n, recvAddr, err := c2.ReadFrom(recv)
- if err != nil || n != len(recv) {
- t.Errorf("got c2.ReadFrom() = %d, %v, want = %d, %v", n, err, len(recv), nil)
- }
-
- if recv := string(recv); recv != sent {
- t.Errorf("got recv = %q, want = %q", recv, sent)
- }
-
- if want := fullToUDPAddr(addr1); !reflect.DeepEqual(recvAddr, want) {
- t.Errorf("got recvAddr = %v, want = %v", recvAddr, want)
- }
-
- if err := c1.Close(); err != nil {
- t.Error("c1.Close():", err)
- }
- if err := c2.Close(); err != nil {
- t.Error("c2.Close():", err)
- }
-}
-
-func TestConnectedPacketConnTransfer(t *testing.T) {
- s, e := newLoopbackStack()
- if e != nil {
- t.Fatalf("newLoopbackStack() = %v", e)
- }
-
- ip := tcpip.Address(net.IPv4(169, 254, 10, 1).To4())
- addr := tcpip.FullAddress{NICID, ip, 11211}
- s.AddAddress(NICID, ipv4.ProtocolNumber, ip)
-
- c1, err := DialUDP(s, &addr, nil, ipv4.ProtocolNumber)
- if err != nil {
- t.Fatal("DialUDP(bind port 4):", err)
- }
- c2, err := DialUDP(s, nil, &addr, ipv4.ProtocolNumber)
- if err != nil {
- t.Fatal("DialUDP(bind port 5):", err)
- }
-
- c1.SetDeadline(time.Now().Add(time.Second))
- c2.SetDeadline(time.Now().Add(time.Second))
-
- sent := "abc123"
- if n, err := c2.Write([]byte(sent)); err != nil || n != len(sent) {
- t.Errorf("got c2.Write(%q) = %d, %v, want = %d, %v", sent, n, err, len(sent), nil)
- }
- recv := make([]byte, len(sent))
- n, err := c1.Read(recv)
- if err != nil || n != len(recv) {
- t.Errorf("got c1.Read() = %d, %v, want = %d, %v", n, err, len(recv), nil)
- }
-
- if recv := string(recv); recv != sent {
- t.Errorf("got recv = %q, want = %q", recv, sent)
- }
-
- if err := c1.Close(); err != nil {
- t.Error("c1.Close():", err)
- }
- if err := c2.Close(); err != nil {
- t.Error("c2.Close():", err)
- }
-}
-
-func makePipe() (c1, c2 net.Conn, stop func(), err error) {
- s, e := newLoopbackStack()
- if e != nil {
- return nil, nil, nil, fmt.Errorf("newLoopbackStack() = %v", e)
- }
-
- ip := tcpip.Address(net.IPv4(169, 254, 10, 1).To4())
- addr := tcpip.FullAddress{NICID, ip, 11211}
- s.AddAddress(NICID, ipv4.ProtocolNumber, ip)
-
- l, err := NewListener(s, addr, ipv4.ProtocolNumber)
- if err != nil {
- return nil, nil, nil, fmt.Errorf("NewListener: %v", err)
- }
-
- c1, err = DialTCP(s, addr, ipv4.ProtocolNumber)
- if err != nil {
- l.Close()
- return nil, nil, nil, fmt.Errorf("DialTCP: %v", err)
- }
-
- c2, err = l.Accept()
- if err != nil {
- l.Close()
- c1.Close()
- return nil, nil, nil, fmt.Errorf("l.Accept: %v", err)
- }
-
- stop = func() {
- c1.Close()
- c2.Close()
- }
-
- if err := l.Close(); err != nil {
- stop()
- return nil, nil, nil, fmt.Errorf("l.Close(): %v", err)
- }
-
- return c1, c2, stop, nil
-}
-
-func TestTCPConnTransfer(t *testing.T) {
- c1, c2, _, err := makePipe()
- if err != nil {
- t.Fatal(err)
- }
- defer func() {
- if err := c1.Close(); err != nil {
- t.Error("c1.Close():", err)
- }
- if err := c2.Close(); err != nil {
- t.Error("c2.Close():", err)
- }
- }()
-
- c1.SetDeadline(time.Now().Add(time.Second))
- c2.SetDeadline(time.Now().Add(time.Second))
-
- const sent = "abc123"
-
- tests := []struct {
- name string
- c1 net.Conn
- c2 net.Conn
- }{
- {"connected to accepted", c1, c2},
- {"accepted to connected", c2, c1},
- }
-
- for _, test := range tests {
- if n, err := test.c1.Write([]byte(sent)); err != nil || n != len(sent) {
- t.Errorf("%s: got test.c1.Write(%q) = %d, %v, want = %d, %v", test.name, sent, n, err, len(sent), nil)
- continue
- }
-
- recv := make([]byte, len(sent))
- n, err := test.c2.Read(recv)
- if err != nil || n != len(recv) {
- t.Errorf("%s: got test.c2.Read() = %d, %v, want = %d, %v", test.name, n, err, len(recv), nil)
- continue
- }
-
- if recv := string(recv); recv != sent {
- t.Errorf("%s: got recv = %q, want = %q", test.name, recv, sent)
- }
- }
-}
-
-func TestTCPDialError(t *testing.T) {
- s, e := newLoopbackStack()
- if e != nil {
- t.Fatalf("newLoopbackStack() = %v", e)
- }
-
- ip := tcpip.Address(net.IPv4(169, 254, 10, 1).To4())
- addr := tcpip.FullAddress{NICID, ip, 11211}
-
- _, err := DialTCP(s, addr, ipv4.ProtocolNumber)
- got, ok := err.(*net.OpError)
- want := tcpip.ErrNoRoute
- if !ok || got.Err.Error() != want.String() {
- t.Errorf("Got DialTCP() = %v, want = %v", err, tcpip.ErrNoRoute)
- }
-}
-
-func TestDialContextTCPCanceled(t *testing.T) {
- s, err := newLoopbackStack()
- if err != nil {
- t.Fatalf("newLoopbackStack() = %v", err)
- }
-
- addr := tcpip.FullAddress{NICID, tcpip.Address(net.IPv4(169, 254, 10, 1).To4()), 11211}
- s.AddAddress(NICID, ipv4.ProtocolNumber, addr.Addr)
-
- ctx := context.Background()
- ctx, cancel := context.WithCancel(ctx)
- cancel()
-
- if _, err := DialContextTCP(ctx, s, addr, ipv4.ProtocolNumber); err != context.Canceled {
- t.Errorf("got DialContextTCP(...) = %v, want = %v", err, context.Canceled)
- }
-}
-
-func TestDialContextTCPTimeout(t *testing.T) {
- s, err := newLoopbackStack()
- if err != nil {
- t.Fatalf("newLoopbackStack() = %v", err)
- }
-
- addr := tcpip.FullAddress{NICID, tcpip.Address(net.IPv4(169, 254, 10, 1).To4()), 11211}
- s.AddAddress(NICID, ipv4.ProtocolNumber, addr.Addr)
-
- fwd := tcp.NewForwarder(s, 30000, 10, func(r *tcp.ForwarderRequest) {
- time.Sleep(time.Second)
- r.Complete(true)
- })
- s.SetTransportProtocolHandler(tcp.ProtocolNumber, fwd.HandlePacket)
-
- ctx := context.Background()
- ctx, cancel := context.WithDeadline(ctx, time.Now().Add(100*time.Millisecond))
- defer cancel()
-
- if _, err := DialContextTCP(ctx, s, addr, ipv4.ProtocolNumber); err != context.DeadlineExceeded {
- t.Errorf("got DialContextTCP(...) = %v, want = %v", err, context.DeadlineExceeded)
- }
-}
-
-func TestNetTest(t *testing.T) {
- nettest.TestConn(t, makePipe)
-}
diff --git a/pkg/tcpip/buffer/BUILD b/pkg/tcpip/buffer/BUILD
deleted file mode 100644
index b4e8d6810..000000000
--- a/pkg/tcpip/buffer/BUILD
+++ /dev/null
@@ -1,22 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "buffer",
- srcs = [
- "prependable.go",
- "view.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/buffer",
- visibility = ["//visibility:public"],
-)
-
-go_test(
- name = "buffer_test",
- size = "small",
- srcs = ["view_test.go"],
- embed = [":buffer"],
-)
diff --git a/pkg/tcpip/buffer/buffer_state_autogen.go b/pkg/tcpip/buffer/buffer_state_autogen.go
new file mode 100755
index 000000000..41a74ea97
--- /dev/null
+++ b/pkg/tcpip/buffer/buffer_state_autogen.go
@@ -0,0 +1,24 @@
+// automatically generated by stateify.
+
+package buffer
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *VectorisedView) beforeSave() {}
+func (x *VectorisedView) save(m state.Map) {
+ x.beforeSave()
+ m.Save("views", &x.views)
+ m.Save("size", &x.size)
+}
+
+func (x *VectorisedView) afterLoad() {}
+func (x *VectorisedView) load(m state.Map) {
+ m.Load("views", &x.views)
+ m.Load("size", &x.size)
+}
+
+func init() {
+ state.Register("buffer.VectorisedView", (*VectorisedView)(nil), state.Fns{Save: (*VectorisedView).save, Load: (*VectorisedView).load})
+}
diff --git a/pkg/tcpip/buffer/view_test.go b/pkg/tcpip/buffer/view_test.go
deleted file mode 100644
index ebc3a17b7..000000000
--- a/pkg/tcpip/buffer/view_test.go
+++ /dev/null
@@ -1,235 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package buffer_test contains tests for the VectorisedView type.
-package buffer
-
-import (
- "reflect"
- "testing"
-)
-
-// copy returns a deep-copy of the vectorised view.
-func (vv VectorisedView) copy() VectorisedView {
- uu := VectorisedView{
- views: make([]View, 0, len(vv.views)),
- size: vv.size,
- }
- for _, v := range vv.views {
- uu.views = append(uu.views, append(View(nil), v...))
- }
- return uu
-}
-
-// vv is an helper to build VectorisedView from different strings.
-func vv(size int, pieces ...string) VectorisedView {
- views := make([]View, len(pieces))
- for i, p := range pieces {
- views[i] = []byte(p)
- }
-
- return NewVectorisedView(size, views)
-}
-
-var capLengthTestCases = []struct {
- comment string
- in VectorisedView
- length int
- want VectorisedView
-}{
- {
- comment: "Simple case",
- in: vv(2, "12"),
- length: 1,
- want: vv(1, "1"),
- },
- {
- comment: "Case spanning across two Views",
- in: vv(4, "123", "4"),
- length: 2,
- want: vv(2, "12"),
- },
- {
- comment: "Corner case with negative length",
- in: vv(1, "1"),
- length: -1,
- want: vv(0),
- },
- {
- comment: "Corner case with length = 0",
- in: vv(3, "12", "3"),
- length: 0,
- want: vv(0),
- },
- {
- comment: "Corner case with length = size",
- in: vv(1, "1"),
- length: 1,
- want: vv(1, "1"),
- },
- {
- comment: "Corner case with length > size",
- in: vv(1, "1"),
- length: 2,
- want: vv(1, "1"),
- },
-}
-
-func TestCapLength(t *testing.T) {
- for _, c := range capLengthTestCases {
- orig := c.in.copy()
- c.in.CapLength(c.length)
- if !reflect.DeepEqual(c.in, c.want) {
- t.Errorf("Test \"%s\" failed when calling CapLength(%d) on %v. Got %v. Want %v",
- c.comment, c.length, orig, c.in, c.want)
- }
- }
-}
-
-var trimFrontTestCases = []struct {
- comment string
- in VectorisedView
- count int
- want VectorisedView
-}{
- {
- comment: "Simple case",
- in: vv(2, "12"),
- count: 1,
- want: vv(1, "2"),
- },
- {
- comment: "Case where we trim an entire View",
- in: vv(2, "1", "2"),
- count: 1,
- want: vv(1, "2"),
- },
- {
- comment: "Case spanning across two Views",
- in: vv(3, "1", "23"),
- count: 2,
- want: vv(1, "3"),
- },
- {
- comment: "Corner case with negative count",
- in: vv(1, "1"),
- count: -1,
- want: vv(1, "1"),
- },
- {
- comment: " Corner case with count = 0",
- in: vv(1, "1"),
- count: 0,
- want: vv(1, "1"),
- },
- {
- comment: "Corner case with count = size",
- in: vv(1, "1"),
- count: 1,
- want: vv(0),
- },
- {
- comment: "Corner case with count > size",
- in: vv(1, "1"),
- count: 2,
- want: vv(0),
- },
-}
-
-func TestTrimFront(t *testing.T) {
- for _, c := range trimFrontTestCases {
- orig := c.in.copy()
- c.in.TrimFront(c.count)
- if !reflect.DeepEqual(c.in, c.want) {
- t.Errorf("Test \"%s\" failed when calling TrimFront(%d) on %v. Got %v. Want %v",
- c.comment, c.count, orig, c.in, c.want)
- }
- }
-}
-
-var toViewCases = []struct {
- comment string
- in VectorisedView
- want View
-}{
- {
- comment: "Simple case",
- in: vv(2, "12"),
- want: []byte("12"),
- },
- {
- comment: "Case with multiple views",
- in: vv(2, "1", "2"),
- want: []byte("12"),
- },
- {
- comment: "Empty case",
- in: vv(0),
- want: []byte(""),
- },
-}
-
-func TestToView(t *testing.T) {
- for _, c := range toViewCases {
- got := c.in.ToView()
- if !reflect.DeepEqual(got, c.want) {
- t.Errorf("Test \"%s\" failed when calling ToView() on %v. Got %v. Want %v",
- c.comment, c.in, got, c.want)
- }
- }
-}
-
-var toCloneCases = []struct {
- comment string
- inView VectorisedView
- inBuffer []View
-}{
- {
- comment: "Simple case",
- inView: vv(1, "1"),
- inBuffer: make([]View, 1),
- },
- {
- comment: "Case with multiple views",
- inView: vv(2, "1", "2"),
- inBuffer: make([]View, 2),
- },
- {
- comment: "Case with buffer too small",
- inView: vv(2, "1", "2"),
- inBuffer: make([]View, 1),
- },
- {
- comment: "Case with buffer larger than needed",
- inView: vv(1, "1"),
- inBuffer: make([]View, 2),
- },
- {
- comment: "Case with nil buffer",
- inView: vv(1, "1"),
- inBuffer: nil,
- },
-}
-
-func TestToClone(t *testing.T) {
- for _, c := range toCloneCases {
- t.Run(c.comment, func(t *testing.T) {
- got := c.inView.Clone(c.inBuffer)
- if !reflect.DeepEqual(got, c.inView) {
- t.Fatalf("got (%+v).Clone(%+v) = %+v, want = %+v",
- c.inView, c.inBuffer, got, c.inView)
- }
- })
- }
-}
diff --git a/pkg/tcpip/checker/BUILD b/pkg/tcpip/checker/BUILD
deleted file mode 100644
index 4cecfb989..000000000
--- a/pkg/tcpip/checker/BUILD
+++ /dev/null
@@ -1,16 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "checker",
- testonly = 1,
- srcs = ["checker.go"],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/checker",
- visibility = ["//visibility:public"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/header",
- "//pkg/tcpip/seqnum",
- ],
-)
diff --git a/pkg/tcpip/checker/checker.go b/pkg/tcpip/checker/checker.go
deleted file mode 100644
index 096ad71ab..000000000
--- a/pkg/tcpip/checker/checker.go
+++ /dev/null
@@ -1,688 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package checker provides helper functions to check networking packets for
-// validity.
-package checker
-
-import (
- "encoding/binary"
- "reflect"
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/seqnum"
-)
-
-// NetworkChecker is a function to check a property of a network packet.
-type NetworkChecker func(*testing.T, []header.Network)
-
-// TransportChecker is a function to check a property of a transport packet.
-type TransportChecker func(*testing.T, header.Transport)
-
-// IPv4 checks the validity and properties of the given IPv4 packet. It is
-// expected to be used in conjunction with other network checkers for specific
-// properties. For example, to check the source and destination address, one
-// would call:
-//
-// checker.IPv4(t, b, checker.SrcAddr(x), checker.DstAddr(y))
-func IPv4(t *testing.T, b []byte, checkers ...NetworkChecker) {
- t.Helper()
-
- ipv4 := header.IPv4(b)
-
- if !ipv4.IsValid(len(b)) {
- t.Error("Not a valid IPv4 packet")
- }
-
- xsum := ipv4.CalculateChecksum()
- if xsum != 0 && xsum != 0xffff {
- t.Errorf("Bad checksum: 0x%x, checksum in packet: 0x%x", xsum, ipv4.Checksum())
- }
-
- for _, f := range checkers {
- f(t, []header.Network{ipv4})
- }
- if t.Failed() {
- t.FailNow()
- }
-}
-
-// IPv6 checks the validity and properties of the given IPv6 packet. The usage
-// is similar to IPv4.
-func IPv6(t *testing.T, b []byte, checkers ...NetworkChecker) {
- t.Helper()
-
- ipv6 := header.IPv6(b)
- if !ipv6.IsValid(len(b)) {
- t.Error("Not a valid IPv6 packet")
- }
-
- for _, f := range checkers {
- f(t, []header.Network{ipv6})
- }
- if t.Failed() {
- t.FailNow()
- }
-}
-
-// SrcAddr creates a checker that checks the source address.
-func SrcAddr(addr tcpip.Address) NetworkChecker {
- return func(t *testing.T, h []header.Network) {
- t.Helper()
-
- if a := h[0].SourceAddress(); a != addr {
- t.Errorf("Bad source address, got %v, want %v", a, addr)
- }
- }
-}
-
-// DstAddr creates a checker that checks the destination address.
-func DstAddr(addr tcpip.Address) NetworkChecker {
- return func(t *testing.T, h []header.Network) {
- t.Helper()
-
- if a := h[0].DestinationAddress(); a != addr {
- t.Errorf("Bad destination address, got %v, want %v", a, addr)
- }
- }
-}
-
-// TTL creates a checker that checks the TTL (ipv4) or HopLimit (ipv6).
-func TTL(ttl uint8) NetworkChecker {
- return func(t *testing.T, h []header.Network) {
- var v uint8
- switch ip := h[0].(type) {
- case header.IPv4:
- v = ip.TTL()
- case header.IPv6:
- v = ip.HopLimit()
- }
- if v != ttl {
- t.Fatalf("Bad TTL, got %v, want %v", v, ttl)
- }
- }
-}
-
-// PayloadLen creates a checker that checks the payload length.
-func PayloadLen(plen int) NetworkChecker {
- return func(t *testing.T, h []header.Network) {
- t.Helper()
-
- if l := len(h[0].Payload()); l != plen {
- t.Errorf("Bad payload length, got %v, want %v", l, plen)
- }
- }
-}
-
-// FragmentOffset creates a checker that checks the FragmentOffset field.
-func FragmentOffset(offset uint16) NetworkChecker {
- return func(t *testing.T, h []header.Network) {
- t.Helper()
-
- // We only do this of IPv4 for now.
- switch ip := h[0].(type) {
- case header.IPv4:
- if v := ip.FragmentOffset(); v != offset {
- t.Errorf("Bad fragment offset, got %v, want %v", v, offset)
- }
- }
- }
-}
-
-// FragmentFlags creates a checker that checks the fragment flags field.
-func FragmentFlags(flags uint8) NetworkChecker {
- return func(t *testing.T, h []header.Network) {
- t.Helper()
-
- // We only do this of IPv4 for now.
- switch ip := h[0].(type) {
- case header.IPv4:
- if v := ip.Flags(); v != flags {
- t.Errorf("Bad fragment offset, got %v, want %v", v, flags)
- }
- }
- }
-}
-
-// TOS creates a checker that checks the TOS field.
-func TOS(tos uint8, label uint32) NetworkChecker {
- return func(t *testing.T, h []header.Network) {
- t.Helper()
-
- if v, l := h[0].TOS(); v != tos || l != label {
- t.Errorf("Bad TOS, got (%v, %v), want (%v,%v)", v, l, tos, label)
- }
- }
-}
-
-// Raw creates a checker that checks the bytes of payload.
-// The checker always checks the payload of the last network header.
-// For instance, in case of IPv6 fragments, the payload that will be checked
-// is the one containing the actual data that the packet is carrying, without
-// the bytes added by the IPv6 fragmentation.
-func Raw(want []byte) NetworkChecker {
- return func(t *testing.T, h []header.Network) {
- t.Helper()
-
- if got := h[len(h)-1].Payload(); !reflect.DeepEqual(got, want) {
- t.Errorf("Wrong payload, got %v, want %v", got, want)
- }
- }
-}
-
-// IPv6Fragment creates a checker that validates an IPv6 fragment.
-func IPv6Fragment(checkers ...NetworkChecker) NetworkChecker {
- return func(t *testing.T, h []header.Network) {
- t.Helper()
-
- if p := h[0].TransportProtocol(); p != header.IPv6FragmentHeader {
- t.Errorf("Bad protocol, got %v, want %v", p, header.UDPProtocolNumber)
- }
-
- ipv6Frag := header.IPv6Fragment(h[0].Payload())
- if !ipv6Frag.IsValid() {
- t.Error("Not a valid IPv6 fragment")
- }
-
- for _, f := range checkers {
- f(t, []header.Network{h[0], ipv6Frag})
- }
- if t.Failed() {
- t.FailNow()
- }
- }
-}
-
-// TCP creates a checker that checks that the transport protocol is TCP and
-// potentially additional transport header fields.
-func TCP(checkers ...TransportChecker) NetworkChecker {
- return func(t *testing.T, h []header.Network) {
- t.Helper()
-
- first := h[0]
- last := h[len(h)-1]
-
- if p := last.TransportProtocol(); p != header.TCPProtocolNumber {
- t.Errorf("Bad protocol, got %v, want %v", p, header.TCPProtocolNumber)
- }
-
- // Verify the checksum.
- tcp := header.TCP(last.Payload())
- l := uint16(len(tcp))
-
- xsum := header.Checksum([]byte(first.SourceAddress()), 0)
- xsum = header.Checksum([]byte(first.DestinationAddress()), xsum)
- xsum = header.Checksum([]byte{0, byte(last.TransportProtocol())}, xsum)
- xsum = header.Checksum([]byte{byte(l >> 8), byte(l)}, xsum)
- xsum = header.Checksum(tcp, xsum)
-
- if xsum != 0 && xsum != 0xffff {
- t.Errorf("Bad checksum: 0x%x, checksum in segment: 0x%x", xsum, tcp.Checksum())
- }
-
- // Run the transport checkers.
- for _, f := range checkers {
- f(t, tcp)
- }
- if t.Failed() {
- t.FailNow()
- }
- }
-}
-
-// UDP creates a checker that checks that the transport protocol is UDP and
-// potentially additional transport header fields.
-func UDP(checkers ...TransportChecker) NetworkChecker {
- return func(t *testing.T, h []header.Network) {
- t.Helper()
-
- last := h[len(h)-1]
-
- if p := last.TransportProtocol(); p != header.UDPProtocolNumber {
- t.Errorf("Bad protocol, got %v, want %v", p, header.UDPProtocolNumber)
- }
-
- udp := header.UDP(last.Payload())
- for _, f := range checkers {
- f(t, udp)
- }
- if t.Failed() {
- t.FailNow()
- }
- }
-}
-
-// SrcPort creates a checker that checks the source port.
-func SrcPort(port uint16) TransportChecker {
- return func(t *testing.T, h header.Transport) {
- t.Helper()
-
- if p := h.SourcePort(); p != port {
- t.Errorf("Bad source port, got %v, want %v", p, port)
- }
- }
-}
-
-// DstPort creates a checker that checks the destination port.
-func DstPort(port uint16) TransportChecker {
- return func(t *testing.T, h header.Transport) {
- if p := h.DestinationPort(); p != port {
- t.Errorf("Bad destination port, got %v, want %v", p, port)
- }
- }
-}
-
-// SeqNum creates a checker that checks the sequence number.
-func SeqNum(seq uint32) TransportChecker {
- return func(t *testing.T, h header.Transport) {
- t.Helper()
-
- tcp, ok := h.(header.TCP)
- if !ok {
- return
- }
-
- if s := tcp.SequenceNumber(); s != seq {
- t.Errorf("Bad sequence number, got %v, want %v", s, seq)
- }
- }
-}
-
-// AckNum creates a checker that checks the ack number.
-func AckNum(seq uint32) TransportChecker {
- return func(t *testing.T, h header.Transport) {
- t.Helper()
- tcp, ok := h.(header.TCP)
- if !ok {
- return
- }
-
- if s := tcp.AckNumber(); s != seq {
- t.Errorf("Bad ack number, got %v, want %v", s, seq)
- }
- }
-}
-
-// Window creates a checker that checks the tcp window.
-func Window(window uint16) TransportChecker {
- return func(t *testing.T, h header.Transport) {
- tcp, ok := h.(header.TCP)
- if !ok {
- return
- }
-
- if w := tcp.WindowSize(); w != window {
- t.Errorf("Bad window, got 0x%x, want 0x%x", w, window)
- }
- }
-}
-
-// TCPFlags creates a checker that checks the tcp flags.
-func TCPFlags(flags uint8) TransportChecker {
- return func(t *testing.T, h header.Transport) {
- t.Helper()
-
- tcp, ok := h.(header.TCP)
- if !ok {
- return
- }
-
- if f := tcp.Flags(); f != flags {
- t.Errorf("Bad flags, got 0x%x, want 0x%x", f, flags)
- }
- }
-}
-
-// TCPFlagsMatch creates a checker that checks that the tcp flags, masked by the
-// given mask, match the supplied flags.
-func TCPFlagsMatch(flags, mask uint8) TransportChecker {
- return func(t *testing.T, h header.Transport) {
- tcp, ok := h.(header.TCP)
- if !ok {
- return
- }
-
- if f := tcp.Flags(); (f & mask) != (flags & mask) {
- t.Errorf("Bad masked flags, got 0x%x, want 0x%x, mask 0x%x", f, flags, mask)
- }
- }
-}
-
-// TCPSynOptions creates a checker that checks the presence of TCP options in
-// SYN segments.
-//
-// If wndscale is negative, the window scale option must not be present.
-func TCPSynOptions(wantOpts header.TCPSynOptions) TransportChecker {
- return func(t *testing.T, h header.Transport) {
- tcp, ok := h.(header.TCP)
- if !ok {
- return
- }
- opts := tcp.Options()
- limit := len(opts)
- foundMSS := false
- foundWS := false
- foundTS := false
- foundSACKPermitted := false
- tsVal := uint32(0)
- tsEcr := uint32(0)
- for i := 0; i < limit; {
- switch opts[i] {
- case header.TCPOptionEOL:
- i = limit
- case header.TCPOptionNOP:
- i++
- case header.TCPOptionMSS:
- v := uint16(opts[i+2])<<8 | uint16(opts[i+3])
- if wantOpts.MSS != v {
- t.Errorf("Bad MSS: got %v, want %v", v, wantOpts.MSS)
- }
- foundMSS = true
- i += 4
- case header.TCPOptionWS:
- if wantOpts.WS < 0 {
- t.Error("WS present when it shouldn't be")
- }
- v := int(opts[i+2])
- if v != wantOpts.WS {
- t.Errorf("Bad WS: got %v, want %v", v, wantOpts.WS)
- }
- foundWS = true
- i += 3
- case header.TCPOptionTS:
- if i+9 >= limit {
- t.Errorf("TS Option truncated , option is only: %d bytes, want 10", limit-i)
- }
- if opts[i+1] != 10 {
- t.Errorf("Bad length %d for TS option, limit: %d", opts[i+1], limit)
- }
- tsVal = binary.BigEndian.Uint32(opts[i+2:])
- tsEcr = uint32(0)
- if tcp.Flags()&header.TCPFlagAck != 0 {
- // If the syn is an SYN-ACK then read
- // the tsEcr value as well.
- tsEcr = binary.BigEndian.Uint32(opts[i+6:])
- }
- foundTS = true
- i += 10
- case header.TCPOptionSACKPermitted:
- if i+1 >= limit {
- t.Errorf("SACKPermitted option truncated, option is only : %d bytes, want 2", limit-i)
- }
- if opts[i+1] != 2 {
- t.Errorf("Bad length %d for SACKPermitted option, limit: %d", opts[i+1], limit)
- }
- foundSACKPermitted = true
- i += 2
-
- default:
- i += int(opts[i+1])
- }
- }
-
- if !foundMSS {
- t.Errorf("MSS option not found. Options: %x", opts)
- }
-
- if !foundWS && wantOpts.WS >= 0 {
- t.Errorf("WS option not found. Options: %x", opts)
- }
- if wantOpts.TS && !foundTS {
- t.Errorf("TS option not found. Options: %x", opts)
- }
- if foundTS && tsVal == 0 {
- t.Error("TS option specified but the timestamp value is zero")
- }
- if foundTS && tsEcr == 0 && wantOpts.TSEcr != 0 {
- t.Errorf("TS option specified but TSEcr is incorrect: got %d, want: %d", tsEcr, wantOpts.TSEcr)
- }
- if wantOpts.SACKPermitted && !foundSACKPermitted {
- t.Errorf("SACKPermitted option not found. Options: %x", opts)
- }
- }
-}
-
-// TCPTimestampChecker creates a checker that validates that a TCP segment has a
-// TCP Timestamp option if wantTS is true, it also compares the wantTSVal and
-// wantTSEcr values with those in the TCP segment (if present).
-//
-// If wantTSVal or wantTSEcr is zero then the corresponding comparison is
-// skipped.
-func TCPTimestampChecker(wantTS bool, wantTSVal uint32, wantTSEcr uint32) TransportChecker {
- return func(t *testing.T, h header.Transport) {
- tcp, ok := h.(header.TCP)
- if !ok {
- return
- }
- opts := []byte(tcp.Options())
- limit := len(opts)
- foundTS := false
- tsVal := uint32(0)
- tsEcr := uint32(0)
- for i := 0; i < limit; {
- switch opts[i] {
- case header.TCPOptionEOL:
- i = limit
- case header.TCPOptionNOP:
- i++
- case header.TCPOptionTS:
- if i+9 >= limit {
- t.Errorf("TS option found, but option is truncated, option length: %d, want 10 bytes", limit-i)
- }
- if opts[i+1] != 10 {
- t.Errorf("TS option found, but bad length specified: %d, want: 10", opts[i+1])
- }
- tsVal = binary.BigEndian.Uint32(opts[i+2:])
- tsEcr = binary.BigEndian.Uint32(opts[i+6:])
- foundTS = true
- i += 10
- default:
- // We don't recognize this option, just skip over it.
- if i+2 > limit {
- return
- }
- l := int(opts[i+1])
- if i < 2 || i+l > limit {
- return
- }
- i += l
- }
- }
-
- if wantTS != foundTS {
- t.Errorf("TS Option mismatch: got TS= %v, want TS= %v", foundTS, wantTS)
- }
- if wantTS && wantTSVal != 0 && wantTSVal != tsVal {
- t.Errorf("Timestamp value is incorrect: got: %d, want: %d", tsVal, wantTSVal)
- }
- if wantTS && wantTSEcr != 0 && tsEcr != wantTSEcr {
- t.Errorf("Timestamp Echo Reply is incorrect: got: %d, want: %d", tsEcr, wantTSEcr)
- }
- }
-}
-
-// TCPNoSACKBlockChecker creates a checker that verifies that the segment does not
-// contain any SACK blocks in the TCP options.
-func TCPNoSACKBlockChecker() TransportChecker {
- return TCPSACKBlockChecker(nil)
-}
-
-// TCPSACKBlockChecker creates a checker that verifies that the segment does
-// contain the specified SACK blocks in the TCP options.
-func TCPSACKBlockChecker(sackBlocks []header.SACKBlock) TransportChecker {
- return func(t *testing.T, h header.Transport) {
- t.Helper()
- tcp, ok := h.(header.TCP)
- if !ok {
- return
- }
- var gotSACKBlocks []header.SACKBlock
-
- opts := []byte(tcp.Options())
- limit := len(opts)
- for i := 0; i < limit; {
- switch opts[i] {
- case header.TCPOptionEOL:
- i = limit
- case header.TCPOptionNOP:
- i++
- case header.TCPOptionSACK:
- if i+2 > limit {
- // Malformed SACK block.
- t.Errorf("malformed SACK option in options: %v", opts)
- }
- sackOptionLen := int(opts[i+1])
- if i+sackOptionLen > limit || (sackOptionLen-2)%8 != 0 {
- // Malformed SACK block.
- t.Errorf("malformed SACK option length in options: %v", opts)
- }
- numBlocks := sackOptionLen / 8
- for j := 0; j < numBlocks; j++ {
- start := binary.BigEndian.Uint32(opts[i+2+j*8:])
- end := binary.BigEndian.Uint32(opts[i+2+j*8+4:])
- gotSACKBlocks = append(gotSACKBlocks, header.SACKBlock{
- Start: seqnum.Value(start),
- End: seqnum.Value(end),
- })
- }
- i += sackOptionLen
- default:
- // We don't recognize this option, just skip over it.
- if i+2 > limit {
- break
- }
- l := int(opts[i+1])
- if l < 2 || i+l > limit {
- break
- }
- i += l
- }
- }
-
- if !reflect.DeepEqual(gotSACKBlocks, sackBlocks) {
- t.Errorf("SACKBlocks are not equal, got: %v, want: %v", gotSACKBlocks, sackBlocks)
- }
- }
-}
-
-// Payload creates a checker that checks the payload.
-func Payload(want []byte) TransportChecker {
- return func(t *testing.T, h header.Transport) {
- if got := h.Payload(); !reflect.DeepEqual(got, want) {
- t.Errorf("Wrong payload, got %v, want %v", got, want)
- }
- }
-}
-
-// ICMPv4 creates a checker that checks that the transport protocol is ICMPv4 and
-// potentially additional ICMPv4 header fields.
-func ICMPv4(checkers ...TransportChecker) NetworkChecker {
- return func(t *testing.T, h []header.Network) {
- t.Helper()
-
- last := h[len(h)-1]
-
- if p := last.TransportProtocol(); p != header.ICMPv4ProtocolNumber {
- t.Fatalf("Bad protocol, got %d, want %d", p, header.ICMPv4ProtocolNumber)
- }
-
- icmp := header.ICMPv4(last.Payload())
- for _, f := range checkers {
- f(t, icmp)
- }
- if t.Failed() {
- t.FailNow()
- }
- }
-}
-
-// ICMPv4Type creates a checker that checks the ICMPv4 Type field.
-func ICMPv4Type(want header.ICMPv4Type) TransportChecker {
- return func(t *testing.T, h header.Transport) {
- t.Helper()
- icmpv4, ok := h.(header.ICMPv4)
- if !ok {
- t.Fatalf("unexpected transport header passed to checker got: %+v, want: header.ICMPv4", h)
- }
- if got := icmpv4.Type(); got != want {
- t.Fatalf("unexpected icmp type got: %d, want: %d", got, want)
- }
- }
-}
-
-// ICMPv4Code creates a checker that checks the ICMPv4 Code field.
-func ICMPv4Code(want byte) TransportChecker {
- return func(t *testing.T, h header.Transport) {
- t.Helper()
- icmpv4, ok := h.(header.ICMPv4)
- if !ok {
- t.Fatalf("unexpected transport header passed to checker got: %+v, want: header.ICMPv4", h)
- }
- if got := icmpv4.Code(); got != want {
- t.Fatalf("unexpected ICMP code got: %d, want: %d", got, want)
- }
- }
-}
-
-// ICMPv6 creates a checker that checks that the transport protocol is ICMPv6 and
-// potentially additional ICMPv6 header fields.
-func ICMPv6(checkers ...TransportChecker) NetworkChecker {
- return func(t *testing.T, h []header.Network) {
- t.Helper()
-
- last := h[len(h)-1]
-
- if p := last.TransportProtocol(); p != header.ICMPv6ProtocolNumber {
- t.Fatalf("Bad protocol, got %d, want %d", p, header.ICMPv6ProtocolNumber)
- }
-
- icmp := header.ICMPv6(last.Payload())
- for _, f := range checkers {
- f(t, icmp)
- }
- if t.Failed() {
- t.FailNow()
- }
- }
-}
-
-// ICMPv6Type creates a checker that checks the ICMPv6 Type field.
-func ICMPv6Type(want header.ICMPv6Type) TransportChecker {
- return func(t *testing.T, h header.Transport) {
- t.Helper()
- icmpv6, ok := h.(header.ICMPv6)
- if !ok {
- t.Fatalf("unexpected transport header passed to checker got: %+v, want: header.ICMPv6", h)
- }
- if got := icmpv6.Type(); got != want {
- t.Fatalf("unexpected icmp type got: %d, want: %d", got, want)
- }
- }
-}
-
-// ICMPv6Code creates a checker that checks the ICMPv6 Code field.
-func ICMPv6Code(want byte) TransportChecker {
- return func(t *testing.T, h header.Transport) {
- t.Helper()
- icmpv6, ok := h.(header.ICMPv6)
- if !ok {
- t.Fatalf("unexpected transport header passed to checker got: %+v, want: header.ICMPv6", h)
- }
- if got := icmpv6.Code(); got != want {
- t.Fatalf("unexpected ICMP code got: %d, want: %d", got, want)
- }
- }
-}
diff --git a/pkg/tcpip/hash/jenkins/BUILD b/pkg/tcpip/hash/jenkins/BUILD
deleted file mode 100644
index 0c5c20cea..000000000
--- a/pkg/tcpip/hash/jenkins/BUILD
+++ /dev/null
@@ -1,22 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "jenkins",
- srcs = ["jenkins.go"],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/hash/jenkins",
- visibility = [
- "//visibility:public",
- ],
-)
-
-go_test(
- name = "jenkins_test",
- size = "small",
- srcs = [
- "jenkins_test.go",
- ],
- embed = [":jenkins"],
-)
diff --git a/pkg/tcpip/hash/jenkins/jenkins_state_autogen.go b/pkg/tcpip/hash/jenkins/jenkins_state_autogen.go
new file mode 100755
index 000000000..310f0ee6d
--- /dev/null
+++ b/pkg/tcpip/hash/jenkins/jenkins_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package jenkins
+
diff --git a/pkg/tcpip/hash/jenkins/jenkins_test.go b/pkg/tcpip/hash/jenkins/jenkins_test.go
deleted file mode 100644
index 4c78b5808..000000000
--- a/pkg/tcpip/hash/jenkins/jenkins_test.go
+++ /dev/null
@@ -1,176 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-package jenkins
-
-import (
- "bytes"
- "encoding/binary"
- "hash"
- "hash/fnv"
- "math"
- "testing"
-)
-
-func TestGolden32(t *testing.T) {
- var golden32 = []struct {
- out []byte
- in string
- }{
- {[]byte{0x00, 0x00, 0x00, 0x00}, ""},
- {[]byte{0xca, 0x2e, 0x94, 0x42}, "a"},
- {[]byte{0x45, 0xe6, 0x1e, 0x58}, "ab"},
- {[]byte{0xed, 0x13, 0x1f, 0x5b}, "abc"},
- }
-
- hash := New32()
-
- for _, g := range golden32 {
- hash.Reset()
- done, error := hash.Write([]byte(g.in))
- if error != nil {
- t.Fatalf("write error: %s", error)
- }
- if done != len(g.in) {
- t.Fatalf("wrote only %d out of %d bytes", done, len(g.in))
- }
- if actual := hash.Sum(nil); !bytes.Equal(g.out, actual) {
- t.Errorf("hash(%q) = 0x%x want 0x%x", g.in, actual, g.out)
- }
- }
-}
-
-func TestIntegrity32(t *testing.T) {
- data := []byte{'1', '2', 3, 4, 5}
-
- h := New32()
- h.Write(data)
- sum := h.Sum(nil)
-
- if size := h.Size(); size != len(sum) {
- t.Fatalf("Size()=%d but len(Sum())=%d", size, len(sum))
- }
-
- if a := h.Sum(nil); !bytes.Equal(sum, a) {
- t.Fatalf("first Sum()=0x%x, second Sum()=0x%x", sum, a)
- }
-
- h.Reset()
- h.Write(data)
- if a := h.Sum(nil); !bytes.Equal(sum, a) {
- t.Fatalf("Sum()=0x%x, but after Reset() Sum()=0x%x", sum, a)
- }
-
- h.Reset()
- h.Write(data[:2])
- h.Write(data[2:])
- if a := h.Sum(nil); !bytes.Equal(sum, a) {
- t.Fatalf("Sum()=0x%x, but with partial writes, Sum()=0x%x", sum, a)
- }
-
- sum32 := h.(hash.Hash32).Sum32()
- if sum32 != binary.BigEndian.Uint32(sum) {
- t.Fatalf("Sum()=0x%x, but Sum32()=0x%x", sum, sum32)
- }
-}
-
-func BenchmarkJenkins32KB(b *testing.B) {
- h := New32()
-
- b.SetBytes(1024)
- data := make([]byte, 1024)
- for i := range data {
- data[i] = byte(i)
- }
- in := make([]byte, 0, h.Size())
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- h.Reset()
- h.Write(data)
- h.Sum(in)
- }
-}
-
-func BenchmarkFnv32(b *testing.B) {
- arr := make([]int64, 1000)
- for i := 0; i < b.N; i++ {
- var payload [8]byte
- binary.BigEndian.PutUint32(payload[:4], uint32(i))
- binary.BigEndian.PutUint32(payload[4:], uint32(i))
-
- h := fnv.New32()
- h.Write(payload[:])
- idx := int(h.Sum32()) % len(arr)
- arr[idx]++
- }
- b.StopTimer()
- c := 0
- if b.N > 1000000 {
- for i := 0; i < len(arr)-1; i++ {
- if math.Abs(float64(arr[i]-arr[i+1]))/float64(arr[i]) > float64(0.1) {
- if c == 0 {
- b.Logf("i %d val[i] %d val[i+1] %d b.N %b\n", i, arr[i], arr[i+1], b.N)
- }
- c++
- }
- }
- if c > 0 {
- b.Logf("Unbalanced buckets: %d", c)
- }
- }
-}
-
-func BenchmarkSum32(b *testing.B) {
- arr := make([]int64, 1000)
- for i := 0; i < b.N; i++ {
- var payload [8]byte
- binary.BigEndian.PutUint32(payload[:4], uint32(i))
- binary.BigEndian.PutUint32(payload[4:], uint32(i))
- h := Sum32(0)
- h.Write(payload[:])
- idx := int(h.Sum32()) % len(arr)
- arr[idx]++
- }
- b.StopTimer()
- if b.N > 1000000 {
- for i := 0; i < len(arr)-1; i++ {
- if math.Abs(float64(arr[i]-arr[i+1]))/float64(arr[i]) > float64(0.1) {
- b.Logf("val[%3d]=%8d\tval[%3d]=%8d\tb.N=%b\n", i, arr[i], i+1, arr[i+1], b.N)
- break
- }
- }
- }
-}
-
-func BenchmarkNew32(b *testing.B) {
- arr := make([]int64, 1000)
- for i := 0; i < b.N; i++ {
- var payload [8]byte
- binary.BigEndian.PutUint32(payload[:4], uint32(i))
- binary.BigEndian.PutUint32(payload[4:], uint32(i))
- h := New32()
- h.Write(payload[:])
- idx := int(h.Sum32()) % len(arr)
- arr[idx]++
- }
- b.StopTimer()
- if b.N > 1000000 {
- for i := 0; i < len(arr)-1; i++ {
- if math.Abs(float64(arr[i]-arr[i+1]))/float64(arr[i]) > float64(0.1) {
- b.Logf("val[%3d]=%8d\tval[%3d]=%8d\tb.N=%b\n", i, arr[i], i+1, arr[i+1], b.N)
- break
- }
- }
- }
-}
diff --git a/pkg/tcpip/header/BUILD b/pkg/tcpip/header/BUILD
deleted file mode 100644
index b558350c3..000000000
--- a/pkg/tcpip/header/BUILD
+++ /dev/null
@@ -1,43 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "header",
- srcs = [
- "arp.go",
- "checksum.go",
- "eth.go",
- "gue.go",
- "icmpv4.go",
- "icmpv6.go",
- "interfaces.go",
- "ipv4.go",
- "ipv6.go",
- "ipv6_fragment.go",
- "tcp.go",
- "udp.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/header",
- visibility = ["//visibility:public"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/seqnum",
- "@com_github_google_btree//:go_default_library",
- ],
-)
-
-go_test(
- name = "header_test",
- size = "small",
- srcs = [
- "ipversion_test.go",
- "tcp_test.go",
- ],
- deps = [
- ":header",
- ],
-)
diff --git a/pkg/tcpip/header/header_state_autogen.go b/pkg/tcpip/header/header_state_autogen.go
new file mode 100755
index 000000000..4fcb89452
--- /dev/null
+++ b/pkg/tcpip/header/header_state_autogen.go
@@ -0,0 +1,42 @@
+// automatically generated by stateify.
+
+package header
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *SACKBlock) beforeSave() {}
+func (x *SACKBlock) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Start", &x.Start)
+ m.Save("End", &x.End)
+}
+
+func (x *SACKBlock) afterLoad() {}
+func (x *SACKBlock) load(m state.Map) {
+ m.Load("Start", &x.Start)
+ m.Load("End", &x.End)
+}
+
+func (x *TCPOptions) beforeSave() {}
+func (x *TCPOptions) save(m state.Map) {
+ x.beforeSave()
+ m.Save("TS", &x.TS)
+ m.Save("TSVal", &x.TSVal)
+ m.Save("TSEcr", &x.TSEcr)
+ m.Save("SACKBlocks", &x.SACKBlocks)
+}
+
+func (x *TCPOptions) afterLoad() {}
+func (x *TCPOptions) load(m state.Map) {
+ m.Load("TS", &x.TS)
+ m.Load("TSVal", &x.TSVal)
+ m.Load("TSEcr", &x.TSEcr)
+ m.Load("SACKBlocks", &x.SACKBlocks)
+}
+
+func init() {
+ state.Register("header.SACKBlock", (*SACKBlock)(nil), state.Fns{Save: (*SACKBlock).save, Load: (*SACKBlock).load})
+ state.Register("header.TCPOptions", (*TCPOptions)(nil), state.Fns{Save: (*TCPOptions).save, Load: (*TCPOptions).load})
+}
diff --git a/pkg/tcpip/header/ipversion_test.go b/pkg/tcpip/header/ipversion_test.go
deleted file mode 100644
index b5540bf66..000000000
--- a/pkg/tcpip/header/ipversion_test.go
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package header_test
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip/header"
-)
-
-func TestIPv4(t *testing.T) {
- b := header.IPv4(make([]byte, header.IPv4MinimumSize))
- b.Encode(&header.IPv4Fields{})
-
- const want = header.IPv4Version
- if v := header.IPVersion(b); v != want {
- t.Fatalf("Bad version, want %v, got %v", want, v)
- }
-}
-
-func TestIPv6(t *testing.T) {
- b := header.IPv6(make([]byte, header.IPv6MinimumSize))
- b.Encode(&header.IPv6Fields{})
-
- const want = header.IPv6Version
- if v := header.IPVersion(b); v != want {
- t.Fatalf("Bad version, want %v, got %v", want, v)
- }
-}
-
-func TestOtherVersion(t *testing.T) {
- const want = header.IPv4Version + header.IPv6Version
- b := make([]byte, 1)
- b[0] = want << 4
-
- if v := header.IPVersion(b); v != want {
- t.Fatalf("Bad version, want %v, got %v", want, v)
- }
-}
-
-func TestTooShort(t *testing.T) {
- b := make([]byte, 1)
- b[0] = (header.IPv4Version + header.IPv6Version) << 4
-
- // Get the version of a zero-length slice.
- const want = -1
- if v := header.IPVersion(b[:0]); v != want {
- t.Fatalf("Bad version, want %v, got %v", want, v)
- }
-
- // Get the version of a nil slice.
- if v := header.IPVersion(nil); v != want {
- t.Fatalf("Bad version, want %v, got %v", want, v)
- }
-}
diff --git a/pkg/tcpip/header/tcp_test.go b/pkg/tcpip/header/tcp_test.go
deleted file mode 100644
index 72563837b..000000000
--- a/pkg/tcpip/header/tcp_test.go
+++ /dev/null
@@ -1,148 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package header_test
-
-import (
- "reflect"
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip/header"
-)
-
-func TestEncodeSACKBlocks(t *testing.T) {
- testCases := []struct {
- sackBlocks []header.SACKBlock
- want []header.SACKBlock
- bufSize int
- }{
- {
- []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}, {42, 50}, {52, 60}, {62, 70}},
- []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}, {42, 50}},
- 40,
- },
- {
- []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}, {42, 50}, {52, 60}, {62, 70}},
- []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}},
- 30,
- },
- {
- []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}, {42, 50}, {52, 60}, {62, 70}},
- []header.SACKBlock{{10, 20}, {22, 30}},
- 20,
- },
- {
- []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}, {42, 50}, {52, 60}, {62, 70}},
- []header.SACKBlock{{10, 20}},
- 10,
- },
- {
- []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}, {42, 50}, {52, 60}, {62, 70}},
- nil,
- 8,
- },
- {
- []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}, {42, 50}, {52, 60}, {62, 70}},
- []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}, {42, 50}},
- 60,
- },
- }
- for _, tc := range testCases {
- b := make([]byte, tc.bufSize)
- t.Logf("testing: %v", tc)
- header.EncodeSACKBlocks(tc.sackBlocks, b)
- opts := header.ParseTCPOptions(b)
- if got, want := opts.SACKBlocks, tc.want; !reflect.DeepEqual(got, want) {
- t.Errorf("header.EncodeSACKBlocks(%v, %v), encoded blocks got: %v, want: %v", tc.sackBlocks, b, got, want)
- }
- }
-}
-
-func TestTCPParseOptions(t *testing.T) {
- type tsOption struct {
- tsVal uint32
- tsEcr uint32
- }
-
- generateOptions := func(tsOpt *tsOption, sackBlocks []header.SACKBlock) []byte {
- l := 0
- if tsOpt != nil {
- l += 10
- }
- if len(sackBlocks) != 0 {
- l += len(sackBlocks)*8 + 2
- }
- b := make([]byte, l)
- offset := 0
- if tsOpt != nil {
- offset = header.EncodeTSOption(tsOpt.tsVal, tsOpt.tsEcr, b)
- }
- header.EncodeSACKBlocks(sackBlocks, b[offset:])
- return b
- }
-
- testCases := []struct {
- b []byte
- want header.TCPOptions
- }{
- // Trivial cases.
- {nil, header.TCPOptions{false, 0, 0, nil}},
- {[]byte{header.TCPOptionNOP}, header.TCPOptions{false, 0, 0, nil}},
- {[]byte{header.TCPOptionNOP, header.TCPOptionNOP}, header.TCPOptions{false, 0, 0, nil}},
- {[]byte{header.TCPOptionEOL}, header.TCPOptions{false, 0, 0, nil}},
- {[]byte{header.TCPOptionNOP, header.TCPOptionEOL, header.TCPOptionTS, 10, 1, 1}, header.TCPOptions{false, 0, 0, nil}},
-
- // Test timestamp parsing.
- {[]byte{header.TCPOptionNOP, header.TCPOptionTS, 10, 0, 0, 0, 1, 0, 0, 0, 1}, header.TCPOptions{true, 1, 1, nil}},
- {[]byte{header.TCPOptionTS, 10, 0, 0, 0, 1, 0, 0, 0, 1}, header.TCPOptions{true, 1, 1, nil}},
-
- // Test malformed timestamp option.
- {[]byte{header.TCPOptionTS, 8, 1, 1}, header.TCPOptions{false, 0, 0, nil}},
- {[]byte{header.TCPOptionNOP, header.TCPOptionTS, 8, 1, 1}, header.TCPOptions{false, 0, 0, nil}},
- {[]byte{header.TCPOptionNOP, header.TCPOptionTS, 8, 0, 0, 0, 1, 0, 0, 0, 1}, header.TCPOptions{false, 0, 0, nil}},
-
- // Test SACKBlock parsing.
- {[]byte{header.TCPOptionSACK, 10, 0, 0, 0, 1, 0, 0, 0, 10}, header.TCPOptions{false, 0, 0, []header.SACKBlock{{1, 10}}}},
- {[]byte{header.TCPOptionSACK, 18, 0, 0, 0, 1, 0, 0, 0, 10, 0, 0, 0, 11, 0, 0, 0, 12}, header.TCPOptions{false, 0, 0, []header.SACKBlock{{1, 10}, {11, 12}}}},
-
- // Test malformed SACK option.
- {[]byte{header.TCPOptionSACK, 0}, header.TCPOptions{false, 0, 0, nil}},
- {[]byte{header.TCPOptionSACK, 8, 0, 0, 0, 1, 0, 0, 0, 10}, header.TCPOptions{false, 0, 0, nil}},
- {[]byte{header.TCPOptionSACK, 11, 0, 0, 0, 1, 0, 0, 0, 10, 0, 0, 0, 11, 0, 0, 0, 12}, header.TCPOptions{false, 0, 0, nil}},
- {[]byte{header.TCPOptionSACK, 17, 0, 0, 0, 1, 0, 0, 0, 10, 0, 0, 0, 11, 0, 0, 0, 12}, header.TCPOptions{false, 0, 0, nil}},
- {[]byte{header.TCPOptionSACK}, header.TCPOptions{false, 0, 0, nil}},
- {[]byte{header.TCPOptionSACK, 10}, header.TCPOptions{false, 0, 0, nil}},
- {[]byte{header.TCPOptionSACK, 10, 0, 0, 0, 1, 0, 0, 0}, header.TCPOptions{false, 0, 0, nil}},
-
- // Test Timestamp + SACK block parsing.
- {generateOptions(&tsOption{1, 1}, []header.SACKBlock{{1, 10}, {11, 12}}), header.TCPOptions{true, 1, 1, []header.SACKBlock{{1, 10}, {11, 12}}}},
- {generateOptions(&tsOption{1, 2}, []header.SACKBlock{{1, 10}, {11, 12}}), header.TCPOptions{true, 1, 2, []header.SACKBlock{{1, 10}, {11, 12}}}},
- {generateOptions(&tsOption{1, 3}, []header.SACKBlock{{1, 10}, {11, 12}, {13, 14}, {14, 15}, {15, 16}}), header.TCPOptions{true, 1, 3, []header.SACKBlock{{1, 10}, {11, 12}, {13, 14}, {14, 15}}}},
-
- // Test valid timestamp + malformed SACK block parsing.
- {[]byte{header.TCPOptionTS, 10, 0, 0, 0, 1, 0, 0, 0, 1, header.TCPOptionSACK}, header.TCPOptions{true, 1, 1, nil}},
- {[]byte{header.TCPOptionTS, 10, 0, 0, 0, 1, 0, 0, 0, 1, header.TCPOptionSACK, 10}, header.TCPOptions{true, 1, 1, nil}},
- {[]byte{header.TCPOptionTS, 10, 0, 0, 0, 1, 0, 0, 0, 1, header.TCPOptionSACK, 10, 0, 0, 0}, header.TCPOptions{true, 1, 1, nil}},
- {[]byte{header.TCPOptionTS, 10, 0, 0, 0, 1, 0, 0, 0, 1, header.TCPOptionSACK, 11, 0, 0, 0, 1, 0, 0, 0, 1}, header.TCPOptions{true, 1, 1, nil}},
- {[]byte{header.TCPOptionSACK, header.TCPOptionTS, 10, 0, 0, 0, 1, 0, 0, 0, 1}, header.TCPOptions{false, 0, 0, nil}},
- {[]byte{header.TCPOptionSACK, 10, header.TCPOptionTS, 10, 0, 0, 0, 1, 0, 0, 0, 1}, header.TCPOptions{false, 0, 0, []header.SACKBlock{{134873088, 65536}}}},
- {[]byte{header.TCPOptionSACK, 10, 0, 0, 0, header.TCPOptionTS, 10, 0, 0, 0, 1, 0, 0, 0, 1}, header.TCPOptions{false, 0, 0, []header.SACKBlock{{8, 167772160}}}},
- {[]byte{header.TCPOptionSACK, 11, 0, 0, 0, 1, 0, 0, 0, 1, header.TCPOptionTS, 10, 0, 0, 0, 1, 0, 0, 0, 1}, header.TCPOptions{false, 0, 0, nil}},
- }
- for _, tc := range testCases {
- if got, want := header.ParseTCPOptions(tc.b), tc.want; !reflect.DeepEqual(got, want) {
- t.Errorf("ParseTCPOptions(%v) = %v, want: %v", tc.b, got, tc.want)
- }
- }
-}
diff --git a/pkg/tcpip/iptables/BUILD b/pkg/tcpip/iptables/BUILD
deleted file mode 100644
index 3fc14bacd..000000000
--- a/pkg/tcpip/iptables/BUILD
+++ /dev/null
@@ -1,15 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "iptables",
- srcs = [
- "iptables.go",
- "targets.go",
- "types.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/iptables",
- visibility = ["//visibility:public"],
- deps = ["//pkg/tcpip/buffer"],
-)
diff --git a/pkg/tcpip/iptables/iptables_state_autogen.go b/pkg/tcpip/iptables/iptables_state_autogen.go
new file mode 100755
index 000000000..f15092db2
--- /dev/null
+++ b/pkg/tcpip/iptables/iptables_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package iptables
+
diff --git a/pkg/tcpip/link/channel/BUILD b/pkg/tcpip/link/channel/BUILD
deleted file mode 100644
index 97a794986..000000000
--- a/pkg/tcpip/link/channel/BUILD
+++ /dev/null
@@ -1,15 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "channel",
- srcs = ["channel.go"],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/link/channel",
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/stack",
- ],
-)
diff --git a/pkg/tcpip/link/channel/channel.go b/pkg/tcpip/link/channel/channel.go
deleted file mode 100644
index eec430d0a..000000000
--- a/pkg/tcpip/link/channel/channel.go
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package channel provides the implemention of channel-based data-link layer
-// endpoints. Such endpoints allow injection of inbound packets and store
-// outbound packets in a channel.
-package channel
-
-import (
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-// PacketInfo holds all the information about an outbound packet.
-type PacketInfo struct {
- Header buffer.View
- Payload buffer.View
- Proto tcpip.NetworkProtocolNumber
- GSO *stack.GSO
-}
-
-// Endpoint is link layer endpoint that stores outbound packets in a channel
-// and allows injection of inbound packets.
-type Endpoint struct {
- dispatcher stack.NetworkDispatcher
- mtu uint32
- linkAddr tcpip.LinkAddress
- GSO bool
-
- // C is where outbound packets are queued.
- C chan PacketInfo
-}
-
-// New creates a new channel endpoint.
-func New(size int, mtu uint32, linkAddr tcpip.LinkAddress) *Endpoint {
- return &Endpoint{
- C: make(chan PacketInfo, size),
- mtu: mtu,
- linkAddr: linkAddr,
- }
-}
-
-// Drain removes all outbound packets from the channel and counts them.
-func (e *Endpoint) Drain() int {
- c := 0
- for {
- select {
- case <-e.C:
- c++
- default:
- return c
- }
- }
-}
-
-// Inject injects an inbound packet.
-func (e *Endpoint) Inject(protocol tcpip.NetworkProtocolNumber, vv buffer.VectorisedView) {
- e.InjectLinkAddr(protocol, "", vv)
-}
-
-// InjectLinkAddr injects an inbound packet with a remote link address.
-func (e *Endpoint) InjectLinkAddr(protocol tcpip.NetworkProtocolNumber, remote tcpip.LinkAddress, vv buffer.VectorisedView) {
- e.dispatcher.DeliverNetworkPacket(e, remote, "" /* local */, protocol, vv.Clone(nil))
-}
-
-// Attach saves the stack network-layer dispatcher for use later when packets
-// are injected.
-func (e *Endpoint) Attach(dispatcher stack.NetworkDispatcher) {
- e.dispatcher = dispatcher
-}
-
-// IsAttached implements stack.LinkEndpoint.IsAttached.
-func (e *Endpoint) IsAttached() bool {
- return e.dispatcher != nil
-}
-
-// MTU implements stack.LinkEndpoint.MTU. It returns the value initialized
-// during construction.
-func (e *Endpoint) MTU() uint32 {
- return e.mtu
-}
-
-// Capabilities implements stack.LinkEndpoint.Capabilities.
-func (e *Endpoint) Capabilities() stack.LinkEndpointCapabilities {
- caps := stack.LinkEndpointCapabilities(0)
- if e.GSO {
- caps |= stack.CapabilityGSO
- }
- return caps
-}
-
-// GSOMaxSize returns the maximum GSO packet size.
-func (*Endpoint) GSOMaxSize() uint32 {
- return 1 << 15
-}
-
-// MaxHeaderLength returns the maximum size of the link layer header. Given it
-// doesn't have a header, it just returns 0.
-func (*Endpoint) MaxHeaderLength() uint16 {
- return 0
-}
-
-// LinkAddress returns the link address of this endpoint.
-func (e *Endpoint) LinkAddress() tcpip.LinkAddress {
- return e.linkAddr
-}
-
-// WritePacket stores outbound packets into the channel.
-func (e *Endpoint) WritePacket(_ *stack.Route, gso *stack.GSO, hdr buffer.Prependable, payload buffer.VectorisedView, protocol tcpip.NetworkProtocolNumber) *tcpip.Error {
- p := PacketInfo{
- Header: hdr.View(),
- Proto: protocol,
- Payload: payload.ToView(),
- GSO: gso,
- }
-
- select {
- case e.C <- p:
- default:
- }
-
- return nil
-}
diff --git a/pkg/tcpip/link/fdbased/BUILD b/pkg/tcpip/link/fdbased/BUILD
deleted file mode 100644
index 8fa9e3984..000000000
--- a/pkg/tcpip/link/fdbased/BUILD
+++ /dev/null
@@ -1,42 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "fdbased",
- srcs = [
- "endpoint.go",
- "endpoint_unsafe.go",
- "mmap.go",
- "mmap_stub.go",
- "mmap_unsafe.go",
- "packet_dispatchers.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/link/fdbased",
- visibility = [
- "//visibility:public",
- ],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/link/rawfile",
- "//pkg/tcpip/stack",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-go_test(
- name = "fdbased_test",
- size = "small",
- srcs = ["endpoint_test.go"],
- embed = [":fdbased"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/link/rawfile",
- "//pkg/tcpip/stack",
- ],
-)
diff --git a/pkg/tcpip/link/fdbased/endpoint_test.go b/pkg/tcpip/link/fdbased/endpoint_test.go
deleted file mode 100644
index 04406bc9a..000000000
--- a/pkg/tcpip/link/fdbased/endpoint_test.go
+++ /dev/null
@@ -1,454 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// +build linux
-
-package fdbased
-
-import (
- "bytes"
- "fmt"
- "math/rand"
- "reflect"
- "syscall"
- "testing"
- "time"
- "unsafe"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/link/rawfile"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-const (
- mtu = 1500
- laddr = tcpip.LinkAddress("\x11\x22\x33\x44\x55\x66")
- raddr = tcpip.LinkAddress("\x77\x88\x99\xaa\xbb\xcc")
- proto = 10
- csumOffset = 48
- gsoMSS = 500
-)
-
-type packetInfo struct {
- raddr tcpip.LinkAddress
- proto tcpip.NetworkProtocolNumber
- contents buffer.View
-}
-
-type context struct {
- t *testing.T
- fds [2]int
- ep stack.LinkEndpoint
- ch chan packetInfo
- done chan struct{}
-}
-
-func newContext(t *testing.T, opt *Options) *context {
- fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_SEQPACKET, 0)
- if err != nil {
- t.Fatalf("Socketpair failed: %v", err)
- }
-
- done := make(chan struct{}, 1)
- opt.ClosedFunc = func(*tcpip.Error) {
- done <- struct{}{}
- }
-
- opt.FDs = []int{fds[1]}
- ep, err := New(opt)
- if err != nil {
- t.Fatalf("Failed to create FD endpoint: %v", err)
- }
-
- c := &context{
- t: t,
- fds: fds,
- ep: ep,
- ch: make(chan packetInfo, 100),
- done: done,
- }
-
- ep.Attach(c)
-
- return c
-}
-
-func (c *context) cleanup() {
- syscall.Close(c.fds[0])
- <-c.done
- syscall.Close(c.fds[1])
-}
-
-func (c *context) DeliverNetworkPacket(linkEP stack.LinkEndpoint, remote tcpip.LinkAddress, local tcpip.LinkAddress, protocol tcpip.NetworkProtocolNumber, vv buffer.VectorisedView) {
- c.ch <- packetInfo{remote, protocol, vv.ToView()}
-}
-
-func TestNoEthernetProperties(t *testing.T) {
- c := newContext(t, &Options{MTU: mtu})
- defer c.cleanup()
-
- if want, v := uint16(0), c.ep.MaxHeaderLength(); want != v {
- t.Fatalf("MaxHeaderLength() = %v, want %v", v, want)
- }
-
- if want, v := uint32(mtu), c.ep.MTU(); want != v {
- t.Fatalf("MTU() = %v, want %v", v, want)
- }
-}
-
-func TestEthernetProperties(t *testing.T) {
- c := newContext(t, &Options{EthernetHeader: true, MTU: mtu})
- defer c.cleanup()
-
- if want, v := uint16(header.EthernetMinimumSize), c.ep.MaxHeaderLength(); want != v {
- t.Fatalf("MaxHeaderLength() = %v, want %v", v, want)
- }
-
- if want, v := uint32(mtu), c.ep.MTU(); want != v {
- t.Fatalf("MTU() = %v, want %v", v, want)
- }
-}
-
-func TestAddress(t *testing.T) {
- addrs := []tcpip.LinkAddress{"", "abc", "def"}
- for _, a := range addrs {
- t.Run(fmt.Sprintf("Address: %q", a), func(t *testing.T) {
- c := newContext(t, &Options{Address: a, MTU: mtu})
- defer c.cleanup()
-
- if want, v := a, c.ep.LinkAddress(); want != v {
- t.Fatalf("LinkAddress() = %v, want %v", v, want)
- }
- })
- }
-}
-
-func testWritePacket(t *testing.T, plen int, eth bool, gsoMaxSize uint32) {
- c := newContext(t, &Options{Address: laddr, MTU: mtu, EthernetHeader: eth, GSOMaxSize: gsoMaxSize})
- defer c.cleanup()
-
- r := &stack.Route{
- RemoteLinkAddress: raddr,
- }
-
- // Build header.
- hdr := buffer.NewPrependable(int(c.ep.MaxHeaderLength()) + 100)
- b := hdr.Prepend(100)
- for i := range b {
- b[i] = uint8(rand.Intn(256))
- }
-
- // Build payload and write.
- payload := make(buffer.View, plen)
- for i := range payload {
- payload[i] = uint8(rand.Intn(256))
- }
- want := append(hdr.View(), payload...)
- var gso *stack.GSO
- if gsoMaxSize != 0 {
- gso = &stack.GSO{
- Type: stack.GSOTCPv6,
- NeedsCsum: true,
- CsumOffset: csumOffset,
- MSS: gsoMSS,
- MaxSize: gsoMaxSize,
- L3HdrLen: header.IPv4MaximumHeaderSize,
- }
- }
- if err := c.ep.WritePacket(r, gso, hdr, payload.ToVectorisedView(), proto); err != nil {
- t.Fatalf("WritePacket failed: %v", err)
- }
-
- // Read from fd, then compare with what we wrote.
- b = make([]byte, mtu)
- n, err := syscall.Read(c.fds[0], b)
- if err != nil {
- t.Fatalf("Read failed: %v", err)
- }
- b = b[:n]
- if gsoMaxSize != 0 {
- vnetHdr := *(*virtioNetHdr)(unsafe.Pointer(&b[0]))
- if vnetHdr.flags&_VIRTIO_NET_HDR_F_NEEDS_CSUM == 0 {
- t.Fatalf("virtioNetHdr.flags %v doesn't contain %v", vnetHdr.flags, _VIRTIO_NET_HDR_F_NEEDS_CSUM)
- }
- csumStart := header.EthernetMinimumSize + gso.L3HdrLen
- if vnetHdr.csumStart != csumStart {
- t.Fatalf("vnetHdr.csumStart = %v, want %v", vnetHdr.csumStart, csumStart)
- }
- if vnetHdr.csumOffset != csumOffset {
- t.Fatalf("vnetHdr.csumOffset = %v, want %v", vnetHdr.csumOffset, csumOffset)
- }
- gsoType := uint8(0)
- if int(gso.MSS) < plen {
- gsoType = _VIRTIO_NET_HDR_GSO_TCPV6
- }
- if vnetHdr.gsoType != gsoType {
- t.Fatalf("vnetHdr.gsoType = %v, want %v", vnetHdr.gsoType, gsoType)
- }
- b = b[virtioNetHdrSize:]
- }
- if eth {
- h := header.Ethernet(b)
- b = b[header.EthernetMinimumSize:]
-
- if a := h.SourceAddress(); a != laddr {
- t.Fatalf("SourceAddress() = %v, want %v", a, laddr)
- }
-
- if a := h.DestinationAddress(); a != raddr {
- t.Fatalf("DestinationAddress() = %v, want %v", a, raddr)
- }
-
- if et := h.Type(); et != proto {
- t.Fatalf("Type() = %v, want %v", et, proto)
- }
- }
- if len(b) != len(want) {
- t.Fatalf("Read returned %v bytes, want %v", len(b), len(want))
- }
- if !bytes.Equal(b, want) {
- t.Fatalf("Read returned %x, want %x", b, want)
- }
-}
-
-func TestWritePacket(t *testing.T) {
- lengths := []int{0, 100, 1000}
- eths := []bool{true, false}
- gsos := []uint32{0, 32768}
-
- for _, eth := range eths {
- for _, plen := range lengths {
- for _, gso := range gsos {
- t.Run(
- fmt.Sprintf("Eth=%v,PayloadLen=%v,GSOMaxSize=%v", eth, plen, gso),
- func(t *testing.T) {
- testWritePacket(t, plen, eth, gso)
- },
- )
- }
- }
- }
-}
-
-func TestPreserveSrcAddress(t *testing.T) {
- baddr := tcpip.LinkAddress("\xcc\xbb\xaa\x77\x88\x99")
-
- c := newContext(t, &Options{Address: laddr, MTU: mtu, EthernetHeader: true})
- defer c.cleanup()
-
- // Set LocalLinkAddress in route to the value of the bridged address.
- r := &stack.Route{
- RemoteLinkAddress: raddr,
- LocalLinkAddress: baddr,
- }
-
- // WritePacket panics given a prependable with anything less than
- // the minimum size of the ethernet header.
- hdr := buffer.NewPrependable(header.EthernetMinimumSize)
- if err := c.ep.WritePacket(r, nil /* gso */, hdr, buffer.VectorisedView{}, proto); err != nil {
- t.Fatalf("WritePacket failed: %v", err)
- }
-
- // Read from the FD, then compare with what we wrote.
- b := make([]byte, mtu)
- n, err := syscall.Read(c.fds[0], b)
- if err != nil {
- t.Fatalf("Read failed: %v", err)
- }
- b = b[:n]
- h := header.Ethernet(b)
-
- if a := h.SourceAddress(); a != baddr {
- t.Fatalf("SourceAddress() = %v, want %v", a, baddr)
- }
-}
-
-func TestDeliverPacket(t *testing.T) {
- lengths := []int{100, 1000}
- eths := []bool{true, false}
-
- for _, eth := range eths {
- for _, plen := range lengths {
- t.Run(fmt.Sprintf("Eth=%v,PayloadLen=%v", eth, plen), func(t *testing.T) {
- c := newContext(t, &Options{Address: laddr, MTU: mtu, EthernetHeader: eth})
- defer c.cleanup()
-
- // Build packet.
- b := make([]byte, plen)
- all := b
- for i := range b {
- b[i] = uint8(rand.Intn(256))
- }
-
- if !eth {
- // So that it looks like an IPv4 packet.
- b[0] = 0x40
- } else {
- hdr := make(header.Ethernet, header.EthernetMinimumSize)
- hdr.Encode(&header.EthernetFields{
- SrcAddr: raddr,
- DstAddr: laddr,
- Type: proto,
- })
- all = append(hdr, b...)
- }
-
- // Write packet via the file descriptor.
- if _, err := syscall.Write(c.fds[0], all); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
-
- // Receive packet through the endpoint.
- select {
- case pi := <-c.ch:
- want := packetInfo{
- raddr: raddr,
- proto: proto,
- contents: b,
- }
- if !eth {
- want.proto = header.IPv4ProtocolNumber
- want.raddr = ""
- }
- if !reflect.DeepEqual(want, pi) {
- t.Fatalf("Unexpected received packet: %+v, want %+v", pi, want)
- }
- case <-time.After(10 * time.Second):
- t.Fatalf("Timed out waiting for packet")
- }
- })
- }
- }
-}
-
-func TestBufConfigMaxLength(t *testing.T) {
- got := 0
- for _, i := range BufConfig {
- got += i
- }
- want := header.MaxIPPacketSize // maximum TCP packet size
- if got < want {
- t.Errorf("total buffer size is invalid: got %d, want >= %d", got, want)
- }
-}
-
-func TestBufConfigFirst(t *testing.T) {
- // The stack assumes that the TCP/IP header is enterily contained in the first view.
- // Therefore, the first view needs to be large enough to contain the maximum TCP/IP
- // header, which is 120 bytes (60 bytes for IP + 60 bytes for TCP).
- want := 120
- got := BufConfig[0]
- if got < want {
- t.Errorf("first view has an invalid size: got %d, want >= %d", got, want)
- }
-}
-
-var capLengthTestCases = []struct {
- comment string
- config []int
- n int
- wantUsed int
- wantLengths []int
-}{
- {
- comment: "Single slice",
- config: []int{2},
- n: 1,
- wantUsed: 1,
- wantLengths: []int{1},
- },
- {
- comment: "Multiple slices",
- config: []int{1, 2},
- n: 2,
- wantUsed: 2,
- wantLengths: []int{1, 1},
- },
- {
- comment: "Entire buffer",
- config: []int{1, 2},
- n: 3,
- wantUsed: 2,
- wantLengths: []int{1, 2},
- },
- {
- comment: "Entire buffer but not on the last slice",
- config: []int{1, 2, 3},
- n: 3,
- wantUsed: 2,
- wantLengths: []int{1, 2, 3},
- },
-}
-
-func TestReadVDispatcherCapLength(t *testing.T) {
- for _, c := range capLengthTestCases {
- // fd does not matter for this test.
- d := readVDispatcher{fd: -1, e: &endpoint{}}
- d.views = make([]buffer.View, len(c.config))
- d.iovecs = make([]syscall.Iovec, len(c.config))
- d.allocateViews(c.config)
-
- used := d.capViews(c.n, c.config)
- if used != c.wantUsed {
- t.Errorf("Test %q failed when calling capViews(%d, %v). Got %d. Want %d", c.comment, c.n, c.config, used, c.wantUsed)
- }
- lengths := make([]int, len(d.views))
- for i, v := range d.views {
- lengths[i] = len(v)
- }
- if !reflect.DeepEqual(lengths, c.wantLengths) {
- t.Errorf("Test %q failed when calling capViews(%d, %v). Got %v. Want %v", c.comment, c.n, c.config, lengths, c.wantLengths)
- }
- }
-}
-
-func TestRecvMMsgDispatcherCapLength(t *testing.T) {
- for _, c := range capLengthTestCases {
- d := recvMMsgDispatcher{
- fd: -1, // fd does not matter for this test.
- e: &endpoint{},
- views: make([][]buffer.View, 1),
- iovecs: make([][]syscall.Iovec, 1),
- msgHdrs: make([]rawfile.MMsgHdr, 1),
- }
-
- for i, _ := range d.views {
- d.views[i] = make([]buffer.View, len(c.config))
- }
- for i := range d.iovecs {
- d.iovecs[i] = make([]syscall.Iovec, len(c.config))
- }
- for k, msgHdr := range d.msgHdrs {
- msgHdr.Msg.Iov = &d.iovecs[k][0]
- msgHdr.Msg.Iovlen = uint64(len(c.config))
- }
-
- d.allocateViews(c.config)
-
- used := d.capViews(0, c.n, c.config)
- if used != c.wantUsed {
- t.Errorf("Test %q failed when calling capViews(%d, %v). Got %d. Want %d", c.comment, c.n, c.config, used, c.wantUsed)
- }
- lengths := make([]int, len(d.views[0]))
- for i, v := range d.views[0] {
- lengths[i] = len(v)
- }
- if !reflect.DeepEqual(lengths, c.wantLengths) {
- t.Errorf("Test %q failed when calling capViews(%d, %v). Got %v. Want %v", c.comment, c.n, c.config, lengths, c.wantLengths)
- }
-
- }
-}
diff --git a/pkg/tcpip/link/fdbased/fdbased_state_autogen.go b/pkg/tcpip/link/fdbased/fdbased_state_autogen.go
new file mode 100755
index 000000000..0555db528
--- /dev/null
+++ b/pkg/tcpip/link/fdbased/fdbased_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package fdbased
+
diff --git a/pkg/tcpip/link/loopback/BUILD b/pkg/tcpip/link/loopback/BUILD
deleted file mode 100644
index 47a54845c..000000000
--- a/pkg/tcpip/link/loopback/BUILD
+++ /dev/null
@@ -1,15 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "loopback",
- srcs = ["loopback.go"],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/link/loopback",
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/stack",
- ],
-)
diff --git a/pkg/tcpip/link/loopback/loopback_state_autogen.go b/pkg/tcpip/link/loopback/loopback_state_autogen.go
new file mode 100755
index 000000000..87ec8cfc7
--- /dev/null
+++ b/pkg/tcpip/link/loopback/loopback_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package loopback
+
diff --git a/pkg/tcpip/link/muxed/BUILD b/pkg/tcpip/link/muxed/BUILD
deleted file mode 100644
index 1bab380b0..000000000
--- a/pkg/tcpip/link/muxed/BUILD
+++ /dev/null
@@ -1,32 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "muxed",
- srcs = ["injectable.go"],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/link/muxed",
- visibility = [
- "//visibility:public",
- ],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/stack",
- ],
-)
-
-go_test(
- name = "muxed_test",
- size = "small",
- srcs = ["injectable_test.go"],
- embed = [":muxed"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/link/fdbased",
- "//pkg/tcpip/network/ipv4",
- "//pkg/tcpip/stack",
- ],
-)
diff --git a/pkg/tcpip/link/muxed/injectable.go b/pkg/tcpip/link/muxed/injectable.go
deleted file mode 100644
index 3ed7b98d1..000000000
--- a/pkg/tcpip/link/muxed/injectable.go
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package muxed provides a muxed link endpoints.
-package muxed
-
-import (
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-// InjectableEndpoint is an injectable multi endpoint. The endpoint has
-// trivial routing rules that determine which InjectableEndpoint a given packet
-// will be written to. Note that HandleLocal works differently for this
-// endpoint (see WritePacket).
-type InjectableEndpoint struct {
- routes map[tcpip.Address]stack.InjectableLinkEndpoint
- dispatcher stack.NetworkDispatcher
-}
-
-// MTU implements stack.LinkEndpoint.
-func (m *InjectableEndpoint) MTU() uint32 {
- minMTU := ^uint32(0)
- for _, endpoint := range m.routes {
- if endpointMTU := endpoint.MTU(); endpointMTU < minMTU {
- minMTU = endpointMTU
- }
- }
- return minMTU
-}
-
-// Capabilities implements stack.LinkEndpoint.
-func (m *InjectableEndpoint) Capabilities() stack.LinkEndpointCapabilities {
- minCapabilities := stack.LinkEndpointCapabilities(^uint(0))
- for _, endpoint := range m.routes {
- minCapabilities &= endpoint.Capabilities()
- }
- return minCapabilities
-}
-
-// MaxHeaderLength implements stack.LinkEndpoint.
-func (m *InjectableEndpoint) MaxHeaderLength() uint16 {
- minHeaderLen := ^uint16(0)
- for _, endpoint := range m.routes {
- if headerLen := endpoint.MaxHeaderLength(); headerLen < minHeaderLen {
- minHeaderLen = headerLen
- }
- }
- return minHeaderLen
-}
-
-// LinkAddress implements stack.LinkEndpoint.
-func (m *InjectableEndpoint) LinkAddress() tcpip.LinkAddress {
- return ""
-}
-
-// Attach implements stack.LinkEndpoint.
-func (m *InjectableEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
- for _, endpoint := range m.routes {
- endpoint.Attach(dispatcher)
- }
- m.dispatcher = dispatcher
-}
-
-// IsAttached implements stack.LinkEndpoint.
-func (m *InjectableEndpoint) IsAttached() bool {
- return m.dispatcher != nil
-}
-
-// Inject implements stack.InjectableLinkEndpoint.
-func (m *InjectableEndpoint) Inject(protocol tcpip.NetworkProtocolNumber, vv buffer.VectorisedView) {
- m.dispatcher.DeliverNetworkPacket(m, "" /* remote */, "" /* local */, protocol, vv)
-}
-
-// WritePacket writes outbound packets to the appropriate LinkInjectableEndpoint
-// based on the RemoteAddress. HandleLocal only works if r.RemoteAddress has a
-// route registered in this endpoint.
-func (m *InjectableEndpoint) WritePacket(r *stack.Route, _ *stack.GSO, hdr buffer.Prependable, payload buffer.VectorisedView, protocol tcpip.NetworkProtocolNumber) *tcpip.Error {
- if endpoint, ok := m.routes[r.RemoteAddress]; ok {
- return endpoint.WritePacket(r, nil /* gso */, hdr, payload, protocol)
- }
- return tcpip.ErrNoRoute
-}
-
-// WriteRawPacket writes outbound packets to the appropriate
-// LinkInjectableEndpoint based on the dest address.
-func (m *InjectableEndpoint) WriteRawPacket(dest tcpip.Address, packet []byte) *tcpip.Error {
- endpoint, ok := m.routes[dest]
- if !ok {
- return tcpip.ErrNoRoute
- }
- return endpoint.WriteRawPacket(dest, packet)
-}
-
-// NewInjectableEndpoint creates a new multi-endpoint injectable endpoint.
-func NewInjectableEndpoint(routes map[tcpip.Address]stack.InjectableLinkEndpoint) *InjectableEndpoint {
- return &InjectableEndpoint{
- routes: routes,
- }
-}
diff --git a/pkg/tcpip/link/muxed/injectable_test.go b/pkg/tcpip/link/muxed/injectable_test.go
deleted file mode 100644
index 3086fec00..000000000
--- a/pkg/tcpip/link/muxed/injectable_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package muxed
-
-import (
- "bytes"
- "net"
- "os"
- "syscall"
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-func TestInjectableEndpointRawDispatch(t *testing.T) {
- endpoint, sock, dstIP := makeTestInjectableEndpoint(t)
-
- endpoint.WriteRawPacket(dstIP, []byte{0xFA})
-
- buf := make([]byte, ipv4.MaxTotalSize)
- bytesRead, err := sock.Read(buf)
- if err != nil {
- t.Fatalf("Unable to read from socketpair: %v", err)
- }
- if got, want := buf[:bytesRead], []byte{0xFA}; !bytes.Equal(got, want) {
- t.Fatalf("Read %v from the socketpair, wanted %v", got, want)
- }
-}
-
-func TestInjectableEndpointDispatch(t *testing.T) {
- endpoint, sock, dstIP := makeTestInjectableEndpoint(t)
-
- hdr := buffer.NewPrependable(1)
- hdr.Prepend(1)[0] = 0xFA
- packetRoute := stack.Route{RemoteAddress: dstIP}
-
- endpoint.WritePacket(&packetRoute, nil /* gso */, hdr,
- buffer.NewViewFromBytes([]byte{0xFB}).ToVectorisedView(), ipv4.ProtocolNumber)
-
- buf := make([]byte, 6500)
- bytesRead, err := sock.Read(buf)
- if err != nil {
- t.Fatalf("Unable to read from socketpair: %v", err)
- }
- if got, want := buf[:bytesRead], []byte{0xFA, 0xFB}; !bytes.Equal(got, want) {
- t.Fatalf("Read %v from the socketpair, wanted %v", got, want)
- }
-}
-
-func TestInjectableEndpointDispatchHdrOnly(t *testing.T) {
- endpoint, sock, dstIP := makeTestInjectableEndpoint(t)
- hdr := buffer.NewPrependable(1)
- hdr.Prepend(1)[0] = 0xFA
- packetRoute := stack.Route{RemoteAddress: dstIP}
- endpoint.WritePacket(&packetRoute, nil /* gso */, hdr,
- buffer.NewView(0).ToVectorisedView(), ipv4.ProtocolNumber)
- buf := make([]byte, 6500)
- bytesRead, err := sock.Read(buf)
- if err != nil {
- t.Fatalf("Unable to read from socketpair: %v", err)
- }
- if got, want := buf[:bytesRead], []byte{0xFA}; !bytes.Equal(got, want) {
- t.Fatalf("Read %v from the socketpair, wanted %v", got, want)
- }
-}
-
-func makeTestInjectableEndpoint(t *testing.T) (*InjectableEndpoint, *os.File, tcpip.Address) {
- dstIP := tcpip.Address(net.ParseIP("1.2.3.4").To4())
- pair, err := syscall.Socketpair(syscall.AF_UNIX,
- syscall.SOCK_SEQPACKET|syscall.SOCK_CLOEXEC|syscall.SOCK_NONBLOCK, 0)
- if err != nil {
- t.Fatal("Failed to create socket pair:", err)
- }
- underlyingEndpoint := fdbased.NewInjectable(pair[1], 6500, stack.CapabilityNone)
- routes := map[tcpip.Address]stack.InjectableLinkEndpoint{dstIP: underlyingEndpoint}
- endpoint := NewInjectableEndpoint(routes)
- return endpoint, os.NewFile(uintptr(pair[0]), "test route end"), dstIP
-}
diff --git a/pkg/tcpip/link/rawfile/BUILD b/pkg/tcpip/link/rawfile/BUILD
deleted file mode 100644
index 2e8bc772a..000000000
--- a/pkg/tcpip/link/rawfile/BUILD
+++ /dev/null
@@ -1,20 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "rawfile",
- srcs = [
- "blockingpoll_amd64.s",
- "blockingpoll_arm64.s",
- "blockingpoll_noyield_unsafe.go",
- "blockingpoll_yield_unsafe.go",
- "errors.go",
- "rawfile_unsafe.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/link/rawfile",
- visibility = [
- "//visibility:public",
- ],
- deps = ["//pkg/tcpip"],
-)
diff --git a/pkg/tcpip/link/rawfile/rawfile_state_autogen.go b/pkg/tcpip/link/rawfile/rawfile_state_autogen.go
new file mode 100755
index 000000000..662c04444
--- /dev/null
+++ b/pkg/tcpip/link/rawfile/rawfile_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package rawfile
+
diff --git a/pkg/tcpip/link/sharedmem/BUILD b/pkg/tcpip/link/sharedmem/BUILD
deleted file mode 100644
index 0a5ea3dc4..000000000
--- a/pkg/tcpip/link/sharedmem/BUILD
+++ /dev/null
@@ -1,43 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "sharedmem",
- srcs = [
- "rx.go",
- "sharedmem.go",
- "sharedmem_unsafe.go",
- "tx.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/link/sharedmem",
- visibility = [
- "//:sandbox",
- ],
- deps = [
- "//pkg/log",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/link/rawfile",
- "//pkg/tcpip/link/sharedmem/queue",
- "//pkg/tcpip/stack",
- ],
-)
-
-go_test(
- name = "sharedmem_test",
- srcs = [
- "sharedmem_test.go",
- ],
- embed = [":sharedmem"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/link/sharedmem/pipe",
- "//pkg/tcpip/link/sharedmem/queue",
- "//pkg/tcpip/stack",
- ],
-)
diff --git a/pkg/tcpip/link/sharedmem/pipe/BUILD b/pkg/tcpip/link/sharedmem/pipe/BUILD
deleted file mode 100644
index 330ed5e94..000000000
--- a/pkg/tcpip/link/sharedmem/pipe/BUILD
+++ /dev/null
@@ -1,24 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "pipe",
- srcs = [
- "pipe.go",
- "pipe_unsafe.go",
- "rx.go",
- "tx.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/link/sharedmem/pipe",
- visibility = ["//:sandbox"],
-)
-
-go_test(
- name = "pipe_test",
- srcs = [
- "pipe_test.go",
- ],
- embed = [":pipe"],
-)
diff --git a/pkg/tcpip/link/sharedmem/pipe/pipe.go b/pkg/tcpip/link/sharedmem/pipe/pipe.go
deleted file mode 100644
index 74c9f0311..000000000
--- a/pkg/tcpip/link/sharedmem/pipe/pipe.go
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package pipe implements a shared memory ring buffer on which a single reader
-// and a single writer can operate (read/write) concurrently. The ring buffer
-// allows for data of different sizes to be written, and preserves the boundary
-// of the written data.
-//
-// Example usage is as follows:
-//
-// wb := t.Push(20)
-// // Write data to wb.
-// t.Flush()
-//
-// rb := r.Pull()
-// // Do something with data in rb.
-// t.Flush()
-package pipe
-
-import (
- "math"
-)
-
-const (
- jump uint64 = math.MaxUint32 + 1
- offsetMask uint64 = math.MaxUint32
- revolutionMask uint64 = ^offsetMask
-
- sizeOfSlotHeader = 8 // sizeof(uint64)
- slotFree uint64 = 1 << 63
- slotSizeMask uint64 = math.MaxUint32
-)
-
-// payloadToSlotSize calculates the total size of a slot based on its payload
-// size. The total size is the header size, plus the payload size, plus padding
-// if necessary to make the total size a multiple of sizeOfSlotHeader.
-func payloadToSlotSize(payloadSize uint64) uint64 {
- s := sizeOfSlotHeader + payloadSize
- return (s + sizeOfSlotHeader - 1) &^ (sizeOfSlotHeader - 1)
-}
-
-// slotToPayloadSize calculates the payload size of a slot based on the total
-// size of the slot. This is only meant to be used when creating slots that
-// don't carry information (e.g., free slots or wrap slots).
-func slotToPayloadSize(offset uint64) uint64 {
- return offset - sizeOfSlotHeader
-}
-
-// pipe is a basic data structure used by both (transmit & receive) ends of a
-// pipe. Indices into this pipe are split into two fields: offset, which counts
-// the number of bytes from the beginning of the buffer, and revolution, which
-// counts the number of times the index has wrapped around.
-type pipe struct {
- buffer []byte
-}
-
-// init initializes the pipe buffer such that its size is a multiple of the size
-// of the slot header.
-func (p *pipe) init(b []byte) {
- p.buffer = b[:len(b)&^(sizeOfSlotHeader-1)]
-}
-
-// data returns a section of the buffer starting at the given index (which may
-// include revolution information) and with the given size.
-func (p *pipe) data(idx uint64, size uint64) []byte {
- return p.buffer[(idx&offsetMask)+sizeOfSlotHeader:][:size]
-}
diff --git a/pkg/tcpip/link/sharedmem/pipe/pipe_test.go b/pkg/tcpip/link/sharedmem/pipe/pipe_test.go
deleted file mode 100644
index 59ef69a8b..000000000
--- a/pkg/tcpip/link/sharedmem/pipe/pipe_test.go
+++ /dev/null
@@ -1,517 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package pipe
-
-import (
- "math/rand"
- "reflect"
- "runtime"
- "sync"
- "testing"
-)
-
-func TestSimpleReadWrite(t *testing.T) {
- // Check that a simple write can be properly read from the rx side.
- tr := rand.New(rand.NewSource(99))
- rr := rand.New(rand.NewSource(99))
-
- b := make([]byte, 100)
- var tx Tx
- tx.Init(b)
-
- wb := tx.Push(10)
- if wb == nil {
- t.Fatalf("Push failed on empty pipe")
- }
- for i := range wb {
- wb[i] = byte(tr.Intn(256))
- }
- tx.Flush()
-
- var rx Rx
- rx.Init(b)
- rb := rx.Pull()
- if len(rb) != 10 {
- t.Fatalf("Bad buffer size returned: got %v, want %v", len(rb), 10)
- }
-
- for i := range rb {
- if v := byte(rr.Intn(256)); v != rb[i] {
- t.Fatalf("Bad read buffer at index %v: got %v, want %v", i, rb[i], v)
- }
- }
- rx.Flush()
-}
-
-func TestEmptyRead(t *testing.T) {
- // Check that pulling from an empty pipe fails.
- b := make([]byte, 100)
- var tx Tx
- tx.Init(b)
-
- var rx Rx
- rx.Init(b)
- if rb := rx.Pull(); rb != nil {
- t.Fatalf("Pull succeeded on empty pipe")
- }
-}
-
-func TestTooLargeWrite(t *testing.T) {
- // Check that writes that are too large are properly rejected.
- b := make([]byte, 96)
- var tx Tx
- tx.Init(b)
-
- if wb := tx.Push(96); wb != nil {
- t.Fatalf("Write of 96 bytes succeeded on 96-byte pipe")
- }
-
- if wb := tx.Push(88); wb != nil {
- t.Fatalf("Write of 88 bytes succeeded on 96-byte pipe")
- }
-
- if wb := tx.Push(80); wb == nil {
- t.Fatalf("Write of 80 bytes failed on 96-byte pipe")
- }
-}
-
-func TestFullWrite(t *testing.T) {
- // Check that writes fail when the pipe is full.
- b := make([]byte, 100)
- var tx Tx
- tx.Init(b)
-
- if wb := tx.Push(80); wb == nil {
- t.Fatalf("Write of 80 bytes failed on 96-byte pipe")
- }
-
- if wb := tx.Push(1); wb != nil {
- t.Fatalf("Write succeeded on full pipe")
- }
-}
-
-func TestFullAndFlushedWrite(t *testing.T) {
- // Check that writes fail when the pipe is full and has already been
- // flushed.
- b := make([]byte, 100)
- var tx Tx
- tx.Init(b)
-
- if wb := tx.Push(80); wb == nil {
- t.Fatalf("Write of 80 bytes failed on 96-byte pipe")
- }
-
- tx.Flush()
-
- if wb := tx.Push(1); wb != nil {
- t.Fatalf("Write succeeded on full pipe")
- }
-}
-
-func TestTxFlushTwice(t *testing.T) {
- // Checks that a second consecutive tx flush is a no-op.
- b := make([]byte, 100)
- var tx Tx
- tx.Init(b)
-
- if wb := tx.Push(50); wb == nil {
- t.Fatalf("Push failed on empty pipe")
- }
- tx.Flush()
-
- // Make copy of original tx queue, flush it, then check that it didn't
- // change.
- orig := tx
- tx.Flush()
-
- if !reflect.DeepEqual(orig, tx) {
- t.Fatalf("Flush mutated tx pipe: got %v, want %v", tx, orig)
- }
-}
-
-func TestRxFlushTwice(t *testing.T) {
- // Checks that a second consecutive rx flush is a no-op.
- b := make([]byte, 100)
- var tx Tx
- tx.Init(b)
-
- if wb := tx.Push(50); wb == nil {
- t.Fatalf("Push failed on empty pipe")
- }
- tx.Flush()
-
- var rx Rx
- rx.Init(b)
- if rb := rx.Pull(); rb == nil {
- t.Fatalf("Pull failed on non-empty pipe")
- }
- rx.Flush()
-
- // Make copy of original rx queue, flush it, then check that it didn't
- // change.
- orig := rx
- rx.Flush()
-
- if !reflect.DeepEqual(orig, rx) {
- t.Fatalf("Flush mutated rx pipe: got %v, want %v", rx, orig)
- }
-}
-
-func TestWrapInMiddleOfTransaction(t *testing.T) {
- // Check that writes are not flushed when we need to wrap the buffer
- // around.
- b := make([]byte, 100)
- var tx Tx
- tx.Init(b)
-
- if wb := tx.Push(50); wb == nil {
- t.Fatalf("Push failed on empty pipe")
- }
- tx.Flush()
-
- var rx Rx
- rx.Init(b)
- if rb := rx.Pull(); rb == nil {
- t.Fatalf("Pull failed on non-empty pipe")
- }
- rx.Flush()
-
- // At this point the ring buffer is empty, but the write is at offset
- // 64 (50 + sizeOfSlotHeader + padding-for-8-byte-alignment).
- if wb := tx.Push(10); wb == nil {
- t.Fatalf("Push failed on empty pipe")
- }
-
- if wb := tx.Push(50); wb == nil {
- t.Fatalf("Push failed on non-full pipe")
- }
-
- // We haven't flushed yet, so pull must return nil.
- if rb := rx.Pull(); rb != nil {
- t.Fatalf("Pull succeeded on non-flushed pipe")
- }
-
- tx.Flush()
-
- // The two buffers must be available now.
- if rb := rx.Pull(); rb == nil {
- t.Fatalf("Pull failed on non-empty pipe")
- }
-
- if rb := rx.Pull(); rb == nil {
- t.Fatalf("Pull failed on non-empty pipe")
- }
-}
-
-func TestWriteAbort(t *testing.T) {
- // Check that a read fails on a pipe that has had data pushed to it but
- // has aborted the push.
- b := make([]byte, 100)
- var tx Tx
- tx.Init(b)
-
- if wb := tx.Push(10); wb == nil {
- t.Fatalf("Write failed on empty pipe")
- }
-
- var rx Rx
- rx.Init(b)
- if rb := rx.Pull(); rb != nil {
- t.Fatalf("Pull succeeded on empty pipe")
- }
-
- tx.Abort()
- if rb := rx.Pull(); rb != nil {
- t.Fatalf("Pull succeeded on empty pipe")
- }
-}
-
-func TestWrappedWriteAbort(t *testing.T) {
- // Check that writes are properly aborted even if the writes wrap
- // around.
- b := make([]byte, 100)
- var tx Tx
- tx.Init(b)
-
- if wb := tx.Push(50); wb == nil {
- t.Fatalf("Push failed on empty pipe")
- }
- tx.Flush()
-
- var rx Rx
- rx.Init(b)
- if rb := rx.Pull(); rb == nil {
- t.Fatalf("Pull failed on non-empty pipe")
- }
- rx.Flush()
-
- // At this point the ring buffer is empty, but the write is at offset
- // 64 (50 + sizeOfSlotHeader + padding-for-8-byte-alignment).
- if wb := tx.Push(10); wb == nil {
- t.Fatalf("Push failed on empty pipe")
- }
-
- if wb := tx.Push(50); wb == nil {
- t.Fatalf("Push failed on non-full pipe")
- }
-
- // We haven't flushed yet, so pull must return nil.
- if rb := rx.Pull(); rb != nil {
- t.Fatalf("Pull succeeded on non-flushed pipe")
- }
-
- tx.Abort()
-
- // The pushes were aborted, so no data should be readable.
- if rb := rx.Pull(); rb != nil {
- t.Fatalf("Pull succeeded on non-flushed pipe")
- }
-
- // Try the same transactions again, but flush this time.
- if wb := tx.Push(10); wb == nil {
- t.Fatalf("Push failed on empty pipe")
- }
-
- if wb := tx.Push(50); wb == nil {
- t.Fatalf("Push failed on non-full pipe")
- }
-
- tx.Flush()
-
- // The two buffers must be available now.
- if rb := rx.Pull(); rb == nil {
- t.Fatalf("Pull failed on non-empty pipe")
- }
-
- if rb := rx.Pull(); rb == nil {
- t.Fatalf("Pull failed on non-empty pipe")
- }
-}
-
-func TestEmptyReadOnNonFlushedWrite(t *testing.T) {
- // Check that a read fails on a pipe that has had data pushed to it
- // but not yet flushed.
- b := make([]byte, 100)
- var tx Tx
- tx.Init(b)
-
- if wb := tx.Push(10); wb == nil {
- t.Fatalf("Write failed on empty pipe")
- }
-
- var rx Rx
- rx.Init(b)
- if rb := rx.Pull(); rb != nil {
- t.Fatalf("Pull succeeded on empty pipe")
- }
-
- tx.Flush()
- if rb := rx.Pull(); rb == nil {
- t.Fatalf("Pull on failed on non-empty pipe")
- }
-}
-
-func TestPullAfterPullingEntirePipe(t *testing.T) {
- // Check that Pull fails when the pipe is full, but all of it has
- // already been pulled but not yet flushed.
- b := make([]byte, 100)
- var tx Tx
- tx.Init(b)
-
- if wb := tx.Push(50); wb == nil {
- t.Fatalf("Push failed on empty pipe")
- }
- tx.Flush()
-
- var rx Rx
- rx.Init(b)
- if rb := rx.Pull(); rb == nil {
- t.Fatalf("Pull failed on non-empty pipe")
- }
- rx.Flush()
-
- // At this point the ring buffer is empty, but the write is at offset
- // 64 (50 + sizeOfSlotHeader + padding-for-8-byte-alignment). Write 3
- // buffers that will fill the pipe.
- if wb := tx.Push(10); wb == nil {
- t.Fatalf("Push failed on empty pipe")
- }
-
- if wb := tx.Push(20); wb == nil {
- t.Fatalf("Push failed on non-full pipe")
- }
-
- if wb := tx.Push(24); wb == nil {
- t.Fatalf("Push failed on non-full pipe")
- }
-
- tx.Flush()
-
- // The three buffers must be available now.
- if rb := rx.Pull(); rb == nil {
- t.Fatalf("Pull failed on non-empty pipe")
- }
-
- if rb := rx.Pull(); rb == nil {
- t.Fatalf("Pull failed on non-empty pipe")
- }
-
- if rb := rx.Pull(); rb == nil {
- t.Fatalf("Pull failed on non-empty pipe")
- }
-
- // Fourth pull must fail.
- if rb := rx.Pull(); rb != nil {
- t.Fatalf("Pull succeeded on empty pipe")
- }
-}
-
-func TestNoRoomToWrapOnPush(t *testing.T) {
- // Check that Push fails when it tries to allocate room to add a wrap
- // message.
- b := make([]byte, 100)
- var tx Tx
- tx.Init(b)
-
- if wb := tx.Push(50); wb == nil {
- t.Fatalf("Push failed on empty pipe")
- }
- tx.Flush()
-
- var rx Rx
- rx.Init(b)
- if rb := rx.Pull(); rb == nil {
- t.Fatalf("Pull failed on non-empty pipe")
- }
- rx.Flush()
-
- // At this point the ring buffer is empty, but the write is at offset
- // 64 (50 + sizeOfSlotHeader + padding-for-8-byte-alignment). Write 20,
- // which won't fit (64+20+8+padding = 96, which wouldn't leave room for
- // the padding), so it wraps around.
- if wb := tx.Push(20); wb == nil {
- t.Fatalf("Push failed on empty pipe")
- }
-
- tx.Flush()
-
- // Buffer offset is at 28. Try to write 70, which would require a wrap
- // slot which cannot be created now.
- if wb := tx.Push(70); wb != nil {
- t.Fatalf("Push succeeded on pipe with no room for wrap message")
- }
-}
-
-func TestRxImplicitFlushOfWrapMessage(t *testing.T) {
- // Check if the first read is that of a wrapping message, that it gets
- // immediately flushed.
- b := make([]byte, 100)
- var tx Tx
- tx.Init(b)
-
- if wb := tx.Push(50); wb == nil {
- t.Fatalf("Push failed on empty pipe")
- }
- tx.Flush()
-
- // This will cause a wrapping message to written.
- if wb := tx.Push(60); wb != nil {
- t.Fatalf("Push succeeded when there is no room in pipe")
- }
-
- var rx Rx
- rx.Init(b)
-
- // Read the first message.
- if rb := rx.Pull(); rb == nil {
- t.Fatalf("Pull failed on non-empty pipe")
- }
- rx.Flush()
-
- // This should fail because of the wrapping message is taking up space.
- if wb := tx.Push(60); wb != nil {
- t.Fatalf("Push succeeded when there is no room in pipe")
- }
-
- // Try to read the next one. This should consume the wrapping message.
- rx.Pull()
-
- // This must now succeed.
- if wb := tx.Push(60); wb == nil {
- t.Fatalf("Push failed on empty pipe")
- }
-}
-
-func TestConcurrentReaderWriter(t *testing.T) {
- // Push a million buffers of random sizes and random contents. Check
- // that buffers read match what was written.
- tr := rand.New(rand.NewSource(99))
- rr := rand.New(rand.NewSource(99))
-
- b := make([]byte, 100)
- var tx Tx
- tx.Init(b)
-
- var rx Rx
- rx.Init(b)
-
- const count = 1000000
- var wg sync.WaitGroup
- wg.Add(1)
- go func() {
- defer wg.Done()
- runtime.Gosched()
- for i := 0; i < count; i++ {
- n := 1 + tr.Intn(80)
- wb := tx.Push(uint64(n))
- for wb == nil {
- wb = tx.Push(uint64(n))
- }
-
- for j := range wb {
- wb[j] = byte(tr.Intn(256))
- }
-
- tx.Flush()
- }
- }()
-
- wg.Add(1)
- go func() {
- defer wg.Done()
- runtime.Gosched()
- for i := 0; i < count; i++ {
- n := 1 + rr.Intn(80)
- rb := rx.Pull()
- for rb == nil {
- rb = rx.Pull()
- }
-
- if n != len(rb) {
- t.Fatalf("Bad %v-th buffer length: got %v, want %v", i, len(rb), n)
- }
-
- for j := range rb {
- if v := byte(rr.Intn(256)); v != rb[j] {
- t.Fatalf("Bad %v-th read buffer at index %v: got %v, want %v", i, j, rb[j], v)
- }
- }
-
- rx.Flush()
- }
- }()
-
- wg.Wait()
-}
diff --git a/pkg/tcpip/link/sharedmem/pipe/pipe_unsafe.go b/pkg/tcpip/link/sharedmem/pipe/pipe_unsafe.go
deleted file mode 100644
index 62d17029e..000000000
--- a/pkg/tcpip/link/sharedmem/pipe/pipe_unsafe.go
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package pipe
-
-import (
- "sync/atomic"
- "unsafe"
-)
-
-func (p *pipe) write(idx uint64, v uint64) {
- ptr := (*uint64)(unsafe.Pointer(&p.buffer[idx&offsetMask:][:8][0]))
- *ptr = v
-}
-
-func (p *pipe) writeAtomic(idx uint64, v uint64) {
- ptr := (*uint64)(unsafe.Pointer(&p.buffer[idx&offsetMask:][:8][0]))
- atomic.StoreUint64(ptr, v)
-}
-
-func (p *pipe) readAtomic(idx uint64) uint64 {
- ptr := (*uint64)(unsafe.Pointer(&p.buffer[idx&offsetMask:][:8][0]))
- return atomic.LoadUint64(ptr)
-}
diff --git a/pkg/tcpip/link/sharedmem/pipe/rx.go b/pkg/tcpip/link/sharedmem/pipe/rx.go
deleted file mode 100644
index f22e533ac..000000000
--- a/pkg/tcpip/link/sharedmem/pipe/rx.go
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package pipe
-
-// Rx is the receive side of the shared memory ring buffer.
-type Rx struct {
- p pipe
-
- tail uint64
- head uint64
-}
-
-// Init initializes the receive end of the pipe. In the initial state, the next
-// slot to be inspected is the very first one.
-func (r *Rx) Init(b []byte) {
- r.p.init(b)
- r.tail = 0xfffffffe * jump
- r.head = r.tail
-}
-
-// Pull reads the next buffer from the pipe, returning nil if there isn't one
-// currently available.
-//
-// The returned slice is available until Flush() is next called. After that, it
-// must not be touched.
-func (r *Rx) Pull() []byte {
- if r.head == r.tail+jump {
- // We've already pulled the whole pipe.
- return nil
- }
-
- header := r.p.readAtomic(r.head)
- if header&slotFree != 0 {
- // The next slot is free, we can't pull it yet.
- return nil
- }
-
- payloadSize := header & slotSizeMask
- newHead := r.head + payloadToSlotSize(payloadSize)
- headWrap := (r.head & revolutionMask) | uint64(len(r.p.buffer))
-
- // Check if this is a wrapping slot. If that's the case, it carries no
- // data, so we just skip it and try again from the first slot.
- if int64(newHead-headWrap) >= 0 {
- if int64(newHead-headWrap) > int64(jump) || newHead&offsetMask != 0 {
- return nil
- }
-
- if r.tail == r.head {
- // If this is the first pull since the last Flush()
- // call, we flush the state so that the sender can use
- // this space if it needs to.
- r.p.writeAtomic(r.head, slotFree|slotToPayloadSize(newHead-r.head))
- r.tail = newHead
- }
-
- r.head = newHead
- return r.Pull()
- }
-
- // Grab the buffer before updating r.head.
- b := r.p.data(r.head, payloadSize)
- r.head = newHead
- return b
-}
-
-// Flush tells the transmitter that all buffers pulled since the last Flush()
-// have been used, so the transmitter is free to used their slots for further
-// transmission.
-func (r *Rx) Flush() {
- if r.head == r.tail {
- return
- }
- r.p.writeAtomic(r.tail, slotFree|slotToPayloadSize(r.head-r.tail))
- r.tail = r.head
-}
-
-// Bytes returns the byte slice on which the pipe operates.
-func (r *Rx) Bytes() []byte {
- return r.p.buffer
-}
diff --git a/pkg/tcpip/link/sharedmem/pipe/tx.go b/pkg/tcpip/link/sharedmem/pipe/tx.go
deleted file mode 100644
index 9841eb231..000000000
--- a/pkg/tcpip/link/sharedmem/pipe/tx.go
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package pipe
-
-// Tx is the transmit side of the shared memory ring buffer.
-type Tx struct {
- p pipe
- maxPayloadSize uint64
-
- head uint64
- tail uint64
- next uint64
-
- tailHeader uint64
-}
-
-// Init initializes the transmit end of the pipe. In the initial state, the next
-// slot to be written is the very first one, and the transmitter has the whole
-// ring buffer available to it.
-func (t *Tx) Init(b []byte) {
- t.p.init(b)
- // maxPayloadSize excludes the header of the payload, and the header
- // of the wrapping message.
- t.maxPayloadSize = uint64(len(t.p.buffer)) - 2*sizeOfSlotHeader
- t.tail = 0xfffffffe * jump
- t.next = t.tail
- t.head = t.tail + jump
- t.p.write(t.tail, slotFree)
-}
-
-// Capacity determines how many records of the given size can be written to the
-// pipe before it fills up.
-func (t *Tx) Capacity(recordSize uint64) uint64 {
- available := uint64(len(t.p.buffer)) - sizeOfSlotHeader
- entryLen := payloadToSlotSize(recordSize)
- return available / entryLen
-}
-
-// Push reserves "payloadSize" bytes for transmission in the pipe. The caller
-// populates the returned slice with the data to be transferred and enventually
-// calls Flush() to make the data visible to the reader, or Abort() to make the
-// pipe forget all Push() calls since the last Flush().
-//
-// The returned slice is available until Flush() or Abort() is next called.
-// After that, it must not be touched.
-func (t *Tx) Push(payloadSize uint64) []byte {
- // Fail request if we know we will never have enough room.
- if payloadSize > t.maxPayloadSize {
- return nil
- }
-
- totalLen := payloadToSlotSize(payloadSize)
- newNext := t.next + totalLen
- nextWrap := (t.next & revolutionMask) | uint64(len(t.p.buffer))
- if int64(newNext-nextWrap) >= 0 {
- // The new buffer would overflow the pipe, so we push a wrapping
- // slot, then try to add the actual slot to the front of the
- // pipe.
- newNext = (newNext & revolutionMask) + jump
- wrappingPayloadSize := slotToPayloadSize(newNext - t.next)
- if !t.reclaim(newNext) {
- return nil
- }
-
- oldNext := t.next
- t.next = newNext
- if oldNext != t.tail {
- t.p.write(oldNext, wrappingPayloadSize)
- } else {
- t.tailHeader = wrappingPayloadSize
- t.Flush()
- }
-
- newNext += totalLen
- }
-
- // Check that we have enough room for the buffer.
- if !t.reclaim(newNext) {
- return nil
- }
-
- if t.next != t.tail {
- t.p.write(t.next, payloadSize)
- } else {
- t.tailHeader = payloadSize
- }
-
- // Grab the buffer before updating t.next.
- b := t.p.data(t.next, payloadSize)
- t.next = newNext
-
- return b
-}
-
-// reclaim attempts to advance the head until at least newNext. If the head is
-// already at or beyond newNext, nothing happens and true is returned; otherwise
-// it tries to reclaim slots that have already been consumed by the receive end
-// of the pipe (they will be marked as free) and returns a boolean indicating
-// whether it was successful in reclaiming enough slots.
-func (t *Tx) reclaim(newNext uint64) bool {
- for int64(newNext-t.head) > 0 {
- // Can't reclaim if slot is not free.
- header := t.p.readAtomic(t.head)
- if header&slotFree == 0 {
- return false
- }
-
- payloadSize := header & slotSizeMask
- newHead := t.head + payloadToSlotSize(payloadSize)
-
- // Check newHead is within bounds and valid.
- if int64(newHead-t.tail) > int64(jump) || newHead&offsetMask >= uint64(len(t.p.buffer)) {
- return false
- }
-
- t.head = newHead
- }
-
- return true
-}
-
-// Abort causes all Push() calls since the last Flush() to be forgotten and
-// therefore they will not be made visible to the receiver.
-func (t *Tx) Abort() {
- t.next = t.tail
-}
-
-// Flush causes all buffers pushed since the last Flush() [or Abort(), whichever
-// is the most recent] to be made visible to the receiver.
-func (t *Tx) Flush() {
- if t.next == t.tail {
- // Nothing to do if there are no pushed buffers.
- return
- }
-
- if t.next != t.head {
- // The receiver will spin in t.next, so we must make sure that
- // the slotFree bit is set.
- t.p.write(t.next, slotFree)
- }
-
- t.p.writeAtomic(t.tail, t.tailHeader)
- t.tail = t.next
-}
-
-// Bytes returns the byte slice on which the pipe operates.
-func (t *Tx) Bytes() []byte {
- return t.p.buffer
-}
diff --git a/pkg/tcpip/link/sharedmem/queue/BUILD b/pkg/tcpip/link/sharedmem/queue/BUILD
deleted file mode 100644
index de1ce043d..000000000
--- a/pkg/tcpip/link/sharedmem/queue/BUILD
+++ /dev/null
@@ -1,29 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "queue",
- srcs = [
- "rx.go",
- "tx.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/link/sharedmem/queue",
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/log",
- "//pkg/tcpip/link/sharedmem/pipe",
- ],
-)
-
-go_test(
- name = "queue_test",
- srcs = [
- "queue_test.go",
- ],
- embed = [":queue"],
- deps = [
- "//pkg/tcpip/link/sharedmem/pipe",
- ],
-)
diff --git a/pkg/tcpip/link/sharedmem/queue/queue_test.go b/pkg/tcpip/link/sharedmem/queue/queue_test.go
deleted file mode 100644
index 9a0aad5d7..000000000
--- a/pkg/tcpip/link/sharedmem/queue/queue_test.go
+++ /dev/null
@@ -1,517 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package queue
-
-import (
- "encoding/binary"
- "reflect"
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip/link/sharedmem/pipe"
-)
-
-func TestBasicTxQueue(t *testing.T) {
- // Tests that a basic transmit on a queue works, and that completion
- // gets properly reported as well.
- pb1 := make([]byte, 100)
- pb2 := make([]byte, 100)
-
- var rxp pipe.Rx
- rxp.Init(pb1)
-
- var txp pipe.Tx
- txp.Init(pb2)
-
- var q Tx
- q.Init(pb1, pb2)
-
- // Enqueue two buffers.
- b := []TxBuffer{
- {nil, 100, 60},
- {nil, 200, 40},
- }
-
- b[0].Next = &b[1]
-
- const usedID = 1002
- const usedTotalSize = 100
- if !q.Enqueue(usedID, usedTotalSize, 2, &b[0]) {
- t.Fatalf("Enqueue failed on empty queue")
- }
-
- // Check the contents of the pipe.
- d := rxp.Pull()
- if d == nil {
- t.Fatalf("Tx pipe is empty after Enqueue")
- }
-
- want := []byte{
- 234, 3, 0, 0, 0, 0, 0, 0, // id
- 100, 0, 0, 0, // total size
- 0, 0, 0, 0, // reserved
- 100, 0, 0, 0, 0, 0, 0, 0, // offset 1
- 60, 0, 0, 0, // size 1
- 200, 0, 0, 0, 0, 0, 0, 0, // offset 2
- 40, 0, 0, 0, // size 2
- }
-
- if !reflect.DeepEqual(want, d) {
- t.Fatalf("Bad posted packet: got %v, want %v", d, want)
- }
-
- rxp.Flush()
-
- // Check that there are no completions yet.
- if _, ok := q.CompletedPacket(); ok {
- t.Fatalf("Packet reported as completed too soon")
- }
-
- // Post a completion.
- d = txp.Push(8)
- if d == nil {
- t.Fatalf("Unable to push to rx pipe")
- }
- binary.LittleEndian.PutUint64(d, usedID)
- txp.Flush()
-
- // Check that completion is properly reported.
- id, ok := q.CompletedPacket()
- if !ok {
- t.Fatalf("Completion not reported")
- }
-
- if id != usedID {
- t.Fatalf("Bad completion id: got %v, want %v", id, usedID)
- }
-}
-
-func TestBasicRxQueue(t *testing.T) {
- // Tests that a basic receive on a queue works.
- pb1 := make([]byte, 100)
- pb2 := make([]byte, 100)
-
- var rxp pipe.Rx
- rxp.Init(pb1)
-
- var txp pipe.Tx
- txp.Init(pb2)
-
- var q Rx
- q.Init(pb1, pb2, nil)
-
- // Post two buffers.
- b := []RxBuffer{
- {100, 60, 1077, 0},
- {200, 40, 2123, 0},
- }
-
- if !q.PostBuffers(b) {
- t.Fatalf("PostBuffers failed on empty queue")
- }
-
- // Check the contents of the pipe.
- want := [][]byte{
- {
- 100, 0, 0, 0, 0, 0, 0, 0, // Offset1
- 60, 0, 0, 0, // Size1
- 0, 0, 0, 0, // Remaining in group 1
- 0, 0, 0, 0, 0, 0, 0, 0, // User data 1
- 53, 4, 0, 0, 0, 0, 0, 0, // ID 1
- },
- {
- 200, 0, 0, 0, 0, 0, 0, 0, // Offset2
- 40, 0, 0, 0, // Size2
- 0, 0, 0, 0, // Remaining in group 2
- 0, 0, 0, 0, 0, 0, 0, 0, // User data 2
- 75, 8, 0, 0, 0, 0, 0, 0, // ID 2
- },
- }
-
- for i := range b {
- d := rxp.Pull()
- if d == nil {
- t.Fatalf("Tx pipe is empty after PostBuffers")
- }
-
- if !reflect.DeepEqual(want[i], d) {
- t.Fatalf("Bad posted packet: got %v, want %v", d, want[i])
- }
-
- rxp.Flush()
- }
-
- // Check that there are no completions.
- if _, n := q.Dequeue(nil); n != 0 {
- t.Fatalf("Packet reported as received too soon")
- }
-
- // Post a completion.
- d := txp.Push(sizeOfConsumedPacketHeader + 2*sizeOfConsumedBuffer)
- if d == nil {
- t.Fatalf("Unable to push to rx pipe")
- }
-
- copy(d, []byte{
- 100, 0, 0, 0, // packet size
- 0, 0, 0, 0, // reserved
-
- 100, 0, 0, 0, 0, 0, 0, 0, // offset 1
- 60, 0, 0, 0, // size 1
- 0, 0, 0, 0, 0, 0, 0, 0, // user data 1
- 53, 4, 0, 0, 0, 0, 0, 0, // ID 1
-
- 200, 0, 0, 0, 0, 0, 0, 0, // offset 2
- 40, 0, 0, 0, // size 2
- 0, 0, 0, 0, 0, 0, 0, 0, // user data 2
- 75, 8, 0, 0, 0, 0, 0, 0, // ID 2
- })
-
- txp.Flush()
-
- // Check that completion is properly reported.
- bufs, n := q.Dequeue(nil)
- if n != 100 {
- t.Fatalf("Bad packet size: got %v, want %v", n, 100)
- }
-
- if !reflect.DeepEqual(bufs, b) {
- t.Fatalf("Bad returned buffers: got %v, want %v", bufs, b)
- }
-}
-
-func TestBadTxCompletion(t *testing.T) {
- // Check that tx completions with bad sizes are properly ignored.
- pb1 := make([]byte, 100)
- pb2 := make([]byte, 100)
-
- var rxp pipe.Rx
- rxp.Init(pb1)
-
- var txp pipe.Tx
- txp.Init(pb2)
-
- var q Tx
- q.Init(pb1, pb2)
-
- // Post a completion that is too short, and check that it is ignored.
- if d := txp.Push(7); d == nil {
- t.Fatalf("Unable to push to rx pipe")
- }
- txp.Flush()
-
- if _, ok := q.CompletedPacket(); ok {
- t.Fatalf("Bad completion not ignored")
- }
-
- // Post a completion that is too long, and check that it is ignored.
- if d := txp.Push(10); d == nil {
- t.Fatalf("Unable to push to rx pipe")
- }
- txp.Flush()
-
- if _, ok := q.CompletedPacket(); ok {
- t.Fatalf("Bad completion not ignored")
- }
-}
-
-func TestBadRxCompletion(t *testing.T) {
- // Check that bad rx completions are properly ignored.
- pb1 := make([]byte, 100)
- pb2 := make([]byte, 100)
-
- var rxp pipe.Rx
- rxp.Init(pb1)
-
- var txp pipe.Tx
- txp.Init(pb2)
-
- var q Rx
- q.Init(pb1, pb2, nil)
-
- // Post a completion that is too short, and check that it is ignored.
- if d := txp.Push(7); d == nil {
- t.Fatalf("Unable to push to rx pipe")
- }
- txp.Flush()
-
- if b, _ := q.Dequeue(nil); b != nil {
- t.Fatalf("Bad completion not ignored")
- }
-
- // Post a completion whose buffer sizes add up to less than the total
- // size.
- d := txp.Push(sizeOfConsumedPacketHeader + 2*sizeOfConsumedBuffer)
- if d == nil {
- t.Fatalf("Unable to push to rx pipe")
- }
-
- copy(d, []byte{
- 100, 0, 0, 0, // packet size
- 0, 0, 0, 0, // reserved
-
- 100, 0, 0, 0, 0, 0, 0, 0, // offset 1
- 10, 0, 0, 0, // size 1
- 0, 0, 0, 0, 0, 0, 0, 0, // user data 1
- 53, 4, 0, 0, 0, 0, 0, 0, // ID 1
-
- 200, 0, 0, 0, 0, 0, 0, 0, // offset 2
- 10, 0, 0, 0, // size 2
- 0, 0, 0, 0, 0, 0, 0, 0, // user data 2
- 75, 8, 0, 0, 0, 0, 0, 0, // ID 2
- })
-
- txp.Flush()
- if b, _ := q.Dequeue(nil); b != nil {
- t.Fatalf("Bad completion not ignored")
- }
-
- // Post a completion whose buffer sizes will cause a 32-bit overflow,
- // but adds up to the right number.
- d = txp.Push(sizeOfConsumedPacketHeader + 2*sizeOfConsumedBuffer)
- if d == nil {
- t.Fatalf("Unable to push to rx pipe")
- }
-
- copy(d, []byte{
- 100, 0, 0, 0, // packet size
- 0, 0, 0, 0, // reserved
-
- 100, 0, 0, 0, 0, 0, 0, 0, // offset 1
- 255, 255, 255, 255, // size 1
- 0, 0, 0, 0, 0, 0, 0, 0, // user data 1
- 53, 4, 0, 0, 0, 0, 0, 0, // ID 1
-
- 200, 0, 0, 0, 0, 0, 0, 0, // offset 2
- 101, 0, 0, 0, // size 2
- 0, 0, 0, 0, 0, 0, 0, 0, // user data 2
- 75, 8, 0, 0, 0, 0, 0, 0, // ID 2
- })
-
- txp.Flush()
- if b, _ := q.Dequeue(nil); b != nil {
- t.Fatalf("Bad completion not ignored")
- }
-}
-
-func TestFillTxPipe(t *testing.T) {
- // Check that transmitting a new buffer when the buffer pipe is full
- // fails gracefully.
- pb1 := make([]byte, 104)
- pb2 := make([]byte, 104)
-
- var rxp pipe.Rx
- rxp.Init(pb1)
-
- var txp pipe.Tx
- txp.Init(pb2)
-
- var q Tx
- q.Init(pb1, pb2)
-
- // Transmit twice, which should fill the tx pipe.
- b := []TxBuffer{
- {nil, 100, 60},
- {nil, 200, 40},
- }
-
- b[0].Next = &b[1]
-
- const usedID = 1002
- const usedTotalSize = 100
- for i := uint64(0); i < 2; i++ {
- if !q.Enqueue(usedID+i, usedTotalSize, 2, &b[0]) {
- t.Fatalf("Failed to transmit buffer")
- }
- }
-
- // Transmit another packet now that the tx pipe is full.
- if q.Enqueue(usedID+2, usedTotalSize, 2, &b[0]) {
- t.Fatalf("Enqueue succeeded when tx pipe is full")
- }
-}
-
-func TestFillRxPipe(t *testing.T) {
- // Check that posting a new buffer when the buffer pipe is full fails
- // gracefully.
- pb1 := make([]byte, 100)
- pb2 := make([]byte, 100)
-
- var rxp pipe.Rx
- rxp.Init(pb1)
-
- var txp pipe.Tx
- txp.Init(pb2)
-
- var q Rx
- q.Init(pb1, pb2, nil)
-
- // Post a buffer twice, it should fill the tx pipe.
- b := []RxBuffer{
- {100, 60, 1077, 0},
- }
-
- for i := 0; i < 2; i++ {
- if !q.PostBuffers(b) {
- t.Fatalf("PostBuffers failed on non-full queue")
- }
- }
-
- // Post another buffer now that the tx pipe is full.
- if q.PostBuffers(b) {
- t.Fatalf("PostBuffers succeeded on full queue")
- }
-}
-
-func TestLotsOfTransmissions(t *testing.T) {
- // Make sure pipes are being properly flushed when transmitting packets.
- pb1 := make([]byte, 100)
- pb2 := make([]byte, 100)
-
- var rxp pipe.Rx
- rxp.Init(pb1)
-
- var txp pipe.Tx
- txp.Init(pb2)
-
- var q Tx
- q.Init(pb1, pb2)
-
- // Prepare packet with two buffers.
- b := []TxBuffer{
- {nil, 100, 60},
- {nil, 200, 40},
- }
-
- b[0].Next = &b[1]
-
- const usedID = 1002
- const usedTotalSize = 100
-
- // Post 100000 packets and completions.
- for i := 100000; i > 0; i-- {
- if !q.Enqueue(usedID, usedTotalSize, 2, &b[0]) {
- t.Fatalf("Enqueue failed on non-full queue")
- }
-
- if d := rxp.Pull(); d == nil {
- t.Fatalf("Tx pipe is empty after Enqueue")
- }
- rxp.Flush()
-
- d := txp.Push(8)
- if d == nil {
- t.Fatalf("Unable to write to rx pipe")
- }
- binary.LittleEndian.PutUint64(d, usedID)
- txp.Flush()
- if _, ok := q.CompletedPacket(); !ok {
- t.Fatalf("Completion not returned")
- }
- }
-}
-
-func TestLotsOfReceptions(t *testing.T) {
- // Make sure pipes are being properly flushed when receiving packets.
- pb1 := make([]byte, 100)
- pb2 := make([]byte, 100)
-
- var rxp pipe.Rx
- rxp.Init(pb1)
-
- var txp pipe.Tx
- txp.Init(pb2)
-
- var q Rx
- q.Init(pb1, pb2, nil)
-
- // Prepare for posting two buffers.
- b := []RxBuffer{
- {100, 60, 1077, 0},
- {200, 40, 2123, 0},
- }
-
- // Post 100000 buffers and completions.
- for i := 100000; i > 0; i-- {
- if !q.PostBuffers(b) {
- t.Fatalf("PostBuffers failed on non-full queue")
- }
-
- if d := rxp.Pull(); d == nil {
- t.Fatalf("Tx pipe is empty after PostBuffers")
- }
- rxp.Flush()
-
- if d := rxp.Pull(); d == nil {
- t.Fatalf("Tx pipe is empty after PostBuffers")
- }
- rxp.Flush()
-
- d := txp.Push(sizeOfConsumedPacketHeader + 2*sizeOfConsumedBuffer)
- if d == nil {
- t.Fatalf("Unable to push to rx pipe")
- }
-
- copy(d, []byte{
- 100, 0, 0, 0, // packet size
- 0, 0, 0, 0, // reserved
-
- 100, 0, 0, 0, 0, 0, 0, 0, // offset 1
- 60, 0, 0, 0, // size 1
- 0, 0, 0, 0, 0, 0, 0, 0, // user data 1
- 53, 4, 0, 0, 0, 0, 0, 0, // ID 1
-
- 200, 0, 0, 0, 0, 0, 0, 0, // offset 2
- 40, 0, 0, 0, // size 2
- 0, 0, 0, 0, 0, 0, 0, 0, // user data 2
- 75, 8, 0, 0, 0, 0, 0, 0, // ID 2
- })
-
- txp.Flush()
-
- if _, n := q.Dequeue(nil); n == 0 {
- t.Fatalf("Dequeue failed when there is a completion")
- }
- }
-}
-
-func TestRxEnableNotification(t *testing.T) {
- // Check that enabling nofifications results in properly updated state.
- pb1 := make([]byte, 100)
- pb2 := make([]byte, 100)
-
- var state uint32
- var q Rx
- q.Init(pb1, pb2, &state)
-
- q.EnableNotification()
- if state != eventFDEnabled {
- t.Fatalf("Bad value in shared state: got %v, want %v", state, eventFDEnabled)
- }
-}
-
-func TestRxDisableNotification(t *testing.T) {
- // Check that disabling nofifications results in properly updated state.
- pb1 := make([]byte, 100)
- pb2 := make([]byte, 100)
-
- var state uint32
- var q Rx
- q.Init(pb1, pb2, &state)
-
- q.DisableNotification()
- if state != eventFDDisabled {
- t.Fatalf("Bad value in shared state: got %v, want %v", state, eventFDDisabled)
- }
-}
diff --git a/pkg/tcpip/link/sharedmem/queue/rx.go b/pkg/tcpip/link/sharedmem/queue/rx.go
deleted file mode 100644
index 696e6c9e5..000000000
--- a/pkg/tcpip/link/sharedmem/queue/rx.go
+++ /dev/null
@@ -1,221 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package queue provides the implementation of transmit and receive queues
-// based on shared memory ring buffers.
-package queue
-
-import (
- "encoding/binary"
- "sync/atomic"
-
- "gvisor.dev/gvisor/pkg/log"
- "gvisor.dev/gvisor/pkg/tcpip/link/sharedmem/pipe"
-)
-
-const (
- // Offsets within a posted buffer.
- postedOffset = 0
- postedSize = 8
- postedRemainingInGroup = 12
- postedUserData = 16
- postedID = 24
-
- sizeOfPostedBuffer = 32
-
- // Offsets within a received packet header.
- consumedPacketSize = 0
- consumedPacketReserved = 4
-
- sizeOfConsumedPacketHeader = 8
-
- // Offsets within a consumed buffer.
- consumedOffset = 0
- consumedSize = 8
- consumedUserData = 12
- consumedID = 20
-
- sizeOfConsumedBuffer = 28
-
- // The following are the allowed states of the shared data area.
- eventFDUninitialized = 0
- eventFDDisabled = 1
- eventFDEnabled = 2
-)
-
-// RxBuffer is the descriptor of a receive buffer.
-type RxBuffer struct {
- Offset uint64
- Size uint32
- ID uint64
- UserData uint64
-}
-
-// Rx is a receive queue. It is implemented with one tx and one rx pipe: the tx
-// pipe is used to "post" buffers, while the rx pipe is used to receive packets
-// whose contents have been written to previously posted buffers.
-//
-// This struct is thread-compatible.
-type Rx struct {
- tx pipe.Tx
- rx pipe.Rx
- sharedEventFDState *uint32
-}
-
-// Init initializes the receive queue with the given pipes, and shared state
-// pointer -- the latter is used to enable/disable eventfd notifications.
-func (r *Rx) Init(tx, rx []byte, sharedEventFDState *uint32) {
- r.sharedEventFDState = sharedEventFDState
- r.tx.Init(tx)
- r.rx.Init(rx)
-}
-
-// EnableNotification updates the shared state such that the peer will notify
-// the eventfd when there are packets to be dequeued.
-func (r *Rx) EnableNotification() {
- atomic.StoreUint32(r.sharedEventFDState, eventFDEnabled)
-}
-
-// DisableNotification updates the shared state such that the peer will not
-// notify the eventfd.
-func (r *Rx) DisableNotification() {
- atomic.StoreUint32(r.sharedEventFDState, eventFDDisabled)
-}
-
-// PostedBuffersLimit returns the maximum number of buffers that can be posted
-// before the tx queue fills up.
-func (r *Rx) PostedBuffersLimit() uint64 {
- return r.tx.Capacity(sizeOfPostedBuffer)
-}
-
-// PostBuffers makes the given buffers available for receiving data from the
-// peer. Once they are posted, the peer is free to write to them and will
-// eventually post them back for consumption.
-func (r *Rx) PostBuffers(buffers []RxBuffer) bool {
- for i := range buffers {
- b := r.tx.Push(sizeOfPostedBuffer)
- if b == nil {
- r.tx.Abort()
- return false
- }
-
- pb := &buffers[i]
- binary.LittleEndian.PutUint64(b[postedOffset:], pb.Offset)
- binary.LittleEndian.PutUint32(b[postedSize:], pb.Size)
- binary.LittleEndian.PutUint32(b[postedRemainingInGroup:], 0)
- binary.LittleEndian.PutUint64(b[postedUserData:], pb.UserData)
- binary.LittleEndian.PutUint64(b[postedID:], pb.ID)
- }
-
- r.tx.Flush()
-
- return true
-}
-
-// Dequeue receives buffers that have been previously posted by PostBuffers()
-// and that have been filled by the peer and posted back.
-//
-// This is similar to append() in that new buffers are appended to "bufs", with
-// reallocation only if "bufs" doesn't have enough capacity.
-func (r *Rx) Dequeue(bufs []RxBuffer) ([]RxBuffer, uint32) {
- for {
- outBufs := bufs
-
- // Pull the next descriptor from the rx pipe.
- b := r.rx.Pull()
- if b == nil {
- return bufs, 0
- }
-
- if len(b) < sizeOfConsumedPacketHeader {
- log.Warningf("Ignoring packet header: size (%v) is less than header size (%v)", len(b), sizeOfConsumedPacketHeader)
- r.rx.Flush()
- continue
- }
-
- totalDataSize := binary.LittleEndian.Uint32(b[consumedPacketSize:])
-
- // Calculate the number of buffer descriptors and copy them
- // over to the output.
- count := (len(b) - sizeOfConsumedPacketHeader) / sizeOfConsumedBuffer
- offset := sizeOfConsumedPacketHeader
- buffersSize := uint32(0)
- for i := count; i > 0; i-- {
- s := binary.LittleEndian.Uint32(b[offset+consumedSize:])
- buffersSize += s
- if buffersSize < s {
- // The buffer size overflows an unsigned 32-bit
- // integer, so break out and force it to be
- // ignored.
- totalDataSize = 1
- buffersSize = 0
- break
- }
-
- outBufs = append(outBufs, RxBuffer{
- Offset: binary.LittleEndian.Uint64(b[offset+consumedOffset:]),
- Size: s,
- ID: binary.LittleEndian.Uint64(b[offset+consumedID:]),
- })
-
- offset += sizeOfConsumedBuffer
- }
-
- r.rx.Flush()
-
- if buffersSize < totalDataSize {
- // The descriptor is corrupted, ignore it.
- log.Warningf("Ignoring packet: actual data size (%v) less than expected size (%v)", buffersSize, totalDataSize)
- continue
- }
-
- return outBufs, totalDataSize
- }
-}
-
-// Bytes returns the byte slices on which the queue operates.
-func (r *Rx) Bytes() (tx, rx []byte) {
- return r.tx.Bytes(), r.rx.Bytes()
-}
-
-// DecodeRxBufferHeader decodes the header of a buffer posted on an rx queue.
-func DecodeRxBufferHeader(b []byte) RxBuffer {
- return RxBuffer{
- Offset: binary.LittleEndian.Uint64(b[postedOffset:]),
- Size: binary.LittleEndian.Uint32(b[postedSize:]),
- ID: binary.LittleEndian.Uint64(b[postedID:]),
- UserData: binary.LittleEndian.Uint64(b[postedUserData:]),
- }
-}
-
-// RxCompletionSize returns the number of bytes needed to encode an rx
-// completion containing "count" buffers.
-func RxCompletionSize(count int) uint64 {
- return sizeOfConsumedPacketHeader + uint64(count)*sizeOfConsumedBuffer
-}
-
-// EncodeRxCompletion encodes an rx completion header.
-func EncodeRxCompletion(b []byte, size, reserved uint32) {
- binary.LittleEndian.PutUint32(b[consumedPacketSize:], size)
- binary.LittleEndian.PutUint32(b[consumedPacketReserved:], reserved)
-}
-
-// EncodeRxCompletionBuffer encodes the i-th rx completion buffer header.
-func EncodeRxCompletionBuffer(b []byte, i int, rxb RxBuffer) {
- b = b[RxCompletionSize(i):]
- binary.LittleEndian.PutUint64(b[consumedOffset:], rxb.Offset)
- binary.LittleEndian.PutUint32(b[consumedSize:], rxb.Size)
- binary.LittleEndian.PutUint64(b[consumedUserData:], rxb.UserData)
- binary.LittleEndian.PutUint64(b[consumedID:], rxb.ID)
-}
diff --git a/pkg/tcpip/link/sharedmem/queue/tx.go b/pkg/tcpip/link/sharedmem/queue/tx.go
deleted file mode 100644
index beffe807b..000000000
--- a/pkg/tcpip/link/sharedmem/queue/tx.go
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package queue
-
-import (
- "encoding/binary"
-
- "gvisor.dev/gvisor/pkg/log"
- "gvisor.dev/gvisor/pkg/tcpip/link/sharedmem/pipe"
-)
-
-const (
- // Offsets within a packet header.
- packetID = 0
- packetSize = 8
- packetReserved = 12
-
- sizeOfPacketHeader = 16
-
- // Offsets with a buffer descriptor
- bufferOffset = 0
- bufferSize = 8
-
- sizeOfBufferDescriptor = 12
-)
-
-// TxBuffer is the descriptor of a transmit buffer.
-type TxBuffer struct {
- Next *TxBuffer
- Offset uint64
- Size uint32
-}
-
-// Tx is a transmit queue. It is implemented with one tx and one rx pipe: the
-// tx pipe is used to request the transmission of packets, while the rx pipe
-// is used to receive which transmissions have completed.
-//
-// This struct is thread-compatible.
-type Tx struct {
- tx pipe.Tx
- rx pipe.Rx
-}
-
-// Init initializes the transmit queue with the given pipes.
-func (t *Tx) Init(tx, rx []byte) {
- t.tx.Init(tx)
- t.rx.Init(rx)
-}
-
-// Enqueue queues the given linked list of buffers for transmission as one
-// packet. While it is queued, the caller must not modify them.
-func (t *Tx) Enqueue(id uint64, totalDataLen, bufferCount uint32, buffer *TxBuffer) bool {
- // Reserve room in the tx pipe.
- totalLen := sizeOfPacketHeader + uint64(bufferCount)*sizeOfBufferDescriptor
-
- b := t.tx.Push(totalLen)
- if b == nil {
- return false
- }
-
- // Initialize the packet and buffer descriptors.
- binary.LittleEndian.PutUint64(b[packetID:], id)
- binary.LittleEndian.PutUint32(b[packetSize:], totalDataLen)
- binary.LittleEndian.PutUint32(b[packetReserved:], 0)
-
- offset := sizeOfPacketHeader
- for i := bufferCount; i != 0; i-- {
- binary.LittleEndian.PutUint64(b[offset+bufferOffset:], buffer.Offset)
- binary.LittleEndian.PutUint32(b[offset+bufferSize:], buffer.Size)
- offset += sizeOfBufferDescriptor
- buffer = buffer.Next
- }
-
- t.tx.Flush()
-
- return true
-}
-
-// CompletedPacket returns the id of the last completed transmission. The
-// returned id, if any, refers to a value passed on a previous call to
-// Enqueue().
-func (t *Tx) CompletedPacket() (id uint64, ok bool) {
- for {
- b := t.rx.Pull()
- if b == nil {
- return 0, false
- }
-
- if len(b) != 8 {
- t.rx.Flush()
- log.Warningf("Ignoring completed packet: size (%v) is less than expected (%v)", len(b), 8)
- continue
- }
-
- v := binary.LittleEndian.Uint64(b)
-
- t.rx.Flush()
-
- return v, true
- }
-}
-
-// Bytes returns the byte slices on which the queue operates.
-func (t *Tx) Bytes() (tx, rx []byte) {
- return t.tx.Bytes(), t.rx.Bytes()
-}
-
-// TxPacketInfo holds information about a packet sent on a tx queue.
-type TxPacketInfo struct {
- ID uint64
- Size uint32
- Reserved uint32
- BufferCount int
-}
-
-// DecodeTxPacketHeader decodes the header of a packet sent over a tx queue.
-func DecodeTxPacketHeader(b []byte) TxPacketInfo {
- return TxPacketInfo{
- ID: binary.LittleEndian.Uint64(b[packetID:]),
- Size: binary.LittleEndian.Uint32(b[packetSize:]),
- Reserved: binary.LittleEndian.Uint32(b[packetReserved:]),
- BufferCount: (len(b) - sizeOfPacketHeader) / sizeOfBufferDescriptor,
- }
-}
-
-// DecodeTxBufferHeader decodes the header of the i-th buffer of a packet sent
-// over a tx queue.
-func DecodeTxBufferHeader(b []byte, i int) TxBuffer {
- b = b[sizeOfPacketHeader+i*sizeOfBufferDescriptor:]
- return TxBuffer{
- Offset: binary.LittleEndian.Uint64(b[bufferOffset:]),
- Size: binary.LittleEndian.Uint32(b[bufferSize:]),
- }
-}
-
-// EncodeTxCompletion encodes a tx completion header.
-func EncodeTxCompletion(b []byte, id uint64) {
- binary.LittleEndian.PutUint64(b, id)
-}
diff --git a/pkg/tcpip/link/sharedmem/rx.go b/pkg/tcpip/link/sharedmem/rx.go
deleted file mode 100644
index eec11e4cb..000000000
--- a/pkg/tcpip/link/sharedmem/rx.go
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// +build linux
-
-package sharedmem
-
-import (
- "sync/atomic"
- "syscall"
-
- "gvisor.dev/gvisor/pkg/tcpip/link/rawfile"
- "gvisor.dev/gvisor/pkg/tcpip/link/sharedmem/queue"
-)
-
-// rx holds all state associated with an rx queue.
-type rx struct {
- data []byte
- sharedData []byte
- q queue.Rx
- eventFD int
-}
-
-// init initializes all state needed by the rx queue based on the information
-// provided.
-//
-// The caller always retains ownership of all file descriptors passed in. The
-// queue implementation will duplicate any that it may need in the future.
-func (r *rx) init(mtu uint32, c *QueueConfig) error {
- // Map in all buffers.
- txPipe, err := getBuffer(c.TxPipeFD)
- if err != nil {
- return err
- }
-
- rxPipe, err := getBuffer(c.RxPipeFD)
- if err != nil {
- syscall.Munmap(txPipe)
- return err
- }
-
- data, err := getBuffer(c.DataFD)
- if err != nil {
- syscall.Munmap(txPipe)
- syscall.Munmap(rxPipe)
- return err
- }
-
- sharedData, err := getBuffer(c.SharedDataFD)
- if err != nil {
- syscall.Munmap(txPipe)
- syscall.Munmap(rxPipe)
- syscall.Munmap(data)
- return err
- }
-
- // Duplicate the eventFD so that caller can close it but we can still
- // use it.
- efd, err := syscall.Dup(c.EventFD)
- if err != nil {
- syscall.Munmap(txPipe)
- syscall.Munmap(rxPipe)
- syscall.Munmap(data)
- syscall.Munmap(sharedData)
- return err
- }
-
- // Set the eventfd as non-blocking.
- if err := syscall.SetNonblock(efd, true); err != nil {
- syscall.Munmap(txPipe)
- syscall.Munmap(rxPipe)
- syscall.Munmap(data)
- syscall.Munmap(sharedData)
- syscall.Close(efd)
- return err
- }
-
- // Initialize state based on buffers.
- r.q.Init(txPipe, rxPipe, sharedDataPointer(sharedData))
- r.data = data
- r.eventFD = efd
- r.sharedData = sharedData
-
- return nil
-}
-
-// cleanup releases all resources allocated during init(). It must only be
-// called if init() has previously succeeded.
-func (r *rx) cleanup() {
- a, b := r.q.Bytes()
- syscall.Munmap(a)
- syscall.Munmap(b)
-
- syscall.Munmap(r.data)
- syscall.Munmap(r.sharedData)
- syscall.Close(r.eventFD)
-}
-
-// postAndReceive posts the provided buffers (if any), and then tries to read
-// from the receive queue.
-//
-// Capacity permitting, it reuses the posted buffer slice to store the buffers
-// that were read as well.
-//
-// This function will block if there aren't any available packets.
-func (r *rx) postAndReceive(b []queue.RxBuffer, stopRequested *uint32) ([]queue.RxBuffer, uint32) {
- // Post the buffers first. If we cannot post, sleep until we can. We
- // never post more than will fit concurrently, so it's safe to wait
- // until enough room is available.
- if len(b) != 0 && !r.q.PostBuffers(b) {
- r.q.EnableNotification()
- for !r.q.PostBuffers(b) {
- var tmp [8]byte
- rawfile.BlockingRead(r.eventFD, tmp[:])
- if atomic.LoadUint32(stopRequested) != 0 {
- r.q.DisableNotification()
- return nil, 0
- }
- }
- r.q.DisableNotification()
- }
-
- // Read the next set of descriptors.
- b, n := r.q.Dequeue(b[:0])
- if len(b) != 0 {
- return b, n
- }
-
- // Data isn't immediately available. Enable eventfd notifications.
- r.q.EnableNotification()
- for {
- b, n = r.q.Dequeue(b)
- if len(b) != 0 {
- break
- }
-
- // Wait for notification.
- var tmp [8]byte
- rawfile.BlockingRead(r.eventFD, tmp[:])
- if atomic.LoadUint32(stopRequested) != 0 {
- r.q.DisableNotification()
- return nil, 0
- }
- }
- r.q.DisableNotification()
-
- return b, n
-}
diff --git a/pkg/tcpip/link/sharedmem/sharedmem.go b/pkg/tcpip/link/sharedmem/sharedmem.go
deleted file mode 100644
index ba387af73..000000000
--- a/pkg/tcpip/link/sharedmem/sharedmem.go
+++ /dev/null
@@ -1,264 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// +build linux
-
-// Package sharedmem provides the implemention of data-link layer endpoints
-// backed by shared memory.
-//
-// Shared memory endpoints can be used in the networking stack by calling New()
-// to create a new endpoint, and then passing it as an argument to
-// Stack.CreateNIC().
-package sharedmem
-
-import (
- "sync"
- "sync/atomic"
- "syscall"
-
- "gvisor.dev/gvisor/pkg/log"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/link/sharedmem/queue"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-// QueueConfig holds all the file descriptors needed to describe a tx or rx
-// queue over shared memory. It is used when creating new shared memory
-// endpoints to describe tx and rx queues.
-type QueueConfig struct {
- // DataFD is a file descriptor for the file that contains the data to
- // be transmitted via this queue. Descriptors contain offsets within
- // this file.
- DataFD int
-
- // EventFD is a file descriptor for the event that is signaled when
- // data is becomes available in this queue.
- EventFD int
-
- // TxPipeFD is a file descriptor for the tx pipe associated with the
- // queue.
- TxPipeFD int
-
- // RxPipeFD is a file descriptor for the rx pipe associated with the
- // queue.
- RxPipeFD int
-
- // SharedDataFD is a file descriptor for the file that contains shared
- // state between the two ends of the queue. This data specifies, for
- // example, whether EventFD signaling is enabled or disabled.
- SharedDataFD int
-}
-
-type endpoint struct {
- // mtu (maximum transmission unit) is the maximum size of a packet.
- mtu uint32
-
- // bufferSize is the size of each individual buffer.
- bufferSize uint32
-
- // addr is the local address of this endpoint.
- addr tcpip.LinkAddress
-
- // rx is the receive queue.
- rx rx
-
- // stopRequested is to be accessed atomically only, and determines if
- // the worker goroutines should stop.
- stopRequested uint32
-
- // Wait group used to indicate that all workers have stopped.
- completed sync.WaitGroup
-
- // mu protects the following fields.
- mu sync.Mutex
-
- // tx is the transmit queue.
- tx tx
-
- // workerStarted specifies whether the worker goroutine was started.
- workerStarted bool
-}
-
-// New creates a new shared-memory-based endpoint. Buffers will be broken up
-// into buffers of "bufferSize" bytes.
-func New(mtu, bufferSize uint32, addr tcpip.LinkAddress, tx, rx QueueConfig) (stack.LinkEndpoint, error) {
- e := &endpoint{
- mtu: mtu,
- bufferSize: bufferSize,
- addr: addr,
- }
-
- if err := e.tx.init(bufferSize, &tx); err != nil {
- return nil, err
- }
-
- if err := e.rx.init(bufferSize, &rx); err != nil {
- e.tx.cleanup()
- return nil, err
- }
-
- return e, nil
-}
-
-// Close frees all resources associated with the endpoint.
-func (e *endpoint) Close() {
- // Tell dispatch goroutine to stop, then write to the eventfd so that
- // it wakes up in case it's sleeping.
- atomic.StoreUint32(&e.stopRequested, 1)
- syscall.Write(e.rx.eventFD, []byte{1, 0, 0, 0, 0, 0, 0, 0})
-
- // Cleanup the queues inline if the worker hasn't started yet; we also
- // know it won't start from now on because stopRequested is set to 1.
- e.mu.Lock()
- workerPresent := e.workerStarted
- e.mu.Unlock()
-
- if !workerPresent {
- e.tx.cleanup()
- e.rx.cleanup()
- }
-}
-
-// Wait waits until all workers have stopped after a Close() call.
-func (e *endpoint) Wait() {
- e.completed.Wait()
-}
-
-// Attach implements stack.LinkEndpoint.Attach. It launches the goroutine that
-// reads packets from the rx queue.
-func (e *endpoint) Attach(dispatcher stack.NetworkDispatcher) {
- e.mu.Lock()
- if !e.workerStarted && atomic.LoadUint32(&e.stopRequested) == 0 {
- e.workerStarted = true
- e.completed.Add(1)
- // Link endpoints are not savable. When transportation endpoints
- // are saved, they stop sending outgoing packets and all
- // incoming packets are rejected.
- go e.dispatchLoop(dispatcher) // S/R-SAFE: see above.
- }
- e.mu.Unlock()
-}
-
-// IsAttached implements stack.LinkEndpoint.IsAttached.
-func (e *endpoint) IsAttached() bool {
- e.mu.Lock()
- defer e.mu.Unlock()
- return e.workerStarted
-}
-
-// MTU implements stack.LinkEndpoint.MTU. It returns the value initialized
-// during construction.
-func (e *endpoint) MTU() uint32 {
- return e.mtu - header.EthernetMinimumSize
-}
-
-// Capabilities implements stack.LinkEndpoint.Capabilities.
-func (*endpoint) Capabilities() stack.LinkEndpointCapabilities {
- return 0
-}
-
-// MaxHeaderLength implements stack.LinkEndpoint.MaxHeaderLength. It returns the
-// ethernet frame header size.
-func (*endpoint) MaxHeaderLength() uint16 {
- return header.EthernetMinimumSize
-}
-
-// LinkAddress implements stack.LinkEndpoint.LinkAddress. It returns the local
-// link address.
-func (e *endpoint) LinkAddress() tcpip.LinkAddress {
- return e.addr
-}
-
-// WritePacket writes outbound packets to the file descriptor. If it is not
-// currently writable, the packet is dropped.
-func (e *endpoint) WritePacket(r *stack.Route, _ *stack.GSO, hdr buffer.Prependable, payload buffer.VectorisedView, protocol tcpip.NetworkProtocolNumber) *tcpip.Error {
- // Add the ethernet header here.
- eth := header.Ethernet(hdr.Prepend(header.EthernetMinimumSize))
- ethHdr := &header.EthernetFields{
- DstAddr: r.RemoteLinkAddress,
- Type: protocol,
- }
- if r.LocalLinkAddress != "" {
- ethHdr.SrcAddr = r.LocalLinkAddress
- } else {
- ethHdr.SrcAddr = e.addr
- }
- eth.Encode(ethHdr)
-
- v := payload.ToView()
- // Transmit the packet.
- e.mu.Lock()
- ok := e.tx.transmit(hdr.View(), v)
- e.mu.Unlock()
-
- if !ok {
- return tcpip.ErrWouldBlock
- }
-
- return nil
-}
-
-// dispatchLoop reads packets from the rx queue in a loop and dispatches them
-// to the network stack.
-func (e *endpoint) dispatchLoop(d stack.NetworkDispatcher) {
- // Post initial set of buffers.
- limit := e.rx.q.PostedBuffersLimit()
- if l := uint64(len(e.rx.data)) / uint64(e.bufferSize); limit > l {
- limit = l
- }
- for i := uint64(0); i < limit; i++ {
- b := queue.RxBuffer{
- Offset: i * uint64(e.bufferSize),
- Size: e.bufferSize,
- ID: i,
- }
- if !e.rx.q.PostBuffers([]queue.RxBuffer{b}) {
- log.Warningf("Unable to post %v-th buffer", i)
- }
- }
-
- // Read in a loop until a stop is requested.
- var rxb []queue.RxBuffer
- for atomic.LoadUint32(&e.stopRequested) == 0 {
- var n uint32
- rxb, n = e.rx.postAndReceive(rxb, &e.stopRequested)
-
- // Copy data from the shared area to its own buffer, then
- // prepare to repost the buffer.
- b := make([]byte, n)
- offset := uint32(0)
- for i := range rxb {
- copy(b[offset:], e.rx.data[rxb[i].Offset:][:rxb[i].Size])
- offset += rxb[i].Size
-
- rxb[i].Size = e.bufferSize
- }
-
- if n < header.EthernetMinimumSize {
- continue
- }
-
- // Send packet up the stack.
- eth := header.Ethernet(b)
- d.DeliverNetworkPacket(e, eth.SourceAddress(), eth.DestinationAddress(), eth.Type(), buffer.View(b[header.EthernetMinimumSize:]).ToVectorisedView())
- }
-
- // Clean state.
- e.tx.cleanup()
- e.rx.cleanup()
-
- e.completed.Done()
-}
diff --git a/pkg/tcpip/link/sharedmem/sharedmem_test.go b/pkg/tcpip/link/sharedmem/sharedmem_test.go
deleted file mode 100644
index 0e9ba0846..000000000
--- a/pkg/tcpip/link/sharedmem/sharedmem_test.go
+++ /dev/null
@@ -1,776 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// +build linux
-
-package sharedmem
-
-import (
- "bytes"
- "io/ioutil"
- "math/rand"
- "os"
- "strings"
- "sync"
- "syscall"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/link/sharedmem/pipe"
- "gvisor.dev/gvisor/pkg/tcpip/link/sharedmem/queue"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-const (
- localLinkAddr = "\xde\xad\xbe\xef\x56\x78"
- remoteLinkAddr = "\xde\xad\xbe\xef\x12\x34"
-
- queueDataSize = 1024 * 1024
- queuePipeSize = 4096
-)
-
-type queueBuffers struct {
- data []byte
- rx pipe.Tx
- tx pipe.Rx
-}
-
-func initQueue(t *testing.T, q *queueBuffers, c *QueueConfig) {
- // Prepare tx pipe.
- b, err := getBuffer(c.TxPipeFD)
- if err != nil {
- t.Fatalf("getBuffer failed: %v", err)
- }
- q.tx.Init(b)
-
- // Prepare rx pipe.
- b, err = getBuffer(c.RxPipeFD)
- if err != nil {
- t.Fatalf("getBuffer failed: %v", err)
- }
- q.rx.Init(b)
-
- // Get data slice.
- q.data, err = getBuffer(c.DataFD)
- if err != nil {
- t.Fatalf("getBuffer failed: %v", err)
- }
-}
-
-func (q *queueBuffers) cleanup() {
- syscall.Munmap(q.tx.Bytes())
- syscall.Munmap(q.rx.Bytes())
- syscall.Munmap(q.data)
-}
-
-type packetInfo struct {
- addr tcpip.LinkAddress
- proto tcpip.NetworkProtocolNumber
- vv buffer.VectorisedView
-}
-
-type testContext struct {
- t *testing.T
- ep *endpoint
- txCfg QueueConfig
- rxCfg QueueConfig
- txq queueBuffers
- rxq queueBuffers
-
- packetCh chan struct{}
- mu sync.Mutex
- packets []packetInfo
-}
-
-func newTestContext(t *testing.T, mtu, bufferSize uint32, addr tcpip.LinkAddress) *testContext {
- var err error
- c := &testContext{
- t: t,
- packetCh: make(chan struct{}, 1000000),
- }
- c.txCfg = createQueueFDs(t, queueSizes{
- dataSize: queueDataSize,
- txPipeSize: queuePipeSize,
- rxPipeSize: queuePipeSize,
- sharedDataSize: 4096,
- })
-
- c.rxCfg = createQueueFDs(t, queueSizes{
- dataSize: queueDataSize,
- txPipeSize: queuePipeSize,
- rxPipeSize: queuePipeSize,
- sharedDataSize: 4096,
- })
-
- initQueue(t, &c.txq, &c.txCfg)
- initQueue(t, &c.rxq, &c.rxCfg)
-
- ep, err := New(mtu, bufferSize, addr, c.txCfg, c.rxCfg)
- if err != nil {
- t.Fatalf("New failed: %v", err)
- }
-
- c.ep = ep.(*endpoint)
- c.ep.Attach(c)
-
- return c
-}
-
-func (c *testContext) DeliverNetworkPacket(_ stack.LinkEndpoint, remoteLinkAddr, localLinkAddr tcpip.LinkAddress, proto tcpip.NetworkProtocolNumber, vv buffer.VectorisedView) {
- c.mu.Lock()
- c.packets = append(c.packets, packetInfo{
- addr: remoteLinkAddr,
- proto: proto,
- vv: vv.Clone(nil),
- })
- c.mu.Unlock()
-
- c.packetCh <- struct{}{}
-}
-
-func (c *testContext) cleanup() {
- c.ep.Close()
- closeFDs(&c.txCfg)
- closeFDs(&c.rxCfg)
- c.txq.cleanup()
- c.rxq.cleanup()
-}
-
-func (c *testContext) waitForPackets(n int, to <-chan time.Time, errorStr string) {
- for i := 0; i < n; i++ {
- select {
- case <-c.packetCh:
- case <-to:
- c.t.Fatalf(errorStr)
- }
- }
-}
-
-func (c *testContext) pushRxCompletion(size uint32, bs []queue.RxBuffer) {
- b := c.rxq.rx.Push(queue.RxCompletionSize(len(bs)))
- queue.EncodeRxCompletion(b, size, 0)
- for i := range bs {
- queue.EncodeRxCompletionBuffer(b, i, queue.RxBuffer{
- Offset: bs[i].Offset,
- Size: bs[i].Size,
- ID: bs[i].ID,
- })
- }
-}
-
-func randomFill(b []byte) {
- for i := range b {
- b[i] = byte(rand.Intn(256))
- }
-}
-
-func shuffle(b []int) {
- for i := len(b) - 1; i >= 0; i-- {
- j := rand.Intn(i + 1)
- b[i], b[j] = b[j], b[i]
- }
-}
-
-func createFile(t *testing.T, size int64, initQueue bool) int {
- tmpDir := os.Getenv("TEST_TMPDIR")
- if tmpDir == "" {
- tmpDir = os.Getenv("TMPDIR")
- }
- f, err := ioutil.TempFile(tmpDir, "sharedmem_test")
- if err != nil {
- t.Fatalf("TempFile failed: %v", err)
- }
- defer f.Close()
- syscall.Unlink(f.Name())
-
- if initQueue {
- // Write the "slot-free" flag in the initial queue.
- _, err := f.WriteAt([]byte{0, 0, 0, 0, 0, 0, 0, 0x80}, 0)
- if err != nil {
- t.Fatalf("WriteAt failed: %v", err)
- }
- }
-
- fd, err := syscall.Dup(int(f.Fd()))
- if err != nil {
- t.Fatalf("Dup failed: %v", err)
- }
-
- if err := syscall.Ftruncate(fd, size); err != nil {
- syscall.Close(fd)
- t.Fatalf("Ftruncate failed: %v", err)
- }
-
- return fd
-}
-
-func closeFDs(c *QueueConfig) {
- syscall.Close(c.DataFD)
- syscall.Close(c.EventFD)
- syscall.Close(c.TxPipeFD)
- syscall.Close(c.RxPipeFD)
- syscall.Close(c.SharedDataFD)
-}
-
-type queueSizes struct {
- dataSize int64
- txPipeSize int64
- rxPipeSize int64
- sharedDataSize int64
-}
-
-func createQueueFDs(t *testing.T, s queueSizes) QueueConfig {
- fd, _, err := syscall.RawSyscall(syscall.SYS_EVENTFD2, 0, 0, 0)
- if err != 0 {
- t.Fatalf("eventfd failed: %v", error(err))
- }
-
- return QueueConfig{
- EventFD: int(fd),
- DataFD: createFile(t, s.dataSize, false),
- TxPipeFD: createFile(t, s.txPipeSize, true),
- RxPipeFD: createFile(t, s.rxPipeSize, true),
- SharedDataFD: createFile(t, s.sharedDataSize, false),
- }
-}
-
-// TestSimpleSend sends 1000 packets with random header and payload sizes,
-// then checks that the right payload is received on the shared memory queues.
-func TestSimpleSend(t *testing.T) {
- c := newTestContext(t, 20000, 1500, localLinkAddr)
- defer c.cleanup()
-
- // Prepare route.
- r := stack.Route{
- RemoteLinkAddress: remoteLinkAddr,
- }
-
- for iters := 1000; iters > 0; iters-- {
- func() {
- // Prepare and send packet.
- n := rand.Intn(10000)
- hdr := buffer.NewPrependable(n + int(c.ep.MaxHeaderLength()))
- hdrBuf := hdr.Prepend(n)
- randomFill(hdrBuf)
-
- n = rand.Intn(10000)
- buf := buffer.NewView(n)
- randomFill(buf)
-
- proto := tcpip.NetworkProtocolNumber(rand.Intn(0x10000))
- if err := c.ep.WritePacket(&r, nil /* gso */, hdr, buf.ToVectorisedView(), proto); err != nil {
- t.Fatalf("WritePacket failed: %v", err)
- }
-
- // Receive packet.
- desc := c.txq.tx.Pull()
- pi := queue.DecodeTxPacketHeader(desc)
- if pi.Reserved != 0 {
- t.Fatalf("Reserved value is non-zero: 0x%x", pi.Reserved)
- }
- contents := make([]byte, 0, pi.Size)
- for i := 0; i < pi.BufferCount; i++ {
- bi := queue.DecodeTxBufferHeader(desc, i)
- contents = append(contents, c.txq.data[bi.Offset:][:bi.Size]...)
- }
- c.txq.tx.Flush()
-
- defer func() {
- // Tell the endpoint about the completion of the write.
- b := c.txq.rx.Push(8)
- queue.EncodeTxCompletion(b, pi.ID)
- c.txq.rx.Flush()
- }()
-
- // Check the ethernet header.
- ethTemplate := make(header.Ethernet, header.EthernetMinimumSize)
- ethTemplate.Encode(&header.EthernetFields{
- SrcAddr: localLinkAddr,
- DstAddr: remoteLinkAddr,
- Type: proto,
- })
- if got := contents[:header.EthernetMinimumSize]; !bytes.Equal(got, []byte(ethTemplate)) {
- t.Fatalf("Bad ethernet header in packet: got %x, want %x", got, ethTemplate)
- }
-
- // Compare contents skipping the ethernet header added by the
- // endpoint.
- merged := append(hdrBuf, buf...)
- if uint32(len(contents)) < pi.Size {
- t.Fatalf("Sum of buffers is less than packet size: %v < %v", len(contents), pi.Size)
- }
- contents = contents[:pi.Size][header.EthernetMinimumSize:]
-
- if !bytes.Equal(contents, merged) {
- t.Fatalf("Buffers are different: got %x (%v bytes), want %x (%v bytes)", contents, len(contents), merged, len(merged))
- }
- }()
- }
-}
-
-// TestPreserveSrcAddressInSend calls WritePacket once with LocalLinkAddress
-// set in Route (using much of the same code as TestSimpleSend), then checks
-// that the encoded ethernet header received includes the correct SrcAddr.
-func TestPreserveSrcAddressInSend(t *testing.T) {
- c := newTestContext(t, 20000, 1500, localLinkAddr)
- defer c.cleanup()
-
- newLocalLinkAddress := tcpip.LinkAddress(strings.Repeat("0xFE", 6))
- // Set both remote and local link address in route.
- r := stack.Route{
- RemoteLinkAddress: remoteLinkAddr,
- LocalLinkAddress: newLocalLinkAddress,
- }
-
- // WritePacket panics given a prependable with anything less than
- // the minimum size of the ethernet header.
- hdr := buffer.NewPrependable(header.EthernetMinimumSize)
-
- proto := tcpip.NetworkProtocolNumber(rand.Intn(0x10000))
- if err := c.ep.WritePacket(&r, nil /* gso */, hdr, buffer.VectorisedView{}, proto); err != nil {
- t.Fatalf("WritePacket failed: %v", err)
- }
-
- // Receive packet.
- desc := c.txq.tx.Pull()
- pi := queue.DecodeTxPacketHeader(desc)
- if pi.Reserved != 0 {
- t.Fatalf("Reserved value is non-zero: 0x%x", pi.Reserved)
- }
- contents := make([]byte, 0, pi.Size)
- for i := 0; i < pi.BufferCount; i++ {
- bi := queue.DecodeTxBufferHeader(desc, i)
- contents = append(contents, c.txq.data[bi.Offset:][:bi.Size]...)
- }
- c.txq.tx.Flush()
-
- defer func() {
- // Tell the endpoint about the completion of the write.
- b := c.txq.rx.Push(8)
- queue.EncodeTxCompletion(b, pi.ID)
- c.txq.rx.Flush()
- }()
-
- // Check that the ethernet header contains the expected SrcAddr.
- ethTemplate := make(header.Ethernet, header.EthernetMinimumSize)
- ethTemplate.Encode(&header.EthernetFields{
- SrcAddr: newLocalLinkAddress,
- DstAddr: remoteLinkAddr,
- Type: proto,
- })
- if got := contents[:header.EthernetMinimumSize]; !bytes.Equal(got, []byte(ethTemplate)) {
- t.Fatalf("Bad ethernet header in packet: got %x, want %x", got, ethTemplate)
- }
-}
-
-// TestFillTxQueue sends packets until the queue is full.
-func TestFillTxQueue(t *testing.T) {
- c := newTestContext(t, 20000, 1500, localLinkAddr)
- defer c.cleanup()
-
- // Prepare to send a packet.
- r := stack.Route{
- RemoteLinkAddress: remoteLinkAddr,
- }
-
- buf := buffer.NewView(100)
-
- // Each packet is uses no more than 40 bytes, so write that many packets
- // until the tx queue if full.
- ids := make(map[uint64]struct{})
- for i := queuePipeSize / 40; i > 0; i-- {
- hdr := buffer.NewPrependable(int(c.ep.MaxHeaderLength()))
-
- if err := c.ep.WritePacket(&r, nil /* gso */, hdr, buf.ToVectorisedView(), header.IPv4ProtocolNumber); err != nil {
- t.Fatalf("WritePacket failed unexpectedly: %v", err)
- }
-
- // Check that they have different IDs.
- desc := c.txq.tx.Pull()
- pi := queue.DecodeTxPacketHeader(desc)
- if _, ok := ids[pi.ID]; ok {
- t.Fatalf("ID (%v) reused", pi.ID)
- }
- ids[pi.ID] = struct{}{}
- }
-
- // Next attempt to write must fail.
- hdr := buffer.NewPrependable(int(c.ep.MaxHeaderLength()))
- if want, err := tcpip.ErrWouldBlock, c.ep.WritePacket(&r, nil /* gso */, hdr, buf.ToVectorisedView(), header.IPv4ProtocolNumber); err != want {
- t.Fatalf("WritePacket return unexpected result: got %v, want %v", err, want)
- }
-}
-
-// TestFillTxQueueAfterBadCompletion sends a bad completion, then sends packets
-// until the queue is full.
-func TestFillTxQueueAfterBadCompletion(t *testing.T) {
- c := newTestContext(t, 20000, 1500, localLinkAddr)
- defer c.cleanup()
-
- // Send a bad completion.
- queue.EncodeTxCompletion(c.txq.rx.Push(8), 1)
- c.txq.rx.Flush()
-
- // Prepare to send a packet.
- r := stack.Route{
- RemoteLinkAddress: remoteLinkAddr,
- }
-
- buf := buffer.NewView(100)
-
- // Send two packets so that the id slice has at least two slots.
- for i := 2; i > 0; i-- {
- hdr := buffer.NewPrependable(int(c.ep.MaxHeaderLength()))
- if err := c.ep.WritePacket(&r, nil /* gso */, hdr, buf.ToVectorisedView(), header.IPv4ProtocolNumber); err != nil {
- t.Fatalf("WritePacket failed unexpectedly: %v", err)
- }
- }
-
- // Complete the two writes twice.
- for i := 2; i > 0; i-- {
- pi := queue.DecodeTxPacketHeader(c.txq.tx.Pull())
-
- queue.EncodeTxCompletion(c.txq.rx.Push(8), pi.ID)
- queue.EncodeTxCompletion(c.txq.rx.Push(8), pi.ID)
- c.txq.rx.Flush()
- }
- c.txq.tx.Flush()
-
- // Each packet is uses no more than 40 bytes, so write that many packets
- // until the tx queue if full.
- ids := make(map[uint64]struct{})
- for i := queuePipeSize / 40; i > 0; i-- {
- hdr := buffer.NewPrependable(int(c.ep.MaxHeaderLength()))
- if err := c.ep.WritePacket(&r, nil /* gso */, hdr, buf.ToVectorisedView(), header.IPv4ProtocolNumber); err != nil {
- t.Fatalf("WritePacket failed unexpectedly: %v", err)
- }
-
- // Check that they have different IDs.
- desc := c.txq.tx.Pull()
- pi := queue.DecodeTxPacketHeader(desc)
- if _, ok := ids[pi.ID]; ok {
- t.Fatalf("ID (%v) reused", pi.ID)
- }
- ids[pi.ID] = struct{}{}
- }
-
- // Next attempt to write must fail.
- hdr := buffer.NewPrependable(int(c.ep.MaxHeaderLength()))
- if want, err := tcpip.ErrWouldBlock, c.ep.WritePacket(&r, nil /* gso */, hdr, buf.ToVectorisedView(), header.IPv4ProtocolNumber); err != want {
- t.Fatalf("WritePacket return unexpected result: got %v, want %v", err, want)
- }
-}
-
-// TestFillTxMemory sends packets until the we run out of shared memory.
-func TestFillTxMemory(t *testing.T) {
- const bufferSize = 1500
- c := newTestContext(t, 20000, bufferSize, localLinkAddr)
- defer c.cleanup()
-
- // Prepare to send a packet.
- r := stack.Route{
- RemoteLinkAddress: remoteLinkAddr,
- }
-
- buf := buffer.NewView(100)
-
- // Each packet is uses up one buffer, so write as many as possible until
- // we fill the memory.
- ids := make(map[uint64]struct{})
- for i := queueDataSize / bufferSize; i > 0; i-- {
- hdr := buffer.NewPrependable(int(c.ep.MaxHeaderLength()))
- if err := c.ep.WritePacket(&r, nil /* gso */, hdr, buf.ToVectorisedView(), header.IPv4ProtocolNumber); err != nil {
- t.Fatalf("WritePacket failed unexpectedly: %v", err)
- }
-
- // Check that they have different IDs.
- desc := c.txq.tx.Pull()
- pi := queue.DecodeTxPacketHeader(desc)
- if _, ok := ids[pi.ID]; ok {
- t.Fatalf("ID (%v) reused", pi.ID)
- }
- ids[pi.ID] = struct{}{}
- c.txq.tx.Flush()
- }
-
- // Next attempt to write must fail.
- hdr := buffer.NewPrependable(int(c.ep.MaxHeaderLength()))
- err := c.ep.WritePacket(&r, nil /* gso */, hdr, buf.ToVectorisedView(), header.IPv4ProtocolNumber)
- if want := tcpip.ErrWouldBlock; err != want {
- t.Fatalf("WritePacket return unexpected result: got %v, want %v", err, want)
- }
-}
-
-// TestFillTxMemoryWithMultiBuffer sends packets until the we run out of
-// shared memory for a 2-buffer packet, but still with room for a 1-buffer
-// packet.
-func TestFillTxMemoryWithMultiBuffer(t *testing.T) {
- const bufferSize = 1500
- c := newTestContext(t, 20000, bufferSize, localLinkAddr)
- defer c.cleanup()
-
- // Prepare to send a packet.
- r := stack.Route{
- RemoteLinkAddress: remoteLinkAddr,
- }
-
- buf := buffer.NewView(100)
-
- // Each packet is uses up one buffer, so write as many as possible
- // until there is only one buffer left.
- for i := queueDataSize/bufferSize - 1; i > 0; i-- {
- hdr := buffer.NewPrependable(int(c.ep.MaxHeaderLength()))
- if err := c.ep.WritePacket(&r, nil /* gso */, hdr, buf.ToVectorisedView(), header.IPv4ProtocolNumber); err != nil {
- t.Fatalf("WritePacket failed unexpectedly: %v", err)
- }
-
- // Pull the posted buffer.
- c.txq.tx.Pull()
- c.txq.tx.Flush()
- }
-
- // Attempt to write a two-buffer packet. It must fail.
- {
- hdr := buffer.NewPrependable(int(c.ep.MaxHeaderLength()))
- uu := buffer.NewView(bufferSize).ToVectorisedView()
- if want, err := tcpip.ErrWouldBlock, c.ep.WritePacket(&r, nil /* gso */, hdr, uu, header.IPv4ProtocolNumber); err != want {
- t.Fatalf("WritePacket return unexpected result: got %v, want %v", err, want)
- }
- }
-
- // Attempt to write the one-buffer packet again. It must succeed.
- {
- hdr := buffer.NewPrependable(int(c.ep.MaxHeaderLength()))
- if err := c.ep.WritePacket(&r, nil /* gso */, hdr, buf.ToVectorisedView(), header.IPv4ProtocolNumber); err != nil {
- t.Fatalf("WritePacket failed unexpectedly: %v", err)
- }
- }
-}
-
-func pollPull(t *testing.T, p *pipe.Rx, to <-chan time.Time, errStr string) []byte {
- t.Helper()
-
- for {
- b := p.Pull()
- if b != nil {
- return b
- }
-
- select {
- case <-time.After(10 * time.Millisecond):
- case <-to:
- t.Fatal(errStr)
- }
- }
-}
-
-// TestSimpleReceive completes 1000 different receives with random payload and
-// random number of buffers. It checks that the contents match the expected
-// values.
-func TestSimpleReceive(t *testing.T) {
- const bufferSize = 1500
- c := newTestContext(t, 20000, bufferSize, localLinkAddr)
- defer c.cleanup()
-
- // Check that buffers have been posted.
- limit := c.ep.rx.q.PostedBuffersLimit()
- for i := uint64(0); i < limit; i++ {
- timeout := time.After(2 * time.Second)
- bi := queue.DecodeRxBufferHeader(pollPull(t, &c.rxq.tx, timeout, "Timeout waiting for all buffers to be posted"))
-
- if want := i * bufferSize; want != bi.Offset {
- t.Fatalf("Bad posted offset: got %v, want %v", bi.Offset, want)
- }
-
- if want := i; want != bi.ID {
- t.Fatalf("Bad posted ID: got %v, want %v", bi.ID, want)
- }
-
- if bufferSize != bi.Size {
- t.Fatalf("Bad posted bufferSize: got %v, want %v", bi.Size, bufferSize)
- }
- }
- c.rxq.tx.Flush()
-
- // Create a slice with the indices 0..limit-1.
- idx := make([]int, limit)
- for i := range idx {
- idx[i] = i
- }
-
- // Complete random packets 1000 times.
- for iters := 1000; iters > 0; iters-- {
- timeout := time.After(2 * time.Second)
- // Prepare a random packet.
- shuffle(idx)
- n := 1 + rand.Intn(10)
- bufs := make([]queue.RxBuffer, n)
- contents := make([]byte, bufferSize*n-rand.Intn(500))
- randomFill(contents)
- for i := range bufs {
- j := idx[i]
- bufs[i].Size = bufferSize
- bufs[i].Offset = uint64(bufferSize * j)
- bufs[i].ID = uint64(j)
-
- copy(c.rxq.data[bufs[i].Offset:][:bufferSize], contents[i*bufferSize:])
- }
-
- // Push completion.
- c.pushRxCompletion(uint32(len(contents)), bufs)
- c.rxq.rx.Flush()
- syscall.Write(c.rxCfg.EventFD, []byte{1, 0, 0, 0, 0, 0, 0, 0})
-
- // Wait for packet to be received, then check it.
- c.waitForPackets(1, time.After(5*time.Second), "Timeout waiting for packet")
- c.mu.Lock()
- rcvd := []byte(c.packets[0].vv.First())
- c.packets = c.packets[:0]
- c.mu.Unlock()
-
- if contents := contents[header.EthernetMinimumSize:]; !bytes.Equal(contents, rcvd) {
- t.Fatalf("Unexpected buffer contents: got %x, want %x", rcvd, contents)
- }
-
- // Check that buffers have been reposted.
- for i := range bufs {
- bi := queue.DecodeRxBufferHeader(pollPull(t, &c.rxq.tx, timeout, "Timeout waiting for buffers to be reposted"))
- if bi != bufs[i] {
- t.Fatalf("Unexpected buffer reposted: got %x, want %x", bi, bufs[i])
- }
- }
- c.rxq.tx.Flush()
- }
-}
-
-// TestRxBuffersReposted tests that rx buffers get reposted after they have been
-// completed.
-func TestRxBuffersReposted(t *testing.T) {
- const bufferSize = 1500
- c := newTestContext(t, 20000, bufferSize, localLinkAddr)
- defer c.cleanup()
-
- // Receive all posted buffers.
- limit := c.ep.rx.q.PostedBuffersLimit()
- buffers := make([]queue.RxBuffer, 0, limit)
- for i := limit; i > 0; i-- {
- timeout := time.After(2 * time.Second)
- buffers = append(buffers, queue.DecodeRxBufferHeader(pollPull(t, &c.rxq.tx, timeout, "Timeout waiting for all buffers")))
- }
- c.rxq.tx.Flush()
-
- // Check that all buffers are reposted when individually completed.
- for i := range buffers {
- timeout := time.After(2 * time.Second)
- // Complete the buffer.
- c.pushRxCompletion(buffers[i].Size, buffers[i:][:1])
- c.rxq.rx.Flush()
- syscall.Write(c.rxCfg.EventFD, []byte{1, 0, 0, 0, 0, 0, 0, 0})
-
- // Wait for it to be reposted.
- bi := queue.DecodeRxBufferHeader(pollPull(t, &c.rxq.tx, timeout, "Timeout waiting for buffer to be reposted"))
- if bi != buffers[i] {
- t.Fatalf("Different buffer posted: got %v, want %v", bi, buffers[i])
- }
- }
- c.rxq.tx.Flush()
-
- // Check that all buffers are reposted when completed in pairs.
- for i := 0; i < len(buffers)/2; i++ {
- timeout := time.After(2 * time.Second)
- // Complete with two buffers.
- c.pushRxCompletion(2*bufferSize, buffers[2*i:][:2])
- c.rxq.rx.Flush()
- syscall.Write(c.rxCfg.EventFD, []byte{1, 0, 0, 0, 0, 0, 0, 0})
-
- // Wait for them to be reposted.
- for j := 0; j < 2; j++ {
- bi := queue.DecodeRxBufferHeader(pollPull(t, &c.rxq.tx, timeout, "Timeout waiting for buffer to be reposted"))
- if bi != buffers[2*i+j] {
- t.Fatalf("Different buffer posted: got %v, want %v", bi, buffers[2*i+j])
- }
- }
- }
- c.rxq.tx.Flush()
-}
-
-// TestReceivePostingIsFull checks that the endpoint will properly handle the
-// case when a received buffer cannot be immediately reposted because it hasn't
-// been pulled from the tx pipe yet.
-func TestReceivePostingIsFull(t *testing.T) {
- const bufferSize = 1500
- c := newTestContext(t, 20000, bufferSize, localLinkAddr)
- defer c.cleanup()
-
- // Complete first posted buffer before flushing it from the tx pipe.
- first := queue.DecodeRxBufferHeader(pollPull(t, &c.rxq.tx, time.After(time.Second), "Timeout waiting for first buffer to be posted"))
- c.pushRxCompletion(first.Size, []queue.RxBuffer{first})
- c.rxq.rx.Flush()
- syscall.Write(c.rxCfg.EventFD, []byte{1, 0, 0, 0, 0, 0, 0, 0})
-
- // Check that packet is received.
- c.waitForPackets(1, time.After(time.Second), "Timeout waiting for completed packet")
-
- // Complete another buffer.
- second := queue.DecodeRxBufferHeader(pollPull(t, &c.rxq.tx, time.After(time.Second), "Timeout waiting for second buffer to be posted"))
- c.pushRxCompletion(second.Size, []queue.RxBuffer{second})
- c.rxq.rx.Flush()
- syscall.Write(c.rxCfg.EventFD, []byte{1, 0, 0, 0, 0, 0, 0, 0})
-
- // Check that no packet is received yet, as the worker is blocked trying
- // to repost.
- select {
- case <-time.After(500 * time.Millisecond):
- case <-c.packetCh:
- t.Fatalf("Unexpected packet received")
- }
-
- // Flush tx queue, which will allow the first buffer to be reposted,
- // and the second completion to be pulled.
- c.rxq.tx.Flush()
- syscall.Write(c.rxCfg.EventFD, []byte{1, 0, 0, 0, 0, 0, 0, 0})
-
- // Check that second packet completes.
- c.waitForPackets(1, time.After(time.Second), "Timeout waiting for second completed packet")
-}
-
-// TestCloseWhileWaitingToPost closes the endpoint while it is waiting to
-// repost a buffer. Make sure it backs out.
-func TestCloseWhileWaitingToPost(t *testing.T) {
- const bufferSize = 1500
- c := newTestContext(t, 20000, bufferSize, localLinkAddr)
- cleaned := false
- defer func() {
- if !cleaned {
- c.cleanup()
- }
- }()
-
- // Complete first posted buffer before flushing it from the tx pipe.
- bi := queue.DecodeRxBufferHeader(pollPull(t, &c.rxq.tx, time.After(time.Second), "Timeout waiting for initial buffer to be posted"))
- c.pushRxCompletion(bi.Size, []queue.RxBuffer{bi})
- c.rxq.rx.Flush()
- syscall.Write(c.rxCfg.EventFD, []byte{1, 0, 0, 0, 0, 0, 0, 0})
-
- // Wait for packet to be indicated.
- c.waitForPackets(1, time.After(time.Second), "Timeout waiting for completed packet")
-
- // Cleanup and wait for worker to complete.
- c.cleanup()
- cleaned = true
- c.ep.Wait()
-}
diff --git a/pkg/tcpip/link/sharedmem/sharedmem_unsafe.go b/pkg/tcpip/link/sharedmem/sharedmem_unsafe.go
deleted file mode 100644
index f7e816a41..000000000
--- a/pkg/tcpip/link/sharedmem/sharedmem_unsafe.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package sharedmem
-
-import (
- "unsafe"
-)
-
-// sharedDataPointer converts the shared data slice into a pointer so that it
-// can be used in atomic operations.
-func sharedDataPointer(sharedData []byte) *uint32 {
- return (*uint32)(unsafe.Pointer(&sharedData[0:4][0]))
-}
diff --git a/pkg/tcpip/link/sharedmem/tx.go b/pkg/tcpip/link/sharedmem/tx.go
deleted file mode 100644
index 6b8d7859d..000000000
--- a/pkg/tcpip/link/sharedmem/tx.go
+++ /dev/null
@@ -1,272 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package sharedmem
-
-import (
- "math"
- "syscall"
-
- "gvisor.dev/gvisor/pkg/tcpip/link/sharedmem/queue"
-)
-
-const (
- nilID = math.MaxUint64
-)
-
-// tx holds all state associated with a tx queue.
-type tx struct {
- data []byte
- q queue.Tx
- ids idManager
- bufs bufferManager
-}
-
-// init initializes all state needed by the tx queue based on the information
-// provided.
-//
-// The caller always retains ownership of all file descriptors passed in. The
-// queue implementation will duplicate any that it may need in the future.
-func (t *tx) init(mtu uint32, c *QueueConfig) error {
- // Map in all buffers.
- txPipe, err := getBuffer(c.TxPipeFD)
- if err != nil {
- return err
- }
-
- rxPipe, err := getBuffer(c.RxPipeFD)
- if err != nil {
- syscall.Munmap(txPipe)
- return err
- }
-
- data, err := getBuffer(c.DataFD)
- if err != nil {
- syscall.Munmap(txPipe)
- syscall.Munmap(rxPipe)
- return err
- }
-
- // Initialize state based on buffers.
- t.q.Init(txPipe, rxPipe)
- t.ids.init()
- t.bufs.init(0, len(data), int(mtu))
- t.data = data
-
- return nil
-}
-
-// cleanup releases all resources allocated during init(). It must only be
-// called if init() has previously succeeded.
-func (t *tx) cleanup() {
- a, b := t.q.Bytes()
- syscall.Munmap(a)
- syscall.Munmap(b)
- syscall.Munmap(t.data)
-}
-
-// transmit sends a packet made up of up to two buffers. Returns a boolean that
-// specifies whether the packet was successfully transmitted.
-func (t *tx) transmit(a, b []byte) bool {
- // Pull completions from the tx queue and add their buffers back to the
- // pool so that we can reuse them.
- for {
- id, ok := t.q.CompletedPacket()
- if !ok {
- break
- }
-
- if buf := t.ids.remove(id); buf != nil {
- t.bufs.free(buf)
- }
- }
-
- bSize := t.bufs.entrySize
- total := uint32(len(a) + len(b))
- bufCount := (total + bSize - 1) / bSize
-
- // Allocate enough buffers to hold all the data.
- var buf *queue.TxBuffer
- for i := bufCount; i != 0; i-- {
- b := t.bufs.alloc()
- if b == nil {
- // Failed to get all buffers. Return to the pool
- // whatever we had managed to get.
- if buf != nil {
- t.bufs.free(buf)
- }
- return false
- }
- b.Next = buf
- buf = b
- }
-
- // Copy data into allocated buffers.
- nBuf := buf
- var dBuf []byte
- for _, data := range [][]byte{a, b} {
- for len(data) > 0 {
- if len(dBuf) == 0 {
- dBuf = t.data[nBuf.Offset:][:nBuf.Size]
- nBuf = nBuf.Next
- }
- n := copy(dBuf, data)
- data = data[n:]
- dBuf = dBuf[n:]
- }
- }
-
- // Get an id for this packet and send it out.
- id := t.ids.add(buf)
- if !t.q.Enqueue(id, total, bufCount, buf) {
- t.ids.remove(id)
- t.bufs.free(buf)
- return false
- }
-
- return true
-}
-
-// getBuffer returns a memory region mapped to the full contents of the given
-// file descriptor.
-func getBuffer(fd int) ([]byte, error) {
- var s syscall.Stat_t
- if err := syscall.Fstat(fd, &s); err != nil {
- return nil, err
- }
-
- // Check that size doesn't overflow an int.
- if s.Size > int64(^uint(0)>>1) {
- return nil, syscall.EDOM
- }
-
- return syscall.Mmap(fd, 0, int(s.Size), syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED|syscall.MAP_FILE)
-}
-
-// idDescriptor is used by idManager to either point to a tx buffer (in case
-// the ID is assigned) or to the next free element (if the id is not assigned).
-type idDescriptor struct {
- buf *queue.TxBuffer
- nextFree uint64
-}
-
-// idManager is a manager of tx buffer identifiers. It assigns unique IDs to
-// tx buffers that are added to it; the IDs can only be reused after they have
-// been removed.
-//
-// The ID assignments are stored so that the tx buffers can be retrieved from
-// the IDs previously assigned to them.
-type idManager struct {
- // ids is a slice containing all tx buffers. The ID is the index into
- // this slice.
- ids []idDescriptor
-
- // freeList a list of free IDs.
- freeList uint64
-}
-
-// init initializes the id manager.
-func (m *idManager) init() {
- m.freeList = nilID
-}
-
-// add assigns an ID to the given tx buffer.
-func (m *idManager) add(b *queue.TxBuffer) uint64 {
- if i := m.freeList; i != nilID {
- // There is an id available in the free list, just use it.
- m.ids[i].buf = b
- m.freeList = m.ids[i].nextFree
- return i
- }
-
- // We need to expand the id descriptor.
- m.ids = append(m.ids, idDescriptor{buf: b})
- return uint64(len(m.ids) - 1)
-}
-
-// remove retrieves the tx buffer associated with the given ID, and removes the
-// ID from the assigned table so that it can be reused in the future.
-func (m *idManager) remove(i uint64) *queue.TxBuffer {
- if i >= uint64(len(m.ids)) {
- return nil
- }
-
- desc := &m.ids[i]
- b := desc.buf
- if b == nil {
- // The provided id is not currently assigned.
- return nil
- }
-
- desc.buf = nil
- desc.nextFree = m.freeList
- m.freeList = i
-
- return b
-}
-
-// bufferManager manages a buffer region broken up into smaller, equally sized
-// buffers. Smaller buffers can be allocated and freed.
-type bufferManager struct {
- freeList *queue.TxBuffer
- curOffset uint64
- limit uint64
- entrySize uint32
-}
-
-// init initializes the buffer manager.
-func (b *bufferManager) init(initialOffset, size, entrySize int) {
- b.freeList = nil
- b.curOffset = uint64(initialOffset)
- b.limit = uint64(initialOffset + size/entrySize*entrySize)
- b.entrySize = uint32(entrySize)
-}
-
-// alloc allocates a buffer from the manager, if one is available.
-func (b *bufferManager) alloc() *queue.TxBuffer {
- if b.freeList != nil {
- // There is a descriptor ready for reuse in the free list.
- d := b.freeList
- b.freeList = d.Next
- d.Next = nil
- return d
- }
-
- if b.curOffset < b.limit {
- // There is room available in the never-used range, so create
- // a new descriptor for it.
- d := &queue.TxBuffer{
- Offset: b.curOffset,
- Size: b.entrySize,
- }
- b.curOffset += uint64(b.entrySize)
- return d
- }
-
- return nil
-}
-
-// free returns all buffers in the list to the buffer manager so that they can
-// be reused.
-func (b *bufferManager) free(d *queue.TxBuffer) {
- // Find the last buffer in the list.
- last := d
- for last.Next != nil {
- last = last.Next
- }
-
- // Push list onto free list.
- last.Next = b.freeList
- b.freeList = d
-}
diff --git a/pkg/tcpip/link/sniffer/BUILD b/pkg/tcpip/link/sniffer/BUILD
deleted file mode 100644
index 1756114e6..000000000
--- a/pkg/tcpip/link/sniffer/BUILD
+++ /dev/null
@@ -1,22 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "sniffer",
- srcs = [
- "pcap.go",
- "sniffer.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/link/sniffer",
- visibility = [
- "//visibility:public",
- ],
- deps = [
- "//pkg/log",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/stack",
- ],
-)
diff --git a/pkg/tcpip/link/sniffer/sniffer_state_autogen.go b/pkg/tcpip/link/sniffer/sniffer_state_autogen.go
new file mode 100755
index 000000000..cfd84a739
--- /dev/null
+++ b/pkg/tcpip/link/sniffer/sniffer_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package sniffer
+
diff --git a/pkg/tcpip/link/tun/BUILD b/pkg/tcpip/link/tun/BUILD
deleted file mode 100644
index 92dce8fac..000000000
--- a/pkg/tcpip/link/tun/BUILD
+++ /dev/null
@@ -1,12 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "tun",
- srcs = ["tun_unsafe.go"],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/link/tun",
- visibility = [
- "//visibility:public",
- ],
-)
diff --git a/pkg/tcpip/link/tun/tun_unsafe.go b/pkg/tcpip/link/tun/tun_unsafe.go
deleted file mode 100644
index 09ca9b527..000000000
--- a/pkg/tcpip/link/tun/tun_unsafe.go
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// +build linux
-
-// Package tun contains methods to open TAP and TUN devices.
-package tun
-
-import (
- "syscall"
- "unsafe"
-)
-
-// Open opens the specified TUN device, sets it to non-blocking mode, and
-// returns its file descriptor.
-func Open(name string) (int, error) {
- return open(name, syscall.IFF_TUN|syscall.IFF_NO_PI)
-}
-
-// OpenTAP opens the specified TAP device, sets it to non-blocking mode, and
-// returns its file descriptor.
-func OpenTAP(name string) (int, error) {
- return open(name, syscall.IFF_TAP|syscall.IFF_NO_PI)
-}
-
-func open(name string, flags uint16) (int, error) {
- fd, err := syscall.Open("/dev/net/tun", syscall.O_RDWR, 0)
- if err != nil {
- return -1, err
- }
-
- var ifr struct {
- name [16]byte
- flags uint16
- _ [22]byte
- }
-
- copy(ifr.name[:], name)
- ifr.flags = flags
- _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.TUNSETIFF, uintptr(unsafe.Pointer(&ifr)))
- if errno != 0 {
- syscall.Close(fd)
- return -1, errno
- }
-
- if err = syscall.SetNonblock(fd, true); err != nil {
- syscall.Close(fd)
- return -1, err
- }
-
- return fd, nil
-}
diff --git a/pkg/tcpip/link/waitable/BUILD b/pkg/tcpip/link/waitable/BUILD
deleted file mode 100644
index 0746dc8ec..000000000
--- a/pkg/tcpip/link/waitable/BUILD
+++ /dev/null
@@ -1,34 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "waitable",
- srcs = [
- "waitable.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/link/waitable",
- visibility = [
- "//visibility:public",
- ],
- deps = [
- "//pkg/gate",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/stack",
- ],
-)
-
-go_test(
- name = "waitable_test",
- srcs = [
- "waitable_test.go",
- ],
- embed = [":waitable"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/stack",
- ],
-)
diff --git a/pkg/tcpip/link/waitable/waitable.go b/pkg/tcpip/link/waitable/waitable.go
deleted file mode 100644
index 408cc62f7..000000000
--- a/pkg/tcpip/link/waitable/waitable.go
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package waitable provides the implementation of data-link layer endpoints
-// that wrap other endpoints, and can wait for inflight calls to WritePacket or
-// DeliverNetworkPacket to finish (and new ones to be prevented).
-//
-// Waitable endpoints can be used in the networking stack by calling New(eID) to
-// create a new endpoint, where eID is the ID of the endpoint being wrapped,
-// and then passing it as an argument to Stack.CreateNIC().
-package waitable
-
-import (
- "gvisor.dev/gvisor/pkg/gate"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-// Endpoint is a waitable link-layer endpoint.
-type Endpoint struct {
- dispatchGate gate.Gate
- dispatcher stack.NetworkDispatcher
-
- writeGate gate.Gate
- lower stack.LinkEndpoint
-}
-
-// New creates a new waitable link-layer endpoint. It wraps around another
-// endpoint and allows the caller to block new write/dispatch calls and wait for
-// the inflight ones to finish before returning.
-func New(lower stack.LinkEndpoint) *Endpoint {
- return &Endpoint{
- lower: lower,
- }
-}
-
-// DeliverNetworkPacket implements stack.NetworkDispatcher.DeliverNetworkPacket.
-// It is called by the link-layer endpoint being wrapped when a packet arrives,
-// and only forwards to the actual dispatcher if Wait or WaitDispatch haven't
-// been called.
-func (e *Endpoint) DeliverNetworkPacket(linkEP stack.LinkEndpoint, remote, local tcpip.LinkAddress, protocol tcpip.NetworkProtocolNumber, vv buffer.VectorisedView) {
- if !e.dispatchGate.Enter() {
- return
- }
-
- e.dispatcher.DeliverNetworkPacket(e, remote, local, protocol, vv)
- e.dispatchGate.Leave()
-}
-
-// Attach implements stack.LinkEndpoint.Attach. It saves the dispatcher and
-// registers with the lower endpoint as its dispatcher so that "e" is called
-// for inbound packets.
-func (e *Endpoint) Attach(dispatcher stack.NetworkDispatcher) {
- e.dispatcher = dispatcher
- e.lower.Attach(e)
-}
-
-// IsAttached implements stack.LinkEndpoint.IsAttached.
-func (e *Endpoint) IsAttached() bool {
- return e.dispatcher != nil
-}
-
-// MTU implements stack.LinkEndpoint.MTU. It just forwards the request to the
-// lower endpoint.
-func (e *Endpoint) MTU() uint32 {
- return e.lower.MTU()
-}
-
-// Capabilities implements stack.LinkEndpoint.Capabilities. It just forwards the
-// request to the lower endpoint.
-func (e *Endpoint) Capabilities() stack.LinkEndpointCapabilities {
- return e.lower.Capabilities()
-}
-
-// MaxHeaderLength implements stack.LinkEndpoint.MaxHeaderLength. It just
-// forwards the request to the lower endpoint.
-func (e *Endpoint) MaxHeaderLength() uint16 {
- return e.lower.MaxHeaderLength()
-}
-
-// LinkAddress implements stack.LinkEndpoint.LinkAddress. It just forwards the
-// request to the lower endpoint.
-func (e *Endpoint) LinkAddress() tcpip.LinkAddress {
- return e.lower.LinkAddress()
-}
-
-// WritePacket implements stack.LinkEndpoint.WritePacket. It is called by
-// higher-level protocols to write packets. It only forwards packets to the
-// lower endpoint if Wait or WaitWrite haven't been called.
-func (e *Endpoint) WritePacket(r *stack.Route, gso *stack.GSO, hdr buffer.Prependable, payload buffer.VectorisedView, protocol tcpip.NetworkProtocolNumber) *tcpip.Error {
- if !e.writeGate.Enter() {
- return nil
- }
-
- err := e.lower.WritePacket(r, gso, hdr, payload, protocol)
- e.writeGate.Leave()
- return err
-}
-
-// WaitWrite prevents new calls to WritePacket from reaching the lower endpoint,
-// and waits for inflight ones to finish before returning.
-func (e *Endpoint) WaitWrite() {
- e.writeGate.Close()
-}
-
-// WaitDispatch prevents new calls to DeliverNetworkPacket from reaching the
-// actual dispatcher, and waits for inflight ones to finish before returning.
-func (e *Endpoint) WaitDispatch() {
- e.dispatchGate.Close()
-}
diff --git a/pkg/tcpip/link/waitable/waitable_test.go b/pkg/tcpip/link/waitable/waitable_test.go
deleted file mode 100644
index 1031438b1..000000000
--- a/pkg/tcpip/link/waitable/waitable_test.go
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package waitable
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-type countedEndpoint struct {
- dispatchCount int
- writeCount int
- attachCount int
-
- mtu uint32
- capabilities stack.LinkEndpointCapabilities
- hdrLen uint16
- linkAddr tcpip.LinkAddress
-
- dispatcher stack.NetworkDispatcher
-}
-
-func (e *countedEndpoint) DeliverNetworkPacket(linkEP stack.LinkEndpoint, remote, local tcpip.LinkAddress, protocol tcpip.NetworkProtocolNumber, vv buffer.VectorisedView) {
- e.dispatchCount++
-}
-
-func (e *countedEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
- e.attachCount++
- e.dispatcher = dispatcher
-}
-
-// IsAttached implements stack.LinkEndpoint.IsAttached.
-func (e *countedEndpoint) IsAttached() bool {
- return e.dispatcher != nil
-}
-
-func (e *countedEndpoint) MTU() uint32 {
- return e.mtu
-}
-
-func (e *countedEndpoint) Capabilities() stack.LinkEndpointCapabilities {
- return e.capabilities
-}
-
-func (e *countedEndpoint) MaxHeaderLength() uint16 {
- return e.hdrLen
-}
-
-func (e *countedEndpoint) LinkAddress() tcpip.LinkAddress {
- return e.linkAddr
-}
-
-func (e *countedEndpoint) WritePacket(r *stack.Route, _ *stack.GSO, hdr buffer.Prependable, payload buffer.VectorisedView, protocol tcpip.NetworkProtocolNumber) *tcpip.Error {
- e.writeCount++
- return nil
-}
-
-func TestWaitWrite(t *testing.T) {
- ep := &countedEndpoint{}
- wep := New(ep)
-
- // Write and check that it goes through.
- wep.WritePacket(nil, nil /* gso */, buffer.Prependable{}, buffer.VectorisedView{}, 0)
- if want := 1; ep.writeCount != want {
- t.Fatalf("Unexpected writeCount: got=%v, want=%v", ep.writeCount, want)
- }
-
- // Wait on dispatches, then try to write. It must go through.
- wep.WaitDispatch()
- wep.WritePacket(nil, nil /* gso */, buffer.Prependable{}, buffer.VectorisedView{}, 0)
- if want := 2; ep.writeCount != want {
- t.Fatalf("Unexpected writeCount: got=%v, want=%v", ep.writeCount, want)
- }
-
- // Wait on writes, then try to write. It must not go through.
- wep.WaitWrite()
- wep.WritePacket(nil, nil /* gso */, buffer.Prependable{}, buffer.VectorisedView{}, 0)
- if want := 2; ep.writeCount != want {
- t.Fatalf("Unexpected writeCount: got=%v, want=%v", ep.writeCount, want)
- }
-}
-
-func TestWaitDispatch(t *testing.T) {
- ep := &countedEndpoint{}
- wep := New(ep)
-
- // Check that attach happens.
- wep.Attach(ep)
- if want := 1; ep.attachCount != want {
- t.Fatalf("Unexpected attachCount: got=%v, want=%v", ep.attachCount, want)
- }
-
- // Dispatch and check that it goes through.
- ep.dispatcher.DeliverNetworkPacket(ep, "", "", 0, buffer.VectorisedView{})
- if want := 1; ep.dispatchCount != want {
- t.Fatalf("Unexpected dispatchCount: got=%v, want=%v", ep.dispatchCount, want)
- }
-
- // Wait on writes, then try to dispatch. It must go through.
- wep.WaitWrite()
- ep.dispatcher.DeliverNetworkPacket(ep, "", "", 0, buffer.VectorisedView{})
- if want := 2; ep.dispatchCount != want {
- t.Fatalf("Unexpected dispatchCount: got=%v, want=%v", ep.dispatchCount, want)
- }
-
- // Wait on dispatches, then try to dispatch. It must not go through.
- wep.WaitDispatch()
- ep.dispatcher.DeliverNetworkPacket(ep, "", "", 0, buffer.VectorisedView{})
- if want := 2; ep.dispatchCount != want {
- t.Fatalf("Unexpected dispatchCount: got=%v, want=%v", ep.dispatchCount, want)
- }
-}
-
-func TestOtherMethods(t *testing.T) {
- const (
- mtu = 0xdead
- capabilities = 0xbeef
- hdrLen = 0x1234
- linkAddr = "test address"
- )
- ep := &countedEndpoint{
- mtu: mtu,
- capabilities: capabilities,
- hdrLen: hdrLen,
- linkAddr: linkAddr,
- }
- wep := New(ep)
-
- if v := wep.MTU(); v != mtu {
- t.Fatalf("Unexpected mtu: got=%v, want=%v", v, mtu)
- }
-
- if v := wep.Capabilities(); v != capabilities {
- t.Fatalf("Unexpected capabilities: got=%v, want=%v", v, capabilities)
- }
-
- if v := wep.MaxHeaderLength(); v != hdrLen {
- t.Fatalf("Unexpected MaxHeaderLength: got=%v, want=%v", v, hdrLen)
- }
-
- if v := wep.LinkAddress(); v != linkAddr {
- t.Fatalf("Unexpected LinkAddress: got=%q, want=%q", v, linkAddr)
- }
-}
diff --git a/pkg/tcpip/network/BUILD b/pkg/tcpip/network/BUILD
deleted file mode 100644
index 9d16ff8c9..000000000
--- a/pkg/tcpip/network/BUILD
+++ /dev/null
@@ -1,22 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_test(
- name = "ip_test",
- size = "small",
- srcs = [
- "ip_test.go",
- ],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/link/loopback",
- "//pkg/tcpip/network/ipv4",
- "//pkg/tcpip/network/ipv6",
- "//pkg/tcpip/stack",
- "//pkg/tcpip/transport/tcp",
- "//pkg/tcpip/transport/udp",
- ],
-)
diff --git a/pkg/tcpip/network/arp/BUILD b/pkg/tcpip/network/arp/BUILD
deleted file mode 100644
index df0d3a8c0..000000000
--- a/pkg/tcpip/network/arp/BUILD
+++ /dev/null
@@ -1,36 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "arp",
- srcs = ["arp.go"],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/network/arp",
- visibility = [
- "//visibility:public",
- ],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/stack",
- ],
-)
-
-go_test(
- name = "arp_test",
- size = "small",
- srcs = ["arp_test.go"],
- deps = [
- ":arp",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/link/channel",
- "//pkg/tcpip/link/sniffer",
- "//pkg/tcpip/network/ipv4",
- "//pkg/tcpip/stack",
- "//pkg/tcpip/transport/icmp",
- ],
-)
diff --git a/pkg/tcpip/network/arp/arp_state_autogen.go b/pkg/tcpip/network/arp/arp_state_autogen.go
new file mode 100755
index 000000000..14a21baff
--- /dev/null
+++ b/pkg/tcpip/network/arp/arp_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package arp
+
diff --git a/pkg/tcpip/network/arp/arp_test.go b/pkg/tcpip/network/arp/arp_test.go
deleted file mode 100644
index 387fca96e..000000000
--- a/pkg/tcpip/network/arp/arp_test.go
+++ /dev/null
@@ -1,140 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package arp_test
-
-import (
- "strconv"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/link/channel"
- "gvisor.dev/gvisor/pkg/tcpip/link/sniffer"
- "gvisor.dev/gvisor/pkg/tcpip/network/arp"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
- "gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
-)
-
-const (
- stackLinkAddr = tcpip.LinkAddress("\x0a\x0a\x0b\x0b\x0c\x0c")
- stackAddr1 = tcpip.Address("\x0a\x00\x00\x01")
- stackAddr2 = tcpip.Address("\x0a\x00\x00\x02")
- stackAddrBad = tcpip.Address("\x0a\x00\x00\x03")
-)
-
-type testContext struct {
- t *testing.T
- linkEP *channel.Endpoint
- s *stack.Stack
-}
-
-func newTestContext(t *testing.T) *testContext {
- s := stack.New([]string{ipv4.ProtocolName, arp.ProtocolName}, []string{icmp.ProtocolName4}, stack.Options{})
-
- const defaultMTU = 65536
- ep := channel.New(256, defaultMTU, stackLinkAddr)
- wep := stack.LinkEndpoint(ep)
-
- if testing.Verbose() {
- wep = sniffer.New(ep)
- }
- if err := s.CreateNIC(1, wep); err != nil {
- t.Fatalf("CreateNIC failed: %v", err)
- }
-
- if err := s.AddAddress(1, ipv4.ProtocolNumber, stackAddr1); err != nil {
- t.Fatalf("AddAddress for ipv4 failed: %v", err)
- }
- if err := s.AddAddress(1, ipv4.ProtocolNumber, stackAddr2); err != nil {
- t.Fatalf("AddAddress for ipv4 failed: %v", err)
- }
- if err := s.AddAddress(1, arp.ProtocolNumber, arp.ProtocolAddress); err != nil {
- t.Fatalf("AddAddress for arp failed: %v", err)
- }
-
- s.SetRouteTable([]tcpip.Route{{
- Destination: header.IPv4EmptySubnet,
- NIC: 1,
- }})
-
- return &testContext{
- t: t,
- s: s,
- linkEP: ep,
- }
-}
-
-func (c *testContext) cleanup() {
- close(c.linkEP.C)
-}
-
-func TestDirectRequest(t *testing.T) {
- c := newTestContext(t)
- defer c.cleanup()
-
- const senderMAC = "\x01\x02\x03\x04\x05\x06"
- const senderIPv4 = "\x0a\x00\x00\x02"
-
- v := make(buffer.View, header.ARPSize)
- h := header.ARP(v)
- h.SetIPv4OverEthernet()
- h.SetOp(header.ARPRequest)
- copy(h.HardwareAddressSender(), senderMAC)
- copy(h.ProtocolAddressSender(), senderIPv4)
-
- inject := func(addr tcpip.Address) {
- copy(h.ProtocolAddressTarget(), addr)
- c.linkEP.Inject(arp.ProtocolNumber, v.ToVectorisedView())
- }
-
- for i, address := range []tcpip.Address{stackAddr1, stackAddr2} {
- t.Run(strconv.Itoa(i), func(t *testing.T) {
- inject(address)
- pkt := <-c.linkEP.C
- if pkt.Proto != arp.ProtocolNumber {
- t.Fatalf("expected ARP response, got network protocol number %d", pkt.Proto)
- }
- rep := header.ARP(pkt.Header)
- if !rep.IsValid() {
- t.Fatalf("invalid ARP response len(pkt.Header)=%d", len(pkt.Header))
- }
- if got, want := tcpip.LinkAddress(rep.HardwareAddressSender()), stackLinkAddr; got != want {
- t.Errorf("got HardwareAddressSender = %s, want = %s", got, want)
- }
- if got, want := tcpip.Address(rep.ProtocolAddressSender()), tcpip.Address(h.ProtocolAddressTarget()); got != want {
- t.Errorf("got ProtocolAddressSender = %s, want = %s", got, want)
- }
- if got, want := tcpip.LinkAddress(rep.HardwareAddressTarget()), tcpip.LinkAddress(h.HardwareAddressSender()); got != want {
- t.Errorf("got HardwareAddressTarget = %s, want = %s", got, want)
- }
- if got, want := tcpip.Address(rep.ProtocolAddressTarget()), tcpip.Address(h.ProtocolAddressSender()); got != want {
- t.Errorf("got ProtocolAddressTarget = %s, want = %s", got, want)
- }
- })
- }
-
- inject(stackAddrBad)
- select {
- case pkt := <-c.linkEP.C:
- t.Errorf("stackAddrBad: unexpected packet sent, Proto=%v", pkt.Proto)
- case <-time.After(100 * time.Millisecond):
- // Sleep tests are gross, but this will only potentially flake
- // if there's a bug. If there is no bug this will reliably
- // succeed.
- }
-}
diff --git a/pkg/tcpip/network/fragmentation/BUILD b/pkg/tcpip/network/fragmentation/BUILD
deleted file mode 100644
index c5c7aad86..000000000
--- a/pkg/tcpip/network/fragmentation/BUILD
+++ /dev/null
@@ -1,54 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_template_instance(
- name = "reassembler_list",
- out = "reassembler_list.go",
- package = "fragmentation",
- prefix = "reassembler",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*reassembler",
- "Linker": "*reassembler",
- },
-)
-
-go_library(
- name = "fragmentation",
- srcs = [
- "frag_heap.go",
- "fragmentation.go",
- "reassembler.go",
- "reassembler_list.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/network/fragmentation",
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/log",
- "//pkg/tcpip/buffer",
- ],
-)
-
-go_test(
- name = "fragmentation_test",
- size = "small",
- srcs = [
- "frag_heap_test.go",
- "fragmentation_test.go",
- "reassembler_test.go",
- ],
- embed = [":fragmentation"],
- deps = ["//pkg/tcpip/buffer"],
-)
-
-filegroup(
- name = "autogen",
- srcs = [
- "reassembler_list.go",
- ],
- visibility = ["//:sandbox"],
-)
diff --git a/pkg/tcpip/network/fragmentation/frag_heap_test.go b/pkg/tcpip/network/fragmentation/frag_heap_test.go
deleted file mode 100644
index 9ececcb9f..000000000
--- a/pkg/tcpip/network/fragmentation/frag_heap_test.go
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fragmentation
-
-import (
- "container/heap"
- "reflect"
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
-)
-
-var reassambleTestCases = []struct {
- comment string
- in []fragment
- want buffer.VectorisedView
-}{
- {
- comment: "Non-overlapping in-order",
- in: []fragment{
- {offset: 0, vv: vv(1, "0")},
- {offset: 1, vv: vv(1, "1")},
- },
- want: vv(2, "0", "1"),
- },
- {
- comment: "Non-overlapping out-of-order",
- in: []fragment{
- {offset: 1, vv: vv(1, "1")},
- {offset: 0, vv: vv(1, "0")},
- },
- want: vv(2, "0", "1"),
- },
- {
- comment: "Duplicated packets",
- in: []fragment{
- {offset: 0, vv: vv(1, "0")},
- {offset: 0, vv: vv(1, "0")},
- },
- want: vv(1, "0"),
- },
- {
- comment: "Overlapping in-order",
- in: []fragment{
- {offset: 0, vv: vv(2, "01")},
- {offset: 1, vv: vv(2, "12")},
- },
- want: vv(3, "01", "2"),
- },
- {
- comment: "Overlapping out-of-order",
- in: []fragment{
- {offset: 1, vv: vv(2, "12")},
- {offset: 0, vv: vv(2, "01")},
- },
- want: vv(3, "01", "2"),
- },
- {
- comment: "Overlapping subset in-order",
- in: []fragment{
- {offset: 0, vv: vv(3, "012")},
- {offset: 1, vv: vv(1, "1")},
- },
- want: vv(3, "012"),
- },
- {
- comment: "Overlapping subset out-of-order",
- in: []fragment{
- {offset: 1, vv: vv(1, "1")},
- {offset: 0, vv: vv(3, "012")},
- },
- want: vv(3, "012"),
- },
-}
-
-func TestReassamble(t *testing.T) {
- for _, c := range reassambleTestCases {
- t.Run(c.comment, func(t *testing.T) {
- h := make(fragHeap, 0, 8)
- heap.Init(&h)
- for _, f := range c.in {
- heap.Push(&h, f)
- }
- got, err := h.reassemble()
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(got, c.want) {
- t.Errorf("got reassemble(%+v) = %v, want = %v", c.in, got, c.want)
- }
- })
- }
-}
-
-func TestReassambleFailsForNonZeroOffset(t *testing.T) {
- h := make(fragHeap, 0, 8)
- heap.Init(&h)
- heap.Push(&h, fragment{offset: 1, vv: vv(1, "0")})
- _, err := h.reassemble()
- if err == nil {
- t.Errorf("reassemble() did not fail when the first packet had offset != 0")
- }
-}
-
-func TestReassambleFailsForHoles(t *testing.T) {
- h := make(fragHeap, 0, 8)
- heap.Init(&h)
- heap.Push(&h, fragment{offset: 0, vv: vv(1, "0")})
- heap.Push(&h, fragment{offset: 2, vv: vv(1, "1")})
- _, err := h.reassemble()
- if err == nil {
- t.Errorf("reassemble() did not fail when there was a hole in the packet")
- }
-}
diff --git a/pkg/tcpip/network/fragmentation/fragmentation_state_autogen.go b/pkg/tcpip/network/fragmentation/fragmentation_state_autogen.go
new file mode 100755
index 000000000..6b71d27c7
--- /dev/null
+++ b/pkg/tcpip/network/fragmentation/fragmentation_state_autogen.go
@@ -0,0 +1,38 @@
+// automatically generated by stateify.
+
+package fragmentation
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *reassemblerList) beforeSave() {}
+func (x *reassemblerList) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *reassemblerList) afterLoad() {}
+func (x *reassemblerList) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *reassemblerEntry) beforeSave() {}
+func (x *reassemblerEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *reassemblerEntry) afterLoad() {}
+func (x *reassemblerEntry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func init() {
+ state.Register("fragmentation.reassemblerList", (*reassemblerList)(nil), state.Fns{Save: (*reassemblerList).save, Load: (*reassemblerList).load})
+ state.Register("fragmentation.reassemblerEntry", (*reassemblerEntry)(nil), state.Fns{Save: (*reassemblerEntry).save, Load: (*reassemblerEntry).load})
+}
diff --git a/pkg/tcpip/network/fragmentation/fragmentation_test.go b/pkg/tcpip/network/fragmentation/fragmentation_test.go
deleted file mode 100644
index 799798544..000000000
--- a/pkg/tcpip/network/fragmentation/fragmentation_test.go
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fragmentation
-
-import (
- "reflect"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
-)
-
-// vv is a helper to build VectorisedView from different strings.
-func vv(size int, pieces ...string) buffer.VectorisedView {
- views := make([]buffer.View, len(pieces))
- for i, p := range pieces {
- views[i] = []byte(p)
- }
-
- return buffer.NewVectorisedView(size, views)
-}
-
-type processInput struct {
- id uint32
- first uint16
- last uint16
- more bool
- vv buffer.VectorisedView
-}
-
-type processOutput struct {
- vv buffer.VectorisedView
- done bool
-}
-
-var processTestCases = []struct {
- comment string
- in []processInput
- out []processOutput
-}{
- {
- comment: "One ID",
- in: []processInput{
- {id: 0, first: 0, last: 1, more: true, vv: vv(2, "01")},
- {id: 0, first: 2, last: 3, more: false, vv: vv(2, "23")},
- },
- out: []processOutput{
- {vv: buffer.VectorisedView{}, done: false},
- {vv: vv(4, "01", "23"), done: true},
- },
- },
- {
- comment: "Two IDs",
- in: []processInput{
- {id: 0, first: 0, last: 1, more: true, vv: vv(2, "01")},
- {id: 1, first: 0, last: 1, more: true, vv: vv(2, "ab")},
- {id: 1, first: 2, last: 3, more: false, vv: vv(2, "cd")},
- {id: 0, first: 2, last: 3, more: false, vv: vv(2, "23")},
- },
- out: []processOutput{
- {vv: buffer.VectorisedView{}, done: false},
- {vv: buffer.VectorisedView{}, done: false},
- {vv: vv(4, "ab", "cd"), done: true},
- {vv: vv(4, "01", "23"), done: true},
- },
- },
-}
-
-func TestFragmentationProcess(t *testing.T) {
- for _, c := range processTestCases {
- t.Run(c.comment, func(t *testing.T) {
- f := NewFragmentation(1024, 512, DefaultReassembleTimeout)
- for i, in := range c.in {
- vv, done := f.Process(in.id, in.first, in.last, in.more, in.vv)
- if !reflect.DeepEqual(vv, c.out[i].vv) {
- t.Errorf("got Process(%d) = %+v, want = %+v", i, vv, c.out[i].vv)
- }
- if done != c.out[i].done {
- t.Errorf("got Process(%d) = %+v, want = %+v", i, done, c.out[i].done)
- }
- if c.out[i].done {
- if _, ok := f.reassemblers[in.id]; ok {
- t.Errorf("Process(%d) did not remove buffer from reassemblers", i)
- }
- for n := f.rList.Front(); n != nil; n = n.Next() {
- if n.id == in.id {
- t.Errorf("Process(%d) did not remove buffer from rList", i)
- }
- }
- }
- }
- })
- }
-}
-
-func TestReassemblingTimeout(t *testing.T) {
- timeout := time.Millisecond
- f := NewFragmentation(1024, 512, timeout)
- // Send first fragment with id = 0, first = 0, last = 0, and more = true.
- f.Process(0, 0, 0, true, vv(1, "0"))
- // Sleep more than the timeout.
- time.Sleep(2 * timeout)
- // Send another fragment that completes a packet.
- // However, no packet should be reassembled because the fragment arrived after the timeout.
- _, done := f.Process(0, 1, 1, false, vv(1, "1"))
- if done {
- t.Errorf("Fragmentation does not respect the reassembling timeout.")
- }
-}
-
-func TestMemoryLimits(t *testing.T) {
- f := NewFragmentation(3, 1, DefaultReassembleTimeout)
- // Send first fragment with id = 0.
- f.Process(0, 0, 0, true, vv(1, "0"))
- // Send first fragment with id = 1.
- f.Process(1, 0, 0, true, vv(1, "1"))
- // Send first fragment with id = 2.
- f.Process(2, 0, 0, true, vv(1, "2"))
-
- // Send first fragment with id = 3. This should caused id = 0 and id = 1 to be
- // evicted.
- f.Process(3, 0, 0, true, vv(1, "3"))
-
- if _, ok := f.reassemblers[0]; ok {
- t.Errorf("Memory limits are not respected: id=0 has not been evicted.")
- }
- if _, ok := f.reassemblers[1]; ok {
- t.Errorf("Memory limits are not respected: id=1 has not been evicted.")
- }
- if _, ok := f.reassemblers[3]; !ok {
- t.Errorf("Implementation of memory limits is wrong: id=3 is not present.")
- }
-}
-
-func TestMemoryLimitsIgnoresDuplicates(t *testing.T) {
- f := NewFragmentation(1, 0, DefaultReassembleTimeout)
- // Send first fragment with id = 0.
- f.Process(0, 0, 0, true, vv(1, "0"))
- // Send the same packet again.
- f.Process(0, 0, 0, true, vv(1, "0"))
-
- got := f.size
- want := 1
- if got != want {
- t.Errorf("Wrong size, duplicates are not handled correctly: got=%d, want=%d.", got, want)
- }
-}
diff --git a/pkg/tcpip/network/fragmentation/reassembler_list.go b/pkg/tcpip/network/fragmentation/reassembler_list.go
new file mode 100755
index 000000000..3189cae29
--- /dev/null
+++ b/pkg/tcpip/network/fragmentation/reassembler_list.go
@@ -0,0 +1,173 @@
+package fragmentation
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type reassemblerElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (reassemblerElementMapper) linkerFor(elem *reassembler) *reassembler { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type reassemblerList struct {
+ head *reassembler
+ tail *reassembler
+}
+
+// Reset resets list l to the empty state.
+func (l *reassemblerList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *reassemblerList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *reassemblerList) Front() *reassembler {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *reassemblerList) Back() *reassembler {
+ return l.tail
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *reassemblerList) PushFront(e *reassembler) {
+ reassemblerElementMapper{}.linkerFor(e).SetNext(l.head)
+ reassemblerElementMapper{}.linkerFor(e).SetPrev(nil)
+
+ if l.head != nil {
+ reassemblerElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *reassemblerList) PushBack(e *reassembler) {
+ reassemblerElementMapper{}.linkerFor(e).SetNext(nil)
+ reassemblerElementMapper{}.linkerFor(e).SetPrev(l.tail)
+
+ if l.tail != nil {
+ reassemblerElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *reassemblerList) PushBackList(m *reassemblerList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ reassemblerElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ reassemblerElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *reassemblerList) InsertAfter(b, e *reassembler) {
+ a := reassemblerElementMapper{}.linkerFor(b).Next()
+ reassemblerElementMapper{}.linkerFor(e).SetNext(a)
+ reassemblerElementMapper{}.linkerFor(e).SetPrev(b)
+ reassemblerElementMapper{}.linkerFor(b).SetNext(e)
+
+ if a != nil {
+ reassemblerElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *reassemblerList) InsertBefore(a, e *reassembler) {
+ b := reassemblerElementMapper{}.linkerFor(a).Prev()
+ reassemblerElementMapper{}.linkerFor(e).SetNext(a)
+ reassemblerElementMapper{}.linkerFor(e).SetPrev(b)
+ reassemblerElementMapper{}.linkerFor(a).SetPrev(e)
+
+ if b != nil {
+ reassemblerElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *reassemblerList) Remove(e *reassembler) {
+ prev := reassemblerElementMapper{}.linkerFor(e).Prev()
+ next := reassemblerElementMapper{}.linkerFor(e).Next()
+
+ if prev != nil {
+ reassemblerElementMapper{}.linkerFor(prev).SetNext(next)
+ } else {
+ l.head = next
+ }
+
+ if next != nil {
+ reassemblerElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else {
+ l.tail = prev
+ }
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type reassemblerEntry struct {
+ next *reassembler
+ prev *reassembler
+}
+
+// Next returns the entry that follows e in the list.
+func (e *reassemblerEntry) Next() *reassembler {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *reassemblerEntry) Prev() *reassembler {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *reassemblerEntry) SetNext(elem *reassembler) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *reassemblerEntry) SetPrev(elem *reassembler) {
+ e.prev = elem
+}
diff --git a/pkg/tcpip/network/fragmentation/reassembler_test.go b/pkg/tcpip/network/fragmentation/reassembler_test.go
deleted file mode 100644
index 7eee0710d..000000000
--- a/pkg/tcpip/network/fragmentation/reassembler_test.go
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fragmentation
-
-import (
- "math"
- "reflect"
- "testing"
-)
-
-type updateHolesInput struct {
- first uint16
- last uint16
- more bool
-}
-
-var holesTestCases = []struct {
- comment string
- in []updateHolesInput
- want []hole
-}{
- {
- comment: "No fragments. Expected holes: {[0 -> inf]}.",
- in: []updateHolesInput{},
- want: []hole{{first: 0, last: math.MaxUint16, deleted: false}},
- },
- {
- comment: "One fragment at beginning. Expected holes: {[2, inf]}.",
- in: []updateHolesInput{{first: 0, last: 1, more: true}},
- want: []hole{
- {first: 0, last: math.MaxUint16, deleted: true},
- {first: 2, last: math.MaxUint16, deleted: false},
- },
- },
- {
- comment: "One fragment in the middle. Expected holes: {[0, 0], [3, inf]}.",
- in: []updateHolesInput{{first: 1, last: 2, more: true}},
- want: []hole{
- {first: 0, last: math.MaxUint16, deleted: true},
- {first: 0, last: 0, deleted: false},
- {first: 3, last: math.MaxUint16, deleted: false},
- },
- },
- {
- comment: "One fragment at the end. Expected holes: {[0, 0]}.",
- in: []updateHolesInput{{first: 1, last: 2, more: false}},
- want: []hole{
- {first: 0, last: math.MaxUint16, deleted: true},
- {first: 0, last: 0, deleted: false},
- },
- },
- {
- comment: "One fragment completing a packet. Expected holes: {}.",
- in: []updateHolesInput{{first: 0, last: 1, more: false}},
- want: []hole{
- {first: 0, last: math.MaxUint16, deleted: true},
- },
- },
- {
- comment: "Two non-overlapping fragments completing a packet. Expected holes: {}.",
- in: []updateHolesInput{
- {first: 0, last: 1, more: true},
- {first: 2, last: 3, more: false},
- },
- want: []hole{
- {first: 0, last: math.MaxUint16, deleted: true},
- {first: 2, last: math.MaxUint16, deleted: true},
- },
- },
- {
- comment: "Two overlapping fragments completing a packet. Expected holes: {}.",
- in: []updateHolesInput{
- {first: 0, last: 2, more: true},
- {first: 2, last: 3, more: false},
- },
- want: []hole{
- {first: 0, last: math.MaxUint16, deleted: true},
- {first: 3, last: math.MaxUint16, deleted: true},
- },
- },
-}
-
-func TestUpdateHoles(t *testing.T) {
- for _, c := range holesTestCases {
- r := newReassembler(0)
- for _, i := range c.in {
- r.updateHoles(i.first, i.last, i.more)
- }
- if !reflect.DeepEqual(r.holes, c.want) {
- t.Errorf("Test \"%s\" produced unexepetced holes. Got %v. Want %v", c.comment, r.holes, c.want)
- }
- }
-}
diff --git a/pkg/tcpip/network/hash/BUILD b/pkg/tcpip/network/hash/BUILD
deleted file mode 100644
index e6db5c0b0..000000000
--- a/pkg/tcpip/network/hash/BUILD
+++ /dev/null
@@ -1,14 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "hash",
- srcs = ["hash.go"],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/network/hash",
- visibility = ["//visibility:public"],
- deps = [
- "//pkg/rand",
- "//pkg/tcpip/header",
- ],
-)
diff --git a/pkg/tcpip/network/hash/hash_state_autogen.go b/pkg/tcpip/network/hash/hash_state_autogen.go
new file mode 100755
index 000000000..a3bcd4b69
--- /dev/null
+++ b/pkg/tcpip/network/hash/hash_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package hash
+
diff --git a/pkg/tcpip/network/ip_test.go b/pkg/tcpip/network/ip_test.go
deleted file mode 100644
index 4b3bd74fa..000000000
--- a/pkg/tcpip/network/ip_test.go
+++ /dev/null
@@ -1,609 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ip_test
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/link/loopback"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
- "gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
- "gvisor.dev/gvisor/pkg/tcpip/transport/udp"
-)
-
-const (
- localIpv4Addr = "\x0a\x00\x00\x01"
- localIpv4PrefixLen = 24
- remoteIpv4Addr = "\x0a\x00\x00\x02"
- ipv4SubnetAddr = "\x0a\x00\x00\x00"
- ipv4SubnetMask = "\xff\xff\xff\x00"
- ipv4Gateway = "\x0a\x00\x00\x03"
- localIpv6Addr = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
- localIpv6PrefixLen = 120
- remoteIpv6Addr = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"
- ipv6SubnetAddr = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
- ipv6SubnetMask = "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00"
- ipv6Gateway = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03"
-)
-
-// testObject implements two interfaces: LinkEndpoint and TransportDispatcher.
-// The former is used to pretend that it's a link endpoint so that we can
-// inspect packets written by the network endpoints. The latter is used to
-// pretend that it's the network stack so that it can inspect incoming packets
-// that have been handled by the network endpoints.
-//
-// Packets are checked by comparing their fields/values against the expected
-// values stored in the test object itself.
-type testObject struct {
- t *testing.T
- protocol tcpip.TransportProtocolNumber
- contents []byte
- srcAddr tcpip.Address
- dstAddr tcpip.Address
- v4 bool
- typ stack.ControlType
- extra uint32
-
- dataCalls int
- controlCalls int
-}
-
-// checkValues verifies that the transport protocol, data contents, src & dst
-// addresses of a packet match what's expected. If any field doesn't match, the
-// test fails.
-func (t *testObject) checkValues(protocol tcpip.TransportProtocolNumber, vv buffer.VectorisedView, srcAddr, dstAddr tcpip.Address) {
- v := vv.ToView()
- if protocol != t.protocol {
- t.t.Errorf("protocol = %v, want %v", protocol, t.protocol)
- }
-
- if srcAddr != t.srcAddr {
- t.t.Errorf("srcAddr = %v, want %v", srcAddr, t.srcAddr)
- }
-
- if dstAddr != t.dstAddr {
- t.t.Errorf("dstAddr = %v, want %v", dstAddr, t.dstAddr)
- }
-
- if len(v) != len(t.contents) {
- t.t.Fatalf("len(payload) = %v, want %v", len(v), len(t.contents))
- }
-
- for i := range t.contents {
- if t.contents[i] != v[i] {
- t.t.Fatalf("payload[%v] = %v, want %v", i, v[i], t.contents[i])
- }
- }
-}
-
-// DeliverTransportPacket is called by network endpoints after parsing incoming
-// packets. This is used by the test object to verify that the results of the
-// parsing are expected.
-func (t *testObject) DeliverTransportPacket(r *stack.Route, protocol tcpip.TransportProtocolNumber, netHeader buffer.View, vv buffer.VectorisedView) {
- t.checkValues(protocol, vv, r.RemoteAddress, r.LocalAddress)
- t.dataCalls++
-}
-
-// DeliverTransportControlPacket is called by network endpoints after parsing
-// incoming control (ICMP) packets. This is used by the test object to verify
-// that the results of the parsing are expected.
-func (t *testObject) DeliverTransportControlPacket(local, remote tcpip.Address, net tcpip.NetworkProtocolNumber, trans tcpip.TransportProtocolNumber, typ stack.ControlType, extra uint32, vv buffer.VectorisedView) {
- t.checkValues(trans, vv, remote, local)
- if typ != t.typ {
- t.t.Errorf("typ = %v, want %v", typ, t.typ)
- }
- if extra != t.extra {
- t.t.Errorf("extra = %v, want %v", extra, t.extra)
- }
- t.controlCalls++
-}
-
-// Attach is only implemented to satisfy the LinkEndpoint interface.
-func (*testObject) Attach(stack.NetworkDispatcher) {}
-
-// IsAttached implements stack.LinkEndpoint.IsAttached.
-func (*testObject) IsAttached() bool {
- return true
-}
-
-// MTU implements stack.LinkEndpoint.MTU. It just returns a constant that
-// matches the linux loopback MTU.
-func (*testObject) MTU() uint32 {
- return 65536
-}
-
-// Capabilities implements stack.LinkEndpoint.Capabilities.
-func (*testObject) Capabilities() stack.LinkEndpointCapabilities {
- return 0
-}
-
-// MaxHeaderLength is only implemented to satisfy the LinkEndpoint interface.
-func (*testObject) MaxHeaderLength() uint16 {
- return 0
-}
-
-// LinkAddress returns the link address of this endpoint.
-func (*testObject) LinkAddress() tcpip.LinkAddress {
- return ""
-}
-
-// WritePacket is called by network endpoints after producing a packet and
-// writing it to the link endpoint. This is used by the test object to verify
-// that the produced packet is as expected.
-func (t *testObject) WritePacket(_ *stack.Route, _ *stack.GSO, hdr buffer.Prependable, payload buffer.VectorisedView, protocol tcpip.NetworkProtocolNumber) *tcpip.Error {
- var prot tcpip.TransportProtocolNumber
- var srcAddr tcpip.Address
- var dstAddr tcpip.Address
-
- if t.v4 {
- h := header.IPv4(hdr.View())
- prot = tcpip.TransportProtocolNumber(h.Protocol())
- srcAddr = h.SourceAddress()
- dstAddr = h.DestinationAddress()
-
- } else {
- h := header.IPv6(hdr.View())
- prot = tcpip.TransportProtocolNumber(h.NextHeader())
- srcAddr = h.SourceAddress()
- dstAddr = h.DestinationAddress()
- }
- t.checkValues(prot, payload, srcAddr, dstAddr)
- return nil
-}
-
-func buildIPv4Route(local, remote tcpip.Address) (stack.Route, *tcpip.Error) {
- s := stack.New([]string{ipv4.ProtocolName}, []string{udp.ProtocolName, tcp.ProtocolName}, stack.Options{})
- s.CreateNIC(1, loopback.New())
- s.AddAddress(1, ipv4.ProtocolNumber, local)
- s.SetRouteTable([]tcpip.Route{{
- Destination: header.IPv4EmptySubnet,
- Gateway: ipv4Gateway,
- NIC: 1,
- }})
-
- return s.FindRoute(1, local, remote, ipv4.ProtocolNumber, false /* multicastLoop */)
-}
-
-func buildIPv6Route(local, remote tcpip.Address) (stack.Route, *tcpip.Error) {
- s := stack.New([]string{ipv6.ProtocolName}, []string{udp.ProtocolName, tcp.ProtocolName}, stack.Options{})
- s.CreateNIC(1, loopback.New())
- s.AddAddress(1, ipv6.ProtocolNumber, local)
- s.SetRouteTable([]tcpip.Route{{
- Destination: header.IPv6EmptySubnet,
- Gateway: ipv6Gateway,
- NIC: 1,
- }})
-
- return s.FindRoute(1, local, remote, ipv6.ProtocolNumber, false /* multicastLoop */)
-}
-
-func TestIPv4Send(t *testing.T) {
- o := testObject{t: t, v4: true}
- proto := ipv4.NewProtocol()
- ep, err := proto.NewEndpoint(1, tcpip.AddressWithPrefix{localIpv4Addr, localIpv4PrefixLen}, nil, nil, &o)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- // Allocate and initialize the payload view.
- payload := buffer.NewView(100)
- for i := 0; i < len(payload); i++ {
- payload[i] = uint8(i)
- }
-
- // Allocate the header buffer.
- hdr := buffer.NewPrependable(int(ep.MaxHeaderLength()))
-
- // Issue the write.
- o.protocol = 123
- o.srcAddr = localIpv4Addr
- o.dstAddr = remoteIpv4Addr
- o.contents = payload
-
- r, err := buildIPv4Route(localIpv4Addr, remoteIpv4Addr)
- if err != nil {
- t.Fatalf("could not find route: %v", err)
- }
- if err := ep.WritePacket(&r, nil /* gso */, hdr, payload.ToVectorisedView(), 123, 123, stack.PacketOut); err != nil {
- t.Fatalf("WritePacket failed: %v", err)
- }
-}
-
-func TestIPv4Receive(t *testing.T) {
- o := testObject{t: t, v4: true}
- proto := ipv4.NewProtocol()
- ep, err := proto.NewEndpoint(1, tcpip.AddressWithPrefix{localIpv4Addr, localIpv4PrefixLen}, nil, &o, nil)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- totalLen := header.IPv4MinimumSize + 30
- view := buffer.NewView(totalLen)
- ip := header.IPv4(view)
- ip.Encode(&header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TotalLength: uint16(totalLen),
- TTL: 20,
- Protocol: 10,
- SrcAddr: remoteIpv4Addr,
- DstAddr: localIpv4Addr,
- })
-
- // Make payload be non-zero.
- for i := header.IPv4MinimumSize; i < totalLen; i++ {
- view[i] = uint8(i)
- }
-
- // Give packet to ipv4 endpoint, dispatcher will validate that it's ok.
- o.protocol = 10
- o.srcAddr = remoteIpv4Addr
- o.dstAddr = localIpv4Addr
- o.contents = view[header.IPv4MinimumSize:totalLen]
-
- r, err := buildIPv4Route(localIpv4Addr, remoteIpv4Addr)
- if err != nil {
- t.Fatalf("could not find route: %v", err)
- }
- ep.HandlePacket(&r, view.ToVectorisedView())
- if o.dataCalls != 1 {
- t.Fatalf("Bad number of data calls: got %x, want 1", o.dataCalls)
- }
-}
-
-func TestIPv4ReceiveControl(t *testing.T) {
- const mtu = 0xbeef - header.IPv4MinimumSize
- cases := []struct {
- name string
- expectedCount int
- fragmentOffset uint16
- code uint8
- expectedTyp stack.ControlType
- expectedExtra uint32
- trunc int
- }{
- {"FragmentationNeeded", 1, 0, header.ICMPv4FragmentationNeeded, stack.ControlPacketTooBig, mtu, 0},
- {"Truncated (10 bytes missing)", 0, 0, header.ICMPv4FragmentationNeeded, stack.ControlPacketTooBig, mtu, 10},
- {"Truncated (missing IPv4 header)", 0, 0, header.ICMPv4FragmentationNeeded, stack.ControlPacketTooBig, mtu, header.IPv4MinimumSize + 8},
- {"Truncated (missing 'extra info')", 0, 0, header.ICMPv4FragmentationNeeded, stack.ControlPacketTooBig, mtu, 4 + header.IPv4MinimumSize + 8},
- {"Truncated (missing ICMP header)", 0, 0, header.ICMPv4FragmentationNeeded, stack.ControlPacketTooBig, mtu, header.ICMPv4MinimumSize + header.IPv4MinimumSize + 8},
- {"Port unreachable", 1, 0, header.ICMPv4PortUnreachable, stack.ControlPortUnreachable, 0, 0},
- {"Non-zero fragment offset", 0, 100, header.ICMPv4PortUnreachable, stack.ControlPortUnreachable, 0, 0},
- {"Zero-length packet", 0, 0, header.ICMPv4PortUnreachable, stack.ControlPortUnreachable, 0, 2*header.IPv4MinimumSize + header.ICMPv4MinimumSize + 8},
- }
- r, err := buildIPv4Route(localIpv4Addr, "\x0a\x00\x00\xbb")
- if err != nil {
- t.Fatal(err)
- }
- for _, c := range cases {
- t.Run(c.name, func(t *testing.T) {
- o := testObject{t: t}
- proto := ipv4.NewProtocol()
- ep, err := proto.NewEndpoint(1, tcpip.AddressWithPrefix{localIpv4Addr, localIpv4PrefixLen}, nil, &o, nil)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
- defer ep.Close()
-
- const dataOffset = header.IPv4MinimumSize*2 + header.ICMPv4MinimumSize
- view := buffer.NewView(dataOffset + 8)
-
- // Create the outer IPv4 header.
- ip := header.IPv4(view)
- ip.Encode(&header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TotalLength: uint16(len(view) - c.trunc),
- TTL: 20,
- Protocol: uint8(header.ICMPv4ProtocolNumber),
- SrcAddr: "\x0a\x00\x00\xbb",
- DstAddr: localIpv4Addr,
- })
-
- // Create the ICMP header.
- icmp := header.ICMPv4(view[header.IPv4MinimumSize:])
- icmp.SetType(header.ICMPv4DstUnreachable)
- icmp.SetCode(c.code)
- icmp.SetIdent(0xdead)
- icmp.SetSequence(0xbeef)
-
- // Create the inner IPv4 header.
- ip = header.IPv4(view[header.IPv4MinimumSize+header.ICMPv4MinimumSize:])
- ip.Encode(&header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TotalLength: 100,
- TTL: 20,
- Protocol: 10,
- FragmentOffset: c.fragmentOffset,
- SrcAddr: localIpv4Addr,
- DstAddr: remoteIpv4Addr,
- })
-
- // Make payload be non-zero.
- for i := dataOffset; i < len(view); i++ {
- view[i] = uint8(i)
- }
-
- // Give packet to IPv4 endpoint, dispatcher will validate that
- // it's ok.
- o.protocol = 10
- o.srcAddr = remoteIpv4Addr
- o.dstAddr = localIpv4Addr
- o.contents = view[dataOffset:]
- o.typ = c.expectedTyp
- o.extra = c.expectedExtra
-
- vv := view[:len(view)-c.trunc].ToVectorisedView()
- ep.HandlePacket(&r, vv)
- if want := c.expectedCount; o.controlCalls != want {
- t.Fatalf("Bad number of control calls for %q case: got %v, want %v", c.name, o.controlCalls, want)
- }
- })
- }
-}
-
-func TestIPv4FragmentationReceive(t *testing.T) {
- o := testObject{t: t, v4: true}
- proto := ipv4.NewProtocol()
- ep, err := proto.NewEndpoint(1, tcpip.AddressWithPrefix{localIpv4Addr, localIpv4PrefixLen}, nil, &o, nil)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- totalLen := header.IPv4MinimumSize + 24
-
- frag1 := buffer.NewView(totalLen)
- ip1 := header.IPv4(frag1)
- ip1.Encode(&header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TotalLength: uint16(totalLen),
- TTL: 20,
- Protocol: 10,
- FragmentOffset: 0,
- Flags: header.IPv4FlagMoreFragments,
- SrcAddr: remoteIpv4Addr,
- DstAddr: localIpv4Addr,
- })
- // Make payload be non-zero.
- for i := header.IPv4MinimumSize; i < totalLen; i++ {
- frag1[i] = uint8(i)
- }
-
- frag2 := buffer.NewView(totalLen)
- ip2 := header.IPv4(frag2)
- ip2.Encode(&header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TotalLength: uint16(totalLen),
- TTL: 20,
- Protocol: 10,
- FragmentOffset: 24,
- SrcAddr: remoteIpv4Addr,
- DstAddr: localIpv4Addr,
- })
- // Make payload be non-zero.
- for i := header.IPv4MinimumSize; i < totalLen; i++ {
- frag2[i] = uint8(i)
- }
-
- // Give packet to ipv4 endpoint, dispatcher will validate that it's ok.
- o.protocol = 10
- o.srcAddr = remoteIpv4Addr
- o.dstAddr = localIpv4Addr
- o.contents = append(frag1[header.IPv4MinimumSize:totalLen], frag2[header.IPv4MinimumSize:totalLen]...)
-
- r, err := buildIPv4Route(localIpv4Addr, remoteIpv4Addr)
- if err != nil {
- t.Fatalf("could not find route: %v", err)
- }
-
- // Send first segment.
- ep.HandlePacket(&r, frag1.ToVectorisedView())
- if o.dataCalls != 0 {
- t.Fatalf("Bad number of data calls: got %x, want 0", o.dataCalls)
- }
-
- // Send second segment.
- ep.HandlePacket(&r, frag2.ToVectorisedView())
- if o.dataCalls != 1 {
- t.Fatalf("Bad number of data calls: got %x, want 1", o.dataCalls)
- }
-}
-
-func TestIPv6Send(t *testing.T) {
- o := testObject{t: t}
- proto := ipv6.NewProtocol()
- ep, err := proto.NewEndpoint(1, tcpip.AddressWithPrefix{localIpv6Addr, localIpv6PrefixLen}, nil, nil, &o)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- // Allocate and initialize the payload view.
- payload := buffer.NewView(100)
- for i := 0; i < len(payload); i++ {
- payload[i] = uint8(i)
- }
-
- // Allocate the header buffer.
- hdr := buffer.NewPrependable(int(ep.MaxHeaderLength()))
-
- // Issue the write.
- o.protocol = 123
- o.srcAddr = localIpv6Addr
- o.dstAddr = remoteIpv6Addr
- o.contents = payload
-
- r, err := buildIPv6Route(localIpv6Addr, remoteIpv6Addr)
- if err != nil {
- t.Fatalf("could not find route: %v", err)
- }
- if err := ep.WritePacket(&r, nil /* gso */, hdr, payload.ToVectorisedView(), 123, 123, stack.PacketOut); err != nil {
- t.Fatalf("WritePacket failed: %v", err)
- }
-}
-
-func TestIPv6Receive(t *testing.T) {
- o := testObject{t: t}
- proto := ipv6.NewProtocol()
- ep, err := proto.NewEndpoint(1, tcpip.AddressWithPrefix{localIpv6Addr, localIpv6PrefixLen}, nil, &o, nil)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- totalLen := header.IPv6MinimumSize + 30
- view := buffer.NewView(totalLen)
- ip := header.IPv6(view)
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(totalLen - header.IPv6MinimumSize),
- NextHeader: 10,
- HopLimit: 20,
- SrcAddr: remoteIpv6Addr,
- DstAddr: localIpv6Addr,
- })
-
- // Make payload be non-zero.
- for i := header.IPv6MinimumSize; i < totalLen; i++ {
- view[i] = uint8(i)
- }
-
- // Give packet to ipv6 endpoint, dispatcher will validate that it's ok.
- o.protocol = 10
- o.srcAddr = remoteIpv6Addr
- o.dstAddr = localIpv6Addr
- o.contents = view[header.IPv6MinimumSize:totalLen]
-
- r, err := buildIPv6Route(localIpv6Addr, remoteIpv6Addr)
- if err != nil {
- t.Fatalf("could not find route: %v", err)
- }
-
- ep.HandlePacket(&r, view.ToVectorisedView())
- if o.dataCalls != 1 {
- t.Fatalf("Bad number of data calls: got %x, want 1", o.dataCalls)
- }
-}
-
-func TestIPv6ReceiveControl(t *testing.T) {
- newUint16 := func(v uint16) *uint16 { return &v }
-
- const mtu = 0xffff
- cases := []struct {
- name string
- expectedCount int
- fragmentOffset *uint16
- typ header.ICMPv6Type
- code uint8
- expectedTyp stack.ControlType
- expectedExtra uint32
- trunc int
- }{
- {"PacketTooBig", 1, nil, header.ICMPv6PacketTooBig, 0, stack.ControlPacketTooBig, mtu, 0},
- {"Truncated (10 bytes missing)", 0, nil, header.ICMPv6PacketTooBig, 0, stack.ControlPacketTooBig, mtu, 10},
- {"Truncated (missing IPv6 header)", 0, nil, header.ICMPv6PacketTooBig, 0, stack.ControlPacketTooBig, mtu, header.IPv6MinimumSize + 8},
- {"Truncated PacketTooBig (missing 'extra info')", 0, nil, header.ICMPv6PacketTooBig, 0, stack.ControlPacketTooBig, mtu, 4 + header.IPv6MinimumSize + 8},
- {"Truncated (missing ICMP header)", 0, nil, header.ICMPv6PacketTooBig, 0, stack.ControlPacketTooBig, mtu, header.ICMPv6PacketTooBigMinimumSize + header.IPv6MinimumSize + 8},
- {"Port unreachable", 1, nil, header.ICMPv6DstUnreachable, header.ICMPv6PortUnreachable, stack.ControlPortUnreachable, 0, 0},
- {"Truncated DstUnreachable (missing 'extra info')", 0, nil, header.ICMPv6DstUnreachable, header.ICMPv6PortUnreachable, stack.ControlPortUnreachable, 0, 4 + header.IPv6MinimumSize + 8},
- {"Fragmented, zero offset", 1, newUint16(0), header.ICMPv6DstUnreachable, header.ICMPv6PortUnreachable, stack.ControlPortUnreachable, 0, 0},
- {"Non-zero fragment offset", 0, newUint16(100), header.ICMPv6DstUnreachable, header.ICMPv6PortUnreachable, stack.ControlPortUnreachable, 0, 0},
- {"Zero-length packet", 0, nil, header.ICMPv6DstUnreachable, header.ICMPv6PortUnreachable, stack.ControlPortUnreachable, 0, 2*header.IPv6MinimumSize + header.ICMPv6DstUnreachableMinimumSize + 8},
- }
- r, err := buildIPv6Route(
- localIpv6Addr,
- "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa",
- )
- if err != nil {
- t.Fatal(err)
- }
- for _, c := range cases {
- t.Run(c.name, func(t *testing.T) {
- o := testObject{t: t}
- proto := ipv6.NewProtocol()
- ep, err := proto.NewEndpoint(1, tcpip.AddressWithPrefix{localIpv6Addr, localIpv6PrefixLen}, nil, &o, nil)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- defer ep.Close()
-
- dataOffset := header.IPv6MinimumSize*2 + header.ICMPv6MinimumSize
- if c.fragmentOffset != nil {
- dataOffset += header.IPv6FragmentHeaderSize
- }
- view := buffer.NewView(dataOffset + 8)
-
- // Create the outer IPv6 header.
- ip := header.IPv6(view)
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(len(view) - header.IPv6MinimumSize - c.trunc),
- NextHeader: uint8(header.ICMPv6ProtocolNumber),
- HopLimit: 20,
- SrcAddr: "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa",
- DstAddr: localIpv6Addr,
- })
-
- // Create the ICMP header.
- icmp := header.ICMPv6(view[header.IPv6MinimumSize:])
- icmp.SetType(c.typ)
- icmp.SetCode(c.code)
- icmp.SetIdent(0xdead)
- icmp.SetSequence(0xbeef)
-
- // Create the inner IPv6 header.
- ip = header.IPv6(view[header.IPv6MinimumSize+header.ICMPv6PayloadOffset:])
- ip.Encode(&header.IPv6Fields{
- PayloadLength: 100,
- NextHeader: 10,
- HopLimit: 20,
- SrcAddr: localIpv6Addr,
- DstAddr: remoteIpv6Addr,
- })
-
- // Build the fragmentation header if needed.
- if c.fragmentOffset != nil {
- ip.SetNextHeader(header.IPv6FragmentHeader)
- frag := header.IPv6Fragment(view[2*header.IPv6MinimumSize+header.ICMPv6MinimumSize:])
- frag.Encode(&header.IPv6FragmentFields{
- NextHeader: 10,
- FragmentOffset: *c.fragmentOffset,
- M: true,
- Identification: 0x12345678,
- })
- }
-
- // Make payload be non-zero.
- for i := dataOffset; i < len(view); i++ {
- view[i] = uint8(i)
- }
-
- // Give packet to IPv6 endpoint, dispatcher will validate that
- // it's ok.
- o.protocol = 10
- o.srcAddr = remoteIpv6Addr
- o.dstAddr = localIpv6Addr
- o.contents = view[dataOffset:]
- o.typ = c.expectedTyp
- o.extra = c.expectedExtra
-
- vv := view[:len(view)-c.trunc].ToVectorisedView()
- ep.HandlePacket(&r, vv)
- if want := c.expectedCount; o.controlCalls != want {
- t.Fatalf("Bad number of control calls for %q case: got %v, want %v", c.name, o.controlCalls, want)
- }
- })
- }
-}
diff --git a/pkg/tcpip/network/ipv4/BUILD b/pkg/tcpip/network/ipv4/BUILD
deleted file mode 100644
index 58e537aad..000000000
--- a/pkg/tcpip/network/ipv4/BUILD
+++ /dev/null
@@ -1,42 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "ipv4",
- srcs = [
- "icmp.go",
- "ipv4.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/network/ipv4",
- visibility = [
- "//visibility:public",
- ],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/network/fragmentation",
- "//pkg/tcpip/network/hash",
- "//pkg/tcpip/stack",
- ],
-)
-
-go_test(
- name = "ipv4_test",
- size = "small",
- srcs = ["ipv4_test.go"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/link/channel",
- "//pkg/tcpip/link/sniffer",
- "//pkg/tcpip/network/ipv4",
- "//pkg/tcpip/stack",
- "//pkg/tcpip/transport/tcp",
- "//pkg/tcpip/transport/udp",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/tcpip/network/ipv4/ipv4_state_autogen.go b/pkg/tcpip/network/ipv4/ipv4_state_autogen.go
new file mode 100755
index 000000000..6b2cc0142
--- /dev/null
+++ b/pkg/tcpip/network/ipv4/ipv4_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package ipv4
+
diff --git a/pkg/tcpip/network/ipv4/ipv4_test.go b/pkg/tcpip/network/ipv4/ipv4_test.go
deleted file mode 100644
index ae827ca27..000000000
--- a/pkg/tcpip/network/ipv4/ipv4_test.go
+++ /dev/null
@@ -1,366 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ipv4_test
-
-import (
- "bytes"
- "encoding/hex"
- "math/rand"
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/link/channel"
- "gvisor.dev/gvisor/pkg/tcpip/link/sniffer"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
- "gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
- "gvisor.dev/gvisor/pkg/tcpip/transport/udp"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-func TestExcludeBroadcast(t *testing.T) {
- s := stack.New([]string{ipv4.ProtocolName}, []string{udp.ProtocolName}, stack.Options{})
-
- const defaultMTU = 65536
- ep := stack.LinkEndpoint(channel.New(256, defaultMTU, ""))
- if testing.Verbose() {
- ep = sniffer.New(ep)
- }
- if err := s.CreateNIC(1, ep); err != nil {
- t.Fatalf("CreateNIC failed: %v", err)
- }
-
- if err := s.AddAddress(1, ipv4.ProtocolNumber, header.IPv4Broadcast); err != nil {
- t.Fatalf("AddAddress failed: %v", err)
- }
- if err := s.AddAddress(1, ipv4.ProtocolNumber, header.IPv4Any); err != nil {
- t.Fatalf("AddAddress failed: %v", err)
- }
-
- s.SetRouteTable([]tcpip.Route{{
- Destination: header.IPv4EmptySubnet,
- NIC: 1,
- }})
-
- randomAddr := tcpip.FullAddress{NIC: 1, Addr: "\x0a\x00\x00\x01", Port: 53}
-
- var wq waiter.Queue
- t.Run("WithoutPrimaryAddress", func(t *testing.T) {
- ep, err := s.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, &wq)
- if err != nil {
- t.Fatal(err)
- }
- defer ep.Close()
-
- // Cannot connect using a broadcast address as the source.
- if err := ep.Connect(randomAddr); err != tcpip.ErrNoRoute {
- t.Errorf("got ep.Connect(...) = %v, want = %v", err, tcpip.ErrNoRoute)
- }
-
- // However, we can bind to a broadcast address to listen.
- if err := ep.Bind(tcpip.FullAddress{Addr: header.IPv4Broadcast, Port: 53, NIC: 1}); err != nil {
- t.Errorf("Bind failed: %v", err)
- }
- })
-
- t.Run("WithPrimaryAddress", func(t *testing.T) {
- ep, err := s.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, &wq)
- if err != nil {
- t.Fatal(err)
- }
- defer ep.Close()
-
- // Add a valid primary endpoint address, now we can connect.
- if err := s.AddAddress(1, ipv4.ProtocolNumber, "\x0a\x00\x00\x02"); err != nil {
- t.Fatalf("AddAddress failed: %v", err)
- }
- if err := ep.Connect(randomAddr); err != nil {
- t.Errorf("Connect failed: %v", err)
- }
- })
-}
-
-// makeHdrAndPayload generates a randomize packet. hdrLength indicates how much
-// data should already be in the header before WritePacket. extraLength
-// indicates how much extra space should be in the header. The payload is made
-// from many Views of the sizes listed in viewSizes.
-func makeHdrAndPayload(hdrLength int, extraLength int, viewSizes []int) (buffer.Prependable, buffer.VectorisedView) {
- hdr := buffer.NewPrependable(hdrLength + extraLength)
- hdr.Prepend(hdrLength)
- rand.Read(hdr.View())
-
- var views []buffer.View
- totalLength := 0
- for _, s := range viewSizes {
- newView := buffer.NewView(s)
- rand.Read(newView)
- views = append(views, newView)
- totalLength += s
- }
- payload := buffer.NewVectorisedView(totalLength, views)
- return hdr, payload
-}
-
-// comparePayloads compared the contents of all the packets against the contents
-// of the source packet.
-func compareFragments(t *testing.T, packets []packetInfo, sourcePacketInfo packetInfo, mtu uint32) {
- t.Helper()
- // Make a complete array of the sourcePacketInfo packet.
- source := header.IPv4(packets[0].Header.View()[:header.IPv4MinimumSize])
- source = append(source, sourcePacketInfo.Header.View()...)
- source = append(source, sourcePacketInfo.Payload.ToView()...)
-
- // Make a copy of the IP header, which will be modified in some fields to make
- // an expected header.
- sourceCopy := header.IPv4(append(buffer.View(nil), source[:source.HeaderLength()]...))
- sourceCopy.SetChecksum(0)
- sourceCopy.SetFlagsFragmentOffset(0, 0)
- sourceCopy.SetTotalLength(0)
- var offset uint16
- // Build up an array of the bytes sent.
- var reassembledPayload []byte
- for i, packet := range packets {
- // Confirm that the packet is valid.
- allBytes := packet.Header.View().ToVectorisedView()
- allBytes.Append(packet.Payload)
- ip := header.IPv4(allBytes.ToView())
- if !ip.IsValid(len(ip)) {
- t.Errorf("IP packet is invalid:\n%s", hex.Dump(ip))
- }
- if got, want := ip.CalculateChecksum(), uint16(0xffff); got != want {
- t.Errorf("ip.CalculateChecksum() got %#x, want %#x", got, want)
- }
- if got, want := len(ip), int(mtu); got > want {
- t.Errorf("fragment is too large, got %d want %d", got, want)
- }
- if got, want := packet.Header.UsedLength(), sourcePacketInfo.Header.UsedLength()+header.IPv4MinimumSize; i == 0 && want < int(mtu) && got != want {
- t.Errorf("first fragment hdr parts should have unmodified length if possible: got %d, want %d", got, want)
- }
- if got, want := packet.Header.AvailableLength(), sourcePacketInfo.Header.AvailableLength()-header.IPv4MinimumSize; got != want {
- t.Errorf("fragment #%d should have the same available space for prepending as source: got %d, want %d", i, got, want)
- }
- if i < len(packets)-1 {
- sourceCopy.SetFlagsFragmentOffset(sourceCopy.Flags()|header.IPv4FlagMoreFragments, offset)
- } else {
- sourceCopy.SetFlagsFragmentOffset(sourceCopy.Flags()&^header.IPv4FlagMoreFragments, offset)
- }
- reassembledPayload = append(reassembledPayload, ip.Payload()...)
- offset += ip.TotalLength() - uint16(ip.HeaderLength())
- // Clear out the checksum and length from the ip because we can't compare
- // it.
- sourceCopy.SetTotalLength(uint16(len(ip)))
- sourceCopy.SetChecksum(0)
- sourceCopy.SetChecksum(^sourceCopy.CalculateChecksum())
- if !bytes.Equal(ip[:ip.HeaderLength()], sourceCopy[:sourceCopy.HeaderLength()]) {
- t.Errorf("ip[:ip.HeaderLength()] got:\n%s\nwant:\n%s", hex.Dump(ip[:ip.HeaderLength()]), hex.Dump(sourceCopy[:sourceCopy.HeaderLength()]))
- }
- }
- expected := source[source.HeaderLength():]
- if !bytes.Equal(reassembledPayload, expected) {
- t.Errorf("reassembledPayload got:\n%s\nwant:\n%s", hex.Dump(reassembledPayload), hex.Dump(expected))
- }
-}
-
-type errorChannel struct {
- *channel.Endpoint
- Ch chan packetInfo
- packetCollectorErrors []*tcpip.Error
-}
-
-// newErrorChannel creates a new errorChannel endpoint. Each call to WritePacket
-// will return successive errors from packetCollectorErrors until the list is
-// empty and then return nil each time.
-func newErrorChannel(size int, mtu uint32, linkAddr tcpip.LinkAddress, packetCollectorErrors []*tcpip.Error) *errorChannel {
- return &errorChannel{
- Endpoint: channel.New(size, mtu, linkAddr),
- Ch: make(chan packetInfo, size),
- packetCollectorErrors: packetCollectorErrors,
- }
-}
-
-// packetInfo holds all the information about an outbound packet.
-type packetInfo struct {
- Header buffer.Prependable
- Payload buffer.VectorisedView
-}
-
-// Drain removes all outbound packets from the channel and counts them.
-func (e *errorChannel) Drain() int {
- c := 0
- for {
- select {
- case <-e.Ch:
- c++
- default:
- return c
- }
- }
-}
-
-// WritePacket stores outbound packets into the channel.
-func (e *errorChannel) WritePacket(r *stack.Route, gso *stack.GSO, hdr buffer.Prependable, payload buffer.VectorisedView, protocol tcpip.NetworkProtocolNumber) *tcpip.Error {
- p := packetInfo{
- Header: hdr,
- Payload: payload,
- }
-
- select {
- case e.Ch <- p:
- default:
- }
-
- nextError := (*tcpip.Error)(nil)
- if len(e.packetCollectorErrors) > 0 {
- nextError = e.packetCollectorErrors[0]
- e.packetCollectorErrors = e.packetCollectorErrors[1:]
- }
- return nextError
-}
-
-type context struct {
- stack.Route
- linkEP *errorChannel
-}
-
-func buildContext(t *testing.T, packetCollectorErrors []*tcpip.Error, mtu uint32) context {
- // Make the packet and write it.
- s := stack.New([]string{ipv4.ProtocolName}, []string{}, stack.Options{})
- ep := newErrorChannel(100 /* Enough for all tests. */, mtu, "", packetCollectorErrors)
- s.CreateNIC(1, ep)
- const (
- src = "\x10\x00\x00\x01"
- dst = "\x10\x00\x00\x02"
- )
- s.AddAddress(1, ipv4.ProtocolNumber, src)
- {
- subnet, err := tcpip.NewSubnet(dst, tcpip.AddressMask(header.IPv4Broadcast))
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{
- Destination: subnet,
- NIC: 1,
- }})
- }
- r, err := s.FindRoute(0, src, dst, ipv4.ProtocolNumber, false /* multicastLoop */)
- if err != nil {
- t.Fatalf("s.FindRoute got %v, want %v", err, nil)
- }
- return context{
- Route: r,
- linkEP: ep,
- }
-}
-
-func TestFragmentation(t *testing.T) {
- var manyPayloadViewsSizes [1000]int
- for i := range manyPayloadViewsSizes {
- manyPayloadViewsSizes[i] = 7
- }
- fragTests := []struct {
- description string
- mtu uint32
- gso *stack.GSO
- hdrLength int
- extraLength int
- payloadViewsSizes []int
- expectedFrags int
- }{
- {"NoFragmentation", 2000, &stack.GSO{}, 0, header.IPv4MinimumSize, []int{1000}, 1},
- {"NoFragmentationWithBigHeader", 2000, &stack.GSO{}, 16, header.IPv4MinimumSize, []int{1000}, 1},
- {"Fragmented", 800, &stack.GSO{}, 0, header.IPv4MinimumSize, []int{1000}, 2},
- {"FragmentedWithGsoNil", 800, nil, 0, header.IPv4MinimumSize, []int{1000}, 2},
- {"FragmentedWithManyViews", 300, &stack.GSO{}, 0, header.IPv4MinimumSize, manyPayloadViewsSizes[:], 25},
- {"FragmentedWithManyViewsAndPrependableBytes", 300, &stack.GSO{}, 0, header.IPv4MinimumSize + 55, manyPayloadViewsSizes[:], 25},
- {"FragmentedWithBigHeader", 800, &stack.GSO{}, 20, header.IPv4MinimumSize, []int{1000}, 2},
- {"FragmentedWithBigHeaderAndPrependableBytes", 800, &stack.GSO{}, 20, header.IPv4MinimumSize + 66, []int{1000}, 2},
- {"FragmentedWithMTUSmallerThanHeaderAndPrependableBytes", 300, &stack.GSO{}, 1000, header.IPv4MinimumSize + 77, []int{500}, 6},
- }
-
- for _, ft := range fragTests {
- t.Run(ft.description, func(t *testing.T) {
- hdr, payload := makeHdrAndPayload(ft.hdrLength, ft.extraLength, ft.payloadViewsSizes)
- source := packetInfo{
- Header: hdr,
- // Save the source payload because WritePacket will modify it.
- Payload: payload.Clone([]buffer.View{}),
- }
- c := buildContext(t, nil, ft.mtu)
- err := c.Route.WritePacket(ft.gso, hdr, payload, tcp.ProtocolNumber, 42)
- if err != nil {
- t.Errorf("err got %v, want %v", err, nil)
- }
-
- var results []packetInfo
- L:
- for {
- select {
- case pi := <-c.linkEP.Ch:
- results = append(results, pi)
- default:
- break L
- }
- }
-
- if got, want := len(results), ft.expectedFrags; got != want {
- t.Errorf("len(result) got %d, want %d", got, want)
- }
- if got, want := len(results), int(c.Route.Stats().IP.PacketsSent.Value()); got != want {
- t.Errorf("no errors yet len(result) got %d, want %d", got, want)
- }
- compareFragments(t, results, source, ft.mtu)
- })
- }
-}
-
-// TestFragmentationErrors checks that errors are returned from write packet
-// correctly.
-func TestFragmentationErrors(t *testing.T) {
- fragTests := []struct {
- description string
- mtu uint32
- hdrLength int
- payloadViewsSizes []int
- packetCollectorErrors []*tcpip.Error
- }{
- {"NoFrag", 2000, 0, []int{1000}, []*tcpip.Error{tcpip.ErrAborted}},
- {"ErrorOnFirstFrag", 500, 0, []int{1000}, []*tcpip.Error{tcpip.ErrAborted}},
- {"ErrorOnSecondFrag", 500, 0, []int{1000}, []*tcpip.Error{nil, tcpip.ErrAborted}},
- {"ErrorOnFirstFragMTUSmallerThanHdr", 500, 1000, []int{500}, []*tcpip.Error{tcpip.ErrAborted}},
- }
-
- for _, ft := range fragTests {
- t.Run(ft.description, func(t *testing.T) {
- hdr, payload := makeHdrAndPayload(ft.hdrLength, header.IPv4MinimumSize, ft.payloadViewsSizes)
- c := buildContext(t, ft.packetCollectorErrors, ft.mtu)
- err := c.Route.WritePacket(&stack.GSO{}, hdr, payload, tcp.ProtocolNumber, 42)
- for i := 0; i < len(ft.packetCollectorErrors)-1; i++ {
- if got, want := ft.packetCollectorErrors[i], (*tcpip.Error)(nil); got != want {
- t.Errorf("ft.packetCollectorErrors[%d] got %v, want %v", i, got, want)
- }
- }
- // We only need to check that last error because all the ones before are
- // nil.
- if got, want := err, ft.packetCollectorErrors[len(ft.packetCollectorErrors)-1]; got != want {
- t.Errorf("err got %v, want %v", got, want)
- }
- if got, want := c.linkEP.Drain(), int(c.Route.Stats().IP.PacketsSent.Value())+1; err != nil && got != want {
- t.Errorf("after linkEP error len(result) got %d, want %d", got, want)
- }
- })
- }
-}
diff --git a/pkg/tcpip/network/ipv6/BUILD b/pkg/tcpip/network/ipv6/BUILD
deleted file mode 100644
index a471abbfb..000000000
--- a/pkg/tcpip/network/ipv6/BUILD
+++ /dev/null
@@ -1,42 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "ipv6",
- srcs = [
- "icmp.go",
- "ipv6.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/network/ipv6",
- visibility = [
- "//visibility:public",
- ],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/stack",
- ],
-)
-
-go_test(
- name = "ipv6_test",
- size = "small",
- srcs = [
- "icmp_test.go",
- "ndp_test.go",
- ],
- embed = [":ipv6"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/link/channel",
- "//pkg/tcpip/link/sniffer",
- "//pkg/tcpip/stack",
- "//pkg/tcpip/transport/icmp",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/tcpip/network/ipv6/icmp_test.go b/pkg/tcpip/network/ipv6/icmp_test.go
deleted file mode 100644
index a6a1a5232..000000000
--- a/pkg/tcpip/network/ipv6/icmp_test.go
+++ /dev/null
@@ -1,362 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ipv6
-
-import (
- "fmt"
- "reflect"
- "strings"
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/link/channel"
- "gvisor.dev/gvisor/pkg/tcpip/link/sniffer"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
- "gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-const (
- linkAddr0 = tcpip.LinkAddress("\x01\x02\x03\x04\x05\x06")
- linkAddr1 = tcpip.LinkAddress("\x0a\x0b\x0c\x0d\x0e\x0f")
-)
-
-var (
- lladdr0 = header.LinkLocalAddr(linkAddr0)
- lladdr1 = header.LinkLocalAddr(linkAddr1)
-)
-
-type stubLinkEndpoint struct {
- stack.LinkEndpoint
-}
-
-func (*stubLinkEndpoint) Capabilities() stack.LinkEndpointCapabilities {
- return 0
-}
-
-func (*stubLinkEndpoint) MaxHeaderLength() uint16 {
- return 0
-}
-
-func (*stubLinkEndpoint) LinkAddress() tcpip.LinkAddress {
- return ""
-}
-
-func (*stubLinkEndpoint) WritePacket(*stack.Route, *stack.GSO, buffer.Prependable, buffer.VectorisedView, tcpip.NetworkProtocolNumber) *tcpip.Error {
- return nil
-}
-
-func (*stubLinkEndpoint) Attach(stack.NetworkDispatcher) {}
-
-type stubDispatcher struct {
- stack.TransportDispatcher
-}
-
-func (*stubDispatcher) DeliverTransportPacket(*stack.Route, tcpip.TransportProtocolNumber, buffer.View, buffer.VectorisedView) {
-}
-
-type stubLinkAddressCache struct {
- stack.LinkAddressCache
-}
-
-func (*stubLinkAddressCache) CheckLocalAddress(tcpip.NICID, tcpip.NetworkProtocolNumber, tcpip.Address) tcpip.NICID {
- return 0
-}
-
-func (*stubLinkAddressCache) AddLinkAddress(tcpip.NICID, tcpip.Address, tcpip.LinkAddress) {
-}
-
-func TestICMPCounts(t *testing.T) {
- s := stack.New([]string{ProtocolName}, []string{icmp.ProtocolName6}, stack.Options{})
- {
- if err := s.CreateNIC(1, &stubLinkEndpoint{}); err != nil {
- t.Fatalf("CreateNIC(_) = %s", err)
- }
- if err := s.AddAddress(1, ProtocolNumber, lladdr0); err != nil {
- t.Fatalf("AddAddress(_, %d, %s) = %s", ProtocolNumber, lladdr0, err)
- }
- }
- {
- subnet, err := tcpip.NewSubnet(lladdr1, tcpip.AddressMask(strings.Repeat("\xff", len(lladdr1))))
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable(
- []tcpip.Route{{
- Destination: subnet,
- NIC: 1,
- }},
- )
- }
-
- netProto := s.NetworkProtocolInstance(ProtocolNumber)
- if netProto == nil {
- t.Fatalf("cannot find protocol instance for network protocol %d", ProtocolNumber)
- }
- ep, err := netProto.NewEndpoint(0, tcpip.AddressWithPrefix{lladdr1, netProto.DefaultPrefixLen()}, &stubLinkAddressCache{}, &stubDispatcher{}, nil)
- if err != nil {
- t.Fatalf("NewEndpoint(_) = _, %s, want = _, nil", err)
- }
-
- r, err := s.FindRoute(1, lladdr0, lladdr1, ProtocolNumber, false /* multicastLoop */)
- if err != nil {
- t.Fatalf("FindRoute(_) = _, %s, want = _, nil", err)
- }
- defer r.Release()
-
- types := []struct {
- typ header.ICMPv6Type
- size int
- }{
- {header.ICMPv6DstUnreachable, header.ICMPv6DstUnreachableMinimumSize},
- {header.ICMPv6PacketTooBig, header.ICMPv6PacketTooBigMinimumSize},
- {header.ICMPv6TimeExceeded, header.ICMPv6MinimumSize},
- {header.ICMPv6ParamProblem, header.ICMPv6MinimumSize},
- {header.ICMPv6EchoRequest, header.ICMPv6EchoMinimumSize},
- {header.ICMPv6EchoReply, header.ICMPv6EchoMinimumSize},
- {header.ICMPv6RouterSolicit, header.ICMPv6MinimumSize},
- {header.ICMPv6RouterAdvert, header.ICMPv6MinimumSize},
- {header.ICMPv6NeighborSolicit, header.ICMPv6NeighborSolicitMinimumSize},
- {header.ICMPv6NeighborAdvert, header.ICMPv6NeighborAdvertSize},
- {header.ICMPv6RedirectMsg, header.ICMPv6MinimumSize},
- }
-
- handleIPv6Payload := func(hdr buffer.Prependable) {
- payloadLength := hdr.UsedLength()
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(payloadLength),
- NextHeader: uint8(header.ICMPv6ProtocolNumber),
- HopLimit: r.DefaultTTL(),
- SrcAddr: r.LocalAddress,
- DstAddr: r.RemoteAddress,
- })
- ep.HandlePacket(&r, hdr.View().ToVectorisedView())
- }
-
- for _, typ := range types {
- hdr := buffer.NewPrependable(header.IPv6MinimumSize + typ.size)
- pkt := header.ICMPv6(hdr.Prepend(typ.size))
- pkt.SetType(typ.typ)
- pkt.SetChecksum(header.ICMPv6Checksum(pkt, r.LocalAddress, r.RemoteAddress, buffer.VectorisedView{}))
-
- handleIPv6Payload(hdr)
- }
-
- // Construct an empty ICMP packet so that
- // Stats().ICMP.ICMPv6ReceivedPacketStats.Invalid is incremented.
- handleIPv6Payload(buffer.NewPrependable(header.IPv6MinimumSize))
-
- icmpv6Stats := s.Stats().ICMP.V6PacketsReceived
- visitStats(reflect.ValueOf(&icmpv6Stats).Elem(), func(name string, s *tcpip.StatCounter) {
- if got, want := s.Value(), uint64(1); got != want {
- t.Errorf("got %s = %d, want = %d", name, got, want)
- }
- })
- if t.Failed() {
- t.Logf("stats:\n%+v", s.Stats())
- }
-}
-
-func visitStats(v reflect.Value, f func(string, *tcpip.StatCounter)) {
- t := v.Type()
- for i := 0; i < v.NumField(); i++ {
- v := v.Field(i)
- switch v.Kind() {
- case reflect.Ptr:
- f(t.Field(i).Name, v.Interface().(*tcpip.StatCounter))
- case reflect.Struct:
- visitStats(v, f)
- default:
- panic(fmt.Sprintf("unexpected type %s", v.Type()))
- }
- }
-}
-
-type testContext struct {
- s0 *stack.Stack
- s1 *stack.Stack
-
- linkEP0 *channel.Endpoint
- linkEP1 *channel.Endpoint
-}
-
-type endpointWithResolutionCapability struct {
- stack.LinkEndpoint
-}
-
-func (e endpointWithResolutionCapability) Capabilities() stack.LinkEndpointCapabilities {
- return e.LinkEndpoint.Capabilities() | stack.CapabilityResolutionRequired
-}
-
-func newTestContext(t *testing.T) *testContext {
- c := &testContext{
- s0: stack.New([]string{ProtocolName}, []string{icmp.ProtocolName6}, stack.Options{}),
- s1: stack.New([]string{ProtocolName}, []string{icmp.ProtocolName6}, stack.Options{}),
- }
-
- const defaultMTU = 65536
- c.linkEP0 = channel.New(256, defaultMTU, linkAddr0)
-
- wrappedEP0 := stack.LinkEndpoint(endpointWithResolutionCapability{LinkEndpoint: c.linkEP0})
- if testing.Verbose() {
- wrappedEP0 = sniffer.New(wrappedEP0)
- }
- if err := c.s0.CreateNIC(1, wrappedEP0); err != nil {
- t.Fatalf("CreateNIC s0: %v", err)
- }
- if err := c.s0.AddAddress(1, ProtocolNumber, lladdr0); err != nil {
- t.Fatalf("AddAddress lladdr0: %v", err)
- }
- if err := c.s0.AddAddress(1, ProtocolNumber, header.SolicitedNodeAddr(lladdr0)); err != nil {
- t.Fatalf("AddAddress sn lladdr0: %v", err)
- }
-
- c.linkEP1 = channel.New(256, defaultMTU, linkAddr1)
- wrappedEP1 := stack.LinkEndpoint(endpointWithResolutionCapability{LinkEndpoint: c.linkEP1})
- if err := c.s1.CreateNIC(1, wrappedEP1); err != nil {
- t.Fatalf("CreateNIC failed: %v", err)
- }
- if err := c.s1.AddAddress(1, ProtocolNumber, lladdr1); err != nil {
- t.Fatalf("AddAddress lladdr1: %v", err)
- }
- if err := c.s1.AddAddress(1, ProtocolNumber, header.SolicitedNodeAddr(lladdr1)); err != nil {
- t.Fatalf("AddAddress sn lladdr1: %v", err)
- }
-
- subnet0, err := tcpip.NewSubnet(lladdr1, tcpip.AddressMask(strings.Repeat("\xff", len(lladdr1))))
- if err != nil {
- t.Fatal(err)
- }
- c.s0.SetRouteTable(
- []tcpip.Route{{
- Destination: subnet0,
- NIC: 1,
- }},
- )
- subnet1, err := tcpip.NewSubnet(lladdr0, tcpip.AddressMask(strings.Repeat("\xff", len(lladdr0))))
- if err != nil {
- t.Fatal(err)
- }
- c.s1.SetRouteTable(
- []tcpip.Route{{
- Destination: subnet1,
- NIC: 1,
- }},
- )
-
- return c
-}
-
-func (c *testContext) cleanup() {
- close(c.linkEP0.C)
- close(c.linkEP1.C)
-}
-
-type routeArgs struct {
- src, dst *channel.Endpoint
- typ header.ICMPv6Type
-}
-
-func routeICMPv6Packet(t *testing.T, args routeArgs, fn func(*testing.T, header.ICMPv6)) {
- t.Helper()
-
- pkt := <-args.src.C
-
- {
- views := []buffer.View{pkt.Header, pkt.Payload}
- size := len(pkt.Header) + len(pkt.Payload)
- vv := buffer.NewVectorisedView(size, views)
- args.dst.InjectLinkAddr(pkt.Proto, args.dst.LinkAddress(), vv)
- }
-
- if pkt.Proto != ProtocolNumber {
- t.Errorf("unexpected protocol number %d", pkt.Proto)
- return
- }
- ipv6 := header.IPv6(pkt.Header)
- transProto := tcpip.TransportProtocolNumber(ipv6.NextHeader())
- if transProto != header.ICMPv6ProtocolNumber {
- t.Errorf("unexpected transport protocol number %d", transProto)
- return
- }
- icmpv6 := header.ICMPv6(ipv6.Payload())
- if got, want := icmpv6.Type(), args.typ; got != want {
- t.Errorf("got ICMPv6 type = %d, want = %d", got, want)
- return
- }
- if fn != nil {
- fn(t, icmpv6)
- }
-}
-
-func TestLinkResolution(t *testing.T) {
- c := newTestContext(t)
- defer c.cleanup()
-
- r, err := c.s0.FindRoute(1, lladdr0, lladdr1, ProtocolNumber, false /* multicastLoop */)
- if err != nil {
- t.Fatalf("FindRoute(_) = _, %s, want = _, nil", err)
- }
- defer r.Release()
-
- hdr := buffer.NewPrependable(int(r.MaxHeaderLength()) + header.IPv6MinimumSize + header.ICMPv6EchoMinimumSize)
- pkt := header.ICMPv6(hdr.Prepend(header.ICMPv6EchoMinimumSize))
- pkt.SetType(header.ICMPv6EchoRequest)
- pkt.SetChecksum(header.ICMPv6Checksum(pkt, r.LocalAddress, r.RemoteAddress, buffer.VectorisedView{}))
- payload := tcpip.SlicePayload(hdr.View())
-
- // We can't send our payload directly over the route because that
- // doesn't provoke NDP discovery.
- var wq waiter.Queue
- ep, err := c.s0.NewEndpoint(header.ICMPv6ProtocolNumber, ProtocolNumber, &wq)
- if err != nil {
- t.Fatalf("NewEndpoint(_) = _, %s, want = _, nil", err)
- }
-
- for {
- _, resCh, err := ep.Write(payload, tcpip.WriteOptions{To: &tcpip.FullAddress{NIC: 1, Addr: lladdr1}})
- if resCh != nil {
- if err != tcpip.ErrNoLinkAddress {
- t.Fatalf("ep.Write(_) = _, <non-nil>, %s, want = _, <non-nil>, tcpip.ErrNoLinkAddress", err)
- }
- for _, args := range []routeArgs{
- {src: c.linkEP0, dst: c.linkEP1, typ: header.ICMPv6NeighborSolicit},
- {src: c.linkEP1, dst: c.linkEP0, typ: header.ICMPv6NeighborAdvert},
- } {
- routeICMPv6Packet(t, args, func(t *testing.T, icmpv6 header.ICMPv6) {
- if got, want := tcpip.Address(icmpv6[8:][:16]), lladdr1; got != want {
- t.Errorf("%d: got target = %s, want = %s", icmpv6.Type(), got, want)
- }
- })
- }
- <-resCh
- continue
- }
- if err != nil {
- t.Fatalf("ep.Write(_) = _, _, %s", err)
- }
- break
- }
-
- for _, args := range []routeArgs{
- {src: c.linkEP0, dst: c.linkEP1, typ: header.ICMPv6EchoRequest},
- {src: c.linkEP1, dst: c.linkEP0, typ: header.ICMPv6EchoReply},
- } {
- routeICMPv6Packet(t, args, nil)
- }
-}
diff --git a/pkg/tcpip/network/ipv6/ipv6_state_autogen.go b/pkg/tcpip/network/ipv6/ipv6_state_autogen.go
new file mode 100755
index 000000000..53319e0c4
--- /dev/null
+++ b/pkg/tcpip/network/ipv6/ipv6_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package ipv6
+
diff --git a/pkg/tcpip/network/ipv6/ndp_test.go b/pkg/tcpip/network/ipv6/ndp_test.go
deleted file mode 100644
index 571915d3f..000000000
--- a/pkg/tcpip/network/ipv6/ndp_test.go
+++ /dev/null
@@ -1,178 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ipv6
-
-import (
- "strings"
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
- "gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
-)
-
-// setupStackAndEndpoint creates a stack with a single NIC with a link-local
-// address llladdr and an IPv6 endpoint to a remote with link-local address
-// rlladdr
-func setupStackAndEndpoint(t *testing.T, llladdr, rlladdr tcpip.Address) (*stack.Stack, stack.NetworkEndpoint) {
- t.Helper()
-
- s := stack.New([]string{ProtocolName}, []string{icmp.ProtocolName6}, stack.Options{})
-
- if err := s.CreateNIC(1, &stubLinkEndpoint{}); err != nil {
- t.Fatalf("CreateNIC(_) = %s", err)
- }
- if err := s.AddAddress(1, ProtocolNumber, llladdr); err != nil {
- t.Fatalf("AddAddress(_, %d, %s) = %s", ProtocolNumber, llladdr, err)
- }
-
- {
- subnet, err := tcpip.NewSubnet(rlladdr, tcpip.AddressMask(strings.Repeat("\xff", len(rlladdr))))
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable(
- []tcpip.Route{{
- Destination: subnet,
- NIC: 1,
- }},
- )
- }
-
- netProto := s.NetworkProtocolInstance(ProtocolNumber)
- if netProto == nil {
- t.Fatalf("cannot find protocol instance for network protocol %d", ProtocolNumber)
- }
-
- ep, err := netProto.NewEndpoint(0, tcpip.AddressWithPrefix{rlladdr, netProto.DefaultPrefixLen()}, &stubLinkAddressCache{}, &stubDispatcher{}, nil)
- if err != nil {
- t.Fatalf("NewEndpoint(_) = _, %s, want = _, nil", err)
- }
-
- return s, ep
-}
-
-// TestHopLimitValidation is a test that makes sure that NDP packets are only
-// received if their IP header's hop limit is set to 255.
-func TestHopLimitValidation(t *testing.T) {
- setup := func(t *testing.T) (*stack.Stack, stack.NetworkEndpoint, stack.Route) {
- t.Helper()
-
- // Create a stack with the assigned link-local address lladdr0
- // and an endpoint to lladdr1.
- s, ep := setupStackAndEndpoint(t, lladdr0, lladdr1)
-
- r, err := s.FindRoute(1, lladdr0, lladdr1, ProtocolNumber, false /* multicastLoop */)
- if err != nil {
- t.Fatalf("FindRoute(_) = _, %s, want = _, nil", err)
- }
-
- return s, ep, r
- }
-
- handleIPv6Payload := func(hdr buffer.Prependable, hopLimit uint8, ep stack.NetworkEndpoint, r *stack.Route) {
- payloadLength := hdr.UsedLength()
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(payloadLength),
- NextHeader: uint8(header.ICMPv6ProtocolNumber),
- HopLimit: hopLimit,
- SrcAddr: r.LocalAddress,
- DstAddr: r.RemoteAddress,
- })
- ep.HandlePacket(r, hdr.View().ToVectorisedView())
- }
-
- types := []struct {
- name string
- typ header.ICMPv6Type
- size int
- statCounter func(tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter
- }{
- {"RouterSolicit", header.ICMPv6RouterSolicit, header.ICMPv6MinimumSize, func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.RouterSolicit
- }},
- {"RouterAdvert", header.ICMPv6RouterAdvert, header.ICMPv6MinimumSize, func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.RouterAdvert
- }},
- {"NeighborSolicit", header.ICMPv6NeighborSolicit, header.ICMPv6NeighborSolicitMinimumSize, func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.NeighborSolicit
- }},
- {"NeighborAdvert", header.ICMPv6NeighborAdvert, header.ICMPv6NeighborAdvertSize, func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.NeighborAdvert
- }},
- {"RedirectMsg", header.ICMPv6RedirectMsg, header.ICMPv6MinimumSize, func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.RedirectMsg
- }},
- }
-
- for _, typ := range types {
- t.Run(typ.name, func(t *testing.T) {
- s, ep, r := setup(t)
- defer r.Release()
-
- stats := s.Stats().ICMP.V6PacketsReceived
- invalid := stats.Invalid
- typStat := typ.statCounter(stats)
-
- hdr := buffer.NewPrependable(header.IPv6MinimumSize + typ.size)
- pkt := header.ICMPv6(hdr.Prepend(typ.size))
- pkt.SetType(typ.typ)
- pkt.SetChecksum(header.ICMPv6Checksum(pkt, r.LocalAddress, r.RemoteAddress, buffer.VectorisedView{}))
-
- // Invalid count should initially be 0.
- if got := invalid.Value(); got != 0 {
- t.Fatalf("got invalid = %d, want = 0", got)
- }
-
- // Should not have received any ICMPv6 packets with
- // type = typ.typ.
- if got := typStat.Value(); got != 0 {
- t.Fatalf("got %s = %d, want = 0", typ.name, got)
- }
-
- // Receive the NDP packet with an invalid hop limit
- // value.
- handleIPv6Payload(hdr, ndpHopLimit-1, ep, &r)
-
- // Invalid count should have increased.
- if got := invalid.Value(); got != 1 {
- t.Fatalf("got invalid = %d, want = 1", got)
- }
-
- // Rx count of NDP packet of type typ.typ should not
- // have increased.
- if got := typStat.Value(); got != 0 {
- t.Fatalf("got %s = %d, want = 0", typ.name, got)
- }
-
- // Receive the NDP packet with a valid hop limit value.
- handleIPv6Payload(hdr, ndpHopLimit, ep, &r)
-
- // Rx count of NDP packet of type typ.typ should have
- // increased.
- if got := typStat.Value(); got != 1 {
- t.Fatalf("got %s = %d, want = 1", typ.name, got)
- }
-
- // Invalid count should not have increased again.
- if got := invalid.Value(); got != 1 {
- t.Fatalf("got invalid = %d, want = 1", got)
- }
- })
- }
-}
diff --git a/pkg/tcpip/ports/BUILD b/pkg/tcpip/ports/BUILD
deleted file mode 100644
index 11efb4e44..000000000
--- a/pkg/tcpip/ports/BUILD
+++ /dev/null
@@ -1,23 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "ports",
- srcs = ["ports.go"],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/ports",
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/tcpip",
- ],
-)
-
-go_test(
- name = "ports_test",
- srcs = ["ports_test.go"],
- embed = [":ports"],
- deps = [
- "//pkg/tcpip",
- ],
-)
diff --git a/pkg/tcpip/ports/ports_state_autogen.go b/pkg/tcpip/ports/ports_state_autogen.go
new file mode 100755
index 000000000..664cc3e71
--- /dev/null
+++ b/pkg/tcpip/ports/ports_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package ports
+
diff --git a/pkg/tcpip/ports/ports_test.go b/pkg/tcpip/ports/ports_test.go
deleted file mode 100644
index 689401661..000000000
--- a/pkg/tcpip/ports/ports_test.go
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ports
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip"
-)
-
-const (
- fakeTransNumber tcpip.TransportProtocolNumber = 1
- fakeNetworkNumber tcpip.NetworkProtocolNumber = 2
-
- fakeIPAddress = tcpip.Address("\x08\x08\x08\x08")
- fakeIPAddress1 = tcpip.Address("\x08\x08\x08\x09")
-)
-
-type portReserveTestAction struct {
- port uint16
- ip tcpip.Address
- want *tcpip.Error
- reuse bool
- release bool
-}
-
-func TestPortReservation(t *testing.T) {
- for _, test := range []struct {
- tname string
- actions []portReserveTestAction
- }{
- {
- tname: "bind to ip",
- actions: []portReserveTestAction{
- {port: 80, ip: fakeIPAddress, want: nil},
- {port: 80, ip: fakeIPAddress1, want: nil},
- /* N.B. Order of tests matters! */
- {port: 80, ip: anyIPAddress, want: tcpip.ErrPortInUse},
- {port: 80, ip: fakeIPAddress, want: tcpip.ErrPortInUse, reuse: true},
- },
- },
- {
- tname: "bind to inaddr any",
- actions: []portReserveTestAction{
- {port: 22, ip: anyIPAddress, want: nil},
- {port: 22, ip: fakeIPAddress, want: tcpip.ErrPortInUse},
- /* release fakeIPAddress, but anyIPAddress is still inuse */
- {port: 22, ip: fakeIPAddress, release: true},
- {port: 22, ip: fakeIPAddress, want: tcpip.ErrPortInUse},
- {port: 22, ip: fakeIPAddress, want: tcpip.ErrPortInUse, reuse: true},
- /* Release port 22 from any IP address, then try to reserve fake IP address on 22 */
- {port: 22, ip: anyIPAddress, want: nil, release: true},
- {port: 22, ip: fakeIPAddress, want: nil},
- },
- }, {
- tname: "bind to zero port",
- actions: []portReserveTestAction{
- {port: 00, ip: fakeIPAddress, want: nil},
- {port: 00, ip: fakeIPAddress, want: nil},
- {port: 00, ip: fakeIPAddress, reuse: true, want: nil},
- },
- }, {
- tname: "bind to ip with reuseport",
- actions: []portReserveTestAction{
- {port: 25, ip: fakeIPAddress, reuse: true, want: nil},
- {port: 25, ip: fakeIPAddress, reuse: true, want: nil},
-
- {port: 25, ip: fakeIPAddress, reuse: false, want: tcpip.ErrPortInUse},
- {port: 25, ip: anyIPAddress, reuse: false, want: tcpip.ErrPortInUse},
-
- {port: 25, ip: anyIPAddress, reuse: true, want: nil},
- },
- }, {
- tname: "bind to inaddr any with reuseport",
- actions: []portReserveTestAction{
- {port: 24, ip: anyIPAddress, reuse: true, want: nil},
- {port: 24, ip: anyIPAddress, reuse: true, want: nil},
-
- {port: 24, ip: anyIPAddress, reuse: false, want: tcpip.ErrPortInUse},
- {port: 24, ip: fakeIPAddress, reuse: false, want: tcpip.ErrPortInUse},
-
- {port: 24, ip: fakeIPAddress, reuse: true, want: nil},
- {port: 24, ip: fakeIPAddress, release: true, want: nil},
-
- {port: 24, ip: anyIPAddress, release: true},
- {port: 24, ip: anyIPAddress, reuse: false, want: tcpip.ErrPortInUse},
-
- {port: 24, ip: anyIPAddress, release: true},
- {port: 24, ip: anyIPAddress, reuse: false, want: nil},
- },
- },
- } {
- t.Run(test.tname, func(t *testing.T) {
- pm := NewPortManager()
- net := []tcpip.NetworkProtocolNumber{fakeNetworkNumber}
-
- for _, test := range test.actions {
- if test.release {
- pm.ReleasePort(net, fakeTransNumber, test.ip, test.port)
- continue
- }
- gotPort, err := pm.ReservePort(net, fakeTransNumber, test.ip, test.port, test.reuse)
- if err != test.want {
- t.Fatalf("ReservePort(.., .., %s, %d, %t) = %v, want %v", test.ip, test.port, test.release, err, test.want)
- }
- if test.port == 0 && (gotPort == 0 || gotPort < FirstEphemeral) {
- t.Fatalf("ReservePort(.., .., .., 0) = %d, want port number >= %d to be picked", gotPort, FirstEphemeral)
- }
- }
- })
-
- }
-}
-
-func TestPickEphemeralPort(t *testing.T) {
- pm := NewPortManager()
- customErr := &tcpip.Error{}
- for _, test := range []struct {
- name string
- f func(port uint16) (bool, *tcpip.Error)
- wantErr *tcpip.Error
- wantPort uint16
- }{
- {
- name: "no-port-available",
- f: func(port uint16) (bool, *tcpip.Error) {
- return false, nil
- },
- wantErr: tcpip.ErrNoPortAvailable,
- },
- {
- name: "port-tester-error",
- f: func(port uint16) (bool, *tcpip.Error) {
- return false, customErr
- },
- wantErr: customErr,
- },
- {
- name: "only-port-16042-available",
- f: func(port uint16) (bool, *tcpip.Error) {
- if port == FirstEphemeral+42 {
- return true, nil
- }
- return false, nil
- },
- wantPort: FirstEphemeral + 42,
- },
- {
- name: "only-port-under-16000-available",
- f: func(port uint16) (bool, *tcpip.Error) {
- if port < FirstEphemeral {
- return true, nil
- }
- return false, nil
- },
- wantErr: tcpip.ErrNoPortAvailable,
- },
- } {
- t.Run(test.name, func(t *testing.T) {
- if port, err := pm.PickEphemeralPort(test.f); port != test.wantPort || err != test.wantErr {
- t.Errorf("PickEphemeralPort(..) = (port %d, err %v); want (port %d, err %v)", port, err, test.wantPort, test.wantErr)
- }
- })
- }
-}
diff --git a/pkg/tcpip/sample/tun_tcp_connect/BUILD b/pkg/tcpip/sample/tun_tcp_connect/BUILD
deleted file mode 100644
index a57752a7c..000000000
--- a/pkg/tcpip/sample/tun_tcp_connect/BUILD
+++ /dev/null
@@ -1,21 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary")
-
-package(licenses = ["notice"])
-
-go_binary(
- name = "tun_tcp_connect",
- srcs = ["main.go"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/link/fdbased",
- "//pkg/tcpip/link/rawfile",
- "//pkg/tcpip/link/sniffer",
- "//pkg/tcpip/link/tun",
- "//pkg/tcpip/network/ipv4",
- "//pkg/tcpip/stack",
- "//pkg/tcpip/transport/tcp",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/tcpip/sample/tun_tcp_connect/main.go b/pkg/tcpip/sample/tun_tcp_connect/main.go
deleted file mode 100644
index e2021cd15..000000000
--- a/pkg/tcpip/sample/tun_tcp_connect/main.go
+++ /dev/null
@@ -1,222 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// +build linux
-
-// This sample creates a stack with TCP and IPv4 protocols on top of a TUN
-// device, and connects to a peer. Similar to "nc <address> <port>". While the
-// sample is running, attempts to connect to its IPv4 address will result in
-// a RST segment.
-//
-// As an example of how to run it, a TUN device can be created and enabled on
-// a linux host as follows (this only needs to be done once per boot):
-//
-// [sudo] ip tuntap add user <username> mode tun <device-name>
-// [sudo] ip link set <device-name> up
-// [sudo] ip addr add <ipv4-address>/<mask-length> dev <device-name>
-//
-// A concrete example:
-//
-// $ sudo ip tuntap add user wedsonaf mode tun tun0
-// $ sudo ip link set tun0 up
-// $ sudo ip addr add 192.168.1.1/24 dev tun0
-//
-// Then one can run tun_tcp_connect as such:
-//
-// $ ./tun/tun_tcp_connect tun0 192.168.1.2 0 192.168.1.1 1234
-//
-// This will attempt to connect to the linux host's stack. One can run nc in
-// listen mode to accept a connect from tun_tcp_connect and exchange data.
-package main
-
-import (
- "bufio"
- "fmt"
- "log"
- "math/rand"
- "net"
- "os"
- "strconv"
- "time"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
- "gvisor.dev/gvisor/pkg/tcpip/link/rawfile"
- "gvisor.dev/gvisor/pkg/tcpip/link/sniffer"
- "gvisor.dev/gvisor/pkg/tcpip/link/tun"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
- "gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-// writer reads from standard input and writes to the endpoint until standard
-// input is closed. It signals that it's done by closing the provided channel.
-func writer(ch chan struct{}, ep tcpip.Endpoint) {
- defer func() {
- ep.Shutdown(tcpip.ShutdownWrite)
- close(ch)
- }()
-
- r := bufio.NewReader(os.Stdin)
- for {
- v := buffer.NewView(1024)
- n, err := r.Read(v)
- if err != nil {
- return
- }
-
- v.CapLength(n)
- for len(v) > 0 {
- n, _, err := ep.Write(tcpip.SlicePayload(v), tcpip.WriteOptions{})
- if err != nil {
- fmt.Println("Write failed:", err)
- return
- }
-
- v.TrimFront(int(n))
- }
- }
-}
-
-func main() {
- if len(os.Args) != 6 {
- log.Fatal("Usage: ", os.Args[0], " <tun-device> <local-ipv4-address> <local-port> <remote-ipv4-address> <remote-port>")
- }
-
- tunName := os.Args[1]
- addrName := os.Args[2]
- portName := os.Args[3]
- remoteAddrName := os.Args[4]
- remotePortName := os.Args[5]
-
- rand.Seed(time.Now().UnixNano())
-
- addr := tcpip.Address(net.ParseIP(addrName).To4())
- remote := tcpip.FullAddress{
- NIC: 1,
- Addr: tcpip.Address(net.ParseIP(remoteAddrName).To4()),
- }
-
- var localPort uint16
- if v, err := strconv.Atoi(portName); err != nil {
- log.Fatalf("Unable to convert port %v: %v", portName, err)
- } else {
- localPort = uint16(v)
- }
-
- if v, err := strconv.Atoi(remotePortName); err != nil {
- log.Fatalf("Unable to convert port %v: %v", remotePortName, err)
- } else {
- remote.Port = uint16(v)
- }
-
- // Create the stack with ipv4 and tcp protocols, then add a tun-based
- // NIC and ipv4 address.
- s := stack.New([]string{ipv4.ProtocolName}, []string{tcp.ProtocolName}, stack.Options{})
-
- mtu, err := rawfile.GetMTU(tunName)
- if err != nil {
- log.Fatal(err)
- }
-
- fd, err := tun.Open(tunName)
- if err != nil {
- log.Fatal(err)
- }
-
- linkID, err := fdbased.New(&fdbased.Options{FDs: []int{fd}, MTU: mtu})
- if err != nil {
- log.Fatal(err)
- }
- if err := s.CreateNIC(1, sniffer.New(linkID)); err != nil {
- log.Fatal(err)
- }
-
- if err := s.AddAddress(1, ipv4.ProtocolNumber, addr); err != nil {
- log.Fatal(err)
- }
-
- // Add default route.
- s.SetRouteTable([]tcpip.Route{
- {
- Destination: header.IPv4EmptySubnet,
- NIC: 1,
- },
- })
-
- // Create TCP endpoint.
- var wq waiter.Queue
- ep, e := s.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &wq)
- if err != nil {
- log.Fatal(e)
- }
-
- // Bind if a port is specified.
- if localPort != 0 {
- if err := ep.Bind(tcpip.FullAddress{0, "", localPort}); err != nil {
- log.Fatal("Bind failed: ", err)
- }
- }
-
- // Issue connect request and wait for it to complete.
- waitEntry, notifyCh := waiter.NewChannelEntry(nil)
- wq.EventRegister(&waitEntry, waiter.EventOut)
- terr := ep.Connect(remote)
- if terr == tcpip.ErrConnectStarted {
- fmt.Println("Connect is pending...")
- <-notifyCh
- terr = ep.GetSockOpt(tcpip.ErrorOption{})
- }
- wq.EventUnregister(&waitEntry)
-
- if terr != nil {
- log.Fatal("Unable to connect: ", terr)
- }
-
- fmt.Println("Connected")
-
- // Start the writer in its own goroutine.
- writerCompletedCh := make(chan struct{})
- go writer(writerCompletedCh, ep) // S/R-SAFE: sample code.
-
- // Read data and write to standard output until the peer closes the
- // connection from its side.
- wq.EventRegister(&waitEntry, waiter.EventIn)
- for {
- v, _, err := ep.Read(nil)
- if err != nil {
- if err == tcpip.ErrClosedForReceive {
- break
- }
-
- if err == tcpip.ErrWouldBlock {
- <-notifyCh
- continue
- }
-
- log.Fatal("Read() failed:", err)
- }
-
- os.Stdout.Write(v)
- }
- wq.EventUnregister(&waitEntry)
-
- // The reader has completed. Now wait for the writer as well.
- <-writerCompletedCh
-
- ep.Close()
-}
diff --git a/pkg/tcpip/sample/tun_tcp_echo/BUILD b/pkg/tcpip/sample/tun_tcp_echo/BUILD
deleted file mode 100644
index dad8ef399..000000000
--- a/pkg/tcpip/sample/tun_tcp_echo/BUILD
+++ /dev/null
@@ -1,20 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary")
-
-package(licenses = ["notice"])
-
-go_binary(
- name = "tun_tcp_echo",
- srcs = ["main.go"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/link/fdbased",
- "//pkg/tcpip/link/rawfile",
- "//pkg/tcpip/link/tun",
- "//pkg/tcpip/network/arp",
- "//pkg/tcpip/network/ipv4",
- "//pkg/tcpip/network/ipv6",
- "//pkg/tcpip/stack",
- "//pkg/tcpip/transport/tcp",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/tcpip/sample/tun_tcp_echo/main.go b/pkg/tcpip/sample/tun_tcp_echo/main.go
deleted file mode 100644
index 1716be285..000000000
--- a/pkg/tcpip/sample/tun_tcp_echo/main.go
+++ /dev/null
@@ -1,200 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// +build linux
-
-// This sample creates a stack with TCP and IPv4 protocols on top of a TUN
-// device, and listens on a port. Data received by the server in the accepted
-// connections is echoed back to the clients.
-package main
-
-import (
- "flag"
- "log"
- "math/rand"
- "net"
- "os"
- "strconv"
- "strings"
- "time"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
- "gvisor.dev/gvisor/pkg/tcpip/link/rawfile"
- "gvisor.dev/gvisor/pkg/tcpip/link/tun"
- "gvisor.dev/gvisor/pkg/tcpip/network/arp"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
- "gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-var tap = flag.Bool("tap", false, "use tap istead of tun")
-var mac = flag.String("mac", "aa:00:01:01:01:01", "mac address to use in tap device")
-
-func echo(wq *waiter.Queue, ep tcpip.Endpoint) {
- defer ep.Close()
-
- // Create wait queue entry that notifies a channel.
- waitEntry, notifyCh := waiter.NewChannelEntry(nil)
-
- wq.EventRegister(&waitEntry, waiter.EventIn)
- defer wq.EventUnregister(&waitEntry)
-
- for {
- v, _, err := ep.Read(nil)
- if err != nil {
- if err == tcpip.ErrWouldBlock {
- <-notifyCh
- continue
- }
-
- return
- }
-
- ep.Write(tcpip.SlicePayload(v), tcpip.WriteOptions{})
- }
-}
-
-func main() {
- flag.Parse()
- if len(flag.Args()) != 3 {
- log.Fatal("Usage: ", os.Args[0], " <tun-device> <local-address> <local-port>")
- }
-
- tunName := flag.Arg(0)
- addrName := flag.Arg(1)
- portName := flag.Arg(2)
-
- rand.Seed(time.Now().UnixNano())
-
- // Parse the mac address.
- maddr, err := net.ParseMAC(*mac)
- if err != nil {
- log.Fatalf("Bad MAC address: %v", *mac)
- }
-
- // Parse the IP address. Support both ipv4 and ipv6.
- parsedAddr := net.ParseIP(addrName)
- if parsedAddr == nil {
- log.Fatalf("Bad IP address: %v", addrName)
- }
-
- var addr tcpip.Address
- var proto tcpip.NetworkProtocolNumber
- if parsedAddr.To4() != nil {
- addr = tcpip.Address(parsedAddr.To4())
- proto = ipv4.ProtocolNumber
- } else if parsedAddr.To16() != nil {
- addr = tcpip.Address(parsedAddr.To16())
- proto = ipv6.ProtocolNumber
- } else {
- log.Fatalf("Unknown IP type: %v", addrName)
- }
-
- localPort, err := strconv.Atoi(portName)
- if err != nil {
- log.Fatalf("Unable to convert port %v: %v", portName, err)
- }
-
- // Create the stack with ip and tcp protocols, then add a tun-based
- // NIC and address.
- s := stack.New([]string{ipv4.ProtocolName, ipv6.ProtocolName, arp.ProtocolName}, []string{tcp.ProtocolName}, stack.Options{})
-
- mtu, err := rawfile.GetMTU(tunName)
- if err != nil {
- log.Fatal(err)
- }
-
- var fd int
- if *tap {
- fd, err = tun.OpenTAP(tunName)
- } else {
- fd, err = tun.Open(tunName)
- }
- if err != nil {
- log.Fatal(err)
- }
-
- linkID, err := fdbased.New(&fdbased.Options{
- FDs: []int{fd},
- MTU: mtu,
- EthernetHeader: *tap,
- Address: tcpip.LinkAddress(maddr),
- })
- if err != nil {
- log.Fatal(err)
- }
- if err := s.CreateNIC(1, linkID); err != nil {
- log.Fatal(err)
- }
-
- if err := s.AddAddress(1, proto, addr); err != nil {
- log.Fatal(err)
- }
-
- if err := s.AddAddress(1, arp.ProtocolNumber, arp.ProtocolAddress); err != nil {
- log.Fatal(err)
- }
-
- subnet, err := tcpip.NewSubnet(tcpip.Address(strings.Repeat("\x00", len(addr))), tcpip.AddressMask(strings.Repeat("\x00", len(addr))))
- if err != nil {
- log.Fatal(err)
- }
-
- // Add default route.
- s.SetRouteTable([]tcpip.Route{
- {
- Destination: subnet,
- NIC: 1,
- },
- })
-
- // Create TCP endpoint, bind it, then start listening.
- var wq waiter.Queue
- ep, e := s.NewEndpoint(tcp.ProtocolNumber, proto, &wq)
- if err != nil {
- log.Fatal(e)
- }
-
- defer ep.Close()
-
- if err := ep.Bind(tcpip.FullAddress{0, "", uint16(localPort)}); err != nil {
- log.Fatal("Bind failed: ", err)
- }
-
- if err := ep.Listen(10); err != nil {
- log.Fatal("Listen failed: ", err)
- }
-
- // Wait for connections to appear.
- waitEntry, notifyCh := waiter.NewChannelEntry(nil)
- wq.EventRegister(&waitEntry, waiter.EventIn)
- defer wq.EventUnregister(&waitEntry)
-
- for {
- n, wq, err := ep.Accept()
- if err != nil {
- if err == tcpip.ErrWouldBlock {
- <-notifyCh
- continue
- }
-
- log.Fatal("Accept() failed:", err)
- }
-
- go echo(wq, n) // S/R-SAFE: sample code.
- }
-}
diff --git a/pkg/tcpip/seqnum/BUILD b/pkg/tcpip/seqnum/BUILD
deleted file mode 100644
index 76b5f4ffa..000000000
--- a/pkg/tcpip/seqnum/BUILD
+++ /dev/null
@@ -1,12 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "seqnum",
- srcs = ["seqnum.go"],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/seqnum",
- visibility = [
- "//visibility:public",
- ],
-)
diff --git a/pkg/tcpip/seqnum/seqnum_state_autogen.go b/pkg/tcpip/seqnum/seqnum_state_autogen.go
new file mode 100755
index 000000000..bf76f6ac4
--- /dev/null
+++ b/pkg/tcpip/seqnum/seqnum_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package seqnum
+
diff --git a/pkg/tcpip/stack/BUILD b/pkg/tcpip/stack/BUILD
deleted file mode 100644
index 28c49e8ff..000000000
--- a/pkg/tcpip/stack/BUILD
+++ /dev/null
@@ -1,88 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_template_instance(
- name = "linkaddrentry_list",
- out = "linkaddrentry_list.go",
- package = "stack",
- prefix = "linkAddrEntry",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*linkAddrEntry",
- "Linker": "*linkAddrEntry",
- },
-)
-
-go_library(
- name = "stack",
- srcs = [
- "icmp_rate_limit.go",
- "linkaddrcache.go",
- "linkaddrentry_list.go",
- "nic.go",
- "registration.go",
- "route.go",
- "stack.go",
- "stack_global_state.go",
- "transport_demuxer.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/stack",
- visibility = [
- "//visibility:public",
- ],
- deps = [
- "//pkg/ilist",
- "//pkg/sleep",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/hash/jenkins",
- "//pkg/tcpip/header",
- "//pkg/tcpip/iptables",
- "//pkg/tcpip/ports",
- "//pkg/tcpip/seqnum",
- "//pkg/waiter",
- "@org_golang_x_time//rate:go_default_library",
- ],
-)
-
-go_test(
- name = "stack_x_test",
- size = "small",
- srcs = [
- "stack_test.go",
- "transport_test.go",
- ],
- deps = [
- ":stack",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/iptables",
- "//pkg/tcpip/link/channel",
- "//pkg/tcpip/link/loopback",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "stack_test",
- size = "small",
- srcs = ["linkaddrcache_test.go"],
- embed = [":stack"],
- deps = [
- "//pkg/sleep",
- "//pkg/tcpip",
- ],
-)
-
-filegroup(
- name = "autogen",
- srcs = [
- "linkaddrentry_list.go",
- ],
- visibility = ["//:sandbox"],
-)
diff --git a/pkg/tcpip/stack/linkaddrcache_test.go b/pkg/tcpip/stack/linkaddrcache_test.go
deleted file mode 100644
index 9946b8fe8..000000000
--- a/pkg/tcpip/stack/linkaddrcache_test.go
+++ /dev/null
@@ -1,277 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package stack
-
-import (
- "fmt"
- "sync"
- "sync/atomic"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/sleep"
- "gvisor.dev/gvisor/pkg/tcpip"
-)
-
-type testaddr struct {
- addr tcpip.FullAddress
- linkAddr tcpip.LinkAddress
-}
-
-var testAddrs = func() []testaddr {
- var addrs []testaddr
- for i := 0; i < 4*linkAddrCacheSize; i++ {
- addr := fmt.Sprintf("Addr%06d", i)
- addrs = append(addrs, testaddr{
- addr: tcpip.FullAddress{NIC: 1, Addr: tcpip.Address(addr)},
- linkAddr: tcpip.LinkAddress("Link" + addr),
- })
- }
- return addrs
-}()
-
-type testLinkAddressResolver struct {
- cache *linkAddrCache
- delay time.Duration
- onLinkAddressRequest func()
-}
-
-func (r *testLinkAddressResolver) LinkAddressRequest(addr, _ tcpip.Address, _ LinkEndpoint) *tcpip.Error {
- time.AfterFunc(r.delay, func() { r.fakeRequest(addr) })
- if f := r.onLinkAddressRequest; f != nil {
- f()
- }
- return nil
-}
-
-func (r *testLinkAddressResolver) fakeRequest(addr tcpip.Address) {
- for _, ta := range testAddrs {
- if ta.addr.Addr == addr {
- r.cache.add(ta.addr, ta.linkAddr)
- break
- }
- }
-}
-
-func (*testLinkAddressResolver) ResolveStaticAddress(addr tcpip.Address) (tcpip.LinkAddress, bool) {
- if addr == "broadcast" {
- return "mac_broadcast", true
- }
- return "", false
-}
-
-func (*testLinkAddressResolver) LinkAddressProtocol() tcpip.NetworkProtocolNumber {
- return 1
-}
-
-func getBlocking(c *linkAddrCache, addr tcpip.FullAddress, linkRes LinkAddressResolver) (tcpip.LinkAddress, *tcpip.Error) {
- w := sleep.Waker{}
- s := sleep.Sleeper{}
- s.AddWaker(&w, 123)
- defer s.Done()
-
- for {
- if got, _, err := c.get(addr, linkRes, "", nil, &w); err != tcpip.ErrWouldBlock {
- return got, err
- }
- s.Fetch(true)
- }
-}
-
-func TestCacheOverflow(t *testing.T) {
- c := newLinkAddrCache(1<<63-1, 1*time.Second, 3)
- for i := len(testAddrs) - 1; i >= 0; i-- {
- e := testAddrs[i]
- c.add(e.addr, e.linkAddr)
- got, _, err := c.get(e.addr, nil, "", nil, nil)
- if err != nil {
- t.Errorf("insert %d, c.get(%q)=%q, got error: %v", i, string(e.addr.Addr), got, err)
- }
- if got != e.linkAddr {
- t.Errorf("insert %d, c.get(%q)=%q, want %q", i, string(e.addr.Addr), got, e.linkAddr)
- }
- }
- // Expect to find at least half of the most recent entries.
- for i := 0; i < linkAddrCacheSize/2; i++ {
- e := testAddrs[i]
- got, _, err := c.get(e.addr, nil, "", nil, nil)
- if err != nil {
- t.Errorf("check %d, c.get(%q)=%q, got error: %v", i, string(e.addr.Addr), got, err)
- }
- if got != e.linkAddr {
- t.Errorf("check %d, c.get(%q)=%q, want %q", i, string(e.addr.Addr), got, e.linkAddr)
- }
- }
- // The earliest entries should no longer be in the cache.
- for i := len(testAddrs) - 1; i >= len(testAddrs)-linkAddrCacheSize; i-- {
- e := testAddrs[i]
- if _, _, err := c.get(e.addr, nil, "", nil, nil); err != tcpip.ErrNoLinkAddress {
- t.Errorf("check %d, c.get(%q), got error: %v, want: error ErrNoLinkAddress", i, string(e.addr.Addr), err)
- }
- }
-}
-
-func TestCacheConcurrent(t *testing.T) {
- c := newLinkAddrCache(1<<63-1, 1*time.Second, 3)
-
- var wg sync.WaitGroup
- for r := 0; r < 16; r++ {
- wg.Add(1)
- go func() {
- for _, e := range testAddrs {
- c.add(e.addr, e.linkAddr)
- c.get(e.addr, nil, "", nil, nil) // make work for gotsan
- }
- wg.Done()
- }()
- }
- wg.Wait()
-
- // All goroutines add in the same order and add more values than
- // can fit in the cache, so our eviction strategy requires that
- // the last entry be present and the first be missing.
- e := testAddrs[len(testAddrs)-1]
- got, _, err := c.get(e.addr, nil, "", nil, nil)
- if err != nil {
- t.Errorf("c.get(%q)=%q, got error: %v", string(e.addr.Addr), got, err)
- }
- if got != e.linkAddr {
- t.Errorf("c.get(%q)=%q, want %q", string(e.addr.Addr), got, e.linkAddr)
- }
-
- e = testAddrs[0]
- if _, _, err := c.get(e.addr, nil, "", nil, nil); err != tcpip.ErrNoLinkAddress {
- t.Errorf("c.get(%q), got error: %v, want: error ErrNoLinkAddress", string(e.addr.Addr), err)
- }
-}
-
-func TestCacheAgeLimit(t *testing.T) {
- c := newLinkAddrCache(1*time.Millisecond, 1*time.Second, 3)
- e := testAddrs[0]
- c.add(e.addr, e.linkAddr)
- time.Sleep(50 * time.Millisecond)
- if _, _, err := c.get(e.addr, nil, "", nil, nil); err != tcpip.ErrNoLinkAddress {
- t.Errorf("c.get(%q), got error: %v, want: error ErrNoLinkAddress", string(e.addr.Addr), err)
- }
-}
-
-func TestCacheReplace(t *testing.T) {
- c := newLinkAddrCache(1<<63-1, 1*time.Second, 3)
- e := testAddrs[0]
- l2 := e.linkAddr + "2"
- c.add(e.addr, e.linkAddr)
- got, _, err := c.get(e.addr, nil, "", nil, nil)
- if err != nil {
- t.Errorf("c.get(%q)=%q, got error: %v", string(e.addr.Addr), got, err)
- }
- if got != e.linkAddr {
- t.Errorf("c.get(%q)=%q, want %q", string(e.addr.Addr), got, e.linkAddr)
- }
-
- c.add(e.addr, l2)
- got, _, err = c.get(e.addr, nil, "", nil, nil)
- if err != nil {
- t.Errorf("c.get(%q)=%q, got error: %v", string(e.addr.Addr), got, err)
- }
- if got != l2 {
- t.Errorf("c.get(%q)=%q, want %q", string(e.addr.Addr), got, l2)
- }
-}
-
-func TestCacheResolution(t *testing.T) {
- c := newLinkAddrCache(1<<63-1, 250*time.Millisecond, 1)
- linkRes := &testLinkAddressResolver{cache: c}
- for i, ta := range testAddrs {
- got, err := getBlocking(c, ta.addr, linkRes)
- if err != nil {
- t.Errorf("check %d, c.get(%q)=%q, got error: %v", i, string(ta.addr.Addr), got, err)
- }
- if got != ta.linkAddr {
- t.Errorf("check %d, c.get(%q)=%q, want %q", i, string(ta.addr.Addr), got, ta.linkAddr)
- }
- }
-
- // Check that after resolved, address stays in the cache and never returns WouldBlock.
- for i := 0; i < 10; i++ {
- e := testAddrs[len(testAddrs)-1]
- got, _, err := c.get(e.addr, linkRes, "", nil, nil)
- if err != nil {
- t.Errorf("c.get(%q)=%q, got error: %v", string(e.addr.Addr), got, err)
- }
- if got != e.linkAddr {
- t.Errorf("c.get(%q)=%q, want %q", string(e.addr.Addr), got, e.linkAddr)
- }
- }
-}
-
-func TestCacheResolutionFailed(t *testing.T) {
- c := newLinkAddrCache(1<<63-1, 10*time.Millisecond, 5)
- linkRes := &testLinkAddressResolver{cache: c}
-
- var requestCount uint32
- linkRes.onLinkAddressRequest = func() {
- atomic.AddUint32(&requestCount, 1)
- }
-
- // First, sanity check that resolution is working...
- e := testAddrs[0]
- got, err := getBlocking(c, e.addr, linkRes)
- if err != nil {
- t.Errorf("c.get(%q)=%q, got error: %v", string(e.addr.Addr), got, err)
- }
- if got != e.linkAddr {
- t.Errorf("c.get(%q)=%q, want %q", string(e.addr.Addr), got, e.linkAddr)
- }
-
- before := atomic.LoadUint32(&requestCount)
-
- e.addr.Addr += "2"
- if _, err := getBlocking(c, e.addr, linkRes); err != tcpip.ErrNoLinkAddress {
- t.Errorf("c.get(%q), got error: %v, want: error ErrNoLinkAddress", string(e.addr.Addr), err)
- }
-
- if got, want := int(atomic.LoadUint32(&requestCount)-before), c.resolutionAttempts; got != want {
- t.Errorf("got link address request count = %d, want = %d", got, want)
- }
-}
-
-func TestCacheResolutionTimeout(t *testing.T) {
- resolverDelay := 500 * time.Millisecond
- expiration := resolverDelay / 10
- c := newLinkAddrCache(expiration, 1*time.Millisecond, 3)
- linkRes := &testLinkAddressResolver{cache: c, delay: resolverDelay}
-
- e := testAddrs[0]
- if _, err := getBlocking(c, e.addr, linkRes); err != tcpip.ErrNoLinkAddress {
- t.Errorf("c.get(%q), got error: %v, want: error ErrNoLinkAddress", string(e.addr.Addr), err)
- }
-}
-
-// TestStaticResolution checks that static link addresses are resolved immediately and don't
-// send resolution requests.
-func TestStaticResolution(t *testing.T) {
- c := newLinkAddrCache(1<<63-1, time.Millisecond, 1)
- linkRes := &testLinkAddressResolver{cache: c, delay: time.Minute}
-
- addr := tcpip.Address("broadcast")
- want := tcpip.LinkAddress("mac_broadcast")
- got, _, err := c.get(tcpip.FullAddress{Addr: addr}, linkRes, "", nil, nil)
- if err != nil {
- t.Errorf("c.get(%q)=%q, got error: %v", string(addr), string(got), err)
- }
- if got != want {
- t.Errorf("c.get(%q)=%q, want %q", string(addr), string(got), string(want))
- }
-}
diff --git a/pkg/tcpip/stack/linkaddrentry_list.go b/pkg/tcpip/stack/linkaddrentry_list.go
new file mode 100755
index 000000000..61a45ddcb
--- /dev/null
+++ b/pkg/tcpip/stack/linkaddrentry_list.go
@@ -0,0 +1,173 @@
+package stack
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type linkAddrEntryElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (linkAddrEntryElementMapper) linkerFor(elem *linkAddrEntry) *linkAddrEntry { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type linkAddrEntryList struct {
+ head *linkAddrEntry
+ tail *linkAddrEntry
+}
+
+// Reset resets list l to the empty state.
+func (l *linkAddrEntryList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *linkAddrEntryList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *linkAddrEntryList) Front() *linkAddrEntry {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *linkAddrEntryList) Back() *linkAddrEntry {
+ return l.tail
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *linkAddrEntryList) PushFront(e *linkAddrEntry) {
+ linkAddrEntryElementMapper{}.linkerFor(e).SetNext(l.head)
+ linkAddrEntryElementMapper{}.linkerFor(e).SetPrev(nil)
+
+ if l.head != nil {
+ linkAddrEntryElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *linkAddrEntryList) PushBack(e *linkAddrEntry) {
+ linkAddrEntryElementMapper{}.linkerFor(e).SetNext(nil)
+ linkAddrEntryElementMapper{}.linkerFor(e).SetPrev(l.tail)
+
+ if l.tail != nil {
+ linkAddrEntryElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *linkAddrEntryList) PushBackList(m *linkAddrEntryList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ linkAddrEntryElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ linkAddrEntryElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *linkAddrEntryList) InsertAfter(b, e *linkAddrEntry) {
+ a := linkAddrEntryElementMapper{}.linkerFor(b).Next()
+ linkAddrEntryElementMapper{}.linkerFor(e).SetNext(a)
+ linkAddrEntryElementMapper{}.linkerFor(e).SetPrev(b)
+ linkAddrEntryElementMapper{}.linkerFor(b).SetNext(e)
+
+ if a != nil {
+ linkAddrEntryElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *linkAddrEntryList) InsertBefore(a, e *linkAddrEntry) {
+ b := linkAddrEntryElementMapper{}.linkerFor(a).Prev()
+ linkAddrEntryElementMapper{}.linkerFor(e).SetNext(a)
+ linkAddrEntryElementMapper{}.linkerFor(e).SetPrev(b)
+ linkAddrEntryElementMapper{}.linkerFor(a).SetPrev(e)
+
+ if b != nil {
+ linkAddrEntryElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *linkAddrEntryList) Remove(e *linkAddrEntry) {
+ prev := linkAddrEntryElementMapper{}.linkerFor(e).Prev()
+ next := linkAddrEntryElementMapper{}.linkerFor(e).Next()
+
+ if prev != nil {
+ linkAddrEntryElementMapper{}.linkerFor(prev).SetNext(next)
+ } else {
+ l.head = next
+ }
+
+ if next != nil {
+ linkAddrEntryElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else {
+ l.tail = prev
+ }
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type linkAddrEntryEntry struct {
+ next *linkAddrEntry
+ prev *linkAddrEntry
+}
+
+// Next returns the entry that follows e in the list.
+func (e *linkAddrEntryEntry) Next() *linkAddrEntry {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *linkAddrEntryEntry) Prev() *linkAddrEntry {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *linkAddrEntryEntry) SetNext(elem *linkAddrEntry) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *linkAddrEntryEntry) SetPrev(elem *linkAddrEntry) {
+ e.prev = elem
+}
diff --git a/pkg/tcpip/stack/stack_state_autogen.go b/pkg/tcpip/stack/stack_state_autogen.go
new file mode 100755
index 000000000..5cc49d648
--- /dev/null
+++ b/pkg/tcpip/stack/stack_state_autogen.go
@@ -0,0 +1,87 @@
+// automatically generated by stateify.
+
+package stack
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *linkAddrEntryList) beforeSave() {}
+func (x *linkAddrEntryList) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *linkAddrEntryList) afterLoad() {}
+func (x *linkAddrEntryList) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *linkAddrEntryEntry) beforeSave() {}
+func (x *linkAddrEntryEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *linkAddrEntryEntry) afterLoad() {}
+func (x *linkAddrEntryEntry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func (x *TransportEndpointID) beforeSave() {}
+func (x *TransportEndpointID) save(m state.Map) {
+ x.beforeSave()
+ m.Save("LocalPort", &x.LocalPort)
+ m.Save("LocalAddress", &x.LocalAddress)
+ m.Save("RemotePort", &x.RemotePort)
+ m.Save("RemoteAddress", &x.RemoteAddress)
+}
+
+func (x *TransportEndpointID) afterLoad() {}
+func (x *TransportEndpointID) load(m state.Map) {
+ m.Load("LocalPort", &x.LocalPort)
+ m.Load("LocalAddress", &x.LocalAddress)
+ m.Load("RemotePort", &x.RemotePort)
+ m.Load("RemoteAddress", &x.RemoteAddress)
+}
+
+func (x *GSOType) save(m state.Map) {
+ m.SaveValue("", (int)(*x))
+}
+
+func (x *GSOType) load(m state.Map) {
+ m.LoadValue("", new(int), func(y interface{}) { *x = (GSOType)(y.(int)) })
+}
+
+func (x *GSO) beforeSave() {}
+func (x *GSO) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Type", &x.Type)
+ m.Save("NeedsCsum", &x.NeedsCsum)
+ m.Save("CsumOffset", &x.CsumOffset)
+ m.Save("MSS", &x.MSS)
+ m.Save("L3HdrLen", &x.L3HdrLen)
+ m.Save("MaxSize", &x.MaxSize)
+}
+
+func (x *GSO) afterLoad() {}
+func (x *GSO) load(m state.Map) {
+ m.Load("Type", &x.Type)
+ m.Load("NeedsCsum", &x.NeedsCsum)
+ m.Load("CsumOffset", &x.CsumOffset)
+ m.Load("MSS", &x.MSS)
+ m.Load("L3HdrLen", &x.L3HdrLen)
+ m.Load("MaxSize", &x.MaxSize)
+}
+
+func init() {
+ state.Register("stack.linkAddrEntryList", (*linkAddrEntryList)(nil), state.Fns{Save: (*linkAddrEntryList).save, Load: (*linkAddrEntryList).load})
+ state.Register("stack.linkAddrEntryEntry", (*linkAddrEntryEntry)(nil), state.Fns{Save: (*linkAddrEntryEntry).save, Load: (*linkAddrEntryEntry).load})
+ state.Register("stack.TransportEndpointID", (*TransportEndpointID)(nil), state.Fns{Save: (*TransportEndpointID).save, Load: (*TransportEndpointID).load})
+ state.Register("stack.GSOType", (*GSOType)(nil), state.Fns{Save: (*GSOType).save, Load: (*GSOType).load})
+ state.Register("stack.GSO", (*GSO)(nil), state.Fns{Save: (*GSO).save, Load: (*GSO).load})
+}
diff --git a/pkg/tcpip/stack/stack_test.go b/pkg/tcpip/stack/stack_test.go
deleted file mode 100644
index 0c26c9911..000000000
--- a/pkg/tcpip/stack/stack_test.go
+++ /dev/null
@@ -1,1722 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package stack_test contains tests for the stack. It is in its own package so
-// that the tests can also validate that all definitions needed to implement
-// transport and network protocols are properly exported by the stack package.
-package stack_test
-
-import (
- "bytes"
- "fmt"
- "math"
- "sort"
- "strings"
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/link/channel"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-const (
- fakeNetNumber tcpip.NetworkProtocolNumber = math.MaxUint32
- fakeNetHeaderLen = 12
- fakeDefaultPrefixLen = 8
-
- // fakeControlProtocol is used for control packets that represent
- // destination port unreachable.
- fakeControlProtocol tcpip.TransportProtocolNumber = 2
-
- // defaultMTU is the MTU, in bytes, used throughout the tests, except
- // where another value is explicitly used. It is chosen to match the MTU
- // of loopback interfaces on linux systems.
- defaultMTU = 65536
-)
-
-// fakeNetworkEndpoint is a network-layer protocol endpoint. It counts sent and
-// received packets; the counts of all endpoints are aggregated in the protocol
-// descriptor.
-//
-// Headers of this protocol are fakeNetHeaderLen bytes, but we currently only
-// use the first three: destination address, source address, and transport
-// protocol. They're all one byte fields to simplify parsing.
-type fakeNetworkEndpoint struct {
- nicid tcpip.NICID
- id stack.NetworkEndpointID
- prefixLen int
- proto *fakeNetworkProtocol
- dispatcher stack.TransportDispatcher
- ep stack.LinkEndpoint
-}
-
-func (f *fakeNetworkEndpoint) MTU() uint32 {
- return f.ep.MTU() - uint32(f.MaxHeaderLength())
-}
-
-func (f *fakeNetworkEndpoint) NICID() tcpip.NICID {
- return f.nicid
-}
-
-func (f *fakeNetworkEndpoint) PrefixLen() int {
- return f.prefixLen
-}
-
-func (*fakeNetworkEndpoint) DefaultTTL() uint8 {
- return 123
-}
-
-func (f *fakeNetworkEndpoint) ID() *stack.NetworkEndpointID {
- return &f.id
-}
-
-func (f *fakeNetworkEndpoint) HandlePacket(r *stack.Route, vv buffer.VectorisedView) {
- // Increment the received packet count in the protocol descriptor.
- f.proto.packetCount[int(f.id.LocalAddress[0])%len(f.proto.packetCount)]++
-
- // Consume the network header.
- b := vv.First()
- vv.TrimFront(fakeNetHeaderLen)
-
- // Handle control packets.
- if b[2] == uint8(fakeControlProtocol) {
- nb := vv.First()
- if len(nb) < fakeNetHeaderLen {
- return
- }
-
- vv.TrimFront(fakeNetHeaderLen)
- f.dispatcher.DeliverTransportControlPacket(tcpip.Address(nb[1:2]), tcpip.Address(nb[0:1]), fakeNetNumber, tcpip.TransportProtocolNumber(nb[2]), stack.ControlPortUnreachable, 0, vv)
- return
- }
-
- // Dispatch the packet to the transport protocol.
- f.dispatcher.DeliverTransportPacket(r, tcpip.TransportProtocolNumber(b[2]), buffer.View([]byte{}), vv)
-}
-
-func (f *fakeNetworkEndpoint) MaxHeaderLength() uint16 {
- return f.ep.MaxHeaderLength() + fakeNetHeaderLen
-}
-
-func (f *fakeNetworkEndpoint) PseudoHeaderChecksum(protocol tcpip.TransportProtocolNumber, dstAddr tcpip.Address) uint16 {
- return 0
-}
-
-func (f *fakeNetworkEndpoint) Capabilities() stack.LinkEndpointCapabilities {
- return f.ep.Capabilities()
-}
-
-func (f *fakeNetworkEndpoint) WritePacket(r *stack.Route, gso *stack.GSO, hdr buffer.Prependable, payload buffer.VectorisedView, protocol tcpip.TransportProtocolNumber, _ uint8, loop stack.PacketLooping) *tcpip.Error {
- // Increment the sent packet count in the protocol descriptor.
- f.proto.sendPacketCount[int(r.RemoteAddress[0])%len(f.proto.sendPacketCount)]++
-
- // Add the protocol's header to the packet and send it to the link
- // endpoint.
- b := hdr.Prepend(fakeNetHeaderLen)
- b[0] = r.RemoteAddress[0]
- b[1] = f.id.LocalAddress[0]
- b[2] = byte(protocol)
-
- if loop&stack.PacketLoop != 0 {
- views := make([]buffer.View, 1, 1+len(payload.Views()))
- views[0] = hdr.View()
- views = append(views, payload.Views()...)
- vv := buffer.NewVectorisedView(len(views[0])+payload.Size(), views)
- f.HandlePacket(r, vv)
- }
- if loop&stack.PacketOut == 0 {
- return nil
- }
-
- return f.ep.WritePacket(r, gso, hdr, payload, fakeNetNumber)
-}
-
-func (*fakeNetworkEndpoint) WriteHeaderIncludedPacket(r *stack.Route, payload buffer.VectorisedView, loop stack.PacketLooping) *tcpip.Error {
- return tcpip.ErrNotSupported
-}
-
-func (*fakeNetworkEndpoint) Close() {}
-
-type fakeNetGoodOption bool
-
-type fakeNetBadOption bool
-
-type fakeNetInvalidValueOption int
-
-type fakeNetOptions struct {
- good bool
-}
-
-// fakeNetworkProtocol is a network-layer protocol descriptor. It aggregates the
-// number of packets sent and received via endpoints of this protocol. The index
-// where packets are added is given by the packet's destination address MOD 10.
-type fakeNetworkProtocol struct {
- packetCount [10]int
- sendPacketCount [10]int
- opts fakeNetOptions
-}
-
-func (f *fakeNetworkProtocol) Number() tcpip.NetworkProtocolNumber {
- return fakeNetNumber
-}
-
-func (f *fakeNetworkProtocol) MinimumPacketSize() int {
- return fakeNetHeaderLen
-}
-
-func (f *fakeNetworkProtocol) DefaultPrefixLen() int {
- return fakeDefaultPrefixLen
-}
-
-func (f *fakeNetworkProtocol) PacketCount(intfAddr byte) int {
- return f.packetCount[int(intfAddr)%len(f.packetCount)]
-}
-
-func (*fakeNetworkProtocol) ParseAddresses(v buffer.View) (src, dst tcpip.Address) {
- return tcpip.Address(v[1:2]), tcpip.Address(v[0:1])
-}
-
-func (f *fakeNetworkProtocol) NewEndpoint(nicid tcpip.NICID, addrWithPrefix tcpip.AddressWithPrefix, linkAddrCache stack.LinkAddressCache, dispatcher stack.TransportDispatcher, ep stack.LinkEndpoint) (stack.NetworkEndpoint, *tcpip.Error) {
- return &fakeNetworkEndpoint{
- nicid: nicid,
- id: stack.NetworkEndpointID{LocalAddress: addrWithPrefix.Address},
- prefixLen: addrWithPrefix.PrefixLen,
- proto: f,
- dispatcher: dispatcher,
- ep: ep,
- }, nil
-}
-
-func (f *fakeNetworkProtocol) SetOption(option interface{}) *tcpip.Error {
- switch v := option.(type) {
- case fakeNetGoodOption:
- f.opts.good = bool(v)
- return nil
- case fakeNetInvalidValueOption:
- return tcpip.ErrInvalidOptionValue
- default:
- return tcpip.ErrUnknownProtocolOption
- }
-}
-
-func (f *fakeNetworkProtocol) Option(option interface{}) *tcpip.Error {
- switch v := option.(type) {
- case *fakeNetGoodOption:
- *v = fakeNetGoodOption(f.opts.good)
- return nil
- default:
- return tcpip.ErrUnknownProtocolOption
- }
-}
-
-func TestNetworkReceive(t *testing.T) {
- // Create a stack with the fake network protocol, one nic, and two
- // addresses attached to it: 1 & 2.
- ep := channel.New(10, defaultMTU, "")
- s := stack.New([]string{"fakeNet"}, nil, stack.Options{})
- if err := s.CreateNIC(1, ep); err != nil {
- t.Fatal("CreateNIC failed:", err)
- }
-
- if err := s.AddAddress(1, fakeNetNumber, "\x01"); err != nil {
- t.Fatal("AddAddress failed:", err)
- }
-
- if err := s.AddAddress(1, fakeNetNumber, "\x02"); err != nil {
- t.Fatal("AddAddress failed:", err)
- }
-
- fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)
-
- buf := buffer.NewView(30)
-
- // Make sure packet with wrong address is not delivered.
- buf[0] = 3
- ep.Inject(fakeNetNumber, buf.ToVectorisedView())
- if fakeNet.packetCount[1] != 0 {
- t.Errorf("packetCount[1] = %d, want %d", fakeNet.packetCount[1], 0)
- }
- if fakeNet.packetCount[2] != 0 {
- t.Errorf("packetCount[2] = %d, want %d", fakeNet.packetCount[2], 0)
- }
-
- // Make sure packet is delivered to first endpoint.
- buf[0] = 1
- ep.Inject(fakeNetNumber, buf.ToVectorisedView())
- if fakeNet.packetCount[1] != 1 {
- t.Errorf("packetCount[1] = %d, want %d", fakeNet.packetCount[1], 1)
- }
- if fakeNet.packetCount[2] != 0 {
- t.Errorf("packetCount[2] = %d, want %d", fakeNet.packetCount[2], 0)
- }
-
- // Make sure packet is delivered to second endpoint.
- buf[0] = 2
- ep.Inject(fakeNetNumber, buf.ToVectorisedView())
- if fakeNet.packetCount[1] != 1 {
- t.Errorf("packetCount[1] = %d, want %d", fakeNet.packetCount[1], 1)
- }
- if fakeNet.packetCount[2] != 1 {
- t.Errorf("packetCount[2] = %d, want %d", fakeNet.packetCount[2], 1)
- }
-
- // Make sure packet is not delivered if protocol number is wrong.
- ep.Inject(fakeNetNumber-1, buf.ToVectorisedView())
- if fakeNet.packetCount[1] != 1 {
- t.Errorf("packetCount[1] = %d, want %d", fakeNet.packetCount[1], 1)
- }
- if fakeNet.packetCount[2] != 1 {
- t.Errorf("packetCount[2] = %d, want %d", fakeNet.packetCount[2], 1)
- }
-
- // Make sure packet that is too small is dropped.
- buf.CapLength(2)
- ep.Inject(fakeNetNumber, buf.ToVectorisedView())
- if fakeNet.packetCount[1] != 1 {
- t.Errorf("packetCount[1] = %d, want %d", fakeNet.packetCount[1], 1)
- }
- if fakeNet.packetCount[2] != 1 {
- t.Errorf("packetCount[2] = %d, want %d", fakeNet.packetCount[2], 1)
- }
-}
-
-func sendTo(s *stack.Stack, addr tcpip.Address, payload buffer.View) *tcpip.Error {
- r, err := s.FindRoute(0, "", addr, fakeNetNumber, false /* multicastLoop */)
- if err != nil {
- return err
- }
- defer r.Release()
- return send(r, payload)
-}
-
-func send(r stack.Route, payload buffer.View) *tcpip.Error {
- hdr := buffer.NewPrependable(int(r.MaxHeaderLength()))
- return r.WritePacket(nil /* gso */, hdr, payload.ToVectorisedView(), fakeTransNumber, 123)
-}
-
-func testSendTo(t *testing.T, s *stack.Stack, addr tcpip.Address, ep *channel.Endpoint, payload buffer.View) {
- t.Helper()
- ep.Drain()
- if err := sendTo(s, addr, payload); err != nil {
- t.Error("sendTo failed:", err)
- }
- if got, want := ep.Drain(), 1; got != want {
- t.Errorf("sendTo packet count: got = %d, want %d", got, want)
- }
-}
-
-func testSend(t *testing.T, r stack.Route, ep *channel.Endpoint, payload buffer.View) {
- t.Helper()
- ep.Drain()
- if err := send(r, payload); err != nil {
- t.Error("send failed:", err)
- }
- if got, want := ep.Drain(), 1; got != want {
- t.Errorf("send packet count: got = %d, want %d", got, want)
- }
-}
-
-func testFailingSend(t *testing.T, r stack.Route, ep *channel.Endpoint, payload buffer.View, wantErr *tcpip.Error) {
- t.Helper()
- if gotErr := send(r, payload); gotErr != wantErr {
- t.Errorf("send failed: got = %s, want = %s ", gotErr, wantErr)
- }
-}
-
-func testFailingSendTo(t *testing.T, s *stack.Stack, addr tcpip.Address, ep *channel.Endpoint, payload buffer.View, wantErr *tcpip.Error) {
- t.Helper()
- if gotErr := sendTo(s, addr, payload); gotErr != wantErr {
- t.Errorf("sendto failed: got = %s, want = %s ", gotErr, wantErr)
- }
-}
-
-func testRecv(t *testing.T, fakeNet *fakeNetworkProtocol, localAddrByte byte, ep *channel.Endpoint, buf buffer.View) {
- t.Helper()
- // testRecvInternal injects one packet, and we expect to receive it.
- want := fakeNet.PacketCount(localAddrByte) + 1
- testRecvInternal(t, fakeNet, localAddrByte, ep, buf, want)
-}
-
-func testFailingRecv(t *testing.T, fakeNet *fakeNetworkProtocol, localAddrByte byte, ep *channel.Endpoint, buf buffer.View) {
- t.Helper()
- // testRecvInternal injects one packet, and we do NOT expect to receive it.
- want := fakeNet.PacketCount(localAddrByte)
- testRecvInternal(t, fakeNet, localAddrByte, ep, buf, want)
-}
-
-func testRecvInternal(t *testing.T, fakeNet *fakeNetworkProtocol, localAddrByte byte, ep *channel.Endpoint, buf buffer.View, want int) {
- t.Helper()
- ep.Inject(fakeNetNumber, buf.ToVectorisedView())
- if got := fakeNet.PacketCount(localAddrByte); got != want {
- t.Errorf("receive packet count: got = %d, want %d", got, want)
- }
-}
-
-func TestNetworkSend(t *testing.T) {
- // Create a stack with the fake network protocol, one nic, and one
- // address: 1. The route table sends all packets through the only
- // existing nic.
- ep := channel.New(10, defaultMTU, "")
- s := stack.New([]string{"fakeNet"}, nil, stack.Options{})
- if err := s.CreateNIC(1, ep); err != nil {
- t.Fatal("NewNIC failed:", err)
- }
-
- {
- subnet, err := tcpip.NewSubnet("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
- }
-
- if err := s.AddAddress(1, fakeNetNumber, "\x01"); err != nil {
- t.Fatal("AddAddress failed:", err)
- }
-
- // Make sure that the link-layer endpoint received the outbound packet.
- testSendTo(t, s, "\x03", ep, nil)
-}
-
-func TestNetworkSendMultiRoute(t *testing.T) {
- // Create a stack with the fake network protocol, two nics, and two
- // addresses per nic, the first nic has odd address, the second one has
- // even addresses.
- s := stack.New([]string{"fakeNet"}, nil, stack.Options{})
-
- ep1 := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(1, ep1); err != nil {
- t.Fatal("CreateNIC failed:", err)
- }
-
- if err := s.AddAddress(1, fakeNetNumber, "\x01"); err != nil {
- t.Fatal("AddAddress failed:", err)
- }
-
- if err := s.AddAddress(1, fakeNetNumber, "\x03"); err != nil {
- t.Fatal("AddAddress failed:", err)
- }
-
- ep2 := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(2, ep2); err != nil {
- t.Fatal("CreateNIC failed:", err)
- }
-
- if err := s.AddAddress(2, fakeNetNumber, "\x02"); err != nil {
- t.Fatal("AddAddress failed:", err)
- }
-
- if err := s.AddAddress(2, fakeNetNumber, "\x04"); err != nil {
- t.Fatal("AddAddress failed:", err)
- }
-
- // Set a route table that sends all packets with odd destination
- // addresses through the first NIC, and all even destination address
- // through the second one.
- {
- subnet0, err := tcpip.NewSubnet("\x00", "\x01")
- if err != nil {
- t.Fatal(err)
- }
- subnet1, err := tcpip.NewSubnet("\x01", "\x01")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{
- {Destination: subnet1, Gateway: "\x00", NIC: 1},
- {Destination: subnet0, Gateway: "\x00", NIC: 2},
- })
- }
-
- // Send a packet to an odd destination.
- testSendTo(t, s, "\x05", ep1, nil)
-
- // Send a packet to an even destination.
- testSendTo(t, s, "\x06", ep2, nil)
-}
-
-func testRoute(t *testing.T, s *stack.Stack, nic tcpip.NICID, srcAddr, dstAddr, expectedSrcAddr tcpip.Address) {
- r, err := s.FindRoute(nic, srcAddr, dstAddr, fakeNetNumber, false /* multicastLoop */)
- if err != nil {
- t.Fatal("FindRoute failed:", err)
- }
-
- defer r.Release()
-
- if r.LocalAddress != expectedSrcAddr {
- t.Fatalf("Bad source address: expected %v, got %v", expectedSrcAddr, r.LocalAddress)
- }
-
- if r.RemoteAddress != dstAddr {
- t.Fatalf("Bad destination address: expected %v, got %v", dstAddr, r.RemoteAddress)
- }
-}
-
-func testNoRoute(t *testing.T, s *stack.Stack, nic tcpip.NICID, srcAddr, dstAddr tcpip.Address) {
- _, err := s.FindRoute(nic, srcAddr, dstAddr, fakeNetNumber, false /* multicastLoop */)
- if err != tcpip.ErrNoRoute {
- t.Fatalf("FindRoute returned unexpected error, got = %v, want = %s", err, tcpip.ErrNoRoute)
- }
-}
-
-func TestRoutes(t *testing.T) {
- // Create a stack with the fake network protocol, two nics, and two
- // addresses per nic, the first nic has odd address, the second one has
- // even addresses.
- s := stack.New([]string{"fakeNet"}, nil, stack.Options{})
-
- ep1 := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(1, ep1); err != nil {
- t.Fatal("CreateNIC failed:", err)
- }
-
- if err := s.AddAddress(1, fakeNetNumber, "\x01"); err != nil {
- t.Fatal("AddAddress failed:", err)
- }
-
- if err := s.AddAddress(1, fakeNetNumber, "\x03"); err != nil {
- t.Fatal("AddAddress failed:", err)
- }
-
- ep2 := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(2, ep2); err != nil {
- t.Fatal("CreateNIC failed:", err)
- }
-
- if err := s.AddAddress(2, fakeNetNumber, "\x02"); err != nil {
- t.Fatal("AddAddress failed:", err)
- }
-
- if err := s.AddAddress(2, fakeNetNumber, "\x04"); err != nil {
- t.Fatal("AddAddress failed:", err)
- }
-
- // Set a route table that sends all packets with odd destination
- // addresses through the first NIC, and all even destination address
- // through the second one.
- {
- subnet0, err := tcpip.NewSubnet("\x00", "\x01")
- if err != nil {
- t.Fatal(err)
- }
- subnet1, err := tcpip.NewSubnet("\x01", "\x01")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{
- {Destination: subnet1, Gateway: "\x00", NIC: 1},
- {Destination: subnet0, Gateway: "\x00", NIC: 2},
- })
- }
-
- // Test routes to odd address.
- testRoute(t, s, 0, "", "\x05", "\x01")
- testRoute(t, s, 0, "\x01", "\x05", "\x01")
- testRoute(t, s, 1, "\x01", "\x05", "\x01")
- testRoute(t, s, 0, "\x03", "\x05", "\x03")
- testRoute(t, s, 1, "\x03", "\x05", "\x03")
-
- // Test routes to even address.
- testRoute(t, s, 0, "", "\x06", "\x02")
- testRoute(t, s, 0, "\x02", "\x06", "\x02")
- testRoute(t, s, 2, "\x02", "\x06", "\x02")
- testRoute(t, s, 0, "\x04", "\x06", "\x04")
- testRoute(t, s, 2, "\x04", "\x06", "\x04")
-
- // Try to send to odd numbered address from even numbered ones, then
- // vice-versa.
- testNoRoute(t, s, 0, "\x02", "\x05")
- testNoRoute(t, s, 2, "\x02", "\x05")
- testNoRoute(t, s, 0, "\x04", "\x05")
- testNoRoute(t, s, 2, "\x04", "\x05")
-
- testNoRoute(t, s, 0, "\x01", "\x06")
- testNoRoute(t, s, 1, "\x01", "\x06")
- testNoRoute(t, s, 0, "\x03", "\x06")
- testNoRoute(t, s, 1, "\x03", "\x06")
-}
-
-func TestAddressRemoval(t *testing.T) {
- const localAddrByte byte = 0x01
- localAddr := tcpip.Address([]byte{localAddrByte})
- remoteAddr := tcpip.Address("\x02")
-
- s := stack.New([]string{"fakeNet"}, nil, stack.Options{})
-
- ep := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(1, ep); err != nil {
- t.Fatal("CreateNIC failed:", err)
- }
-
- if err := s.AddAddress(1, fakeNetNumber, localAddr); err != nil {
- t.Fatal("AddAddress failed:", err)
- }
- {
- subnet, err := tcpip.NewSubnet("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
- }
-
- fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)
-
- buf := buffer.NewView(30)
-
- // Send and receive packets, and verify they are received.
- buf[0] = localAddrByte
- testRecv(t, fakeNet, localAddrByte, ep, buf)
- testSendTo(t, s, remoteAddr, ep, nil)
-
- // Remove the address, then check that send/receive doesn't work anymore.
- if err := s.RemoveAddress(1, localAddr); err != nil {
- t.Fatal("RemoveAddress failed:", err)
- }
- testFailingRecv(t, fakeNet, localAddrByte, ep, buf)
- testFailingSendTo(t, s, remoteAddr, ep, nil, tcpip.ErrNoRoute)
-
- // Check that removing the same address fails.
- if err := s.RemoveAddress(1, localAddr); err != tcpip.ErrBadLocalAddress {
- t.Fatalf("RemoveAddress returned unexpected error, got = %v, want = %s", err, tcpip.ErrBadLocalAddress)
- }
-}
-
-func TestAddressRemovalWithRouteHeld(t *testing.T) {
- const localAddrByte byte = 0x01
- localAddr := tcpip.Address([]byte{localAddrByte})
- remoteAddr := tcpip.Address("\x02")
-
- s := stack.New([]string{"fakeNet"}, nil, stack.Options{})
-
- ep := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(1, ep); err != nil {
- t.Fatalf("CreateNIC failed: %v", err)
- }
- fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)
- buf := buffer.NewView(30)
-
- if err := s.AddAddress(1, fakeNetNumber, localAddr); err != nil {
- t.Fatal("AddAddress failed:", err)
- }
- {
- subnet, err := tcpip.NewSubnet("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
- }
-
- r, err := s.FindRoute(0, "", remoteAddr, fakeNetNumber, false /* multicastLoop */)
- if err != nil {
- t.Fatal("FindRoute failed:", err)
- }
-
- // Send and receive packets, and verify they are received.
- buf[0] = localAddrByte
- testRecv(t, fakeNet, localAddrByte, ep, buf)
- testSend(t, r, ep, nil)
- testSendTo(t, s, remoteAddr, ep, nil)
-
- // Remove the address, then check that send/receive doesn't work anymore.
- if err := s.RemoveAddress(1, localAddr); err != nil {
- t.Fatal("RemoveAddress failed:", err)
- }
- testFailingRecv(t, fakeNet, localAddrByte, ep, buf)
- testFailingSend(t, r, ep, nil, tcpip.ErrInvalidEndpointState)
- testFailingSendTo(t, s, remoteAddr, ep, nil, tcpip.ErrNoRoute)
-
- // Check that removing the same address fails.
- if err := s.RemoveAddress(1, localAddr); err != tcpip.ErrBadLocalAddress {
- t.Fatalf("RemoveAddress returned unexpected error, got = %v, want = %s", err, tcpip.ErrBadLocalAddress)
- }
-}
-
-func verifyAddress(t *testing.T, s *stack.Stack, nicid tcpip.NICID, addr tcpip.Address) {
- t.Helper()
- info, ok := s.NICInfo()[nicid]
- if !ok {
- t.Fatalf("NICInfo() failed to find nicid=%d", nicid)
- }
- if len(addr) == 0 {
- // No address given, verify that there is no address assigned to the NIC.
- for _, a := range info.ProtocolAddresses {
- if a.Protocol == fakeNetNumber && a.AddressWithPrefix != (tcpip.AddressWithPrefix{}) {
- t.Errorf("verify no-address: got = %s, want = %s", a.AddressWithPrefix, (tcpip.AddressWithPrefix{}))
- }
- }
- return
- }
- // Address given, verify the address is assigned to the NIC and no other
- // address is.
- found := false
- for _, a := range info.ProtocolAddresses {
- if a.Protocol == fakeNetNumber {
- if a.AddressWithPrefix.Address == addr {
- found = true
- } else {
- t.Errorf("verify address: got = %s, want = %s", a.AddressWithPrefix.Address, addr)
- }
- }
- }
- if !found {
- t.Errorf("verify address: couldn't find %s on the NIC", addr)
- }
-}
-
-func TestEndpointExpiration(t *testing.T) {
- const (
- localAddrByte byte = 0x01
- remoteAddr tcpip.Address = "\x03"
- noAddr tcpip.Address = ""
- nicid tcpip.NICID = 1
- )
- localAddr := tcpip.Address([]byte{localAddrByte})
-
- for _, promiscuous := range []bool{true, false} {
- for _, spoofing := range []bool{true, false} {
- t.Run(fmt.Sprintf("promiscuous=%t spoofing=%t", promiscuous, spoofing), func(t *testing.T) {
- s := stack.New([]string{"fakeNet"}, nil, stack.Options{})
-
- ep := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(nicid, ep); err != nil {
- t.Fatal("CreateNIC failed:", err)
- }
-
- {
- subnet, err := tcpip.NewSubnet("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
- }
-
- fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)
- buf := buffer.NewView(30)
- buf[0] = localAddrByte
-
- if promiscuous {
- if err := s.SetPromiscuousMode(nicid, true); err != nil {
- t.Fatal("SetPromiscuousMode failed:", err)
- }
- }
-
- if spoofing {
- if err := s.SetSpoofing(nicid, true); err != nil {
- t.Fatal("SetSpoofing failed:", err)
- }
- }
-
- // 1. No Address yet, send should only work for spoofing, receive for
- // promiscuous mode.
- //-----------------------
- verifyAddress(t, s, nicid, noAddr)
- if promiscuous {
- testRecv(t, fakeNet, localAddrByte, ep, buf)
- } else {
- testFailingRecv(t, fakeNet, localAddrByte, ep, buf)
- }
- if spoofing {
- // FIXME(b/139841518):Spoofing doesn't work if there is no primary address.
- // testSendTo(t, s, remoteAddr, ep, nil)
- } else {
- testFailingSendTo(t, s, remoteAddr, ep, nil, tcpip.ErrNoRoute)
- }
-
- // 2. Add Address, everything should work.
- //-----------------------
- if err := s.AddAddress(nicid, fakeNetNumber, localAddr); err != nil {
- t.Fatal("AddAddress failed:", err)
- }
- verifyAddress(t, s, nicid, localAddr)
- testRecv(t, fakeNet, localAddrByte, ep, buf)
- testSendTo(t, s, remoteAddr, ep, nil)
-
- // 3. Remove the address, send should only work for spoofing, receive
- // for promiscuous mode.
- //-----------------------
- if err := s.RemoveAddress(nicid, localAddr); err != nil {
- t.Fatal("RemoveAddress failed:", err)
- }
- verifyAddress(t, s, nicid, noAddr)
- if promiscuous {
- testRecv(t, fakeNet, localAddrByte, ep, buf)
- } else {
- testFailingRecv(t, fakeNet, localAddrByte, ep, buf)
- }
- if spoofing {
- // FIXME(b/139841518):Spoofing doesn't work if there is no primary address.
- // testSendTo(t, s, remoteAddr, ep, nil)
- } else {
- testFailingSendTo(t, s, remoteAddr, ep, nil, tcpip.ErrNoRoute)
- }
-
- // 4. Add Address back, everything should work again.
- //-----------------------
- if err := s.AddAddress(nicid, fakeNetNumber, localAddr); err != nil {
- t.Fatal("AddAddress failed:", err)
- }
- verifyAddress(t, s, nicid, localAddr)
- testRecv(t, fakeNet, localAddrByte, ep, buf)
- testSendTo(t, s, remoteAddr, ep, nil)
-
- // 5. Take a reference to the endpoint by getting a route. Verify that
- // we can still send/receive, including sending using the route.
- //-----------------------
- r, err := s.FindRoute(0, "", remoteAddr, fakeNetNumber, false /* multicastLoop */)
- if err != nil {
- t.Fatal("FindRoute failed:", err)
- }
- testRecv(t, fakeNet, localAddrByte, ep, buf)
- testSendTo(t, s, remoteAddr, ep, nil)
- testSend(t, r, ep, nil)
-
- // 6. Remove the address. Send should only work for spoofing, receive
- // for promiscuous mode.
- //-----------------------
- if err := s.RemoveAddress(nicid, localAddr); err != nil {
- t.Fatal("RemoveAddress failed:", err)
- }
- verifyAddress(t, s, nicid, noAddr)
- if promiscuous {
- testRecv(t, fakeNet, localAddrByte, ep, buf)
- } else {
- testFailingRecv(t, fakeNet, localAddrByte, ep, buf)
- }
- if spoofing {
- testSend(t, r, ep, nil)
- testSendTo(t, s, remoteAddr, ep, nil)
- } else {
- testFailingSend(t, r, ep, nil, tcpip.ErrInvalidEndpointState)
- testFailingSendTo(t, s, remoteAddr, ep, nil, tcpip.ErrNoRoute)
- }
-
- // 7. Add Address back, everything should work again.
- //-----------------------
- if err := s.AddAddress(nicid, fakeNetNumber, localAddr); err != nil {
- t.Fatal("AddAddress failed:", err)
- }
- verifyAddress(t, s, nicid, localAddr)
- testRecv(t, fakeNet, localAddrByte, ep, buf)
- testSendTo(t, s, remoteAddr, ep, nil)
- testSend(t, r, ep, nil)
-
- // 8. Remove the route, sendTo/recv should still work.
- //-----------------------
- r.Release()
- verifyAddress(t, s, nicid, localAddr)
- testRecv(t, fakeNet, localAddrByte, ep, buf)
- testSendTo(t, s, remoteAddr, ep, nil)
-
- // 9. Remove the address. Send should only work for spoofing, receive
- // for promiscuous mode.
- //-----------------------
- if err := s.RemoveAddress(nicid, localAddr); err != nil {
- t.Fatal("RemoveAddress failed:", err)
- }
- verifyAddress(t, s, nicid, noAddr)
- if promiscuous {
- testRecv(t, fakeNet, localAddrByte, ep, buf)
- } else {
- testFailingRecv(t, fakeNet, localAddrByte, ep, buf)
- }
- if spoofing {
- // FIXME(b/139841518):Spoofing doesn't work if there is no primary address.
- // testSendTo(t, s, remoteAddr, ep, nil)
- } else {
- testFailingSendTo(t, s, remoteAddr, ep, nil, tcpip.ErrNoRoute)
- }
- })
- }
- }
-}
-
-func TestPromiscuousMode(t *testing.T) {
- s := stack.New([]string{"fakeNet"}, nil, stack.Options{})
-
- ep := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(1, ep); err != nil {
- t.Fatal("CreateNIC failed:", err)
- }
-
- {
- subnet, err := tcpip.NewSubnet("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
- }
-
- fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)
-
- buf := buffer.NewView(30)
-
- // Write a packet, and check that it doesn't get delivered as we don't
- // have a matching endpoint.
- const localAddrByte byte = 0x01
- buf[0] = localAddrByte
- testFailingRecv(t, fakeNet, localAddrByte, ep, buf)
-
- // Set promiscuous mode, then check that packet is delivered.
- if err := s.SetPromiscuousMode(1, true); err != nil {
- t.Fatal("SetPromiscuousMode failed:", err)
- }
- testRecv(t, fakeNet, localAddrByte, ep, buf)
-
- // Check that we can't get a route as there is no local address.
- _, err := s.FindRoute(0, "", "\x02", fakeNetNumber, false /* multicastLoop */)
- if err != tcpip.ErrNoRoute {
- t.Fatalf("FindRoute returned unexpected error: got = %v, want = %s", err, tcpip.ErrNoRoute)
- }
-
- // Set promiscuous mode to false, then check that packet can't be
- // delivered anymore.
- if err := s.SetPromiscuousMode(1, false); err != nil {
- t.Fatal("SetPromiscuousMode failed:", err)
- }
- testFailingRecv(t, fakeNet, localAddrByte, ep, buf)
-}
-
-func TestSpoofingWithAddress(t *testing.T) {
- localAddr := tcpip.Address("\x01")
- nonExistentLocalAddr := tcpip.Address("\x02")
- dstAddr := tcpip.Address("\x03")
-
- s := stack.New([]string{"fakeNet"}, nil, stack.Options{})
-
- ep := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(1, ep); err != nil {
- t.Fatal("CreateNIC failed:", err)
- }
-
- if err := s.AddAddress(1, fakeNetNumber, localAddr); err != nil {
- t.Fatal("AddAddress failed:", err)
- }
-
- {
- subnet, err := tcpip.NewSubnet("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
- }
-
- // With address spoofing disabled, FindRoute does not permit an address
- // that was not added to the NIC to be used as the source.
- r, err := s.FindRoute(0, nonExistentLocalAddr, dstAddr, fakeNetNumber, false /* multicastLoop */)
- if err == nil {
- t.Errorf("FindRoute succeeded with route %+v when it should have failed", r)
- }
-
- // With address spoofing enabled, FindRoute permits any address to be used
- // as the source.
- if err := s.SetSpoofing(1, true); err != nil {
- t.Fatal("SetSpoofing failed:", err)
- }
- r, err = s.FindRoute(0, nonExistentLocalAddr, dstAddr, fakeNetNumber, false /* multicastLoop */)
- if err != nil {
- t.Fatal("FindRoute failed:", err)
- }
- if r.LocalAddress != nonExistentLocalAddr {
- t.Errorf("Route has wrong local address: got %v, wanted %v", r.LocalAddress, nonExistentLocalAddr)
- }
- if r.RemoteAddress != dstAddr {
- t.Errorf("Route has wrong remote address: got %v, wanted %v", r.RemoteAddress, dstAddr)
- }
- // Sending a packet works.
- testSendTo(t, s, dstAddr, ep, nil)
- testSend(t, r, ep, nil)
-
- // FindRoute should also work with a local address that exists on the NIC.
- r, err = s.FindRoute(0, localAddr, dstAddr, fakeNetNumber, false /* multicastLoop */)
- if err != nil {
- t.Fatal("FindRoute failed:", err)
- }
- if r.LocalAddress != localAddr {
- t.Errorf("Route has wrong local address: got %v, wanted %v", r.LocalAddress, nonExistentLocalAddr)
- }
- if r.RemoteAddress != dstAddr {
- t.Errorf("Route has wrong remote address: got %v, wanted %v", r.RemoteAddress, dstAddr)
- }
- // Sending a packet using the route works.
- testSend(t, r, ep, nil)
-}
-
-func TestSpoofingNoAddress(t *testing.T) {
- nonExistentLocalAddr := tcpip.Address("\x01")
- dstAddr := tcpip.Address("\x02")
-
- s := stack.New([]string{"fakeNet"}, nil, stack.Options{})
-
- ep := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(1, ep); err != nil {
- t.Fatal("CreateNIC failed:", err)
- }
-
- {
- subnet, err := tcpip.NewSubnet("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
- }
-
- // With address spoofing disabled, FindRoute does not permit an address
- // that was not added to the NIC to be used as the source.
- r, err := s.FindRoute(0, nonExistentLocalAddr, dstAddr, fakeNetNumber, false /* multicastLoop */)
- if err == nil {
- t.Errorf("FindRoute succeeded with route %+v when it should have failed", r)
- }
- // Sending a packet fails.
- testFailingSendTo(t, s, dstAddr, ep, nil, tcpip.ErrNoRoute)
-
- // With address spoofing enabled, FindRoute permits any address to be used
- // as the source.
- if err := s.SetSpoofing(1, true); err != nil {
- t.Fatal("SetSpoofing failed:", err)
- }
- r, err = s.FindRoute(0, nonExistentLocalAddr, dstAddr, fakeNetNumber, false /* multicastLoop */)
- if err != nil {
- t.Fatal("FindRoute failed:", err)
- }
- if r.LocalAddress != nonExistentLocalAddr {
- t.Errorf("Route has wrong local address: got %v, wanted %v", r.LocalAddress, nonExistentLocalAddr)
- }
- if r.RemoteAddress != dstAddr {
- t.Errorf("Route has wrong remote address: got %v, wanted %v", r.RemoteAddress, dstAddr)
- }
- // Sending a packet works.
- // FIXME(b/139841518):Spoofing doesn't work if there is no primary address.
- // testSendTo(t, s, remoteAddr, ep, nil)
-}
-
-func TestBroadcastNeedsNoRoute(t *testing.T) {
- s := stack.New([]string{"fakeNet"}, nil, stack.Options{})
-
- ep := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(1, ep); err != nil {
- t.Fatal("CreateNIC failed:", err)
- }
- s.SetRouteTable([]tcpip.Route{})
-
- // If there is no endpoint, it won't work.
- if _, err := s.FindRoute(1, header.IPv4Any, header.IPv4Broadcast, fakeNetNumber, false /* multicastLoop */); err != tcpip.ErrNetworkUnreachable {
- t.Fatalf("got FindRoute(1, %v, %v, %v) = %v, want = %s", header.IPv4Any, header.IPv4Broadcast, fakeNetNumber, err, tcpip.ErrNetworkUnreachable)
- }
-
- if err := s.AddAddress(1, fakeNetNumber, header.IPv4Any); err != nil {
- t.Fatalf("AddAddress(%v, %v) failed: %s", fakeNetNumber, header.IPv4Any, err)
- }
- r, err := s.FindRoute(1, header.IPv4Any, header.IPv4Broadcast, fakeNetNumber, false /* multicastLoop */)
- if err != nil {
- t.Fatalf("FindRoute(1, %v, %v, %v) failed: %v", header.IPv4Any, header.IPv4Broadcast, fakeNetNumber, err)
- }
-
- if r.LocalAddress != header.IPv4Any {
- t.Errorf("Bad local address: got %v, want = %v", r.LocalAddress, header.IPv4Any)
- }
-
- if r.RemoteAddress != header.IPv4Broadcast {
- t.Errorf("Bad remote address: got %v, want = %v", r.RemoteAddress, header.IPv4Broadcast)
- }
-
- // If the NIC doesn't exist, it won't work.
- if _, err := s.FindRoute(2, header.IPv4Any, header.IPv4Broadcast, fakeNetNumber, false /* multicastLoop */); err != tcpip.ErrNetworkUnreachable {
- t.Fatalf("got FindRoute(2, %v, %v, %v) = %v want = %v", header.IPv4Any, header.IPv4Broadcast, fakeNetNumber, err, tcpip.ErrNetworkUnreachable)
- }
-}
-
-func TestMulticastOrIPv6LinkLocalNeedsNoRoute(t *testing.T) {
- for _, tc := range []struct {
- name string
- routeNeeded bool
- address tcpip.Address
- }{
- // IPv4 multicast address range: 224.0.0.0 - 239.255.255.255
- // <=> 0xe0.0x00.0x00.0x00 - 0xef.0xff.0xff.0xff
- {"IPv4 Multicast 1", false, "\xe0\x00\x00\x00"},
- {"IPv4 Multicast 2", false, "\xef\xff\xff\xff"},
- {"IPv4 Unicast 1", true, "\xdf\xff\xff\xff"},
- {"IPv4 Unicast 2", true, "\xf0\x00\x00\x00"},
- {"IPv4 Unicast 3", true, "\x00\x00\x00\x00"},
-
- // IPv6 multicast address is 0xff[8] + flags[4] + scope[4] + groupId[112]
- {"IPv6 Multicast 1", false, "\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
- {"IPv6 Multicast 2", false, "\xff\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
- {"IPv6 Multicast 3", false, "\xff\x0f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"},
-
- // IPv6 link-local address starts with fe80::/10.
- {"IPv6 Unicast Link-Local 1", false, "\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
- {"IPv6 Unicast Link-Local 2", false, "\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"},
- {"IPv6 Unicast Link-Local 3", false, "\xfe\x80\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff"},
- {"IPv6 Unicast Link-Local 4", false, "\xfe\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
- {"IPv6 Unicast Link-Local 5", false, "\xfe\xbf\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"},
-
- // IPv6 addresses that are neither multicast nor link-local.
- {"IPv6 Unicast Not Link-Local 1", true, "\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
- {"IPv6 Unicast Not Link-Local 2", true, "\xf0\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"},
- {"IPv6 Unicast Not Link-local 3", true, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
- {"IPv6 Unicast Not Link-Local 4", true, "\xfe\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
- {"IPv6 Unicast Not Link-Local 5", true, "\xfe\xdf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
- {"IPv6 Unicast Not Link-Local 6", true, "\xfd\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
- {"IPv6 Unicast Not Link-Local 7", true, "\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
- } {
- t.Run(tc.name, func(t *testing.T) {
- s := stack.New([]string{"fakeNet"}, nil, stack.Options{})
-
- ep := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(1, ep); err != nil {
- t.Fatal("CreateNIC failed:", err)
- }
-
- s.SetRouteTable([]tcpip.Route{})
-
- var anyAddr tcpip.Address
- if len(tc.address) == header.IPv4AddressSize {
- anyAddr = header.IPv4Any
- } else {
- anyAddr = header.IPv6Any
- }
-
- want := tcpip.ErrNetworkUnreachable
- if tc.routeNeeded {
- want = tcpip.ErrNoRoute
- }
-
- // If there is no endpoint, it won't work.
- if _, err := s.FindRoute(1, anyAddr, tc.address, fakeNetNumber, false /* multicastLoop */); err != want {
- t.Fatalf("got FindRoute(1, %v, %v, %v) = %v, want = %v", anyAddr, tc.address, fakeNetNumber, err, want)
- }
-
- if err := s.AddAddress(1, fakeNetNumber, anyAddr); err != nil {
- t.Fatalf("AddAddress(%v, %v) failed: %v", fakeNetNumber, anyAddr, err)
- }
-
- if r, err := s.FindRoute(1, anyAddr, tc.address, fakeNetNumber, false /* multicastLoop */); tc.routeNeeded {
- // Route table is empty but we need a route, this should cause an error.
- if err != tcpip.ErrNoRoute {
- t.Fatalf("got FindRoute(1, %v, %v, %v) = %v, want = %v", anyAddr, tc.address, fakeNetNumber, err, tcpip.ErrNoRoute)
- }
- } else {
- if err != nil {
- t.Fatalf("FindRoute(1, %v, %v, %v) failed: %v", anyAddr, tc.address, fakeNetNumber, err)
- }
- if r.LocalAddress != anyAddr {
- t.Errorf("Bad local address: got %v, want = %v", r.LocalAddress, anyAddr)
- }
- if r.RemoteAddress != tc.address {
- t.Errorf("Bad remote address: got %v, want = %v", r.RemoteAddress, tc.address)
- }
- }
- // If the NIC doesn't exist, it won't work.
- if _, err := s.FindRoute(2, anyAddr, tc.address, fakeNetNumber, false /* multicastLoop */); err != want {
- t.Fatalf("got FindRoute(2, %v, %v, %v) = %v want = %v", anyAddr, tc.address, fakeNetNumber, err, want)
- }
- })
- }
-}
-
-// Add a range of addresses, then check that a packet is delivered.
-func TestAddressRangeAcceptsMatchingPacket(t *testing.T) {
- s := stack.New([]string{"fakeNet"}, nil, stack.Options{})
-
- ep := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(1, ep); err != nil {
- t.Fatal("CreateNIC failed:", err)
- }
-
- {
- subnet, err := tcpip.NewSubnet("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
- }
-
- fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)
-
- buf := buffer.NewView(30)
-
- const localAddrByte byte = 0x01
- buf[0] = localAddrByte
- subnet, err := tcpip.NewSubnet(tcpip.Address("\x00"), tcpip.AddressMask("\xF0"))
- if err != nil {
- t.Fatal("NewSubnet failed:", err)
- }
- if err := s.AddAddressRange(1, fakeNetNumber, subnet); err != nil {
- t.Fatal("AddAddressRange failed:", err)
- }
-
- testRecv(t, fakeNet, localAddrByte, ep, buf)
-}
-
-func testNicForAddressRange(t *testing.T, nicID tcpip.NICID, s *stack.Stack, subnet tcpip.Subnet, rangeExists bool) {
- t.Helper()
-
- // Loop over all addresses and check them.
- numOfAddresses := 1 << uint(8-subnet.Prefix())
- if numOfAddresses < 1 || numOfAddresses > 255 {
- t.Fatalf("got numOfAddresses = %d, want = [1 .. 255] (subnet=%s)", numOfAddresses, subnet)
- }
-
- addrBytes := []byte(subnet.ID())
- for i := 0; i < numOfAddresses; i++ {
- addr := tcpip.Address(addrBytes)
- wantNicID := nicID
- // The subnet and broadcast addresses are skipped.
- if !rangeExists || addr == subnet.ID() || addr == subnet.Broadcast() {
- wantNicID = 0
- }
- if gotNicID := s.CheckLocalAddress(0, fakeNetNumber, addr); gotNicID != wantNicID {
- t.Errorf("got CheckLocalAddress(0, %d, %s) = %d, want = %d", fakeNetNumber, addr, gotNicID, wantNicID)
- }
- addrBytes[0]++
- }
-
- // Trying the next address should always fail since it is outside the range.
- if gotNicID := s.CheckLocalAddress(0, fakeNetNumber, tcpip.Address(addrBytes)); gotNicID != 0 {
- t.Errorf("got CheckLocalAddress(0, %d, %s) = %d, want = %d", fakeNetNumber, tcpip.Address(addrBytes), gotNicID, 0)
- }
-}
-
-// Set a range of addresses, then remove it again, and check at each step that
-// CheckLocalAddress returns the correct NIC for each address or zero if not
-// existent.
-func TestCheckLocalAddressForSubnet(t *testing.T) {
- const nicID tcpip.NICID = 1
- s := stack.New([]string{"fakeNet"}, nil, stack.Options{})
-
- ep := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(nicID, ep); err != nil {
- t.Fatal("CreateNIC failed:", err)
- }
-
- {
- subnet, err := tcpip.NewSubnet("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: nicID}})
- }
-
- subnet, err := tcpip.NewSubnet(tcpip.Address("\xa0"), tcpip.AddressMask("\xf0"))
- if err != nil {
- t.Fatal("NewSubnet failed:", err)
- }
-
- testNicForAddressRange(t, nicID, s, subnet, false /* rangeExists */)
-
- if err := s.AddAddressRange(nicID, fakeNetNumber, subnet); err != nil {
- t.Fatal("AddAddressRange failed:", err)
- }
-
- testNicForAddressRange(t, nicID, s, subnet, true /* rangeExists */)
-
- if err := s.RemoveAddressRange(nicID, subnet); err != nil {
- t.Fatal("RemoveAddressRange failed:", err)
- }
-
- testNicForAddressRange(t, nicID, s, subnet, false /* rangeExists */)
-}
-
-// Set a range of addresses, then send a packet to a destination outside the
-// range and then check it doesn't get delivered.
-func TestAddressRangeRejectsNonmatchingPacket(t *testing.T) {
- s := stack.New([]string{"fakeNet"}, nil, stack.Options{})
-
- ep := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(1, ep); err != nil {
- t.Fatal("CreateNIC failed:", err)
- }
-
- {
- subnet, err := tcpip.NewSubnet("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
- }
-
- fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)
-
- buf := buffer.NewView(30)
-
- const localAddrByte byte = 0x01
- buf[0] = localAddrByte
- subnet, err := tcpip.NewSubnet(tcpip.Address("\x10"), tcpip.AddressMask("\xF0"))
- if err != nil {
- t.Fatal("NewSubnet failed:", err)
- }
- if err := s.AddAddressRange(1, fakeNetNumber, subnet); err != nil {
- t.Fatal("AddAddressRange failed:", err)
- }
- testFailingRecv(t, fakeNet, localAddrByte, ep, buf)
-}
-
-func TestNetworkOptions(t *testing.T) {
- s := stack.New([]string{"fakeNet"}, []string{}, stack.Options{})
-
- // Try an unsupported network protocol.
- if err := s.SetNetworkProtocolOption(tcpip.NetworkProtocolNumber(99999), fakeNetGoodOption(false)); err != tcpip.ErrUnknownProtocol {
- t.Fatalf("SetNetworkProtocolOption(fakeNet2, blah, false) = %v, want = tcpip.ErrUnknownProtocol", err)
- }
-
- testCases := []struct {
- option interface{}
- wantErr *tcpip.Error
- verifier func(t *testing.T, p stack.NetworkProtocol)
- }{
- {fakeNetGoodOption(true), nil, func(t *testing.T, p stack.NetworkProtocol) {
- t.Helper()
- fakeNet := p.(*fakeNetworkProtocol)
- if fakeNet.opts.good != true {
- t.Fatalf("fakeNet.opts.good = false, want = true")
- }
- var v fakeNetGoodOption
- if err := s.NetworkProtocolOption(fakeNetNumber, &v); err != nil {
- t.Fatalf("s.NetworkProtocolOption(fakeNetNumber, &v) = %v, want = nil, where v is option %T", v, err)
- }
- if v != true {
- t.Fatalf("s.NetworkProtocolOption(fakeNetNumber, &v) returned v = %v, want = true", v)
- }
- }},
- {fakeNetBadOption(true), tcpip.ErrUnknownProtocolOption, nil},
- {fakeNetInvalidValueOption(1), tcpip.ErrInvalidOptionValue, nil},
- }
- for _, tc := range testCases {
- if got := s.SetNetworkProtocolOption(fakeNetNumber, tc.option); got != tc.wantErr {
- t.Errorf("s.SetNetworkProtocolOption(fakeNet, %v) = %v, want = %v", tc.option, got, tc.wantErr)
- }
- if tc.verifier != nil {
- tc.verifier(t, s.NetworkProtocolInstance(fakeNetNumber))
- }
- }
-}
-
-func stackContainsAddressRange(s *stack.Stack, id tcpip.NICID, addrRange tcpip.Subnet) bool {
- ranges, ok := s.NICAddressRanges()[id]
- if !ok {
- return false
- }
- for _, r := range ranges {
- if r == addrRange {
- return true
- }
- }
- return false
-}
-
-func TestAddresRangeAddRemove(t *testing.T) {
- s := stack.New([]string{"fakeNet"}, nil, stack.Options{})
- ep := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(1, ep); err != nil {
- t.Fatal("CreateNIC failed:", err)
- }
-
- addr := tcpip.Address("\x01\x01\x01\x01")
- mask := tcpip.AddressMask(strings.Repeat("\xff", len(addr)))
- addrRange, err := tcpip.NewSubnet(addr, mask)
- if err != nil {
- t.Fatal("NewSubnet failed:", err)
- }
-
- if got, want := stackContainsAddressRange(s, 1, addrRange), false; got != want {
- t.Fatalf("got stackContainsAddressRange(...) = %t, want = %t", got, want)
- }
-
- if err := s.AddAddressRange(1, fakeNetNumber, addrRange); err != nil {
- t.Fatal("AddAddressRange failed:", err)
- }
-
- if got, want := stackContainsAddressRange(s, 1, addrRange), true; got != want {
- t.Fatalf("got stackContainsAddressRange(...) = %t, want = %t", got, want)
- }
-
- if err := s.RemoveAddressRange(1, addrRange); err != nil {
- t.Fatal("RemoveAddressRange failed:", err)
- }
-
- if got, want := stackContainsAddressRange(s, 1, addrRange), false; got != want {
- t.Fatalf("got stackContainsAddressRange(...) = %t, want = %t", got, want)
- }
-}
-
-func TestGetMainNICAddressAddPrimaryNonPrimary(t *testing.T) {
- for _, addrLen := range []int{4, 16} {
- t.Run(fmt.Sprintf("addrLen=%d", addrLen), func(t *testing.T) {
- for canBe := 0; canBe < 3; canBe++ {
- t.Run(fmt.Sprintf("canBe=%d", canBe), func(t *testing.T) {
- for never := 0; never < 3; never++ {
- t.Run(fmt.Sprintf("never=%d", never), func(t *testing.T) {
- s := stack.New([]string{"fakeNet"}, nil, stack.Options{})
- ep := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(1, ep); err != nil {
- t.Fatal("CreateNIC failed:", err)
- }
- // Insert <canBe> primary and <never> never-primary addresses.
- // Each one will add a network endpoint to the NIC.
- primaryAddrAdded := make(map[tcpip.AddressWithPrefix]struct{})
- for i := 0; i < canBe+never; i++ {
- var behavior stack.PrimaryEndpointBehavior
- if i < canBe {
- behavior = stack.CanBePrimaryEndpoint
- } else {
- behavior = stack.NeverPrimaryEndpoint
- }
- // Add an address and in case of a primary one include a
- // prefixLen.
- address := tcpip.Address(bytes.Repeat([]byte{byte(i)}, addrLen))
- if behavior == stack.CanBePrimaryEndpoint {
- protocolAddress := tcpip.ProtocolAddress{
- Protocol: fakeNetNumber,
- AddressWithPrefix: tcpip.AddressWithPrefix{
- Address: address,
- PrefixLen: addrLen * 8,
- },
- }
- if err := s.AddProtocolAddressWithOptions(1, protocolAddress, behavior); err != nil {
- t.Fatal("AddProtocolAddressWithOptions failed:", err)
- }
- // Remember the address/prefix.
- primaryAddrAdded[protocolAddress.AddressWithPrefix] = struct{}{}
- } else {
- if err := s.AddAddressWithOptions(1, fakeNetNumber, address, behavior); err != nil {
- t.Fatal("AddAddressWithOptions failed:", err)
- }
- }
- }
- // Check that GetMainNICAddress returns an address if at least
- // one primary address was added. In that case make sure the
- // address/prefixLen matches what we added.
- if len(primaryAddrAdded) == 0 {
- // No primary addresses present, expect an error.
- if _, err := s.GetMainNICAddress(1, fakeNetNumber); err != tcpip.ErrNoLinkAddress {
- t.Fatalf("got s.GetMainNICAddress(...) = %v, wanted = %s", err, tcpip.ErrNoLinkAddress)
- }
- } else {
- // At least one primary address was added, expect a valid
- // address and prefixLen.
- gotAddressWithPefix, err := s.GetMainNICAddress(1, fakeNetNumber)
- if err != nil {
- t.Fatal("GetMainNICAddress failed:", err)
- }
- if _, ok := primaryAddrAdded[gotAddressWithPefix]; !ok {
- t.Fatalf("GetMainNICAddress: got addressWithPrefix = %v, wanted any in {%v}", gotAddressWithPefix, primaryAddrAdded)
- }
- }
- })
- }
- })
- }
- })
- }
-}
-
-func TestGetMainNICAddressAddRemove(t *testing.T) {
- s := stack.New([]string{"fakeNet"}, nil, stack.Options{})
- ep := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(1, ep); err != nil {
- t.Fatal("CreateNIC failed:", err)
- }
-
- for _, tc := range []struct {
- name string
- address tcpip.Address
- prefixLen int
- }{
- {"IPv4", "\x01\x01\x01\x01", 24},
- {"IPv6", "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 116},
- } {
- t.Run(tc.name, func(t *testing.T) {
- protocolAddress := tcpip.ProtocolAddress{
- Protocol: fakeNetNumber,
- AddressWithPrefix: tcpip.AddressWithPrefix{
- Address: tc.address,
- PrefixLen: tc.prefixLen,
- },
- }
- if err := s.AddProtocolAddress(1, protocolAddress); err != nil {
- t.Fatal("AddProtocolAddress failed:", err)
- }
-
- // Check that we get the right initial address and prefix length.
- if gotAddressWithPrefix, err := s.GetMainNICAddress(1, fakeNetNumber); err != nil {
- t.Fatal("GetMainNICAddress failed:", err)
- } else if gotAddressWithPrefix != protocolAddress.AddressWithPrefix {
- t.Fatalf("got GetMainNICAddress = %+v, want = %+v", gotAddressWithPrefix, protocolAddress.AddressWithPrefix)
- }
-
- if err := s.RemoveAddress(1, protocolAddress.AddressWithPrefix.Address); err != nil {
- t.Fatal("RemoveAddress failed:", err)
- }
-
- // Check that we get an error after removal.
- if _, err := s.GetMainNICAddress(1, fakeNetNumber); err != tcpip.ErrNoLinkAddress {
- t.Fatalf("got s.GetMainNICAddress(...) = %v, want = %s", err, tcpip.ErrNoLinkAddress)
- }
- })
- }
-}
-
-// Simple network address generator. Good for 255 addresses.
-type addressGenerator struct{ cnt byte }
-
-func (g *addressGenerator) next(addrLen int) tcpip.Address {
- g.cnt++
- return tcpip.Address(bytes.Repeat([]byte{g.cnt}, addrLen))
-}
-
-func verifyAddresses(t *testing.T, expectedAddresses, gotAddresses []tcpip.ProtocolAddress) {
- if len(gotAddresses) != len(expectedAddresses) {
- t.Fatalf("got len(addresses) = %d, wanted = %d", len(gotAddresses), len(expectedAddresses))
- }
-
- sort.Slice(gotAddresses, func(i, j int) bool {
- return gotAddresses[i].AddressWithPrefix.Address < gotAddresses[j].AddressWithPrefix.Address
- })
- sort.Slice(expectedAddresses, func(i, j int) bool {
- return expectedAddresses[i].AddressWithPrefix.Address < expectedAddresses[j].AddressWithPrefix.Address
- })
-
- for i, gotAddr := range gotAddresses {
- expectedAddr := expectedAddresses[i]
- if gotAddr != expectedAddr {
- t.Errorf("got address = %+v, wanted = %+v", gotAddr, expectedAddr)
- }
- }
-}
-
-func TestAddAddress(t *testing.T) {
- const nicid = 1
- s := stack.New([]string{"fakeNet"}, nil, stack.Options{})
- ep := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(nicid, ep); err != nil {
- t.Fatal("CreateNIC failed:", err)
- }
-
- var addrGen addressGenerator
- expectedAddresses := make([]tcpip.ProtocolAddress, 0, 2)
- for _, addrLen := range []int{4, 16} {
- address := addrGen.next(addrLen)
- if err := s.AddAddress(nicid, fakeNetNumber, address); err != nil {
- t.Fatalf("AddAddress(address=%s) failed: %s", address, err)
- }
- expectedAddresses = append(expectedAddresses, tcpip.ProtocolAddress{
- Protocol: fakeNetNumber,
- AddressWithPrefix: tcpip.AddressWithPrefix{address, fakeDefaultPrefixLen},
- })
- }
-
- gotAddresses := s.NICInfo()[nicid].ProtocolAddresses
- verifyAddresses(t, expectedAddresses, gotAddresses)
-}
-
-func TestAddProtocolAddress(t *testing.T) {
- const nicid = 1
- s := stack.New([]string{"fakeNet"}, nil, stack.Options{})
- ep := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(nicid, ep); err != nil {
- t.Fatal("CreateNIC failed:", err)
- }
-
- var addrGen addressGenerator
- addrLenRange := []int{4, 16}
- prefixLenRange := []int{8, 13, 20, 32}
- expectedAddresses := make([]tcpip.ProtocolAddress, 0, len(addrLenRange)*len(prefixLenRange))
- for _, addrLen := range addrLenRange {
- for _, prefixLen := range prefixLenRange {
- protocolAddress := tcpip.ProtocolAddress{
- Protocol: fakeNetNumber,
- AddressWithPrefix: tcpip.AddressWithPrefix{
- Address: addrGen.next(addrLen),
- PrefixLen: prefixLen,
- },
- }
- if err := s.AddProtocolAddress(nicid, protocolAddress); err != nil {
- t.Errorf("AddProtocolAddress(%+v) failed: %s", protocolAddress, err)
- }
- expectedAddresses = append(expectedAddresses, protocolAddress)
- }
- }
-
- gotAddresses := s.NICInfo()[nicid].ProtocolAddresses
- verifyAddresses(t, expectedAddresses, gotAddresses)
-}
-
-func TestAddAddressWithOptions(t *testing.T) {
- const nicid = 1
- s := stack.New([]string{"fakeNet"}, nil, stack.Options{})
- ep := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(nicid, ep); err != nil {
- t.Fatal("CreateNIC failed:", err)
- }
-
- addrLenRange := []int{4, 16}
- behaviorRange := []stack.PrimaryEndpointBehavior{stack.CanBePrimaryEndpoint, stack.FirstPrimaryEndpoint, stack.NeverPrimaryEndpoint}
- expectedAddresses := make([]tcpip.ProtocolAddress, 0, len(addrLenRange)*len(behaviorRange))
- var addrGen addressGenerator
- for _, addrLen := range addrLenRange {
- for _, behavior := range behaviorRange {
- address := addrGen.next(addrLen)
- if err := s.AddAddressWithOptions(nicid, fakeNetNumber, address, behavior); err != nil {
- t.Fatalf("AddAddressWithOptions(address=%s, behavior=%d) failed: %s", address, behavior, err)
- }
- expectedAddresses = append(expectedAddresses, tcpip.ProtocolAddress{
- Protocol: fakeNetNumber,
- AddressWithPrefix: tcpip.AddressWithPrefix{address, fakeDefaultPrefixLen},
- })
- }
- }
-
- gotAddresses := s.NICInfo()[nicid].ProtocolAddresses
- verifyAddresses(t, expectedAddresses, gotAddresses)
-}
-
-func TestAddProtocolAddressWithOptions(t *testing.T) {
- const nicid = 1
- s := stack.New([]string{"fakeNet"}, nil, stack.Options{})
- ep := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(nicid, ep); err != nil {
- t.Fatal("CreateNIC failed:", err)
- }
-
- addrLenRange := []int{4, 16}
- prefixLenRange := []int{8, 13, 20, 32}
- behaviorRange := []stack.PrimaryEndpointBehavior{stack.CanBePrimaryEndpoint, stack.FirstPrimaryEndpoint, stack.NeverPrimaryEndpoint}
- expectedAddresses := make([]tcpip.ProtocolAddress, 0, len(addrLenRange)*len(prefixLenRange)*len(behaviorRange))
- var addrGen addressGenerator
- for _, addrLen := range addrLenRange {
- for _, prefixLen := range prefixLenRange {
- for _, behavior := range behaviorRange {
- protocolAddress := tcpip.ProtocolAddress{
- Protocol: fakeNetNumber,
- AddressWithPrefix: tcpip.AddressWithPrefix{
- Address: addrGen.next(addrLen),
- PrefixLen: prefixLen,
- },
- }
- if err := s.AddProtocolAddressWithOptions(nicid, protocolAddress, behavior); err != nil {
- t.Fatalf("AddProtocolAddressWithOptions(%+v, %d) failed: %s", protocolAddress, behavior, err)
- }
- expectedAddresses = append(expectedAddresses, protocolAddress)
- }
- }
- }
-
- gotAddresses := s.NICInfo()[nicid].ProtocolAddresses
- verifyAddresses(t, expectedAddresses, gotAddresses)
-}
-
-func TestNICStats(t *testing.T) {
- s := stack.New([]string{"fakeNet"}, nil, stack.Options{})
- ep1 := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(1, ep1); err != nil {
- t.Fatal("CreateNIC failed: ", err)
- }
- if err := s.AddAddress(1, fakeNetNumber, "\x01"); err != nil {
- t.Fatal("AddAddress failed:", err)
- }
- // Route all packets for address \x01 to NIC 1.
- {
- subnet, err := tcpip.NewSubnet("\x01", "\xff")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
- }
-
- // Send a packet to address 1.
- buf := buffer.NewView(30)
- ep1.Inject(fakeNetNumber, buf.ToVectorisedView())
- if got, want := s.NICInfo()[1].Stats.Rx.Packets.Value(), uint64(1); got != want {
- t.Errorf("got Rx.Packets.Value() = %d, want = %d", got, want)
- }
-
- if got, want := s.NICInfo()[1].Stats.Rx.Bytes.Value(), uint64(len(buf)); got != want {
- t.Errorf("got Rx.Bytes.Value() = %d, want = %d", got, want)
- }
-
- payload := buffer.NewView(10)
- // Write a packet out via the address for NIC 1
- if err := sendTo(s, "\x01", payload); err != nil {
- t.Fatal("sendTo failed: ", err)
- }
- want := uint64(ep1.Drain())
- if got := s.NICInfo()[1].Stats.Tx.Packets.Value(); got != want {
- t.Errorf("got Tx.Packets.Value() = %d, ep1.Drain() = %d", got, want)
- }
-
- if got, want := s.NICInfo()[1].Stats.Tx.Bytes.Value(), uint64(len(payload)); got != want {
- t.Errorf("got Tx.Bytes.Value() = %d, want = %d", got, want)
- }
-}
-
-func TestNICForwarding(t *testing.T) {
- // Create a stack with the fake network protocol, two NICs, each with
- // an address.
- s := stack.New([]string{"fakeNet"}, nil, stack.Options{})
- s.SetForwarding(true)
-
- ep1 := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(1, ep1); err != nil {
- t.Fatal("CreateNIC #1 failed:", err)
- }
- if err := s.AddAddress(1, fakeNetNumber, "\x01"); err != nil {
- t.Fatal("AddAddress #1 failed:", err)
- }
-
- ep2 := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(2, ep2); err != nil {
- t.Fatal("CreateNIC #2 failed:", err)
- }
- if err := s.AddAddress(2, fakeNetNumber, "\x02"); err != nil {
- t.Fatal("AddAddress #2 failed:", err)
- }
-
- // Route all packets to address 3 to NIC 2.
- {
- subnet, err := tcpip.NewSubnet("\x03", "\xff")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 2}})
- }
-
- // Send a packet to address 3.
- buf := buffer.NewView(30)
- buf[0] = 3
- ep1.Inject(fakeNetNumber, buf.ToVectorisedView())
-
- select {
- case <-ep2.C:
- default:
- t.Fatal("Packet not forwarded")
- }
-
- // Test that forwarding increments Tx stats correctly.
- if got, want := s.NICInfo()[2].Stats.Tx.Packets.Value(), uint64(1); got != want {
- t.Errorf("got Tx.Packets.Value() = %d, want = %d", got, want)
- }
-
- if got, want := s.NICInfo()[2].Stats.Tx.Bytes.Value(), uint64(len(buf)); got != want {
- t.Errorf("got Tx.Bytes.Value() = %d, want = %d", got, want)
- }
-}
-
-func init() {
- stack.RegisterNetworkProtocolFactory("fakeNet", func() stack.NetworkProtocol {
- return &fakeNetworkProtocol{}
- })
-}
diff --git a/pkg/tcpip/stack/transport_test.go b/pkg/tcpip/stack/transport_test.go
deleted file mode 100644
index 847d02982..000000000
--- a/pkg/tcpip/stack/transport_test.go
+++ /dev/null
@@ -1,579 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package stack_test
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/iptables"
- "gvisor.dev/gvisor/pkg/tcpip/link/channel"
- "gvisor.dev/gvisor/pkg/tcpip/link/loopback"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-const (
- fakeTransNumber tcpip.TransportProtocolNumber = 1
- fakeTransHeaderLen = 3
-)
-
-// fakeTransportEndpoint is a transport-layer protocol endpoint. It counts
-// received packets; the counts of all endpoints are aggregated in the protocol
-// descriptor.
-//
-// Headers of this protocol are fakeTransHeaderLen bytes, but we currently don't
-// use it.
-type fakeTransportEndpoint struct {
- id stack.TransportEndpointID
- stack *stack.Stack
- netProto tcpip.NetworkProtocolNumber
- proto *fakeTransportProtocol
- peerAddr tcpip.Address
- route stack.Route
-
- // acceptQueue is non-nil iff bound.
- acceptQueue []fakeTransportEndpoint
-}
-
-func newFakeTransportEndpoint(stack *stack.Stack, proto *fakeTransportProtocol, netProto tcpip.NetworkProtocolNumber) tcpip.Endpoint {
- return &fakeTransportEndpoint{stack: stack, netProto: netProto, proto: proto}
-}
-
-func (f *fakeTransportEndpoint) Close() {
- f.route.Release()
-}
-
-func (*fakeTransportEndpoint) Readiness(mask waiter.EventMask) waiter.EventMask {
- return mask
-}
-
-func (*fakeTransportEndpoint) Read(*tcpip.FullAddress) (buffer.View, tcpip.ControlMessages, *tcpip.Error) {
- return buffer.View{}, tcpip.ControlMessages{}, nil
-}
-
-func (f *fakeTransportEndpoint) Write(p tcpip.Payloader, opts tcpip.WriteOptions) (int64, <-chan struct{}, *tcpip.Error) {
- if len(f.route.RemoteAddress) == 0 {
- return 0, nil, tcpip.ErrNoRoute
- }
-
- hdr := buffer.NewPrependable(int(f.route.MaxHeaderLength()))
- v, err := p.FullPayload()
- if err != nil {
- return 0, nil, err
- }
- if err := f.route.WritePacket(nil /* gso */, hdr, buffer.View(v).ToVectorisedView(), fakeTransNumber, 123); err != nil {
- return 0, nil, err
- }
-
- return int64(len(v)), nil, nil
-}
-
-func (f *fakeTransportEndpoint) Peek([][]byte) (int64, tcpip.ControlMessages, *tcpip.Error) {
- return 0, tcpip.ControlMessages{}, nil
-}
-
-// SetSockOpt sets a socket option. Currently not supported.
-func (*fakeTransportEndpoint) SetSockOpt(interface{}) *tcpip.Error {
- return tcpip.ErrInvalidEndpointState
-}
-
-// GetSockOptInt implements tcpip.Endpoint.GetSockOptInt.
-func (*fakeTransportEndpoint) GetSockOptInt(opt tcpip.SockOpt) (int, *tcpip.Error) {
- return -1, tcpip.ErrUnknownProtocolOption
-}
-
-// GetSockOpt implements tcpip.Endpoint.GetSockOpt.
-func (*fakeTransportEndpoint) GetSockOpt(opt interface{}) *tcpip.Error {
- switch opt.(type) {
- case tcpip.ErrorOption:
- return nil
- }
- return tcpip.ErrInvalidEndpointState
-}
-
-// Disconnect implements tcpip.Endpoint.Disconnect.
-func (*fakeTransportEndpoint) Disconnect() *tcpip.Error {
- return tcpip.ErrNotSupported
-}
-
-func (f *fakeTransportEndpoint) Connect(addr tcpip.FullAddress) *tcpip.Error {
- f.peerAddr = addr.Addr
-
- // Find the route.
- r, err := f.stack.FindRoute(addr.NIC, "", addr.Addr, fakeNetNumber, false /* multicastLoop */)
- if err != nil {
- return tcpip.ErrNoRoute
- }
- defer r.Release()
-
- // Try to register so that we can start receiving packets.
- f.id.RemoteAddress = addr.Addr
- err = f.stack.RegisterTransportEndpoint(0, []tcpip.NetworkProtocolNumber{fakeNetNumber}, fakeTransNumber, f.id, f, false)
- if err != nil {
- return err
- }
-
- f.route = r.Clone()
-
- return nil
-}
-
-func (f *fakeTransportEndpoint) ConnectEndpoint(e tcpip.Endpoint) *tcpip.Error {
- return nil
-}
-
-func (*fakeTransportEndpoint) Shutdown(tcpip.ShutdownFlags) *tcpip.Error {
- return nil
-}
-
-func (*fakeTransportEndpoint) Reset() {
-}
-
-func (*fakeTransportEndpoint) Listen(int) *tcpip.Error {
- return nil
-}
-
-func (f *fakeTransportEndpoint) Accept() (tcpip.Endpoint, *waiter.Queue, *tcpip.Error) {
- if len(f.acceptQueue) == 0 {
- return nil, nil, nil
- }
- a := f.acceptQueue[0]
- f.acceptQueue = f.acceptQueue[1:]
- return &a, nil, nil
-}
-
-func (f *fakeTransportEndpoint) Bind(a tcpip.FullAddress) *tcpip.Error {
- if err := f.stack.RegisterTransportEndpoint(
- a.NIC,
- []tcpip.NetworkProtocolNumber{fakeNetNumber},
- fakeTransNumber,
- stack.TransportEndpointID{LocalAddress: a.Addr},
- f,
- false,
- ); err != nil {
- return err
- }
- f.acceptQueue = []fakeTransportEndpoint{}
- return nil
-}
-
-func (*fakeTransportEndpoint) GetLocalAddress() (tcpip.FullAddress, *tcpip.Error) {
- return tcpip.FullAddress{}, nil
-}
-
-func (*fakeTransportEndpoint) GetRemoteAddress() (tcpip.FullAddress, *tcpip.Error) {
- return tcpip.FullAddress{}, nil
-}
-
-func (f *fakeTransportEndpoint) HandlePacket(r *stack.Route, id stack.TransportEndpointID, _ buffer.VectorisedView) {
- // Increment the number of received packets.
- f.proto.packetCount++
- if f.acceptQueue != nil {
- f.acceptQueue = append(f.acceptQueue, fakeTransportEndpoint{
- id: id,
- stack: f.stack,
- netProto: f.netProto,
- proto: f.proto,
- peerAddr: r.RemoteAddress,
- route: r.Clone(),
- })
- }
-}
-
-func (f *fakeTransportEndpoint) HandleControlPacket(stack.TransportEndpointID, stack.ControlType, uint32, buffer.VectorisedView) {
- // Increment the number of received control packets.
- f.proto.controlCount++
-}
-
-func (f *fakeTransportEndpoint) State() uint32 {
- return 0
-}
-
-func (f *fakeTransportEndpoint) ModerateRecvBuf(copied int) {
-}
-
-func (f *fakeTransportEndpoint) IPTables() (iptables.IPTables, error) {
- return iptables.IPTables{}, nil
-}
-
-func (f *fakeTransportEndpoint) Resume(*stack.Stack) {
-}
-
-type fakeTransportGoodOption bool
-
-type fakeTransportBadOption bool
-
-type fakeTransportInvalidValueOption int
-
-type fakeTransportProtocolOptions struct {
- good bool
-}
-
-// fakeTransportProtocol is a transport-layer protocol descriptor. It
-// aggregates the number of packets received via endpoints of this protocol.
-type fakeTransportProtocol struct {
- packetCount int
- controlCount int
- opts fakeTransportProtocolOptions
-}
-
-func (*fakeTransportProtocol) Number() tcpip.TransportProtocolNumber {
- return fakeTransNumber
-}
-
-func (f *fakeTransportProtocol) NewEndpoint(stack *stack.Stack, netProto tcpip.NetworkProtocolNumber, _ *waiter.Queue) (tcpip.Endpoint, *tcpip.Error) {
- return newFakeTransportEndpoint(stack, f, netProto), nil
-}
-
-func (f *fakeTransportProtocol) NewRawEndpoint(stack *stack.Stack, netProto tcpip.NetworkProtocolNumber, _ *waiter.Queue) (tcpip.Endpoint, *tcpip.Error) {
- return nil, tcpip.ErrUnknownProtocol
-}
-
-func (*fakeTransportProtocol) MinimumPacketSize() int {
- return fakeTransHeaderLen
-}
-
-func (*fakeTransportProtocol) ParsePorts(buffer.View) (src, dst uint16, err *tcpip.Error) {
- return 0, 0, nil
-}
-
-func (*fakeTransportProtocol) HandleUnknownDestinationPacket(*stack.Route, stack.TransportEndpointID, buffer.View, buffer.VectorisedView) bool {
- return true
-}
-
-func (f *fakeTransportProtocol) SetOption(option interface{}) *tcpip.Error {
- switch v := option.(type) {
- case fakeTransportGoodOption:
- f.opts.good = bool(v)
- return nil
- case fakeTransportInvalidValueOption:
- return tcpip.ErrInvalidOptionValue
- default:
- return tcpip.ErrUnknownProtocolOption
- }
-}
-
-func (f *fakeTransportProtocol) Option(option interface{}) *tcpip.Error {
- switch v := option.(type) {
- case *fakeTransportGoodOption:
- *v = fakeTransportGoodOption(f.opts.good)
- return nil
- default:
- return tcpip.ErrUnknownProtocolOption
- }
-}
-
-func TestTransportReceive(t *testing.T) {
- linkEP := channel.New(10, defaultMTU, "")
- s := stack.New([]string{"fakeNet"}, []string{"fakeTrans"}, stack.Options{})
- if err := s.CreateNIC(1, linkEP); err != nil {
- t.Fatalf("CreateNIC failed: %v", err)
- }
-
- {
- subnet, err := tcpip.NewSubnet("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
- }
-
- if err := s.AddAddress(1, fakeNetNumber, "\x01"); err != nil {
- t.Fatalf("AddAddress failed: %v", err)
- }
-
- // Create endpoint and connect to remote address.
- wq := waiter.Queue{}
- ep, err := s.NewEndpoint(fakeTransNumber, fakeNetNumber, &wq)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- if err := ep.Connect(tcpip.FullAddress{0, "\x02", 0}); err != nil {
- t.Fatalf("Connect failed: %v", err)
- }
-
- fakeTrans := s.TransportProtocolInstance(fakeTransNumber).(*fakeTransportProtocol)
-
- // Create buffer that will hold the packet.
- buf := buffer.NewView(30)
-
- // Make sure packet with wrong protocol is not delivered.
- buf[0] = 1
- buf[2] = 0
- linkEP.Inject(fakeNetNumber, buf.ToVectorisedView())
- if fakeTrans.packetCount != 0 {
- t.Errorf("packetCount = %d, want %d", fakeTrans.packetCount, 0)
- }
-
- // Make sure packet from the wrong source is not delivered.
- buf[0] = 1
- buf[1] = 3
- buf[2] = byte(fakeTransNumber)
- linkEP.Inject(fakeNetNumber, buf.ToVectorisedView())
- if fakeTrans.packetCount != 0 {
- t.Errorf("packetCount = %d, want %d", fakeTrans.packetCount, 0)
- }
-
- // Make sure packet is delivered.
- buf[0] = 1
- buf[1] = 2
- buf[2] = byte(fakeTransNumber)
- linkEP.Inject(fakeNetNumber, buf.ToVectorisedView())
- if fakeTrans.packetCount != 1 {
- t.Errorf("packetCount = %d, want %d", fakeTrans.packetCount, 1)
- }
-}
-
-func TestTransportControlReceive(t *testing.T) {
- linkEP := channel.New(10, defaultMTU, "")
- s := stack.New([]string{"fakeNet"}, []string{"fakeTrans"}, stack.Options{})
- if err := s.CreateNIC(1, linkEP); err != nil {
- t.Fatalf("CreateNIC failed: %v", err)
- }
-
- {
- subnet, err := tcpip.NewSubnet("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
- }
-
- if err := s.AddAddress(1, fakeNetNumber, "\x01"); err != nil {
- t.Fatalf("AddAddress failed: %v", err)
- }
-
- // Create endpoint and connect to remote address.
- wq := waiter.Queue{}
- ep, err := s.NewEndpoint(fakeTransNumber, fakeNetNumber, &wq)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- if err := ep.Connect(tcpip.FullAddress{0, "\x02", 0}); err != nil {
- t.Fatalf("Connect failed: %v", err)
- }
-
- fakeTrans := s.TransportProtocolInstance(fakeTransNumber).(*fakeTransportProtocol)
-
- // Create buffer that will hold the control packet.
- buf := buffer.NewView(2*fakeNetHeaderLen + 30)
-
- // Outer packet contains the control protocol number.
- buf[0] = 1
- buf[1] = 0xfe
- buf[2] = uint8(fakeControlProtocol)
-
- // Make sure packet with wrong protocol is not delivered.
- buf[fakeNetHeaderLen+0] = 0
- buf[fakeNetHeaderLen+1] = 1
- buf[fakeNetHeaderLen+2] = 0
- linkEP.Inject(fakeNetNumber, buf.ToVectorisedView())
- if fakeTrans.controlCount != 0 {
- t.Errorf("controlCount = %d, want %d", fakeTrans.controlCount, 0)
- }
-
- // Make sure packet from the wrong source is not delivered.
- buf[fakeNetHeaderLen+0] = 3
- buf[fakeNetHeaderLen+1] = 1
- buf[fakeNetHeaderLen+2] = byte(fakeTransNumber)
- linkEP.Inject(fakeNetNumber, buf.ToVectorisedView())
- if fakeTrans.controlCount != 0 {
- t.Errorf("controlCount = %d, want %d", fakeTrans.controlCount, 0)
- }
-
- // Make sure packet is delivered.
- buf[fakeNetHeaderLen+0] = 2
- buf[fakeNetHeaderLen+1] = 1
- buf[fakeNetHeaderLen+2] = byte(fakeTransNumber)
- linkEP.Inject(fakeNetNumber, buf.ToVectorisedView())
- if fakeTrans.controlCount != 1 {
- t.Errorf("controlCount = %d, want %d", fakeTrans.controlCount, 1)
- }
-}
-
-func TestTransportSend(t *testing.T) {
- linkEP := channel.New(10, defaultMTU, "")
- s := stack.New([]string{"fakeNet"}, []string{"fakeTrans"}, stack.Options{})
- if err := s.CreateNIC(1, linkEP); err != nil {
- t.Fatalf("CreateNIC failed: %v", err)
- }
-
- if err := s.AddAddress(1, fakeNetNumber, "\x01"); err != nil {
- t.Fatalf("AddAddress failed: %v", err)
- }
-
- {
- subnet, err := tcpip.NewSubnet("\x00", "\x00")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
- }
-
- // Create endpoint and bind it.
- wq := waiter.Queue{}
- ep, err := s.NewEndpoint(fakeTransNumber, fakeNetNumber, &wq)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- if err := ep.Connect(tcpip.FullAddress{0, "\x02", 0}); err != nil {
- t.Fatalf("Connect failed: %v", err)
- }
-
- // Create buffer that will hold the payload.
- view := buffer.NewView(30)
- _, _, err = ep.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{})
- if err != nil {
- t.Fatalf("write failed: %v", err)
- }
-
- fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)
-
- if fakeNet.sendPacketCount[2] != 1 {
- t.Errorf("sendPacketCount = %d, want %d", fakeNet.sendPacketCount[2], 1)
- }
-}
-
-func TestTransportOptions(t *testing.T) {
- s := stack.New([]string{"fakeNet"}, []string{"fakeTrans"}, stack.Options{})
-
- // Try an unsupported transport protocol.
- if err := s.SetTransportProtocolOption(tcpip.TransportProtocolNumber(99999), fakeTransportGoodOption(false)); err != tcpip.ErrUnknownProtocol {
- t.Fatalf("SetTransportProtocolOption(fakeTrans2, blah, false) = %v, want = tcpip.ErrUnknownProtocol", err)
- }
-
- testCases := []struct {
- option interface{}
- wantErr *tcpip.Error
- verifier func(t *testing.T, p stack.TransportProtocol)
- }{
- {fakeTransportGoodOption(true), nil, func(t *testing.T, p stack.TransportProtocol) {
- t.Helper()
- fakeTrans := p.(*fakeTransportProtocol)
- if fakeTrans.opts.good != true {
- t.Fatalf("fakeTrans.opts.good = false, want = true")
- }
- var v fakeTransportGoodOption
- if err := s.TransportProtocolOption(fakeTransNumber, &v); err != nil {
- t.Fatalf("s.TransportProtocolOption(fakeTransNumber, &v) = %v, want = nil, where v is option %T", v, err)
- }
- if v != true {
- t.Fatalf("s.TransportProtocolOption(fakeTransNumber, &v) returned v = %v, want = true", v)
- }
-
- }},
- {fakeTransportBadOption(true), tcpip.ErrUnknownProtocolOption, nil},
- {fakeTransportInvalidValueOption(1), tcpip.ErrInvalidOptionValue, nil},
- }
- for _, tc := range testCases {
- if got := s.SetTransportProtocolOption(fakeTransNumber, tc.option); got != tc.wantErr {
- t.Errorf("s.SetTransportProtocolOption(fakeTrans, %v) = %v, want = %v", tc.option, got, tc.wantErr)
- }
- if tc.verifier != nil {
- tc.verifier(t, s.TransportProtocolInstance(fakeTransNumber))
- }
- }
-}
-
-func TestTransportForwarding(t *testing.T) {
- s := stack.New([]string{"fakeNet"}, []string{"fakeTrans"}, stack.Options{})
- s.SetForwarding(true)
-
- // TODO(b/123449044): Change this to a channel NIC.
- ep1 := loopback.New()
- if err := s.CreateNIC(1, ep1); err != nil {
- t.Fatalf("CreateNIC #1 failed: %v", err)
- }
- if err := s.AddAddress(1, fakeNetNumber, "\x01"); err != nil {
- t.Fatalf("AddAddress #1 failed: %v", err)
- }
-
- ep2 := channel.New(10, defaultMTU, "")
- if err := s.CreateNIC(2, ep2); err != nil {
- t.Fatalf("CreateNIC #2 failed: %v", err)
- }
- if err := s.AddAddress(2, fakeNetNumber, "\x02"); err != nil {
- t.Fatalf("AddAddress #2 failed: %v", err)
- }
-
- // Route all packets to address 3 to NIC 2 and all packets to address
- // 1 to NIC 1.
- {
- subnet0, err := tcpip.NewSubnet("\x03", "\xff")
- if err != nil {
- t.Fatal(err)
- }
- subnet1, err := tcpip.NewSubnet("\x01", "\xff")
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable([]tcpip.Route{
- {Destination: subnet0, Gateway: "\x00", NIC: 2},
- {Destination: subnet1, Gateway: "\x00", NIC: 1},
- })
- }
-
- wq := waiter.Queue{}
- ep, err := s.NewEndpoint(fakeTransNumber, fakeNetNumber, &wq)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- if err := ep.Bind(tcpip.FullAddress{Addr: "\x01", NIC: 1}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- // Send a packet to address 1 from address 3.
- req := buffer.NewView(30)
- req[0] = 1
- req[1] = 3
- req[2] = byte(fakeTransNumber)
- ep2.Inject(fakeNetNumber, req.ToVectorisedView())
-
- aep, _, err := ep.Accept()
- if err != nil || aep == nil {
- t.Fatalf("Accept failed: %v, %v", aep, err)
- }
-
- resp := buffer.NewView(30)
- if _, _, err := aep.Write(tcpip.SlicePayload(resp), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
-
- var p channel.PacketInfo
- select {
- case p = <-ep2.C:
- default:
- t.Fatal("Response packet not forwarded")
- }
-
- if dst := p.Header[0]; dst != 3 {
- t.Errorf("Response packet has incorrect destination addresss: got = %d, want = 3", dst)
- }
- if src := p.Header[1]; src != 1 {
- t.Errorf("Response packet has incorrect source addresss: got = %d, want = 3", src)
- }
-}
-
-func init() {
- stack.RegisterTransportProtocolFactory("fakeTrans", func() stack.TransportProtocol {
- return &fakeTransportProtocol{}
- })
-}
diff --git a/pkg/tcpip/tcpip_state_autogen.go b/pkg/tcpip/tcpip_state_autogen.go
new file mode 100755
index 000000000..054f95858
--- /dev/null
+++ b/pkg/tcpip/tcpip_state_autogen.go
@@ -0,0 +1,44 @@
+// automatically generated by stateify.
+
+package tcpip
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *FullAddress) beforeSave() {}
+func (x *FullAddress) save(m state.Map) {
+ x.beforeSave()
+ m.Save("NIC", &x.NIC)
+ m.Save("Addr", &x.Addr)
+ m.Save("Port", &x.Port)
+}
+
+func (x *FullAddress) afterLoad() {}
+func (x *FullAddress) load(m state.Map) {
+ m.Load("NIC", &x.NIC)
+ m.Load("Addr", &x.Addr)
+ m.Load("Port", &x.Port)
+}
+
+func (x *ControlMessages) beforeSave() {}
+func (x *ControlMessages) save(m state.Map) {
+ x.beforeSave()
+ m.Save("HasTimestamp", &x.HasTimestamp)
+ m.Save("Timestamp", &x.Timestamp)
+ m.Save("HasInq", &x.HasInq)
+ m.Save("Inq", &x.Inq)
+}
+
+func (x *ControlMessages) afterLoad() {}
+func (x *ControlMessages) load(m state.Map) {
+ m.Load("HasTimestamp", &x.HasTimestamp)
+ m.Load("Timestamp", &x.Timestamp)
+ m.Load("HasInq", &x.HasInq)
+ m.Load("Inq", &x.Inq)
+}
+
+func init() {
+ state.Register("tcpip.FullAddress", (*FullAddress)(nil), state.Fns{Save: (*FullAddress).save, Load: (*FullAddress).load})
+ state.Register("tcpip.ControlMessages", (*ControlMessages)(nil), state.Fns{Save: (*ControlMessages).save, Load: (*ControlMessages).load})
+}
diff --git a/pkg/tcpip/tcpip_test.go b/pkg/tcpip/tcpip_test.go
deleted file mode 100644
index fb3a0a5ee..000000000
--- a/pkg/tcpip/tcpip_test.go
+++ /dev/null
@@ -1,197 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tcpip
-
-import (
- "fmt"
- "net"
- "strings"
- "testing"
-)
-
-func TestSubnetContains(t *testing.T) {
- tests := []struct {
- s Address
- m AddressMask
- a Address
- want bool
- }{
- {"\xa0", "\xf0", "\x90", false},
- {"\xa0", "\xf0", "\xa0", true},
- {"\xa0", "\xf0", "\xa5", true},
- {"\xa0", "\xf0", "\xaf", true},
- {"\xa0", "\xf0", "\xb0", false},
- {"\xa0", "\xf0", "", false},
- {"\xa0", "\xf0", "\xa0\x00", false},
- {"\xc2\x80", "\xff\xf0", "\xc2\x80", true},
- {"\xc2\x80", "\xff\xf0", "\xc2\x00", false},
- {"\xc2\x00", "\xff\xf0", "\xc2\x00", true},
- {"\xc2\x00", "\xff\xf0", "\xc2\x80", false},
- }
- for _, tt := range tests {
- s, err := NewSubnet(tt.s, tt.m)
- if err != nil {
- t.Errorf("NewSubnet(%v, %v) = %v", tt.s, tt.m, err)
- continue
- }
- if got := s.Contains(tt.a); got != tt.want {
- t.Errorf("Subnet(%v).Contains(%v) = %v, want %v", s, tt.a, got, tt.want)
- }
- }
-}
-
-func TestSubnetBits(t *testing.T) {
- tests := []struct {
- a AddressMask
- want1 int
- want0 int
- }{
- {"\x00", 0, 8},
- {"\x00\x00", 0, 16},
- {"\x36", 0, 8},
- {"\x5c", 0, 8},
- {"\x5c\x5c", 0, 16},
- {"\x5c\x36", 0, 16},
- {"\x36\x5c", 0, 16},
- {"\x36\x36", 0, 16},
- {"\xff", 8, 0},
- {"\xff\xff", 16, 0},
- }
- for _, tt := range tests {
- s := &Subnet{mask: tt.a}
- got1, got0 := s.Bits()
- if got1 != tt.want1 || got0 != tt.want0 {
- t.Errorf("Subnet{mask: %x}.Bits() = %d, %d, want %d, %d", tt.a, got1, got0, tt.want1, tt.want0)
- }
- }
-}
-
-func TestSubnetPrefix(t *testing.T) {
- tests := []struct {
- a AddressMask
- want int
- }{
- {"\x00", 0},
- {"\x00\x00", 0},
- {"\x36", 0},
- {"\x86", 1},
- {"\xc5", 2},
- {"\xff\x00", 8},
- {"\xff\x36", 8},
- {"\xff\x8c", 9},
- {"\xff\xc8", 10},
- {"\xff", 8},
- {"\xff\xff", 16},
- }
- for _, tt := range tests {
- s := &Subnet{mask: tt.a}
- got := s.Prefix()
- if got != tt.want {
- t.Errorf("Subnet{mask: %x}.Bits() = %d want %d", tt.a, got, tt.want)
- }
- }
-}
-
-func TestSubnetCreation(t *testing.T) {
- tests := []struct {
- a Address
- m AddressMask
- want error
- }{
- {"\xa0", "\xf0", nil},
- {"\xa0\xa0", "\xf0", errSubnetLengthMismatch},
- {"\xaa", "\xf0", errSubnetAddressMasked},
- {"", "", nil},
- }
- for _, tt := range tests {
- if _, err := NewSubnet(tt.a, tt.m); err != tt.want {
- t.Errorf("NewSubnet(%v, %v) = %v, want %v", tt.a, tt.m, err, tt.want)
- }
- }
-}
-
-func TestAddressString(t *testing.T) {
- for _, want := range []string{
- // Taken from stdlib.
- "2001:db8::123:12:1",
- "2001:db8::1",
- "2001:db8:0:1:0:1:0:1",
- "2001:db8:1:0:1:0:1:0",
- "2001::1:0:0:1",
- "2001:db8:0:0:1::",
- "2001:db8::1:0:0:1",
- "2001:db8::a:b:c:d",
-
- // Leading zeros.
- "::1",
- // Trailing zeros.
- "8::",
- // No zeros.
- "1:1:1:1:1:1:1:1",
- // Longer sequence is after other zeros, but not at the end.
- "1:0:0:1::1",
- // Longer sequence is at the beginning, shorter sequence is at
- // the end.
- "::1:1:1:0:0",
- // Longer sequence is not at the beginning, shorter sequence is
- // at the end.
- "1::1:1:0:0",
- // Longer sequence is at the beginning, shorter sequence is not
- // at the end.
- "::1:1:0:0:1",
- // Neither sequence is at an end, longer is after shorter.
- "1:0:0:1::1",
- // Shorter sequence is at the beginning, longer sequence is not
- // at the end.
- "0:0:1:1::1",
- // Shorter sequence is at the beginning, longer sequence is at
- // the end.
- "0:0:1:1:1::",
- // Short sequences at both ends, longer one in the middle.
- "0:1:1::1:1:0",
- // Short sequences at both ends, longer one in the middle.
- "0:1::1:0:0",
- // Short sequences at both ends, longer one in the middle.
- "0:0:1::1:0",
- // Longer sequence surrounded by shorter sequences, but none at
- // the end.
- "1:0:1::1:0:1",
- } {
- addr := Address(net.ParseIP(want))
- if got := addr.String(); got != want {
- t.Errorf("Address(%x).String() = '%s', want = '%s'", addr, got, want)
- }
- }
-}
-
-func TestStatsString(t *testing.T) {
- got := fmt.Sprintf("%+v", Stats{}.FillIn())
-
- matchers := []string{
- // Print root-level stats correctly.
- "UnknownProtocolRcvdPackets:0",
- // Print protocol-specific stats correctly.
- "TCP:{ActiveConnectionOpenings:0",
- }
-
- for _, m := range matchers {
- if !strings.Contains(got, m) {
- t.Errorf("string.Contains(got, %q) = false", m)
- }
- }
- if t.Failed() {
- t.Logf(`got = fmt.Sprintf("%%+v", Stats{}.FillIn()) = %q`, got)
- }
-}
diff --git a/pkg/tcpip/time.s b/pkg/tcpip/time.s
deleted file mode 100644
index fb37360ac..000000000
--- a/pkg/tcpip/time.s
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Empty assembly file so empty func definitions work.
diff --git a/pkg/tcpip/transport/icmp/BUILD b/pkg/tcpip/transport/icmp/BUILD
deleted file mode 100644
index d78a162b8..000000000
--- a/pkg/tcpip/transport/icmp/BUILD
+++ /dev/null
@@ -1,48 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_template_instance(
- name = "icmp_packet_list",
- out = "icmp_packet_list.go",
- package = "icmp",
- prefix = "icmpPacket",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*icmpPacket",
- "Linker": "*icmpPacket",
- },
-)
-
-go_library(
- name = "icmp",
- srcs = [
- "endpoint.go",
- "endpoint_state.go",
- "icmp_packet_list.go",
- "protocol.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/transport/icmp",
- imports = ["gvisor.dev/gvisor/pkg/tcpip/buffer"],
- visibility = ["//visibility:public"],
- deps = [
- "//pkg/sleep",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/iptables",
- "//pkg/tcpip/stack",
- "//pkg/tcpip/transport/raw",
- "//pkg/tcpip/transport/tcp",
- "//pkg/waiter",
- ],
-)
-
-filegroup(
- name = "autogen",
- srcs = [
- "icmp_packet_list.go",
- ],
- visibility = ["//:sandbox"],
-)
diff --git a/pkg/tcpip/transport/icmp/icmp_packet_list.go b/pkg/tcpip/transport/icmp/icmp_packet_list.go
new file mode 100755
index 000000000..1b35e5b4a
--- /dev/null
+++ b/pkg/tcpip/transport/icmp/icmp_packet_list.go
@@ -0,0 +1,173 @@
+package icmp
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type icmpPacketElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (icmpPacketElementMapper) linkerFor(elem *icmpPacket) *icmpPacket { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type icmpPacketList struct {
+ head *icmpPacket
+ tail *icmpPacket
+}
+
+// Reset resets list l to the empty state.
+func (l *icmpPacketList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *icmpPacketList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *icmpPacketList) Front() *icmpPacket {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *icmpPacketList) Back() *icmpPacket {
+ return l.tail
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *icmpPacketList) PushFront(e *icmpPacket) {
+ icmpPacketElementMapper{}.linkerFor(e).SetNext(l.head)
+ icmpPacketElementMapper{}.linkerFor(e).SetPrev(nil)
+
+ if l.head != nil {
+ icmpPacketElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *icmpPacketList) PushBack(e *icmpPacket) {
+ icmpPacketElementMapper{}.linkerFor(e).SetNext(nil)
+ icmpPacketElementMapper{}.linkerFor(e).SetPrev(l.tail)
+
+ if l.tail != nil {
+ icmpPacketElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *icmpPacketList) PushBackList(m *icmpPacketList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ icmpPacketElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ icmpPacketElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *icmpPacketList) InsertAfter(b, e *icmpPacket) {
+ a := icmpPacketElementMapper{}.linkerFor(b).Next()
+ icmpPacketElementMapper{}.linkerFor(e).SetNext(a)
+ icmpPacketElementMapper{}.linkerFor(e).SetPrev(b)
+ icmpPacketElementMapper{}.linkerFor(b).SetNext(e)
+
+ if a != nil {
+ icmpPacketElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *icmpPacketList) InsertBefore(a, e *icmpPacket) {
+ b := icmpPacketElementMapper{}.linkerFor(a).Prev()
+ icmpPacketElementMapper{}.linkerFor(e).SetNext(a)
+ icmpPacketElementMapper{}.linkerFor(e).SetPrev(b)
+ icmpPacketElementMapper{}.linkerFor(a).SetPrev(e)
+
+ if b != nil {
+ icmpPacketElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *icmpPacketList) Remove(e *icmpPacket) {
+ prev := icmpPacketElementMapper{}.linkerFor(e).Prev()
+ next := icmpPacketElementMapper{}.linkerFor(e).Next()
+
+ if prev != nil {
+ icmpPacketElementMapper{}.linkerFor(prev).SetNext(next)
+ } else {
+ l.head = next
+ }
+
+ if next != nil {
+ icmpPacketElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else {
+ l.tail = prev
+ }
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type icmpPacketEntry struct {
+ next *icmpPacket
+ prev *icmpPacket
+}
+
+// Next returns the entry that follows e in the list.
+func (e *icmpPacketEntry) Next() *icmpPacket {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *icmpPacketEntry) Prev() *icmpPacket {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *icmpPacketEntry) SetNext(elem *icmpPacket) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *icmpPacketEntry) SetPrev(elem *icmpPacket) {
+ e.prev = elem
+}
diff --git a/pkg/tcpip/transport/icmp/icmp_state_autogen.go b/pkg/tcpip/transport/icmp/icmp_state_autogen.go
new file mode 100755
index 000000000..5caa1cd64
--- /dev/null
+++ b/pkg/tcpip/transport/icmp/icmp_state_autogen.go
@@ -0,0 +1,98 @@
+// automatically generated by stateify.
+
+package icmp
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+ "gvisor.dev/gvisor/pkg/tcpip/buffer"
+)
+
+func (x *icmpPacket) beforeSave() {}
+func (x *icmpPacket) save(m state.Map) {
+ x.beforeSave()
+ var data buffer.VectorisedView = x.saveData()
+ m.SaveValue("data", data)
+ m.Save("icmpPacketEntry", &x.icmpPacketEntry)
+ m.Save("senderAddress", &x.senderAddress)
+ m.Save("timestamp", &x.timestamp)
+}
+
+func (x *icmpPacket) afterLoad() {}
+func (x *icmpPacket) load(m state.Map) {
+ m.Load("icmpPacketEntry", &x.icmpPacketEntry)
+ m.Load("senderAddress", &x.senderAddress)
+ m.Load("timestamp", &x.timestamp)
+ m.LoadValue("data", new(buffer.VectorisedView), func(y interface{}) { x.loadData(y.(buffer.VectorisedView)) })
+}
+
+func (x *endpoint) save(m state.Map) {
+ x.beforeSave()
+ var rcvBufSizeMax int = x.saveRcvBufSizeMax()
+ m.SaveValue("rcvBufSizeMax", rcvBufSizeMax)
+ m.Save("netProto", &x.netProto)
+ m.Save("transProto", &x.transProto)
+ m.Save("waiterQueue", &x.waiterQueue)
+ m.Save("rcvReady", &x.rcvReady)
+ m.Save("rcvList", &x.rcvList)
+ m.Save("rcvBufSize", &x.rcvBufSize)
+ m.Save("rcvClosed", &x.rcvClosed)
+ m.Save("sndBufSize", &x.sndBufSize)
+ m.Save("shutdownFlags", &x.shutdownFlags)
+ m.Save("id", &x.id)
+ m.Save("state", &x.state)
+ m.Save("bindNICID", &x.bindNICID)
+ m.Save("bindAddr", &x.bindAddr)
+ m.Save("regNICID", &x.regNICID)
+}
+
+func (x *endpoint) load(m state.Map) {
+ m.Load("netProto", &x.netProto)
+ m.Load("transProto", &x.transProto)
+ m.Load("waiterQueue", &x.waiterQueue)
+ m.Load("rcvReady", &x.rcvReady)
+ m.Load("rcvList", &x.rcvList)
+ m.Load("rcvBufSize", &x.rcvBufSize)
+ m.Load("rcvClosed", &x.rcvClosed)
+ m.Load("sndBufSize", &x.sndBufSize)
+ m.Load("shutdownFlags", &x.shutdownFlags)
+ m.Load("id", &x.id)
+ m.Load("state", &x.state)
+ m.Load("bindNICID", &x.bindNICID)
+ m.Load("bindAddr", &x.bindAddr)
+ m.Load("regNICID", &x.regNICID)
+ m.LoadValue("rcvBufSizeMax", new(int), func(y interface{}) { x.loadRcvBufSizeMax(y.(int)) })
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *icmpPacketList) beforeSave() {}
+func (x *icmpPacketList) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *icmpPacketList) afterLoad() {}
+func (x *icmpPacketList) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *icmpPacketEntry) beforeSave() {}
+func (x *icmpPacketEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *icmpPacketEntry) afterLoad() {}
+func (x *icmpPacketEntry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func init() {
+ state.Register("icmp.icmpPacket", (*icmpPacket)(nil), state.Fns{Save: (*icmpPacket).save, Load: (*icmpPacket).load})
+ state.Register("icmp.endpoint", (*endpoint)(nil), state.Fns{Save: (*endpoint).save, Load: (*endpoint).load})
+ state.Register("icmp.icmpPacketList", (*icmpPacketList)(nil), state.Fns{Save: (*icmpPacketList).save, Load: (*icmpPacketList).load})
+ state.Register("icmp.icmpPacketEntry", (*icmpPacketEntry)(nil), state.Fns{Save: (*icmpPacketEntry).save, Load: (*icmpPacketEntry).load})
+}
diff --git a/pkg/tcpip/transport/raw/BUILD b/pkg/tcpip/transport/raw/BUILD
deleted file mode 100644
index 7241f6c19..000000000
--- a/pkg/tcpip/transport/raw/BUILD
+++ /dev/null
@@ -1,47 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_template_instance(
- name = "packet_list",
- out = "packet_list.go",
- package = "raw",
- prefix = "packet",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*packet",
- "Linker": "*packet",
- },
-)
-
-go_library(
- name = "raw",
- srcs = [
- "endpoint.go",
- "endpoint_state.go",
- "packet_list.go",
- "protocol.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/transport/raw",
- imports = ["gvisor.dev/gvisor/pkg/tcpip/buffer"],
- visibility = ["//visibility:public"],
- deps = [
- "//pkg/log",
- "//pkg/sleep",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/iptables",
- "//pkg/tcpip/stack",
- "//pkg/waiter",
- ],
-)
-
-filegroup(
- name = "autogen",
- srcs = [
- "packet_list.go",
- ],
- visibility = ["//:sandbox"],
-)
diff --git a/pkg/tcpip/transport/raw/packet_list.go b/pkg/tcpip/transport/raw/packet_list.go
new file mode 100755
index 000000000..2e9074934
--- /dev/null
+++ b/pkg/tcpip/transport/raw/packet_list.go
@@ -0,0 +1,173 @@
+package raw
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type packetElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (packetElementMapper) linkerFor(elem *packet) *packet { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type packetList struct {
+ head *packet
+ tail *packet
+}
+
+// Reset resets list l to the empty state.
+func (l *packetList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *packetList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *packetList) Front() *packet {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *packetList) Back() *packet {
+ return l.tail
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *packetList) PushFront(e *packet) {
+ packetElementMapper{}.linkerFor(e).SetNext(l.head)
+ packetElementMapper{}.linkerFor(e).SetPrev(nil)
+
+ if l.head != nil {
+ packetElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *packetList) PushBack(e *packet) {
+ packetElementMapper{}.linkerFor(e).SetNext(nil)
+ packetElementMapper{}.linkerFor(e).SetPrev(l.tail)
+
+ if l.tail != nil {
+ packetElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *packetList) PushBackList(m *packetList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ packetElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ packetElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *packetList) InsertAfter(b, e *packet) {
+ a := packetElementMapper{}.linkerFor(b).Next()
+ packetElementMapper{}.linkerFor(e).SetNext(a)
+ packetElementMapper{}.linkerFor(e).SetPrev(b)
+ packetElementMapper{}.linkerFor(b).SetNext(e)
+
+ if a != nil {
+ packetElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *packetList) InsertBefore(a, e *packet) {
+ b := packetElementMapper{}.linkerFor(a).Prev()
+ packetElementMapper{}.linkerFor(e).SetNext(a)
+ packetElementMapper{}.linkerFor(e).SetPrev(b)
+ packetElementMapper{}.linkerFor(a).SetPrev(e)
+
+ if b != nil {
+ packetElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *packetList) Remove(e *packet) {
+ prev := packetElementMapper{}.linkerFor(e).Prev()
+ next := packetElementMapper{}.linkerFor(e).Next()
+
+ if prev != nil {
+ packetElementMapper{}.linkerFor(prev).SetNext(next)
+ } else {
+ l.head = next
+ }
+
+ if next != nil {
+ packetElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else {
+ l.tail = prev
+ }
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type packetEntry struct {
+ next *packet
+ prev *packet
+}
+
+// Next returns the entry that follows e in the list.
+func (e *packetEntry) Next() *packet {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *packetEntry) Prev() *packet {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *packetEntry) SetNext(elem *packet) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *packetEntry) SetPrev(elem *packet) {
+ e.prev = elem
+}
diff --git a/pkg/tcpip/transport/raw/raw_state_autogen.go b/pkg/tcpip/transport/raw/raw_state_autogen.go
new file mode 100755
index 000000000..947fcc1bb
--- /dev/null
+++ b/pkg/tcpip/transport/raw/raw_state_autogen.go
@@ -0,0 +1,98 @@
+// automatically generated by stateify.
+
+package raw
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+ "gvisor.dev/gvisor/pkg/tcpip/buffer"
+)
+
+func (x *packet) beforeSave() {}
+func (x *packet) save(m state.Map) {
+ x.beforeSave()
+ var data buffer.VectorisedView = x.saveData()
+ m.SaveValue("data", data)
+ m.Save("packetEntry", &x.packetEntry)
+ m.Save("timestampNS", &x.timestampNS)
+ m.Save("senderAddr", &x.senderAddr)
+}
+
+func (x *packet) afterLoad() {}
+func (x *packet) load(m state.Map) {
+ m.Load("packetEntry", &x.packetEntry)
+ m.Load("timestampNS", &x.timestampNS)
+ m.Load("senderAddr", &x.senderAddr)
+ m.LoadValue("data", new(buffer.VectorisedView), func(y interface{}) { x.loadData(y.(buffer.VectorisedView)) })
+}
+
+func (x *endpoint) save(m state.Map) {
+ x.beforeSave()
+ var rcvBufSizeMax int = x.saveRcvBufSizeMax()
+ m.SaveValue("rcvBufSizeMax", rcvBufSizeMax)
+ m.Save("netProto", &x.netProto)
+ m.Save("transProto", &x.transProto)
+ m.Save("waiterQueue", &x.waiterQueue)
+ m.Save("associated", &x.associated)
+ m.Save("rcvList", &x.rcvList)
+ m.Save("rcvBufSize", &x.rcvBufSize)
+ m.Save("rcvClosed", &x.rcvClosed)
+ m.Save("sndBufSize", &x.sndBufSize)
+ m.Save("closed", &x.closed)
+ m.Save("connected", &x.connected)
+ m.Save("bound", &x.bound)
+ m.Save("registeredNIC", &x.registeredNIC)
+ m.Save("boundNIC", &x.boundNIC)
+ m.Save("boundAddr", &x.boundAddr)
+}
+
+func (x *endpoint) load(m state.Map) {
+ m.Load("netProto", &x.netProto)
+ m.Load("transProto", &x.transProto)
+ m.Load("waiterQueue", &x.waiterQueue)
+ m.Load("associated", &x.associated)
+ m.Load("rcvList", &x.rcvList)
+ m.Load("rcvBufSize", &x.rcvBufSize)
+ m.Load("rcvClosed", &x.rcvClosed)
+ m.Load("sndBufSize", &x.sndBufSize)
+ m.Load("closed", &x.closed)
+ m.Load("connected", &x.connected)
+ m.Load("bound", &x.bound)
+ m.Load("registeredNIC", &x.registeredNIC)
+ m.Load("boundNIC", &x.boundNIC)
+ m.Load("boundAddr", &x.boundAddr)
+ m.LoadValue("rcvBufSizeMax", new(int), func(y interface{}) { x.loadRcvBufSizeMax(y.(int)) })
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *packetList) beforeSave() {}
+func (x *packetList) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *packetList) afterLoad() {}
+func (x *packetList) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *packetEntry) beforeSave() {}
+func (x *packetEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *packetEntry) afterLoad() {}
+func (x *packetEntry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func init() {
+ state.Register("raw.packet", (*packet)(nil), state.Fns{Save: (*packet).save, Load: (*packet).load})
+ state.Register("raw.endpoint", (*endpoint)(nil), state.Fns{Save: (*endpoint).save, Load: (*endpoint).load})
+ state.Register("raw.packetList", (*packetList)(nil), state.Fns{Save: (*packetList).save, Load: (*packetList).load})
+ state.Register("raw.packetEntry", (*packetEntry)(nil), state.Fns{Save: (*packetEntry).save, Load: (*packetEntry).load})
+}
diff --git a/pkg/tcpip/transport/tcp/BUILD b/pkg/tcpip/transport/tcp/BUILD
deleted file mode 100644
index 39a839ab7..000000000
--- a/pkg/tcpip/transport/tcp/BUILD
+++ /dev/null
@@ -1,100 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_template_instance(
- name = "tcp_segment_list",
- out = "tcp_segment_list.go",
- package = "tcp",
- prefix = "segment",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*segment",
- "Linker": "*segment",
- },
-)
-
-go_library(
- name = "tcp",
- srcs = [
- "accept.go",
- "connect.go",
- "cubic.go",
- "cubic_state.go",
- "endpoint.go",
- "endpoint_state.go",
- "forwarder.go",
- "protocol.go",
- "rcv.go",
- "reno.go",
- "sack.go",
- "sack_scoreboard.go",
- "segment.go",
- "segment_heap.go",
- "segment_queue.go",
- "segment_state.go",
- "snd.go",
- "snd_state.go",
- "tcp_segment_list.go",
- "timer.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/transport/tcp",
- imports = ["gvisor.dev/gvisor/pkg/tcpip/buffer"],
- visibility = ["//visibility:public"],
- deps = [
- "//pkg/rand",
- "//pkg/sleep",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/iptables",
- "//pkg/tcpip/seqnum",
- "//pkg/tcpip/stack",
- "//pkg/tcpip/transport/raw",
- "//pkg/tmutex",
- "//pkg/waiter",
- "@com_github_google_btree//:go_default_library",
- ],
-)
-
-filegroup(
- name = "autogen",
- srcs = [
- "tcp_segment_list.go",
- ],
- visibility = ["//:sandbox"],
-)
-
-go_test(
- name = "tcp_test",
- size = "small",
- srcs = [
- "dual_stack_test.go",
- "sack_scoreboard_test.go",
- "tcp_noracedetector_test.go",
- "tcp_sack_test.go",
- "tcp_test.go",
- "tcp_timestamp_test.go",
- ],
- # FIXME(b/68809571)
- tags = ["flaky"],
- deps = [
- ":tcp",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/checker",
- "//pkg/tcpip/header",
- "//pkg/tcpip/link/loopback",
- "//pkg/tcpip/link/sniffer",
- "//pkg/tcpip/network/ipv4",
- "//pkg/tcpip/network/ipv6",
- "//pkg/tcpip/ports",
- "//pkg/tcpip/seqnum",
- "//pkg/tcpip/stack",
- "//pkg/tcpip/transport/tcp/testing/context",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/tcpip/transport/tcp/dual_stack_test.go b/pkg/tcpip/transport/tcp/dual_stack_test.go
deleted file mode 100644
index c54610a87..000000000
--- a/pkg/tcpip/transport/tcp/dual_stack_test.go
+++ /dev/null
@@ -1,658 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tcp_test
-
-import (
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/checker"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
- "gvisor.dev/gvisor/pkg/tcpip/seqnum"
- "gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
- "gvisor.dev/gvisor/pkg/tcpip/transport/tcp/testing/context"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-func TestV4MappedConnectOnV6Only(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateV6Endpoint(true)
-
- // Start connection attempt, it must fail.
- err := c.EP.Connect(tcpip.FullAddress{Addr: context.TestV4MappedAddr, Port: context.TestPort})
- if err != tcpip.ErrNoRoute {
- t.Fatalf("Unexpected return value from Connect: %v", err)
- }
-}
-
-func testV4Connect(t *testing.T, c *context.Context) {
- // Start connection attempt.
- we, ch := waiter.NewChannelEntry(nil)
- c.WQ.EventRegister(&we, waiter.EventOut)
- defer c.WQ.EventUnregister(&we)
-
- err := c.EP.Connect(tcpip.FullAddress{Addr: context.TestV4MappedAddr, Port: context.TestPort})
- if err != tcpip.ErrConnectStarted {
- t.Fatalf("Unexpected return value from Connect: %v", err)
- }
-
- // Receive SYN packet.
- b := c.GetPacket()
- checker.IPv4(t, b,
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.TCPFlags(header.TCPFlagSyn),
- ),
- )
-
- tcp := header.TCP(header.IPv4(b).Payload())
- c.IRS = seqnum.Value(tcp.SequenceNumber())
-
- iss := seqnum.Value(789)
- c.SendPacket(nil, &context.Headers{
- SrcPort: tcp.DestinationPort(),
- DstPort: tcp.SourcePort(),
- Flags: header.TCPFlagSyn | header.TCPFlagAck,
- SeqNum: iss,
- AckNum: c.IRS.Add(1),
- RcvWnd: 30000,
- })
-
- // Receive ACK packet.
- checker.IPv4(t, c.GetPacket(),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.TCPFlags(header.TCPFlagAck),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(uint32(iss)+1),
- ),
- )
-
- // Wait for connection to be established.
- select {
- case <-ch:
- err = c.EP.GetSockOpt(tcpip.ErrorOption{})
- if err != nil {
- t.Fatalf("Unexpected error when connecting: %v", err)
- }
- case <-time.After(1 * time.Second):
- t.Fatalf("Timed out waiting for connection")
- }
-}
-
-func TestV4MappedConnect(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateV6Endpoint(false)
-
- // Test the connection request.
- testV4Connect(t, c)
-}
-
-func TestV4ConnectWhenBoundToWildcard(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateV6Endpoint(false)
-
- // Bind to wildcard.
- if err := c.EP.Bind(tcpip.FullAddress{}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- // Test the connection request.
- testV4Connect(t, c)
-}
-
-func TestV4ConnectWhenBoundToV4MappedWildcard(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateV6Endpoint(false)
-
- // Bind to v4 mapped wildcard.
- if err := c.EP.Bind(tcpip.FullAddress{Addr: context.V4MappedWildcardAddr}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- // Test the connection request.
- testV4Connect(t, c)
-}
-
-func TestV4ConnectWhenBoundToV4Mapped(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateV6Endpoint(false)
-
- // Bind to v4 mapped address.
- if err := c.EP.Bind(tcpip.FullAddress{Addr: context.StackV4MappedAddr}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- // Test the connection request.
- testV4Connect(t, c)
-}
-
-func testV6Connect(t *testing.T, c *context.Context) {
- // Start connection attempt to IPv6 address.
- we, ch := waiter.NewChannelEntry(nil)
- c.WQ.EventRegister(&we, waiter.EventOut)
- defer c.WQ.EventUnregister(&we)
-
- err := c.EP.Connect(tcpip.FullAddress{Addr: context.TestV6Addr, Port: context.TestPort})
- if err != tcpip.ErrConnectStarted {
- t.Fatalf("Unexpected return value from Connect: %v", err)
- }
-
- // Receive SYN packet.
- b := c.GetV6Packet()
- checker.IPv6(t, b,
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.TCPFlags(header.TCPFlagSyn),
- ),
- )
-
- tcp := header.TCP(header.IPv6(b).Payload())
- c.IRS = seqnum.Value(tcp.SequenceNumber())
-
- iss := seqnum.Value(789)
- c.SendV6Packet(nil, &context.Headers{
- SrcPort: tcp.DestinationPort(),
- DstPort: tcp.SourcePort(),
- Flags: header.TCPFlagSyn | header.TCPFlagAck,
- SeqNum: iss,
- AckNum: c.IRS.Add(1),
- RcvWnd: 30000,
- })
-
- // Receive ACK packet.
- checker.IPv6(t, c.GetV6Packet(),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.TCPFlags(header.TCPFlagAck),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(uint32(iss)+1),
- ),
- )
-
- // Wait for connection to be established.
- select {
- case <-ch:
- err = c.EP.GetSockOpt(tcpip.ErrorOption{})
- if err != nil {
- t.Fatalf("Unexpected error when connecting: %v", err)
- }
- case <-time.After(1 * time.Second):
- t.Fatalf("Timed out waiting for connection")
- }
-}
-
-func TestV6Connect(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateV6Endpoint(false)
-
- // Test the connection request.
- testV6Connect(t, c)
-}
-
-func TestV6ConnectV6Only(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateV6Endpoint(true)
-
- // Test the connection request.
- testV6Connect(t, c)
-}
-
-func TestV6ConnectWhenBoundToWildcard(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateV6Endpoint(false)
-
- // Bind to wildcard.
- if err := c.EP.Bind(tcpip.FullAddress{}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- // Test the connection request.
- testV6Connect(t, c)
-}
-
-func TestV6ConnectWhenBoundToLocalAddress(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateV6Endpoint(false)
-
- // Bind to local address.
- if err := c.EP.Bind(tcpip.FullAddress{Addr: context.StackV6Addr}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- // Test the connection request.
- testV6Connect(t, c)
-}
-
-func TestV4RefuseOnV6Only(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateV6Endpoint(true)
-
- // Bind to wildcard.
- if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- // Start listening.
- if err := c.EP.Listen(10); err != nil {
- t.Fatalf("Listen failed: %v", err)
- }
-
- // Send a SYN request.
- irs := seqnum.Value(789)
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: context.StackPort,
- Flags: header.TCPFlagSyn,
- SeqNum: irs,
- RcvWnd: 30000,
- })
-
- // Receive the RST reply.
- checker.IPv4(t, c.GetPacket(),
- checker.TCP(
- checker.SrcPort(context.StackPort),
- checker.DstPort(context.TestPort),
- checker.TCPFlags(header.TCPFlagRst|header.TCPFlagAck),
- checker.AckNum(uint32(irs)+1),
- ),
- )
-}
-
-func TestV6RefuseOnBoundToV4Mapped(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateV6Endpoint(false)
-
- // Bind and listen.
- if err := c.EP.Bind(tcpip.FullAddress{Addr: context.V4MappedWildcardAddr, Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- if err := c.EP.Listen(10); err != nil {
- t.Fatalf("Listen failed: %v", err)
- }
-
- // Send a SYN request.
- irs := seqnum.Value(789)
- c.SendV6Packet(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: context.StackPort,
- Flags: header.TCPFlagSyn,
- SeqNum: irs,
- RcvWnd: 30000,
- })
-
- // Receive the RST reply.
- checker.IPv6(t, c.GetV6Packet(),
- checker.TCP(
- checker.SrcPort(context.StackPort),
- checker.DstPort(context.TestPort),
- checker.TCPFlags(header.TCPFlagRst|header.TCPFlagAck),
- checker.AckNum(uint32(irs)+1),
- ),
- )
-}
-
-func testV4Accept(t *testing.T, c *context.Context) {
- c.SetGSOEnabled(true)
- defer c.SetGSOEnabled(false)
-
- // Start listening.
- if err := c.EP.Listen(10); err != nil {
- t.Fatalf("Listen failed: %v", err)
- }
-
- // Send a SYN request.
- irs := seqnum.Value(789)
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: context.StackPort,
- Flags: header.TCPFlagSyn,
- SeqNum: irs,
- RcvWnd: 30000,
- })
-
- // Receive the SYN-ACK reply.
- b := c.GetPacket()
- tcp := header.TCP(header.IPv4(b).Payload())
- iss := seqnum.Value(tcp.SequenceNumber())
- checker.IPv4(t, b,
- checker.TCP(
- checker.SrcPort(context.StackPort),
- checker.DstPort(context.TestPort),
- checker.TCPFlags(header.TCPFlagAck|header.TCPFlagSyn),
- checker.AckNum(uint32(irs)+1),
- ),
- )
-
- // Send ACK.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: context.StackPort,
- Flags: header.TCPFlagAck,
- SeqNum: irs + 1,
- AckNum: iss + 1,
- RcvWnd: 30000,
- })
-
- // Try to accept the connection.
- we, ch := waiter.NewChannelEntry(nil)
- c.WQ.EventRegister(&we, waiter.EventIn)
- defer c.WQ.EventUnregister(&we)
-
- nep, _, err := c.EP.Accept()
- if err == tcpip.ErrWouldBlock {
- // Wait for connection to be established.
- select {
- case <-ch:
- nep, _, err = c.EP.Accept()
- if err != nil {
- t.Fatalf("Accept failed: %v", err)
- }
-
- case <-time.After(1 * time.Second):
- t.Fatalf("Timed out waiting for accept")
- }
- }
-
- // Make sure we get the same error when calling the original ep and the
- // new one. This validates that v4-mapped endpoints are still able to
- // query the V6Only flag, whereas pure v4 endpoints are not.
- var v tcpip.V6OnlyOption
- expected := c.EP.GetSockOpt(&v)
- if err := nep.GetSockOpt(&v); err != expected {
- t.Fatalf("GetSockOpt returned unexpected value: got %v, want %v", err, expected)
- }
-
- // Check the peer address.
- addr, err := nep.GetRemoteAddress()
- if err != nil {
- t.Fatalf("GetRemoteAddress failed failed: %v", err)
- }
-
- if addr.Addr != context.TestAddr {
- t.Fatalf("Unexpected remote address: got %v, want %v", addr.Addr, context.TestAddr)
- }
-
- data := "Don't panic"
- nep.Write(tcpip.SlicePayload(buffer.NewViewFromBytes([]byte(data))), tcpip.WriteOptions{})
- b = c.GetPacket()
- tcp = header.TCP(header.IPv4(b).Payload())
- if string(tcp.Payload()) != data {
- t.Fatalf("Unexpected data: got %v, want %v", string(tcp.Payload()), data)
- }
-}
-
-func TestV4AcceptOnV6(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateV6Endpoint(false)
-
- // Bind to wildcard.
- if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- // Test acceptance.
- testV4Accept(t, c)
-}
-
-func TestV4AcceptOnBoundToV4MappedWildcard(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateV6Endpoint(false)
-
- // Bind to v4 mapped wildcard.
- if err := c.EP.Bind(tcpip.FullAddress{Addr: context.V4MappedWildcardAddr, Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- // Test acceptance.
- testV4Accept(t, c)
-}
-
-func TestV4AcceptOnBoundToV4Mapped(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateV6Endpoint(false)
-
- // Bind and listen.
- if err := c.EP.Bind(tcpip.FullAddress{Addr: context.StackV4MappedAddr, Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- // Test acceptance.
- testV4Accept(t, c)
-}
-
-func TestV6AcceptOnV6(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateV6Endpoint(false)
-
- // Bind and listen.
- if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- if err := c.EP.Listen(10); err != nil {
- t.Fatalf("Listen failed: %v", err)
- }
-
- // Send a SYN request.
- irs := seqnum.Value(789)
- c.SendV6Packet(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: context.StackPort,
- Flags: header.TCPFlagSyn,
- SeqNum: irs,
- RcvWnd: 30000,
- })
-
- // Receive the SYN-ACK reply.
- b := c.GetV6Packet()
- tcp := header.TCP(header.IPv6(b).Payload())
- iss := seqnum.Value(tcp.SequenceNumber())
- checker.IPv6(t, b,
- checker.TCP(
- checker.SrcPort(context.StackPort),
- checker.DstPort(context.TestPort),
- checker.TCPFlags(header.TCPFlagAck|header.TCPFlagSyn),
- checker.AckNum(uint32(irs)+1),
- ),
- )
-
- // Send ACK.
- c.SendV6Packet(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: context.StackPort,
- Flags: header.TCPFlagAck,
- SeqNum: irs + 1,
- AckNum: iss + 1,
- RcvWnd: 30000,
- })
-
- // Try to accept the connection.
- we, ch := waiter.NewChannelEntry(nil)
- c.WQ.EventRegister(&we, waiter.EventIn)
- defer c.WQ.EventUnregister(&we)
-
- nep, _, err := c.EP.Accept()
- if err == tcpip.ErrWouldBlock {
- // Wait for connection to be established.
- select {
- case <-ch:
- nep, _, err = c.EP.Accept()
- if err != nil {
- t.Fatalf("Accept failed: %v", err)
- }
-
- case <-time.After(1 * time.Second):
- t.Fatalf("Timed out waiting for accept")
- }
- }
-
- // Make sure we can still query the v6 only status of the new endpoint,
- // that is, that it is in fact a v6 socket.
- var v tcpip.V6OnlyOption
- if err := nep.GetSockOpt(&v); err != nil {
- t.Fatalf("GetSockOpt failed failed: %v", err)
- }
-
- // Check the peer address.
- addr, err := nep.GetRemoteAddress()
- if err != nil {
- t.Fatalf("GetRemoteAddress failed failed: %v", err)
- }
-
- if addr.Addr != context.TestV6Addr {
- t.Fatalf("Unexpected remote address: got %v, want %v", addr.Addr, context.TestV6Addr)
- }
-}
-
-func TestV4AcceptOnV4(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- // Create TCP endpoint.
- var err *tcpip.Error
- c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- // Bind to wildcard.
- if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- // Test acceptance.
- testV4Accept(t, c)
-}
-
-func testV4ListenClose(t *testing.T, c *context.Context) {
- // Set the SynRcvd threshold to zero to force a syn cookie based accept
- // to happen.
- saved := tcp.SynRcvdCountThreshold
- defer func() {
- tcp.SynRcvdCountThreshold = saved
- }()
- tcp.SynRcvdCountThreshold = 0
- const n = uint16(32)
-
- // Start listening.
- if err := c.EP.Listen(int(tcp.SynRcvdCountThreshold + 1)); err != nil {
- t.Fatalf("Listen failed: %v", err)
- }
-
- irs := seqnum.Value(789)
- for i := uint16(0); i < n; i++ {
- // Send a SYN request.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort + i,
- DstPort: context.StackPort,
- Flags: header.TCPFlagSyn,
- SeqNum: irs,
- RcvWnd: 30000,
- })
- }
-
- // Each of these ACK's will cause a syn-cookie based connection to be
- // accepted and delivered to the listening endpoint.
- for i := uint16(0); i < n; i++ {
- b := c.GetPacket()
- tcp := header.TCP(header.IPv4(b).Payload())
- iss := seqnum.Value(tcp.SequenceNumber())
- // Send ACK.
- c.SendPacket(nil, &context.Headers{
- SrcPort: tcp.DestinationPort(),
- DstPort: context.StackPort,
- Flags: header.TCPFlagAck,
- SeqNum: irs + 1,
- AckNum: iss + 1,
- RcvWnd: 30000,
- })
- }
-
- // Try to accept the connection.
- we, ch := waiter.NewChannelEntry(nil)
- c.WQ.EventRegister(&we, waiter.EventIn)
- defer c.WQ.EventUnregister(&we)
- nep, _, err := c.EP.Accept()
- if err == tcpip.ErrWouldBlock {
- // Wait for connection to be established.
- select {
- case <-ch:
- nep, _, err = c.EP.Accept()
- if err != nil {
- t.Fatalf("Accept failed: %v", err)
- }
-
- case <-time.After(10 * time.Second):
- t.Fatalf("Timed out waiting for accept")
- }
- }
- nep.Close()
- c.EP.Close()
-}
-
-func TestV4ListenCloseOnV4(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- // Create TCP endpoint.
- var err *tcpip.Error
- c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- // Bind to wildcard.
- if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- // Test acceptance.
- testV4ListenClose(t, c)
-}
diff --git a/pkg/tcpip/transport/tcp/sack_scoreboard_test.go b/pkg/tcpip/transport/tcp/sack_scoreboard_test.go
deleted file mode 100644
index b4e5ba0df..000000000
--- a/pkg/tcpip/transport/tcp/sack_scoreboard_test.go
+++ /dev/null
@@ -1,249 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tcp_test
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/seqnum"
- "gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
-)
-
-const smss = 1500
-
-func initScoreboard(blocks []header.SACKBlock, iss seqnum.Value) *tcp.SACKScoreboard {
- s := tcp.NewSACKScoreboard(smss, iss)
- for _, blk := range blocks {
- s.Insert(blk)
- }
- return s
-}
-
-func TestSACKScoreboardIsSACKED(t *testing.T) {
- type blockTest struct {
- block header.SACKBlock
- sacked bool
- }
- testCases := []struct {
- comment string
- scoreboardBlocks []header.SACKBlock
- blockTests []blockTest
- iss seqnum.Value
- }{
- {
- "Test holes and unsacked SACK blocks in SACKed ranges and insertion of overlapping SACK blocks",
- []header.SACKBlock{{10, 20}, {10, 30}, {30, 40}, {41, 50}, {5, 10}, {1, 50}, {111, 120}, {101, 110}, {52, 120}},
- []blockTest{
- {header.SACKBlock{15, 21}, true},
- {header.SACKBlock{200, 201}, false},
- {header.SACKBlock{50, 51}, false},
- {header.SACKBlock{53, 120}, true},
- },
- 0,
- },
- {
- "Test disjoint SACKBlocks",
- []header.SACKBlock{{2288624809, 2288810057}, {2288811477, 2288838565}},
- []blockTest{
- {header.SACKBlock{2288624809, 2288810057}, true},
- {header.SACKBlock{2288811477, 2288838565}, true},
- {header.SACKBlock{2288810057, 2288811477}, false},
- },
- 2288624809,
- },
- {
- "Test sequence number wrap around",
- []header.SACKBlock{{4294254144, 225652}, {5340409, 5350509}},
- []blockTest{
- {header.SACKBlock{4294254144, 4294254145}, true},
- {header.SACKBlock{4294254143, 4294254144}, false},
- {header.SACKBlock{4294254144, 1}, true},
- {header.SACKBlock{225652, 5350509}, false},
- {header.SACKBlock{5340409, 5350509}, true},
- {header.SACKBlock{5350509, 5350609}, false},
- },
- 4294254144,
- },
- {
- "Test disjoint SACKBlocks out of order",
- []header.SACKBlock{{827450276, 827454536}, {827426028, 827428868}},
- []blockTest{
- {header.SACKBlock{827426028, 827428867}, true},
- {header.SACKBlock{827450168, 827450275}, false},
- },
- 827426000,
- },
- }
- for _, tc := range testCases {
- sb := initScoreboard(tc.scoreboardBlocks, tc.iss)
- for _, blkTest := range tc.blockTests {
- if want, got := blkTest.sacked, sb.IsSACKED(blkTest.block); got != want {
- t.Errorf("%s: s.IsSACKED(%v) = %v, want %v", tc.comment, blkTest.block, got, want)
- }
- }
- }
-}
-
-func TestSACKScoreboardIsRangeLost(t *testing.T) {
- s := tcp.NewSACKScoreboard(10, 0)
- s.Insert(header.SACKBlock{1, 25})
- s.Insert(header.SACKBlock{25, 50})
- s.Insert(header.SACKBlock{51, 100})
- s.Insert(header.SACKBlock{111, 120})
- s.Insert(header.SACKBlock{101, 110})
- s.Insert(header.SACKBlock{121, 141})
- s.Insert(header.SACKBlock{145, 146})
- s.Insert(header.SACKBlock{147, 148})
- s.Insert(header.SACKBlock{149, 150})
- s.Insert(header.SACKBlock{153, 154})
- s.Insert(header.SACKBlock{155, 156})
- testCases := []struct {
- block header.SACKBlock
- lost bool
- }{
- // Block not covered by SACK block and has more than
- // nDupAckThreshold discontiguous SACK blocks after it as well
- // as (nDupAckThreshold -1) * 10 (smss) bytes that have been
- // SACKED above the sequence number covered by this block.
- {block: header.SACKBlock{0, 1}, lost: true},
-
- // These blocks have all been SACKed and should not be
- // considered lost.
- {block: header.SACKBlock{1, 2}, lost: false},
- {block: header.SACKBlock{25, 26}, lost: false},
- {block: header.SACKBlock{1, 45}, lost: false},
-
- // Same as the first case above.
- {block: header.SACKBlock{50, 51}, lost: true},
-
- // This block has been SACKed and should not be considered lost.
- {block: header.SACKBlock{119, 120}, lost: false},
-
- // This one should return true because there are >
- // (nDupAckThreshold - 1) * 10 (smss) bytes that have been
- // sacked above this sequence number.
- {block: header.SACKBlock{120, 121}, lost: true},
-
- // This block has been SACKed and should not be considered lost.
- {block: header.SACKBlock{125, 126}, lost: false},
-
- // This block has not been SACKed and there are nDupAckThreshold
- // number of SACKed blocks after it.
- {block: header.SACKBlock{141, 145}, lost: true},
-
- // This block has not been SACKed and there are less than
- // nDupAckThreshold SACKed sequences after it.
- {block: header.SACKBlock{151, 152}, lost: false},
- }
- for _, tc := range testCases {
- if want, got := tc.lost, s.IsRangeLost(tc.block); got != want {
- t.Errorf("s.IsRangeLost(%v) = %v, want %v", tc.block, got, want)
- }
- }
-}
-
-func TestSACKScoreboardIsLost(t *testing.T) {
- s := tcp.NewSACKScoreboard(10, 0)
- s.Insert(header.SACKBlock{1, 25})
- s.Insert(header.SACKBlock{25, 50})
- s.Insert(header.SACKBlock{51, 100})
- s.Insert(header.SACKBlock{111, 120})
- s.Insert(header.SACKBlock{101, 110})
- s.Insert(header.SACKBlock{121, 141})
- s.Insert(header.SACKBlock{121, 141})
- s.Insert(header.SACKBlock{145, 146})
- s.Insert(header.SACKBlock{147, 148})
- s.Insert(header.SACKBlock{149, 150})
- s.Insert(header.SACKBlock{153, 154})
- s.Insert(header.SACKBlock{155, 156})
- testCases := []struct {
- seq seqnum.Value
- lost bool
- }{
- // Sequence number not covered by SACK block and has more than
- // nDupAckThreshold discontiguous SACK blocks after it as well
- // as (nDupAckThreshold -1) * 10 (smss) bytes that have been
- // SACKED above the sequence number.
- {seq: 0, lost: true},
-
- // These sequence numbers have all been SACKed and should not be
- // considered lost.
- {seq: 1, lost: false},
- {seq: 25, lost: false},
- {seq: 45, lost: false},
-
- // Same as first case above.
- {seq: 50, lost: true},
-
- // This block has been SACKed and should not be considered lost.
- {seq: 119, lost: false},
-
- // This one should return true because there are >
- // (nDupAckThreshold - 1) * 10 (smss) bytes that have been
- // sacked above this sequence number.
- {seq: 120, lost: true},
-
- // This sequence number has been SACKed and should not be
- // considered lost.
- {seq: 125, lost: false},
-
- // This sequence number has not been SACKed and there are
- // nDupAckThreshold number of SACKed blocks after it.
- {seq: 141, lost: true},
-
- // This sequence number has not been SACKed and there are less
- // than nDupAckThreshold SACKed sequences after it.
- {seq: 151, lost: false},
- }
- for _, tc := range testCases {
- if want, got := tc.lost, s.IsLost(tc.seq); got != want {
- t.Errorf("s.IsLost(%v) = %v, want %v", tc.seq, got, want)
- }
- }
-}
-
-func TestSACKScoreboardDelete(t *testing.T) {
- blocks := []header.SACKBlock{{4294254144, 225652}, {5340409, 5350509}}
- s := initScoreboard(blocks, 4294254143)
- s.Delete(5340408)
- if s.Empty() {
- t.Fatalf("s.Empty() = true, want false")
- }
- if got, want := s.Sacked(), blocks[1].Start.Size(blocks[1].End); got != want {
- t.Fatalf("incorrect sacked bytes in scoreboard got: %v, want: %v", got, want)
- }
- s.Delete(5340410)
- if s.Empty() {
- t.Fatal("s.Empty() = true, want false")
- }
- newSB := header.SACKBlock{5340410, 5350509}
- if !s.IsSACKED(newSB) {
- t.Fatalf("s.IsSACKED(%v) = false, want true, scoreboard: %v", newSB, s)
- }
- s.Delete(5350509)
- lastOctet := header.SACKBlock{5350508, 5350509}
- if s.IsSACKED(lastOctet) {
- t.Fatalf("s.IsSACKED(%v) = false, want true", lastOctet)
- }
-
- s.Delete(5350510)
- if !s.Empty() {
- t.Fatal("s.Empty() = false, want true")
- }
- if got, want := s.Sacked(), seqnum.Size(0); got != want {
- t.Fatalf("incorrect sacked bytes in scoreboard got: %v, want: %v", got, want)
- }
-}
diff --git a/pkg/tcpip/transport/tcp/tcp_noracedetector_test.go b/pkg/tcpip/transport/tcp/tcp_noracedetector_test.go
deleted file mode 100644
index 272bbcdbd..000000000
--- a/pkg/tcpip/transport/tcp/tcp_noracedetector_test.go
+++ /dev/null
@@ -1,519 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-// These tests are flaky when run under the go race detector due to some
-// iterations taking long enough that the retransmit timer can kick in causing
-// the congestion window measurements to fail due to extra packets etc.
-//
-// +build !race
-
-package tcp_test
-
-import (
- "fmt"
- "math"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
- "gvisor.dev/gvisor/pkg/tcpip/transport/tcp/testing/context"
-)
-
-func TestFastRecovery(t *testing.T) {
- maxPayload := 32
- c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxPayload))
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-
- const iterations = 7
- data := buffer.NewView(2 * maxPayload * (tcp.InitialCwnd << (iterations + 1)))
- for i := range data {
- data[i] = byte(i)
- }
-
- // Write all the data in one shot. Packets will only be written at the
- // MTU size though.
- if _, _, err := c.EP.Write(tcpip.SlicePayload(data), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
-
- // Do slow start for a few iterations.
- expected := tcp.InitialCwnd
- bytesRead := 0
- for i := 0; i < iterations; i++ {
- expected = tcp.InitialCwnd << uint(i)
- if i > 0 {
- // Acknowledge all the data received so far if not on
- // first iteration.
- c.SendAck(790, bytesRead)
- }
-
- // Read all packets expected on this iteration. Don't
- // acknowledge any of them just yet, so that we can measure the
- // congestion window.
- for j := 0; j < expected; j++ {
- c.ReceiveAndCheckPacket(data, bytesRead, maxPayload)
- bytesRead += maxPayload
- }
-
- // Check we don't receive any more packets on this iteration.
- // The timeout can't be too high or we'll trigger a timeout.
- c.CheckNoPacketTimeout("More packets received than expected for this cwnd.", 50*time.Millisecond)
- }
-
- // Send 3 duplicate acks. This should force an immediate retransmit of
- // the pending packet and put the sender into fast recovery.
- rtxOffset := bytesRead - maxPayload*expected
- for i := 0; i < 3; i++ {
- c.SendAck(790, rtxOffset)
- }
-
- // Receive the retransmitted packet.
- c.ReceiveAndCheckPacket(data, rtxOffset, maxPayload)
-
- if got, want := c.Stack().Stats().TCP.FastRetransmit.Value(), uint64(1); got != want {
- t.Errorf("got stats.TCP.FastRetransmit.Value = %v, want = %v", got, want)
- }
-
- if got, want := c.Stack().Stats().TCP.Retransmits.Value(), uint64(1); got != want {
- t.Errorf("got stats.TCP.Retransmit.Value = %v, want = %v", got, want)
- }
-
- if got, want := c.Stack().Stats().TCP.FastRecovery.Value(), uint64(1); got != want {
- t.Errorf("got stats.TCP.FastRecovery.Value = %v, want = %v", got, want)
- }
-
- // Now send 7 mode duplicate acks. Each of these should cause a window
- // inflation by 1 and cause the sender to send an extra packet.
- for i := 0; i < 7; i++ {
- c.SendAck(790, rtxOffset)
- }
-
- recover := bytesRead
-
- // Ensure no new packets arrive.
- c.CheckNoPacketTimeout("More packets received than expected during recovery after dupacks for this cwnd.",
- 50*time.Millisecond)
-
- // Acknowledge half of the pending data.
- rtxOffset = bytesRead - expected*maxPayload/2
- c.SendAck(790, rtxOffset)
-
- // Receive the retransmit due to partial ack.
- c.ReceiveAndCheckPacket(data, rtxOffset, maxPayload)
-
- if got, want := c.Stack().Stats().TCP.FastRetransmit.Value(), uint64(2); got != want {
- t.Errorf("got stats.TCP.FastRetransmit.Value = %v, want = %v", got, want)
- }
-
- if got, want := c.Stack().Stats().TCP.Retransmits.Value(), uint64(2); got != want {
- t.Errorf("got stats.TCP.Retransmit.Value = %v, want = %v", got, want)
- }
-
- // Receive the 10 extra packets that should have been released due to
- // the congestion window inflation in recovery.
- for i := 0; i < 10; i++ {
- c.ReceiveAndCheckPacket(data, bytesRead, maxPayload)
- bytesRead += maxPayload
- }
-
- // A partial ACK during recovery should reduce congestion window by the
- // number acked. Since we had "expected" packets outstanding before sending
- // partial ack and we acked expected/2 , the cwnd and outstanding should
- // be expected/2 + 10 (7 dupAcks + 3 for the original 3 dupacks that triggered
- // fast recovery). Which means the sender should not send any more packets
- // till we ack this one.
- c.CheckNoPacketTimeout("More packets received than expected during recovery after partial ack for this cwnd.",
- 50*time.Millisecond)
-
- // Acknowledge all pending data to recover point.
- c.SendAck(790, recover)
-
- // At this point, the cwnd should reset to expected/2 and there are 10
- // packets outstanding.
- //
- // NOTE: Technically netstack is incorrect in that we adjust the cwnd on
- // the same segment that takes us out of recovery. But because of that
- // the actual cwnd at exit of recovery will be expected/2 + 1 as we
- // acked a cwnd worth of packets which will increase the cwnd further by
- // 1 in congestion avoidance.
- //
- // Now in the first iteration since there are 10 packets outstanding.
- // We would expect to get expected/2 +1 - 10 packets. But subsequent
- // iterations will send us expected/2 + 1 + 1 (per iteration).
- expected = expected/2 + 1 - 10
- for i := 0; i < iterations; i++ {
- // Read all packets expected on this iteration. Don't
- // acknowledge any of them just yet, so that we can measure the
- // congestion window.
- for j := 0; j < expected; j++ {
- c.ReceiveAndCheckPacket(data, bytesRead, maxPayload)
- bytesRead += maxPayload
- }
-
- // Check we don't receive any more packets on this iteration.
- // The timeout can't be too high or we'll trigger a timeout.
- c.CheckNoPacketTimeout(fmt.Sprintf("More packets received(after deflation) than expected %d for this cwnd.", expected), 50*time.Millisecond)
-
- // Acknowledge all the data received so far.
- c.SendAck(790, bytesRead)
-
- // In cogestion avoidance, the packets trains increase by 1 in
- // each iteration.
- if i == 0 {
- // After the first iteration we expect to get the full
- // congestion window worth of packets in every
- // iteration.
- expected += 10
- }
- expected++
- }
-}
-
-func TestExponentialIncreaseDuringSlowStart(t *testing.T) {
- maxPayload := 32
- c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxPayload))
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-
- const iterations = 7
- data := buffer.NewView(maxPayload * (tcp.InitialCwnd << (iterations + 1)))
- for i := range data {
- data[i] = byte(i)
- }
-
- // Write all the data in one shot. Packets will only be written at the
- // MTU size though.
- if _, _, err := c.EP.Write(tcpip.SlicePayload(data), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
-
- expected := tcp.InitialCwnd
- bytesRead := 0
- for i := 0; i < iterations; i++ {
- // Read all packets expected on this iteration. Don't
- // acknowledge any of them just yet, so that we can measure the
- // congestion window.
- for j := 0; j < expected; j++ {
- c.ReceiveAndCheckPacket(data, bytesRead, maxPayload)
- bytesRead += maxPayload
- }
-
- // Check we don't receive any more packets on this iteration.
- // The timeout can't be too high or we'll trigger a timeout.
- c.CheckNoPacketTimeout("More packets received than expected for this cwnd.", 50*time.Millisecond)
-
- // Acknowledge all the data received so far.
- c.SendAck(790, bytesRead)
-
- // Double the number of expected packets for the next iteration.
- expected *= 2
- }
-}
-
-func TestCongestionAvoidance(t *testing.T) {
- maxPayload := 32
- c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxPayload))
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-
- const iterations = 7
- data := buffer.NewView(2 * maxPayload * (tcp.InitialCwnd << (iterations + 1)))
- for i := range data {
- data[i] = byte(i)
- }
-
- // Write all the data in one shot. Packets will only be written at the
- // MTU size though.
- if _, _, err := c.EP.Write(tcpip.SlicePayload(data), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
-
- // Do slow start for a few iterations.
- expected := tcp.InitialCwnd
- bytesRead := 0
- for i := 0; i < iterations; i++ {
- expected = tcp.InitialCwnd << uint(i)
- if i > 0 {
- // Acknowledge all the data received so far if not on
- // first iteration.
- c.SendAck(790, bytesRead)
- }
-
- // Read all packets expected on this iteration. Don't
- // acknowledge any of them just yet, so that we can measure the
- // congestion window.
- for j := 0; j < expected; j++ {
- c.ReceiveAndCheckPacket(data, bytesRead, maxPayload)
- bytesRead += maxPayload
- }
-
- // Check we don't receive any more packets on this iteration.
- // The timeout can't be too high or we'll trigger a timeout.
- c.CheckNoPacketTimeout("More packets received than expected for this cwnd (slow start phase).", 50*time.Millisecond)
- }
-
- // Don't acknowledge the first packet of the last packet train. Let's
- // wait for them to time out, which will trigger a restart of slow
- // start, and initialization of ssthresh to cwnd/2.
- rtxOffset := bytesRead - maxPayload*expected
- c.ReceiveAndCheckPacket(data, rtxOffset, maxPayload)
-
- // Acknowledge all the data received so far.
- c.SendAck(790, bytesRead)
-
- // This part is tricky: when the timeout happened, we had "expected"
- // packets pending, cwnd reset to 1, and ssthresh set to expected/2.
- // By acknowledging "expected" packets, the slow-start part will
- // increase cwnd to expected/2 (which "consumes" expected/2-1 of the
- // acknowledgements), then the congestion avoidance part will consume
- // an extra expected/2 acks to take cwnd to expected/2 + 1. One ack
- // remains in the "ack count" (which will cause cwnd to be incremented
- // once it reaches cwnd acks).
- //
- // So we're straight into congestion avoidance with cwnd set to
- // expected/2 + 1.
- //
- // Check that packets trains of cwnd packets are sent, and that cwnd is
- // incremented by 1 after we acknowledge each packet.
- expected = expected/2 + 1
- for i := 0; i < iterations; i++ {
- // Read all packets expected on this iteration. Don't
- // acknowledge any of them just yet, so that we can measure the
- // congestion window.
- for j := 0; j < expected; j++ {
- c.ReceiveAndCheckPacket(data, bytesRead, maxPayload)
- bytesRead += maxPayload
- }
-
- // Check we don't receive any more packets on this iteration.
- // The timeout can't be too high or we'll trigger a timeout.
- c.CheckNoPacketTimeout("More packets received than expected for this cwnd (congestion avoidance phase).", 50*time.Millisecond)
-
- // Acknowledge all the data received so far.
- c.SendAck(790, bytesRead)
-
- // In cogestion avoidance, the packets trains increase by 1 in
- // each iteration.
- expected++
- }
-}
-
-// cubicCwnd returns an estimate of a cubic window given the
-// originalCwnd, wMax, last congestion event time and sRTT.
-func cubicCwnd(origCwnd int, wMax int, congEventTime time.Time, sRTT time.Duration) int {
- cwnd := float64(origCwnd)
- // We wait 50ms between each iteration so sRTT as computed by cubic
- // should be close to 50ms.
- elapsed := (time.Since(congEventTime) + sRTT).Seconds()
- k := math.Cbrt(float64(wMax) * 0.3 / 0.7)
- wtRTT := 0.4*math.Pow(elapsed-k, 3) + float64(wMax)
- cwnd += (wtRTT - cwnd) / cwnd
- return int(cwnd)
-}
-
-func TestCubicCongestionAvoidance(t *testing.T) {
- maxPayload := 32
- c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxPayload))
- defer c.Cleanup()
-
- enableCUBIC(t, c)
-
- c.CreateConnected(789, 30000, nil)
-
- const iterations = 7
- data := buffer.NewView(2 * maxPayload * (tcp.InitialCwnd << (iterations + 1)))
-
- for i := range data {
- data[i] = byte(i)
- }
-
- // Write all the data in one shot. Packets will only be written at the
- // MTU size though.
- if _, _, err := c.EP.Write(tcpip.SlicePayload(data), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
-
- // Do slow start for a few iterations.
- expected := tcp.InitialCwnd
- bytesRead := 0
- for i := 0; i < iterations; i++ {
- expected = tcp.InitialCwnd << uint(i)
- if i > 0 {
- // Acknowledge all the data received so far if not on
- // first iteration.
- c.SendAck(790, bytesRead)
- }
-
- // Read all packets expected on this iteration. Don't
- // acknowledge any of them just yet, so that we can measure the
- // congestion window.
- for j := 0; j < expected; j++ {
- c.ReceiveAndCheckPacket(data, bytesRead, maxPayload)
- bytesRead += maxPayload
- }
-
- // Check we don't receive any more packets on this iteration.
- // The timeout can't be too high or we'll trigger a timeout.
- c.CheckNoPacketTimeout("More packets received than expected for this cwnd (during slow-start phase).", 50*time.Millisecond)
- }
-
- // Don't acknowledge the first packet of the last packet train. Let's
- // wait for them to time out, which will trigger a restart of slow
- // start, and initialization of ssthresh to cwnd * 0.7.
- rtxOffset := bytesRead - maxPayload*expected
- c.ReceiveAndCheckPacket(data, rtxOffset, maxPayload)
-
- // Acknowledge all pending data.
- c.SendAck(790, bytesRead)
-
- // Store away the time we sent the ACK and assuming a 200ms RTO
- // we estimate that the sender will have an RTO 200ms from now
- // and go back into slow start.
- packetDropTime := time.Now().Add(200 * time.Millisecond)
-
- // This part is tricky: when the timeout happened, we had "expected"
- // packets pending, cwnd reset to 1, and ssthresh set to expected * 0.7.
- // By acknowledging "expected" packets, the slow-start part will
- // increase cwnd to expected/2 essentially putting the connection
- // straight into congestion avoidance.
- wMax := expected
- // Lower expected as per cubic spec after a congestion event.
- expected = int(float64(expected) * 0.7)
- cwnd := expected
- for i := 0; i < iterations; i++ {
- // Cubic grows window independent of ACKs. Cubic Window growth
- // is a function of time elapsed since last congestion event.
- // As a result the congestion window does not grow
- // deterministically in response to ACKs.
- //
- // We need to roughly estimate what the cwnd of the sender is
- // based on when we sent the dupacks.
- cwnd := cubicCwnd(cwnd, wMax, packetDropTime, 50*time.Millisecond)
-
- packetsExpected := cwnd
- for j := 0; j < packetsExpected; j++ {
- c.ReceiveAndCheckPacket(data, bytesRead, maxPayload)
- bytesRead += maxPayload
- }
- t.Logf("expected packets received, next trying to receive any extra packets that may come")
-
- // If our estimate was correct there should be no more pending packets.
- // We attempt to read a packet a few times with a short sleep in between
- // to ensure that we don't see the sender send any unexpected packets.
- unexpectedPackets := 0
- for {
- gotPacket := c.ReceiveNonBlockingAndCheckPacket(data, bytesRead, maxPayload)
- if !gotPacket {
- break
- }
- bytesRead += maxPayload
- unexpectedPackets++
- time.Sleep(1 * time.Millisecond)
- }
- if unexpectedPackets != 0 {
- t.Fatalf("received %d unexpected packets for iteration %d", unexpectedPackets, i)
- }
- // Check we don't receive any more packets on this iteration.
- // The timeout can't be too high or we'll trigger a timeout.
- c.CheckNoPacketTimeout("More packets received than expected for this cwnd(congestion avoidance)", 5*time.Millisecond)
-
- // Acknowledge all the data received so far.
- c.SendAck(790, bytesRead)
- }
-}
-
-func TestRetransmit(t *testing.T) {
- maxPayload := 32
- c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxPayload))
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-
- const iterations = 7
- data := buffer.NewView(maxPayload * (tcp.InitialCwnd << (iterations + 1)))
- for i := range data {
- data[i] = byte(i)
- }
-
- // Write all the data in two shots. Packets will only be written at the
- // MTU size though.
- half := data[:len(data)/2]
- if _, _, err := c.EP.Write(tcpip.SlicePayload(half), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
- half = data[len(data)/2:]
- if _, _, err := c.EP.Write(tcpip.SlicePayload(half), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
-
- // Do slow start for a few iterations.
- expected := tcp.InitialCwnd
- bytesRead := 0
- for i := 0; i < iterations; i++ {
- expected = tcp.InitialCwnd << uint(i)
- if i > 0 {
- // Acknowledge all the data received so far if not on
- // first iteration.
- c.SendAck(790, bytesRead)
- }
-
- // Read all packets expected on this iteration. Don't
- // acknowledge any of them just yet, so that we can measure the
- // congestion window.
- for j := 0; j < expected; j++ {
- c.ReceiveAndCheckPacket(data, bytesRead, maxPayload)
- bytesRead += maxPayload
- }
-
- // Check we don't receive any more packets on this iteration.
- // The timeout can't be too high or we'll trigger a timeout.
- c.CheckNoPacketTimeout("More packets received than expected for this cwnd.", 50*time.Millisecond)
- }
-
- // Wait for a timeout and retransmit.
- rtxOffset := bytesRead - maxPayload*expected
- c.ReceiveAndCheckPacket(data, rtxOffset, maxPayload)
-
- if got, want := c.Stack().Stats().TCP.Timeouts.Value(), uint64(1); got != want {
- t.Errorf("got stats.TCP.Timeouts.Value = %v, want = %v", got, want)
- }
-
- if got, want := c.Stack().Stats().TCP.Retransmits.Value(), uint64(1); got != want {
- t.Errorf("got stats.TCP.Retransmits.Value = %v, want = %v", got, want)
- }
-
- if got, want := c.Stack().Stats().TCP.SlowStartRetransmits.Value(), uint64(1); got != want {
- t.Errorf("got stats.TCP.SlowStartRetransmits.Value = %v, want = %v", got, want)
- }
-
- // Acknowledge half of the pending data.
- rtxOffset = bytesRead - expected*maxPayload/2
- c.SendAck(790, rtxOffset)
-
- // Receive the remaining data, making sure that acknowledged data is not
- // retransmitted.
- for offset := rtxOffset; offset < len(data); offset += maxPayload {
- c.ReceiveAndCheckPacket(data, offset, maxPayload)
- c.SendAck(790, offset+maxPayload)
- }
-
- c.CheckNoPacketTimeout("More packets received than expected for this cwnd.", 50*time.Millisecond)
-}
diff --git a/pkg/tcpip/transport/tcp/tcp_sack_test.go b/pkg/tcpip/transport/tcp/tcp_sack_test.go
deleted file mode 100644
index 4e7f1a740..000000000
--- a/pkg/tcpip/transport/tcp/tcp_sack_test.go
+++ /dev/null
@@ -1,564 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tcp_test
-
-import (
- "fmt"
- "log"
- "reflect"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/seqnum"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
- "gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
- "gvisor.dev/gvisor/pkg/tcpip/transport/tcp/testing/context"
-)
-
-// createConnectedWithSACKPermittedOption creates and connects c.ep with the
-// SACKPermitted option enabled if the stack in the context has the SACK support
-// enabled.
-func createConnectedWithSACKPermittedOption(c *context.Context) *context.RawEndpoint {
- return c.CreateConnectedWithOptions(header.TCPSynOptions{SACKPermitted: c.SACKEnabled()})
-}
-
-// createConnectedWithSACKAndTS creates and connects c.ep with the SACK & TS
-// option enabled if the stack in the context has SACK and TS enabled.
-func createConnectedWithSACKAndTS(c *context.Context) *context.RawEndpoint {
- return c.CreateConnectedWithOptions(header.TCPSynOptions{SACKPermitted: c.SACKEnabled(), TS: true})
-}
-
-func setStackSACKPermitted(t *testing.T, c *context.Context, enable bool) {
- t.Helper()
- if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, tcp.SACKEnabled(enable)); err != nil {
- t.Fatalf("c.s.SetTransportProtocolOption(tcp.ProtocolNumber, SACKEnabled(%v) = %v", enable, err)
- }
-}
-
-// TestSackPermittedConnect establishes a connection with the SACK option
-// enabled.
-func TestSackPermittedConnect(t *testing.T) {
- for _, sackEnabled := range []bool{false, true} {
- t.Run(fmt.Sprintf("stack.sackEnabled: %v", sackEnabled), func(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- setStackSACKPermitted(t, c, sackEnabled)
- rep := createConnectedWithSACKPermittedOption(c)
- data := []byte{1, 2, 3}
-
- rep.SendPacket(data, nil)
- savedSeqNum := rep.NextSeqNum
- rep.VerifyACKNoSACK()
-
- // Make an out of order packet and send it.
- rep.NextSeqNum += 3
- sackBlocks := []header.SACKBlock{
- {rep.NextSeqNum, rep.NextSeqNum.Add(seqnum.Size(len(data)))},
- }
- rep.SendPacket(data, nil)
-
- // Restore the saved sequence number so that the
- // VerifyXXX calls use the right sequence number for
- // checking ACK numbers.
- rep.NextSeqNum = savedSeqNum
- if sackEnabled {
- rep.VerifyACKHasSACK(sackBlocks)
- } else {
- rep.VerifyACKNoSACK()
- }
-
- // Send the missing segment.
- rep.SendPacket(data, nil)
- // The ACK should contain the cumulative ACK for all 9
- // bytes sent and no SACK blocks.
- rep.NextSeqNum += 3
- // Check that no SACK block is returned in the ACK.
- rep.VerifyACKNoSACK()
- })
- }
-}
-
-// TestSackDisabledConnect establishes a connection with the SACK option
-// disabled and verifies that no SACKs are sent for out of order segments.
-func TestSackDisabledConnect(t *testing.T) {
- for _, sackEnabled := range []bool{false, true} {
- t.Run(fmt.Sprintf("sackEnabled: %v", sackEnabled), func(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- setStackSACKPermitted(t, c, sackEnabled)
-
- rep := c.CreateConnectedWithOptions(header.TCPSynOptions{})
-
- data := []byte{1, 2, 3}
-
- rep.SendPacket(data, nil)
- savedSeqNum := rep.NextSeqNum
- rep.VerifyACKNoSACK()
-
- // Make an out of order packet and send it.
- rep.NextSeqNum += 3
- rep.SendPacket(data, nil)
-
- // The ACK should contain the older sequence number and
- // no SACK blocks.
- rep.NextSeqNum = savedSeqNum
- rep.VerifyACKNoSACK()
-
- // Send the missing segment.
- rep.SendPacket(data, nil)
- // The ACK should contain the cumulative ACK for all 9
- // bytes sent and no SACK blocks.
- rep.NextSeqNum += 3
- // Check that no SACK block is returned in the ACK.
- rep.VerifyACKNoSACK()
- })
- }
-}
-
-// TestSackPermittedAccept accepts and establishes a connection with the
-// SACKPermitted option enabled if the connection request specifies the
-// SACKPermitted option. In case of SYN cookies SACK should be disabled as we
-// don't encode the SACK information in the cookie.
-func TestSackPermittedAccept(t *testing.T) {
- type testCase struct {
- cookieEnabled bool
- sackPermitted bool
- wndScale int
- wndSize uint16
- }
-
- testCases := []testCase{
- // When cookie is used window scaling is disabled.
- {true, false, -1, 0xffff}, // When cookie is used window scaling is disabled.
- {false, true, 5, 0x8000}, // 0x8000 * 2^5 = 1<<20 = 1MB window (the default).
- }
- savedSynCountThreshold := tcp.SynRcvdCountThreshold
- defer func() {
- tcp.SynRcvdCountThreshold = savedSynCountThreshold
- }()
- for _, tc := range testCases {
- t.Run(fmt.Sprintf("test: %#v", tc), func(t *testing.T) {
- if tc.cookieEnabled {
- tcp.SynRcvdCountThreshold = 0
- } else {
- tcp.SynRcvdCountThreshold = savedSynCountThreshold
- }
- for _, sackEnabled := range []bool{false, true} {
- t.Run(fmt.Sprintf("test stack.sackEnabled: %v", sackEnabled), func(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
- setStackSACKPermitted(t, c, sackEnabled)
-
- rep := c.AcceptWithOptions(tc.wndScale, header.TCPSynOptions{MSS: defaultIPv4MSS, SACKPermitted: tc.sackPermitted})
- // Now verify no SACK blocks are
- // received when sack is disabled.
- data := []byte{1, 2, 3}
- rep.SendPacket(data, nil)
- rep.VerifyACKNoSACK()
-
- savedSeqNum := rep.NextSeqNum
-
- // Make an out of order packet and send
- // it.
- rep.NextSeqNum += 3
- sackBlocks := []header.SACKBlock{
- {rep.NextSeqNum, rep.NextSeqNum.Add(seqnum.Size(len(data)))},
- }
- rep.SendPacket(data, nil)
-
- // The ACK should contain the older
- // sequence number.
- rep.NextSeqNum = savedSeqNum
- if sackEnabled && tc.sackPermitted {
- rep.VerifyACKHasSACK(sackBlocks)
- } else {
- rep.VerifyACKNoSACK()
- }
-
- // Send the missing segment.
- rep.SendPacket(data, nil)
- // The ACK should contain the cumulative
- // ACK for all 9 bytes sent and no SACK
- // blocks.
- rep.NextSeqNum += 3
- // Check that no SACK block is returned
- // in the ACK.
- rep.VerifyACKNoSACK()
- })
- }
- })
- }
-}
-
-// TestSackDisabledAccept accepts and establishes a connection with
-// the SACKPermitted option disabled and verifies that no SACKs are
-// sent for out of order packets.
-func TestSackDisabledAccept(t *testing.T) {
- type testCase struct {
- cookieEnabled bool
- wndScale int
- wndSize uint16
- }
-
- testCases := []testCase{
- // When cookie is used window scaling is disabled.
- {true, -1, 0xffff}, // When cookie is used window scaling is disabled.
- {false, 5, 0x8000}, // 0x8000 * 2^5 = 1<<20 = 1MB window (the default).
- }
- savedSynCountThreshold := tcp.SynRcvdCountThreshold
- defer func() {
- tcp.SynRcvdCountThreshold = savedSynCountThreshold
- }()
- for _, tc := range testCases {
- t.Run(fmt.Sprintf("test: %#v", tc), func(t *testing.T) {
- if tc.cookieEnabled {
- tcp.SynRcvdCountThreshold = 0
- } else {
- tcp.SynRcvdCountThreshold = savedSynCountThreshold
- }
- for _, sackEnabled := range []bool{false, true} {
- t.Run(fmt.Sprintf("test: sackEnabled: %v", sackEnabled), func(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
- setStackSACKPermitted(t, c, sackEnabled)
-
- rep := c.AcceptWithOptions(tc.wndScale, header.TCPSynOptions{MSS: defaultIPv4MSS})
-
- // Now verify no SACK blocks are
- // received when sack is disabled.
- data := []byte{1, 2, 3}
- rep.SendPacket(data, nil)
- rep.VerifyACKNoSACK()
- savedSeqNum := rep.NextSeqNum
-
- // Make an out of order packet and send
- // it.
- rep.NextSeqNum += 3
- rep.SendPacket(data, nil)
-
- // The ACK should contain the older
- // sequence number and no SACK blocks.
- rep.NextSeqNum = savedSeqNum
- rep.VerifyACKNoSACK()
-
- // Send the missing segment.
- rep.SendPacket(data, nil)
- // The ACK should contain the cumulative
- // ACK for all 9 bytes sent and no SACK
- // blocks.
- rep.NextSeqNum += 3
- // Check that no SACK block is returned
- // in the ACK.
- rep.VerifyACKNoSACK()
- })
- }
- })
- }
-}
-
-func TestUpdateSACKBlocks(t *testing.T) {
- testCases := []struct {
- segStart seqnum.Value
- segEnd seqnum.Value
- rcvNxt seqnum.Value
- sackBlocks []header.SACKBlock
- updated []header.SACKBlock
- }{
- // Trivial cases where current SACK block list is empty and we
- // have an out of order delivery.
- {10, 11, 2, []header.SACKBlock{}, []header.SACKBlock{{10, 11}}},
- {10, 12, 2, []header.SACKBlock{}, []header.SACKBlock{{10, 12}}},
- {10, 20, 2, []header.SACKBlock{}, []header.SACKBlock{{10, 20}}},
-
- // Cases where current SACK block list is not empty and we have
- // an out of order delivery. Tests that the updated SACK block
- // list has the first block as the one that contains the new
- // SACK block representing the segment that was just delivered.
- {10, 11, 9, []header.SACKBlock{{12, 20}}, []header.SACKBlock{{10, 11}, {12, 20}}},
- {24, 30, 9, []header.SACKBlock{{12, 20}}, []header.SACKBlock{{24, 30}, {12, 20}}},
- {24, 30, 9, []header.SACKBlock{{12, 20}, {32, 40}}, []header.SACKBlock{{24, 30}, {12, 20}, {32, 40}}},
-
- // Ensure that we only retain header.MaxSACKBlocks and drop the
- // oldest one if adding a new block exceeds
- // header.MaxSACKBlocks.
- {24, 30, 9,
- []header.SACKBlock{{12, 20}, {32, 40}, {42, 50}, {52, 60}, {62, 70}, {72, 80}},
- []header.SACKBlock{{24, 30}, {12, 20}, {32, 40}, {42, 50}, {52, 60}, {62, 70}}},
-
- // Cases where segment extends an existing SACK block.
- {10, 12, 9, []header.SACKBlock{{12, 20}}, []header.SACKBlock{{10, 20}}},
- {10, 22, 9, []header.SACKBlock{{12, 20}}, []header.SACKBlock{{10, 22}}},
- {10, 22, 9, []header.SACKBlock{{12, 20}}, []header.SACKBlock{{10, 22}}},
- {15, 22, 9, []header.SACKBlock{{12, 20}}, []header.SACKBlock{{12, 22}}},
- {15, 25, 9, []header.SACKBlock{{12, 20}}, []header.SACKBlock{{12, 25}}},
- {11, 25, 9, []header.SACKBlock{{12, 20}}, []header.SACKBlock{{11, 25}}},
- {10, 12, 9, []header.SACKBlock{{12, 20}, {32, 40}}, []header.SACKBlock{{10, 20}, {32, 40}}},
- {10, 22, 9, []header.SACKBlock{{12, 20}, {32, 40}}, []header.SACKBlock{{10, 22}, {32, 40}}},
- {10, 22, 9, []header.SACKBlock{{12, 20}, {32, 40}}, []header.SACKBlock{{10, 22}, {32, 40}}},
- {15, 22, 9, []header.SACKBlock{{12, 20}, {32, 40}}, []header.SACKBlock{{12, 22}, {32, 40}}},
- {15, 25, 9, []header.SACKBlock{{12, 20}, {32, 40}}, []header.SACKBlock{{12, 25}, {32, 40}}},
- {11, 25, 9, []header.SACKBlock{{12, 20}, {32, 40}}, []header.SACKBlock{{11, 25}, {32, 40}}},
-
- // Cases where segment contains rcvNxt.
- {10, 20, 15, []header.SACKBlock{{20, 30}, {40, 50}}, []header.SACKBlock{{40, 50}}},
- }
-
- for _, tc := range testCases {
- var sack tcp.SACKInfo
- copy(sack.Blocks[:], tc.sackBlocks)
- sack.NumBlocks = len(tc.sackBlocks)
- tcp.UpdateSACKBlocks(&sack, tc.segStart, tc.segEnd, tc.rcvNxt)
- if got, want := sack.Blocks[:sack.NumBlocks], tc.updated; !reflect.DeepEqual(got, want) {
- t.Errorf("UpdateSACKBlocks(%v, %v, %v, %v), got: %v, want: %v", tc.sackBlocks, tc.segStart, tc.segEnd, tc.rcvNxt, got, want)
- }
-
- }
-}
-
-func TestTrimSackBlockList(t *testing.T) {
- testCases := []struct {
- rcvNxt seqnum.Value
- sackBlocks []header.SACKBlock
- trimmed []header.SACKBlock
- }{
- // Simple cases where we trim whole entries.
- {2, []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}}, []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}}},
- {21, []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}}, []header.SACKBlock{{22, 30}, {32, 40}}},
- {31, []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}}, []header.SACKBlock{{32, 40}}},
- {40, []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}}, []header.SACKBlock{}},
- // Cases where we need to update a block.
- {12, []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}}, []header.SACKBlock{{12, 20}, {22, 30}, {32, 40}}},
- {23, []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}}, []header.SACKBlock{{23, 30}, {32, 40}}},
- {33, []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}}, []header.SACKBlock{{33, 40}}},
- {41, []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}}, []header.SACKBlock{}},
- }
- for _, tc := range testCases {
- var sack tcp.SACKInfo
- copy(sack.Blocks[:], tc.sackBlocks)
- sack.NumBlocks = len(tc.sackBlocks)
- tcp.TrimSACKBlockList(&sack, tc.rcvNxt)
- if got, want := sack.Blocks[:sack.NumBlocks], tc.trimmed; !reflect.DeepEqual(got, want) {
- t.Errorf("TrimSackBlockList(%v, %v), got: %v, want: %v", tc.sackBlocks, tc.rcvNxt, got, want)
- }
- }
-}
-
-func TestSACKRecovery(t *testing.T) {
- const maxPayload = 10
- // See: tcp.makeOptions for why tsOptionSize is set to 12 here.
- const tsOptionSize = 12
- // Enabling SACK means the payload size is reduced to account
- // for the extra space required for the TCP options.
- //
- // We increase the MTU by 40 bytes to account for SACK and Timestamp
- // options.
- const maxTCPOptionSize = 40
-
- c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxTCPOptionSize+maxPayload))
- defer c.Cleanup()
-
- c.Stack().AddTCPProbe(func(s stack.TCPEndpointState) {
- // We use log.Printf instead of t.Logf here because this probe
- // can fire even when the test function has finished. This is
- // because closing the endpoint in cleanup() does not mean the
- // actual worker loop terminates immediately as it still has to
- // do a full TCP shutdown. But this test can finish running
- // before the shutdown is done. Using t.Logf in such a case
- // causes the test to panic due to logging after test finished.
- log.Printf("state: %+v\n", s)
- })
- setStackSACKPermitted(t, c, true)
- createConnectedWithSACKAndTS(c)
-
- const iterations = 7
- data := buffer.NewView(2 * maxPayload * (tcp.InitialCwnd << (iterations + 1)))
- for i := range data {
- data[i] = byte(i)
- }
-
- // Write all the data in one shot. Packets will only be written at the
- // MTU size though.
- if _, _, err := c.EP.Write(tcpip.SlicePayload(data), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
-
- // Do slow start for a few iterations.
- expected := tcp.InitialCwnd
- bytesRead := 0
- for i := 0; i < iterations; i++ {
- expected = tcp.InitialCwnd << uint(i)
- if i > 0 {
- // Acknowledge all the data received so far if not on
- // first iteration.
- c.SendAck(790, bytesRead)
- }
-
- // Read all packets expected on this iteration. Don't
- // acknowledge any of them just yet, so that we can measure the
- // congestion window.
- for j := 0; j < expected; j++ {
- c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
- bytesRead += maxPayload
- }
-
- // Check we don't receive any more packets on this iteration.
- // The timeout can't be too high or we'll trigger a timeout.
- c.CheckNoPacketTimeout("More packets received than expected for this cwnd.", 50*time.Millisecond)
- }
-
- // Send 3 duplicate acks. This should force an immediate retransmit of
- // the pending packet and put the sender into fast recovery.
- rtxOffset := bytesRead - maxPayload*expected
- start := c.IRS.Add(seqnum.Size(rtxOffset) + 30 + 1)
- end := start.Add(10)
- for i := 0; i < 3; i++ {
- c.SendAckWithSACK(790, rtxOffset, []header.SACKBlock{{start, end}})
- end = end.Add(10)
- }
-
- // Receive the retransmitted packet.
- c.ReceiveAndCheckPacketWithOptions(data, rtxOffset, maxPayload, tsOptionSize)
-
- tcpStats := c.Stack().Stats().TCP
- stats := []struct {
- stat *tcpip.StatCounter
- name string
- want uint64
- }{
- {tcpStats.FastRetransmit, "stats.TCP.FastRetransmit", 1},
- {tcpStats.Retransmits, "stats.TCP.Retransmits", 1},
- {tcpStats.SACKRecovery, "stats.TCP.SACKRecovery", 1},
- {tcpStats.FastRecovery, "stats.TCP.FastRecovery", 0},
- }
- for _, s := range stats {
- if got, want := s.stat.Value(), s.want; got != want {
- t.Errorf("got %s.Value() = %v, want = %v", s.name, got, want)
- }
- }
-
- // Now send 7 mode duplicate ACKs. In SACK TCP dupAcks do not cause
- // window inflation and sending of packets is completely handled by the
- // SACK Recovery algorithm. We should see no packets being released, as
- // the cwnd at this point after entering recovery should be half of the
- // outstanding number of packets in flight.
- for i := 0; i < 7; i++ {
- c.SendAckWithSACK(790, rtxOffset, []header.SACKBlock{{start, end}})
- end = end.Add(10)
- }
-
- recover := bytesRead
-
- // Ensure no new packets arrive.
- c.CheckNoPacketTimeout("More packets received than expected during recovery after dupacks for this cwnd.",
- 50*time.Millisecond)
-
- // Acknowledge half of the pending data. This along with the 10 sacked
- // segments above should reduce the outstanding below the current
- // congestion window allowing the sender to transmit data.
- rtxOffset = bytesRead - expected*maxPayload/2
-
- // Now send a partial ACK w/ a SACK block that indicates that the next 3
- // segments are lost and we have received 6 segments after the lost
- // segments. This should cause the sender to immediately transmit all 3
- // segments in response to this ACK unlike in FastRecovery where only 1
- // segment is retransmitted per ACK.
- start = c.IRS.Add(seqnum.Size(rtxOffset) + 30 + 1)
- end = start.Add(60)
- c.SendAckWithSACK(790, rtxOffset, []header.SACKBlock{{start, end}})
-
- // At this point, we acked expected/2 packets and we SACKED 6 packets and
- // 3 segments were considered lost due to the SACK block we sent.
- //
- // So total packets outstanding can be calculated as follows after 7
- // iterations of slow start -> 10/20/40/80/160/320/640. So expected
- // should be 640 at start, then we went to recover at which point the
- // cwnd should be set to 320 + 3 (for the 3 dupAcks which have left the
- // network).
- // Outstanding at this point after acking half the window
- // (320 packets) will be:
- // outstanding = 640-320-6(due to SACK block)-3 = 311
- //
- // The last 3 is due to the fact that the first 3 packets after
- // rtxOffset will be considered lost due to the SACK blocks sent.
- // Receive the retransmit due to partial ack.
-
- c.ReceiveAndCheckPacketWithOptions(data, rtxOffset, maxPayload, tsOptionSize)
- // Receive the 2 extra packets that should have been retransmitted as
- // those should be considered lost and immediately retransmitted based
- // on the SACK information in the previous ACK sent above.
- for i := 0; i < 2; i++ {
- c.ReceiveAndCheckPacketWithOptions(data, rtxOffset+maxPayload*(i+1), maxPayload, tsOptionSize)
- }
-
- // Now we should get 9 more new unsent packets as the cwnd is 323 and
- // outstanding is 311.
- for i := 0; i < 9; i++ {
- c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
- bytesRead += maxPayload
- }
-
- // In SACK recovery only the first segment is fast retransmitted when
- // entering recovery.
- if got, want := c.Stack().Stats().TCP.FastRetransmit.Value(), uint64(1); got != want {
- t.Errorf("got stats.TCP.FastRetransmit.Value = %v, want = %v", got, want)
- }
-
- if got, want := c.Stack().Stats().TCP.Retransmits.Value(), uint64(4); got != want {
- t.Errorf("got stats.TCP.Retransmits.Value = %v, want = %v", got, want)
- }
-
- c.CheckNoPacketTimeout("More packets received than expected during recovery after partial ack for this cwnd.", 50*time.Millisecond)
-
- // Acknowledge all pending data to recover point.
- c.SendAck(790, recover)
-
- // At this point, the cwnd should reset to expected/2 and there are 9
- // packets outstanding.
- //
- // Now in the first iteration since there are 9 packets outstanding.
- // We would expect to get expected/2 - 9 packets. But subsequent
- // iterations will send us expected/2 + 1 (per iteration).
- expected = expected/2 - 9
- for i := 0; i < iterations; i++ {
- // Read all packets expected on this iteration. Don't
- // acknowledge any of them just yet, so that we can measure the
- // congestion window.
- for j := 0; j < expected; j++ {
- c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
- bytesRead += maxPayload
- }
- // Check we don't receive any more packets on this iteration.
- // The timeout can't be too high or we'll trigger a timeout.
- c.CheckNoPacketTimeout(fmt.Sprintf("More packets received(after deflation) than expected %d for this cwnd and iteration: %d.", expected, i), 50*time.Millisecond)
-
- // Acknowledge all the data received so far.
- c.SendAck(790, bytesRead)
-
- // In cogestion avoidance, the packets trains increase by 1 in
- // each iteration.
- if i == 0 {
- // After the first iteration we expect to get the full
- // congestion window worth of packets in every
- // iteration.
- expected += 9
- }
- expected++
- }
-}
diff --git a/pkg/tcpip/transport/tcp/tcp_segment_list.go b/pkg/tcpip/transport/tcp/tcp_segment_list.go
new file mode 100755
index 000000000..029f98a11
--- /dev/null
+++ b/pkg/tcpip/transport/tcp/tcp_segment_list.go
@@ -0,0 +1,173 @@
+package tcp
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type segmentElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (segmentElementMapper) linkerFor(elem *segment) *segment { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type segmentList struct {
+ head *segment
+ tail *segment
+}
+
+// Reset resets list l to the empty state.
+func (l *segmentList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *segmentList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *segmentList) Front() *segment {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *segmentList) Back() *segment {
+ return l.tail
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *segmentList) PushFront(e *segment) {
+ segmentElementMapper{}.linkerFor(e).SetNext(l.head)
+ segmentElementMapper{}.linkerFor(e).SetPrev(nil)
+
+ if l.head != nil {
+ segmentElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *segmentList) PushBack(e *segment) {
+ segmentElementMapper{}.linkerFor(e).SetNext(nil)
+ segmentElementMapper{}.linkerFor(e).SetPrev(l.tail)
+
+ if l.tail != nil {
+ segmentElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *segmentList) PushBackList(m *segmentList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ segmentElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ segmentElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *segmentList) InsertAfter(b, e *segment) {
+ a := segmentElementMapper{}.linkerFor(b).Next()
+ segmentElementMapper{}.linkerFor(e).SetNext(a)
+ segmentElementMapper{}.linkerFor(e).SetPrev(b)
+ segmentElementMapper{}.linkerFor(b).SetNext(e)
+
+ if a != nil {
+ segmentElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *segmentList) InsertBefore(a, e *segment) {
+ b := segmentElementMapper{}.linkerFor(a).Prev()
+ segmentElementMapper{}.linkerFor(e).SetNext(a)
+ segmentElementMapper{}.linkerFor(e).SetPrev(b)
+ segmentElementMapper{}.linkerFor(a).SetPrev(e)
+
+ if b != nil {
+ segmentElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *segmentList) Remove(e *segment) {
+ prev := segmentElementMapper{}.linkerFor(e).Prev()
+ next := segmentElementMapper{}.linkerFor(e).Next()
+
+ if prev != nil {
+ segmentElementMapper{}.linkerFor(prev).SetNext(next)
+ } else {
+ l.head = next
+ }
+
+ if next != nil {
+ segmentElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else {
+ l.tail = prev
+ }
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type segmentEntry struct {
+ next *segment
+ prev *segment
+}
+
+// Next returns the entry that follows e in the list.
+func (e *segmentEntry) Next() *segment {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *segmentEntry) Prev() *segment {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *segmentEntry) SetNext(elem *segment) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *segmentEntry) SetPrev(elem *segment) {
+ e.prev = elem
+}
diff --git a/pkg/tcpip/transport/tcp/tcp_state_autogen.go b/pkg/tcpip/transport/tcp/tcp_state_autogen.go
new file mode 100755
index 000000000..d92c1b67d
--- /dev/null
+++ b/pkg/tcpip/transport/tcp/tcp_state_autogen.go
@@ -0,0 +1,469 @@
+// automatically generated by stateify.
+
+package tcp
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+ "gvisor.dev/gvisor/pkg/tcpip/buffer"
+)
+
+func (x *cubicState) beforeSave() {}
+func (x *cubicState) save(m state.Map) {
+ x.beforeSave()
+ var t unixTime = x.saveT()
+ m.SaveValue("t", t)
+ m.Save("wLastMax", &x.wLastMax)
+ m.Save("wMax", &x.wMax)
+ m.Save("numCongestionEvents", &x.numCongestionEvents)
+ m.Save("c", &x.c)
+ m.Save("k", &x.k)
+ m.Save("beta", &x.beta)
+ m.Save("wC", &x.wC)
+ m.Save("wEst", &x.wEst)
+ m.Save("s", &x.s)
+}
+
+func (x *cubicState) afterLoad() {}
+func (x *cubicState) load(m state.Map) {
+ m.Load("wLastMax", &x.wLastMax)
+ m.Load("wMax", &x.wMax)
+ m.Load("numCongestionEvents", &x.numCongestionEvents)
+ m.Load("c", &x.c)
+ m.Load("k", &x.k)
+ m.Load("beta", &x.beta)
+ m.Load("wC", &x.wC)
+ m.Load("wEst", &x.wEst)
+ m.Load("s", &x.s)
+ m.LoadValue("t", new(unixTime), func(y interface{}) { x.loadT(y.(unixTime)) })
+}
+
+func (x *SACKInfo) beforeSave() {}
+func (x *SACKInfo) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Blocks", &x.Blocks)
+ m.Save("NumBlocks", &x.NumBlocks)
+}
+
+func (x *SACKInfo) afterLoad() {}
+func (x *SACKInfo) load(m state.Map) {
+ m.Load("Blocks", &x.Blocks)
+ m.Load("NumBlocks", &x.NumBlocks)
+}
+
+func (x *rcvBufAutoTuneParams) beforeSave() {}
+func (x *rcvBufAutoTuneParams) save(m state.Map) {
+ x.beforeSave()
+ var measureTime unixTime = x.saveMeasureTime()
+ m.SaveValue("measureTime", measureTime)
+ var rttMeasureTime unixTime = x.saveRttMeasureTime()
+ m.SaveValue("rttMeasureTime", rttMeasureTime)
+ m.Save("copied", &x.copied)
+ m.Save("prevCopied", &x.prevCopied)
+ m.Save("rtt", &x.rtt)
+ m.Save("rttMeasureSeqNumber", &x.rttMeasureSeqNumber)
+ m.Save("disabled", &x.disabled)
+}
+
+func (x *rcvBufAutoTuneParams) afterLoad() {}
+func (x *rcvBufAutoTuneParams) load(m state.Map) {
+ m.Load("copied", &x.copied)
+ m.Load("prevCopied", &x.prevCopied)
+ m.Load("rtt", &x.rtt)
+ m.Load("rttMeasureSeqNumber", &x.rttMeasureSeqNumber)
+ m.Load("disabled", &x.disabled)
+ m.LoadValue("measureTime", new(unixTime), func(y interface{}) { x.loadMeasureTime(y.(unixTime)) })
+ m.LoadValue("rttMeasureTime", new(unixTime), func(y interface{}) { x.loadRttMeasureTime(y.(unixTime)) })
+}
+
+func (x *endpoint) save(m state.Map) {
+ x.beforeSave()
+ var lastError string = x.saveLastError()
+ m.SaveValue("lastError", lastError)
+ var state EndpointState = x.saveState()
+ m.SaveValue("state", state)
+ var hardError string = x.saveHardError()
+ m.SaveValue("hardError", hardError)
+ var acceptedChan []*endpoint = x.saveAcceptedChan()
+ m.SaveValue("acceptedChan", acceptedChan)
+ m.Save("netProto", &x.netProto)
+ m.Save("waiterQueue", &x.waiterQueue)
+ m.Save("rcvList", &x.rcvList)
+ m.Save("rcvClosed", &x.rcvClosed)
+ m.Save("rcvBufSize", &x.rcvBufSize)
+ m.Save("rcvBufUsed", &x.rcvBufUsed)
+ m.Save("rcvAutoParams", &x.rcvAutoParams)
+ m.Save("zeroWindow", &x.zeroWindow)
+ m.Save("id", &x.id)
+ m.Save("isRegistered", &x.isRegistered)
+ m.Save("v6only", &x.v6only)
+ m.Save("isConnectNotified", &x.isConnectNotified)
+ m.Save("broadcast", &x.broadcast)
+ m.Save("workerRunning", &x.workerRunning)
+ m.Save("workerCleanup", &x.workerCleanup)
+ m.Save("sendTSOk", &x.sendTSOk)
+ m.Save("recentTS", &x.recentTS)
+ m.Save("tsOffset", &x.tsOffset)
+ m.Save("shutdownFlags", &x.shutdownFlags)
+ m.Save("sackPermitted", &x.sackPermitted)
+ m.Save("sack", &x.sack)
+ m.Save("reusePort", &x.reusePort)
+ m.Save("delay", &x.delay)
+ m.Save("cork", &x.cork)
+ m.Save("scoreboard", &x.scoreboard)
+ m.Save("reuseAddr", &x.reuseAddr)
+ m.Save("slowAck", &x.slowAck)
+ m.Save("segmentQueue", &x.segmentQueue)
+ m.Save("synRcvdCount", &x.synRcvdCount)
+ m.Save("userMSS", &x.userMSS)
+ m.Save("sndBufSize", &x.sndBufSize)
+ m.Save("sndBufUsed", &x.sndBufUsed)
+ m.Save("sndClosed", &x.sndClosed)
+ m.Save("sndBufInQueue", &x.sndBufInQueue)
+ m.Save("sndQueue", &x.sndQueue)
+ m.Save("cc", &x.cc)
+ m.Save("packetTooBigCount", &x.packetTooBigCount)
+ m.Save("sndMTU", &x.sndMTU)
+ m.Save("keepalive", &x.keepalive)
+ m.Save("rcv", &x.rcv)
+ m.Save("snd", &x.snd)
+ m.Save("bindAddress", &x.bindAddress)
+ m.Save("connectingAddress", &x.connectingAddress)
+ m.Save("amss", &x.amss)
+ m.Save("gso", &x.gso)
+}
+
+func (x *endpoint) load(m state.Map) {
+ m.Load("netProto", &x.netProto)
+ m.LoadWait("waiterQueue", &x.waiterQueue)
+ m.LoadWait("rcvList", &x.rcvList)
+ m.Load("rcvClosed", &x.rcvClosed)
+ m.Load("rcvBufSize", &x.rcvBufSize)
+ m.Load("rcvBufUsed", &x.rcvBufUsed)
+ m.Load("rcvAutoParams", &x.rcvAutoParams)
+ m.Load("zeroWindow", &x.zeroWindow)
+ m.Load("id", &x.id)
+ m.Load("isRegistered", &x.isRegistered)
+ m.Load("v6only", &x.v6only)
+ m.Load("isConnectNotified", &x.isConnectNotified)
+ m.Load("broadcast", &x.broadcast)
+ m.Load("workerRunning", &x.workerRunning)
+ m.Load("workerCleanup", &x.workerCleanup)
+ m.Load("sendTSOk", &x.sendTSOk)
+ m.Load("recentTS", &x.recentTS)
+ m.Load("tsOffset", &x.tsOffset)
+ m.Load("shutdownFlags", &x.shutdownFlags)
+ m.Load("sackPermitted", &x.sackPermitted)
+ m.Load("sack", &x.sack)
+ m.Load("reusePort", &x.reusePort)
+ m.Load("delay", &x.delay)
+ m.Load("cork", &x.cork)
+ m.Load("scoreboard", &x.scoreboard)
+ m.Load("reuseAddr", &x.reuseAddr)
+ m.Load("slowAck", &x.slowAck)
+ m.LoadWait("segmentQueue", &x.segmentQueue)
+ m.Load("synRcvdCount", &x.synRcvdCount)
+ m.Load("userMSS", &x.userMSS)
+ m.Load("sndBufSize", &x.sndBufSize)
+ m.Load("sndBufUsed", &x.sndBufUsed)
+ m.Load("sndClosed", &x.sndClosed)
+ m.Load("sndBufInQueue", &x.sndBufInQueue)
+ m.LoadWait("sndQueue", &x.sndQueue)
+ m.Load("cc", &x.cc)
+ m.Load("packetTooBigCount", &x.packetTooBigCount)
+ m.Load("sndMTU", &x.sndMTU)
+ m.Load("keepalive", &x.keepalive)
+ m.LoadWait("rcv", &x.rcv)
+ m.LoadWait("snd", &x.snd)
+ m.Load("bindAddress", &x.bindAddress)
+ m.Load("connectingAddress", &x.connectingAddress)
+ m.Load("amss", &x.amss)
+ m.Load("gso", &x.gso)
+ m.LoadValue("lastError", new(string), func(y interface{}) { x.loadLastError(y.(string)) })
+ m.LoadValue("state", new(EndpointState), func(y interface{}) { x.loadState(y.(EndpointState)) })
+ m.LoadValue("hardError", new(string), func(y interface{}) { x.loadHardError(y.(string)) })
+ m.LoadValue("acceptedChan", new([]*endpoint), func(y interface{}) { x.loadAcceptedChan(y.([]*endpoint)) })
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *keepalive) beforeSave() {}
+func (x *keepalive) save(m state.Map) {
+ x.beforeSave()
+ m.Save("enabled", &x.enabled)
+ m.Save("idle", &x.idle)
+ m.Save("interval", &x.interval)
+ m.Save("count", &x.count)
+ m.Save("unacked", &x.unacked)
+}
+
+func (x *keepalive) afterLoad() {}
+func (x *keepalive) load(m state.Map) {
+ m.Load("enabled", &x.enabled)
+ m.Load("idle", &x.idle)
+ m.Load("interval", &x.interval)
+ m.Load("count", &x.count)
+ m.Load("unacked", &x.unacked)
+}
+
+func (x *receiver) beforeSave() {}
+func (x *receiver) save(m state.Map) {
+ x.beforeSave()
+ m.Save("ep", &x.ep)
+ m.Save("rcvNxt", &x.rcvNxt)
+ m.Save("rcvAcc", &x.rcvAcc)
+ m.Save("rcvWnd", &x.rcvWnd)
+ m.Save("rcvWndScale", &x.rcvWndScale)
+ m.Save("closed", &x.closed)
+ m.Save("pendingRcvdSegments", &x.pendingRcvdSegments)
+ m.Save("pendingBufUsed", &x.pendingBufUsed)
+ m.Save("pendingBufSize", &x.pendingBufSize)
+}
+
+func (x *receiver) afterLoad() {}
+func (x *receiver) load(m state.Map) {
+ m.Load("ep", &x.ep)
+ m.Load("rcvNxt", &x.rcvNxt)
+ m.Load("rcvAcc", &x.rcvAcc)
+ m.Load("rcvWnd", &x.rcvWnd)
+ m.Load("rcvWndScale", &x.rcvWndScale)
+ m.Load("closed", &x.closed)
+ m.Load("pendingRcvdSegments", &x.pendingRcvdSegments)
+ m.Load("pendingBufUsed", &x.pendingBufUsed)
+ m.Load("pendingBufSize", &x.pendingBufSize)
+}
+
+func (x *renoState) beforeSave() {}
+func (x *renoState) save(m state.Map) {
+ x.beforeSave()
+ m.Save("s", &x.s)
+}
+
+func (x *renoState) afterLoad() {}
+func (x *renoState) load(m state.Map) {
+ m.Load("s", &x.s)
+}
+
+func (x *SACKScoreboard) beforeSave() {}
+func (x *SACKScoreboard) save(m state.Map) {
+ x.beforeSave()
+ m.Save("smss", &x.smss)
+ m.Save("maxSACKED", &x.maxSACKED)
+}
+
+func (x *SACKScoreboard) afterLoad() {}
+func (x *SACKScoreboard) load(m state.Map) {
+ m.Load("smss", &x.smss)
+ m.Load("maxSACKED", &x.maxSACKED)
+}
+
+func (x *segment) beforeSave() {}
+func (x *segment) save(m state.Map) {
+ x.beforeSave()
+ var data buffer.VectorisedView = x.saveData()
+ m.SaveValue("data", data)
+ var options []byte = x.saveOptions()
+ m.SaveValue("options", options)
+ var rcvdTime unixTime = x.saveRcvdTime()
+ m.SaveValue("rcvdTime", rcvdTime)
+ var xmitTime unixTime = x.saveXmitTime()
+ m.SaveValue("xmitTime", xmitTime)
+ m.Save("segmentEntry", &x.segmentEntry)
+ m.Save("refCnt", &x.refCnt)
+ m.Save("viewToDeliver", &x.viewToDeliver)
+ m.Save("sequenceNumber", &x.sequenceNumber)
+ m.Save("ackNumber", &x.ackNumber)
+ m.Save("flags", &x.flags)
+ m.Save("window", &x.window)
+ m.Save("csum", &x.csum)
+ m.Save("csumValid", &x.csumValid)
+ m.Save("parsedOptions", &x.parsedOptions)
+ m.Save("hasNewSACKInfo", &x.hasNewSACKInfo)
+}
+
+func (x *segment) afterLoad() {}
+func (x *segment) load(m state.Map) {
+ m.Load("segmentEntry", &x.segmentEntry)
+ m.Load("refCnt", &x.refCnt)
+ m.Load("viewToDeliver", &x.viewToDeliver)
+ m.Load("sequenceNumber", &x.sequenceNumber)
+ m.Load("ackNumber", &x.ackNumber)
+ m.Load("flags", &x.flags)
+ m.Load("window", &x.window)
+ m.Load("csum", &x.csum)
+ m.Load("csumValid", &x.csumValid)
+ m.Load("parsedOptions", &x.parsedOptions)
+ m.Load("hasNewSACKInfo", &x.hasNewSACKInfo)
+ m.LoadValue("data", new(buffer.VectorisedView), func(y interface{}) { x.loadData(y.(buffer.VectorisedView)) })
+ m.LoadValue("options", new([]byte), func(y interface{}) { x.loadOptions(y.([]byte)) })
+ m.LoadValue("rcvdTime", new(unixTime), func(y interface{}) { x.loadRcvdTime(y.(unixTime)) })
+ m.LoadValue("xmitTime", new(unixTime), func(y interface{}) { x.loadXmitTime(y.(unixTime)) })
+}
+
+func (x *segmentQueue) beforeSave() {}
+func (x *segmentQueue) save(m state.Map) {
+ x.beforeSave()
+ m.Save("list", &x.list)
+ m.Save("limit", &x.limit)
+ m.Save("used", &x.used)
+}
+
+func (x *segmentQueue) afterLoad() {}
+func (x *segmentQueue) load(m state.Map) {
+ m.LoadWait("list", &x.list)
+ m.Load("limit", &x.limit)
+ m.Load("used", &x.used)
+}
+
+func (x *sender) beforeSave() {}
+func (x *sender) save(m state.Map) {
+ x.beforeSave()
+ var lastSendTime unixTime = x.saveLastSendTime()
+ m.SaveValue("lastSendTime", lastSendTime)
+ var rttMeasureTime unixTime = x.saveRttMeasureTime()
+ m.SaveValue("rttMeasureTime", rttMeasureTime)
+ m.Save("ep", &x.ep)
+ m.Save("dupAckCount", &x.dupAckCount)
+ m.Save("fr", &x.fr)
+ m.Save("sndCwnd", &x.sndCwnd)
+ m.Save("sndSsthresh", &x.sndSsthresh)
+ m.Save("sndCAAckCount", &x.sndCAAckCount)
+ m.Save("outstanding", &x.outstanding)
+ m.Save("sndWnd", &x.sndWnd)
+ m.Save("sndUna", &x.sndUna)
+ m.Save("sndNxt", &x.sndNxt)
+ m.Save("sndNxtList", &x.sndNxtList)
+ m.Save("rttMeasureSeqNum", &x.rttMeasureSeqNum)
+ m.Save("closed", &x.closed)
+ m.Save("writeNext", &x.writeNext)
+ m.Save("writeList", &x.writeList)
+ m.Save("rtt", &x.rtt)
+ m.Save("rto", &x.rto)
+ m.Save("maxPayloadSize", &x.maxPayloadSize)
+ m.Save("gso", &x.gso)
+ m.Save("sndWndScale", &x.sndWndScale)
+ m.Save("maxSentAck", &x.maxSentAck)
+ m.Save("state", &x.state)
+ m.Save("cc", &x.cc)
+}
+
+func (x *sender) load(m state.Map) {
+ m.Load("ep", &x.ep)
+ m.Load("dupAckCount", &x.dupAckCount)
+ m.Load("fr", &x.fr)
+ m.Load("sndCwnd", &x.sndCwnd)
+ m.Load("sndSsthresh", &x.sndSsthresh)
+ m.Load("sndCAAckCount", &x.sndCAAckCount)
+ m.Load("outstanding", &x.outstanding)
+ m.Load("sndWnd", &x.sndWnd)
+ m.Load("sndUna", &x.sndUna)
+ m.Load("sndNxt", &x.sndNxt)
+ m.Load("sndNxtList", &x.sndNxtList)
+ m.Load("rttMeasureSeqNum", &x.rttMeasureSeqNum)
+ m.Load("closed", &x.closed)
+ m.Load("writeNext", &x.writeNext)
+ m.Load("writeList", &x.writeList)
+ m.Load("rtt", &x.rtt)
+ m.Load("rto", &x.rto)
+ m.Load("maxPayloadSize", &x.maxPayloadSize)
+ m.Load("gso", &x.gso)
+ m.Load("sndWndScale", &x.sndWndScale)
+ m.Load("maxSentAck", &x.maxSentAck)
+ m.Load("state", &x.state)
+ m.Load("cc", &x.cc)
+ m.LoadValue("lastSendTime", new(unixTime), func(y interface{}) { x.loadLastSendTime(y.(unixTime)) })
+ m.LoadValue("rttMeasureTime", new(unixTime), func(y interface{}) { x.loadRttMeasureTime(y.(unixTime)) })
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *rtt) beforeSave() {}
+func (x *rtt) save(m state.Map) {
+ x.beforeSave()
+ m.Save("srtt", &x.srtt)
+ m.Save("rttvar", &x.rttvar)
+ m.Save("srttInited", &x.srttInited)
+}
+
+func (x *rtt) afterLoad() {}
+func (x *rtt) load(m state.Map) {
+ m.Load("srtt", &x.srtt)
+ m.Load("rttvar", &x.rttvar)
+ m.Load("srttInited", &x.srttInited)
+}
+
+func (x *fastRecovery) beforeSave() {}
+func (x *fastRecovery) save(m state.Map) {
+ x.beforeSave()
+ m.Save("active", &x.active)
+ m.Save("first", &x.first)
+ m.Save("last", &x.last)
+ m.Save("maxCwnd", &x.maxCwnd)
+ m.Save("highRxt", &x.highRxt)
+ m.Save("rescueRxt", &x.rescueRxt)
+}
+
+func (x *fastRecovery) afterLoad() {}
+func (x *fastRecovery) load(m state.Map) {
+ m.Load("active", &x.active)
+ m.Load("first", &x.first)
+ m.Load("last", &x.last)
+ m.Load("maxCwnd", &x.maxCwnd)
+ m.Load("highRxt", &x.highRxt)
+ m.Load("rescueRxt", &x.rescueRxt)
+}
+
+func (x *unixTime) beforeSave() {}
+func (x *unixTime) save(m state.Map) {
+ x.beforeSave()
+ m.Save("second", &x.second)
+ m.Save("nano", &x.nano)
+}
+
+func (x *unixTime) afterLoad() {}
+func (x *unixTime) load(m state.Map) {
+ m.Load("second", &x.second)
+ m.Load("nano", &x.nano)
+}
+
+func (x *segmentList) beforeSave() {}
+func (x *segmentList) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *segmentList) afterLoad() {}
+func (x *segmentList) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *segmentEntry) beforeSave() {}
+func (x *segmentEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *segmentEntry) afterLoad() {}
+func (x *segmentEntry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func init() {
+ state.Register("tcp.cubicState", (*cubicState)(nil), state.Fns{Save: (*cubicState).save, Load: (*cubicState).load})
+ state.Register("tcp.SACKInfo", (*SACKInfo)(nil), state.Fns{Save: (*SACKInfo).save, Load: (*SACKInfo).load})
+ state.Register("tcp.rcvBufAutoTuneParams", (*rcvBufAutoTuneParams)(nil), state.Fns{Save: (*rcvBufAutoTuneParams).save, Load: (*rcvBufAutoTuneParams).load})
+ state.Register("tcp.endpoint", (*endpoint)(nil), state.Fns{Save: (*endpoint).save, Load: (*endpoint).load})
+ state.Register("tcp.keepalive", (*keepalive)(nil), state.Fns{Save: (*keepalive).save, Load: (*keepalive).load})
+ state.Register("tcp.receiver", (*receiver)(nil), state.Fns{Save: (*receiver).save, Load: (*receiver).load})
+ state.Register("tcp.renoState", (*renoState)(nil), state.Fns{Save: (*renoState).save, Load: (*renoState).load})
+ state.Register("tcp.SACKScoreboard", (*SACKScoreboard)(nil), state.Fns{Save: (*SACKScoreboard).save, Load: (*SACKScoreboard).load})
+ state.Register("tcp.segment", (*segment)(nil), state.Fns{Save: (*segment).save, Load: (*segment).load})
+ state.Register("tcp.segmentQueue", (*segmentQueue)(nil), state.Fns{Save: (*segmentQueue).save, Load: (*segmentQueue).load})
+ state.Register("tcp.sender", (*sender)(nil), state.Fns{Save: (*sender).save, Load: (*sender).load})
+ state.Register("tcp.rtt", (*rtt)(nil), state.Fns{Save: (*rtt).save, Load: (*rtt).load})
+ state.Register("tcp.fastRecovery", (*fastRecovery)(nil), state.Fns{Save: (*fastRecovery).save, Load: (*fastRecovery).load})
+ state.Register("tcp.unixTime", (*unixTime)(nil), state.Fns{Save: (*unixTime).save, Load: (*unixTime).load})
+ state.Register("tcp.segmentList", (*segmentList)(nil), state.Fns{Save: (*segmentList).save, Load: (*segmentList).load})
+ state.Register("tcp.segmentEntry", (*segmentEntry)(nil), state.Fns{Save: (*segmentEntry).save, Load: (*segmentEntry).load})
+}
diff --git a/pkg/tcpip/transport/tcp/tcp_test.go b/pkg/tcpip/transport/tcp/tcp_test.go
deleted file mode 100644
index 32bb45224..000000000
--- a/pkg/tcpip/transport/tcp/tcp_test.go
+++ /dev/null
@@ -1,4370 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tcp_test
-
-import (
- "bytes"
- "fmt"
- "math"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/checker"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/link/loopback"
- "gvisor.dev/gvisor/pkg/tcpip/link/sniffer"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
- "gvisor.dev/gvisor/pkg/tcpip/ports"
- "gvisor.dev/gvisor/pkg/tcpip/seqnum"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
- "gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
- "gvisor.dev/gvisor/pkg/tcpip/transport/tcp/testing/context"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-const (
- // defaultMTU is the MTU, in bytes, used throughout the tests, except
- // where another value is explicitly used. It is chosen to match the MTU
- // of loopback interfaces on linux systems.
- defaultMTU = 65535
-
- // defaultIPv4MSS is the MSS sent by the network stack in SYN/SYN-ACK for an
- // IPv4 endpoint when the MTU is set to defaultMTU in the test.
- defaultIPv4MSS = defaultMTU - header.IPv4MinimumSize - header.TCPMinimumSize
-)
-
-func TestGiveUpConnect(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- var wq waiter.Queue
- ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &wq)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- // Register for notification, then start connection attempt.
- waitEntry, notifyCh := waiter.NewChannelEntry(nil)
- wq.EventRegister(&waitEntry, waiter.EventOut)
- defer wq.EventUnregister(&waitEntry)
-
- if err := ep.Connect(tcpip.FullAddress{Addr: context.TestAddr, Port: context.TestPort}); err != tcpip.ErrConnectStarted {
- t.Fatalf("got ep.Connect(...) = %v, want = %v", err, tcpip.ErrConnectStarted)
- }
-
- // Close the connection, wait for completion.
- ep.Close()
-
- // Wait for ep to become writable.
- <-notifyCh
- if err := ep.GetSockOpt(tcpip.ErrorOption{}); err != tcpip.ErrAborted {
- t.Fatalf("got ep.GetSockOpt(tcpip.ErrorOption{}) = %v, want = %v", err, tcpip.ErrAborted)
- }
-}
-
-func TestConnectIncrementActiveConnection(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- stats := c.Stack().Stats()
- want := stats.TCP.ActiveConnectionOpenings.Value() + 1
-
- c.CreateConnected(789, 30000, nil)
- if got := stats.TCP.ActiveConnectionOpenings.Value(); got != want {
- t.Errorf("got stats.TCP.ActtiveConnectionOpenings.Value() = %v, want = %v", got, want)
- }
-}
-
-func TestConnectDoesNotIncrementFailedConnectionAttempts(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- stats := c.Stack().Stats()
- want := stats.TCP.FailedConnectionAttempts.Value()
-
- c.CreateConnected(789, 30000, nil)
- if got := stats.TCP.FailedConnectionAttempts.Value(); got != want {
- t.Errorf("got stats.TCP.FailedConnectionOpenings.Value() = %v, want = %v", got, want)
- }
-}
-
-func TestActiveFailedConnectionAttemptIncrement(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- stats := c.Stack().Stats()
- ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
- c.EP = ep
- want := stats.TCP.FailedConnectionAttempts.Value() + 1
-
- if err := c.EP.Connect(tcpip.FullAddress{NIC: 2, Addr: context.TestAddr, Port: context.TestPort}); err != tcpip.ErrNoRoute {
- t.Errorf("got c.EP.Connect(...) = %v, want = %v", err, tcpip.ErrNoRoute)
- }
-
- if got := stats.TCP.FailedConnectionAttempts.Value(); got != want {
- t.Errorf("got stats.TCP.FailedConnectionAttempts.Value() = %v, want = %v", got, want)
- }
-}
-
-func TestTCPSegmentsSentIncrement(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- stats := c.Stack().Stats()
- // SYN and ACK
- want := stats.TCP.SegmentsSent.Value() + 2
- c.CreateConnected(789, 30000, nil)
-
- if got := stats.TCP.SegmentsSent.Value(); got != want {
- t.Errorf("got stats.TCP.SegmentsSent.Value() = %v, want = %v", got, want)
- }
-}
-
-func TestTCPResetsSentIncrement(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
- stats := c.Stack().Stats()
- wq := &waiter.Queue{}
- ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
- want := stats.TCP.SegmentsSent.Value() + 1
-
- if err := ep.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- if err := ep.Listen(10); err != nil {
- t.Fatalf("Listen failed: %v", err)
- }
-
- // Send a SYN request.
- iss := seqnum.Value(789)
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: context.StackPort,
- Flags: header.TCPFlagSyn,
- SeqNum: iss,
- })
-
- // Receive the SYN-ACK reply.
- b := c.GetPacket()
- tcpHdr := header.TCP(header.IPv4(b).Payload())
- c.IRS = seqnum.Value(tcpHdr.SequenceNumber())
-
- ackHeaders := &context.Headers{
- SrcPort: context.TestPort,
- DstPort: context.StackPort,
- Flags: header.TCPFlagAck,
- SeqNum: iss + 1,
- // If the AckNum is not the increment of the last sequence number, a RST
- // segment is sent back in response.
- AckNum: c.IRS + 2,
- }
-
- // Send ACK.
- c.SendPacket(nil, ackHeaders)
-
- c.GetPacket()
- if got := stats.TCP.ResetsSent.Value(); got != want {
- t.Errorf("got stats.TCP.ResetsSent.Value() = %v, want = %v", got, want)
- }
-}
-
-// TestTCPResetSentForACKWhenNotUsingSynCookies checks that the stack generates
-// a RST if an ACK is received on the listening socket for which there is no
-// active handshake in progress and we are not using SYN cookies.
-func TestTCPResetSentForACKWhenNotUsingSynCookies(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- wq := &waiter.Queue{}
- ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
- if err := ep.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- if err := ep.Listen(10); err != nil {
- t.Fatalf("Listen failed: %v", err)
- }
-
- // Send a SYN request.
- iss := seqnum.Value(789)
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: context.StackPort,
- Flags: header.TCPFlagSyn,
- SeqNum: iss,
- })
-
- // Receive the SYN-ACK reply.
- b := c.GetPacket()
- tcpHdr := header.TCP(header.IPv4(b).Payload())
- c.IRS = seqnum.Value(tcpHdr.SequenceNumber())
-
- ackHeaders := &context.Headers{
- SrcPort: context.TestPort,
- DstPort: context.StackPort,
- Flags: header.TCPFlagAck,
- SeqNum: iss + 1,
- AckNum: c.IRS + 1,
- }
-
- // Send ACK.
- c.SendPacket(nil, ackHeaders)
-
- // Try to accept the connection.
- we, ch := waiter.NewChannelEntry(nil)
- wq.EventRegister(&we, waiter.EventIn)
- defer wq.EventUnregister(&we)
-
- c.EP, _, err = ep.Accept()
- if err == tcpip.ErrWouldBlock {
- // Wait for connection to be established.
- select {
- case <-ch:
- c.EP, _, err = ep.Accept()
- if err != nil {
- t.Fatalf("Accept failed: %v", err)
- }
-
- case <-time.After(1 * time.Second):
- t.Fatalf("Timed out waiting for accept")
- }
- }
-
- c.EP.Close()
- checker.IPv4(t, c.GetPacket(), checker.TCP(
- checker.SrcPort(context.StackPort),
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS+1)),
- checker.AckNum(uint32(iss)+1),
- checker.TCPFlags(header.TCPFlagFin|header.TCPFlagAck)))
-
- finHeaders := &context.Headers{
- SrcPort: context.TestPort,
- DstPort: context.StackPort,
- Flags: header.TCPFlagAck | header.TCPFlagFin,
- SeqNum: iss + 1,
- AckNum: c.IRS + 2,
- }
-
- c.SendPacket(nil, finHeaders)
-
- // Get the ACK to the FIN we just sent.
- c.GetPacket()
-
- // Now resend the same ACK, this ACK should generate a RST as there
- // should be no endpoint in SYN-RCVD state and we are not using
- // syn-cookies yet. The reason we send the same ACK is we need a valid
- // cookie(IRS) generated by the netstack without which the ACK will be
- // rejected.
- c.SendPacket(nil, ackHeaders)
-
- checker.IPv4(t, c.GetPacket(), checker.TCP(
- checker.SrcPort(context.StackPort),
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS+1)),
- checker.AckNum(uint32(iss)+1),
- checker.TCPFlags(header.TCPFlagRst|header.TCPFlagAck)))
-}
-
-func TestTCPResetsReceivedIncrement(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- stats := c.Stack().Stats()
- want := stats.TCP.ResetsReceived.Value() + 1
- iss := seqnum.Value(789)
- rcvWnd := seqnum.Size(30000)
- c.CreateConnected(iss, rcvWnd, nil)
-
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- SeqNum: iss.Add(1),
- AckNum: c.IRS.Add(1),
- RcvWnd: rcvWnd,
- Flags: header.TCPFlagRst,
- })
-
- if got := stats.TCP.ResetsReceived.Value(); got != want {
- t.Errorf("got stats.TCP.ResetsReceived.Value() = %v, want = %v", got, want)
- }
-}
-
-func TestTCPResetsDoNotGenerateResets(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- stats := c.Stack().Stats()
- want := stats.TCP.ResetsReceived.Value() + 1
- iss := seqnum.Value(789)
- rcvWnd := seqnum.Size(30000)
- c.CreateConnected(iss, rcvWnd, nil)
-
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- SeqNum: iss.Add(1),
- AckNum: c.IRS.Add(1),
- RcvWnd: rcvWnd,
- Flags: header.TCPFlagRst,
- })
-
- if got := stats.TCP.ResetsReceived.Value(); got != want {
- t.Errorf("got stats.TCP.ResetsReceived.Value() = %v, want = %v", got, want)
- }
- c.CheckNoPacketTimeout("got an unexpected packet", 100*time.Millisecond)
-}
-
-func TestActiveHandshake(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-}
-
-func TestNonBlockingClose(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
- ep := c.EP
- c.EP = nil
-
- // Close the endpoint and measure how long it takes.
- t0 := time.Now()
- ep.Close()
- if diff := time.Now().Sub(t0); diff > 3*time.Second {
- t.Fatalf("Took too long to close: %v", diff)
- }
-}
-
-func TestConnectResetAfterClose(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
- ep := c.EP
- c.EP = nil
-
- // Close the endpoint, make sure we get a FIN segment, then acknowledge
- // to complete closure of sender, but don't send our own FIN.
- ep.Close()
- checker.IPv4(t, c.GetPacket(),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(790),
- checker.TCPFlags(header.TCPFlagAck|header.TCPFlagFin),
- ),
- )
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 790,
- AckNum: c.IRS.Add(1),
- RcvWnd: 30000,
- })
-
- // Wait for the ep to give up waiting for a FIN, and send a RST.
- time.Sleep(3 * time.Second)
- for {
- b := c.GetPacket()
- tcpHdr := header.TCP(header.IPv4(b).Payload())
- if tcpHdr.Flags() == header.TCPFlagAck|header.TCPFlagFin {
- // This is a retransmit of the FIN, ignore it.
- continue
- }
-
- checker.IPv4(t, b,
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(790),
- checker.TCPFlags(header.TCPFlagAck|header.TCPFlagRst),
- ),
- )
- break
- }
-}
-
-func TestSimpleReceive(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-
- we, ch := waiter.NewChannelEntry(nil)
- c.WQ.EventRegister(&we, waiter.EventIn)
- defer c.WQ.EventUnregister(&we)
-
- if _, _, err := c.EP.Read(nil); err != tcpip.ErrWouldBlock {
- t.Fatalf("got c.EP.Read(nil) = %v, want = %v", err, tcpip.ErrWouldBlock)
- }
-
- data := []byte{1, 2, 3}
- c.SendPacket(data, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 790,
- AckNum: c.IRS.Add(1),
- RcvWnd: 30000,
- })
-
- // Wait for receive to be notified.
- select {
- case <-ch:
- case <-time.After(1 * time.Second):
- t.Fatalf("Timed out waiting for data to arrive")
- }
-
- // Receive data.
- v, _, err := c.EP.Read(nil)
- if err != nil {
- t.Fatalf("Read failed: %v", err)
- }
-
- if !bytes.Equal(data, v) {
- t.Fatalf("got data = %v, want = %v", v, data)
- }
-
- // Check that ACK is received.
- checker.IPv4(t, c.GetPacket(),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(uint32(790+len(data))),
- checker.TCPFlags(header.TCPFlagAck),
- ),
- )
-}
-
-func TestOutOfOrderReceive(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-
- we, ch := waiter.NewChannelEntry(nil)
- c.WQ.EventRegister(&we, waiter.EventIn)
- defer c.WQ.EventUnregister(&we)
-
- if _, _, err := c.EP.Read(nil); err != tcpip.ErrWouldBlock {
- t.Fatalf("got c.EP.Read(nil) = %v, want = %v", err, tcpip.ErrWouldBlock)
- }
-
- // Send second half of data first, with seqnum 3 ahead of expected.
- data := []byte{1, 2, 3, 4, 5, 6}
- c.SendPacket(data[3:], &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 793,
- AckNum: c.IRS.Add(1),
- RcvWnd: 30000,
- })
-
- // Check that we get an ACK specifying which seqnum is expected.
- checker.IPv4(t, c.GetPacket(),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(790),
- checker.TCPFlags(header.TCPFlagAck),
- ),
- )
-
- // Wait 200ms and check that no data has been received.
- time.Sleep(200 * time.Millisecond)
- if _, _, err := c.EP.Read(nil); err != tcpip.ErrWouldBlock {
- t.Fatalf("got c.EP.Read(nil) = %v, want = %v", err, tcpip.ErrWouldBlock)
- }
-
- // Send the first 3 bytes now.
- c.SendPacket(data[:3], &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 790,
- AckNum: c.IRS.Add(1),
- RcvWnd: 30000,
- })
-
- // Receive data.
- read := make([]byte, 0, 6)
- for len(read) < len(data) {
- v, _, err := c.EP.Read(nil)
- if err != nil {
- if err == tcpip.ErrWouldBlock {
- // Wait for receive to be notified.
- select {
- case <-ch:
- case <-time.After(5 * time.Second):
- t.Fatalf("Timed out waiting for data to arrive")
- }
- continue
- }
- t.Fatalf("Read failed: %v", err)
- }
-
- read = append(read, v...)
- }
-
- // Check that we received the data in proper order.
- if !bytes.Equal(data, read) {
- t.Fatalf("got data = %v, want = %v", read, data)
- }
-
- // Check that the whole data is acknowledged.
- checker.IPv4(t, c.GetPacket(),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(uint32(790+len(data))),
- checker.TCPFlags(header.TCPFlagAck),
- ),
- )
-}
-
-func TestOutOfOrderFlood(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- // Create a new connection with initial window size of 10.
- opt := tcpip.ReceiveBufferSizeOption(10)
- c.CreateConnected(789, 30000, &opt)
-
- if _, _, err := c.EP.Read(nil); err != tcpip.ErrWouldBlock {
- t.Fatalf("got c.EP.Read(nil) = %v, want = %v", err, tcpip.ErrWouldBlock)
- }
-
- // Send 100 packets before the actual one that is expected.
- data := []byte{1, 2, 3, 4, 5, 6}
- for i := 0; i < 100; i++ {
- c.SendPacket(data[3:], &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 796,
- AckNum: c.IRS.Add(1),
- RcvWnd: 30000,
- })
-
- checker.IPv4(t, c.GetPacket(),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(790),
- checker.TCPFlags(header.TCPFlagAck),
- ),
- )
- }
-
- // Send packet with seqnum 793. It must be discarded because the
- // out-of-order buffer was filled by the previous packets.
- c.SendPacket(data[3:], &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 793,
- AckNum: c.IRS.Add(1),
- RcvWnd: 30000,
- })
-
- checker.IPv4(t, c.GetPacket(),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(790),
- checker.TCPFlags(header.TCPFlagAck),
- ),
- )
-
- // Now send the expected packet, seqnum 790.
- c.SendPacket(data[:3], &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 790,
- AckNum: c.IRS.Add(1),
- RcvWnd: 30000,
- })
-
- // Check that only packet 790 is acknowledged.
- checker.IPv4(t, c.GetPacket(),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(793),
- checker.TCPFlags(header.TCPFlagAck),
- ),
- )
-}
-
-func TestRstOnCloseWithUnreadData(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-
- we, ch := waiter.NewChannelEntry(nil)
- c.WQ.EventRegister(&we, waiter.EventIn)
- defer c.WQ.EventUnregister(&we)
-
- if _, _, err := c.EP.Read(nil); err != tcpip.ErrWouldBlock {
- t.Fatalf("got c.EP.Read(nil) = %v, want = %v", err, tcpip.ErrWouldBlock)
- }
-
- data := []byte{1, 2, 3}
- c.SendPacket(data, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 790,
- AckNum: c.IRS.Add(1),
- RcvWnd: 30000,
- })
-
- // Wait for receive to be notified.
- select {
- case <-ch:
- case <-time.After(3 * time.Second):
- t.Fatalf("Timed out waiting for data to arrive")
- }
-
- // Check that ACK is received, this happens regardless of the read.
- checker.IPv4(t, c.GetPacket(),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(uint32(790+len(data))),
- checker.TCPFlags(header.TCPFlagAck),
- ),
- )
-
- // Now that we know we have unread data, let's just close the connection
- // and verify that netstack sends an RST rather than a FIN.
- c.EP.Close()
-
- checker.IPv4(t, c.GetPacket(),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.TCPFlags(header.TCPFlagAck|header.TCPFlagRst),
- // We shouldn't consume a sequence number on RST.
- checker.SeqNum(uint32(c.IRS)+1),
- ))
- // The RST puts the endpoint into an error state.
- if got, want := tcp.EndpointState(c.EP.State()), tcp.StateError; got != want {
- t.Errorf("Unexpected endpoint state: want %v, got %v", want, got)
- }
-
- // This final ACK should be ignored because an ACK on a reset doesn't mean
- // anything.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: seqnum.Value(790 + len(data)),
- AckNum: c.IRS.Add(seqnum.Size(2)),
- RcvWnd: 30000,
- })
-}
-
-func TestRstOnCloseWithUnreadDataFinConvertRst(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-
- we, ch := waiter.NewChannelEntry(nil)
- c.WQ.EventRegister(&we, waiter.EventIn)
- defer c.WQ.EventUnregister(&we)
-
- if _, _, err := c.EP.Read(nil); err != tcpip.ErrWouldBlock {
- t.Fatalf("got c.EP.Read(nil) = %v, want = %v", err, tcpip.ErrWouldBlock)
- }
-
- data := []byte{1, 2, 3}
- c.SendPacket(data, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 790,
- AckNum: c.IRS.Add(1),
- RcvWnd: 30000,
- })
-
- // Wait for receive to be notified.
- select {
- case <-ch:
- case <-time.After(3 * time.Second):
- t.Fatalf("Timed out waiting for data to arrive")
- }
-
- // Check that ACK is received, this happens regardless of the read.
- checker.IPv4(t, c.GetPacket(),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(uint32(790+len(data))),
- checker.TCPFlags(header.TCPFlagAck),
- ),
- )
-
- // Cause a FIN to be generated.
- c.EP.Shutdown(tcpip.ShutdownWrite)
-
- // Make sure we get the FIN but DON't ACK IT.
- checker.IPv4(t, c.GetPacket(),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.TCPFlags(header.TCPFlagAck|header.TCPFlagFin),
- checker.SeqNum(uint32(c.IRS)+1),
- ))
-
- if got, want := tcp.EndpointState(c.EP.State()), tcp.StateFinWait1; got != want {
- t.Errorf("Unexpected endpoint state: want %v, got %v", want, got)
- }
-
- // Cause a RST to be generated by closing the read end now since we have
- // unread data.
- c.EP.Shutdown(tcpip.ShutdownRead)
-
- // Make sure we get the RST
- checker.IPv4(t, c.GetPacket(),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.TCPFlags(header.TCPFlagAck|header.TCPFlagRst),
- // We shouldn't consume a sequence number on RST.
- checker.SeqNum(uint32(c.IRS)+1),
- ))
- // The RST puts the endpoint into an error state.
- if got, want := tcp.EndpointState(c.EP.State()), tcp.StateError; got != want {
- t.Errorf("Unexpected endpoint state: want %v, got %v", want, got)
- }
-
- // The ACK to the FIN should now be rejected since the connection has been
- // closed by a RST.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: seqnum.Value(790 + len(data)),
- AckNum: c.IRS.Add(seqnum.Size(2)),
- RcvWnd: 30000,
- })
-}
-
-func TestShutdownRead(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-
- if _, _, err := c.EP.Read(nil); err != tcpip.ErrWouldBlock {
- t.Fatalf("got c.EP.Read(nil) = %v, want = %v", err, tcpip.ErrWouldBlock)
- }
-
- if err := c.EP.Shutdown(tcpip.ShutdownRead); err != nil {
- t.Fatalf("Shutdown failed: %v", err)
- }
-
- if _, _, err := c.EP.Read(nil); err != tcpip.ErrClosedForReceive {
- t.Fatalf("got c.EP.Read(nil) = %v, want = %v", err, tcpip.ErrClosedForReceive)
- }
-}
-
-func TestFullWindowReceive(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- opt := tcpip.ReceiveBufferSizeOption(10)
- c.CreateConnected(789, 30000, &opt)
-
- we, ch := waiter.NewChannelEntry(nil)
- c.WQ.EventRegister(&we, waiter.EventIn)
- defer c.WQ.EventUnregister(&we)
-
- _, _, err := c.EP.Read(nil)
- if err != tcpip.ErrWouldBlock {
- t.Fatalf("Read failed: %v", err)
- }
-
- // Fill up the window.
- data := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
- c.SendPacket(data, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 790,
- AckNum: c.IRS.Add(1),
- RcvWnd: 30000,
- })
-
- // Wait for receive to be notified.
- select {
- case <-ch:
- case <-time.After(5 * time.Second):
- t.Fatalf("Timed out waiting for data to arrive")
- }
-
- // Check that data is acknowledged, and window goes to zero.
- checker.IPv4(t, c.GetPacket(),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(uint32(790+len(data))),
- checker.TCPFlags(header.TCPFlagAck),
- checker.Window(0),
- ),
- )
-
- // Receive data and check it.
- v, _, err := c.EP.Read(nil)
- if err != nil {
- t.Fatalf("Read failed: %v", err)
- }
-
- if !bytes.Equal(data, v) {
- t.Fatalf("got data = %v, want = %v", v, data)
- }
-
- // Check that we get an ACK for the newly non-zero window.
- checker.IPv4(t, c.GetPacket(),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(uint32(790+len(data))),
- checker.TCPFlags(header.TCPFlagAck),
- checker.Window(10),
- ),
- )
-}
-
-func TestNoWindowShrinking(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- // Start off with a window size of 10, then shrink it to 5.
- opt := tcpip.ReceiveBufferSizeOption(10)
- c.CreateConnected(789, 30000, &opt)
-
- opt = 5
- if err := c.EP.SetSockOpt(opt); err != nil {
- t.Fatalf("SetSockOpt failed: %v", err)
- }
-
- we, ch := waiter.NewChannelEntry(nil)
- c.WQ.EventRegister(&we, waiter.EventIn)
- defer c.WQ.EventUnregister(&we)
-
- if _, _, err := c.EP.Read(nil); err != tcpip.ErrWouldBlock {
- t.Fatalf("got c.EP.Read(nil) = %v, want = %v", err, tcpip.ErrWouldBlock)
- }
-
- // Send 3 bytes, check that the peer acknowledges them.
- data := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
- c.SendPacket(data[:3], &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 790,
- AckNum: c.IRS.Add(1),
- RcvWnd: 30000,
- })
-
- // Wait for receive to be notified.
- select {
- case <-ch:
- case <-time.After(5 * time.Second):
- t.Fatalf("Timed out waiting for data to arrive")
- }
-
- // Check that data is acknowledged, and that window doesn't go to zero
- // just yet because it was previously set to 10. It must go to 7 now.
- checker.IPv4(t, c.GetPacket(),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(793),
- checker.TCPFlags(header.TCPFlagAck),
- checker.Window(7),
- ),
- )
-
- // Send 7 more bytes, check that the window fills up.
- c.SendPacket(data[3:], &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 793,
- AckNum: c.IRS.Add(1),
- RcvWnd: 30000,
- })
-
- select {
- case <-ch:
- case <-time.After(5 * time.Second):
- t.Fatalf("Timed out waiting for data to arrive")
- }
-
- checker.IPv4(t, c.GetPacket(),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(uint32(790+len(data))),
- checker.TCPFlags(header.TCPFlagAck),
- checker.Window(0),
- ),
- )
-
- // Receive data and check it.
- read := make([]byte, 0, 10)
- for len(read) < len(data) {
- v, _, err := c.EP.Read(nil)
- if err != nil {
- t.Fatalf("Read failed: %v", err)
- }
-
- read = append(read, v...)
- }
-
- if !bytes.Equal(data, read) {
- t.Fatalf("got data = %v, want = %v", read, data)
- }
-
- // Check that we get an ACK for the newly non-zero window, which is the
- // new size.
- checker.IPv4(t, c.GetPacket(),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(uint32(790+len(data))),
- checker.TCPFlags(header.TCPFlagAck),
- checker.Window(5),
- ),
- )
-}
-
-func TestSimpleSend(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-
- data := []byte{1, 2, 3}
- view := buffer.NewView(len(data))
- copy(view, data)
-
- if _, _, err := c.EP.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
-
- // Check that data is received.
- b := c.GetPacket()
- checker.IPv4(t, b,
- checker.PayloadLen(len(data)+header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(790),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
-
- if p := b[header.IPv4MinimumSize+header.TCPMinimumSize:]; !bytes.Equal(data, p) {
- t.Fatalf("got data = %v, want = %v", p, data)
- }
-
- // Acknowledge the data.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 790,
- AckNum: c.IRS.Add(1 + seqnum.Size(len(data))),
- RcvWnd: 30000,
- })
-}
-
-func TestZeroWindowSend(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnected(789, 0, nil)
-
- data := []byte{1, 2, 3}
- view := buffer.NewView(len(data))
- copy(view, data)
-
- _, _, err := c.EP.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{})
- if err != nil {
- t.Fatalf("Write failed: %v", err)
- }
-
- // Since the window is currently zero, check that no packet is received.
- c.CheckNoPacket("Packet received when window is zero")
-
- // Open up the window. Data should be received now.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 790,
- AckNum: c.IRS.Add(1),
- RcvWnd: 30000,
- })
-
- // Check that data is received.
- b := c.GetPacket()
- checker.IPv4(t, b,
- checker.PayloadLen(len(data)+header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(790),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
-
- if p := b[header.IPv4MinimumSize+header.TCPMinimumSize:]; !bytes.Equal(data, p) {
- t.Fatalf("got data = %v, want = %v", p, data)
- }
-
- // Acknowledge the data.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 790,
- AckNum: c.IRS.Add(1 + seqnum.Size(len(data))),
- RcvWnd: 30000,
- })
-}
-
-func TestScaledWindowConnect(t *testing.T) {
- // This test ensures that window scaling is used when the peer
- // does advertise it and connection is established with Connect().
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- // Set the window size greater than the maximum non-scaled window.
- opt := tcpip.ReceiveBufferSizeOption(65535 * 3)
- c.CreateConnectedWithRawOptions(789, 30000, &opt, []byte{
- header.TCPOptionWS, 3, 0, header.TCPOptionNOP,
- })
-
- data := []byte{1, 2, 3}
- view := buffer.NewView(len(data))
- copy(view, data)
-
- if _, _, err := c.EP.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
-
- // Check that data is received, and that advertised window is 0xbfff,
- // that is, that it is scaled.
- b := c.GetPacket()
- checker.IPv4(t, b,
- checker.PayloadLen(len(data)+header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(790),
- checker.Window(0xbfff),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
-}
-
-func TestNonScaledWindowConnect(t *testing.T) {
- // This test ensures that window scaling is not used when the peer
- // doesn't advertise it and connection is established with Connect().
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- // Set the window size greater than the maximum non-scaled window.
- opt := tcpip.ReceiveBufferSizeOption(65535 * 3)
- c.CreateConnected(789, 30000, &opt)
-
- data := []byte{1, 2, 3}
- view := buffer.NewView(len(data))
- copy(view, data)
-
- if _, _, err := c.EP.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
-
- // Check that data is received, and that advertised window is 0xffff,
- // that is, that it's not scaled.
- b := c.GetPacket()
- checker.IPv4(t, b,
- checker.PayloadLen(len(data)+header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(790),
- checker.Window(0xffff),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
-}
-
-func TestScaledWindowAccept(t *testing.T) {
- // This test ensures that window scaling is used when the peer
- // does advertise it and connection is established with Accept().
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- // Create EP and start listening.
- wq := &waiter.Queue{}
- ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
- defer ep.Close()
-
- // Set the window size greater than the maximum non-scaled window.
- if err := ep.SetSockOpt(tcpip.ReceiveBufferSizeOption(65535 * 3)); err != nil {
- t.Fatalf("SetSockOpt failed failed: %v", err)
- }
-
- if err := ep.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- if err := ep.Listen(10); err != nil {
- t.Fatalf("Listen failed: %v", err)
- }
-
- // Do 3-way handshake.
- c.PassiveConnectWithOptions(100, 2, header.TCPSynOptions{MSS: defaultIPv4MSS})
-
- // Try to accept the connection.
- we, ch := waiter.NewChannelEntry(nil)
- wq.EventRegister(&we, waiter.EventIn)
- defer wq.EventUnregister(&we)
-
- c.EP, _, err = ep.Accept()
- if err == tcpip.ErrWouldBlock {
- // Wait for connection to be established.
- select {
- case <-ch:
- c.EP, _, err = ep.Accept()
- if err != nil {
- t.Fatalf("Accept failed: %v", err)
- }
-
- case <-time.After(1 * time.Second):
- t.Fatalf("Timed out waiting for accept")
- }
- }
-
- data := []byte{1, 2, 3}
- view := buffer.NewView(len(data))
- copy(view, data)
-
- if _, _, err := c.EP.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
-
- // Check that data is received, and that advertised window is 0xbfff,
- // that is, that it is scaled.
- b := c.GetPacket()
- checker.IPv4(t, b,
- checker.PayloadLen(len(data)+header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(790),
- checker.Window(0xbfff),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
-}
-
-func TestNonScaledWindowAccept(t *testing.T) {
- // This test ensures that window scaling is not used when the peer
- // doesn't advertise it and connection is established with Accept().
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- // Create EP and start listening.
- wq := &waiter.Queue{}
- ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
- defer ep.Close()
-
- // Set the window size greater than the maximum non-scaled window.
- if err := ep.SetSockOpt(tcpip.ReceiveBufferSizeOption(65535 * 3)); err != nil {
- t.Fatalf("SetSockOpt failed failed: %v", err)
- }
-
- if err := ep.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- if err := ep.Listen(10); err != nil {
- t.Fatalf("Listen failed: %v", err)
- }
-
- // Do 3-way handshake w/ window scaling disabled. The SYN-ACK to the SYN
- // should not carry the window scaling option.
- c.PassiveConnect(100, -1, header.TCPSynOptions{MSS: defaultIPv4MSS})
-
- // Try to accept the connection.
- we, ch := waiter.NewChannelEntry(nil)
- wq.EventRegister(&we, waiter.EventIn)
- defer wq.EventUnregister(&we)
-
- c.EP, _, err = ep.Accept()
- if err == tcpip.ErrWouldBlock {
- // Wait for connection to be established.
- select {
- case <-ch:
- c.EP, _, err = ep.Accept()
- if err != nil {
- t.Fatalf("Accept failed: %v", err)
- }
-
- case <-time.After(1 * time.Second):
- t.Fatalf("Timed out waiting for accept")
- }
- }
-
- data := []byte{1, 2, 3}
- view := buffer.NewView(len(data))
- copy(view, data)
-
- if _, _, err := c.EP.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
-
- // Check that data is received, and that advertised window is 0xffff,
- // that is, that it's not scaled.
- b := c.GetPacket()
- checker.IPv4(t, b,
- checker.PayloadLen(len(data)+header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(790),
- checker.Window(0xffff),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
-}
-
-func TestZeroScaledWindowReceive(t *testing.T) {
- // This test ensures that the endpoint sends a non-zero window size
- // advertisement when the scaled window transitions from 0 to non-zero,
- // but the actual window (not scaled) hasn't gotten to zero.
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- // Set the window size such that a window scale of 4 will be used.
- const wnd = 65535 * 10
- const ws = uint32(4)
- opt := tcpip.ReceiveBufferSizeOption(wnd)
- c.CreateConnectedWithRawOptions(789, 30000, &opt, []byte{
- header.TCPOptionWS, 3, 0, header.TCPOptionNOP,
- })
-
- // Write chunks of 50000 bytes.
- remain := wnd
- sent := 0
- data := make([]byte, 50000)
- for remain > len(data) {
- c.SendPacket(data, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: seqnum.Value(790 + sent),
- AckNum: c.IRS.Add(1),
- RcvWnd: 30000,
- })
- sent += len(data)
- remain -= len(data)
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(uint32(790+sent)),
- checker.Window(uint16(remain>>ws)),
- checker.TCPFlags(header.TCPFlagAck),
- ),
- )
- }
-
- // Make the window non-zero, but the scaled window zero.
- if remain >= 16 {
- data = data[:remain-15]
- c.SendPacket(data, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: seqnum.Value(790 + sent),
- AckNum: c.IRS.Add(1),
- RcvWnd: 30000,
- })
- sent += len(data)
- remain -= len(data)
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(uint32(790+sent)),
- checker.Window(0),
- checker.TCPFlags(header.TCPFlagAck),
- ),
- )
- }
-
- // Read some data. An ack should be sent in response to that.
- v, _, err := c.EP.Read(nil)
- if err != nil {
- t.Fatalf("Read failed: %v", err)
- }
-
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(uint32(790+sent)),
- checker.Window(uint16(len(v)>>ws)),
- checker.TCPFlags(header.TCPFlagAck),
- ),
- )
-}
-
-func TestSegmentMerging(t *testing.T) {
- tests := []struct {
- name string
- stop func(tcpip.Endpoint)
- resume func(tcpip.Endpoint)
- }{
- {
- "stop work",
- func(ep tcpip.Endpoint) {
- ep.(interface{ StopWork() }).StopWork()
- },
- func(ep tcpip.Endpoint) {
- ep.(interface{ ResumeWork() }).ResumeWork()
- },
- },
- {
- "cork",
- func(ep tcpip.Endpoint) {
- ep.SetSockOpt(tcpip.CorkOption(1))
- },
- func(ep tcpip.Endpoint) {
- ep.SetSockOpt(tcpip.CorkOption(0))
- },
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-
- // Prevent the endpoint from processing packets.
- test.stop(c.EP)
-
- var allData []byte
- for i, data := range [][]byte{{1, 2, 3, 4}, {5, 6, 7}, {8, 9}, {10}, {11}} {
- allData = append(allData, data...)
- view := buffer.NewViewFromBytes(data)
- if _, _, err := c.EP.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write #%d failed: %v", i+1, err)
- }
- }
-
- // Let the endpoint process the segments that we just sent.
- test.resume(c.EP)
-
- // Check that data is received.
- b := c.GetPacket()
- checker.IPv4(t, b,
- checker.PayloadLen(len(allData)+header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(790),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
-
- if got := b[header.IPv4MinimumSize+header.TCPMinimumSize:]; !bytes.Equal(got, allData) {
- t.Fatalf("got data = %v, want = %v", got, allData)
- }
-
- // Acknowledge the data.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 790,
- AckNum: c.IRS.Add(1 + seqnum.Size(len(allData))),
- RcvWnd: 30000,
- })
- })
- }
-}
-
-func TestDelay(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-
- c.EP.SetSockOpt(tcpip.DelayOption(1))
-
- var allData []byte
- for i, data := range [][]byte{{0}, {1, 2, 3, 4}, {5, 6, 7}, {8, 9}, {10}, {11}} {
- allData = append(allData, data...)
- view := buffer.NewViewFromBytes(data)
- if _, _, err := c.EP.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write #%d failed: %v", i+1, err)
- }
- }
-
- seq := c.IRS.Add(1)
- for _, want := range [][]byte{allData[:1], allData[1:]} {
- // Check that data is received.
- b := c.GetPacket()
- checker.IPv4(t, b,
- checker.PayloadLen(len(want)+header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(seq)),
- checker.AckNum(790),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
-
- if got := b[header.IPv4MinimumSize+header.TCPMinimumSize:]; !bytes.Equal(got, want) {
- t.Fatalf("got data = %v, want = %v", got, want)
- }
-
- seq = seq.Add(seqnum.Size(len(want)))
- // Acknowledge the data.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 790,
- AckNum: seq,
- RcvWnd: 30000,
- })
- }
-}
-
-func TestUndelay(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-
- c.EP.SetSockOpt(tcpip.DelayOption(1))
-
- allData := [][]byte{{0}, {1, 2, 3}}
- for i, data := range allData {
- view := buffer.NewViewFromBytes(data)
- if _, _, err := c.EP.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write #%d failed: %v", i+1, err)
- }
- }
-
- seq := c.IRS.Add(1)
-
- // Check that data is received.
- first := c.GetPacket()
- checker.IPv4(t, first,
- checker.PayloadLen(len(allData[0])+header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(seq)),
- checker.AckNum(790),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
-
- if got, want := first[header.IPv4MinimumSize+header.TCPMinimumSize:], allData[0]; !bytes.Equal(got, want) {
- t.Fatalf("got first packet's data = %v, want = %v", got, want)
- }
-
- seq = seq.Add(seqnum.Size(len(allData[0])))
-
- // Check that we don't get the second packet yet.
- c.CheckNoPacketTimeout("delayed second packet transmitted", 100*time.Millisecond)
-
- c.EP.SetSockOpt(tcpip.DelayOption(0))
-
- // Check that data is received.
- second := c.GetPacket()
- checker.IPv4(t, second,
- checker.PayloadLen(len(allData[1])+header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(seq)),
- checker.AckNum(790),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
-
- if got, want := second[header.IPv4MinimumSize+header.TCPMinimumSize:], allData[1]; !bytes.Equal(got, want) {
- t.Fatalf("got second packet's data = %v, want = %v", got, want)
- }
-
- seq = seq.Add(seqnum.Size(len(allData[1])))
-
- // Acknowledge the data.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 790,
- AckNum: seq,
- RcvWnd: 30000,
- })
-}
-
-func TestMSSNotDelayed(t *testing.T) {
- tests := []struct {
- name string
- fn func(tcpip.Endpoint)
- }{
- {"no-op", func(tcpip.Endpoint) {}},
- {"delay", func(ep tcpip.Endpoint) { ep.SetSockOpt(tcpip.DelayOption(1)) }},
- {"cork", func(ep tcpip.Endpoint) { ep.SetSockOpt(tcpip.CorkOption(1)) }},
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- const maxPayload = 100
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnectedWithRawOptions(789, 30000, nil, []byte{
- header.TCPOptionMSS, 4, byte(maxPayload / 256), byte(maxPayload % 256),
- })
-
- test.fn(c.EP)
-
- allData := [][]byte{{0}, make([]byte, maxPayload), make([]byte, maxPayload)}
- for i, data := range allData {
- view := buffer.NewViewFromBytes(data)
- if _, _, err := c.EP.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write #%d failed: %v", i+1, err)
- }
- }
-
- seq := c.IRS.Add(1)
-
- for i, data := range allData {
- // Check that data is received.
- packet := c.GetPacket()
- checker.IPv4(t, packet,
- checker.PayloadLen(len(data)+header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(seq)),
- checker.AckNum(790),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
-
- if got, want := packet[header.IPv4MinimumSize+header.TCPMinimumSize:], data; !bytes.Equal(got, want) {
- t.Fatalf("got packet #%d's data = %v, want = %v", i+1, got, want)
- }
-
- seq = seq.Add(seqnum.Size(len(data)))
- }
-
- // Acknowledge the data.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 790,
- AckNum: seq,
- RcvWnd: 30000,
- })
- })
- }
-}
-
-func testBrokenUpWrite(t *testing.T, c *context.Context, maxPayload int) {
- payloadMultiplier := 10
- dataLen := payloadMultiplier * maxPayload
- data := make([]byte, dataLen)
- for i := range data {
- data[i] = byte(i)
- }
-
- view := buffer.NewView(len(data))
- copy(view, data)
-
- if _, _, err := c.EP.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
-
- // Check that data is received in chunks.
- bytesReceived := 0
- numPackets := 0
- for bytesReceived != dataLen {
- b := c.GetPacket()
- numPackets++
- tcpHdr := header.TCP(header.IPv4(b).Payload())
- payloadLen := len(tcpHdr.Payload())
- checker.IPv4(t, b,
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1+uint32(bytesReceived)),
- checker.AckNum(790),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
-
- pdata := data[bytesReceived : bytesReceived+payloadLen]
- if p := tcpHdr.Payload(); !bytes.Equal(pdata, p) {
- t.Fatalf("got data = %v, want = %v", p, pdata)
- }
- bytesReceived += payloadLen
- var options []byte
- if c.TimeStampEnabled {
- // If timestamp option is enabled, echo back the timestamp and increment
- // the TSEcr value included in the packet and send that back as the TSVal.
- parsedOpts := tcpHdr.ParsedOptions()
- tsOpt := [12]byte{header.TCPOptionNOP, header.TCPOptionNOP}
- header.EncodeTSOption(parsedOpts.TSEcr+1, parsedOpts.TSVal, tsOpt[2:])
- options = tsOpt[:]
- }
- // Acknowledge the data.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 790,
- AckNum: c.IRS.Add(1 + seqnum.Size(bytesReceived)),
- RcvWnd: 30000,
- TCPOpts: options,
- })
- }
- if numPackets == 1 {
- t.Fatalf("expected write to be broken up into multiple packets, but got 1 packet")
- }
-}
-
-func TestSendGreaterThanMTU(t *testing.T) {
- const maxPayload = 100
- c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxPayload))
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
- testBrokenUpWrite(t, c, maxPayload)
-}
-
-func TestActiveSendMSSLessThanMTU(t *testing.T) {
- const maxPayload = 100
- c := context.New(t, 65535)
- defer c.Cleanup()
-
- c.CreateConnectedWithRawOptions(789, 30000, nil, []byte{
- header.TCPOptionMSS, 4, byte(maxPayload / 256), byte(maxPayload % 256),
- })
- testBrokenUpWrite(t, c, maxPayload)
-}
-
-func TestPassiveSendMSSLessThanMTU(t *testing.T) {
- const maxPayload = 100
- const mtu = 1200
- c := context.New(t, mtu)
- defer c.Cleanup()
-
- // Create EP and start listening.
- wq := &waiter.Queue{}
- ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
- defer ep.Close()
-
- // Set the buffer size to a deterministic size so that we can check the
- // window scaling option.
- const rcvBufferSize = 0x20000
- if err := ep.SetSockOpt(tcpip.ReceiveBufferSizeOption(rcvBufferSize)); err != nil {
- t.Fatalf("SetSockOpt failed failed: %v", err)
- }
-
- if err := ep.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- if err := ep.Listen(10); err != nil {
- t.Fatalf("Listen failed: %v", err)
- }
-
- // Do 3-way handshake.
- c.PassiveConnect(maxPayload, -1, header.TCPSynOptions{MSS: mtu - header.IPv4MinimumSize - header.TCPMinimumSize})
-
- // Try to accept the connection.
- we, ch := waiter.NewChannelEntry(nil)
- wq.EventRegister(&we, waiter.EventIn)
- defer wq.EventUnregister(&we)
-
- c.EP, _, err = ep.Accept()
- if err == tcpip.ErrWouldBlock {
- // Wait for connection to be established.
- select {
- case <-ch:
- c.EP, _, err = ep.Accept()
- if err != nil {
- t.Fatalf("Accept failed: %v", err)
- }
-
- case <-time.After(1 * time.Second):
- t.Fatalf("Timed out waiting for accept")
- }
- }
-
- // Check that data gets properly segmented.
- testBrokenUpWrite(t, c, maxPayload)
-}
-
-func TestSynCookiePassiveSendMSSLessThanMTU(t *testing.T) {
- const maxPayload = 536
- const mtu = 2000
- c := context.New(t, mtu)
- defer c.Cleanup()
-
- // Set the SynRcvd threshold to zero to force a syn cookie based accept
- // to happen.
- saved := tcp.SynRcvdCountThreshold
- defer func() {
- tcp.SynRcvdCountThreshold = saved
- }()
- tcp.SynRcvdCountThreshold = 0
-
- // Create EP and start listening.
- wq := &waiter.Queue{}
- ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
- defer ep.Close()
-
- if err := ep.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- if err := ep.Listen(10); err != nil {
- t.Fatalf("Listen failed: %v", err)
- }
-
- // Do 3-way handshake.
- c.PassiveConnect(maxPayload, -1, header.TCPSynOptions{MSS: mtu - header.IPv4MinimumSize - header.TCPMinimumSize})
-
- // Try to accept the connection.
- we, ch := waiter.NewChannelEntry(nil)
- wq.EventRegister(&we, waiter.EventIn)
- defer wq.EventUnregister(&we)
-
- c.EP, _, err = ep.Accept()
- if err == tcpip.ErrWouldBlock {
- // Wait for connection to be established.
- select {
- case <-ch:
- c.EP, _, err = ep.Accept()
- if err != nil {
- t.Fatalf("Accept failed: %v", err)
- }
-
- case <-time.After(1 * time.Second):
- t.Fatalf("Timed out waiting for accept")
- }
- }
-
- // Check that data gets properly segmented.
- testBrokenUpWrite(t, c, maxPayload)
-}
-
-func TestForwarderSendMSSLessThanMTU(t *testing.T) {
- const maxPayload = 100
- const mtu = 1200
- c := context.New(t, mtu)
- defer c.Cleanup()
-
- s := c.Stack()
- ch := make(chan *tcpip.Error, 1)
- f := tcp.NewForwarder(s, 65536, 10, func(r *tcp.ForwarderRequest) {
- var err *tcpip.Error
- c.EP, err = r.CreateEndpoint(&c.WQ)
- ch <- err
- })
- s.SetTransportProtocolHandler(tcp.ProtocolNumber, f.HandlePacket)
-
- // Do 3-way handshake.
- c.PassiveConnect(maxPayload, -1, header.TCPSynOptions{MSS: mtu - header.IPv4MinimumSize - header.TCPMinimumSize})
-
- // Wait for connection to be available.
- select {
- case err := <-ch:
- if err != nil {
- t.Fatalf("Error creating endpoint: %v", err)
- }
- case <-time.After(2 * time.Second):
- t.Fatalf("Timed out waiting for connection")
- }
-
- // Check that data gets properly segmented.
- testBrokenUpWrite(t, c, maxPayload)
-}
-
-func TestSynOptionsOnActiveConnect(t *testing.T) {
- const mtu = 1400
- c := context.New(t, mtu)
- defer c.Cleanup()
-
- // Create TCP endpoint.
- var err *tcpip.Error
- c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- // Set the buffer size to a deterministic size so that we can check the
- // window scaling option.
- const rcvBufferSize = 0x20000
- const wndScale = 2
- if err := c.EP.SetSockOpt(tcpip.ReceiveBufferSizeOption(rcvBufferSize)); err != nil {
- t.Fatalf("SetSockOpt failed failed: %v", err)
- }
-
- // Start connection attempt.
- we, ch := waiter.NewChannelEntry(nil)
- c.WQ.EventRegister(&we, waiter.EventOut)
- defer c.WQ.EventUnregister(&we)
-
- if err := c.EP.Connect(tcpip.FullAddress{Addr: context.TestAddr, Port: context.TestPort}); err != tcpip.ErrConnectStarted {
- t.Fatalf("got c.EP.Connect(...) = %v, want = %v", err, tcpip.ErrConnectStarted)
- }
-
- // Receive SYN packet.
- b := c.GetPacket()
- mss := uint16(mtu - header.IPv4MinimumSize - header.TCPMinimumSize)
- checker.IPv4(t, b,
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.TCPFlags(header.TCPFlagSyn),
- checker.TCPSynOptions(header.TCPSynOptions{MSS: mss, WS: wndScale}),
- ),
- )
-
- tcpHdr := header.TCP(header.IPv4(b).Payload())
- c.IRS = seqnum.Value(tcpHdr.SequenceNumber())
-
- // Wait for retransmit.
- time.Sleep(1 * time.Second)
- checker.IPv4(t, c.GetPacket(),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.TCPFlags(header.TCPFlagSyn),
- checker.SrcPort(tcpHdr.SourcePort()),
- checker.SeqNum(tcpHdr.SequenceNumber()),
- checker.TCPSynOptions(header.TCPSynOptions{MSS: mss, WS: wndScale}),
- ),
- )
-
- // Send SYN-ACK.
- iss := seqnum.Value(789)
- c.SendPacket(nil, &context.Headers{
- SrcPort: tcpHdr.DestinationPort(),
- DstPort: tcpHdr.SourcePort(),
- Flags: header.TCPFlagSyn | header.TCPFlagAck,
- SeqNum: iss,
- AckNum: c.IRS.Add(1),
- RcvWnd: 30000,
- })
-
- // Receive ACK packet.
- checker.IPv4(t, c.GetPacket(),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.TCPFlags(header.TCPFlagAck),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(uint32(iss)+1),
- ),
- )
-
- // Wait for connection to be established.
- select {
- case <-ch:
- if err := c.EP.GetSockOpt(tcpip.ErrorOption{}); err != nil {
- t.Fatalf("GetSockOpt failed: %v", err)
- }
- case <-time.After(1 * time.Second):
- t.Fatalf("Timed out waiting for connection")
- }
-}
-
-func TestCloseListener(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- // Create listener.
- var wq waiter.Queue
- ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &wq)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- if err := ep.Bind(tcpip.FullAddress{}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- if err := ep.Listen(10); err != nil {
- t.Fatalf("Listen failed: %v", err)
- }
-
- // Close the listener and measure how long it takes.
- t0 := time.Now()
- ep.Close()
- if diff := time.Now().Sub(t0); diff > 3*time.Second {
- t.Fatalf("Took too long to close: %v", diff)
- }
-}
-
-func TestReceiveOnResetConnection(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-
- // Send RST segment.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagRst,
- SeqNum: 790,
- RcvWnd: 30000,
- })
-
- // Try to read.
- we, ch := waiter.NewChannelEntry(nil)
- c.WQ.EventRegister(&we, waiter.EventIn)
- defer c.WQ.EventUnregister(&we)
-
-loop:
- for {
- switch _, _, err := c.EP.Read(nil); err {
- case tcpip.ErrWouldBlock:
- select {
- case <-ch:
- case <-time.After(1 * time.Second):
- t.Fatalf("Timed out waiting for reset to arrive")
- }
- case tcpip.ErrConnectionReset:
- break loop
- default:
- t.Fatalf("got c.EP.Read(nil) = %v, want = %v", err, tcpip.ErrConnectionReset)
- }
- }
-}
-
-func TestSendOnResetConnection(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-
- // Send RST segment.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagRst,
- SeqNum: 790,
- RcvWnd: 30000,
- })
-
- // Wait for the RST to be received.
- time.Sleep(1 * time.Second)
-
- // Try to write.
- view := buffer.NewView(10)
- if _, _, err := c.EP.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{}); err != tcpip.ErrConnectionReset {
- t.Fatalf("got c.EP.Write(...) = %v, want = %v", err, tcpip.ErrConnectionReset)
- }
-}
-
-func TestFinImmediately(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-
- // Shutdown immediately, check that we get a FIN.
- if err := c.EP.Shutdown(tcpip.ShutdownWrite); err != nil {
- t.Fatalf("Shutdown failed: %v", err)
- }
-
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(790),
- checker.TCPFlags(header.TCPFlagAck|header.TCPFlagFin),
- ),
- )
-
- // Ack and send FIN as well.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck | header.TCPFlagFin,
- SeqNum: 790,
- AckNum: c.IRS.Add(2),
- RcvWnd: 30000,
- })
-
- // Check that the stack acks the FIN.
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+2),
- checker.AckNum(791),
- checker.TCPFlags(header.TCPFlagAck),
- ),
- )
-}
-
-func TestFinRetransmit(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-
- // Shutdown immediately, check that we get a FIN.
- if err := c.EP.Shutdown(tcpip.ShutdownWrite); err != nil {
- t.Fatalf("Shutdown failed: %v", err)
- }
-
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(790),
- checker.TCPFlags(header.TCPFlagAck|header.TCPFlagFin),
- ),
- )
-
- // Don't acknowledge yet. We should get a retransmit of the FIN.
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(790),
- checker.TCPFlags(header.TCPFlagAck|header.TCPFlagFin),
- ),
- )
-
- // Ack and send FIN as well.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck | header.TCPFlagFin,
- SeqNum: 790,
- AckNum: c.IRS.Add(2),
- RcvWnd: 30000,
- })
-
- // Check that the stack acks the FIN.
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+2),
- checker.AckNum(791),
- checker.TCPFlags(header.TCPFlagAck),
- ),
- )
-}
-
-func TestFinWithNoPendingData(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-
- // Write something out, and have it acknowledged.
- view := buffer.NewView(10)
- if _, _, err := c.EP.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
-
- next := uint32(c.IRS) + 1
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(len(view)+header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(next),
- checker.AckNum(790),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
- next += uint32(len(view))
-
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 790,
- AckNum: seqnum.Value(next),
- RcvWnd: 30000,
- })
-
- // Shutdown, check that we get a FIN.
- if err := c.EP.Shutdown(tcpip.ShutdownWrite); err != nil {
- t.Fatalf("Shutdown failed: %v", err)
- }
-
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(next),
- checker.AckNum(790),
- checker.TCPFlags(header.TCPFlagAck|header.TCPFlagFin),
- ),
- )
- next++
-
- // Ack and send FIN as well.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck | header.TCPFlagFin,
- SeqNum: 790,
- AckNum: seqnum.Value(next),
- RcvWnd: 30000,
- })
-
- // Check that the stack acks the FIN.
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(next),
- checker.AckNum(791),
- checker.TCPFlags(header.TCPFlagAck),
- ),
- )
-}
-
-func TestFinWithPendingDataCwndFull(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-
- // Write enough segments to fill the congestion window before ACK'ing
- // any of them.
- view := buffer.NewView(10)
- for i := tcp.InitialCwnd; i > 0; i-- {
- if _, _, err := c.EP.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
- }
-
- next := uint32(c.IRS) + 1
- for i := tcp.InitialCwnd; i > 0; i-- {
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(len(view)+header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(next),
- checker.AckNum(790),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
- next += uint32(len(view))
- }
-
- // Shutdown the connection, check that the FIN segment isn't sent
- // because the congestion window doesn't allow it. Wait until a
- // retransmit is received.
- if err := c.EP.Shutdown(tcpip.ShutdownWrite); err != nil {
- t.Fatalf("Shutdown failed: %v", err)
- }
-
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(len(view)+header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(790),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
-
- // Send the ACK that will allow the FIN to be sent as well.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 790,
- AckNum: seqnum.Value(next),
- RcvWnd: 30000,
- })
-
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(next),
- checker.AckNum(790),
- checker.TCPFlags(header.TCPFlagAck|header.TCPFlagFin),
- ),
- )
- next++
-
- // Send a FIN that acknowledges everything. Get an ACK back.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck | header.TCPFlagFin,
- SeqNum: 790,
- AckNum: seqnum.Value(next),
- RcvWnd: 30000,
- })
-
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(next),
- checker.AckNum(791),
- checker.TCPFlags(header.TCPFlagAck),
- ),
- )
-}
-
-func TestFinWithPendingData(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-
- // Write something out, and acknowledge it to get cwnd to 2.
- view := buffer.NewView(10)
- if _, _, err := c.EP.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
-
- next := uint32(c.IRS) + 1
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(len(view)+header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(next),
- checker.AckNum(790),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
- next += uint32(len(view))
-
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 790,
- AckNum: seqnum.Value(next),
- RcvWnd: 30000,
- })
-
- // Write new data, but don't acknowledge it.
- if _, _, err := c.EP.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
-
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(len(view)+header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(next),
- checker.AckNum(790),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
- next += uint32(len(view))
-
- // Shutdown the connection, check that we do get a FIN.
- if err := c.EP.Shutdown(tcpip.ShutdownWrite); err != nil {
- t.Fatalf("Shutdown failed: %v", err)
- }
-
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(next),
- checker.AckNum(790),
- checker.TCPFlags(header.TCPFlagAck|header.TCPFlagFin),
- ),
- )
- next++
-
- // Send a FIN that acknowledges everything. Get an ACK back.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck | header.TCPFlagFin,
- SeqNum: 790,
- AckNum: seqnum.Value(next),
- RcvWnd: 30000,
- })
-
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(next),
- checker.AckNum(791),
- checker.TCPFlags(header.TCPFlagAck),
- ),
- )
-}
-
-func TestFinWithPartialAck(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-
- // Write something out, and acknowledge it to get cwnd to 2. Also send
- // FIN from the test side.
- view := buffer.NewView(10)
- if _, _, err := c.EP.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
-
- next := uint32(c.IRS) + 1
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(len(view)+header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(next),
- checker.AckNum(790),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
- next += uint32(len(view))
-
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck | header.TCPFlagFin,
- SeqNum: 790,
- AckNum: seqnum.Value(next),
- RcvWnd: 30000,
- })
-
- // Check that we get an ACK for the fin.
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(next),
- checker.AckNum(791),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
-
- // Write new data, but don't acknowledge it.
- if _, _, err := c.EP.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
-
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(len(view)+header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(next),
- checker.AckNum(791),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
- next += uint32(len(view))
-
- // Shutdown the connection, check that we do get a FIN.
- if err := c.EP.Shutdown(tcpip.ShutdownWrite); err != nil {
- t.Fatalf("Shutdown failed: %v", err)
- }
-
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(next),
- checker.AckNum(791),
- checker.TCPFlags(header.TCPFlagAck|header.TCPFlagFin),
- ),
- )
- next++
-
- // Send an ACK for the data, but not for the FIN yet.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 791,
- AckNum: seqnum.Value(next - 1),
- RcvWnd: 30000,
- })
-
- // Check that we don't get a retransmit of the FIN.
- c.CheckNoPacketTimeout("FIN retransmitted when data was ack'd", 100*time.Millisecond)
-
- // Ack the FIN.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck | header.TCPFlagFin,
- SeqNum: 791,
- AckNum: seqnum.Value(next),
- RcvWnd: 30000,
- })
-}
-
-func TestUpdateListenBacklog(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- // Create listener.
- var wq waiter.Queue
- ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &wq)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- if err := ep.Bind(tcpip.FullAddress{}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- if err := ep.Listen(10); err != nil {
- t.Fatalf("Listen failed: %v", err)
- }
-
- // Update the backlog with another Listen() on the same endpoint.
- if err := ep.Listen(20); err != nil {
- t.Fatalf("Listen failed to update backlog: %v", err)
- }
-
- ep.Close()
-}
-
-func scaledSendWindow(t *testing.T, scale uint8) {
- // This test ensures that the endpoint is using the right scaling by
- // sending a buffer that is larger than the window size, and ensuring
- // that the endpoint doesn't send more than allowed.
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- maxPayload := defaultMTU - header.IPv4MinimumSize - header.TCPMinimumSize
- c.CreateConnectedWithRawOptions(789, 0, nil, []byte{
- header.TCPOptionMSS, 4, byte(maxPayload / 256), byte(maxPayload % 256),
- header.TCPOptionWS, 3, scale, header.TCPOptionNOP,
- })
-
- // Open up the window with a scaled value.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 790,
- AckNum: c.IRS.Add(1),
- RcvWnd: 1,
- })
-
- // Send some data. Check that it's capped by the window size.
- view := buffer.NewView(65535)
- if _, _, err := c.EP.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
-
- // Check that only data that fits in the scaled window is sent.
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen((1<<scale)+header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(790),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
-
- // Reset the connection to free resources.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagRst,
- SeqNum: 790,
- })
-}
-
-func TestScaledSendWindow(t *testing.T) {
- for scale := uint8(0); scale <= 14; scale++ {
- scaledSendWindow(t, scale)
- }
-}
-
-func TestReceivedValidSegmentCountIncrement(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
- c.CreateConnected(789, 30000, nil)
- stats := c.Stack().Stats()
- want := stats.TCP.ValidSegmentsReceived.Value() + 1
-
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: seqnum.Value(790),
- AckNum: c.IRS.Add(1),
- RcvWnd: 30000,
- })
-
- if got := stats.TCP.ValidSegmentsReceived.Value(); got != want {
- t.Errorf("got stats.TCP.ValidSegmentsReceived.Value() = %v, want = %v", got, want)
- }
-}
-
-func TestReceivedInvalidSegmentCountIncrement(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
- c.CreateConnected(789, 30000, nil)
- stats := c.Stack().Stats()
- want := stats.TCP.InvalidSegmentsReceived.Value() + 1
- vv := c.BuildSegment(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: seqnum.Value(790),
- AckNum: c.IRS.Add(1),
- RcvWnd: 30000,
- })
- tcpbuf := vv.First()[header.IPv4MinimumSize:]
- tcpbuf[header.TCPDataOffset] = ((header.TCPMinimumSize - 1) / 4) << 4
-
- c.SendSegment(vv)
-
- if got := stats.TCP.InvalidSegmentsReceived.Value(); got != want {
- t.Errorf("got stats.TCP.InvalidSegmentsReceived.Value() = %v, want = %v", got, want)
- }
-}
-
-func TestReceivedIncorrectChecksumIncrement(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
- c.CreateConnected(789, 30000, nil)
- stats := c.Stack().Stats()
- want := stats.TCP.ChecksumErrors.Value() + 1
- vv := c.BuildSegment([]byte{0x1, 0x2, 0x3}, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: seqnum.Value(790),
- AckNum: c.IRS.Add(1),
- RcvWnd: 30000,
- })
- tcpbuf := vv.First()[header.IPv4MinimumSize:]
- // Overwrite a byte in the payload which should cause checksum
- // verification to fail.
- tcpbuf[(tcpbuf[header.TCPDataOffset]>>4)*4] = 0x4
-
- c.SendSegment(vv)
-
- if got := stats.TCP.ChecksumErrors.Value(); got != want {
- t.Errorf("got stats.TCP.ChecksumErrors.Value() = %d, want = %d", got, want)
- }
-}
-
-func TestReceivedSegmentQueuing(t *testing.T) {
- // This test sends 200 segments containing a few bytes each to an
- // endpoint and checks that they're all received and acknowledged by
- // the endpoint, that is, that none of the segments are dropped by
- // internal queues.
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-
- // Send 200 segments.
- data := []byte{1, 2, 3}
- for i := 0; i < 200; i++ {
- c.SendPacket(data, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: seqnum.Value(790 + i*len(data)),
- AckNum: c.IRS.Add(1),
- RcvWnd: 30000,
- })
- }
-
- // Receive ACKs for all segments.
- last := seqnum.Value(790 + 200*len(data))
- for {
- b := c.GetPacket()
- checker.IPv4(t, b,
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.TCPFlags(header.TCPFlagAck),
- ),
- )
- tcpHdr := header.TCP(header.IPv4(b).Payload())
- ack := seqnum.Value(tcpHdr.AckNumber())
- if ack == last {
- break
- }
-
- if last.LessThan(ack) {
- t.Fatalf("Acknowledge (%v) beyond the expected (%v)", ack, last)
- }
- }
-}
-
-func TestReadAfterClosedState(t *testing.T) {
- // This test ensures that calling Read() or Peek() after the endpoint
- // has transitioned to closedState still works if there is pending
- // data. To transition to stateClosed without calling Close(), we must
- // shutdown the send path and the peer must send its own FIN.
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-
- we, ch := waiter.NewChannelEntry(nil)
- c.WQ.EventRegister(&we, waiter.EventIn)
- defer c.WQ.EventUnregister(&we)
-
- if _, _, err := c.EP.Read(nil); err != tcpip.ErrWouldBlock {
- t.Fatalf("got c.EP.Read(nil) = %v, want = %v", err, tcpip.ErrWouldBlock)
- }
-
- // Shutdown immediately for write, check that we get a FIN.
- if err := c.EP.Shutdown(tcpip.ShutdownWrite); err != nil {
- t.Fatalf("Shutdown failed: %v", err)
- }
-
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(790),
- checker.TCPFlags(header.TCPFlagAck|header.TCPFlagFin),
- ),
- )
-
- if got, want := tcp.EndpointState(c.EP.State()), tcp.StateFinWait1; got != want {
- t.Errorf("Unexpected endpoint state: want %v, got %v", want, got)
- }
-
- // Send some data and acknowledge the FIN.
- data := []byte{1, 2, 3}
- c.SendPacket(data, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck | header.TCPFlagFin,
- SeqNum: 790,
- AckNum: c.IRS.Add(2),
- RcvWnd: 30000,
- })
-
- // Check that ACK is received.
- checker.IPv4(t, c.GetPacket(),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+2),
- checker.AckNum(uint32(791+len(data))),
- checker.TCPFlags(header.TCPFlagAck),
- ),
- )
-
- // Give the stack the chance to transition to closed state. Note that since
- // both the sender and receiver are now closed, we effectively skip the
- // TIME-WAIT state.
- time.Sleep(1 * time.Second)
-
- if got, want := tcp.EndpointState(c.EP.State()), tcp.StateClose; got != want {
- t.Errorf("Unexpected endpoint state: want %v, got %v", want, got)
- }
-
- // Wait for receive to be notified.
- select {
- case <-ch:
- case <-time.After(1 * time.Second):
- t.Fatalf("Timed out waiting for data to arrive")
- }
-
- // Check that peek works.
- peekBuf := make([]byte, 10)
- n, _, err := c.EP.Peek([][]byte{peekBuf})
- if err != nil {
- t.Fatalf("Peek failed: %v", err)
- }
-
- peekBuf = peekBuf[:n]
- if !bytes.Equal(data, peekBuf) {
- t.Fatalf("got data = %v, want = %v", peekBuf, data)
- }
-
- // Receive data.
- v, _, err := c.EP.Read(nil)
- if err != nil {
- t.Fatalf("Read failed: %v", err)
- }
-
- if !bytes.Equal(data, v) {
- t.Fatalf("got data = %v, want = %v", v, data)
- }
-
- // Now that we drained the queue, check that functions fail with the
- // right error code.
- if _, _, err := c.EP.Read(nil); err != tcpip.ErrClosedForReceive {
- t.Fatalf("got c.EP.Read(nil) = %v, want = %v", err, tcpip.ErrClosedForReceive)
- }
-
- if _, _, err := c.EP.Peek([][]byte{peekBuf}); err != tcpip.ErrClosedForReceive {
- t.Fatalf("got c.EP.Peek(...) = %v, want = %v", err, tcpip.ErrClosedForReceive)
- }
-}
-
-func TestReusePort(t *testing.T) {
- // This test ensures that ports are immediately available for reuse
- // after Close on the endpoints using them returns.
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- // First case, just an endpoint that was bound.
- var err *tcpip.Error
- c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &waiter.Queue{})
- if err != nil {
- t.Fatalf("NewEndpoint failed; %v", err)
- }
- if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- c.EP.Close()
- c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &waiter.Queue{})
- if err != nil {
- t.Fatalf("NewEndpoint failed; %v", err)
- }
- if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
- c.EP.Close()
-
- // Second case, an endpoint that was bound and is connecting..
- c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &waiter.Queue{})
- if err != nil {
- t.Fatalf("NewEndpoint failed; %v", err)
- }
- if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
- if err := c.EP.Connect(tcpip.FullAddress{Addr: context.TestAddr, Port: context.TestPort}); err != tcpip.ErrConnectStarted {
- t.Fatalf("got c.EP.Connect(...) = %v, want = %v", err, tcpip.ErrConnectStarted)
- }
- c.EP.Close()
-
- c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &waiter.Queue{})
- if err != nil {
- t.Fatalf("NewEndpoint failed; %v", err)
- }
- if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
- c.EP.Close()
-
- // Third case, an endpoint that was bound and is listening.
- c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &waiter.Queue{})
- if err != nil {
- t.Fatalf("NewEndpoint failed; %v", err)
- }
- if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
- if err := c.EP.Listen(10); err != nil {
- t.Fatalf("Listen failed: %v", err)
- }
- c.EP.Close()
-
- c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &waiter.Queue{})
- if err != nil {
- t.Fatalf("NewEndpoint failed; %v", err)
- }
- if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
- if err := c.EP.Listen(10); err != nil {
- t.Fatalf("Listen failed: %v", err)
- }
-}
-
-func checkRecvBufferSize(t *testing.T, ep tcpip.Endpoint, v int) {
- t.Helper()
-
- var s tcpip.ReceiveBufferSizeOption
- if err := ep.GetSockOpt(&s); err != nil {
- t.Fatalf("GetSockOpt failed: %v", err)
- }
-
- if int(s) != v {
- t.Fatalf("got receive buffer size = %v, want = %v", s, v)
- }
-}
-
-func checkSendBufferSize(t *testing.T, ep tcpip.Endpoint, v int) {
- t.Helper()
-
- var s tcpip.SendBufferSizeOption
- if err := ep.GetSockOpt(&s); err != nil {
- t.Fatalf("GetSockOpt failed: %v", err)
- }
-
- if int(s) != v {
- t.Fatalf("got send buffer size = %v, want = %v", s, v)
- }
-}
-
-func TestDefaultBufferSizes(t *testing.T) {
- s := stack.New([]string{ipv4.ProtocolName}, []string{tcp.ProtocolName}, stack.Options{})
-
- // Check the default values.
- ep, err := s.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &waiter.Queue{})
- if err != nil {
- t.Fatalf("NewEndpoint failed; %v", err)
- }
- defer func() {
- if ep != nil {
- ep.Close()
- }
- }()
-
- checkSendBufferSize(t, ep, tcp.DefaultSendBufferSize)
- checkRecvBufferSize(t, ep, tcp.DefaultReceiveBufferSize)
-
- // Change the default send buffer size.
- if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, tcp.SendBufferSizeOption{1, tcp.DefaultSendBufferSize * 2, tcp.DefaultSendBufferSize * 20}); err != nil {
- t.Fatalf("SetTransportProtocolOption failed: %v", err)
- }
-
- ep.Close()
- ep, err = s.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &waiter.Queue{})
- if err != nil {
- t.Fatalf("NewEndpoint failed; %v", err)
- }
-
- checkSendBufferSize(t, ep, tcp.DefaultSendBufferSize*2)
- checkRecvBufferSize(t, ep, tcp.DefaultReceiveBufferSize)
-
- // Change the default receive buffer size.
- if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, tcp.ReceiveBufferSizeOption{1, tcp.DefaultReceiveBufferSize * 3, tcp.DefaultReceiveBufferSize * 30}); err != nil {
- t.Fatalf("SetTransportProtocolOption failed: %v", err)
- }
-
- ep.Close()
- ep, err = s.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &waiter.Queue{})
- if err != nil {
- t.Fatalf("NewEndpoint failed; %v", err)
- }
-
- checkSendBufferSize(t, ep, tcp.DefaultSendBufferSize*2)
- checkRecvBufferSize(t, ep, tcp.DefaultReceiveBufferSize*3)
-}
-
-func TestMinMaxBufferSizes(t *testing.T) {
- s := stack.New([]string{ipv4.ProtocolName}, []string{tcp.ProtocolName}, stack.Options{})
-
- // Check the default values.
- ep, err := s.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &waiter.Queue{})
- if err != nil {
- t.Fatalf("NewEndpoint failed; %v", err)
- }
- defer ep.Close()
-
- // Change the min/max values for send/receive
- if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, tcp.ReceiveBufferSizeOption{200, tcp.DefaultReceiveBufferSize * 2, tcp.DefaultReceiveBufferSize * 20}); err != nil {
- t.Fatalf("SetTransportProtocolOption failed: %v", err)
- }
-
- if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, tcp.SendBufferSizeOption{300, tcp.DefaultSendBufferSize * 3, tcp.DefaultSendBufferSize * 30}); err != nil {
- t.Fatalf("SetTransportProtocolOption failed: %v", err)
- }
-
- // Set values below the min.
- if err := ep.SetSockOpt(tcpip.ReceiveBufferSizeOption(199)); err != nil {
- t.Fatalf("GetSockOpt failed: %v", err)
- }
-
- checkRecvBufferSize(t, ep, 200)
-
- if err := ep.SetSockOpt(tcpip.SendBufferSizeOption(299)); err != nil {
- t.Fatalf("GetSockOpt failed: %v", err)
- }
-
- checkSendBufferSize(t, ep, 300)
-
- // Set values above the max.
- if err := ep.SetSockOpt(tcpip.ReceiveBufferSizeOption(1 + tcp.DefaultReceiveBufferSize*20)); err != nil {
- t.Fatalf("GetSockOpt failed: %v", err)
- }
-
- checkRecvBufferSize(t, ep, tcp.DefaultReceiveBufferSize*20)
-
- if err := ep.SetSockOpt(tcpip.SendBufferSizeOption(1 + tcp.DefaultSendBufferSize*30)); err != nil {
- t.Fatalf("GetSockOpt failed: %v", err)
- }
-
- checkSendBufferSize(t, ep, tcp.DefaultSendBufferSize*30)
-}
-
-func makeStack() (*stack.Stack, *tcpip.Error) {
- s := stack.New([]string{
- ipv4.ProtocolName,
- ipv6.ProtocolName,
- }, []string{tcp.ProtocolName}, stack.Options{})
-
- id := loopback.New()
- if testing.Verbose() {
- id = sniffer.New(id)
- }
-
- if err := s.CreateNIC(1, id); err != nil {
- return nil, err
- }
-
- for _, ct := range []struct {
- number tcpip.NetworkProtocolNumber
- address tcpip.Address
- }{
- {ipv4.ProtocolNumber, context.StackAddr},
- {ipv6.ProtocolNumber, context.StackV6Addr},
- } {
- if err := s.AddAddress(1, ct.number, ct.address); err != nil {
- return nil, err
- }
- }
-
- s.SetRouteTable([]tcpip.Route{
- {
- Destination: header.IPv4EmptySubnet,
- NIC: 1,
- },
- {
- Destination: header.IPv6EmptySubnet,
- NIC: 1,
- },
- })
-
- return s, nil
-}
-
-func TestSelfConnect(t *testing.T) {
- // This test ensures that intentional self-connects work. In particular,
- // it checks that if an endpoint binds to say 127.0.0.1:1000 then
- // connects to 127.0.0.1:1000, then it will be connected to itself, and
- // is able to send and receive data through the same endpoint.
- s, err := makeStack()
- if err != nil {
- t.Fatal(err)
- }
-
- var wq waiter.Queue
- ep, err := s.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &wq)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
- defer ep.Close()
-
- if err := ep.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- // Register for notification, then start connection attempt.
- waitEntry, notifyCh := waiter.NewChannelEntry(nil)
- wq.EventRegister(&waitEntry, waiter.EventOut)
- defer wq.EventUnregister(&waitEntry)
-
- if err := ep.Connect(tcpip.FullAddress{Addr: context.StackAddr, Port: context.StackPort}); err != tcpip.ErrConnectStarted {
- t.Fatalf("got ep.Connect(...) = %v, want = %v", err, tcpip.ErrConnectStarted)
- }
-
- <-notifyCh
- if err := ep.GetSockOpt(tcpip.ErrorOption{}); err != nil {
- t.Fatalf("Connect failed: %v", err)
- }
-
- // Write something.
- data := []byte{1, 2, 3}
- view := buffer.NewView(len(data))
- copy(view, data)
- if _, _, err := ep.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
-
- // Read back what was written.
- wq.EventUnregister(&waitEntry)
- wq.EventRegister(&waitEntry, waiter.EventIn)
- rd, _, err := ep.Read(nil)
- if err != nil {
- if err != tcpip.ErrWouldBlock {
- t.Fatalf("Read failed: %v", err)
- }
- <-notifyCh
- rd, _, err = ep.Read(nil)
- if err != nil {
- t.Fatalf("Read failed: %v", err)
- }
- }
-
- if !bytes.Equal(data, rd) {
- t.Fatalf("got data = %v, want = %v", rd, data)
- }
-}
-
-func TestConnectAvoidsBoundPorts(t *testing.T) {
- addressTypes := func(t *testing.T, network string) []string {
- switch network {
- case "ipv4":
- return []string{"v4"}
- case "ipv6":
- return []string{"v6"}
- case "dual":
- return []string{"v6", "mapped"}
- default:
- t.Fatalf("unknown network: '%s'", network)
- }
-
- panic("unreachable")
- }
-
- address := func(t *testing.T, addressType string, isAny bool) tcpip.Address {
- switch addressType {
- case "v4":
- if isAny {
- return ""
- }
- return context.StackAddr
- case "v6":
- if isAny {
- return ""
- }
- return context.StackV6Addr
- case "mapped":
- if isAny {
- return context.V4MappedWildcardAddr
- }
- return context.StackV4MappedAddr
- default:
- t.Fatalf("unknown address type: '%s'", addressType)
- }
-
- panic("unreachable")
- }
- // This test ensures that Endpoint.Connect doesn't select already-bound ports.
- networks := []string{"ipv4", "ipv6", "dual"}
- for _, exhaustedNetwork := range networks {
- t.Run(fmt.Sprintf("exhaustedNetwork=%s", exhaustedNetwork), func(t *testing.T) {
- for _, exhaustedAddressType := range addressTypes(t, exhaustedNetwork) {
- t.Run(fmt.Sprintf("exhaustedAddressType=%s", exhaustedAddressType), func(t *testing.T) {
- for _, isAny := range []bool{false, true} {
- t.Run(fmt.Sprintf("isAny=%t", isAny), func(t *testing.T) {
- for _, candidateNetwork := range networks {
- t.Run(fmt.Sprintf("candidateNetwork=%s", candidateNetwork), func(t *testing.T) {
- for _, candidateAddressType := range addressTypes(t, candidateNetwork) {
- t.Run(fmt.Sprintf("candidateAddressType=%s", candidateAddressType), func(t *testing.T) {
- s, err := makeStack()
- if err != nil {
- t.Fatal(err)
- }
-
- var wq waiter.Queue
- var eps []tcpip.Endpoint
- defer func() {
- for _, ep := range eps {
- ep.Close()
- }
- }()
- makeEP := func(network string) tcpip.Endpoint {
- var networkProtocolNumber tcpip.NetworkProtocolNumber
- switch network {
- case "ipv4":
- networkProtocolNumber = ipv4.ProtocolNumber
- case "ipv6", "dual":
- networkProtocolNumber = ipv6.ProtocolNumber
- default:
- t.Fatalf("unknown network: '%s'", network)
- }
- ep, err := s.NewEndpoint(tcp.ProtocolNumber, networkProtocolNumber, &wq)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
- eps = append(eps, ep)
- switch network {
- case "ipv4":
- case "ipv6":
- if err := ep.SetSockOpt(tcpip.V6OnlyOption(1)); err != nil {
- t.Fatalf("SetSockOpt(V6OnlyOption(1)) failed: %v", err)
- }
- case "dual":
- if err := ep.SetSockOpt(tcpip.V6OnlyOption(0)); err != nil {
- t.Fatalf("SetSockOpt(V6OnlyOption(0)) failed: %v", err)
- }
- default:
- t.Fatalf("unknown network: '%s'", network)
- }
- return ep
- }
-
- var v4reserved, v6reserved bool
- switch exhaustedAddressType {
- case "v4", "mapped":
- v4reserved = true
- case "v6":
- v6reserved = true
- // Dual stack sockets bound to v6 any reserve on v4 as
- // well.
- if isAny {
- switch exhaustedNetwork {
- case "ipv6":
- case "dual":
- v4reserved = true
- default:
- t.Fatalf("unknown address type: '%s'", exhaustedNetwork)
- }
- }
- default:
- t.Fatalf("unknown address type: '%s'", exhaustedAddressType)
- }
- var collides bool
- switch candidateAddressType {
- case "v4", "mapped":
- collides = v4reserved
- case "v6":
- collides = v6reserved
- default:
- t.Fatalf("unknown address type: '%s'", candidateAddressType)
- }
-
- for i := ports.FirstEphemeral; i <= math.MaxUint16; i++ {
- if makeEP(exhaustedNetwork).Bind(tcpip.FullAddress{Addr: address(t, exhaustedAddressType, isAny), Port: uint16(i)}); err != nil {
- t.Fatalf("Bind(%d) failed: %v", i, err)
- }
- }
- want := tcpip.ErrConnectStarted
- if collides {
- want = tcpip.ErrNoPortAvailable
- }
- if err := makeEP(candidateNetwork).Connect(tcpip.FullAddress{Addr: address(t, candidateAddressType, false), Port: 31337}); err != want {
- t.Fatalf("got ep.Connect(..) = %v, want = %v", err, want)
- }
- })
- }
- })
- }
- })
- }
- })
- }
- })
- }
-}
-
-func TestPathMTUDiscovery(t *testing.T) {
- // This test verifies the stack retransmits packets after it receives an
- // ICMP packet indicating that the path MTU has been exceeded.
- c := context.New(t, 1500)
- defer c.Cleanup()
-
- // Create new connection with MSS of 1460.
- const maxPayload = 1500 - header.TCPMinimumSize - header.IPv4MinimumSize
- c.CreateConnectedWithRawOptions(789, 30000, nil, []byte{
- header.TCPOptionMSS, 4, byte(maxPayload / 256), byte(maxPayload % 256),
- })
-
- // Send 3200 bytes of data.
- const writeSize = 3200
- data := buffer.NewView(writeSize)
- for i := range data {
- data[i] = byte(i)
- }
-
- if _, _, err := c.EP.Write(tcpip.SlicePayload(data), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
-
- receivePackets := func(c *context.Context, sizes []int, which int, seqNum uint32) []byte {
- var ret []byte
- for i, size := range sizes {
- p := c.GetPacket()
- if i == which {
- ret = p
- }
- checker.IPv4(t, p,
- checker.PayloadLen(size+header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(seqNum),
- checker.AckNum(790),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
- seqNum += uint32(size)
- }
- return ret
- }
-
- // Receive three packets.
- sizes := []int{maxPayload, maxPayload, writeSize - 2*maxPayload}
- first := receivePackets(c, sizes, 0, uint32(c.IRS)+1)
-
- // Send "packet too big" messages back to netstack.
- const newMTU = 1200
- const newMaxPayload = newMTU - header.IPv4MinimumSize - header.TCPMinimumSize
- mtu := []byte{0, 0, newMTU / 256, newMTU % 256}
- c.SendICMPPacket(header.ICMPv4DstUnreachable, header.ICMPv4FragmentationNeeded, mtu, first, newMTU)
-
- // See retransmitted packets. None exceeding the new max.
- sizes = []int{newMaxPayload, maxPayload - newMaxPayload, newMaxPayload, maxPayload - newMaxPayload, writeSize - 2*maxPayload}
- receivePackets(c, sizes, -1, uint32(c.IRS)+1)
-}
-
-func TestTCPEndpointProbe(t *testing.T) {
- c := context.New(t, 1500)
- defer c.Cleanup()
-
- invoked := make(chan struct{})
- c.Stack().AddTCPProbe(func(state stack.TCPEndpointState) {
- // Validate that the endpoint ID is what we expect.
- //
- // We don't do an extensive validation of every field but a
- // basic sanity test.
- if got, want := state.ID.LocalAddress, tcpip.Address(context.StackAddr); got != want {
- t.Fatalf("got LocalAddress: %q, want: %q", got, want)
- }
- if got, want := state.ID.LocalPort, c.Port; got != want {
- t.Fatalf("got LocalPort: %d, want: %d", got, want)
- }
- if got, want := state.ID.RemoteAddress, tcpip.Address(context.TestAddr); got != want {
- t.Fatalf("got RemoteAddress: %q, want: %q", got, want)
- }
- if got, want := state.ID.RemotePort, uint16(context.TestPort); got != want {
- t.Fatalf("got RemotePort: %d, want: %d", got, want)
- }
-
- invoked <- struct{}{}
- })
-
- c.CreateConnected(789, 30000, nil)
-
- data := []byte{1, 2, 3}
- c.SendPacket(data, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 790,
- AckNum: c.IRS.Add(1),
- RcvWnd: 30000,
- })
-
- select {
- case <-invoked:
- case <-time.After(100 * time.Millisecond):
- t.Fatalf("TCP Probe function was not called")
- }
-}
-
-func TestStackSetCongestionControl(t *testing.T) {
- testCases := []struct {
- cc tcpip.CongestionControlOption
- err *tcpip.Error
- }{
- {"reno", nil},
- {"cubic", nil},
- {"blahblah", tcpip.ErrNoSuchFile},
- }
-
- for _, tc := range testCases {
- t.Run(fmt.Sprintf("SetTransportProtocolOption(.., %v)", tc.cc), func(t *testing.T) {
- c := context.New(t, 1500)
- defer c.Cleanup()
-
- s := c.Stack()
-
- var oldCC tcpip.CongestionControlOption
- if err := s.TransportProtocolOption(tcp.ProtocolNumber, &oldCC); err != nil {
- t.Fatalf("s.TransportProtocolOption(%v, %v) = %v", tcp.ProtocolNumber, &oldCC, err)
- }
-
- if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, tc.cc); err != tc.err {
- t.Fatalf("s.SetTransportProtocolOption(%v, %v) = %v, want %v", tcp.ProtocolNumber, tc.cc, err, tc.err)
- }
-
- var cc tcpip.CongestionControlOption
- if err := s.TransportProtocolOption(tcp.ProtocolNumber, &cc); err != nil {
- t.Fatalf("s.TransportProtocolOption(%v, %v) = %v", tcp.ProtocolNumber, &cc, err)
- }
-
- got, want := cc, oldCC
- // If SetTransportProtocolOption is expected to succeed
- // then the returned value for congestion control should
- // match the one specified in the
- // SetTransportProtocolOption call above, else it should
- // be what it was before the call to
- // SetTransportProtocolOption.
- if tc.err == nil {
- want = tc.cc
- }
- if got != want {
- t.Fatalf("got congestion control: %v, want: %v", got, want)
- }
- })
- }
-}
-
-func TestStackAvailableCongestionControl(t *testing.T) {
- c := context.New(t, 1500)
- defer c.Cleanup()
-
- s := c.Stack()
-
- // Query permitted congestion control algorithms.
- var aCC tcpip.AvailableCongestionControlOption
- if err := s.TransportProtocolOption(tcp.ProtocolNumber, &aCC); err != nil {
- t.Fatalf("s.TransportProtocolOption(%v, %v) = %v", tcp.ProtocolNumber, &aCC, err)
- }
- if got, want := aCC, tcpip.AvailableCongestionControlOption("reno cubic"); got != want {
- t.Fatalf("got tcpip.AvailableCongestionControlOption: %v, want: %v", got, want)
- }
-}
-
-func TestStackSetAvailableCongestionControl(t *testing.T) {
- c := context.New(t, 1500)
- defer c.Cleanup()
-
- s := c.Stack()
-
- // Setting AvailableCongestionControlOption should fail.
- aCC := tcpip.AvailableCongestionControlOption("xyz")
- if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &aCC); err == nil {
- t.Fatalf("s.TransportProtocolOption(%v, %v) = nil, want non-nil", tcp.ProtocolNumber, &aCC)
- }
-
- // Verify that we still get the expected list of congestion control options.
- var cc tcpip.AvailableCongestionControlOption
- if err := s.TransportProtocolOption(tcp.ProtocolNumber, &cc); err != nil {
- t.Fatalf("s.TransportProtocolOption(%v, %v) = %v", tcp.ProtocolNumber, &cc, err)
- }
- if got, want := cc, tcpip.AvailableCongestionControlOption("reno cubic"); got != want {
- t.Fatalf("got tcpip.AvailableCongestionControlOption: %v, want: %v", got, want)
- }
-}
-
-func TestEndpointSetCongestionControl(t *testing.T) {
- testCases := []struct {
- cc tcpip.CongestionControlOption
- err *tcpip.Error
- }{
- {"reno", nil},
- {"cubic", nil},
- {"blahblah", tcpip.ErrNoSuchFile},
- }
-
- for _, connected := range []bool{false, true} {
- for _, tc := range testCases {
- t.Run(fmt.Sprintf("SetSockOpt(.., %v) w/ connected = %v", tc.cc, connected), func(t *testing.T) {
- c := context.New(t, 1500)
- defer c.Cleanup()
-
- // Create TCP endpoint.
- var err *tcpip.Error
- c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- var oldCC tcpip.CongestionControlOption
- if err := c.EP.GetSockOpt(&oldCC); err != nil {
- t.Fatalf("c.EP.SockOpt(%v) = %v", &oldCC, err)
- }
-
- if connected {
- c.Connect(789 /* iss */, 32768 /* rcvWnd */, nil)
- }
-
- if err := c.EP.SetSockOpt(tc.cc); err != tc.err {
- t.Fatalf("c.EP.SetSockOpt(%v) = %v, want %v", tc.cc, err, tc.err)
- }
-
- var cc tcpip.CongestionControlOption
- if err := c.EP.GetSockOpt(&cc); err != nil {
- t.Fatalf("c.EP.SockOpt(%v) = %v", &cc, err)
- }
-
- got, want := cc, oldCC
- // If SetSockOpt is expected to succeed then the
- // returned value for congestion control should match
- // the one specified in the SetSockOpt above, else it
- // should be what it was before the call to SetSockOpt.
- if tc.err == nil {
- want = tc.cc
- }
- if got != want {
- t.Fatalf("got congestion control: %v, want: %v", got, want)
- }
- })
- }
- }
-}
-
-func enableCUBIC(t *testing.T, c *context.Context) {
- t.Helper()
- opt := tcpip.CongestionControlOption("cubic")
- if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, opt); err != nil {
- t.Fatalf("c.s.SetTransportProtocolOption(tcp.ProtocolNumber, %v = %v", opt, err)
- }
-}
-
-func TestKeepalive(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnected(789, 30000, nil)
-
- c.EP.SetSockOpt(tcpip.KeepaliveIdleOption(10 * time.Millisecond))
- c.EP.SetSockOpt(tcpip.KeepaliveIntervalOption(10 * time.Millisecond))
- c.EP.SetSockOpt(tcpip.KeepaliveCountOption(5))
- c.EP.SetSockOpt(tcpip.KeepaliveEnabledOption(1))
-
- // 5 unacked keepalives are sent. ACK each one, and check that the
- // connection stays alive after 5.
- for i := 0; i < 10; i++ {
- b := c.GetPacket()
- checker.IPv4(t, b,
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)),
- checker.AckNum(uint32(790)),
- checker.TCPFlags(header.TCPFlagAck),
- ),
- )
-
- // Acknowledge the keepalive.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 790,
- AckNum: c.IRS,
- RcvWnd: 30000,
- })
- }
-
- // Check that the connection is still alive.
- if _, _, err := c.EP.Read(nil); err != tcpip.ErrWouldBlock {
- t.Fatalf("got c.EP.Read(nil) = %v, want = %v", err, tcpip.ErrWouldBlock)
- }
-
- // Send some data and wait before ACKing it. Keepalives should be disabled
- // during this period.
- view := buffer.NewView(3)
- if _, _, err := c.EP.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Write failed: %v", err)
- }
-
- next := uint32(c.IRS) + 1
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(len(view)+header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(next),
- checker.AckNum(790),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
-
- // Wait for the packet to be retransmitted. Verify that no keepalives
- // were sent.
- checker.IPv4(t, c.GetPacket(),
- checker.PayloadLen(len(view)+header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(next),
- checker.AckNum(790),
- checker.TCPFlags(header.TCPFlagAck|header.TCPFlagPsh),
- ),
- )
- c.CheckNoPacket("Keepalive packet received while unACKed data is pending")
-
- next += uint32(len(view))
-
- // Send ACK. Keepalives should start sending again.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: 790,
- AckNum: seqnum.Value(next),
- RcvWnd: 30000,
- })
-
- // Now receive 5 keepalives, but don't ACK them. The connection
- // should be reset after 5.
- for i := 0; i < 5; i++ {
- b := c.GetPacket()
- checker.IPv4(t, b,
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(next-1)),
- checker.AckNum(uint32(790)),
- checker.TCPFlags(header.TCPFlagAck),
- ),
- )
- }
-
- // The connection should be terminated after 5 unacked keepalives.
- checker.IPv4(t, c.GetPacket(),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(next)),
- checker.AckNum(uint32(790)),
- checker.TCPFlags(header.TCPFlagAck|header.TCPFlagRst),
- ),
- )
-
- if _, _, err := c.EP.Read(nil); err != tcpip.ErrTimeout {
- t.Fatalf("got c.EP.Read(nil) = %v, want = %v", err, tcpip.ErrTimeout)
- }
-}
-
-func executeHandshake(t *testing.T, c *context.Context, srcPort uint16, synCookieInUse bool) (irs, iss seqnum.Value) {
- // Send a SYN request.
- irs = seqnum.Value(789)
- c.SendPacket(nil, &context.Headers{
- SrcPort: srcPort,
- DstPort: context.StackPort,
- Flags: header.TCPFlagSyn,
- SeqNum: irs,
- RcvWnd: 30000,
- })
-
- // Receive the SYN-ACK reply.w
- b := c.GetPacket()
- tcp := header.TCP(header.IPv4(b).Payload())
- iss = seqnum.Value(tcp.SequenceNumber())
- tcpCheckers := []checker.TransportChecker{
- checker.SrcPort(context.StackPort),
- checker.DstPort(srcPort),
- checker.TCPFlags(header.TCPFlagAck | header.TCPFlagSyn),
- checker.AckNum(uint32(irs) + 1),
- }
-
- if synCookieInUse {
- // When cookies are in use window scaling is disabled.
- tcpCheckers = append(tcpCheckers, checker.TCPSynOptions(header.TCPSynOptions{
- WS: -1,
- MSS: c.MSSWithoutOptions(),
- }))
- }
-
- checker.IPv4(t, b, checker.TCP(tcpCheckers...))
-
- // Send ACK.
- c.SendPacket(nil, &context.Headers{
- SrcPort: srcPort,
- DstPort: context.StackPort,
- Flags: header.TCPFlagAck,
- SeqNum: irs + 1,
- AckNum: iss + 1,
- RcvWnd: 30000,
- })
- return irs, iss
-}
-
-// TestListenBacklogFull tests that netstack does not complete handshakes if the
-// listen backlog for the endpoint is full.
-func TestListenBacklogFull(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- // Create TCP endpoint.
- var err *tcpip.Error
- c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- // Bind to wildcard.
- if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- // Test acceptance.
- // Start listening.
- listenBacklog := 2
- if err := c.EP.Listen(listenBacklog); err != nil {
- t.Fatalf("Listen failed: %v", err)
- }
-
- for i := 0; i < listenBacklog; i++ {
- executeHandshake(t, c, context.TestPort+uint16(i), false /*synCookieInUse */)
- }
-
- time.Sleep(50 * time.Millisecond)
-
- // Now execute send one more SYN. The stack should not respond as the backlog
- // is full at this point.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort + 2,
- DstPort: context.StackPort,
- Flags: header.TCPFlagSyn,
- SeqNum: seqnum.Value(789),
- RcvWnd: 30000,
- })
- c.CheckNoPacketTimeout("unexpected packet received", 50*time.Millisecond)
-
- // Try to accept the connections in the backlog.
- we, ch := waiter.NewChannelEntry(nil)
- c.WQ.EventRegister(&we, waiter.EventIn)
- defer c.WQ.EventUnregister(&we)
-
- for i := 0; i < listenBacklog; i++ {
- _, _, err = c.EP.Accept()
- if err == tcpip.ErrWouldBlock {
- // Wait for connection to be established.
- select {
- case <-ch:
- _, _, err = c.EP.Accept()
- if err != nil {
- t.Fatalf("Accept failed: %v", err)
- }
-
- case <-time.After(1 * time.Second):
- t.Fatalf("Timed out waiting for accept")
- }
- }
- }
-
- // Now verify that there are no more connections that can be accepted.
- _, _, err = c.EP.Accept()
- if err != tcpip.ErrWouldBlock {
- select {
- case <-ch:
- t.Fatalf("unexpected endpoint delivered on Accept: %+v", c.EP)
- case <-time.After(1 * time.Second):
- }
- }
-
- // Now a new handshake must succeed.
- executeHandshake(t, c, context.TestPort+2, false /*synCookieInUse */)
-
- newEP, _, err := c.EP.Accept()
- if err == tcpip.ErrWouldBlock {
- // Wait for connection to be established.
- select {
- case <-ch:
- newEP, _, err = c.EP.Accept()
- if err != nil {
- t.Fatalf("Accept failed: %v", err)
- }
-
- case <-time.After(1 * time.Second):
- t.Fatalf("Timed out waiting for accept")
- }
- }
-
- // Now verify that the TCP socket is usable and in a connected state.
- data := "Don't panic"
- newEP.Write(tcpip.SlicePayload(buffer.NewViewFromBytes([]byte(data))), tcpip.WriteOptions{})
- b := c.GetPacket()
- tcp := header.TCP(header.IPv4(b).Payload())
- if string(tcp.Payload()) != data {
- t.Fatalf("Unexpected data: got %v, want %v", string(tcp.Payload()), data)
- }
-}
-
-func TestListenSynRcvdQueueFull(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- // Create TCP endpoint.
- var err *tcpip.Error
- c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- // Bind to wildcard.
- if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- // Test acceptance.
- // Start listening.
- listenBacklog := 1
- if err := c.EP.Listen(listenBacklog); err != nil {
- t.Fatalf("Listen failed: %v", err)
- }
-
- // Send two SYN's the first one should get a SYN-ACK, the
- // second one should not get any response and is dropped as
- // the synRcvd count will be equal to backlog.
- irs := seqnum.Value(789)
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: context.StackPort,
- Flags: header.TCPFlagSyn,
- SeqNum: seqnum.Value(789),
- RcvWnd: 30000,
- })
-
- // Receive the SYN-ACK reply.
- b := c.GetPacket()
- tcp := header.TCP(header.IPv4(b).Payload())
- iss := seqnum.Value(tcp.SequenceNumber())
- tcpCheckers := []checker.TransportChecker{
- checker.SrcPort(context.StackPort),
- checker.DstPort(context.TestPort),
- checker.TCPFlags(header.TCPFlagAck | header.TCPFlagSyn),
- checker.AckNum(uint32(irs) + 1),
- }
- checker.IPv4(t, b, checker.TCP(tcpCheckers...))
-
- // Now execute send one more SYN. The stack should not respond as the backlog
- // is full at this point.
- //
- // NOTE: we did not complete the handshake for the previous one so the
- // accept backlog should be empty and there should be one connection in
- // synRcvd state.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort + 1,
- DstPort: context.StackPort,
- Flags: header.TCPFlagSyn,
- SeqNum: seqnum.Value(889),
- RcvWnd: 30000,
- })
- c.CheckNoPacketTimeout("unexpected packet received", 50*time.Millisecond)
-
- // Now complete the previous connection and verify that there is a connection
- // to accept.
- // Send ACK.
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: context.StackPort,
- Flags: header.TCPFlagAck,
- SeqNum: irs + 1,
- AckNum: iss + 1,
- RcvWnd: 30000,
- })
-
- // Try to accept the connections in the backlog.
- we, ch := waiter.NewChannelEntry(nil)
- c.WQ.EventRegister(&we, waiter.EventIn)
- defer c.WQ.EventUnregister(&we)
-
- newEP, _, err := c.EP.Accept()
- if err == tcpip.ErrWouldBlock {
- // Wait for connection to be established.
- select {
- case <-ch:
- newEP, _, err = c.EP.Accept()
- if err != nil {
- t.Fatalf("Accept failed: %v", err)
- }
-
- case <-time.After(1 * time.Second):
- t.Fatalf("Timed out waiting for accept")
- }
- }
-
- // Now verify that the TCP socket is usable and in a connected state.
- data := "Don't panic"
- newEP.Write(tcpip.SlicePayload(buffer.NewViewFromBytes([]byte(data))), tcpip.WriteOptions{})
- pkt := c.GetPacket()
- tcp = header.TCP(header.IPv4(pkt).Payload())
- if string(tcp.Payload()) != data {
- t.Fatalf("Unexpected data: got %v, want %v", string(tcp.Payload()), data)
- }
-}
-
-func TestListenBacklogFullSynCookieInUse(t *testing.T) {
- saved := tcp.SynRcvdCountThreshold
- defer func() {
- tcp.SynRcvdCountThreshold = saved
- }()
- tcp.SynRcvdCountThreshold = 1
-
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- // Create TCP endpoint.
- var err *tcpip.Error
- c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- // Bind to wildcard.
- if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
-
- // Test acceptance.
- // Start listening.
- listenBacklog := 1
- portOffset := uint16(0)
- if err := c.EP.Listen(listenBacklog); err != nil {
- t.Fatalf("Listen failed: %v", err)
- }
-
- executeHandshake(t, c, context.TestPort+portOffset, false)
- portOffset++
- // Wait for this to be delivered to the accept queue.
- time.Sleep(50 * time.Millisecond)
-
- // Send a SYN request.
- irs := seqnum.Value(789)
- c.SendPacket(nil, &context.Headers{
- SrcPort: context.TestPort,
- DstPort: context.StackPort,
- Flags: header.TCPFlagSyn,
- SeqNum: irs,
- RcvWnd: 30000,
- })
- // The Syn should be dropped as the endpoint's backlog is full.
- c.CheckNoPacketTimeout("unexpected packet received", 50*time.Millisecond)
-
- // Verify that there is only one acceptable connection at this point.
- we, ch := waiter.NewChannelEntry(nil)
- c.WQ.EventRegister(&we, waiter.EventIn)
- defer c.WQ.EventUnregister(&we)
-
- _, _, err = c.EP.Accept()
- if err == tcpip.ErrWouldBlock {
- // Wait for connection to be established.
- select {
- case <-ch:
- _, _, err = c.EP.Accept()
- if err != nil {
- t.Fatalf("Accept failed: %v", err)
- }
-
- case <-time.After(1 * time.Second):
- t.Fatalf("Timed out waiting for accept")
- }
- }
-
- // Now verify that there are no more connections that can be accepted.
- _, _, err = c.EP.Accept()
- if err != tcpip.ErrWouldBlock {
- select {
- case <-ch:
- t.Fatalf("unexpected endpoint delivered on Accept: %+v", c.EP)
- case <-time.After(1 * time.Second):
- }
- }
-}
-
-func TestPassiveConnectionAttemptIncrement(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
- c.EP = ep
- if err := ep.Bind(tcpip.FullAddress{Addr: context.StackAddr, Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
- if got, want := tcp.EndpointState(ep.State()), tcp.StateBound; got != want {
- t.Errorf("Unexpected endpoint state: want %v, got %v", want, got)
- }
- if err := c.EP.Listen(1); err != nil {
- t.Fatalf("Listen failed: %v", err)
- }
- if got, want := tcp.EndpointState(c.EP.State()), tcp.StateListen; got != want {
- t.Errorf("Unexpected endpoint state: want %v, got %v", want, got)
- }
-
- stats := c.Stack().Stats()
- want := stats.TCP.PassiveConnectionOpenings.Value() + 1
-
- srcPort := uint16(context.TestPort)
- executeHandshake(t, c, srcPort+1, false)
-
- we, ch := waiter.NewChannelEntry(nil)
- c.WQ.EventRegister(&we, waiter.EventIn)
- defer c.WQ.EventUnregister(&we)
-
- // Verify that there is only one acceptable connection at this point.
- _, _, err = c.EP.Accept()
- if err == tcpip.ErrWouldBlock {
- // Wait for connection to be established.
- select {
- case <-ch:
- _, _, err = c.EP.Accept()
- if err != nil {
- t.Fatalf("Accept failed: %v", err)
- }
-
- case <-time.After(1 * time.Second):
- t.Fatalf("Timed out waiting for accept")
- }
- }
-
- if got := stats.TCP.PassiveConnectionOpenings.Value(); got != want {
- t.Errorf("got stats.TCP.PassiveConnectionOpenings.Value() = %v, want = %v", got, want)
- }
-}
-
-func TestPassiveFailedConnectionAttemptIncrement(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- stats := c.Stack().Stats()
- ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
- c.EP = ep
- if err := c.EP.Bind(tcpip.FullAddress{Addr: context.StackAddr, Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
- if err := c.EP.Listen(1); err != nil {
- t.Fatalf("Listen failed: %v", err)
- }
-
- srcPort := uint16(context.TestPort)
- // Now attempt a handshakes it will fill up the accept backlog.
- executeHandshake(t, c, srcPort, false)
-
- // Give time for the final ACK to be processed as otherwise the next handshake could
- // get accepted before the previous one based on goroutine scheduling.
- time.Sleep(50 * time.Millisecond)
-
- want := stats.TCP.ListenOverflowSynDrop.Value() + 1
-
- // Now we will send one more SYN and this one should get dropped
- // Send a SYN request.
- c.SendPacket(nil, &context.Headers{
- SrcPort: srcPort + 2,
- DstPort: context.StackPort,
- Flags: header.TCPFlagSyn,
- SeqNum: seqnum.Value(789),
- RcvWnd: 30000,
- })
-
- time.Sleep(50 * time.Millisecond)
- if got := stats.TCP.ListenOverflowSynDrop.Value(); got != want {
- t.Errorf("got stats.TCP.ListenOverflowSynDrop.Value() = %v, want = %v", got, want)
- }
-
- we, ch := waiter.NewChannelEntry(nil)
- c.WQ.EventRegister(&we, waiter.EventIn)
- defer c.WQ.EventUnregister(&we)
-
- // Now check that there is one acceptable connections.
- _, _, err = c.EP.Accept()
- if err == tcpip.ErrWouldBlock {
- // Wait for connection to be established.
- select {
- case <-ch:
- _, _, err = c.EP.Accept()
- if err != nil {
- t.Fatalf("Accept failed: %v", err)
- }
-
- case <-time.After(1 * time.Second):
- t.Fatalf("Timed out waiting for accept")
- }
- }
-}
-
-func TestEndpointBindListenAcceptState(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
- wq := &waiter.Queue{}
- ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- if err := ep.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
- t.Fatalf("Bind failed: %v", err)
- }
- if got, want := tcp.EndpointState(ep.State()), tcp.StateBound; got != want {
- t.Errorf("Unexpected endpoint state: want %v, got %v", want, got)
- }
-
- if err := ep.Listen(10); err != nil {
- t.Fatalf("Listen failed: %v", err)
- }
- if got, want := tcp.EndpointState(ep.State()), tcp.StateListen; got != want {
- t.Errorf("Unexpected endpoint state: want %v, got %v", want, got)
- }
-
- c.PassiveConnectWithOptions(100, 5, header.TCPSynOptions{MSS: defaultIPv4MSS})
-
- // Try to accept the connection.
- we, ch := waiter.NewChannelEntry(nil)
- wq.EventRegister(&we, waiter.EventIn)
- defer wq.EventUnregister(&we)
-
- aep, _, err := ep.Accept()
- if err == tcpip.ErrWouldBlock {
- // Wait for connection to be established.
- select {
- case <-ch:
- aep, _, err = ep.Accept()
- if err != nil {
- t.Fatalf("Accept failed: %v", err)
- }
-
- case <-time.After(1 * time.Second):
- t.Fatalf("Timed out waiting for accept")
- }
- }
- if got, want := tcp.EndpointState(aep.State()), tcp.StateEstablished; got != want {
- t.Errorf("Unexpected endpoint state: want %v, got %v", want, got)
- }
- // Listening endpoint remains in listen state.
- if got, want := tcp.EndpointState(ep.State()), tcp.StateListen; got != want {
- t.Errorf("Unexpected endpoint state: want %v, got %v", want, got)
- }
-
- ep.Close()
- // Give worker goroutines time to receive the close notification.
- time.Sleep(1 * time.Second)
- if got, want := tcp.EndpointState(ep.State()), tcp.StateClose; got != want {
- t.Errorf("Unexpected endpoint state: want %v, got %v", want, got)
- }
- // Accepted endpoint remains open when the listen endpoint is closed.
- if got, want := tcp.EndpointState(aep.State()), tcp.StateEstablished; got != want {
- t.Errorf("Unexpected endpoint state: want %v, got %v", want, got)
- }
-
-}
-
-// This test verifies that the auto tuning does not grow the receive buffer if
-// the application is not reading the data actively.
-func TestReceiveBufferAutoTuningApplicationLimited(t *testing.T) {
- const mtu = 1500
- const mss = mtu - header.IPv4MinimumSize - header.TCPMinimumSize
-
- c := context.New(t, mtu)
- defer c.Cleanup()
-
- stk := c.Stack()
- // Set lower limits for auto-tuning tests. This is required because the
- // test stops the worker which can cause packets to be dropped because
- // the segment queue holding unprocessed packets is limited to 500.
- const receiveBufferSize = 80 << 10 // 80KB.
- const maxReceiveBufferSize = receiveBufferSize * 10
- if err := stk.SetTransportProtocolOption(tcp.ProtocolNumber, tcp.ReceiveBufferSizeOption{1, receiveBufferSize, maxReceiveBufferSize}); err != nil {
- t.Fatalf("SetTransportProtocolOption failed: %v", err)
- }
-
- // Enable auto-tuning.
- if err := stk.SetTransportProtocolOption(tcp.ProtocolNumber, tcpip.ModerateReceiveBufferOption(true)); err != nil {
- t.Fatalf("SetTransportProtocolOption failed: %v", err)
- }
- // Change the expected window scale to match the value needed for the
- // maximum buffer size defined above.
- c.WindowScale = uint8(tcp.FindWndScale(maxReceiveBufferSize))
-
- rawEP := c.CreateConnectedWithOptions(header.TCPSynOptions{TS: true, WS: 4})
-
- // NOTE: The timestamp values in the sent packets are meaningless to the
- // peer so we just increment the timestamp value by 1 every batch as we
- // are not really using them for anything. Send a single byte to verify
- // the advertised window.
- tsVal := rawEP.TSVal + 1
-
- // Introduce a 25ms latency by delaying the first byte.
- latency := 25 * time.Millisecond
- time.Sleep(latency)
- rawEP.SendPacketWithTS([]byte{1}, tsVal)
-
- // Verify that the ACK has the expected window.
- wantRcvWnd := receiveBufferSize
- wantRcvWnd = (wantRcvWnd >> uint32(c.WindowScale))
- rawEP.VerifyACKRcvWnd(uint16(wantRcvWnd - 1))
- time.Sleep(25 * time.Millisecond)
-
- // Allocate a large enough payload for the test.
- b := make([]byte, int(receiveBufferSize)*2)
- offset := 0
- payloadSize := receiveBufferSize - 1
- worker := (c.EP).(interface {
- StopWork()
- ResumeWork()
- })
- tsVal++
-
- // Stop the worker goroutine.
- worker.StopWork()
- start := offset
- end := offset + payloadSize
- packetsSent := 0
- for ; start < end; start += mss {
- rawEP.SendPacketWithTS(b[start:start+mss], tsVal)
- packetsSent++
- }
- // Resume the worker so that it only sees the packets once all of them
- // are waiting to be read.
- worker.ResumeWork()
-
- // Since we read no bytes the window should goto zero till the
- // application reads some of the data.
- // Discard all intermediate acks except the last one.
- if packetsSent > 100 {
- for i := 0; i < (packetsSent / 100); i++ {
- _ = c.GetPacket()
- }
- }
- rawEP.VerifyACKRcvWnd(0)
-
- time.Sleep(25 * time.Millisecond)
- // Verify that sending more data when window is closed is dropped and
- // not acked.
- rawEP.SendPacketWithTS(b[start:start+mss], tsVal)
-
- // Verify that the stack sends us back an ACK with the sequence number
- // of the last packet sent indicating it was dropped.
- p := c.GetPacket()
- checker.IPv4(t, p, checker.TCP(
- checker.AckNum(uint32(rawEP.NextSeqNum)-uint32(mss)),
- checker.Window(0),
- ))
-
- // Now read all the data from the endpoint and verify that advertised
- // window increases to the full available buffer size.
- for {
- _, _, err := c.EP.Read(nil)
- if err == tcpip.ErrWouldBlock {
- break
- }
- }
-
- // Verify that we receive a non-zero window update ACK. When running
- // under thread santizer this test can end up sending more than 1
- // ack, 1 for the non-zero window
- p = c.GetPacket()
- checker.IPv4(t, p, checker.TCP(
- checker.AckNum(uint32(rawEP.NextSeqNum)-uint32(mss)),
- func(t *testing.T, h header.Transport) {
- tcp, ok := h.(header.TCP)
- if !ok {
- return
- }
- if w := tcp.WindowSize(); w == 0 || w > uint16(wantRcvWnd) {
- t.Errorf("expected a non-zero window: got %d, want <= wantRcvWnd", w, wantRcvWnd)
- }
- },
- ))
-}
-
-// This test verifies that the auto tuning does not grow the receive buffer if
-// the application is not reading the data actively.
-func TestReceiveBufferAutoTuning(t *testing.T) {
- const mtu = 1500
- const mss = mtu - header.IPv4MinimumSize - header.TCPMinimumSize
-
- c := context.New(t, mtu)
- defer c.Cleanup()
-
- // Enable Auto-tuning.
- stk := c.Stack()
- // Set lower limits for auto-tuning tests. This is required because the
- // test stops the worker which can cause packets to be dropped because
- // the segment queue holding unprocessed packets is limited to 500.
- const receiveBufferSize = 80 << 10 // 80KB.
- const maxReceiveBufferSize = receiveBufferSize * 10
- if err := stk.SetTransportProtocolOption(tcp.ProtocolNumber, tcp.ReceiveBufferSizeOption{1, receiveBufferSize, maxReceiveBufferSize}); err != nil {
- t.Fatalf("SetTransportProtocolOption failed: %v", err)
- }
-
- // Enable auto-tuning.
- if err := stk.SetTransportProtocolOption(tcp.ProtocolNumber, tcpip.ModerateReceiveBufferOption(true)); err != nil {
- t.Fatalf("SetTransportProtocolOption failed: %v", err)
- }
- // Change the expected window scale to match the value needed for the
- // maximum buffer size used by stack.
- c.WindowScale = uint8(tcp.FindWndScale(maxReceiveBufferSize))
-
- rawEP := c.CreateConnectedWithOptions(header.TCPSynOptions{TS: true, WS: 4})
-
- wantRcvWnd := receiveBufferSize
- scaleRcvWnd := func(rcvWnd int) uint16 {
- return uint16(rcvWnd >> uint16(c.WindowScale))
- }
- // Allocate a large array to send to the endpoint.
- b := make([]byte, receiveBufferSize*48)
-
- // In every iteration we will send double the number of bytes sent in
- // the previous iteration and read the same from the app. The received
- // window should grow by at least 2x of bytes read by the app in every
- // RTT.
- offset := 0
- payloadSize := receiveBufferSize / 8
- worker := (c.EP).(interface {
- StopWork()
- ResumeWork()
- })
- tsVal := rawEP.TSVal
- // We are going to do our own computation of what the moderated receive
- // buffer should be based on sent/copied data per RTT and verify that
- // the advertised window by the stack matches our calculations.
- prevCopied := 0
- done := false
- latency := 1 * time.Millisecond
- for i := 0; !done; i++ {
- tsVal++
-
- // Stop the worker goroutine.
- worker.StopWork()
- start := offset
- end := offset + payloadSize
- totalSent := 0
- packetsSent := 0
- for ; start < end; start += mss {
- rawEP.SendPacketWithTS(b[start:start+mss], tsVal)
- totalSent += mss
- packetsSent++
- }
- // Resume it so that it only sees the packets once all of them
- // are waiting to be read.
- worker.ResumeWork()
-
- // Give 1ms for the worker to process the packets.
- time.Sleep(1 * time.Millisecond)
-
- // Verify that the advertised window on the ACK is reduced by
- // the total bytes sent.
- expectedWnd := wantRcvWnd - totalSent
- if packetsSent > 100 {
- for i := 0; i < (packetsSent / 100); i++ {
- _ = c.GetPacket()
- }
- }
- rawEP.VerifyACKRcvWnd(scaleRcvWnd(expectedWnd))
-
- // Now read all the data from the endpoint and invoke the
- // moderation API to allow for receive buffer auto-tuning
- // to happen before we measure the new window.
- totalCopied := 0
- for {
- b, _, err := c.EP.Read(nil)
- if err == tcpip.ErrWouldBlock {
- break
- }
- totalCopied += len(b)
- }
-
- // Invoke the moderation API. This is required for auto-tuning
- // to happen. This method is normally expected to be invoked
- // from a higher layer than tcpip.Endpoint. So we simulate
- // copying to user-space by invoking it explicitly here.
- c.EP.ModerateRecvBuf(totalCopied)
-
- // Now send a keep-alive packet to trigger an ACK so that we can
- // measure the new window.
- rawEP.NextSeqNum--
- rawEP.SendPacketWithTS(nil, tsVal)
- rawEP.NextSeqNum++
-
- if i == 0 {
- // In the first iteration the receiver based RTT is not
- // yet known as a result the moderation code should not
- // increase the advertised window.
- rawEP.VerifyACKRcvWnd(scaleRcvWnd(wantRcvWnd))
- prevCopied = totalCopied
- } else {
- rttCopied := totalCopied
- if i == 1 {
- // The moderation code accumulates copied bytes till
- // RTT is established. So add in the bytes sent in
- // the first iteration to the total bytes for this
- // RTT.
- rttCopied += prevCopied
- // Now reset it to the initial value used by the
- // auto tuning logic.
- prevCopied = tcp.InitialCwnd * mss * 2
- }
- newWnd := rttCopied<<1 + 16*mss
- grow := (newWnd * (rttCopied - prevCopied)) / prevCopied
- newWnd += (grow << 1)
- if newWnd > maxReceiveBufferSize {
- newWnd = maxReceiveBufferSize
- done = true
- }
- rawEP.VerifyACKRcvWnd(scaleRcvWnd(newWnd))
- wantRcvWnd = newWnd
- prevCopied = rttCopied
- // Increase the latency after first two iterations to
- // establish a low RTT value in the receiver since it
- // only tracks the lowest value. This ensures that when
- // ModerateRcvBuf is called the elapsed time is always >
- // rtt. Without this the test is flaky due to delays due
- // to scheduling/wakeup etc.
- latency += 50 * time.Millisecond
- }
- time.Sleep(latency)
- offset += payloadSize
- payloadSize *= 2
- }
-}
diff --git a/pkg/tcpip/transport/tcp/tcp_timestamp_test.go b/pkg/tcpip/transport/tcp/tcp_timestamp_test.go
deleted file mode 100644
index a641e953d..000000000
--- a/pkg/tcpip/transport/tcp/tcp_timestamp_test.go
+++ /dev/null
@@ -1,295 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tcp_test
-
-import (
- "bytes"
- "math/rand"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/checker"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
- "gvisor.dev/gvisor/pkg/tcpip/transport/tcp/testing/context"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-// createConnectedWithTimestampOption creates and connects c.ep with the
-// timestamp option enabled.
-func createConnectedWithTimestampOption(c *context.Context) *context.RawEndpoint {
- return c.CreateConnectedWithOptions(header.TCPSynOptions{TS: true, TSVal: 1})
-}
-
-// TestTimeStampEnabledConnect tests that netstack sends the timestamp option on
-// an active connect and sets the TS Echo Reply fields correctly when the
-// SYN-ACK also indicates support for the TS option and provides a TSVal.
-func TestTimeStampEnabledConnect(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- rep := createConnectedWithTimestampOption(c)
-
- // Register for read and validate that we have data to read.
- we, ch := waiter.NewChannelEntry(nil)
- c.WQ.EventRegister(&we, waiter.EventIn)
- defer c.WQ.EventUnregister(&we)
-
- // The following tests ensure that TS option once enabled behaves
- // correctly as described in
- // https://tools.ietf.org/html/rfc7323#section-4.3.
- //
- // We are not testing delayed ACKs here, but we do test out of order
- // packet delivery and filling the sequence number hole created due to
- // the out of order packet.
- //
- // The test also verifies that the sequence numbers and timestamps are
- // as expected.
- data := []byte{1, 2, 3}
-
- // First we increment tsVal by a small amount.
- tsVal := rep.TSVal + 100
- rep.SendPacketWithTS(data, tsVal)
- rep.VerifyACKWithTS(tsVal)
-
- // Next we send an out of order packet.
- rep.NextSeqNum += 3
- tsVal += 200
- rep.SendPacketWithTS(data, tsVal)
-
- // The ACK should contain the original sequenceNumber and an older TS.
- rep.NextSeqNum -= 6
- rep.VerifyACKWithTS(tsVal - 200)
-
- // Next we fill the hole and the returned ACK should contain the
- // cumulative sequence number acking all data sent till now and have the
- // latest timestamp sent below in its TSEcr field.
- tsVal -= 100
- rep.SendPacketWithTS(data, tsVal)
- rep.NextSeqNum += 3
- rep.VerifyACKWithTS(tsVal)
-
- // Increment tsVal by a large value that doesn't result in a wrap around.
- tsVal += 0x7fffffff
- rep.SendPacketWithTS(data, tsVal)
- rep.VerifyACKWithTS(tsVal)
-
- // Increment tsVal again by a large value which should cause the
- // timestamp value to wrap around. The returned ACK should contain the
- // wrapped around timestamp in its tsEcr field and not the tsVal from
- // the previous packet sent above.
- tsVal += 0x7fffffff
- rep.SendPacketWithTS(data, tsVal)
- rep.VerifyACKWithTS(tsVal)
-
- select {
- case <-ch:
- case <-time.After(1 * time.Second):
- t.Fatalf("Timed out waiting for data to arrive")
- }
-
- // There should be 5 views to read and each of them should
- // contain the same data.
- for i := 0; i < 5; i++ {
- got, _, err := c.EP.Read(nil)
- if err != nil {
- t.Fatalf("Unexpected error from Read: %v", err)
- }
- if want := data; bytes.Compare(got, want) != 0 {
- t.Fatalf("Data is different: got: %v, want: %v", got, want)
- }
- }
-}
-
-// TestTimeStampDisabledConnect tests that netstack sends timestamp option on an
-// active connect but if the SYN-ACK doesn't specify the TS option then
-// timestamp option is not enabled and future packets do not contain a
-// timestamp.
-func TestTimeStampDisabledConnect(t *testing.T) {
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- c.CreateConnectedWithOptions(header.TCPSynOptions{})
-}
-
-func timeStampEnabledAccept(t *testing.T, cookieEnabled bool, wndScale int, wndSize uint16) {
- savedSynCountThreshold := tcp.SynRcvdCountThreshold
- defer func() {
- tcp.SynRcvdCountThreshold = savedSynCountThreshold
- }()
-
- if cookieEnabled {
- tcp.SynRcvdCountThreshold = 0
- }
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- t.Logf("Test w/ CookieEnabled = %v", cookieEnabled)
- tsVal := rand.Uint32()
- c.AcceptWithOptions(wndScale, header.TCPSynOptions{MSS: defaultIPv4MSS, TS: true, TSVal: tsVal})
-
- // Now send some data and validate that timestamp is echoed correctly in the ACK.
- data := []byte{1, 2, 3}
- view := buffer.NewView(len(data))
- copy(view, data)
-
- if _, _, err := c.EP.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Unexpected error from Write: %v", err)
- }
-
- // Check that data is received and that the timestamp option TSEcr field
- // matches the expected value.
- b := c.GetPacket()
- checker.IPv4(t, b,
- // Add 12 bytes for the timestamp option + 2 NOPs to align at 4
- // byte boundary.
- checker.PayloadLen(len(data)+header.TCPMinimumSize+12),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(790),
- checker.Window(wndSize),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- checker.TCPTimestampChecker(true, 0, tsVal+1),
- ),
- )
-}
-
-// TestTimeStampEnabledAccept tests that if the SYN on a passive connect
-// specifies the Timestamp option then the Timestamp option is sent on a SYN-ACK
-// and echoes the tsVal field of the original SYN in the tcEcr field of the
-// SYN-ACK. We cover the cases where SYN cookies are enabled/disabled and verify
-// that Timestamp option is enabled in both cases if requested in the original
-// SYN.
-func TestTimeStampEnabledAccept(t *testing.T) {
- testCases := []struct {
- cookieEnabled bool
- wndScale int
- wndSize uint16
- }{
- {true, -1, 0xffff}, // When cookie is used window scaling is disabled.
- {false, 5, 0x8000}, // DefaultReceiveBufferSize is 1MB >> 5.
- }
- for _, tc := range testCases {
- timeStampEnabledAccept(t, tc.cookieEnabled, tc.wndScale, tc.wndSize)
- }
-}
-
-func timeStampDisabledAccept(t *testing.T, cookieEnabled bool, wndScale int, wndSize uint16) {
- savedSynCountThreshold := tcp.SynRcvdCountThreshold
- defer func() {
- tcp.SynRcvdCountThreshold = savedSynCountThreshold
- }()
- if cookieEnabled {
- tcp.SynRcvdCountThreshold = 0
- }
-
- c := context.New(t, defaultMTU)
- defer c.Cleanup()
-
- t.Logf("Test w/ CookieEnabled = %v", cookieEnabled)
- c.AcceptWithOptions(wndScale, header.TCPSynOptions{MSS: defaultIPv4MSS})
-
- // Now send some data with the accepted connection endpoint and validate
- // that no timestamp option is sent in the TCP segment.
- data := []byte{1, 2, 3}
- view := buffer.NewView(len(data))
- copy(view, data)
-
- if _, _, err := c.EP.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{}); err != nil {
- t.Fatalf("Unexpected error from Write: %v", err)
- }
-
- // Check that data is received and that the timestamp option is disabled
- // when SYN cookies are enabled/disabled.
- b := c.GetPacket()
- checker.IPv4(t, b,
- checker.PayloadLen(len(data)+header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(context.TestPort),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(790),
- checker.Window(wndSize),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- checker.TCPTimestampChecker(false, 0, 0),
- ),
- )
-}
-
-// TestTimeStampDisabledAccept tests that Timestamp option is not used when the
-// peer doesn't advertise it and connection is established with Accept().
-func TestTimeStampDisabledAccept(t *testing.T) {
- testCases := []struct {
- cookieEnabled bool
- wndScale int
- wndSize uint16
- }{
- {true, -1, 0xffff}, // When cookie is used window scaling is disabled.
- {false, 5, 0x8000}, // DefaultReceiveBufferSize is 1MB >> 5.
- }
- for _, tc := range testCases {
- timeStampDisabledAccept(t, tc.cookieEnabled, tc.wndScale, tc.wndSize)
- }
-}
-
-func TestSendGreaterThanMTUWithOptions(t *testing.T) {
- const maxPayload = 100
- c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxPayload))
- defer c.Cleanup()
-
- createConnectedWithTimestampOption(c)
- testBrokenUpWrite(t, c, maxPayload)
-}
-
-func TestSegmentNotDroppedWhenTimestampMissing(t *testing.T) {
- const maxPayload = 100
- c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxPayload))
- defer c.Cleanup()
-
- rep := createConnectedWithTimestampOption(c)
-
- // Register for read.
- we, ch := waiter.NewChannelEntry(nil)
- c.WQ.EventRegister(&we, waiter.EventIn)
- defer c.WQ.EventUnregister(&we)
-
- droppedPacketsStat := c.Stack().Stats().DroppedPackets
- droppedPackets := droppedPacketsStat.Value()
- data := []byte{1, 2, 3}
- // Send a packet with no TCP options/timestamp.
- rep.SendPacket(data, nil)
-
- select {
- case <-ch:
- case <-time.After(1 * time.Second):
- t.Fatalf("Timed out waiting for data to arrive")
- }
-
- // Assert that DroppedPackets was not incremented.
- if got, want := droppedPacketsStat.Value(), droppedPackets; got != want {
- t.Fatalf("incorrect number of dropped packets, got: %v, want: %v", got, want)
- }
-
- // Issue a read and we should data.
- got, _, err := c.EP.Read(nil)
- if err != nil {
- t.Fatalf("Unexpected error from Read: %v", err)
- }
- if want := data; bytes.Compare(got, want) != 0 {
- t.Fatalf("Data is different: got: %v, want: %v", got, want)
- }
-}
diff --git a/pkg/tcpip/transport/tcp/testing/context/BUILD b/pkg/tcpip/transport/tcp/testing/context/BUILD
deleted file mode 100644
index 19b0d31c5..000000000
--- a/pkg/tcpip/transport/tcp/testing/context/BUILD
+++ /dev/null
@@ -1,27 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "context",
- testonly = 1,
- srcs = ["context.go"],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/transport/tcp/testing/context",
- visibility = [
- "//:sandbox",
- ],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/checker",
- "//pkg/tcpip/header",
- "//pkg/tcpip/link/channel",
- "//pkg/tcpip/link/sniffer",
- "//pkg/tcpip/network/ipv4",
- "//pkg/tcpip/network/ipv6",
- "//pkg/tcpip/seqnum",
- "//pkg/tcpip/stack",
- "//pkg/tcpip/transport/tcp",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/tcpip/transport/tcp/testing/context/context.go b/pkg/tcpip/transport/tcp/testing/context/context.go
deleted file mode 100644
index 16783e716..000000000
--- a/pkg/tcpip/transport/tcp/testing/context/context.go
+++ /dev/null
@@ -1,1046 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package context provides a test context for use in tcp tests. It also
-// provides helper methods to assert/check certain behaviours.
-package context
-
-import (
- "bytes"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/checker"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/link/channel"
- "gvisor.dev/gvisor/pkg/tcpip/link/sniffer"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
- "gvisor.dev/gvisor/pkg/tcpip/seqnum"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
- "gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-const (
- // StackAddr is the IPv4 address assigned to the stack.
- StackAddr = "\x0a\x00\x00\x01"
-
- // StackPort is used as the listening port in tests for passive
- // connects.
- StackPort = 1234
-
- // TestAddr is the source address for packets sent to the stack via the
- // link layer endpoint.
- TestAddr = "\x0a\x00\x00\x02"
-
- // TestPort is the TCP port used for packets sent to the stack
- // via the link layer endpoint.
- TestPort = 4096
-
- // StackV6Addr is the IPv6 address assigned to the stack.
- StackV6Addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
-
- // TestV6Addr is the source address for packets sent to the stack via
- // the link layer endpoint.
- TestV6Addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"
-
- // StackV4MappedAddr is StackAddr as a mapped v6 address.
- StackV4MappedAddr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff" + StackAddr
-
- // TestV4MappedAddr is TestAddr as a mapped v6 address.
- TestV4MappedAddr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff" + TestAddr
-
- // V4MappedWildcardAddr is the mapped v6 representation of 0.0.0.0.
- V4MappedWildcardAddr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00"
-
- // testInitialSequenceNumber is the initial sequence number sent in packets that
- // are sent in response to a SYN or in the initial SYN sent to the stack.
- testInitialSequenceNumber = 789
-)
-
-// Headers is used to represent the TCP header fields when building a
-// new packet.
-type Headers struct {
- // SrcPort holds the src port value to be used in the packet.
- SrcPort uint16
-
- // DstPort holds the destination port value to be used in the packet.
- DstPort uint16
-
- // SeqNum is the value of the sequence number field in the TCP header.
- SeqNum seqnum.Value
-
- // AckNum represents the acknowledgement number field in the TCP header.
- AckNum seqnum.Value
-
- // Flags are the TCP flags in the TCP header.
- Flags int
-
- // RcvWnd is the window to be advertised in the ReceiveWindow field of
- // the TCP header.
- RcvWnd seqnum.Size
-
- // TCPOpts holds the options to be sent in the option field of the TCP
- // header.
- TCPOpts []byte
-}
-
-// Context provides an initialized Network stack and a link layer endpoint
-// for use in TCP tests.
-type Context struct {
- t *testing.T
- linkEP *channel.Endpoint
- s *stack.Stack
-
- // IRS holds the initial sequence number in the SYN sent by endpoint in
- // case of an active connect or the sequence number sent by the endpoint
- // in the SYN-ACK sent in response to a SYN when listening in passive
- // mode.
- IRS seqnum.Value
-
- // Port holds the port bound by EP below in case of an active connect or
- // the listening port number in case of a passive connect.
- Port uint16
-
- // EP is the test endpoint in the stack owned by this context. This endpoint
- // is used in various tests to either initiate an active connect or is used
- // as a passive listening endpoint to accept inbound connections.
- EP tcpip.Endpoint
-
- // Wq is the wait queue associated with EP and is used to block for events
- // on EP.
- WQ waiter.Queue
-
- // TimeStampEnabled is true if ep is connected with the timestamp option
- // enabled.
- TimeStampEnabled bool
-
- // WindowScale is the expected window scale in SYN packets sent by
- // the stack.
- WindowScale uint8
-}
-
-// New allocates and initializes a test context containing a new
-// stack and a link-layer endpoint.
-func New(t *testing.T, mtu uint32) *Context {
- s := stack.New([]string{ipv4.ProtocolName, ipv6.ProtocolName}, []string{tcp.ProtocolName}, stack.Options{})
-
- // Allow minimum send/receive buffer sizes to be 1 during tests.
- if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, tcp.SendBufferSizeOption{1, tcp.DefaultSendBufferSize, 10 * tcp.DefaultSendBufferSize}); err != nil {
- t.Fatalf("SetTransportProtocolOption failed: %v", err)
- }
-
- if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, tcp.ReceiveBufferSizeOption{1, tcp.DefaultReceiveBufferSize, 10 * tcp.DefaultReceiveBufferSize}); err != nil {
- t.Fatalf("SetTransportProtocolOption failed: %v", err)
- }
-
- // Some of the congestion control tests send up to 640 packets, we so
- // set the channel size to 1000.
- ep := channel.New(1000, mtu, "")
- wep := stack.LinkEndpoint(ep)
- if testing.Verbose() {
- wep = sniffer.New(ep)
- }
- if err := s.CreateNIC(1, wep); err != nil {
- t.Fatalf("CreateNIC failed: %v", err)
- }
-
- if err := s.AddAddress(1, ipv4.ProtocolNumber, StackAddr); err != nil {
- t.Fatalf("AddAddress failed: %v", err)
- }
-
- if err := s.AddAddress(1, ipv6.ProtocolNumber, StackV6Addr); err != nil {
- t.Fatalf("AddAddress failed: %v", err)
- }
-
- s.SetRouteTable([]tcpip.Route{
- {
- Destination: header.IPv4EmptySubnet,
- NIC: 1,
- },
- {
- Destination: header.IPv6EmptySubnet,
- NIC: 1,
- },
- })
-
- return &Context{
- t: t,
- s: s,
- linkEP: ep,
- WindowScale: uint8(tcp.FindWndScale(tcp.DefaultReceiveBufferSize)),
- }
-}
-
-// Cleanup closes the context endpoint if required.
-func (c *Context) Cleanup() {
- if c.EP != nil {
- c.EP.Close()
- }
-}
-
-// Stack returns a reference to the stack in the Context.
-func (c *Context) Stack() *stack.Stack {
- return c.s
-}
-
-// CheckNoPacketTimeout verifies that no packet is received during the time
-// specified by wait.
-func (c *Context) CheckNoPacketTimeout(errMsg string, wait time.Duration) {
- c.t.Helper()
-
- select {
- case <-c.linkEP.C:
- c.t.Fatal(errMsg)
-
- case <-time.After(wait):
- }
-}
-
-// CheckNoPacket verifies that no packet is received for 1 second.
-func (c *Context) CheckNoPacket(errMsg string) {
- c.CheckNoPacketTimeout(errMsg, 1*time.Second)
-}
-
-// GetPacket reads a packet from the link layer endpoint and verifies
-// that it is an IPv4 packet with the expected source and destination
-// addresses. It will fail with an error if no packet is received for
-// 2 seconds.
-func (c *Context) GetPacket() []byte {
- select {
- case p := <-c.linkEP.C:
- if p.Proto != ipv4.ProtocolNumber {
- c.t.Fatalf("Bad network protocol: got %v, wanted %v", p.Proto, ipv4.ProtocolNumber)
- }
- b := make([]byte, len(p.Header)+len(p.Payload))
- copy(b, p.Header)
- copy(b[len(p.Header):], p.Payload)
-
- if p.GSO != nil && p.GSO.L3HdrLen != header.IPv4MinimumSize {
- c.t.Errorf("L3HdrLen %v (expected %v)", p.GSO.L3HdrLen, header.IPv4MinimumSize)
- }
-
- checker.IPv4(c.t, b, checker.SrcAddr(StackAddr), checker.DstAddr(TestAddr))
- return b
-
- case <-time.After(2 * time.Second):
- c.t.Fatalf("Packet wasn't written out")
- }
-
- return nil
-}
-
-// GetPacketNonBlocking reads a packet from the link layer endpoint
-// and verifies that it is an IPv4 packet with the expected source
-// and destination address. If no packet is available it will return
-// nil immediately.
-func (c *Context) GetPacketNonBlocking() []byte {
- select {
- case p := <-c.linkEP.C:
- if p.Proto != ipv4.ProtocolNumber {
- c.t.Fatalf("Bad network protocol: got %v, wanted %v", p.Proto, ipv4.ProtocolNumber)
- }
- b := make([]byte, len(p.Header)+len(p.Payload))
- copy(b, p.Header)
- copy(b[len(p.Header):], p.Payload)
-
- checker.IPv4(c.t, b, checker.SrcAddr(StackAddr), checker.DstAddr(TestAddr))
- return b
- default:
- return nil
- }
-}
-
-// SendICMPPacket builds and sends an ICMPv4 packet via the link layer endpoint.
-func (c *Context) SendICMPPacket(typ header.ICMPv4Type, code uint8, p1, p2 []byte, maxTotalSize int) {
- // Allocate a buffer data and headers.
- buf := buffer.NewView(header.IPv4MinimumSize + header.ICMPv4PayloadOffset + len(p2))
- if len(buf) > maxTotalSize {
- buf = buf[:maxTotalSize]
- }
-
- ip := header.IPv4(buf)
- ip.Encode(&header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TotalLength: uint16(len(buf)),
- TTL: 65,
- Protocol: uint8(header.ICMPv4ProtocolNumber),
- SrcAddr: TestAddr,
- DstAddr: StackAddr,
- })
- ip.SetChecksum(^ip.CalculateChecksum())
-
- icmp := header.ICMPv4(buf[header.IPv4MinimumSize:])
- icmp.SetType(typ)
- icmp.SetCode(code)
- const icmpv4VariableHeaderOffset = 4
- copy(icmp[icmpv4VariableHeaderOffset:], p1)
- copy(icmp[header.ICMPv4PayloadOffset:], p2)
-
- // Inject packet.
- c.linkEP.Inject(ipv4.ProtocolNumber, buf.ToVectorisedView())
-}
-
-// BuildSegment builds a TCP segment based on the given Headers and payload.
-func (c *Context) BuildSegment(payload []byte, h *Headers) buffer.VectorisedView {
- // Allocate a buffer for data and headers.
- buf := buffer.NewView(header.TCPMinimumSize + header.IPv4MinimumSize + len(h.TCPOpts) + len(payload))
- copy(buf[len(buf)-len(payload):], payload)
- copy(buf[len(buf)-len(payload)-len(h.TCPOpts):], h.TCPOpts)
-
- // Initialize the IP header.
- ip := header.IPv4(buf)
- ip.Encode(&header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TotalLength: uint16(len(buf)),
- TTL: 65,
- Protocol: uint8(tcp.ProtocolNumber),
- SrcAddr: TestAddr,
- DstAddr: StackAddr,
- })
- ip.SetChecksum(^ip.CalculateChecksum())
-
- // Initialize the TCP header.
- t := header.TCP(buf[header.IPv4MinimumSize:])
- t.Encode(&header.TCPFields{
- SrcPort: h.SrcPort,
- DstPort: h.DstPort,
- SeqNum: uint32(h.SeqNum),
- AckNum: uint32(h.AckNum),
- DataOffset: uint8(header.TCPMinimumSize + len(h.TCPOpts)),
- Flags: uint8(h.Flags),
- WindowSize: uint16(h.RcvWnd),
- })
-
- // Calculate the TCP pseudo-header checksum.
- xsum := header.PseudoHeaderChecksum(tcp.ProtocolNumber, TestAddr, StackAddr, uint16(len(t)))
-
- // Calculate the TCP checksum and set it.
- xsum = header.Checksum(payload, xsum)
- t.SetChecksum(^t.CalculateChecksum(xsum))
-
- // Inject packet.
- return buf.ToVectorisedView()
-}
-
-// SendSegment sends a TCP segment that has already been built and written to a
-// buffer.VectorisedView.
-func (c *Context) SendSegment(s buffer.VectorisedView) {
- c.linkEP.Inject(ipv4.ProtocolNumber, s)
-}
-
-// SendPacket builds and sends a TCP segment(with the provided payload & TCP
-// headers) in an IPv4 packet via the link layer endpoint.
-func (c *Context) SendPacket(payload []byte, h *Headers) {
- c.linkEP.Inject(ipv4.ProtocolNumber, c.BuildSegment(payload, h))
-}
-
-// SendAck sends an ACK packet.
-func (c *Context) SendAck(seq seqnum.Value, bytesReceived int) {
- c.SendAckWithSACK(seq, bytesReceived, nil)
-}
-
-// SendAckWithSACK sends an ACK packet which includes the sackBlocks specified.
-func (c *Context) SendAckWithSACK(seq seqnum.Value, bytesReceived int, sackBlocks []header.SACKBlock) {
- options := make([]byte, 40)
- offset := 0
- if len(sackBlocks) > 0 {
- offset += header.EncodeNOP(options[offset:])
- offset += header.EncodeNOP(options[offset:])
- offset += header.EncodeSACKBlocks(sackBlocks, options[offset:])
- }
-
- c.SendPacket(nil, &Headers{
- SrcPort: TestPort,
- DstPort: c.Port,
- Flags: header.TCPFlagAck,
- SeqNum: seq,
- AckNum: c.IRS.Add(1 + seqnum.Size(bytesReceived)),
- RcvWnd: 30000,
- TCPOpts: options[:offset],
- })
-}
-
-// ReceiveAndCheckPacket reads a packet from the link layer endpoint and
-// verifies that the packet packet payload of packet matches the slice
-// of data indicated by offset & size.
-func (c *Context) ReceiveAndCheckPacket(data []byte, offset, size int) {
- c.ReceiveAndCheckPacketWithOptions(data, offset, size, 0)
-}
-
-// ReceiveAndCheckPacketWithOptions reads a packet from the link layer endpoint
-// and verifies that the packet packet payload of packet matches the slice of
-// data indicated by offset & size and skips optlen bytes in addition to the IP
-// TCP headers when comparing the data.
-func (c *Context) ReceiveAndCheckPacketWithOptions(data []byte, offset, size, optlen int) {
- b := c.GetPacket()
- checker.IPv4(c.t, b,
- checker.PayloadLen(size+header.TCPMinimumSize+optlen),
- checker.TCP(
- checker.DstPort(TestPort),
- checker.SeqNum(uint32(c.IRS.Add(seqnum.Size(1+offset)))),
- checker.AckNum(uint32(seqnum.Value(testInitialSequenceNumber).Add(1))),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
-
- pdata := data[offset:][:size]
- if p := b[header.IPv4MinimumSize+header.TCPMinimumSize+optlen:]; bytes.Compare(pdata, p) != 0 {
- c.t.Fatalf("Data is different: expected %v, got %v", pdata, p)
- }
-}
-
-// ReceiveNonBlockingAndCheckPacket reads a packet from the link layer endpoint
-// and verifies that the packet packet payload of packet matches the slice of
-// data indicated by offset & size. It returns true if a packet was received and
-// processed.
-func (c *Context) ReceiveNonBlockingAndCheckPacket(data []byte, offset, size int) bool {
- b := c.GetPacketNonBlocking()
- if b == nil {
- return false
- }
- checker.IPv4(c.t, b,
- checker.PayloadLen(size+header.TCPMinimumSize),
- checker.TCP(
- checker.DstPort(TestPort),
- checker.SeqNum(uint32(c.IRS.Add(seqnum.Size(1+offset)))),
- checker.AckNum(uint32(seqnum.Value(testInitialSequenceNumber).Add(1))),
- checker.TCPFlagsMatch(header.TCPFlagAck, ^uint8(header.TCPFlagPsh)),
- ),
- )
-
- pdata := data[offset:][:size]
- if p := b[header.IPv4MinimumSize+header.TCPMinimumSize:]; bytes.Compare(pdata, p) != 0 {
- c.t.Fatalf("Data is different: expected %v, got %v", pdata, p)
- }
- return true
-}
-
-// CreateV6Endpoint creates and initializes c.ep as a IPv6 Endpoint. If v6Only
-// is true then it sets the IP_V6ONLY option on the socket to make it a IPv6
-// only endpoint instead of a default dual stack socket.
-func (c *Context) CreateV6Endpoint(v6only bool) {
- var err *tcpip.Error
- c.EP, err = c.s.NewEndpoint(tcp.ProtocolNumber, ipv6.ProtocolNumber, &c.WQ)
- if err != nil {
- c.t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- var v tcpip.V6OnlyOption
- if v6only {
- v = 1
- }
- if err := c.EP.SetSockOpt(v); err != nil {
- c.t.Fatalf("SetSockOpt failed failed: %v", err)
- }
-}
-
-// GetV6Packet reads a single packet from the link layer endpoint of the context
-// and asserts that it is an IPv6 Packet with the expected src/dest addresses.
-func (c *Context) GetV6Packet() []byte {
- select {
- case p := <-c.linkEP.C:
- if p.Proto != ipv6.ProtocolNumber {
- c.t.Fatalf("Bad network protocol: got %v, wanted %v", p.Proto, ipv6.ProtocolNumber)
- }
- b := make([]byte, len(p.Header)+len(p.Payload))
- copy(b, p.Header)
- copy(b[len(p.Header):], p.Payload)
-
- checker.IPv6(c.t, b, checker.SrcAddr(StackV6Addr), checker.DstAddr(TestV6Addr))
- return b
-
- case <-time.After(2 * time.Second):
- c.t.Fatalf("Packet wasn't written out")
- }
-
- return nil
-}
-
-// SendV6Packet builds and sends an IPv6 Packet via the link layer endpoint of
-// the context.
-func (c *Context) SendV6Packet(payload []byte, h *Headers) {
- // Allocate a buffer for data and headers.
- buf := buffer.NewView(header.TCPMinimumSize + header.IPv6MinimumSize + len(payload))
- copy(buf[len(buf)-len(payload):], payload)
-
- // Initialize the IP header.
- ip := header.IPv6(buf)
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(header.TCPMinimumSize + len(payload)),
- NextHeader: uint8(tcp.ProtocolNumber),
- HopLimit: 65,
- SrcAddr: TestV6Addr,
- DstAddr: StackV6Addr,
- })
-
- // Initialize the TCP header.
- t := header.TCP(buf[header.IPv6MinimumSize:])
- t.Encode(&header.TCPFields{
- SrcPort: h.SrcPort,
- DstPort: h.DstPort,
- SeqNum: uint32(h.SeqNum),
- AckNum: uint32(h.AckNum),
- DataOffset: header.TCPMinimumSize,
- Flags: uint8(h.Flags),
- WindowSize: uint16(h.RcvWnd),
- })
-
- // Calculate the TCP pseudo-header checksum.
- xsum := header.PseudoHeaderChecksum(tcp.ProtocolNumber, TestV6Addr, StackV6Addr, uint16(len(t)))
-
- // Calculate the TCP checksum and set it.
- xsum = header.Checksum(payload, xsum)
- t.SetChecksum(^t.CalculateChecksum(xsum))
-
- // Inject packet.
- c.linkEP.Inject(ipv6.ProtocolNumber, buf.ToVectorisedView())
-}
-
-// CreateConnected creates a connected TCP endpoint.
-func (c *Context) CreateConnected(iss seqnum.Value, rcvWnd seqnum.Size, epRcvBuf *tcpip.ReceiveBufferSizeOption) {
- c.CreateConnectedWithRawOptions(iss, rcvWnd, epRcvBuf, nil)
-}
-
-// Connect performs the 3-way handshake for c.EP with the provided Initial
-// Sequence Number (iss) and receive window(rcvWnd) and any options if
-// specified.
-//
-// It also sets the receive buffer for the endpoint to the specified
-// value in epRcvBuf.
-//
-// PreCondition: c.EP must already be created.
-func (c *Context) Connect(iss seqnum.Value, rcvWnd seqnum.Size, options []byte) {
- // Start connection attempt.
- waitEntry, notifyCh := waiter.NewChannelEntry(nil)
- c.WQ.EventRegister(&waitEntry, waiter.EventOut)
- defer c.WQ.EventUnregister(&waitEntry)
-
- if err := c.EP.Connect(tcpip.FullAddress{Addr: TestAddr, Port: TestPort}); err != tcpip.ErrConnectStarted {
- c.t.Fatalf("Unexpected return value from Connect: %v", err)
- }
-
- // Receive SYN packet.
- b := c.GetPacket()
- checker.IPv4(c.t, b,
- checker.TCP(
- checker.DstPort(TestPort),
- checker.TCPFlags(header.TCPFlagSyn),
- ),
- )
- if got, want := tcp.EndpointState(c.EP.State()), tcp.StateSynSent; got != want {
- c.t.Fatalf("Unexpected endpoint state: want %v, got %v", want, got)
- }
-
- tcpHdr := header.TCP(header.IPv4(b).Payload())
- c.IRS = seqnum.Value(tcpHdr.SequenceNumber())
-
- c.SendPacket(nil, &Headers{
- SrcPort: tcpHdr.DestinationPort(),
- DstPort: tcpHdr.SourcePort(),
- Flags: header.TCPFlagSyn | header.TCPFlagAck,
- SeqNum: iss,
- AckNum: c.IRS.Add(1),
- RcvWnd: rcvWnd,
- TCPOpts: options,
- })
-
- // Receive ACK packet.
- checker.IPv4(c.t, c.GetPacket(),
- checker.TCP(
- checker.DstPort(TestPort),
- checker.TCPFlags(header.TCPFlagAck),
- checker.SeqNum(uint32(c.IRS)+1),
- checker.AckNum(uint32(iss)+1),
- ),
- )
-
- // Wait for connection to be established.
- select {
- case <-notifyCh:
- if err := c.EP.GetSockOpt(tcpip.ErrorOption{}); err != nil {
- c.t.Fatalf("Unexpected error when connecting: %v", err)
- }
- case <-time.After(1 * time.Second):
- c.t.Fatalf("Timed out waiting for connection")
- }
- if got, want := tcp.EndpointState(c.EP.State()), tcp.StateEstablished; got != want {
- c.t.Fatalf("Unexpected endpoint state: want %v, got %v", want, got)
- }
-
- c.Port = tcpHdr.SourcePort()
-}
-
-// CreateConnectedWithRawOptions creates a connected TCP endpoint and sends
-// the specified option bytes as the Option field in the initial SYN packet.
-//
-// It also sets the receive buffer for the endpoint to the specified
-// value in epRcvBuf.
-func (c *Context) CreateConnectedWithRawOptions(iss seqnum.Value, rcvWnd seqnum.Size, epRcvBuf *tcpip.ReceiveBufferSizeOption, options []byte) {
- // Create TCP endpoint.
- var err *tcpip.Error
- c.EP, err = c.s.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
- if err != nil {
- c.t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- if epRcvBuf != nil {
- if err := c.EP.SetSockOpt(*epRcvBuf); err != nil {
- c.t.Fatalf("SetSockOpt failed failed: %v", err)
- }
- }
- c.Connect(iss, rcvWnd, options)
-}
-
-// RawEndpoint is just a small wrapper around a TCP endpoint's state to make
-// sending data and ACK packets easy while being able to manipulate the sequence
-// numbers and timestamp values as needed.
-type RawEndpoint struct {
- C *Context
- SrcPort uint16
- DstPort uint16
- Flags int
- NextSeqNum seqnum.Value
- AckNum seqnum.Value
- WndSize seqnum.Size
- RecentTS uint32 // Stores the latest timestamp to echo back.
- TSVal uint32 // TSVal stores the last timestamp sent by this endpoint.
-
- // SackPermitted is true if SACKPermitted option was negotiated for this endpoint.
- SACKPermitted bool
-}
-
-// SendPacketWithTS embeds the provided tsVal in the Timestamp option
-// for the packet to be sent out.
-func (r *RawEndpoint) SendPacketWithTS(payload []byte, tsVal uint32) {
- r.TSVal = tsVal
- tsOpt := [12]byte{header.TCPOptionNOP, header.TCPOptionNOP}
- header.EncodeTSOption(r.TSVal, r.RecentTS, tsOpt[2:])
- r.SendPacket(payload, tsOpt[:])
-}
-
-// SendPacket is a small wrapper function to build and send packets.
-func (r *RawEndpoint) SendPacket(payload []byte, opts []byte) {
- packetHeaders := &Headers{
- SrcPort: r.SrcPort,
- DstPort: r.DstPort,
- Flags: r.Flags,
- SeqNum: r.NextSeqNum,
- AckNum: r.AckNum,
- RcvWnd: r.WndSize,
- TCPOpts: opts,
- }
- r.C.SendPacket(payload, packetHeaders)
- r.NextSeqNum = r.NextSeqNum.Add(seqnum.Size(len(payload)))
-}
-
-// VerifyACKWithTS verifies that the tsEcr field in the ack matches the provided
-// tsVal.
-func (r *RawEndpoint) VerifyACKWithTS(tsVal uint32) {
- // Read ACK and verify that tsEcr of ACK packet is [1,2,3,4]
- ackPacket := r.C.GetPacket()
- checker.IPv4(r.C.t, ackPacket,
- checker.TCP(
- checker.DstPort(r.SrcPort),
- checker.TCPFlags(header.TCPFlagAck),
- checker.SeqNum(uint32(r.AckNum)),
- checker.AckNum(uint32(r.NextSeqNum)),
- checker.TCPTimestampChecker(true, 0, tsVal),
- ),
- )
- // Store the parsed TSVal from the ack as recentTS.
- tcpSeg := header.TCP(header.IPv4(ackPacket).Payload())
- opts := tcpSeg.ParsedOptions()
- r.RecentTS = opts.TSVal
-}
-
-// VerifyACKRcvWnd verifies that the window advertised by the incoming ACK
-// matches the provided rcvWnd.
-func (r *RawEndpoint) VerifyACKRcvWnd(rcvWnd uint16) {
- ackPacket := r.C.GetPacket()
- checker.IPv4(r.C.t, ackPacket,
- checker.TCP(
- checker.DstPort(r.SrcPort),
- checker.TCPFlags(header.TCPFlagAck),
- checker.SeqNum(uint32(r.AckNum)),
- checker.AckNum(uint32(r.NextSeqNum)),
- checker.Window(rcvWnd),
- ),
- )
-}
-
-// VerifyACKNoSACK verifies that the ACK does not contain a SACK block.
-func (r *RawEndpoint) VerifyACKNoSACK() {
- r.VerifyACKHasSACK(nil)
-}
-
-// VerifyACKHasSACK verifies that the ACK contains the specified SACKBlocks.
-func (r *RawEndpoint) VerifyACKHasSACK(sackBlocks []header.SACKBlock) {
- // Read ACK and verify that the TCP options in the segment do
- // not contain a SACK block.
- ackPacket := r.C.GetPacket()
- checker.IPv4(r.C.t, ackPacket,
- checker.TCP(
- checker.DstPort(r.SrcPort),
- checker.TCPFlags(header.TCPFlagAck),
- checker.SeqNum(uint32(r.AckNum)),
- checker.AckNum(uint32(r.NextSeqNum)),
- checker.TCPSACKBlockChecker(sackBlocks),
- ),
- )
-}
-
-// CreateConnectedWithOptions creates and connects c.ep with the specified TCP
-// options enabled and returns a RawEndpoint which represents the other end of
-// the connection.
-//
-// It also verifies where required(eg.Timestamp) that the ACK to the SYN-ACK
-// does not carry an option that was not requested.
-func (c *Context) CreateConnectedWithOptions(wantOptions header.TCPSynOptions) *RawEndpoint {
- var err *tcpip.Error
- c.EP, err = c.s.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
- if err != nil {
- c.t.Fatalf("c.s.NewEndpoint(tcp, ipv4...) = %v", err)
- }
- if got, want := tcp.EndpointState(c.EP.State()), tcp.StateInitial; got != want {
- c.t.Fatalf("Unexpected endpoint state: want %v, got %v", want, got)
- }
-
- // Start connection attempt.
- waitEntry, notifyCh := waiter.NewChannelEntry(nil)
- c.WQ.EventRegister(&waitEntry, waiter.EventOut)
- defer c.WQ.EventUnregister(&waitEntry)
-
- testFullAddr := tcpip.FullAddress{Addr: TestAddr, Port: TestPort}
- err = c.EP.Connect(testFullAddr)
- if err != tcpip.ErrConnectStarted {
- c.t.Fatalf("c.ep.Connect(%v) = %v", testFullAddr, err)
- }
- // Receive SYN packet.
- b := c.GetPacket()
- // Validate that the syn has the timestamp option and a valid
- // TS value.
- mss := uint16(c.linkEP.MTU() - header.IPv4MinimumSize - header.TCPMinimumSize)
-
- checker.IPv4(c.t, b,
- checker.TCP(
- checker.DstPort(TestPort),
- checker.TCPFlags(header.TCPFlagSyn),
- checker.TCPSynOptions(header.TCPSynOptions{
- MSS: mss,
- TS: true,
- WS: int(c.WindowScale),
- SACKPermitted: c.SACKEnabled(),
- }),
- ),
- )
- if got, want := tcp.EndpointState(c.EP.State()), tcp.StateSynSent; got != want {
- c.t.Fatalf("Unexpected endpoint state: want %v, got %v", want, got)
- }
-
- tcpSeg := header.TCP(header.IPv4(b).Payload())
- synOptions := header.ParseSynOptions(tcpSeg.Options(), false)
-
- // Build options w/ tsVal to be sent in the SYN-ACK.
- synAckOptions := make([]byte, header.TCPOptionsMaximumSize)
- offset := 0
- if wantOptions.WS != -1 {
- offset += header.EncodeWSOption(wantOptions.WS, synAckOptions[offset:])
- }
- if wantOptions.TS {
- offset += header.EncodeTSOption(wantOptions.TSVal, synOptions.TSVal, synAckOptions[offset:])
- }
- if wantOptions.SACKPermitted {
- offset += header.EncodeSACKPermittedOption(synAckOptions[offset:])
- }
-
- offset += header.AddTCPOptionPadding(synAckOptions, offset)
-
- // Build SYN-ACK.
- c.IRS = seqnum.Value(tcpSeg.SequenceNumber())
- iss := seqnum.Value(testInitialSequenceNumber)
- c.SendPacket(nil, &Headers{
- SrcPort: tcpSeg.DestinationPort(),
- DstPort: tcpSeg.SourcePort(),
- Flags: header.TCPFlagSyn | header.TCPFlagAck,
- SeqNum: iss,
- AckNum: c.IRS.Add(1),
- RcvWnd: 30000,
- TCPOpts: synAckOptions[:offset],
- })
-
- // Read ACK.
- ackPacket := c.GetPacket()
-
- // Verify TCP header fields.
- tcpCheckers := []checker.TransportChecker{
- checker.DstPort(TestPort),
- checker.TCPFlags(header.TCPFlagAck),
- checker.SeqNum(uint32(c.IRS) + 1),
- checker.AckNum(uint32(iss) + 1),
- }
-
- // Verify that tsEcr of ACK packet is wantOptions.TSVal if the
- // timestamp option was enabled, if not then we verify that
- // there is no timestamp in the ACK packet.
- if wantOptions.TS {
- tcpCheckers = append(tcpCheckers, checker.TCPTimestampChecker(true, 0, wantOptions.TSVal))
- } else {
- tcpCheckers = append(tcpCheckers, checker.TCPTimestampChecker(false, 0, 0))
- }
-
- checker.IPv4(c.t, ackPacket, checker.TCP(tcpCheckers...))
-
- ackSeg := header.TCP(header.IPv4(ackPacket).Payload())
- ackOptions := ackSeg.ParsedOptions()
-
- // Wait for connection to be established.
- select {
- case <-notifyCh:
- err = c.EP.GetSockOpt(tcpip.ErrorOption{})
- if err != nil {
- c.t.Fatalf("Unexpected error when connecting: %v", err)
- }
- case <-time.After(1 * time.Second):
- c.t.Fatalf("Timed out waiting for connection")
- }
- if got, want := tcp.EndpointState(c.EP.State()), tcp.StateEstablished; got != want {
- c.t.Fatalf("Unexpected endpoint state: want %v, got %v", want, got)
- }
-
- // Store the source port in use by the endpoint.
- c.Port = tcpSeg.SourcePort()
-
- // Mark in context that timestamp option is enabled for this endpoint.
- c.TimeStampEnabled = true
-
- return &RawEndpoint{
- C: c,
- SrcPort: tcpSeg.DestinationPort(),
- DstPort: tcpSeg.SourcePort(),
- Flags: header.TCPFlagAck | header.TCPFlagPsh,
- NextSeqNum: iss + 1,
- AckNum: c.IRS.Add(1),
- WndSize: 30000,
- RecentTS: ackOptions.TSVal,
- TSVal: wantOptions.TSVal,
- SACKPermitted: wantOptions.SACKPermitted,
- }
-}
-
-// AcceptWithOptions initializes a listening endpoint and connects to it with the
-// provided options enabled. It also verifies that the SYN-ACK has the expected
-// values for the provided options.
-//
-// The function returns a RawEndpoint representing the other end of the accepted
-// endpoint.
-func (c *Context) AcceptWithOptions(wndScale int, synOptions header.TCPSynOptions) *RawEndpoint {
- // Create EP and start listening.
- wq := &waiter.Queue{}
- ep, err := c.s.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq)
- if err != nil {
- c.t.Fatalf("NewEndpoint failed: %v", err)
- }
- defer ep.Close()
-
- if err := ep.Bind(tcpip.FullAddress{Port: StackPort}); err != nil {
- c.t.Fatalf("Bind failed: %v", err)
- }
- if got, want := tcp.EndpointState(ep.State()), tcp.StateBound; got != want {
- c.t.Errorf("Unexpected endpoint state: want %v, got %v", want, got)
- }
-
- if err := ep.Listen(10); err != nil {
- c.t.Fatalf("Listen failed: %v", err)
- }
- if got, want := tcp.EndpointState(ep.State()), tcp.StateListen; got != want {
- c.t.Errorf("Unexpected endpoint state: want %v, got %v", want, got)
- }
-
- rep := c.PassiveConnectWithOptions(100, wndScale, synOptions)
-
- // Try to accept the connection.
- we, ch := waiter.NewChannelEntry(nil)
- wq.EventRegister(&we, waiter.EventIn)
- defer wq.EventUnregister(&we)
-
- c.EP, _, err = ep.Accept()
- if err == tcpip.ErrWouldBlock {
- // Wait for connection to be established.
- select {
- case <-ch:
- c.EP, _, err = ep.Accept()
- if err != nil {
- c.t.Fatalf("Accept failed: %v", err)
- }
-
- case <-time.After(1 * time.Second):
- c.t.Fatalf("Timed out waiting for accept")
- }
- }
- if got, want := tcp.EndpointState(c.EP.State()), tcp.StateEstablished; got != want {
- c.t.Errorf("Unexpected endpoint state: want %v, got %v", want, got)
- }
-
- return rep
-}
-
-// PassiveConnect just disables WindowScaling and delegates the call to
-// PassiveConnectWithOptions.
-func (c *Context) PassiveConnect(maxPayload, wndScale int, synOptions header.TCPSynOptions) {
- synOptions.WS = -1
- c.PassiveConnectWithOptions(maxPayload, wndScale, synOptions)
-}
-
-// PassiveConnectWithOptions initiates a new connection (with the specified TCP
-// options enabled) to the port on which the Context.ep is listening for new
-// connections. It also validates that the SYN-ACK has the expected values for
-// the enabled options.
-//
-// NOTE: MSS is not a negotiated option and it can be asymmetric
-// in each direction. This function uses the maxPayload to set the MSS to be
-// sent to the peer on a connect and validates that the MSS in the SYN-ACK
-// response is equal to the MTU - (tcphdr len + iphdr len).
-//
-// wndScale is the expected window scale in the SYN-ACK and synOptions.WS is the
-// value of the window scaling option to be sent in the SYN. If synOptions.WS >
-// 0 then we send the WindowScale option.
-func (c *Context) PassiveConnectWithOptions(maxPayload, wndScale int, synOptions header.TCPSynOptions) *RawEndpoint {
- opts := make([]byte, header.TCPOptionsMaximumSize)
- offset := 0
- offset += header.EncodeMSSOption(uint32(maxPayload), opts)
-
- if synOptions.WS >= 0 {
- offset += header.EncodeWSOption(3, opts[offset:])
- }
- if synOptions.TS {
- offset += header.EncodeTSOption(synOptions.TSVal, synOptions.TSEcr, opts[offset:])
- }
-
- if synOptions.SACKPermitted {
- offset += header.EncodeSACKPermittedOption(opts[offset:])
- }
-
- paddingToAdd := 4 - offset%4
- // Now add any padding bytes that might be required to quad align the
- // options.
- for i := offset; i < offset+paddingToAdd; i++ {
- opts[i] = header.TCPOptionNOP
- }
- offset += paddingToAdd
-
- // Send a SYN request.
- iss := seqnum.Value(testInitialSequenceNumber)
- c.SendPacket(nil, &Headers{
- SrcPort: TestPort,
- DstPort: StackPort,
- Flags: header.TCPFlagSyn,
- SeqNum: iss,
- RcvWnd: 30000,
- TCPOpts: opts[:offset],
- })
-
- // Receive the SYN-ACK reply. Make sure MSS and other expected options
- // are present.
- b := c.GetPacket()
- tcp := header.TCP(header.IPv4(b).Payload())
- c.IRS = seqnum.Value(tcp.SequenceNumber())
-
- tcpCheckers := []checker.TransportChecker{
- checker.SrcPort(StackPort),
- checker.DstPort(TestPort),
- checker.TCPFlags(header.TCPFlagAck | header.TCPFlagSyn),
- checker.AckNum(uint32(iss) + 1),
- checker.TCPSynOptions(header.TCPSynOptions{MSS: synOptions.MSS, WS: wndScale, SACKPermitted: synOptions.SACKPermitted && c.SACKEnabled()}),
- }
-
- // If TS option was enabled in the original SYN then add a checker to
- // validate the Timestamp option in the SYN-ACK.
- if synOptions.TS {
- tcpCheckers = append(tcpCheckers, checker.TCPTimestampChecker(synOptions.TS, 0, synOptions.TSVal))
- } else {
- tcpCheckers = append(tcpCheckers, checker.TCPTimestampChecker(false, 0, 0))
- }
-
- checker.IPv4(c.t, b, checker.TCP(tcpCheckers...))
- rcvWnd := seqnum.Size(30000)
- ackHeaders := &Headers{
- SrcPort: TestPort,
- DstPort: StackPort,
- Flags: header.TCPFlagAck,
- SeqNum: iss + 1,
- AckNum: c.IRS + 1,
- RcvWnd: rcvWnd,
- }
-
- // If WS was expected to be in effect then scale the advertised window
- // correspondingly.
- if synOptions.WS > 0 {
- ackHeaders.RcvWnd = rcvWnd >> byte(synOptions.WS)
- }
-
- parsedOpts := tcp.ParsedOptions()
- if synOptions.TS {
- // Echo the tsVal back to the peer in the tsEcr field of the
- // timestamp option.
- // Increment TSVal by 1 from the value sent in the SYN and echo
- // the TSVal in the SYN-ACK in the TSEcr field.
- opts := [12]byte{header.TCPOptionNOP, header.TCPOptionNOP}
- header.EncodeTSOption(synOptions.TSVal+1, parsedOpts.TSVal, opts[2:])
- ackHeaders.TCPOpts = opts[:]
- }
-
- // Send ACK.
- c.SendPacket(nil, ackHeaders)
-
- c.Port = StackPort
-
- return &RawEndpoint{
- C: c,
- SrcPort: TestPort,
- DstPort: StackPort,
- Flags: header.TCPFlagPsh | header.TCPFlagAck,
- NextSeqNum: iss + 1,
- AckNum: c.IRS + 1,
- WndSize: rcvWnd,
- SACKPermitted: synOptions.SACKPermitted && c.SACKEnabled(),
- RecentTS: parsedOpts.TSVal,
- TSVal: synOptions.TSVal + 1,
- }
-}
-
-// SACKEnabled returns true if the TCP Protocol option SACKEnabled is set to true
-// for the Stack in the context.
-func (c *Context) SACKEnabled() bool {
- var v tcp.SACKEnabled
- if err := c.Stack().TransportProtocolOption(tcp.ProtocolNumber, &v); err != nil {
- // Stack doesn't support SACK. So just return.
- return false
- }
- return bool(v)
-}
-
-// SetGSOEnabled enables or disables generic segmentation offload.
-func (c *Context) SetGSOEnabled(enable bool) {
- c.linkEP.GSO = enable
-}
-
-// MSSWithoutOptions returns the value for the MSS used by the stack when no
-// options are in use.
-func (c *Context) MSSWithoutOptions() uint16 {
- return uint16(c.linkEP.MTU() - header.IPv4MinimumSize - header.TCPMinimumSize)
-}
diff --git a/pkg/tcpip/transport/tcpconntrack/BUILD b/pkg/tcpip/transport/tcpconntrack/BUILD
deleted file mode 100644
index 43fcc27f0..000000000
--- a/pkg/tcpip/transport/tcpconntrack/BUILD
+++ /dev/null
@@ -1,25 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "tcpconntrack",
- srcs = ["tcp_conntrack.go"],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/transport/tcpconntrack",
- visibility = ["//visibility:public"],
- deps = [
- "//pkg/tcpip/header",
- "//pkg/tcpip/seqnum",
- ],
-)
-
-go_test(
- name = "tcpconntrack_test",
- size = "small",
- srcs = ["tcp_conntrack_test.go"],
- deps = [
- ":tcpconntrack",
- "//pkg/tcpip/header",
- ],
-)
diff --git a/pkg/tcpip/transport/tcpconntrack/tcp_conntrack.go b/pkg/tcpip/transport/tcpconntrack/tcp_conntrack.go
deleted file mode 100644
index 93712cd45..000000000
--- a/pkg/tcpip/transport/tcpconntrack/tcp_conntrack.go
+++ /dev/null
@@ -1,349 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package tcpconntrack implements a TCP connection tracking object. It allows
-// users with access to a segment stream to figure out when a connection is
-// established, reset, and closed (and in the last case, who closed first).
-package tcpconntrack
-
-import (
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/seqnum"
-)
-
-// Result is returned when the state of a TCB is updated in response to an
-// inbound or outbound segment.
-type Result int
-
-const (
- // ResultDrop indicates that the segment should be dropped.
- ResultDrop Result = iota
-
- // ResultConnecting indicates that the connection remains in a
- // connecting state.
- ResultConnecting
-
- // ResultAlive indicates that the connection remains alive (connected).
- ResultAlive
-
- // ResultReset indicates that the connection was reset.
- ResultReset
-
- // ResultClosedByPeer indicates that the connection was gracefully
- // closed, and the inbound stream was closed first.
- ResultClosedByPeer
-
- // ResultClosedBySelf indicates that the connection was gracefully
- // closed, and the outbound stream was closed first.
- ResultClosedBySelf
-)
-
-// TCB is a TCP Control Block. It holds state necessary to keep track of a TCP
-// connection and inform the caller when the connection has been closed.
-type TCB struct {
- inbound stream
- outbound stream
-
- // State handlers.
- handlerInbound func(*TCB, header.TCP) Result
- handlerOutbound func(*TCB, header.TCP) Result
-
- // firstFin holds a pointer to the first stream to send a FIN.
- firstFin *stream
-
- // state is the current state of the stream.
- state Result
-}
-
-// Init initializes the state of the TCB according to the initial SYN.
-func (t *TCB) Init(initialSyn header.TCP) Result {
- t.handlerInbound = synSentStateInbound
- t.handlerOutbound = synSentStateOutbound
-
- iss := seqnum.Value(initialSyn.SequenceNumber())
- t.outbound.una = iss
- t.outbound.nxt = iss.Add(logicalLen(initialSyn))
- t.outbound.end = t.outbound.nxt
-
- // Even though "end" is a sequence number, we don't know the initial
- // receive sequence number yet, so we store the window size until we get
- // a SYN from the peer.
- t.inbound.una = 0
- t.inbound.nxt = 0
- t.inbound.end = seqnum.Value(initialSyn.WindowSize())
- t.state = ResultConnecting
- return t.state
-}
-
-// UpdateStateInbound updates the state of the TCB based on the supplied inbound
-// segment.
-func (t *TCB) UpdateStateInbound(tcp header.TCP) Result {
- st := t.handlerInbound(t, tcp)
- if st != ResultDrop {
- t.state = st
- }
- return st
-}
-
-// UpdateStateOutbound updates the state of the TCB based on the supplied
-// outbound segment.
-func (t *TCB) UpdateStateOutbound(tcp header.TCP) Result {
- st := t.handlerOutbound(t, tcp)
- if st != ResultDrop {
- t.state = st
- }
- return st
-}
-
-// IsAlive returns true as long as the connection is established(Alive)
-// or connecting state.
-func (t *TCB) IsAlive() bool {
- return !t.inbound.rstSeen && !t.outbound.rstSeen && (!t.inbound.closed() || !t.outbound.closed())
-}
-
-// OutboundSendSequenceNumber returns the snd.NXT for the outbound stream.
-func (t *TCB) OutboundSendSequenceNumber() seqnum.Value {
- return t.outbound.nxt
-}
-
-// InboundSendSequenceNumber returns the snd.NXT for the inbound stream.
-func (t *TCB) InboundSendSequenceNumber() seqnum.Value {
- return t.inbound.nxt
-}
-
-// adapResult modifies the supplied "Result" according to the state of the TCB;
-// if r is anything other than "Alive", or if one of the streams isn't closed
-// yet, it is returned unmodified. Otherwise it's converted to either
-// ClosedBySelf or ClosedByPeer depending on which stream was closed first.
-func (t *TCB) adaptResult(r Result) Result {
- // Check the unmodified case.
- if r != ResultAlive || !t.inbound.closed() || !t.outbound.closed() {
- return r
- }
-
- // Find out which was closed first.
- if t.firstFin == &t.outbound {
- return ResultClosedBySelf
- }
-
- return ResultClosedByPeer
-}
-
-// synSentStateInbound is the state handler for inbound segments when the
-// connection is in SYN-SENT state.
-func synSentStateInbound(t *TCB, tcp header.TCP) Result {
- flags := tcp.Flags()
- ackPresent := flags&header.TCPFlagAck != 0
- ack := seqnum.Value(tcp.AckNumber())
-
- // Ignore segment if ack is present but not acceptable.
- if ackPresent && !(ack-1).InRange(t.outbound.una, t.outbound.nxt) {
- return ResultConnecting
- }
-
- // If reset is specified, we will let the packet through no matter what
- // but we will also destroy the connection if the ACK is present (and
- // implicitly acceptable).
- if flags&header.TCPFlagRst != 0 {
- if ackPresent {
- t.inbound.rstSeen = true
- return ResultReset
- }
- return ResultConnecting
- }
-
- // Ignore segment if SYN is not set.
- if flags&header.TCPFlagSyn == 0 {
- return ResultConnecting
- }
-
- // Update state informed by this SYN.
- irs := seqnum.Value(tcp.SequenceNumber())
- t.inbound.una = irs
- t.inbound.nxt = irs.Add(logicalLen(tcp))
- t.inbound.end += irs
-
- t.outbound.end = t.outbound.una.Add(seqnum.Size(tcp.WindowSize()))
-
- // If the ACK was set (it is acceptable), update our unacknowledgement
- // tracking.
- if ackPresent {
- // Advance the "una" and "end" indices of the outbound stream.
- if t.outbound.una.LessThan(ack) {
- t.outbound.una = ack
- }
-
- if end := ack.Add(seqnum.Size(tcp.WindowSize())); t.outbound.end.LessThan(end) {
- t.outbound.end = end
- }
- }
-
- // Update handlers so that new calls will be handled by new state.
- t.handlerInbound = allOtherInbound
- t.handlerOutbound = allOtherOutbound
-
- return ResultAlive
-}
-
-// synSentStateOutbound is the state handler for outbound segments when the
-// connection is in SYN-SENT state.
-func synSentStateOutbound(t *TCB, tcp header.TCP) Result {
- // Drop outbound segments that aren't retransmits of the original one.
- if tcp.Flags() != header.TCPFlagSyn ||
- tcp.SequenceNumber() != uint32(t.outbound.una) {
- return ResultDrop
- }
-
- // Update the receive window. We only remember the largest value seen.
- if wnd := seqnum.Value(tcp.WindowSize()); wnd > t.inbound.end {
- t.inbound.end = wnd
- }
-
- return ResultConnecting
-}
-
-// update updates the state of inbound and outbound streams, given the supplied
-// inbound segment. For outbound segments, this same function can be called with
-// swapped inbound/outbound streams.
-func update(tcp header.TCP, inbound, outbound *stream, firstFin **stream) Result {
- // Ignore segments out of the window.
- s := seqnum.Value(tcp.SequenceNumber())
- if !inbound.acceptable(s, dataLen(tcp)) {
- return ResultAlive
- }
-
- flags := tcp.Flags()
- if flags&header.TCPFlagRst != 0 {
- inbound.rstSeen = true
- return ResultReset
- }
-
- // Ignore segments that don't have the ACK flag, and those with the SYN
- // flag.
- if flags&header.TCPFlagAck == 0 || flags&header.TCPFlagSyn != 0 {
- return ResultAlive
- }
-
- // Ignore segments that acknowledge not yet sent data.
- ack := seqnum.Value(tcp.AckNumber())
- if outbound.nxt.LessThan(ack) {
- return ResultAlive
- }
-
- // Advance the "una" and "end" indices of the outbound stream.
- if outbound.una.LessThan(ack) {
- outbound.una = ack
- }
-
- if end := ack.Add(seqnum.Size(tcp.WindowSize())); outbound.end.LessThan(end) {
- outbound.end = end
- }
-
- // Advance the "nxt" index of the inbound stream.
- end := s.Add(logicalLen(tcp))
- if inbound.nxt.LessThan(end) {
- inbound.nxt = end
- }
-
- // Note the index of the FIN segment. And stash away a pointer to the
- // first stream to see a FIN.
- if flags&header.TCPFlagFin != 0 && !inbound.finSeen {
- inbound.finSeen = true
- inbound.fin = end - 1
-
- if *firstFin == nil {
- *firstFin = inbound
- }
- }
-
- return ResultAlive
-}
-
-// allOtherInbound is the state handler for inbound segments in all states
-// except SYN-SENT.
-func allOtherInbound(t *TCB, tcp header.TCP) Result {
- return t.adaptResult(update(tcp, &t.inbound, &t.outbound, &t.firstFin))
-}
-
-// allOtherOutbound is the state handler for outbound segments in all states
-// except SYN-SENT.
-func allOtherOutbound(t *TCB, tcp header.TCP) Result {
- return t.adaptResult(update(tcp, &t.outbound, &t.inbound, &t.firstFin))
-}
-
-// streams holds the state of a TCP unidirectional stream.
-type stream struct {
- // The interval [una, end) is the allowed interval as defined by the
- // receiver, i.e., anything less than una has already been acknowledged
- // and anything greater than or equal to end is beyond the receiver
- // window. The interval [una, nxt) is the acknowledgable range, whose
- // right edge indicates the sequence number of the next byte to be sent
- // by the sender, i.e., anything greater than or equal to nxt hasn't
- // been sent yet.
- una seqnum.Value
- nxt seqnum.Value
- end seqnum.Value
-
- // finSeen indicates if a FIN has already been sent on this stream.
- finSeen bool
-
- // fin is the sequence number of the FIN. It is only valid after finSeen
- // is set to true.
- fin seqnum.Value
-
- // rstSeen indicates if a RST has already been sent on this stream.
- rstSeen bool
-}
-
-// acceptable determines if the segment with the given sequence number and data
-// length is acceptable, i.e., if it's within the [una, end) window or, in case
-// the window is zero, if it's a packet with no payload and sequence number
-// equal to una.
-func (s *stream) acceptable(segSeq seqnum.Value, segLen seqnum.Size) bool {
- wnd := s.una.Size(s.end)
- if wnd == 0 {
- return segLen == 0 && segSeq == s.una
- }
-
- // Make sure [segSeq, seqSeq+segLen) is non-empty.
- if segLen == 0 {
- segLen = 1
- }
-
- return seqnum.Overlap(s.una, wnd, segSeq, segLen)
-}
-
-// closed determines if the stream has already been closed. This happens when
-// a FIN has been set by the sender and acknowledged by the receiver.
-func (s *stream) closed() bool {
- return s.finSeen && s.fin.LessThan(s.una)
-}
-
-// dataLen returns the length of the TCP segment payload.
-func dataLen(tcp header.TCP) seqnum.Size {
- return seqnum.Size(len(tcp) - int(tcp.DataOffset()))
-}
-
-// logicalLen calculates the logical length of the TCP segment.
-func logicalLen(tcp header.TCP) seqnum.Size {
- l := dataLen(tcp)
- flags := tcp.Flags()
- if flags&header.TCPFlagSyn != 0 {
- l++
- }
- if flags&header.TCPFlagFin != 0 {
- l++
- }
- return l
-}
diff --git a/pkg/tcpip/transport/tcpconntrack/tcp_conntrack_test.go b/pkg/tcpip/transport/tcpconntrack/tcp_conntrack_test.go
deleted file mode 100644
index 5e271b7ca..000000000
--- a/pkg/tcpip/transport/tcpconntrack/tcp_conntrack_test.go
+++ /dev/null
@@ -1,511 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tcpconntrack_test
-
-import (
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/transport/tcpconntrack"
-)
-
-// connected creates a connection tracker TCB and sets it to a connected state
-// by performing a 3-way handshake.
-func connected(t *testing.T, iss, irs uint32, isw, irw uint16) *tcpconntrack.TCB {
- // Send SYN.
- tcp := make(header.TCP, header.TCPMinimumSize)
- tcp.Encode(&header.TCPFields{
- SeqNum: iss,
- AckNum: 0,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagSyn,
- WindowSize: irw,
- })
-
- tcb := tcpconntrack.TCB{}
- tcb.Init(tcp)
-
- // Receive SYN-ACK.
- tcp.Encode(&header.TCPFields{
- SeqNum: irs,
- AckNum: iss + 1,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagSyn | header.TCPFlagAck,
- WindowSize: isw,
- })
-
- if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultAlive {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
- }
-
- // Send ACK.
- tcp.Encode(&header.TCPFields{
- SeqNum: iss + 1,
- AckNum: irs + 1,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagAck,
- WindowSize: irw,
- })
-
- if r := tcb.UpdateStateOutbound(tcp); r != tcpconntrack.ResultAlive {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
- }
-
- return &tcb
-}
-
-func TestConnectionRefused(t *testing.T) {
- // Send SYN.
- tcp := make(header.TCP, header.TCPMinimumSize)
- tcp.Encode(&header.TCPFields{
- SeqNum: 1234,
- AckNum: 0,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagSyn,
- WindowSize: 30000,
- })
-
- tcb := tcpconntrack.TCB{}
- tcb.Init(tcp)
-
- // Receive RST.
- tcp.Encode(&header.TCPFields{
- SeqNum: 789,
- AckNum: 1235,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagRst | header.TCPFlagAck,
- WindowSize: 50000,
- })
-
- if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultReset {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultReset)
- }
-}
-
-func TestConnectionRefusedInSynRcvd(t *testing.T) {
- // Send SYN.
- tcp := make(header.TCP, header.TCPMinimumSize)
- tcp.Encode(&header.TCPFields{
- SeqNum: 1234,
- AckNum: 0,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagSyn,
- WindowSize: 30000,
- })
-
- tcb := tcpconntrack.TCB{}
- tcb.Init(tcp)
-
- // Receive SYN.
- tcp.Encode(&header.TCPFields{
- SeqNum: 789,
- AckNum: 0,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagSyn,
- WindowSize: 50000,
- })
-
- if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultAlive {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
- }
-
- // Receive RST with no ACK.
- tcp.Encode(&header.TCPFields{
- SeqNum: 790,
- AckNum: 0,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagRst,
- WindowSize: 50000,
- })
-
- if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultReset {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultReset)
- }
-}
-
-func TestConnectionResetInSynRcvd(t *testing.T) {
- // Send SYN.
- tcp := make(header.TCP, header.TCPMinimumSize)
- tcp.Encode(&header.TCPFields{
- SeqNum: 1234,
- AckNum: 0,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagSyn,
- WindowSize: 30000,
- })
-
- tcb := tcpconntrack.TCB{}
- tcb.Init(tcp)
-
- // Receive SYN.
- tcp.Encode(&header.TCPFields{
- SeqNum: 789,
- AckNum: 0,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagSyn,
- WindowSize: 50000,
- })
-
- if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultAlive {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
- }
-
- // Send RST with no ACK.
- tcp.Encode(&header.TCPFields{
- SeqNum: 1235,
- AckNum: 0,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagRst,
- })
-
- if r := tcb.UpdateStateOutbound(tcp); r != tcpconntrack.ResultReset {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultReset)
- }
-}
-
-func TestRetransmitOnSynSent(t *testing.T) {
- // Send initial SYN.
- tcp := make(header.TCP, header.TCPMinimumSize)
- tcp.Encode(&header.TCPFields{
- SeqNum: 1234,
- AckNum: 0,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagSyn,
- WindowSize: 30000,
- })
-
- tcb := tcpconntrack.TCB{}
- tcb.Init(tcp)
-
- // Retransmit the same SYN.
- if r := tcb.UpdateStateOutbound(tcp); r != tcpconntrack.ResultConnecting {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultConnecting)
- }
-}
-
-func TestRetransmitOnSynRcvd(t *testing.T) {
- // Send initial SYN.
- tcp := make(header.TCP, header.TCPMinimumSize)
- tcp.Encode(&header.TCPFields{
- SeqNum: 1234,
- AckNum: 0,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagSyn,
- WindowSize: 30000,
- })
-
- tcb := tcpconntrack.TCB{}
- tcb.Init(tcp)
-
- // Receive SYN. This will cause the state to go to SYN-RCVD.
- tcp.Encode(&header.TCPFields{
- SeqNum: 789,
- AckNum: 0,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagSyn,
- WindowSize: 50000,
- })
-
- if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultAlive {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
- }
-
- // Retransmit the original SYN.
- tcp.Encode(&header.TCPFields{
- SeqNum: 1234,
- AckNum: 0,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagSyn,
- WindowSize: 30000,
- })
-
- if r := tcb.UpdateStateOutbound(tcp); r != tcpconntrack.ResultAlive {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
- }
-
- // Transmit a SYN-ACK.
- tcp.Encode(&header.TCPFields{
- SeqNum: 1234,
- AckNum: 790,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagSyn | header.TCPFlagAck,
- WindowSize: 30000,
- })
-
- if r := tcb.UpdateStateOutbound(tcp); r != tcpconntrack.ResultAlive {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
- }
-}
-
-func TestClosedBySelf(t *testing.T) {
- tcb := connected(t, 1234, 789, 30000, 50000)
-
- // Send FIN.
- tcp := make(header.TCP, header.TCPMinimumSize)
- tcp.Encode(&header.TCPFields{
- SeqNum: 1235,
- AckNum: 790,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagAck | header.TCPFlagFin,
- WindowSize: 30000,
- })
-
- if r := tcb.UpdateStateOutbound(tcp); r != tcpconntrack.ResultAlive {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
- }
-
- // Receive FIN/ACK.
- tcp.Encode(&header.TCPFields{
- SeqNum: 790,
- AckNum: 1236,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagAck | header.TCPFlagFin,
- WindowSize: 50000,
- })
-
- if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultAlive {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
- }
-
- // Send ACK.
- tcp.Encode(&header.TCPFields{
- SeqNum: 1236,
- AckNum: 791,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagAck,
- WindowSize: 30000,
- })
-
- if r := tcb.UpdateStateOutbound(tcp); r != tcpconntrack.ResultClosedBySelf {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultClosedBySelf)
- }
-}
-
-func TestClosedByPeer(t *testing.T) {
- tcb := connected(t, 1234, 789, 30000, 50000)
-
- // Receive FIN.
- tcp := make(header.TCP, header.TCPMinimumSize)
- tcp.Encode(&header.TCPFields{
- SeqNum: 790,
- AckNum: 1235,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagAck | header.TCPFlagFin,
- WindowSize: 50000,
- })
-
- if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultAlive {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
- }
-
- // Send FIN/ACK.
- tcp.Encode(&header.TCPFields{
- SeqNum: 1235,
- AckNum: 791,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagAck | header.TCPFlagFin,
- WindowSize: 30000,
- })
-
- if r := tcb.UpdateStateOutbound(tcp); r != tcpconntrack.ResultAlive {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
- }
-
- // Receive ACK.
- tcp.Encode(&header.TCPFields{
- SeqNum: 791,
- AckNum: 1236,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagAck,
- WindowSize: 50000,
- })
-
- if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultClosedByPeer {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultClosedByPeer)
- }
-}
-
-func TestSendAndReceiveDataClosedBySelf(t *testing.T) {
- sseq := uint32(1234)
- rseq := uint32(789)
- tcb := connected(t, sseq, rseq, 30000, 50000)
- sseq++
- rseq++
-
- // Send some data.
- tcp := make(header.TCP, header.TCPMinimumSize+1024)
-
- for i := uint32(0); i < 10; i++ {
- // Send some data.
- tcp.Encode(&header.TCPFields{
- SeqNum: sseq,
- AckNum: rseq,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagAck,
- WindowSize: 30000,
- })
- sseq += uint32(len(tcp)) - header.TCPMinimumSize
-
- if r := tcb.UpdateStateOutbound(tcp); r != tcpconntrack.ResultAlive {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
- }
-
- // Receive ack for data.
- tcp.Encode(&header.TCPFields{
- SeqNum: rseq,
- AckNum: sseq,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagAck,
- WindowSize: 50000,
- })
-
- if r := tcb.UpdateStateInbound(tcp[:header.TCPMinimumSize]); r != tcpconntrack.ResultAlive {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
- }
- }
-
- for i := uint32(0); i < 10; i++ {
- // Receive some data.
- tcp.Encode(&header.TCPFields{
- SeqNum: rseq,
- AckNum: sseq,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagAck,
- WindowSize: 50000,
- })
- rseq += uint32(len(tcp)) - header.TCPMinimumSize
-
- if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultAlive {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
- }
-
- // Send ack for data.
- tcp.Encode(&header.TCPFields{
- SeqNum: sseq,
- AckNum: rseq,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagAck,
- WindowSize: 30000,
- })
-
- if r := tcb.UpdateStateOutbound(tcp[:header.TCPMinimumSize]); r != tcpconntrack.ResultAlive {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
- }
- }
-
- // Send FIN.
- tcp = tcp[:header.TCPMinimumSize]
- tcp.Encode(&header.TCPFields{
- SeqNum: sseq,
- AckNum: rseq,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagAck | header.TCPFlagFin,
- WindowSize: 30000,
- })
- sseq++
-
- if r := tcb.UpdateStateOutbound(tcp); r != tcpconntrack.ResultAlive {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
- }
-
- // Receive FIN/ACK.
- tcp.Encode(&header.TCPFields{
- SeqNum: rseq,
- AckNum: sseq,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagAck | header.TCPFlagFin,
- WindowSize: 50000,
- })
- rseq++
-
- if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultAlive {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
- }
-
- // Send ACK.
- tcp.Encode(&header.TCPFields{
- SeqNum: sseq,
- AckNum: rseq,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagAck,
- WindowSize: 30000,
- })
-
- if r := tcb.UpdateStateOutbound(tcp); r != tcpconntrack.ResultClosedBySelf {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultClosedBySelf)
- }
-}
-
-func TestIgnoreBadResetOnSynSent(t *testing.T) {
- // Send SYN.
- tcp := make(header.TCP, header.TCPMinimumSize)
- tcp.Encode(&header.TCPFields{
- SeqNum: 1234,
- AckNum: 0,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagSyn,
- WindowSize: 30000,
- })
-
- tcb := tcpconntrack.TCB{}
- tcb.Init(tcp)
-
- // Receive a RST with a bad ACK, it should not cause the connection to
- // be reset.
- acks := []uint32{1234, 1236, 1000, 5000}
- flags := []uint8{header.TCPFlagRst, header.TCPFlagRst | header.TCPFlagAck}
- for _, a := range acks {
- for _, f := range flags {
- tcp.Encode(&header.TCPFields{
- SeqNum: 789,
- AckNum: a,
- DataOffset: header.TCPMinimumSize,
- Flags: f,
- WindowSize: 50000,
- })
-
- if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultConnecting {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
- }
- }
- }
-
- // Complete the handshake.
- // Receive SYN-ACK.
- tcp.Encode(&header.TCPFields{
- SeqNum: 789,
- AckNum: 1235,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagSyn | header.TCPFlagAck,
- WindowSize: 50000,
- })
-
- if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultAlive {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
- }
-
- // Send ACK.
- tcp.Encode(&header.TCPFields{
- SeqNum: 1235,
- AckNum: 790,
- DataOffset: header.TCPMinimumSize,
- Flags: header.TCPFlagAck,
- WindowSize: 30000,
- })
-
- if r := tcb.UpdateStateOutbound(tcp); r != tcpconntrack.ResultAlive {
- t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
- }
-}
diff --git a/pkg/tcpip/transport/udp/BUILD b/pkg/tcpip/transport/udp/BUILD
deleted file mode 100644
index c1ca22b35..000000000
--- a/pkg/tcpip/transport/udp/BUILD
+++ /dev/null
@@ -1,69 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_template_instance(
- name = "udp_packet_list",
- out = "udp_packet_list.go",
- package = "udp",
- prefix = "udpPacket",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*udpPacket",
- "Linker": "*udpPacket",
- },
-)
-
-go_library(
- name = "udp",
- srcs = [
- "endpoint.go",
- "endpoint_state.go",
- "forwarder.go",
- "protocol.go",
- "udp_packet_list.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/tcpip/transport/udp",
- imports = ["gvisor.dev/gvisor/pkg/tcpip/buffer"],
- visibility = ["//visibility:public"],
- deps = [
- "//pkg/sleep",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/iptables",
- "//pkg/tcpip/stack",
- "//pkg/tcpip/transport/raw",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "udp_x_test",
- size = "small",
- srcs = ["udp_test.go"],
- deps = [
- ":udp",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/checker",
- "//pkg/tcpip/header",
- "//pkg/tcpip/link/channel",
- "//pkg/tcpip/link/sniffer",
- "//pkg/tcpip/network/ipv4",
- "//pkg/tcpip/network/ipv6",
- "//pkg/tcpip/stack",
- "//pkg/waiter",
- ],
-)
-
-filegroup(
- name = "autogen",
- srcs = [
- "udp_packet_list.go",
- ],
- visibility = ["//:sandbox"],
-)
diff --git a/pkg/tcpip/transport/udp/udp_packet_list.go b/pkg/tcpip/transport/udp/udp_packet_list.go
new file mode 100755
index 000000000..673a9373b
--- /dev/null
+++ b/pkg/tcpip/transport/udp/udp_packet_list.go
@@ -0,0 +1,173 @@
+package udp
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type udpPacketElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (udpPacketElementMapper) linkerFor(elem *udpPacket) *udpPacket { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type udpPacketList struct {
+ head *udpPacket
+ tail *udpPacket
+}
+
+// Reset resets list l to the empty state.
+func (l *udpPacketList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *udpPacketList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *udpPacketList) Front() *udpPacket {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *udpPacketList) Back() *udpPacket {
+ return l.tail
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *udpPacketList) PushFront(e *udpPacket) {
+ udpPacketElementMapper{}.linkerFor(e).SetNext(l.head)
+ udpPacketElementMapper{}.linkerFor(e).SetPrev(nil)
+
+ if l.head != nil {
+ udpPacketElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *udpPacketList) PushBack(e *udpPacket) {
+ udpPacketElementMapper{}.linkerFor(e).SetNext(nil)
+ udpPacketElementMapper{}.linkerFor(e).SetPrev(l.tail)
+
+ if l.tail != nil {
+ udpPacketElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *udpPacketList) PushBackList(m *udpPacketList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ udpPacketElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ udpPacketElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *udpPacketList) InsertAfter(b, e *udpPacket) {
+ a := udpPacketElementMapper{}.linkerFor(b).Next()
+ udpPacketElementMapper{}.linkerFor(e).SetNext(a)
+ udpPacketElementMapper{}.linkerFor(e).SetPrev(b)
+ udpPacketElementMapper{}.linkerFor(b).SetNext(e)
+
+ if a != nil {
+ udpPacketElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *udpPacketList) InsertBefore(a, e *udpPacket) {
+ b := udpPacketElementMapper{}.linkerFor(a).Prev()
+ udpPacketElementMapper{}.linkerFor(e).SetNext(a)
+ udpPacketElementMapper{}.linkerFor(e).SetPrev(b)
+ udpPacketElementMapper{}.linkerFor(a).SetPrev(e)
+
+ if b != nil {
+ udpPacketElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *udpPacketList) Remove(e *udpPacket) {
+ prev := udpPacketElementMapper{}.linkerFor(e).Prev()
+ next := udpPacketElementMapper{}.linkerFor(e).Next()
+
+ if prev != nil {
+ udpPacketElementMapper{}.linkerFor(prev).SetNext(next)
+ } else {
+ l.head = next
+ }
+
+ if next != nil {
+ udpPacketElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else {
+ l.tail = prev
+ }
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type udpPacketEntry struct {
+ next *udpPacket
+ prev *udpPacket
+}
+
+// Next returns the entry that follows e in the list.
+func (e *udpPacketEntry) Next() *udpPacket {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *udpPacketEntry) Prev() *udpPacket {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *udpPacketEntry) SetNext(elem *udpPacket) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *udpPacketEntry) SetPrev(elem *udpPacket) {
+ e.prev = elem
+}
diff --git a/pkg/tcpip/transport/udp/udp_state_autogen.go b/pkg/tcpip/transport/udp/udp_state_autogen.go
new file mode 100755
index 000000000..eb9d419ef
--- /dev/null
+++ b/pkg/tcpip/transport/udp/udp_state_autogen.go
@@ -0,0 +1,128 @@
+// automatically generated by stateify.
+
+package udp
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+ "gvisor.dev/gvisor/pkg/tcpip/buffer"
+)
+
+func (x *udpPacket) beforeSave() {}
+func (x *udpPacket) save(m state.Map) {
+ x.beforeSave()
+ var data buffer.VectorisedView = x.saveData()
+ m.SaveValue("data", data)
+ m.Save("udpPacketEntry", &x.udpPacketEntry)
+ m.Save("senderAddress", &x.senderAddress)
+ m.Save("timestamp", &x.timestamp)
+}
+
+func (x *udpPacket) afterLoad() {}
+func (x *udpPacket) load(m state.Map) {
+ m.Load("udpPacketEntry", &x.udpPacketEntry)
+ m.Load("senderAddress", &x.senderAddress)
+ m.Load("timestamp", &x.timestamp)
+ m.LoadValue("data", new(buffer.VectorisedView), func(y interface{}) { x.loadData(y.(buffer.VectorisedView)) })
+}
+
+func (x *endpoint) save(m state.Map) {
+ x.beforeSave()
+ var rcvBufSizeMax int = x.saveRcvBufSizeMax()
+ m.SaveValue("rcvBufSizeMax", rcvBufSizeMax)
+ m.Save("netProto", &x.netProto)
+ m.Save("waiterQueue", &x.waiterQueue)
+ m.Save("rcvReady", &x.rcvReady)
+ m.Save("rcvList", &x.rcvList)
+ m.Save("rcvBufSize", &x.rcvBufSize)
+ m.Save("rcvClosed", &x.rcvClosed)
+ m.Save("sndBufSize", &x.sndBufSize)
+ m.Save("id", &x.id)
+ m.Save("state", &x.state)
+ m.Save("bindNICID", &x.bindNICID)
+ m.Save("regNICID", &x.regNICID)
+ m.Save("dstPort", &x.dstPort)
+ m.Save("v6only", &x.v6only)
+ m.Save("multicastTTL", &x.multicastTTL)
+ m.Save("multicastAddr", &x.multicastAddr)
+ m.Save("multicastNICID", &x.multicastNICID)
+ m.Save("multicastLoop", &x.multicastLoop)
+ m.Save("reusePort", &x.reusePort)
+ m.Save("broadcast", &x.broadcast)
+ m.Save("shutdownFlags", &x.shutdownFlags)
+ m.Save("multicastMemberships", &x.multicastMemberships)
+ m.Save("effectiveNetProtos", &x.effectiveNetProtos)
+}
+
+func (x *endpoint) load(m state.Map) {
+ m.Load("netProto", &x.netProto)
+ m.Load("waiterQueue", &x.waiterQueue)
+ m.Load("rcvReady", &x.rcvReady)
+ m.Load("rcvList", &x.rcvList)
+ m.Load("rcvBufSize", &x.rcvBufSize)
+ m.Load("rcvClosed", &x.rcvClosed)
+ m.Load("sndBufSize", &x.sndBufSize)
+ m.Load("id", &x.id)
+ m.Load("state", &x.state)
+ m.Load("bindNICID", &x.bindNICID)
+ m.Load("regNICID", &x.regNICID)
+ m.Load("dstPort", &x.dstPort)
+ m.Load("v6only", &x.v6only)
+ m.Load("multicastTTL", &x.multicastTTL)
+ m.Load("multicastAddr", &x.multicastAddr)
+ m.Load("multicastNICID", &x.multicastNICID)
+ m.Load("multicastLoop", &x.multicastLoop)
+ m.Load("reusePort", &x.reusePort)
+ m.Load("broadcast", &x.broadcast)
+ m.Load("shutdownFlags", &x.shutdownFlags)
+ m.Load("multicastMemberships", &x.multicastMemberships)
+ m.Load("effectiveNetProtos", &x.effectiveNetProtos)
+ m.LoadValue("rcvBufSizeMax", new(int), func(y interface{}) { x.loadRcvBufSizeMax(y.(int)) })
+ m.AfterLoad(x.afterLoad)
+}
+
+func (x *multicastMembership) beforeSave() {}
+func (x *multicastMembership) save(m state.Map) {
+ x.beforeSave()
+ m.Save("nicID", &x.nicID)
+ m.Save("multicastAddr", &x.multicastAddr)
+}
+
+func (x *multicastMembership) afterLoad() {}
+func (x *multicastMembership) load(m state.Map) {
+ m.Load("nicID", &x.nicID)
+ m.Load("multicastAddr", &x.multicastAddr)
+}
+
+func (x *udpPacketList) beforeSave() {}
+func (x *udpPacketList) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *udpPacketList) afterLoad() {}
+func (x *udpPacketList) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *udpPacketEntry) beforeSave() {}
+func (x *udpPacketEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *udpPacketEntry) afterLoad() {}
+func (x *udpPacketEntry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func init() {
+ state.Register("udp.udpPacket", (*udpPacket)(nil), state.Fns{Save: (*udpPacket).save, Load: (*udpPacket).load})
+ state.Register("udp.endpoint", (*endpoint)(nil), state.Fns{Save: (*endpoint).save, Load: (*endpoint).load})
+ state.Register("udp.multicastMembership", (*multicastMembership)(nil), state.Fns{Save: (*multicastMembership).save, Load: (*multicastMembership).load})
+ state.Register("udp.udpPacketList", (*udpPacketList)(nil), state.Fns{Save: (*udpPacketList).save, Load: (*udpPacketList).load})
+ state.Register("udp.udpPacketEntry", (*udpPacketEntry)(nil), state.Fns{Save: (*udpPacketEntry).save, Load: (*udpPacketEntry).load})
+}
diff --git a/pkg/tcpip/transport/udp/udp_test.go b/pkg/tcpip/transport/udp/udp_test.go
deleted file mode 100644
index c6deab892..000000000
--- a/pkg/tcpip/transport/udp/udp_test.go
+++ /dev/null
@@ -1,1395 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package udp_test
-
-import (
- "bytes"
- "fmt"
- "math"
- "math/rand"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/checker"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/link/channel"
- "gvisor.dev/gvisor/pkg/tcpip/link/sniffer"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
- "gvisor.dev/gvisor/pkg/tcpip/transport/udp"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-// Addresses and ports used for testing. It is recommended that tests stick to
-// using these addresses as it allows using the testFlow helper.
-// Naming rules: 'stack*'' denotes local addresses and ports, while 'test*'
-// represents the remote endpoint.
-const (
- v4MappedAddrPrefix = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff"
- stackV6Addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
- testV6Addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"
- stackV4MappedAddr = v4MappedAddrPrefix + stackAddr
- testV4MappedAddr = v4MappedAddrPrefix + testAddr
- multicastV4MappedAddr = v4MappedAddrPrefix + multicastAddr
- broadcastV4MappedAddr = v4MappedAddrPrefix + broadcastAddr
- v4MappedWildcardAddr = v4MappedAddrPrefix + "\x00\x00\x00\x00"
-
- stackAddr = "\x0a\x00\x00\x01"
- stackPort = 1234
- testAddr = "\x0a\x00\x00\x02"
- testPort = 4096
- multicastAddr = "\xe8\x2b\xd3\xea"
- multicastV6Addr = "\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
- broadcastAddr = header.IPv4Broadcast
-
- // defaultMTU is the MTU, in bytes, used throughout the tests, except
- // where another value is explicitly used. It is chosen to match the MTU
- // of loopback interfaces on linux systems.
- defaultMTU = 65536
-)
-
-// header4Tuple stores the 4-tuple {src-IP, src-port, dst-IP, dst-port} used in
-// a packet header. These values are used to populate a header or verify one.
-// Note that because they are used in packet headers, the addresses are never in
-// a V4-mapped format.
-type header4Tuple struct {
- srcAddr tcpip.FullAddress
- dstAddr tcpip.FullAddress
-}
-
-// testFlow implements a helper type used for sending and receiving test
-// packets. A given test flow value defines 1) the socket endpoint used for the
-// test and 2) the type of packet send or received on the endpoint. E.g., a
-// multicastV6Only flow is a V6 multicast packet passing through a V6-only
-// endpoint. The type provides helper methods to characterize the flow (e.g.,
-// isV4) as well as return a proper header4Tuple for it.
-type testFlow int
-
-const (
- unicastV4 testFlow = iota // V4 unicast on a V4 socket
- unicastV4in6 // V4-mapped unicast on a V6-dual socket
- unicastV6 // V6 unicast on a V6 socket
- unicastV6Only // V6 unicast on a V6-only socket
- multicastV4 // V4 multicast on a V4 socket
- multicastV4in6 // V4-mapped multicast on a V6-dual socket
- multicastV6 // V6 multicast on a V6 socket
- multicastV6Only // V6 multicast on a V6-only socket
- broadcast // V4 broadcast on a V4 socket
- broadcastIn6 // V4-mapped broadcast on a V6-dual socket
-)
-
-func (flow testFlow) String() string {
- switch flow {
- case unicastV4:
- return "unicastV4"
- case unicastV6:
- return "unicastV6"
- case unicastV6Only:
- return "unicastV6Only"
- case unicastV4in6:
- return "unicastV4in6"
- case multicastV4:
- return "multicastV4"
- case multicastV6:
- return "multicastV6"
- case multicastV6Only:
- return "multicastV6Only"
- case multicastV4in6:
- return "multicastV4in6"
- case broadcast:
- return "broadcast"
- case broadcastIn6:
- return "broadcastIn6"
- default:
- return "unknown"
- }
-}
-
-// packetDirection explains if a flow is incoming (read) or outgoing (write).
-type packetDirection int
-
-const (
- incoming packetDirection = iota
- outgoing
-)
-
-// header4Tuple returns the header4Tuple for the given flow and direction. Note
-// that the tuple contains no mapped addresses as those only exist at the socket
-// level but not at the packet header level.
-func (flow testFlow) header4Tuple(d packetDirection) header4Tuple {
- var h header4Tuple
- if flow.isV4() {
- if d == outgoing {
- h = header4Tuple{
- srcAddr: tcpip.FullAddress{Addr: stackAddr, Port: stackPort},
- dstAddr: tcpip.FullAddress{Addr: testAddr, Port: testPort},
- }
- } else {
- h = header4Tuple{
- srcAddr: tcpip.FullAddress{Addr: testAddr, Port: testPort},
- dstAddr: tcpip.FullAddress{Addr: stackAddr, Port: stackPort},
- }
- }
- if flow.isMulticast() {
- h.dstAddr.Addr = multicastAddr
- } else if flow.isBroadcast() {
- h.dstAddr.Addr = broadcastAddr
- }
- } else { // IPv6
- if d == outgoing {
- h = header4Tuple{
- srcAddr: tcpip.FullAddress{Addr: stackV6Addr, Port: stackPort},
- dstAddr: tcpip.FullAddress{Addr: testV6Addr, Port: testPort},
- }
- } else {
- h = header4Tuple{
- srcAddr: tcpip.FullAddress{Addr: testV6Addr, Port: testPort},
- dstAddr: tcpip.FullAddress{Addr: stackV6Addr, Port: stackPort},
- }
- }
- if flow.isMulticast() {
- h.dstAddr.Addr = multicastV6Addr
- }
- }
- return h
-}
-
-func (flow testFlow) getMcastAddr() tcpip.Address {
- if flow.isV4() {
- return multicastAddr
- }
- return multicastV6Addr
-}
-
-// mapAddrIfApplicable converts the given V4 address into its V4-mapped version
-// if it is applicable to the flow.
-func (flow testFlow) mapAddrIfApplicable(v4Addr tcpip.Address) tcpip.Address {
- if flow.isMapped() {
- return v4MappedAddrPrefix + v4Addr
- }
- return v4Addr
-}
-
-// netProto returns the protocol number used for the network packet.
-func (flow testFlow) netProto() tcpip.NetworkProtocolNumber {
- if flow.isV4() {
- return ipv4.ProtocolNumber
- }
- return ipv6.ProtocolNumber
-}
-
-// sockProto returns the protocol number used when creating the socket
-// endpoint for this flow.
-func (flow testFlow) sockProto() tcpip.NetworkProtocolNumber {
- switch flow {
- case unicastV4in6, unicastV6, unicastV6Only, multicastV4in6, multicastV6, multicastV6Only, broadcastIn6:
- return ipv6.ProtocolNumber
- case unicastV4, multicastV4, broadcast:
- return ipv4.ProtocolNumber
- default:
- panic(fmt.Sprintf("invalid testFlow given: %d", flow))
- }
-}
-
-func (flow testFlow) checkerFn() func(*testing.T, []byte, ...checker.NetworkChecker) {
- if flow.isV4() {
- return checker.IPv4
- }
- return checker.IPv6
-}
-
-func (flow testFlow) isV6() bool { return !flow.isV4() }
-func (flow testFlow) isV4() bool {
- return flow.sockProto() == ipv4.ProtocolNumber || flow.isMapped()
-}
-
-func (flow testFlow) isV6Only() bool {
- switch flow {
- case unicastV6Only, multicastV6Only:
- return true
- case unicastV4, unicastV4in6, unicastV6, multicastV4, multicastV4in6, multicastV6, broadcast, broadcastIn6:
- return false
- default:
- panic(fmt.Sprintf("invalid testFlow given: %d", flow))
- }
-}
-
-func (flow testFlow) isMulticast() bool {
- switch flow {
- case multicastV4, multicastV4in6, multicastV6, multicastV6Only:
- return true
- case unicastV4, unicastV4in6, unicastV6, unicastV6Only, broadcast, broadcastIn6:
- return false
- default:
- panic(fmt.Sprintf("invalid testFlow given: %d", flow))
- }
-}
-
-func (flow testFlow) isBroadcast() bool {
- switch flow {
- case broadcast, broadcastIn6:
- return true
- case unicastV4, unicastV4in6, unicastV6, unicastV6Only, multicastV4, multicastV4in6, multicastV6, multicastV6Only:
- return false
- default:
- panic(fmt.Sprintf("invalid testFlow given: %d", flow))
- }
-}
-
-func (flow testFlow) isMapped() bool {
- switch flow {
- case unicastV4in6, multicastV4in6, broadcastIn6:
- return true
- case unicastV4, unicastV6, unicastV6Only, multicastV4, multicastV6, multicastV6Only, broadcast:
- return false
- default:
- panic(fmt.Sprintf("invalid testFlow given: %d", flow))
- }
-}
-
-type testContext struct {
- t *testing.T
- linkEP *channel.Endpoint
- s *stack.Stack
-
- ep tcpip.Endpoint
- wq waiter.Queue
-}
-
-func newDualTestContext(t *testing.T, mtu uint32) *testContext {
- t.Helper()
-
- s := stack.New([]string{ipv4.ProtocolName, ipv6.ProtocolName}, []string{udp.ProtocolName}, stack.Options{})
- ep := channel.New(256, mtu, "")
- wep := stack.LinkEndpoint(ep)
-
- if testing.Verbose() {
- wep = sniffer.New(ep)
- }
- if err := s.CreateNIC(1, wep); err != nil {
- t.Fatalf("CreateNIC failed: %v", err)
- }
-
- if err := s.AddAddress(1, ipv4.ProtocolNumber, stackAddr); err != nil {
- t.Fatalf("AddAddress failed: %v", err)
- }
-
- if err := s.AddAddress(1, ipv6.ProtocolNumber, stackV6Addr); err != nil {
- t.Fatalf("AddAddress failed: %v", err)
- }
-
- s.SetRouteTable([]tcpip.Route{
- {
- Destination: header.IPv4EmptySubnet,
- NIC: 1,
- },
- {
- Destination: header.IPv6EmptySubnet,
- NIC: 1,
- },
- })
-
- return &testContext{
- t: t,
- s: s,
- linkEP: ep,
- }
-}
-
-func (c *testContext) cleanup() {
- if c.ep != nil {
- c.ep.Close()
- }
-}
-
-func (c *testContext) createEndpoint(proto tcpip.NetworkProtocolNumber) {
- c.t.Helper()
-
- var err *tcpip.Error
- c.ep, err = c.s.NewEndpoint(udp.ProtocolNumber, proto, &c.wq)
- if err != nil {
- c.t.Fatal("NewEndpoint failed: ", err)
- }
-}
-
-func (c *testContext) createEndpointForFlow(flow testFlow) {
- c.t.Helper()
-
- c.createEndpoint(flow.sockProto())
- if flow.isV6Only() {
- if err := c.ep.SetSockOpt(tcpip.V6OnlyOption(1)); err != nil {
- c.t.Fatalf("SetSockOpt failed: %v", err)
- }
- } else if flow.isBroadcast() {
- if err := c.ep.SetSockOpt(tcpip.BroadcastOption(1)); err != nil {
- c.t.Fatal("SetSockOpt failed:", err)
- }
- }
-}
-
-// getPacketAndVerify reads a packet from the link endpoint and verifies the
-// header against expected values from the given test flow. In addition, it
-// calls any extra checker functions provided.
-func (c *testContext) getPacketAndVerify(flow testFlow, checkers ...checker.NetworkChecker) []byte {
- c.t.Helper()
-
- select {
- case p := <-c.linkEP.C:
- if p.Proto != flow.netProto() {
- c.t.Fatalf("Bad network protocol: got %v, wanted %v", p.Proto, flow.netProto())
- }
- b := make([]byte, len(p.Header)+len(p.Payload))
- copy(b, p.Header)
- copy(b[len(p.Header):], p.Payload)
-
- h := flow.header4Tuple(outgoing)
- checkers := append(
- checkers,
- checker.SrcAddr(h.srcAddr.Addr),
- checker.DstAddr(h.dstAddr.Addr),
- checker.UDP(checker.DstPort(h.dstAddr.Port)),
- )
- flow.checkerFn()(c.t, b, checkers...)
- return b
-
- case <-time.After(2 * time.Second):
- c.t.Fatalf("Packet wasn't written out")
- }
-
- return nil
-}
-
-// injectPacket creates a packet of the given flow and with the given payload,
-// and injects it into the link endpoint.
-func (c *testContext) injectPacket(flow testFlow, payload []byte) {
- c.t.Helper()
-
- h := flow.header4Tuple(incoming)
- if flow.isV4() {
- c.injectV4Packet(payload, &h)
- } else {
- c.injectV6Packet(payload, &h)
- }
-}
-
-// injectV6Packet creates a V6 test packet with the given payload and header
-// values, and injects it into the link endpoint.
-func (c *testContext) injectV6Packet(payload []byte, h *header4Tuple) {
- // Allocate a buffer for data and headers.
- buf := buffer.NewView(header.UDPMinimumSize + header.IPv6MinimumSize + len(payload))
- copy(buf[len(buf)-len(payload):], payload)
-
- // Initialize the IP header.
- ip := header.IPv6(buf)
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(header.UDPMinimumSize + len(payload)),
- NextHeader: uint8(udp.ProtocolNumber),
- HopLimit: 65,
- SrcAddr: h.srcAddr.Addr,
- DstAddr: h.dstAddr.Addr,
- })
-
- // Initialize the UDP header.
- u := header.UDP(buf[header.IPv6MinimumSize:])
- u.Encode(&header.UDPFields{
- SrcPort: h.srcAddr.Port,
- DstPort: h.dstAddr.Port,
- Length: uint16(header.UDPMinimumSize + len(payload)),
- })
-
- // Calculate the UDP pseudo-header checksum.
- xsum := header.PseudoHeaderChecksum(udp.ProtocolNumber, h.srcAddr.Addr, h.dstAddr.Addr, uint16(len(u)))
-
- // Calculate the UDP checksum and set it.
- xsum = header.Checksum(payload, xsum)
- u.SetChecksum(^u.CalculateChecksum(xsum))
-
- // Inject packet.
- c.linkEP.Inject(ipv6.ProtocolNumber, buf.ToVectorisedView())
-}
-
-// injectV6Packet creates a V4 test packet with the given payload and header
-// values, and injects it into the link endpoint.
-func (c *testContext) injectV4Packet(payload []byte, h *header4Tuple) {
- // Allocate a buffer for data and headers.
- buf := buffer.NewView(header.UDPMinimumSize + header.IPv4MinimumSize + len(payload))
- copy(buf[len(buf)-len(payload):], payload)
-
- // Initialize the IP header.
- ip := header.IPv4(buf)
- ip.Encode(&header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TotalLength: uint16(len(buf)),
- TTL: 65,
- Protocol: uint8(udp.ProtocolNumber),
- SrcAddr: h.srcAddr.Addr,
- DstAddr: h.dstAddr.Addr,
- })
- ip.SetChecksum(^ip.CalculateChecksum())
-
- // Initialize the UDP header.
- u := header.UDP(buf[header.IPv4MinimumSize:])
- u.Encode(&header.UDPFields{
- SrcPort: h.srcAddr.Port,
- DstPort: h.dstAddr.Port,
- Length: uint16(header.UDPMinimumSize + len(payload)),
- })
-
- // Calculate the UDP pseudo-header checksum.
- xsum := header.PseudoHeaderChecksum(udp.ProtocolNumber, h.srcAddr.Addr, h.dstAddr.Addr, uint16(len(u)))
-
- // Calculate the UDP checksum and set it.
- xsum = header.Checksum(payload, xsum)
- u.SetChecksum(^u.CalculateChecksum(xsum))
-
- // Inject packet.
- c.linkEP.Inject(ipv4.ProtocolNumber, buf.ToVectorisedView())
-}
-
-func newPayload() []byte {
- return newMinPayload(30)
-}
-
-func newMinPayload(minSize int) []byte {
- b := make([]byte, minSize+rand.Intn(100))
- for i := range b {
- b[i] = byte(rand.Intn(256))
- }
- return b
-}
-
-func TestBindPortReuse(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpoint(ipv6.ProtocolNumber)
-
- var eps [5]tcpip.Endpoint
- reusePortOpt := tcpip.ReusePortOption(1)
-
- pollChannel := make(chan tcpip.Endpoint)
- for i := 0; i < len(eps); i++ {
- // Try to receive the data.
- wq := waiter.Queue{}
- we, ch := waiter.NewChannelEntry(nil)
- wq.EventRegister(&we, waiter.EventIn)
- defer wq.EventUnregister(&we)
- defer close(ch)
-
- var err *tcpip.Error
- eps[i], err = c.s.NewEndpoint(udp.ProtocolNumber, ipv6.ProtocolNumber, &wq)
- if err != nil {
- c.t.Fatalf("NewEndpoint failed: %v", err)
- }
-
- go func(ep tcpip.Endpoint) {
- for range ch {
- pollChannel <- ep
- }
- }(eps[i])
-
- defer eps[i].Close()
- if err := eps[i].SetSockOpt(reusePortOpt); err != nil {
- c.t.Fatalf("SetSockOpt failed failed: %v", err)
- }
- if err := eps[i].Bind(tcpip.FullAddress{Addr: stackV6Addr, Port: stackPort}); err != nil {
- t.Fatalf("ep.Bind(...) failed: %v", err)
- }
- }
-
- npackets := 100000
- nports := 10000
- ports := make(map[uint16]tcpip.Endpoint)
- stats := make(map[tcpip.Endpoint]int)
- for i := 0; i < npackets; i++ {
- // Send a packet.
- port := uint16(i % nports)
- payload := newPayload()
- c.injectV6Packet(payload, &header4Tuple{
- srcAddr: tcpip.FullAddress{Addr: testV6Addr, Port: testPort + port},
- dstAddr: tcpip.FullAddress{Addr: stackV6Addr, Port: stackPort},
- })
-
- var addr tcpip.FullAddress
- ep := <-pollChannel
- _, _, err := ep.Read(&addr)
- if err != nil {
- c.t.Fatalf("Read failed: %v", err)
- }
- stats[ep]++
- if i < nports {
- ports[uint16(i)] = ep
- } else {
- // Check that all packets from one client are handled
- // by the same socket.
- if ports[port] != ep {
- t.Fatalf("Port mismatch")
- }
- }
- }
-
- if len(stats) != len(eps) {
- t.Fatalf("Only %d(expected %d) sockets received packets", len(stats), len(eps))
- }
-
- // Check that a packet distribution is fair between sockets.
- for _, c := range stats {
- n := float64(npackets) / float64(len(eps))
- // The deviation is less than 10%.
- if math.Abs(float64(c)-n) > n/10 {
- t.Fatal(c, n)
- }
- }
-}
-
-// testRead sends a packet of the given test flow into the stack by injecting it
-// into the link endpoint. It then reads it from the UDP endpoint and verifies
-// its correctness.
-func testRead(c *testContext, flow testFlow) {
- c.t.Helper()
-
- payload := newPayload()
- c.injectPacket(flow, payload)
-
- // Try to receive the data.
- we, ch := waiter.NewChannelEntry(nil)
- c.wq.EventRegister(&we, waiter.EventIn)
- defer c.wq.EventUnregister(&we)
-
- var addr tcpip.FullAddress
- v, _, err := c.ep.Read(&addr)
- if err == tcpip.ErrWouldBlock {
- // Wait for data to become available.
- select {
- case <-ch:
- v, _, err = c.ep.Read(&addr)
- if err != nil {
- c.t.Fatalf("Read failed: %v", err)
- }
-
- case <-time.After(1 * time.Second):
- c.t.Fatalf("Timed out waiting for data")
- }
- }
-
- // Check the peer address.
- h := flow.header4Tuple(incoming)
- if addr.Addr != h.srcAddr.Addr {
- c.t.Fatalf("Unexpected remote address: got %v, want %v", addr.Addr, h.srcAddr)
- }
-
- // Check the payload.
- if !bytes.Equal(payload, v) {
- c.t.Fatalf("Bad payload: got %x, want %x", v, payload)
- }
-}
-
-func TestBindEphemeralPort(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpoint(ipv6.ProtocolNumber)
-
- if err := c.ep.Bind(tcpip.FullAddress{}); err != nil {
- t.Fatalf("ep.Bind(...) failed: %v", err)
- }
-}
-
-func TestBindReservedPort(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpoint(ipv6.ProtocolNumber)
-
- if err := c.ep.Connect(tcpip.FullAddress{Addr: testV6Addr, Port: testPort}); err != nil {
- c.t.Fatalf("Connect failed: %v", err)
- }
-
- addr, err := c.ep.GetLocalAddress()
- if err != nil {
- t.Fatalf("GetLocalAddress failed: %v", err)
- }
-
- // We can't bind the address reserved by the connected endpoint above.
- {
- ep, err := c.s.NewEndpoint(udp.ProtocolNumber, ipv6.ProtocolNumber, &c.wq)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
- defer ep.Close()
- if got, want := ep.Bind(addr), tcpip.ErrPortInUse; got != want {
- t.Fatalf("got ep.Bind(...) = %v, want = %v", got, want)
- }
- }
-
- func() {
- ep, err := c.s.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, &c.wq)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
- defer ep.Close()
- // We can't bind ipv4-any on the port reserved by the connected endpoint
- // above, since the endpoint is dual-stack.
- if got, want := ep.Bind(tcpip.FullAddress{Port: addr.Port}), tcpip.ErrPortInUse; got != want {
- t.Fatalf("got ep.Bind(...) = %v, want = %v", got, want)
- }
- // We can bind an ipv4 address on this port, though.
- if err := ep.Bind(tcpip.FullAddress{Addr: stackAddr, Port: addr.Port}); err != nil {
- t.Fatalf("ep.Bind(...) failed: %v", err)
- }
- }()
-
- // Once the connected endpoint releases its port reservation, we are able to
- // bind ipv4-any once again.
- c.ep.Close()
- func() {
- ep, err := c.s.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, &c.wq)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
- defer ep.Close()
- if err := ep.Bind(tcpip.FullAddress{Port: addr.Port}); err != nil {
- t.Fatalf("ep.Bind(...) failed: %v", err)
- }
- }()
-}
-
-func TestV4ReadOnV6(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpointForFlow(unicastV4in6)
-
- // Bind to wildcard.
- if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil {
- c.t.Fatalf("Bind failed: %v", err)
- }
-
- // Test acceptance.
- testRead(c, unicastV4in6)
-}
-
-func TestV4ReadOnBoundToV4MappedWildcard(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpointForFlow(unicastV4in6)
-
- // Bind to v4 mapped wildcard.
- if err := c.ep.Bind(tcpip.FullAddress{Addr: v4MappedWildcardAddr, Port: stackPort}); err != nil {
- c.t.Fatalf("Bind failed: %v", err)
- }
-
- // Test acceptance.
- testRead(c, unicastV4in6)
-}
-
-func TestV4ReadOnBoundToV4Mapped(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpointForFlow(unicastV4in6)
-
- // Bind to local address.
- if err := c.ep.Bind(tcpip.FullAddress{Addr: stackV4MappedAddr, Port: stackPort}); err != nil {
- c.t.Fatalf("Bind failed: %v", err)
- }
-
- // Test acceptance.
- testRead(c, unicastV4in6)
-}
-
-func TestV6ReadOnV6(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpointForFlow(unicastV6)
-
- // Bind to wildcard.
- if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil {
- c.t.Fatalf("Bind failed: %v", err)
- }
-
- // Test acceptance.
- testRead(c, unicastV6)
-}
-
-func TestV4ReadOnV4(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpointForFlow(unicastV4)
-
- // Bind to wildcard.
- if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil {
- c.t.Fatalf("Bind failed: %v", err)
- }
-
- // Test acceptance.
- testRead(c, unicastV4)
-}
-
-// TestReadOnBoundToMulticast checks that an endpoint can bind to a multicast
-// address and receive data sent to that address.
-func TestReadOnBoundToMulticast(t *testing.T) {
- // FIXME(b/128189410): multicastV4in6 currently doesn't work as
- // AddMembershipOption doesn't handle V4in6 addresses.
- for _, flow := range []testFlow{multicastV4, multicastV6, multicastV6Only} {
- t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpointForFlow(flow)
-
- // Bind to multicast address.
- mcastAddr := flow.mapAddrIfApplicable(flow.getMcastAddr())
- if err := c.ep.Bind(tcpip.FullAddress{Addr: mcastAddr, Port: stackPort}); err != nil {
- c.t.Fatal("Bind failed:", err)
- }
-
- // Join multicast group.
- ifoptSet := tcpip.AddMembershipOption{NIC: 1, MulticastAddr: mcastAddr}
- if err := c.ep.SetSockOpt(ifoptSet); err != nil {
- c.t.Fatal("SetSockOpt failed:", err)
- }
-
- testRead(c, flow)
- })
- }
-}
-
-// TestV4ReadOnBoundToBroadcast checks that an endpoint can bind to a broadcast
-// address and receive broadcast data on it.
-func TestV4ReadOnBoundToBroadcast(t *testing.T) {
- for _, flow := range []testFlow{broadcast, broadcastIn6} {
- t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpointForFlow(flow)
-
- // Bind to broadcast address.
- bcastAddr := flow.mapAddrIfApplicable(broadcastAddr)
- if err := c.ep.Bind(tcpip.FullAddress{Addr: bcastAddr, Port: stackPort}); err != nil {
- c.t.Fatalf("Bind failed: %s", err)
- }
-
- // Test acceptance.
- testRead(c, flow)
- })
- }
-}
-
-// testFailingWrite sends a packet of the given test flow into the UDP endpoint
-// and verifies it fails with the provided error code.
-func testFailingWrite(c *testContext, flow testFlow, wantErr *tcpip.Error) {
- c.t.Helper()
-
- h := flow.header4Tuple(outgoing)
- writeDstAddr := flow.mapAddrIfApplicable(h.dstAddr.Addr)
-
- payload := buffer.View(newPayload())
- _, _, gotErr := c.ep.Write(tcpip.SlicePayload(payload), tcpip.WriteOptions{
- To: &tcpip.FullAddress{Addr: writeDstAddr, Port: h.dstAddr.Port},
- })
- if gotErr != wantErr {
- c.t.Fatalf("Write returned unexpected error: got %v, want %v", gotErr, wantErr)
- }
-}
-
-// testWrite sends a packet of the given test flow from the UDP endpoint to the
-// flow's destination address:port. It then receives it from the link endpoint
-// and verifies its correctness including any additional checker functions
-// provided.
-func testWrite(c *testContext, flow testFlow, checkers ...checker.NetworkChecker) uint16 {
- c.t.Helper()
- return testWriteInternal(c, flow, true, checkers...)
-}
-
-// testWriteWithoutDestination sends a packet of the given test flow from the
-// UDP endpoint without giving a destination address:port. It then receives it
-// from the link endpoint and verifies its correctness including any additional
-// checker functions provided.
-func testWriteWithoutDestination(c *testContext, flow testFlow, checkers ...checker.NetworkChecker) uint16 {
- c.t.Helper()
- return testWriteInternal(c, flow, false, checkers...)
-}
-
-func testWriteInternal(c *testContext, flow testFlow, setDest bool, checkers ...checker.NetworkChecker) uint16 {
- c.t.Helper()
-
- writeOpts := tcpip.WriteOptions{}
- if setDest {
- h := flow.header4Tuple(outgoing)
- writeDstAddr := flow.mapAddrIfApplicable(h.dstAddr.Addr)
- writeOpts = tcpip.WriteOptions{
- To: &tcpip.FullAddress{Addr: writeDstAddr, Port: h.dstAddr.Port},
- }
- }
- payload := buffer.View(newPayload())
- n, _, err := c.ep.Write(tcpip.SlicePayload(payload), writeOpts)
- if err != nil {
- c.t.Fatalf("Write failed: %v", err)
- }
- if n != int64(len(payload)) {
- c.t.Fatalf("Bad number of bytes written: got %v, want %v", n, len(payload))
- }
-
- // Received the packet and check the payload.
- b := c.getPacketAndVerify(flow, checkers...)
- var udp header.UDP
- if flow.isV4() {
- udp = header.UDP(header.IPv4(b).Payload())
- } else {
- udp = header.UDP(header.IPv6(b).Payload())
- }
- if !bytes.Equal(payload, udp.Payload()) {
- c.t.Fatalf("Bad payload: got %x, want %x", udp.Payload(), payload)
- }
-
- return udp.SourcePort()
-}
-
-func testDualWrite(c *testContext) uint16 {
- c.t.Helper()
-
- v4Port := testWrite(c, unicastV4in6)
- v6Port := testWrite(c, unicastV6)
- if v4Port != v6Port {
- c.t.Fatalf("expected v4 and v6 ports to be equal: got v4Port = %d, v6Port = %d", v4Port, v6Port)
- }
-
- return v4Port
-}
-
-func TestDualWriteUnbound(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpoint(ipv6.ProtocolNumber)
-
- testDualWrite(c)
-}
-
-func TestDualWriteBoundToWildcard(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpoint(ipv6.ProtocolNumber)
-
- // Bind to wildcard.
- if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil {
- c.t.Fatalf("Bind failed: %v", err)
- }
-
- p := testDualWrite(c)
- if p != stackPort {
- c.t.Fatalf("Bad port: got %v, want %v", p, stackPort)
- }
-}
-
-func TestDualWriteConnectedToV6(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpoint(ipv6.ProtocolNumber)
-
- // Connect to v6 address.
- if err := c.ep.Connect(tcpip.FullAddress{Addr: testV6Addr, Port: testPort}); err != nil {
- c.t.Fatalf("Bind failed: %v", err)
- }
-
- testWrite(c, unicastV6)
-
- // Write to V4 mapped address.
- testFailingWrite(c, unicastV4in6, tcpip.ErrNetworkUnreachable)
-}
-
-func TestDualWriteConnectedToV4Mapped(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpoint(ipv6.ProtocolNumber)
-
- // Connect to v4 mapped address.
- if err := c.ep.Connect(tcpip.FullAddress{Addr: testV4MappedAddr, Port: testPort}); err != nil {
- c.t.Fatalf("Bind failed: %v", err)
- }
-
- testWrite(c, unicastV4in6)
-
- // Write to v6 address.
- testFailingWrite(c, unicastV6, tcpip.ErrInvalidEndpointState)
-}
-
-func TestV4WriteOnV6Only(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpointForFlow(unicastV6Only)
-
- // Write to V4 mapped address.
- testFailingWrite(c, unicastV4in6, tcpip.ErrNoRoute)
-}
-
-func TestV6WriteOnBoundToV4Mapped(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpoint(ipv6.ProtocolNumber)
-
- // Bind to v4 mapped address.
- if err := c.ep.Bind(tcpip.FullAddress{Addr: stackV4MappedAddr, Port: stackPort}); err != nil {
- c.t.Fatalf("Bind failed: %v", err)
- }
-
- // Write to v6 address.
- testFailingWrite(c, unicastV6, tcpip.ErrInvalidEndpointState)
-}
-
-func TestV6WriteOnConnected(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpoint(ipv6.ProtocolNumber)
-
- // Connect to v6 address.
- if err := c.ep.Connect(tcpip.FullAddress{Addr: testV6Addr, Port: testPort}); err != nil {
- c.t.Fatalf("Connect failed: %v", err)
- }
-
- testWriteWithoutDestination(c, unicastV6)
-}
-
-func TestV4WriteOnConnected(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpoint(ipv6.ProtocolNumber)
-
- // Connect to v4 mapped address.
- if err := c.ep.Connect(tcpip.FullAddress{Addr: testV4MappedAddr, Port: testPort}); err != nil {
- c.t.Fatalf("Connect failed: %v", err)
- }
-
- testWriteWithoutDestination(c, unicastV4)
-}
-
-// TestWriteOnBoundToV4Multicast checks that we can send packets out of a socket
-// that is bound to a V4 multicast address.
-func TestWriteOnBoundToV4Multicast(t *testing.T) {
- for _, flow := range []testFlow{unicastV4, multicastV4, broadcast} {
- t.Run(fmt.Sprintf("%s", flow), func(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpointForFlow(flow)
-
- // Bind to V4 mcast address.
- if err := c.ep.Bind(tcpip.FullAddress{Addr: multicastAddr, Port: stackPort}); err != nil {
- c.t.Fatal("Bind failed:", err)
- }
-
- testWrite(c, flow)
- })
- }
-}
-
-// TestWriteOnBoundToV4MappedMulticast checks that we can send packets out of a
-// socket that is bound to a V4-mapped multicast address.
-func TestWriteOnBoundToV4MappedMulticast(t *testing.T) {
- for _, flow := range []testFlow{unicastV4in6, multicastV4in6, broadcastIn6} {
- t.Run(fmt.Sprintf("%s", flow), func(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpointForFlow(flow)
-
- // Bind to V4Mapped mcast address.
- if err := c.ep.Bind(tcpip.FullAddress{Addr: multicastV4MappedAddr, Port: stackPort}); err != nil {
- c.t.Fatalf("Bind failed: %s", err)
- }
-
- testWrite(c, flow)
- })
- }
-}
-
-// TestWriteOnBoundToV6Multicast checks that we can send packets out of a
-// socket that is bound to a V6 multicast address.
-func TestWriteOnBoundToV6Multicast(t *testing.T) {
- for _, flow := range []testFlow{unicastV6, multicastV6} {
- t.Run(fmt.Sprintf("%s", flow), func(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpointForFlow(flow)
-
- // Bind to V6 mcast address.
- if err := c.ep.Bind(tcpip.FullAddress{Addr: multicastV6Addr, Port: stackPort}); err != nil {
- c.t.Fatalf("Bind failed: %s", err)
- }
-
- testWrite(c, flow)
- })
- }
-}
-
-// TestWriteOnBoundToV6Multicast checks that we can send packets out of a
-// V6-only socket that is bound to a V6 multicast address.
-func TestWriteOnBoundToV6OnlyMulticast(t *testing.T) {
- for _, flow := range []testFlow{unicastV6Only, multicastV6Only} {
- t.Run(fmt.Sprintf("%s", flow), func(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpointForFlow(flow)
-
- // Bind to V6 mcast address.
- if err := c.ep.Bind(tcpip.FullAddress{Addr: multicastV6Addr, Port: stackPort}); err != nil {
- c.t.Fatalf("Bind failed: %s", err)
- }
-
- testWrite(c, flow)
- })
- }
-}
-
-// TestWriteOnBoundToBroadcast checks that we can send packets out of a
-// socket that is bound to the broadcast address.
-func TestWriteOnBoundToBroadcast(t *testing.T) {
- for _, flow := range []testFlow{unicastV4, multicastV4, broadcast} {
- t.Run(fmt.Sprintf("%s", flow), func(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpointForFlow(flow)
-
- // Bind to V4 broadcast address.
- if err := c.ep.Bind(tcpip.FullAddress{Addr: broadcastAddr, Port: stackPort}); err != nil {
- c.t.Fatal("Bind failed:", err)
- }
-
- testWrite(c, flow)
- })
- }
-}
-
-// TestWriteOnBoundToV4MappedBroadcast checks that we can send packets out of a
-// socket that is bound to the V4-mapped broadcast address.
-func TestWriteOnBoundToV4MappedBroadcast(t *testing.T) {
- for _, flow := range []testFlow{unicastV4in6, multicastV4in6, broadcastIn6} {
- t.Run(fmt.Sprintf("%s", flow), func(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpointForFlow(flow)
-
- // Bind to V4Mapped mcast address.
- if err := c.ep.Bind(tcpip.FullAddress{Addr: broadcastV4MappedAddr, Port: stackPort}); err != nil {
- c.t.Fatalf("Bind failed: %s", err)
- }
-
- testWrite(c, flow)
- })
- }
-}
-
-func TestReadIncrementsPacketsReceived(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- // Create IPv4 UDP endpoint
- c.createEndpoint(ipv6.ProtocolNumber)
-
- // Bind to wildcard.
- if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil {
- c.t.Fatalf("Bind failed: %v", err)
- }
-
- testRead(c, unicastV4)
-
- var want uint64 = 1
- if got := c.s.Stats().UDP.PacketsReceived.Value(); got != want {
- c.t.Fatalf("Read did not increment PacketsReceived: got %v, want %v", got, want)
- }
-}
-
-func TestWriteIncrementsPacketsSent(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpoint(ipv6.ProtocolNumber)
-
- testDualWrite(c)
-
- var want uint64 = 2
- if got := c.s.Stats().UDP.PacketsSent.Value(); got != want {
- c.t.Fatalf("Write did not increment PacketsSent: got %v, want %v", got, want)
- }
-}
-
-func TestTTL(t *testing.T) {
- for _, flow := range []testFlow{unicastV4, unicastV4in6, unicastV6, unicastV6Only, multicastV4, multicastV4in6, multicastV6, broadcast, broadcastIn6} {
- t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpointForFlow(flow)
-
- const multicastTTL = 42
- if err := c.ep.SetSockOpt(tcpip.MulticastTTLOption(multicastTTL)); err != nil {
- c.t.Fatalf("SetSockOpt failed: %v", err)
- }
-
- var wantTTL uint8
- if flow.isMulticast() {
- wantTTL = multicastTTL
- } else {
- var p stack.NetworkProtocol
- if flow.isV4() {
- p = ipv4.NewProtocol()
- } else {
- p = ipv6.NewProtocol()
- }
- ep, err := p.NewEndpoint(0, tcpip.AddressWithPrefix{}, nil, nil, nil)
- if err != nil {
- t.Fatal(err)
- }
- wantTTL = ep.DefaultTTL()
- ep.Close()
- }
-
- testWrite(c, flow, checker.TTL(wantTTL))
- })
- }
-}
-
-func TestMulticastInterfaceOption(t *testing.T) {
- for _, flow := range []testFlow{multicastV4, multicastV4in6, multicastV6, multicastV6Only} {
- t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) {
- for _, bindTyp := range []string{"bound", "unbound"} {
- t.Run(bindTyp, func(t *testing.T) {
- for _, optTyp := range []string{"use local-addr", "use NICID", "use local-addr and NIC"} {
- t.Run(optTyp, func(t *testing.T) {
- h := flow.header4Tuple(outgoing)
- mcastAddr := h.dstAddr.Addr
- localIfAddr := h.srcAddr.Addr
-
- var ifoptSet tcpip.MulticastInterfaceOption
- switch optTyp {
- case "use local-addr":
- ifoptSet.InterfaceAddr = localIfAddr
- case "use NICID":
- ifoptSet.NIC = 1
- case "use local-addr and NIC":
- ifoptSet.InterfaceAddr = localIfAddr
- ifoptSet.NIC = 1
- default:
- t.Fatal("unknown test variant")
- }
-
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- c.createEndpoint(flow.sockProto())
-
- if bindTyp == "bound" {
- // Bind the socket by connecting to the multicast address.
- // This may have an influence on how the multicast interface
- // is set.
- addr := tcpip.FullAddress{
- Addr: flow.mapAddrIfApplicable(mcastAddr),
- Port: stackPort,
- }
- if err := c.ep.Connect(addr); err != nil {
- c.t.Fatalf("Connect failed: %v", err)
- }
- }
-
- if err := c.ep.SetSockOpt(ifoptSet); err != nil {
- c.t.Fatalf("SetSockOpt failed: %v", err)
- }
-
- // Verify multicast interface addr and NIC were set correctly.
- // Note that NIC must be 1 since this is our outgoing interface.
- ifoptWant := tcpip.MulticastInterfaceOption{NIC: 1, InterfaceAddr: ifoptSet.InterfaceAddr}
- var ifoptGot tcpip.MulticastInterfaceOption
- if err := c.ep.GetSockOpt(&ifoptGot); err != nil {
- c.t.Fatalf("GetSockOpt failed: %v", err)
- }
- if ifoptGot != ifoptWant {
- c.t.Errorf("got GetSockOpt() = %#v, want = %#v", ifoptGot, ifoptWant)
- }
- })
- }
- })
- }
- })
- }
-}
-
-// TestV4UnknownDestination verifies that we generate an ICMPv4 Destination
-// Unreachable message when a udp datagram is received on ports for which there
-// is no bound udp socket.
-func TestV4UnknownDestination(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- testCases := []struct {
- flow testFlow
- icmpRequired bool
- // largePayload if true, will result in a payload large enough
- // so that the final generated IPv4 packet is larger than
- // header.IPv4MinimumProcessableDatagramSize.
- largePayload bool
- }{
- {unicastV4, true, false},
- {unicastV4, true, true},
- {multicastV4, false, false},
- {multicastV4, false, true},
- {broadcast, false, false},
- {broadcast, false, true},
- }
- for _, tc := range testCases {
- t.Run(fmt.Sprintf("flow:%s icmpRequired:%t largePayload:%t", tc.flow, tc.icmpRequired, tc.largePayload), func(t *testing.T) {
- payload := newPayload()
- if tc.largePayload {
- payload = newMinPayload(576)
- }
- c.injectPacket(tc.flow, payload)
- if !tc.icmpRequired {
- select {
- case p := <-c.linkEP.C:
- t.Fatalf("unexpected packet received: %+v", p)
- case <-time.After(1 * time.Second):
- return
- }
- }
-
- select {
- case p := <-c.linkEP.C:
- var pkt []byte
- pkt = append(pkt, p.Header...)
- pkt = append(pkt, p.Payload...)
- if got, want := len(pkt), header.IPv4MinimumProcessableDatagramSize; got > want {
- t.Fatalf("got an ICMP packet of size: %d, want: sz <= %d", got, want)
- }
-
- hdr := header.IPv4(pkt)
- checker.IPv4(t, hdr, checker.ICMPv4(
- checker.ICMPv4Type(header.ICMPv4DstUnreachable),
- checker.ICMPv4Code(header.ICMPv4PortUnreachable)))
-
- icmpPkt := header.ICMPv4(hdr.Payload())
- payloadIPHeader := header.IPv4(icmpPkt.Payload())
- wantLen := len(payload)
- if tc.largePayload {
- wantLen = header.IPv4MinimumProcessableDatagramSize - header.IPv4MinimumSize*2 - header.ICMPv4MinimumSize - header.UDPMinimumSize
- }
-
- // In case of large payloads the IP packet may be truncated. Update
- // the length field before retrieving the udp datagram payload.
- payloadIPHeader.SetTotalLength(uint16(wantLen + header.UDPMinimumSize + header.IPv4MinimumSize))
-
- origDgram := header.UDP(payloadIPHeader.Payload())
- if got, want := len(origDgram.Payload()), wantLen; got != want {
- t.Fatalf("unexpected payload length got: %d, want: %d", got, want)
- }
- if got, want := origDgram.Payload(), payload[:wantLen]; !bytes.Equal(got, want) {
- t.Fatalf("unexpected payload got: %d, want: %d", got, want)
- }
- case <-time.After(1 * time.Second):
- t.Fatalf("packet wasn't written out")
- }
- })
- }
-}
-
-// TestV6UnknownDestination verifies that we generate an ICMPv6 Destination
-// Unreachable message when a udp datagram is received on ports for which there
-// is no bound udp socket.
-func TestV6UnknownDestination(t *testing.T) {
- c := newDualTestContext(t, defaultMTU)
- defer c.cleanup()
-
- testCases := []struct {
- flow testFlow
- icmpRequired bool
- // largePayload if true will result in a payload large enough to
- // create an IPv6 packet > header.IPv6MinimumMTU bytes.
- largePayload bool
- }{
- {unicastV6, true, false},
- {unicastV6, true, true},
- {multicastV6, false, false},
- {multicastV6, false, true},
- }
- for _, tc := range testCases {
- t.Run(fmt.Sprintf("flow:%s icmpRequired:%t largePayload:%t", tc.flow, tc.icmpRequired, tc.largePayload), func(t *testing.T) {
- payload := newPayload()
- if tc.largePayload {
- payload = newMinPayload(1280)
- }
- c.injectPacket(tc.flow, payload)
- if !tc.icmpRequired {
- select {
- case p := <-c.linkEP.C:
- t.Fatalf("unexpected packet received: %+v", p)
- case <-time.After(1 * time.Second):
- return
- }
- }
-
- select {
- case p := <-c.linkEP.C:
- var pkt []byte
- pkt = append(pkt, p.Header...)
- pkt = append(pkt, p.Payload...)
- if got, want := len(pkt), header.IPv6MinimumMTU; got > want {
- t.Fatalf("got an ICMP packet of size: %d, want: sz <= %d", got, want)
- }
-
- hdr := header.IPv6(pkt)
- checker.IPv6(t, hdr, checker.ICMPv6(
- checker.ICMPv6Type(header.ICMPv6DstUnreachable),
- checker.ICMPv6Code(header.ICMPv6PortUnreachable)))
-
- icmpPkt := header.ICMPv6(hdr.Payload())
- payloadIPHeader := header.IPv6(icmpPkt.Payload())
- wantLen := len(payload)
- if tc.largePayload {
- wantLen = header.IPv6MinimumMTU - header.IPv6MinimumSize*2 - header.ICMPv6MinimumSize - header.UDPMinimumSize
- }
- // In case of large payloads the IP packet may be truncated. Update
- // the length field before retrieving the udp datagram payload.
- payloadIPHeader.SetPayloadLength(uint16(wantLen + header.UDPMinimumSize))
-
- origDgram := header.UDP(payloadIPHeader.Payload())
- if got, want := len(origDgram.Payload()), wantLen; got != want {
- t.Fatalf("unexpected payload length got: %d, want: %d", got, want)
- }
- if got, want := origDgram.Payload(), payload[:wantLen]; !bytes.Equal(got, want) {
- t.Fatalf("unexpected payload got: %v, want: %v", got, want)
- }
- case <-time.After(1 * time.Second):
- t.Fatalf("packet wasn't written out")
- }
- })
- }
-}
diff --git a/pkg/tmutex/BUILD b/pkg/tmutex/BUILD
deleted file mode 100644
index 6afdb29b7..000000000
--- a/pkg/tmutex/BUILD
+++ /dev/null
@@ -1,18 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "tmutex",
- srcs = ["tmutex.go"],
- importpath = "gvisor.dev/gvisor/pkg/tmutex",
- visibility = ["//:sandbox"],
-)
-
-go_test(
- name = "tmutex_test",
- size = "medium",
- srcs = ["tmutex_test.go"],
- embed = [":tmutex"],
-)
diff --git a/pkg/tmutex/tmutex_state_autogen.go b/pkg/tmutex/tmutex_state_autogen.go
new file mode 100755
index 000000000..2b2bb599e
--- /dev/null
+++ b/pkg/tmutex/tmutex_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package tmutex
+
diff --git a/pkg/tmutex/tmutex_test.go b/pkg/tmutex/tmutex_test.go
deleted file mode 100644
index ce34c7962..000000000
--- a/pkg/tmutex/tmutex_test.go
+++ /dev/null
@@ -1,257 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tmutex
-
-import (
- "fmt"
- "runtime"
- "sync"
- "sync/atomic"
- "testing"
- "time"
-)
-
-func TestBasicLock(t *testing.T) {
- var m Mutex
- m.Init()
-
- m.Lock()
-
- // Try blocking lock the mutex from a different goroutine. This must
- // not block because the mutex is held.
- ch := make(chan struct{}, 1)
- go func() {
- m.Lock()
- ch <- struct{}{}
- m.Unlock()
- ch <- struct{}{}
- }()
-
- select {
- case <-ch:
- t.Fatalf("Lock succeeded on locked mutex")
- case <-time.After(100 * time.Millisecond):
- }
-
- // Unlock the mutex and make sure that the goroutine waiting on Lock()
- // unblocks and succeeds.
- m.Unlock()
-
- select {
- case <-ch:
- case <-time.After(100 * time.Millisecond):
- t.Fatalf("Lock failed to acquire unlocked mutex")
- }
-
- // Make sure we can lock and unlock again.
- m.Lock()
- m.Unlock()
-}
-
-func TestTryLock(t *testing.T) {
- var m Mutex
- m.Init()
-
- // Try to lock. It should succeed.
- if !m.TryLock() {
- t.Fatalf("TryLock failed on unlocked mutex")
- }
-
- // Try to lock again, it should now fail.
- if m.TryLock() {
- t.Fatalf("TryLock succeeded on locked mutex")
- }
-
- // Try blocking lock the mutex from a different goroutine. This must
- // not block because the mutex is held.
- ch := make(chan struct{}, 1)
- go func() {
- m.Lock()
- ch <- struct{}{}
- m.Unlock()
- }()
-
- select {
- case <-ch:
- t.Fatalf("Lock succeeded on locked mutex")
- case <-time.After(100 * time.Millisecond):
- }
-
- // Unlock the mutex and make sure that the goroutine waiting on Lock()
- // unblocks and succeeds.
- m.Unlock()
-
- select {
- case <-ch:
- case <-time.After(100 * time.Millisecond):
- t.Fatalf("Lock failed to acquire unlocked mutex")
- }
-}
-
-func TestMutualExclusion(t *testing.T) {
- var m Mutex
- m.Init()
-
- // Test mutual exclusion by running "gr" goroutines concurrently, and
- // have each one increment a counter "iters" times within the critical
- // section established by the mutex.
- //
- // If at the end the counter is not gr * iters, then we know that
- // goroutines ran concurrently within the critical section.
- //
- // If one of the goroutines doesn't complete, it's likely a bug that
- // causes to it to wait forever.
- const gr = 1000
- const iters = 100000
- v := 0
- var wg sync.WaitGroup
- for i := 0; i < gr; i++ {
- wg.Add(1)
- go func() {
- for j := 0; j < iters; j++ {
- m.Lock()
- v++
- m.Unlock()
- }
- wg.Done()
- }()
- }
-
- wg.Wait()
-
- if v != gr*iters {
- t.Fatalf("Bad count: got %v, want %v", v, gr*iters)
- }
-}
-
-func TestMutualExclusionWithTryLock(t *testing.T) {
- var m Mutex
- m.Init()
-
- // Similar to the previous, with the addition of some goroutines that
- // only increment the count if TryLock succeeds.
- const gr = 1000
- const iters = 100000
- total := int64(gr * iters)
- var tryTotal int64
- v := int64(0)
- var wg sync.WaitGroup
- for i := 0; i < gr; i++ {
- wg.Add(2)
- go func() {
- for j := 0; j < iters; j++ {
- m.Lock()
- v++
- m.Unlock()
- }
- wg.Done()
- }()
- go func() {
- local := int64(0)
- for j := 0; j < iters; j++ {
- if m.TryLock() {
- v++
- m.Unlock()
- local++
- }
- }
- atomic.AddInt64(&tryTotal, local)
- wg.Done()
- }()
- }
-
- wg.Wait()
-
- t.Logf("tryTotal = %d", tryTotal)
- total += tryTotal
-
- if v != total {
- t.Fatalf("Bad count: got %v, want %v", v, total)
- }
-}
-
-// BenchmarkTmutex is equivalent to TestMutualExclusion, with the following
-// differences:
-//
-// - The number of goroutines is variable, with the maximum value depending on
-// GOMAXPROCS.
-//
-// - The number of iterations per benchmark is controlled by the benchmarking
-// framework.
-//
-// - Care is taken to ensure that all goroutines participating in the benchmark
-// have been created before the benchmark begins.
-func BenchmarkTmutex(b *testing.B) {
- for n, max := 1, 4*runtime.GOMAXPROCS(0); n > 0 && n <= max; n *= 2 {
- b.Run(fmt.Sprintf("%d", n), func(b *testing.B) {
- var m Mutex
- m.Init()
-
- var ready sync.WaitGroup
- begin := make(chan struct{})
- var end sync.WaitGroup
- for i := 0; i < n; i++ {
- ready.Add(1)
- end.Add(1)
- go func() {
- ready.Done()
- <-begin
- for j := 0; j < b.N; j++ {
- m.Lock()
- m.Unlock()
- }
- end.Done()
- }()
- }
-
- ready.Wait()
- b.ResetTimer()
- close(begin)
- end.Wait()
- })
- }
-}
-
-// BenchmarkSyncMutex is equivalent to BenchmarkTmutex, but uses sync.Mutex as
-// a comparison point.
-func BenchmarkSyncMutex(b *testing.B) {
- for n, max := 1, 4*runtime.GOMAXPROCS(0); n > 0 && n <= max; n *= 2 {
- b.Run(fmt.Sprintf("%d", n), func(b *testing.B) {
- var m sync.Mutex
-
- var ready sync.WaitGroup
- begin := make(chan struct{})
- var end sync.WaitGroup
- for i := 0; i < n; i++ {
- ready.Add(1)
- end.Add(1)
- go func() {
- ready.Done()
- <-begin
- for j := 0; j < b.N; j++ {
- m.Lock()
- m.Unlock()
- }
- end.Done()
- }()
- }
-
- ready.Wait()
- b.ResetTimer()
- close(begin)
- end.Wait()
- })
- }
-}
diff --git a/pkg/unet/BUILD b/pkg/unet/BUILD
deleted file mode 100644
index 8f6f180e5..000000000
--- a/pkg/unet/BUILD
+++ /dev/null
@@ -1,27 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "unet",
- srcs = [
- "unet.go",
- "unet_unsafe.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/unet",
- visibility = ["//visibility:public"],
- deps = [
- "//pkg/gate",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-go_test(
- name = "unet_test",
- size = "small",
- srcs = [
- "unet_test.go",
- ],
- embed = [":unet"],
-)
diff --git a/pkg/unet/unet_state_autogen.go b/pkg/unet/unet_state_autogen.go
new file mode 100755
index 000000000..1f7c7fa59
--- /dev/null
+++ b/pkg/unet/unet_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package unet
+
diff --git a/pkg/unet/unet_test.go b/pkg/unet/unet_test.go
deleted file mode 100644
index a3cc6f5d3..000000000
--- a/pkg/unet/unet_test.go
+++ /dev/null
@@ -1,735 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package unet
-
-import (
- "io/ioutil"
- "os"
- "path/filepath"
- "reflect"
- "sync"
- "syscall"
- "testing"
- "time"
-)
-
-func randomFilename() (string, error) {
- // Return a randomly generated file in the test dir.
- f, err := ioutil.TempFile("", "unet-test")
- if err != nil {
- return "", err
- }
- file := f.Name()
- os.Remove(file)
- f.Close()
-
- cwd, err := os.Getwd()
- if err != nil {
- return "", err
- }
-
- // NOTE(b/26918832): We try to use relative path if possible. This is
- // to help conforming to the unix path length limit.
- if rel, err := filepath.Rel(cwd, file); err == nil {
- return rel, nil
- }
-
- return file, nil
-}
-
-func TestConnectFailure(t *testing.T) {
- name, err := randomFilename()
- if err != nil {
- t.Fatalf("unable to generate file, got err %v expected nil", err)
- }
-
- if _, err := Connect(name, false); err == nil {
- t.Fatalf("connect was successful, expected err")
- }
-}
-
-func TestBindFailure(t *testing.T) {
- name, err := randomFilename()
- if err != nil {
- t.Fatalf("unable to generate file, got err %v expected nil", err)
- }
-
- ss, err := BindAndListen(name, false)
- if err != nil {
- t.Fatalf("first bind failed, got err %v expected nil", err)
- }
- defer ss.Close()
-
- if _, err = BindAndListen(name, false); err == nil {
- t.Fatalf("second bind succeeded, expected non-nil err")
- }
-}
-
-func TestMultipleAccept(t *testing.T) {
- name, err := randomFilename()
- if err != nil {
- t.Fatalf("unable to generate file, got err %v expected nil", err)
- }
-
- ss, err := BindAndListen(name, false)
- if err != nil {
- t.Fatalf("first bind failed, got err %v expected nil", err)
- }
- defer ss.Close()
-
- // Connect backlog times asynchronously.
- var wg sync.WaitGroup
- defer wg.Wait()
- for i := 0; i < backlog; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- s, err := Connect(name, false)
- if err != nil {
- t.Fatalf("connect failed, got err %v expected nil", err)
- }
- s.Close()
- }()
- }
-
- // Accept backlog times.
- for i := 0; i < backlog; i++ {
- s, err := ss.Accept()
- if err != nil {
- t.Errorf("accept failed, got err %v expected nil", err)
- continue
- }
- s.Close()
- }
-}
-
-func TestServerClose(t *testing.T) {
- name, err := randomFilename()
- if err != nil {
- t.Fatalf("unable to generate file, got err %v expected nil", err)
- }
-
- ss, err := BindAndListen(name, false)
- if err != nil {
- t.Fatalf("first bind failed, got err %v expected nil", err)
- }
-
- // Make sure the first close succeeds.
- if err := ss.Close(); err != nil {
- t.Fatalf("first close failed, got err %v expected nil", err)
- }
-
- // The second one should fail.
- if err := ss.Close(); err == nil {
- t.Fatalf("second close succeeded, expected non-nil err")
- }
-}
-
-func socketPair(t *testing.T, packet bool) (*Socket, *Socket) {
- name, err := randomFilename()
- if err != nil {
- t.Fatalf("unable to generate file, got err %v expected nil", err)
- }
-
- // Bind a server.
- ss, err := BindAndListen(name, packet)
- if err != nil {
- t.Fatalf("error binding, got %v expected nil", err)
- }
- defer ss.Close()
-
- // Accept a client.
- acceptSocket := make(chan *Socket)
- acceptErr := make(chan error)
- go func() {
- server, err := ss.Accept()
- if err != nil {
- acceptErr <- err
- }
- acceptSocket <- server
- }()
-
- // Connect the client.
- client, err := Connect(name, packet)
- if err != nil {
- t.Fatalf("error connecting, got %v expected nil", err)
- }
-
- // Grab the server handle.
- select {
- case server := <-acceptSocket:
- return server, client
- case err := <-acceptErr:
- t.Fatalf("accept error: %v", err)
- }
- panic("unreachable")
-}
-
-func TestSendRecv(t *testing.T) {
- server, client := socketPair(t, false)
- defer server.Close()
- defer client.Close()
-
- // Write on the client.
- w := client.Writer(true)
- if n, err := w.WriteVec([][]byte{{'a'}}); n != 1 || err != nil {
- t.Fatalf("for client write, got n=%d err=%v, expected n=1 err=nil", n, err)
- }
-
- // Read on the server.
- b := [][]byte{{'b'}}
- r := server.Reader(true)
- if n, err := r.ReadVec(b); n != 1 || err != nil {
- t.Fatalf("for server read, got n=%d err=%v, expected n=1 err=nil", n, err)
- }
- if b[0][0] != 'a' {
- t.Fatalf("got bad read data, got %c, expected a", b[0][0])
- }
-}
-
-// TestSymmetric exists to assert that the two sockets received from socketPair
-// are interchangeable. They should be, this just provides a basic sanity check
-// by running TestSendRecv "backwards".
-func TestSymmetric(t *testing.T) {
- server, client := socketPair(t, false)
- defer server.Close()
- defer client.Close()
-
- // Write on the server.
- w := server.Writer(true)
- if n, err := w.WriteVec([][]byte{{'a'}}); n != 1 || err != nil {
- t.Fatalf("for server write, got n=%d err=%v, expected n=1 err=nil", n, err)
- }
-
- // Read on the client.
- b := [][]byte{{'b'}}
- r := client.Reader(true)
- if n, err := r.ReadVec(b); n != 1 || err != nil {
- t.Fatalf("for client read, got n=%d err=%v, expected n=1 err=nil", n, err)
- }
- if b[0][0] != 'a' {
- t.Fatalf("got bad read data, got %c, expected a", b[0][0])
- }
-}
-
-func TestPacket(t *testing.T) {
- server, client := socketPair(t, true)
- defer server.Close()
- defer client.Close()
-
- // Write on the client.
- w := client.Writer(true)
- if n, err := w.WriteVec([][]byte{{'a'}}); n != 1 || err != nil {
- t.Fatalf("for client write, got n=%d err=%v, expected n=1 err=nil", n, err)
- }
-
- // Write on the client again.
- w = client.Writer(true)
- if n, err := w.WriteVec([][]byte{{'a'}}); n != 1 || err != nil {
- t.Fatalf("for client write, got n=%d err=%v, expected n=1 err=nil", n, err)
- }
-
- // Read on the server.
- //
- // This should only get back a single byte, despite the buffer
- // being size two. This is because it's a _packet_ buffer.
- b := [][]byte{{'b', 'b'}}
- r := server.Reader(true)
- if n, err := r.ReadVec(b); n != 1 || err != nil {
- t.Fatalf("for server read, got n=%d err=%v, expected n=1 err=nil", n, err)
- }
- if b[0][0] != 'a' {
- t.Fatalf("got bad read data, got %c, expected a", b[0][0])
- }
-
- // Do it again.
- r = server.Reader(true)
- if n, err := r.ReadVec(b); n != 1 || err != nil {
- t.Fatalf("for server read, got n=%d err=%v, expected n=1 err=nil", n, err)
- }
- if b[0][0] != 'a' {
- t.Fatalf("got bad read data, got %c, expected a", b[0][0])
- }
-}
-
-func TestClose(t *testing.T) {
- server, client := socketPair(t, false)
- defer server.Close()
-
- // Make sure the first close succeeds.
- if err := client.Close(); err != nil {
- t.Fatalf("first close failed, got err %v expected nil", err)
- }
-
- // The second one should fail.
- if err := client.Close(); err == nil {
- t.Fatalf("second close succeeded, expected non-nil err")
- }
-}
-
-func TestNonBlockingSend(t *testing.T) {
- server, client := socketPair(t, false)
- defer server.Close()
- defer client.Close()
-
- // Try up to 1000 writes, of 1000 bytes.
- blockCount := 0
- for i := 0; i < 1000; i++ {
- w := client.Writer(false)
- if n, err := w.WriteVec([][]byte{make([]byte, 1000)}); n != 1000 || err != nil {
- if err == syscall.EWOULDBLOCK || err == syscall.EAGAIN {
- // We're good. That's what we wanted.
- blockCount++
- } else {
- t.Fatalf("for client write, got n=%d err=%v, expected n=1000 err=nil", n, err)
- }
- }
- }
-
- if blockCount == 1000 {
- // Shouldn't have _always_ blocked.
- t.Fatalf("socket always blocked!")
- } else if blockCount == 0 {
- // Should have started blocking eventually.
- t.Fatalf("socket never blocked!")
- }
-}
-
-func TestNonBlockingRecv(t *testing.T) {
- server, client := socketPair(t, false)
- defer server.Close()
- defer client.Close()
-
- b := [][]byte{{'b'}}
- r := client.Reader(false)
-
- // Expected to block immediately.
- _, err := r.ReadVec(b)
- if err != syscall.EWOULDBLOCK && err != syscall.EAGAIN {
- t.Fatalf("read didn't block, got err %v expected blocking err", err)
- }
-
- // Put some data in the pipe.
- w := server.Writer(false)
- if n, err := w.WriteVec(b); n != 1 || err != nil {
- t.Fatalf("write failed with n=%d err=%v, expected n=1 err=nil", n, err)
- }
-
- // Expect it not to block.
- if n, err := r.ReadVec(b); n != 1 || err != nil {
- t.Fatalf("read failed with n=%d err=%v, expected n=1 err=nil", n, err)
- }
-
- // Expect it to return a block error again.
- r = client.Reader(false)
- _, err = r.ReadVec(b)
- if err != syscall.EWOULDBLOCK && err != syscall.EAGAIN {
- t.Fatalf("read didn't block, got err %v expected blocking err", err)
- }
-}
-
-func TestRecvVectors(t *testing.T) {
- server, client := socketPair(t, false)
- defer server.Close()
- defer client.Close()
-
- // Write on the client.
- w := client.Writer(true)
- if n, err := w.WriteVec([][]byte{{'a', 'b'}}); n != 2 || err != nil {
- t.Fatalf("for client write, got n=%d err=%v, expected n=2 err=nil", n, err)
- }
-
- // Read on the server.
- b := [][]byte{{'c'}, {'c'}}
- r := server.Reader(true)
- if n, err := r.ReadVec(b); n != 2 || err != nil {
- t.Fatalf("for server read, got n=%d err=%v, expected n=2 err=nil", n, err)
- }
- if b[0][0] != 'a' || b[1][0] != 'b' {
- t.Fatalf("got bad read data, got %c,%c, expected a,b", b[0][0], b[1][0])
- }
-}
-
-func TestSendVectors(t *testing.T) {
- server, client := socketPair(t, false)
- defer server.Close()
- defer client.Close()
-
- // Write on the client.
- w := client.Writer(true)
- if n, err := w.WriteVec([][]byte{{'a'}, {'b'}}); n != 2 || err != nil {
- t.Fatalf("for client write, got n=%d err=%v, expected n=2 err=nil", n, err)
- }
-
- // Read on the server.
- b := [][]byte{{'c', 'c'}}
- r := server.Reader(true)
- if n, err := r.ReadVec(b); n != 2 || err != nil {
- t.Fatalf("for server read, got n=%d err=%v, expected n=2 err=nil", n, err)
- }
- if b[0][0] != 'a' || b[0][1] != 'b' {
- t.Fatalf("got bad read data, got %c,%c, expected a,b", b[0][0], b[0][1])
- }
-}
-
-func TestSendFDsNotEnabled(t *testing.T) {
- server, client := socketPair(t, false)
- defer server.Close()
- defer client.Close()
-
- // Write on the server.
- w := server.Writer(true)
- w.PackFDs(0, 1, 2)
- if n, err := w.WriteVec([][]byte{{'a'}}); n != 1 || err != nil {
- t.Fatalf("for server write, got n=%d err=%v, expected n=1 err=nil", n, err)
- }
-
- // Read on the client, without enabling FDs.
- b := [][]byte{{'b'}}
- r := client.Reader(true)
- if n, err := r.ReadVec(b); n != 1 || err != nil {
- t.Fatalf("for client read, got n=%d err=%v, expected n=1 err=nil", n, err)
- }
- if b[0][0] != 'a' {
- t.Fatalf("got bad read data, got %c, expected a", b[0][0])
- }
-
- // Make sure the FDs are not received.
- fds, err := r.ExtractFDs()
- if len(fds) != 0 || err != nil {
- t.Fatalf("got fds=%v err=%v, expected len(fds)=0 err=nil", fds, err)
- }
-}
-
-func sendFDs(t *testing.T, s *Socket, fds []int) {
- w := s.Writer(true)
- w.PackFDs(fds...)
- if n, err := w.WriteVec([][]byte{{'a'}}); n != 1 || err != nil {
- t.Fatalf("for write, got n=%d err=%v, expected n=1 err=nil", n, err)
- }
-}
-
-func recvFDs(t *testing.T, s *Socket, enableSize int, origFDs []int) {
- expected := len(origFDs)
-
- // Count the number of FDs.
- preEntries, err := ioutil.ReadDir("/proc/self/fd")
- if err != nil {
- t.Fatalf("can't readdir, got err %v expected nil", err)
- }
-
- // Read on the client.
- b := [][]byte{{'b'}}
- r := s.Reader(true)
- if enableSize >= 0 {
- r.EnableFDs(enableSize)
- }
- if n, err := r.ReadVec(b); n != 1 || err != nil {
- t.Fatalf("for client read, got n=%d err=%v, expected n=1 err=nil", n, err)
- }
- if b[0][0] != 'a' {
- t.Fatalf("got bad read data, got %c, expected a", b[0][0])
- }
-
- // Count the new number of FDs.
- postEntries, err := ioutil.ReadDir("/proc/self/fd")
- if err != nil {
- t.Fatalf("can't readdir, got err %v expected nil", err)
- }
- if len(preEntries)+expected != len(postEntries) {
- t.Errorf("process fd count isn't right, expected %d got %d", len(preEntries)+expected, len(postEntries))
- }
-
- // Make sure the FDs are there.
- fds, err := r.ExtractFDs()
- if len(fds) != expected || err != nil {
- t.Fatalf("got fds=%v err=%v, expected len(fds)=%d err=nil", fds, err, expected)
- }
-
- // Make sure they are different from the originals.
- for i := 0; i < len(fds); i++ {
- if fds[i] == origFDs[i] {
- t.Errorf("got original fd for index %d, expected different", i)
- }
- }
-
- // Make sure they can be accessed as expected.
- for i := 0; i < len(fds); i++ {
- var st syscall.Stat_t
- if err := syscall.Fstat(fds[i], &st); err != nil {
- t.Errorf("fds[%d] can't be stated, got err %v expected nil", i, err)
- }
- }
-
- // Close them off.
- r.CloseFDs()
-
- // Make sure the count is back to normal.
- finalEntries, err := ioutil.ReadDir("/proc/self/fd")
- if err != nil {
- t.Fatalf("can't readdir, got err %v expected nil", err)
- }
- if len(finalEntries) != len(preEntries) {
- t.Errorf("process fd count isn't right, expected %d got %d", len(preEntries), len(finalEntries))
- }
-}
-
-func TestFDsSingle(t *testing.T) {
- server, client := socketPair(t, false)
- defer server.Close()
- defer client.Close()
-
- sendFDs(t, server, []int{0})
- recvFDs(t, client, 1, []int{0})
-}
-
-func TestFDsMultiple(t *testing.T) {
- server, client := socketPair(t, false)
- defer server.Close()
- defer client.Close()
-
- // Basic case, multiple FDs.
- sendFDs(t, server, []int{0, 1, 2})
- recvFDs(t, client, 3, []int{0, 1, 2})
-}
-
-// See TestSymmetric above.
-func TestFDsSymmetric(t *testing.T) {
- server, client := socketPair(t, false)
- defer server.Close()
- defer client.Close()
-
- sendFDs(t, server, []int{0, 1, 2})
- recvFDs(t, client, 3, []int{0, 1, 2})
-}
-
-func TestFDsReceiveLargeBuffer(t *testing.T) {
- server, client := socketPair(t, false)
- defer server.Close()
- defer client.Close()
-
- sendFDs(t, server, []int{0})
- recvFDs(t, client, 3, []int{0})
-}
-
-func TestFDsReceiveSmallBuffer(t *testing.T) {
- server, client := socketPair(t, false)
- defer server.Close()
- defer client.Close()
-
- sendFDs(t, server, []int{0, 1, 2})
-
- // Per the spec, we may still receive more than the buffer. In fact,
- // it'll be rounded up and we can receive two with a size one buffer.
- recvFDs(t, client, 1, []int{0, 1})
-}
-
-func TestFDsReceiveNotEnabled(t *testing.T) {
- server, client := socketPair(t, false)
- defer server.Close()
- defer client.Close()
-
- sendFDs(t, server, []int{0})
- recvFDs(t, client, -1, []int{})
-}
-
-func TestFDsReceiveSizeZero(t *testing.T) {
- server, client := socketPair(t, false)
- defer server.Close()
- defer client.Close()
-
- sendFDs(t, server, []int{0})
- recvFDs(t, client, 0, []int{})
-}
-
-func TestGetPeerCred(t *testing.T) {
- server, client := socketPair(t, false)
- defer server.Close()
- defer client.Close()
-
- want := &syscall.Ucred{
- Pid: int32(os.Getpid()),
- Uid: uint32(os.Getuid()),
- Gid: uint32(os.Getgid()),
- }
-
- if got, err := client.GetPeerCred(); err != nil || !reflect.DeepEqual(got, want) {
- t.Errorf("got GetPeerCred() = %v, %v, want = %+v, %+v", got, err, want, nil)
- }
-}
-
-func newClosedSocket() (*Socket, error) {
- fd, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
- if err != nil {
- return nil, err
- }
-
- s, err := NewSocket(fd)
- if err != nil {
- syscall.Close(fd)
- return nil, err
- }
-
- return s, s.Close()
-}
-
-func TestGetPeerCredFailure(t *testing.T) {
- s, err := newClosedSocket()
- if err != nil {
- t.Fatalf("newClosedSocket got error %v want nil", err)
- }
-
- want := "bad file descriptor"
- if _, err := s.GetPeerCred(); err == nil || err.Error() != want {
- t.Errorf("got s.GetPeerCred() = %v, want = %s", err, want)
- }
-}
-
-func TestAcceptClosed(t *testing.T) {
- name, err := randomFilename()
- if err != nil {
- t.Fatalf("unable to generate file, got err %v expected nil", err)
- }
-
- ss, err := BindAndListen(name, false)
- if err != nil {
- t.Fatalf("bind failed, got err %v expected nil", err)
- }
-
- if err := ss.Close(); err != nil {
- t.Fatalf("close failed, got err %v expected nil", err)
- }
-
- if _, err := ss.Accept(); err == nil {
- t.Errorf("accept on closed SocketServer, got err %v, want != nil", err)
- }
-}
-
-func TestCloseAfterAcceptStart(t *testing.T) {
- name, err := randomFilename()
- if err != nil {
- t.Fatalf("unable to generate file, got err %v expected nil", err)
- }
-
- ss, err := BindAndListen(name, false)
- if err != nil {
- t.Fatalf("bind failed, got err %v expected nil", err)
- }
-
- wg := sync.WaitGroup{}
- wg.Add(1)
- go func() {
- time.Sleep(50 * time.Millisecond)
- if err := ss.Close(); err != nil {
- t.Fatalf("close failed, got err %v expected nil", err)
- }
- wg.Done()
- }()
-
- if _, err := ss.Accept(); err == nil {
- t.Errorf("accept on closed SocketServer, got err %v, want != nil", err)
- }
-
- wg.Wait()
-}
-
-func TestReleaseAfterAcceptStart(t *testing.T) {
- name, err := randomFilename()
- if err != nil {
- t.Fatalf("unable to generate file, got err %v expected nil", err)
- }
-
- ss, err := BindAndListen(name, false)
- if err != nil {
- t.Fatalf("bind failed, got err %v expected nil", err)
- }
-
- wg := sync.WaitGroup{}
- wg.Add(1)
- go func() {
- time.Sleep(50 * time.Millisecond)
- fd, err := ss.Release()
- if err != nil {
- t.Fatalf("Release failed, got err %v expected nil", err)
- }
- syscall.Close(fd)
- wg.Done()
- }()
-
- if _, err := ss.Accept(); err == nil {
- t.Errorf("accept on closed SocketServer, got err %v, want != nil", err)
- }
-
- wg.Wait()
-}
-
-func TestControlMessage(t *testing.T) {
- for i := 0; i <= 10; i++ {
- var want []int
- for j := 0; j < i; j++ {
- want = append(want, i+j+1)
- }
-
- var cm ControlMessage
- cm.EnableFDs(i)
- cm.PackFDs(want...)
- got, err := cm.ExtractFDs()
- if err != nil || !reflect.DeepEqual(got, want) {
- t.Errorf("got cm.ExtractFDs() = %v, %v, want = %v, %v", got, err, want, nil)
- }
- }
-}
-
-func benchmarkSendRecv(b *testing.B, packet bool) {
- server, client, err := SocketPair(packet)
- if err != nil {
- b.Fatalf("SocketPair: got %v, wanted nil", err)
- }
- defer server.Close()
- defer client.Close()
- go func() {
- buf := make([]byte, 1)
- for i := 0; i < b.N; i++ {
- n, err := server.Read(buf)
- if n != 1 || err != nil {
- b.Fatalf("server.Read: got (%d, %v), wanted (1, nil)", n, err)
- }
- n, err = server.Write(buf)
- if n != 1 || err != nil {
- b.Fatalf("server.Write: got (%d, %v), wanted (1, nil)", n, err)
- }
- }
- }()
- buf := make([]byte, 1)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- n, err := client.Write(buf)
- if n != 1 || err != nil {
- b.Fatalf("client.Write: got (%d, %v), wanted (1, nil)", n, err)
- }
- n, err = client.Read(buf)
- if n != 1 || err != nil {
- b.Fatalf("client.Read: got (%d, %v), wanted (1, nil)", n, err)
- }
- }
-}
-
-func BenchmarkSendRecvStream(b *testing.B) {
- benchmarkSendRecv(b, false)
-}
-
-func BenchmarkSendRecvPacket(b *testing.B) {
- benchmarkSendRecv(b, true)
-}
diff --git a/pkg/urpc/BUILD b/pkg/urpc/BUILD
deleted file mode 100644
index b6bbb0ea2..000000000
--- a/pkg/urpc/BUILD
+++ /dev/null
@@ -1,24 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "urpc",
- srcs = ["urpc.go"],
- importpath = "gvisor.dev/gvisor/pkg/urpc",
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/fd",
- "//pkg/log",
- "//pkg/unet",
- ],
-)
-
-go_test(
- name = "urpc_test",
- size = "small",
- srcs = ["urpc_test.go"],
- embed = [":urpc"],
- deps = ["//pkg/unet"],
-)
diff --git a/pkg/urpc/urpc_state_autogen.go b/pkg/urpc/urpc_state_autogen.go
new file mode 100755
index 000000000..01bf2172f
--- /dev/null
+++ b/pkg/urpc/urpc_state_autogen.go
@@ -0,0 +1,4 @@
+// automatically generated by stateify.
+
+package urpc
+
diff --git a/pkg/urpc/urpc_test.go b/pkg/urpc/urpc_test.go
deleted file mode 100644
index c6c7ce9d4..000000000
--- a/pkg/urpc/urpc_test.go
+++ /dev/null
@@ -1,210 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package urpc
-
-import (
- "errors"
- "os"
- "testing"
-
- "gvisor.dev/gvisor/pkg/unet"
-)
-
-type test struct {
-}
-
-type testArg struct {
- StringArg string
- IntArg int
- FilePayload
-}
-
-type testResult struct {
- StringResult string
- IntResult int
- FilePayload
-}
-
-func (t test) Func(a *testArg, r *testResult) error {
- r.StringResult = a.StringArg
- r.IntResult = a.IntArg
- return nil
-}
-
-func (t test) Err(a *testArg, r *testResult) error {
- return errors.New("test error")
-}
-
-func (t test) FailNoFile(a *testArg, r *testResult) error {
- if a.Files == nil {
- return errors.New("no file found")
- }
-
- return nil
-}
-
-func (t test) SendFile(a *testArg, r *testResult) error {
- r.Files = []*os.File{os.Stdin, os.Stdout, os.Stderr}
- return nil
-}
-
-func (t test) TooManyFiles(a *testArg, r *testResult) error {
- for i := 0; i <= maxFiles; i++ {
- r.Files = append(r.Files, os.Stdin)
- }
- return nil
-}
-
-func startServer(socket *unet.Socket) {
- s := NewServer()
- s.Register(test{})
- s.StartHandling(socket)
-}
-
-func testClient() (*Client, error) {
- serverSock, clientSock, err := unet.SocketPair(false)
- if err != nil {
- return nil, err
- }
- startServer(serverSock)
-
- return NewClient(clientSock), nil
-}
-
-func TestCall(t *testing.T) {
- c, err := testClient()
- if err != nil {
- t.Fatalf("error creating test client: %v", err)
- }
- defer c.Close()
-
- var r testResult
- if err := c.Call("test.Func", &testArg{}, &r); err != nil {
- t.Errorf("basic call failed: %v", err)
- } else if r.StringResult != "" || r.IntResult != 0 {
- t.Errorf("unexpected result, got %v expected zero value", r)
- }
- if err := c.Call("test.Func", &testArg{StringArg: "hello"}, &r); err != nil {
- t.Errorf("basic call failed: %v", err)
- } else if r.StringResult != "hello" {
- t.Errorf("unexpected result, got %v expected hello", r.StringResult)
- }
- if err := c.Call("test.Func", &testArg{IntArg: 1}, &r); err != nil {
- t.Errorf("basic call failed: %v", err)
- } else if r.IntResult != 1 {
- t.Errorf("unexpected result, got %v expected 1", r.IntResult)
- }
-}
-
-func TestUnknownMethod(t *testing.T) {
- c, err := testClient()
- if err != nil {
- t.Fatalf("error creating test client: %v", err)
- }
- defer c.Close()
-
- var r testResult
- if err := c.Call("test.Unknown", &testArg{}, &r); err == nil {
- t.Errorf("expected non-nil err, got nil")
- } else if err.Error() != ErrUnknownMethod.Error() {
- t.Errorf("expected test error, got %v", err)
- }
-}
-
-func TestErr(t *testing.T) {
- c, err := testClient()
- if err != nil {
- t.Fatalf("error creating test client: %v", err)
- }
- defer c.Close()
-
- var r testResult
- if err := c.Call("test.Err", &testArg{}, &r); err == nil {
- t.Errorf("expected non-nil err, got nil")
- } else if err.Error() != "test error" {
- t.Errorf("expected test error, got %v", err)
- }
-}
-
-func TestSendFile(t *testing.T) {
- c, err := testClient()
- if err != nil {
- t.Fatalf("error creating test client: %v", err)
- }
- defer c.Close()
-
- var r testResult
- if err := c.Call("test.FailNoFile", &testArg{}, &r); err == nil {
- t.Errorf("expected non-nil err, got nil")
- }
- if err := c.Call("test.FailNoFile", &testArg{FilePayload: FilePayload{Files: []*os.File{os.Stdin, os.Stdout, os.Stdin}}}, &r); err != nil {
- t.Errorf("expected nil err, got %v", err)
- }
-}
-
-func TestRecvFile(t *testing.T) {
- c, err := testClient()
- if err != nil {
- t.Fatalf("error creating test client: %v", err)
- }
- defer c.Close()
-
- var r testResult
- if err := c.Call("test.SendFile", &testArg{}, &r); err != nil {
- t.Errorf("expected nil err, got %v", err)
- }
- if r.Files == nil {
- t.Errorf("expected file, got nil")
- }
-}
-
-func TestShutdown(t *testing.T) {
- serverSock, clientSock, err := unet.SocketPair(false)
- if err != nil {
- t.Fatalf("error creating test client: %v", err)
- }
- clientSock.Close()
-
- s := NewServer()
- if err := s.Handle(serverSock); err == nil {
- t.Errorf("expected non-nil err, got nil")
- }
-}
-
-func TestTooManyFiles(t *testing.T) {
- c, err := testClient()
- if err != nil {
- t.Fatalf("error creating test client: %v", err)
- }
- defer c.Close()
-
- var r testResult
- var a testArg
- for i := 0; i <= maxFiles; i++ {
- a.Files = append(a.Files, os.Stdin)
- }
-
- // Client-side error.
- if err := c.Call("test.Func", &a, &r); err != ErrTooManyFiles {
- t.Errorf("expected ErrTooManyFiles, got %v", err)
- }
-
- // Server-side error.
- if err := c.Call("test.TooManyFiles", &testArg{}, &r); err == nil {
- t.Errorf("expected non-nil err, got nil")
- } else if err.Error() != "too many files" {
- t.Errorf("expected too many files, got %v", err.Error())
- }
-}
diff --git a/pkg/waiter/BUILD b/pkg/waiter/BUILD
deleted file mode 100644
index 8dc88becb..000000000
--- a/pkg/waiter/BUILD
+++ /dev/null
@@ -1,45 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_template_instance(
- name = "waiter_list",
- out = "waiter_list.go",
- package = "waiter",
- prefix = "waiter",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*Entry",
- "Linker": "*Entry",
- },
-)
-
-go_library(
- name = "waiter",
- srcs = [
- "waiter.go",
- "waiter_list.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/waiter",
- visibility = ["//visibility:public"],
-)
-
-go_test(
- name = "waiter_test",
- size = "small",
- srcs = [
- "waiter_test.go",
- ],
- embed = [":waiter"],
-)
-
-filegroup(
- name = "autogen",
- srcs = [
- "waiter_list.go",
- ],
- visibility = ["//:sandbox"],
-)
diff --git a/pkg/waiter/waiter_list.go b/pkg/waiter/waiter_list.go
new file mode 100755
index 000000000..00b304a31
--- /dev/null
+++ b/pkg/waiter/waiter_list.go
@@ -0,0 +1,173 @@
+package waiter
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type waiterElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (waiterElementMapper) linkerFor(elem *Entry) *Entry { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type waiterList struct {
+ head *Entry
+ tail *Entry
+}
+
+// Reset resets list l to the empty state.
+func (l *waiterList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *waiterList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *waiterList) Front() *Entry {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *waiterList) Back() *Entry {
+ return l.tail
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *waiterList) PushFront(e *Entry) {
+ waiterElementMapper{}.linkerFor(e).SetNext(l.head)
+ waiterElementMapper{}.linkerFor(e).SetPrev(nil)
+
+ if l.head != nil {
+ waiterElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *waiterList) PushBack(e *Entry) {
+ waiterElementMapper{}.linkerFor(e).SetNext(nil)
+ waiterElementMapper{}.linkerFor(e).SetPrev(l.tail)
+
+ if l.tail != nil {
+ waiterElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *waiterList) PushBackList(m *waiterList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ waiterElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ waiterElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *waiterList) InsertAfter(b, e *Entry) {
+ a := waiterElementMapper{}.linkerFor(b).Next()
+ waiterElementMapper{}.linkerFor(e).SetNext(a)
+ waiterElementMapper{}.linkerFor(e).SetPrev(b)
+ waiterElementMapper{}.linkerFor(b).SetNext(e)
+
+ if a != nil {
+ waiterElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *waiterList) InsertBefore(a, e *Entry) {
+ b := waiterElementMapper{}.linkerFor(a).Prev()
+ waiterElementMapper{}.linkerFor(e).SetNext(a)
+ waiterElementMapper{}.linkerFor(e).SetPrev(b)
+ waiterElementMapper{}.linkerFor(a).SetPrev(e)
+
+ if b != nil {
+ waiterElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *waiterList) Remove(e *Entry) {
+ prev := waiterElementMapper{}.linkerFor(e).Prev()
+ next := waiterElementMapper{}.linkerFor(e).Next()
+
+ if prev != nil {
+ waiterElementMapper{}.linkerFor(prev).SetNext(next)
+ } else {
+ l.head = next
+ }
+
+ if next != nil {
+ waiterElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else {
+ l.tail = prev
+ }
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type waiterEntry struct {
+ next *Entry
+ prev *Entry
+}
+
+// Next returns the entry that follows e in the list.
+func (e *waiterEntry) Next() *Entry {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *waiterEntry) Prev() *Entry {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *waiterEntry) SetNext(elem *Entry) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *waiterEntry) SetPrev(elem *Entry) {
+ e.prev = elem
+}
diff --git a/pkg/waiter/waiter_state_autogen.go b/pkg/waiter/waiter_state_autogen.go
new file mode 100755
index 000000000..b73646808
--- /dev/null
+++ b/pkg/waiter/waiter_state_autogen.go
@@ -0,0 +1,67 @@
+// automatically generated by stateify.
+
+package waiter
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *Entry) beforeSave() {}
+func (x *Entry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("Context", &x.Context)
+ m.Save("Callback", &x.Callback)
+ m.Save("mask", &x.mask)
+ m.Save("waiterEntry", &x.waiterEntry)
+}
+
+func (x *Entry) afterLoad() {}
+func (x *Entry) load(m state.Map) {
+ m.Load("Context", &x.Context)
+ m.Load("Callback", &x.Callback)
+ m.Load("mask", &x.mask)
+ m.Load("waiterEntry", &x.waiterEntry)
+}
+
+func (x *Queue) beforeSave() {}
+func (x *Queue) save(m state.Map) {
+ x.beforeSave()
+ if !state.IsZeroValue(x.list) { m.Failf("list is %v, expected zero", x.list) }
+}
+
+func (x *Queue) afterLoad() {}
+func (x *Queue) load(m state.Map) {
+}
+
+func (x *waiterList) beforeSave() {}
+func (x *waiterList) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *waiterList) afterLoad() {}
+func (x *waiterList) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *waiterEntry) beforeSave() {}
+func (x *waiterEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *waiterEntry) afterLoad() {}
+func (x *waiterEntry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func init() {
+ state.Register("waiter.Entry", (*Entry)(nil), state.Fns{Save: (*Entry).save, Load: (*Entry).load})
+ state.Register("waiter.Queue", (*Queue)(nil), state.Fns{Save: (*Queue).save, Load: (*Queue).load})
+ state.Register("waiter.waiterList", (*waiterList)(nil), state.Fns{Save: (*waiterList).save, Load: (*waiterList).load})
+ state.Register("waiter.waiterEntry", (*waiterEntry)(nil), state.Fns{Save: (*waiterEntry).save, Load: (*waiterEntry).load})
+}
diff --git a/pkg/waiter/waiter_test.go b/pkg/waiter/waiter_test.go
deleted file mode 100644
index c1b94a4f3..000000000
--- a/pkg/waiter/waiter_test.go
+++ /dev/null
@@ -1,192 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package waiter
-
-import (
- "sync/atomic"
- "testing"
-)
-
-type callbackStub struct {
- f func(e *Entry)
-}
-
-// Callback implements EntryCallback.Callback.
-func (c *callbackStub) Callback(e *Entry) {
- c.f(e)
-}
-
-func TestEmptyQueue(t *testing.T) {
- var q Queue
-
- // Notify the zero-value of a queue.
- q.Notify(EventIn)
-
- // Register then unregister a waiter, then notify the queue.
- cnt := 0
- e := Entry{Callback: &callbackStub{func(*Entry) { cnt++ }}}
- q.EventRegister(&e, EventIn)
- q.EventUnregister(&e)
- q.Notify(EventIn)
- if cnt != 0 {
- t.Errorf("Callback was called when it shouldn't have been")
- }
-}
-
-func TestMask(t *testing.T) {
- // Register a waiter.
- var q Queue
- var cnt int
- e := Entry{Callback: &callbackStub{func(*Entry) { cnt++ }}}
- q.EventRegister(&e, EventIn|EventErr)
-
- // Notify with an overlapping mask.
- cnt = 0
- q.Notify(EventIn | EventOut)
- if cnt != 1 {
- t.Errorf("Callback wasn't called when it should have been")
- }
-
- // Notify with a subset mask.
- cnt = 0
- q.Notify(EventIn)
- if cnt != 1 {
- t.Errorf("Callback wasn't called when it should have been")
- }
-
- // Notify with a superset mask.
- cnt = 0
- q.Notify(EventIn | EventErr | EventOut)
- if cnt != 1 {
- t.Errorf("Callback wasn't called when it should have been")
- }
-
- // Notify with the exact same mask.
- cnt = 0
- q.Notify(EventIn | EventErr)
- if cnt != 1 {
- t.Errorf("Callback wasn't called when it should have been")
- }
-
- // Notify with a disjoint mask.
- cnt = 0
- q.Notify(EventOut | EventHUp)
- if cnt != 0 {
- t.Errorf("Callback was called when it shouldn't have been")
- }
-}
-
-func TestConcurrentRegistration(t *testing.T) {
- var q Queue
- var cnt int
- const concurrency = 1000
-
- ch1 := make(chan struct{})
- ch2 := make(chan struct{})
- ch3 := make(chan struct{})
-
- // Create goroutines that will all register/unregister concurrently.
- for i := 0; i < concurrency; i++ {
- go func() {
- var e Entry
- e.Callback = &callbackStub{func(entry *Entry) {
- cnt++
- if entry != &e {
- t.Errorf("entry = %p, want %p", entry, &e)
- }
- }}
-
- // Wait for notification, then register.
- <-ch1
- q.EventRegister(&e, EventIn|EventErr)
-
- // Tell main goroutine that we're done registering.
- ch2 <- struct{}{}
-
- // Wait for notification, then unregister.
- <-ch3
- q.EventUnregister(&e)
-
- // Tell main goroutine that we're done unregistering.
- ch2 <- struct{}{}
- }()
- }
-
- // Let the goroutines register.
- close(ch1)
- for i := 0; i < concurrency; i++ {
- <-ch2
- }
-
- // Issue a notification.
- q.Notify(EventIn)
- if cnt != concurrency {
- t.Errorf("cnt = %d, want %d", cnt, concurrency)
- }
-
- // Let the goroutine unregister.
- close(ch3)
- for i := 0; i < concurrency; i++ {
- <-ch2
- }
-
- // Issue a notification.
- q.Notify(EventIn)
- if cnt != concurrency {
- t.Errorf("cnt = %d, want %d", cnt, concurrency)
- }
-}
-
-func TestConcurrentNotification(t *testing.T) {
- var q Queue
- var cnt int32
- const concurrency = 1000
- const waiterCount = 1000
-
- // Register waiters.
- for i := 0; i < waiterCount; i++ {
- var e Entry
- e.Callback = &callbackStub{func(entry *Entry) {
- atomic.AddInt32(&cnt, 1)
- if entry != &e {
- t.Errorf("entry = %p, want %p", entry, &e)
- }
- }}
-
- q.EventRegister(&e, EventIn|EventErr)
- }
-
- // Launch notifiers.
- ch1 := make(chan struct{})
- ch2 := make(chan struct{})
- for i := 0; i < concurrency; i++ {
- go func() {
- <-ch1
- q.Notify(EventIn)
- ch2 <- struct{}{}
- }()
- }
-
- // Let notifiers go.
- close(ch1)
- for i := 0; i < concurrency; i++ {
- <-ch2
- }
-
- // Check the count.
- if cnt != concurrency*waiterCount {
- t.Errorf("cnt = %d, want %d", cnt, concurrency*waiterCount)
- }
-}
diff --git a/runsc/BUILD b/runsc/BUILD
deleted file mode 100644
index 5e7dacb87..000000000
--- a/runsc/BUILD
+++ /dev/null
@@ -1,110 +0,0 @@
-package(licenses = ["notice"]) # Apache 2.0
-
-load("@io_bazel_rules_go//go:def.bzl", "go_binary")
-load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_deb", "pkg_tar")
-
-go_binary(
- name = "runsc",
- srcs = [
- "main.go",
- "version.go",
- ],
- pure = "on",
- visibility = [
- "//visibility:public",
- ],
- x_defs = {"main.version": "{STABLE_VERSION}"},
- deps = [
- "//pkg/log",
- "//pkg/refs",
- "//pkg/sentry/platform",
- "//runsc/boot",
- "//runsc/cmd",
- "//runsc/specutils",
- "@com_github_google_subcommands//:go_default_library",
- ],
-)
-
-# The runsc-race target is a race-compatible BUILD target. This must be built
-# via "bazel build --features=race //runsc:runsc-race", since the race feature
-# must apply to all dependencies due a bug in gazelle file selection. The pure
-# attribute must be off because the race detector requires linking with non-Go
-# components, although we still require a static binary.
-#
-# Note that in the future this might be convertible to a compatible target by
-# using the pure and static attributes within a select function, but select is
-# not currently compatible with string attributes [1].
-#
-# [1] https://github.com/bazelbuild/bazel/issues/1698
-go_binary(
- name = "runsc-race",
- srcs = [
- "main.go",
- "version.go",
- ],
- static = "on",
- visibility = [
- "//visibility:public",
- ],
- x_defs = {"main.version": "{STABLE_VERSION}"},
- deps = [
- "//pkg/log",
- "//pkg/refs",
- "//pkg/sentry/platform",
- "//runsc/boot",
- "//runsc/cmd",
- "//runsc/specutils",
- "@com_github_google_subcommands//:go_default_library",
- ],
-)
-
-pkg_tar(
- name = "runsc-bin",
- srcs = [":runsc"],
- mode = "0755",
- package_dir = "/usr/bin",
- strip_prefix = "/runsc/linux_amd64_pure_stripped",
-)
-
-pkg_tar(
- name = "debian-data",
- extension = "tar.gz",
- deps = [
- ":runsc-bin",
- ],
-)
-
-genrule(
- name = "deb-version",
- outs = ["version.txt"],
- cmd = "$(location :runsc) -version | grep 'runsc version' | sed 's/^[^0-9]*//' > $@",
- stamp = 1,
- tools = [":runsc"],
-)
-
-pkg_deb(
- name = "runsc-debian",
- architecture = "amd64",
- data = ":debian-data",
- description_file = "debian/description",
- homepage = "https://gvisor.dev/",
- maintainer = "The gVisor Authors <gvisor-dev@googlegroups.com>",
- package = "runsc",
- postinst = "debian/postinst.sh",
- tags = [
- # TODO(b/135475885): pkg_deb requires python2:
- # https://github.com/bazelbuild/bazel/issues/8443
- "manual",
- ],
- version_file = ":version.txt",
- visibility = [
- "//visibility:public",
- ],
-)
-
-sh_test(
- name = "version_test",
- size = "small",
- srcs = ["version_test.sh"],
- data = [":runsc"],
-)
diff --git a/runsc/boot/BUILD b/runsc/boot/BUILD
deleted file mode 100644
index 588bb8851..000000000
--- a/runsc/boot/BUILD
+++ /dev/null
@@ -1,116 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "boot",
- srcs = [
- "compat.go",
- "compat_amd64.go",
- "config.go",
- "controller.go",
- "debug.go",
- "events.go",
- "fds.go",
- "fs.go",
- "limits.go",
- "loader.go",
- "network.go",
- "pprof.go",
- "strace.go",
- "user.go",
- ],
- importpath = "gvisor.dev/gvisor/runsc/boot",
- visibility = [
- "//runsc:__subpackages__",
- "//test:__subpackages__",
- ],
- deps = [
- "//pkg/abi",
- "//pkg/abi/linux",
- "//pkg/control/server",
- "//pkg/cpuid",
- "//pkg/eventchannel",
- "//pkg/log",
- "//pkg/memutil",
- "//pkg/rand",
- "//pkg/refs",
- "//pkg/sentry/arch",
- "//pkg/sentry/arch:registers_go_proto",
- "//pkg/sentry/context",
- "//pkg/sentry/control",
- "//pkg/sentry/fs",
- "//pkg/sentry/fs/dev",
- "//pkg/sentry/fs/gofer",
- "//pkg/sentry/fs/host",
- "//pkg/sentry/fs/proc",
- "//pkg/sentry/fs/ramfs",
- "//pkg/sentry/fs/sys",
- "//pkg/sentry/fs/tmpfs",
- "//pkg/sentry/fs/tty",
- "//pkg/sentry/inet",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel:uncaught_signal_go_proto",
- "//pkg/sentry/kernel/auth",
- "//pkg/sentry/limits",
- "//pkg/sentry/loader",
- "//pkg/sentry/pgalloc",
- "//pkg/sentry/platform",
- "//pkg/sentry/sighandling",
- "//pkg/sentry/socket/epsocket",
- "//pkg/sentry/socket/hostinet",
- "//pkg/sentry/socket/netlink",
- "//pkg/sentry/socket/netlink/route",
- "//pkg/sentry/socket/unix",
- "//pkg/sentry/state",
- "//pkg/sentry/strace",
- "//pkg/sentry/syscalls/linux",
- "//pkg/sentry/time",
- "//pkg/sentry/unimpl:unimplemented_syscall_go_proto",
- "//pkg/sentry/usage",
- "//pkg/sentry/usermem",
- "//pkg/sentry/watchdog",
- "//pkg/syserror",
- "//pkg/tcpip",
- "//pkg/tcpip/link/fdbased",
- "//pkg/tcpip/link/loopback",
- "//pkg/tcpip/link/sniffer",
- "//pkg/tcpip/network/arp",
- "//pkg/tcpip/network/ipv4",
- "//pkg/tcpip/network/ipv6",
- "//pkg/tcpip/stack",
- "//pkg/tcpip/transport/icmp",
- "//pkg/tcpip/transport/tcp",
- "//pkg/tcpip/transport/udp",
- "//pkg/urpc",
- "//runsc/boot/filter",
- "//runsc/boot/platforms",
- "//runsc/specutils",
- "@com_github_golang_protobuf//proto:go_default_library",
- "@com_github_opencontainers_runtime-spec//specs-go:go_default_library",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-go_test(
- name = "boot_test",
- size = "small",
- srcs = [
- "compat_test.go",
- "fs_test.go",
- "loader_test.go",
- "user_test.go",
- ],
- embed = [":boot"],
- deps = [
- "//pkg/control/server",
- "//pkg/log",
- "//pkg/p9",
- "//pkg/sentry/arch:registers_go_proto",
- "//pkg/sentry/context/contexttest",
- "//pkg/sentry/fs",
- "//pkg/unet",
- "//runsc/fsgofer",
- "@com_github_opencontainers_runtime-spec//specs-go:go_default_library",
- ],
-)
diff --git a/runsc/boot/compat_test.go b/runsc/boot/compat_test.go
deleted file mode 100644
index 388298d8d..000000000
--- a/runsc/boot/compat_test.go
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package boot
-
-import (
- "testing"
-
- rpb "gvisor.dev/gvisor/pkg/sentry/arch/registers_go_proto"
-)
-
-func TestOnceTracker(t *testing.T) {
- o := onceTracker{}
- if !o.shouldReport(nil) {
- t.Error("first call to checkAndMark, got: false, want: true")
- }
- o.onReported(nil)
- for i := 0; i < 2; i++ {
- if o.shouldReport(nil) {
- t.Error("after first call to checkAndMark, got: true, want: false")
- }
- }
-}
-
-func TestArgsTracker(t *testing.T) {
- for _, tc := range []struct {
- name string
- idx []int
- rdi1 uint64
- rdi2 uint64
- rsi1 uint64
- rsi2 uint64
- want bool
- }{
- {name: "same rdi", idx: []int{0}, rdi1: 123, rdi2: 123, want: false},
- {name: "same rsi", idx: []int{1}, rsi1: 123, rsi2: 123, want: false},
- {name: "diff rdi", idx: []int{0}, rdi1: 123, rdi2: 321, want: true},
- {name: "diff rsi", idx: []int{1}, rsi1: 123, rsi2: 321, want: true},
- {name: "cmd is uint32", idx: []int{0}, rsi1: 0xdead00000123, rsi2: 0xbeef00000123, want: false},
- {name: "same 2 args", idx: []int{0, 1}, rsi1: 123, rdi1: 321, rsi2: 123, rdi2: 321, want: false},
- {name: "diff 2 args", idx: []int{0, 1}, rsi1: 123, rdi1: 321, rsi2: 789, rdi2: 987, want: true},
- } {
- t.Run(tc.name, func(t *testing.T) {
- c := newArgsTracker(tc.idx...)
- regs := &rpb.AMD64Registers{Rdi: tc.rdi1, Rsi: tc.rsi1}
- if !c.shouldReport(regs) {
- t.Error("first call to shouldReport, got: false, want: true")
- }
- c.onReported(regs)
-
- regs.Rdi, regs.Rsi = tc.rdi2, tc.rsi2
- if got := c.shouldReport(regs); tc.want != got {
- t.Errorf("second call to shouldReport, got: %t, want: %t", got, tc.want)
- }
- })
- }
-}
-
-func TestArgsTrackerLimit(t *testing.T) {
- c := newArgsTracker(0, 1)
- for i := 0; i < reportLimit; i++ {
- regs := &rpb.AMD64Registers{Rdi: 123, Rsi: uint64(i)}
- if !c.shouldReport(regs) {
- t.Error("shouldReport before limit was reached, got: false, want: true")
- }
- c.onReported(regs)
- }
-
- // Should hit the count limit now.
- regs := &rpb.AMD64Registers{Rdi: 123, Rsi: 123456}
- if c.shouldReport(regs) {
- t.Error("shouldReport after limit was reached, got: true, want: false")
- }
-}
diff --git a/runsc/boot/filter/BUILD b/runsc/boot/filter/BUILD
deleted file mode 100644
index f5509b6b7..000000000
--- a/runsc/boot/filter/BUILD
+++ /dev/null
@@ -1,26 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "filter",
- srcs = [
- "config.go",
- "extra_filters.go",
- "extra_filters_msan.go",
- "extra_filters_race.go",
- "filter.go",
- ],
- importpath = "gvisor.dev/gvisor/runsc/boot/filter",
- visibility = [
- "//runsc/boot:__subpackages__",
- ],
- deps = [
- "//pkg/abi/linux",
- "//pkg/log",
- "//pkg/seccomp",
- "//pkg/sentry/platform",
- "//pkg/tcpip/link/fdbased",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
diff --git a/runsc/boot/fs_test.go b/runsc/boot/fs_test.go
deleted file mode 100644
index 49ab34b33..000000000
--- a/runsc/boot/fs_test.go
+++ /dev/null
@@ -1,193 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package boot
-
-import (
- "path"
- "reflect"
- "strings"
- "testing"
-
- specs "github.com/opencontainers/runtime-spec/specs-go"
-)
-
-func TestPodMountHintsHappy(t *testing.T) {
- spec := &specs.Spec{
- Annotations: map[string]string{
- path.Join(MountPrefix, "mount1", "source"): "foo",
- path.Join(MountPrefix, "mount1", "type"): "tmpfs",
- path.Join(MountPrefix, "mount1", "share"): "pod",
-
- path.Join(MountPrefix, "mount2", "source"): "bar",
- path.Join(MountPrefix, "mount2", "type"): "bind",
- path.Join(MountPrefix, "mount2", "share"): "container",
- path.Join(MountPrefix, "mount2", "options"): "rw,private",
- },
- }
- podHints, err := newPodMountHints(spec)
- if err != nil {
- t.Errorf("newPodMountHints failed: %v", err)
- }
-
- // Check that fields were set correctly.
- mount1 := podHints.mounts["mount1"]
- if want := "mount1"; want != mount1.name {
- t.Errorf("mount1 name, want: %q, got: %q", want, mount1.name)
- }
- if want := "foo"; want != mount1.mount.Source {
- t.Errorf("mount1 source, want: %q, got: %q", want, mount1.mount.Source)
- }
- if want := "tmpfs"; want != mount1.mount.Type {
- t.Errorf("mount1 type, want: %q, got: %q", want, mount1.mount.Type)
- }
- if want := pod; want != mount1.share {
- t.Errorf("mount1 type, want: %q, got: %q", want, mount1.share)
- }
- if want := []string(nil); !reflect.DeepEqual(want, mount1.mount.Options) {
- t.Errorf("mount1 type, want: %q, got: %q", want, mount1.mount.Options)
- }
-
- mount2 := podHints.mounts["mount2"]
- if want := "mount2"; want != mount2.name {
- t.Errorf("mount2 name, want: %q, got: %q", want, mount2.name)
- }
- if want := "bar"; want != mount2.mount.Source {
- t.Errorf("mount2 source, want: %q, got: %q", want, mount2.mount.Source)
- }
- if want := "bind"; want != mount2.mount.Type {
- t.Errorf("mount2 type, want: %q, got: %q", want, mount2.mount.Type)
- }
- if want := container; want != mount2.share {
- t.Errorf("mount2 type, want: %q, got: %q", want, mount2.share)
- }
- if want := []string{"private", "rw"}; !reflect.DeepEqual(want, mount2.mount.Options) {
- t.Errorf("mount2 type, want: %q, got: %q", want, mount2.mount.Options)
- }
-}
-
-func TestPodMountHintsErrors(t *testing.T) {
- for _, tst := range []struct {
- name string
- annotations map[string]string
- error string
- }{
- {
- name: "too short",
- annotations: map[string]string{
- path.Join(MountPrefix, "mount1"): "foo",
- },
- error: "invalid mount annotation",
- },
- {
- name: "no name",
- annotations: map[string]string{
- MountPrefix + "//source": "foo",
- },
- error: "invalid mount name",
- },
- {
- name: "missing source",
- annotations: map[string]string{
- path.Join(MountPrefix, "mount1", "type"): "tmpfs",
- path.Join(MountPrefix, "mount1", "share"): "pod",
- },
- error: "source field",
- },
- {
- name: "missing type",
- annotations: map[string]string{
- path.Join(MountPrefix, "mount1", "source"): "foo",
- path.Join(MountPrefix, "mount1", "share"): "pod",
- },
- error: "type field",
- },
- {
- name: "missing share",
- annotations: map[string]string{
- path.Join(MountPrefix, "mount1", "source"): "foo",
- path.Join(MountPrefix, "mount1", "type"): "tmpfs",
- },
- error: "share field",
- },
- {
- name: "invalid field name",
- annotations: map[string]string{
- path.Join(MountPrefix, "mount1", "invalid"): "foo",
- },
- error: "invalid mount annotation",
- },
- {
- name: "invalid source",
- annotations: map[string]string{
- path.Join(MountPrefix, "mount1", "source"): "",
- path.Join(MountPrefix, "mount1", "type"): "tmpfs",
- path.Join(MountPrefix, "mount1", "share"): "pod",
- },
- error: "source cannot be empty",
- },
- {
- name: "invalid type",
- annotations: map[string]string{
- path.Join(MountPrefix, "mount1", "source"): "foo",
- path.Join(MountPrefix, "mount1", "type"): "invalid-type",
- path.Join(MountPrefix, "mount1", "share"): "pod",
- },
- error: "invalid type",
- },
- {
- name: "invalid share",
- annotations: map[string]string{
- path.Join(MountPrefix, "mount1", "source"): "foo",
- path.Join(MountPrefix, "mount1", "type"): "tmpfs",
- path.Join(MountPrefix, "mount1", "share"): "invalid-share",
- },
- error: "invalid share",
- },
- {
- name: "invalid options",
- annotations: map[string]string{
- path.Join(MountPrefix, "mount1", "source"): "foo",
- path.Join(MountPrefix, "mount1", "type"): "tmpfs",
- path.Join(MountPrefix, "mount1", "share"): "pod",
- path.Join(MountPrefix, "mount1", "options"): "invalid-option",
- },
- error: "unknown mount option",
- },
- {
- name: "duplicate source",
- annotations: map[string]string{
- path.Join(MountPrefix, "mount1", "source"): "foo",
- path.Join(MountPrefix, "mount1", "type"): "tmpfs",
- path.Join(MountPrefix, "mount1", "share"): "pod",
-
- path.Join(MountPrefix, "mount2", "source"): "foo",
- path.Join(MountPrefix, "mount2", "type"): "bind",
- path.Join(MountPrefix, "mount2", "share"): "container",
- },
- error: "have the same mount source",
- },
- } {
- t.Run(tst.name, func(t *testing.T) {
- spec := &specs.Spec{Annotations: tst.annotations}
- podHints, err := newPodMountHints(spec)
- if err == nil || !strings.Contains(err.Error(), tst.error) {
- t.Errorf("newPodMountHints invalid error, want: .*%s.*, got: %v", tst.error, err)
- }
- if podHints != nil {
- t.Errorf("newPodMountHints must return nil on failure: %+v", podHints)
- }
- })
- }
-}
diff --git a/runsc/boot/loader_test.go b/runsc/boot/loader_test.go
deleted file mode 100644
index 147ff7703..000000000
--- a/runsc/boot/loader_test.go
+++ /dev/null
@@ -1,631 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package boot
-
-import (
- "fmt"
- "math/rand"
- "os"
- "reflect"
- "sync"
- "syscall"
- "testing"
- "time"
-
- specs "github.com/opencontainers/runtime-spec/specs-go"
- "gvisor.dev/gvisor/pkg/control/server"
- "gvisor.dev/gvisor/pkg/log"
- "gvisor.dev/gvisor/pkg/p9"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
- "gvisor.dev/gvisor/pkg/unet"
- "gvisor.dev/gvisor/runsc/fsgofer"
-)
-
-func init() {
- log.SetLevel(log.Debug)
- rand.Seed(time.Now().UnixNano())
- if err := fsgofer.OpenProcSelfFD(); err != nil {
- panic(err)
- }
-}
-
-func testConfig() *Config {
- return &Config{
- RootDir: "unused_root_dir",
- Network: NetworkNone,
- DisableSeccomp: true,
- Platform: "ptrace",
- }
-}
-
-// testSpec returns a simple spec that can be used in tests.
-func testSpec() *specs.Spec {
- return &specs.Spec{
- // The host filesystem root is the sandbox root.
- Root: &specs.Root{
- Path: "/",
- Readonly: true,
- },
- Process: &specs.Process{
- Args: []string{"/bin/true"},
- },
- }
-}
-
-// startGofer starts a new gofer routine serving 'root' path. It returns the
-// sandbox side of the connection, and a function that when called will stop the
-// gofer.
-func startGofer(root string) (int, func(), error) {
- fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
- if err != nil {
- return 0, nil, err
- }
- sandboxEnd, goferEnd := fds[0], fds[1]
-
- socket, err := unet.NewSocket(goferEnd)
- if err != nil {
- syscall.Close(sandboxEnd)
- syscall.Close(goferEnd)
- return 0, nil, fmt.Errorf("error creating server on FD %d: %v", goferEnd, err)
- }
- at, err := fsgofer.NewAttachPoint(root, fsgofer.Config{ROMount: true})
- if err != nil {
- return 0, nil, err
- }
- go func() {
- s := p9.NewServer(at)
- if err := s.Handle(socket); err != nil {
- log.Infof("Gofer is stopping. FD: %d, err: %v\n", goferEnd, err)
- }
- }()
- // Closing the gofer socket will stop the gofer and exit goroutine above.
- cleanup := func() {
- if err := socket.Close(); err != nil {
- log.Warningf("Error closing gofer socket: %v", err)
- }
- }
- return sandboxEnd, cleanup, nil
-}
-
-func createLoader() (*Loader, func(), error) {
- fd, err := server.CreateSocket(ControlSocketAddr(fmt.Sprintf("%010d", rand.Int())[:10]))
- if err != nil {
- return nil, nil, err
- }
- conf := testConfig()
- spec := testSpec()
-
- sandEnd, cleanup, err := startGofer(spec.Root.Path)
- if err != nil {
- return nil, nil, err
- }
-
- stdio := []int{int(os.Stdin.Fd()), int(os.Stdout.Fd()), int(os.Stderr.Fd())}
- args := Args{
- ID: "foo",
- Spec: spec,
- Conf: conf,
- ControllerFD: fd,
- GoferFDs: []int{sandEnd},
- StdioFDs: stdio,
- }
- l, err := New(args)
- if err != nil {
- cleanup()
- return nil, nil, err
- }
- return l, cleanup, nil
-}
-
-// TestRun runs a simple application in a sandbox and checks that it succeeds.
-func TestRun(t *testing.T) {
- l, cleanup, err := createLoader()
- if err != nil {
- t.Fatalf("error creating loader: %v", err)
- }
- defer l.Destroy()
- defer cleanup()
-
- // Start a goroutine to read the start chan result, otherwise Run will
- // block forever.
- var resultChanErr error
- var wg sync.WaitGroup
- wg.Add(1)
- go func() {
- resultChanErr = <-l.ctrl.manager.startResultChan
- wg.Done()
- }()
-
- // Run the container.
- if err := l.Run(); err != nil {
- t.Errorf("error running container: %v", err)
- }
-
- // We should have not gotten an error on the startResultChan.
- wg.Wait()
- if resultChanErr != nil {
- t.Errorf("error on startResultChan: %v", resultChanErr)
- }
-
- // Wait for the application to exit. It should succeed.
- if status := l.WaitExit(); status.Code != 0 || status.Signo != 0 {
- t.Errorf("application exited with status %+v, want 0", status)
- }
-}
-
-// TestStartSignal tests that the controller Start message will cause
-// WaitForStartSignal to return.
-func TestStartSignal(t *testing.T) {
- l, cleanup, err := createLoader()
- if err != nil {
- t.Fatalf("error creating loader: %v", err)
- }
- defer l.Destroy()
- defer cleanup()
-
- // We aren't going to wait on this application, so the control server
- // needs to be shut down manually.
- defer l.ctrl.srv.Stop()
-
- // Start a goroutine that calls WaitForStartSignal and writes to a
- // channel when it returns.
- waitFinished := make(chan struct{})
- go func() {
- l.WaitForStartSignal()
- // Pretend that Run() executed and returned no error.
- l.ctrl.manager.startResultChan <- nil
- waitFinished <- struct{}{}
- }()
-
- // Nothing has been written to the channel, so waitFinished should not
- // return. Give it a little bit of time to make sure the goroutine has
- // started.
- select {
- case <-waitFinished:
- t.Errorf("WaitForStartSignal completed but it should not have")
- case <-time.After(50 * time.Millisecond):
- // OK.
- }
-
- // Trigger the control server StartRoot method.
- cid := "foo"
- if err := l.ctrl.manager.StartRoot(&cid, nil); err != nil {
- t.Errorf("error calling StartRoot: %v", err)
- }
-
- // Now WaitForStartSignal should return (within a short amount of
- // time).
- select {
- case <-waitFinished:
- // OK.
- case <-time.After(50 * time.Millisecond):
- t.Errorf("WaitForStartSignal did not complete but it should have")
- }
-
-}
-
-// Test that MountNamespace can be created with various specs.
-func TestCreateMountNamespace(t *testing.T) {
- testCases := []struct {
- name string
- // Spec that will be used to create the mount manager. Note
- // that we can't mount procfs without a kernel, so each spec
- // MUST contain something other than procfs mounted at /proc.
- spec specs.Spec
- // Paths that are expected to exist in the resulting fs.
- expectedPaths []string
- }{
- {
- // Only proc.
- name: "only proc mount",
- spec: specs.Spec{
- Root: &specs.Root{
- Path: os.TempDir(),
- Readonly: true,
- },
- Mounts: []specs.Mount{
- {
- Destination: "/proc",
- Type: "tmpfs",
- },
- },
- },
- // /proc, /dev, and /sys should always be mounted.
- expectedPaths: []string{"/proc", "/dev", "/sys"},
- },
- {
- // Mount at a deep path, with many components that do
- // not exist in the root.
- name: "deep mount path",
- spec: specs.Spec{
- Root: &specs.Root{
- Path: os.TempDir(),
- Readonly: true,
- },
- Mounts: []specs.Mount{
- {
- Destination: "/some/very/very/deep/path",
- Type: "tmpfs",
- },
- {
- Destination: "/proc",
- Type: "tmpfs",
- },
- },
- },
- // /some/deep/path should be mounted, along with /proc,
- // /dev, and /sys.
- expectedPaths: []string{"/some/very/very/deep/path", "/proc", "/dev", "/sys"},
- },
- {
- // Mounts are nested inside each other.
- name: "nested mounts",
- spec: specs.Spec{
- Root: &specs.Root{
- Path: os.TempDir(),
- Readonly: true,
- },
- Mounts: []specs.Mount{
- {
- Destination: "/proc",
- Type: "tmpfs",
- },
- {
- Destination: "/foo",
- Type: "tmpfs",
- },
- {
- Destination: "/foo/qux",
- Type: "tmpfs",
- },
- {
- // File mounts with the same prefix.
- Destination: "/foo/qux-quz",
- Type: "tmpfs",
- },
- {
- Destination: "/foo/bar",
- Type: "tmpfs",
- },
- {
- Destination: "/foo/bar/baz",
- Type: "tmpfs",
- },
- {
- // A deep path that is in foo but not the other mounts.
- Destination: "/foo/some/very/very/deep/path",
- Type: "tmpfs",
- },
- },
- },
- expectedPaths: []string{"/foo", "/foo/bar", "/foo/bar/baz", "/foo/qux",
- "/foo/qux-quz", "/foo/some/very/very/deep/path", "/proc", "/dev", "/sys"},
- },
- {
- name: "mount inside /dev",
- spec: specs.Spec{
- Root: &specs.Root{
- Path: os.TempDir(),
- Readonly: true,
- },
- Mounts: []specs.Mount{
- {
- Destination: "/proc",
- Type: "tmpfs",
- },
- {
- Destination: "/dev",
- Type: "tmpfs",
- },
- {
- // Mounted by runsc by default.
- Destination: "/dev/fd",
- Type: "tmpfs",
- },
- {
- // Mount with the same prefix.
- Destination: "/dev/fd-foo",
- Type: "tmpfs",
- },
- {
- // Unsupported fs type.
- Destination: "/dev/mqueue",
- Type: "mqueue",
- },
- {
- Destination: "/dev/foo",
- Type: "tmpfs",
- },
- {
- Destination: "/dev/bar",
- Type: "tmpfs",
- },
- },
- },
- expectedPaths: []string{"/proc", "/dev", "/dev/fd-foo", "/dev/foo", "/dev/bar", "/sys"},
- },
- {
- name: "mounts inside mandatory mounts",
- spec: specs.Spec{
- Root: &specs.Root{
- Path: os.TempDir(),
- Readonly: true,
- },
- Mounts: []specs.Mount{
- {
- Destination: "/proc",
- Type: "tmpfs",
- },
- // We don't include /sys, and /tmp in
- // the spec, since they will be added
- // automatically.
- //
- // Instead, add submounts inside these
- // directories and make sure they are
- // visible under the mandatory mounts.
- {
- Destination: "/sys/bar",
- Type: "tmpfs",
- },
- {
- Destination: "/tmp/baz",
- Type: "tmpfs",
- },
- },
- },
- expectedPaths: []string{"/proc", "/sys", "/sys/bar", "/tmp", "/tmp/baz"},
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- conf := testConfig()
- ctx := contexttest.Context(t)
-
- sandEnd, cleanup, err := startGofer(tc.spec.Root.Path)
- if err != nil {
- t.Fatalf("failed to create gofer: %v", err)
- }
- defer cleanup()
-
- mntr := newContainerMounter(&tc.spec, []int{sandEnd}, nil, &podMountHints{})
- mns, err := mntr.createMountNamespace(ctx, conf)
- if err != nil {
- t.Fatalf("failed to create mount namespace: %v", err)
- }
- ctx = fs.WithRoot(ctx, mns.Root())
- if err := mntr.mountSubmounts(ctx, conf, mns); err != nil {
- t.Fatalf("failed to create mount namespace: %v", err)
- }
-
- root := mns.Root()
- defer root.DecRef()
- for _, p := range tc.expectedPaths {
- maxTraversals := uint(0)
- if d, err := mns.FindInode(ctx, root, root, p, &maxTraversals); err != nil {
- t.Errorf("expected path %v to exist with spec %v, but got error %v", p, tc.spec, err)
- } else {
- d.DecRef()
- }
- }
- })
- }
-}
-
-// TestRestoreEnvironment tests that the correct mounts are collected from the spec and config
-// in order to build the environment for restoring.
-func TestRestoreEnvironment(t *testing.T) {
- testCases := []struct {
- name string
- spec *specs.Spec
- ioFDs []int
- errorExpected bool
- expectedRenv fs.RestoreEnvironment
- }{
- {
- name: "basic spec test",
- spec: &specs.Spec{
- Root: &specs.Root{
- Path: os.TempDir(),
- Readonly: true,
- },
- Mounts: []specs.Mount{
- {
- Destination: "/some/very/very/deep/path",
- Type: "tmpfs",
- },
- {
- Destination: "/proc",
- Type: "tmpfs",
- },
- },
- },
- ioFDs: []int{0},
- errorExpected: false,
- expectedRenv: fs.RestoreEnvironment{
- MountSources: map[string][]fs.MountArgs{
- "9p": {
- {
- Dev: "9pfs-/",
- Flags: fs.MountSourceFlags{ReadOnly: true},
- DataString: "trans=fd,rfdno=0,wfdno=0,privateunixsocket=true,cache=remote_revalidating",
- },
- },
- "tmpfs": {
- {
- Dev: "none",
- },
- {
- Dev: "none",
- },
- {
- Dev: "none",
- },
- },
- "devtmpfs": {
- {
- Dev: "none",
- },
- },
- "devpts": {
- {
- Dev: "none",
- },
- },
- "sysfs": {
- {
- Dev: "none",
- },
- },
- },
- },
- },
- {
- name: "bind type test",
- spec: &specs.Spec{
- Root: &specs.Root{
- Path: os.TempDir(),
- Readonly: true,
- },
- Mounts: []specs.Mount{
- {
- Destination: "/dev/fd-foo",
- Type: "bind",
- },
- },
- },
- ioFDs: []int{0, 1},
- errorExpected: false,
- expectedRenv: fs.RestoreEnvironment{
- MountSources: map[string][]fs.MountArgs{
- "9p": {
- {
- Dev: "9pfs-/",
- Flags: fs.MountSourceFlags{ReadOnly: true},
- DataString: "trans=fd,rfdno=0,wfdno=0,privateunixsocket=true,cache=remote_revalidating",
- },
- {
- Dev: "9pfs-/dev/fd-foo",
- DataString: "trans=fd,rfdno=1,wfdno=1,privateunixsocket=true,cache=remote_revalidating",
- },
- },
- "tmpfs": {
- {
- Dev: "none",
- },
- },
- "devtmpfs": {
- {
- Dev: "none",
- },
- },
- "devpts": {
- {
- Dev: "none",
- },
- },
- "proc": {
- {
- Dev: "none",
- },
- },
- "sysfs": {
- {
- Dev: "none",
- },
- },
- },
- },
- },
- {
- name: "options test",
- spec: &specs.Spec{
- Root: &specs.Root{
- Path: os.TempDir(),
- Readonly: true,
- },
- Mounts: []specs.Mount{
- {
- Destination: "/dev/fd-foo",
- Type: "tmpfs",
- Options: []string{"uid=1022", "noatime"},
- },
- },
- },
- ioFDs: []int{0},
- errorExpected: false,
- expectedRenv: fs.RestoreEnvironment{
- MountSources: map[string][]fs.MountArgs{
- "9p": {
- {
- Dev: "9pfs-/",
- Flags: fs.MountSourceFlags{ReadOnly: true},
- DataString: "trans=fd,rfdno=0,wfdno=0,privateunixsocket=true,cache=remote_revalidating",
- },
- },
- "tmpfs": {
- {
- Dev: "none",
- Flags: fs.MountSourceFlags{NoAtime: true},
- DataString: "uid=1022",
- },
- {
- Dev: "none",
- },
- },
- "devtmpfs": {
- {
- Dev: "none",
- },
- },
- "devpts": {
- {
- Dev: "none",
- },
- },
- "proc": {
- {
- Dev: "none",
- },
- },
- "sysfs": {
- {
- Dev: "none",
- },
- },
- },
- },
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- conf := testConfig()
- mntr := newContainerMounter(tc.spec, tc.ioFDs, nil, &podMountHints{})
- actualRenv, err := mntr.createRestoreEnvironment(conf)
- if !tc.errorExpected && err != nil {
- t.Fatalf("could not create restore environment for test:%s", tc.name)
- } else if tc.errorExpected {
- if err == nil {
- t.Errorf("expected an error, but no error occurred.")
- }
- } else {
- if !reflect.DeepEqual(*actualRenv, tc.expectedRenv) {
- t.Errorf("restore environments did not match for test:%s\ngot:%+v\nwant:%+v\n", tc.name, *actualRenv, tc.expectedRenv)
- }
- }
- })
- }
-}
diff --git a/runsc/boot/platforms/BUILD b/runsc/boot/platforms/BUILD
deleted file mode 100644
index 03391cdca..000000000
--- a/runsc/boot/platforms/BUILD
+++ /dev/null
@@ -1,16 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "platforms",
- srcs = ["platforms.go"],
- importpath = "gvisor.dev/gvisor/runsc/boot/platforms",
- visibility = [
- "//runsc:__subpackages__",
- ],
- deps = [
- "//pkg/sentry/platform/kvm",
- "//pkg/sentry/platform/ptrace",
- ],
-)
diff --git a/runsc/boot/user_test.go b/runsc/boot/user_test.go
deleted file mode 100644
index 906baf3e5..000000000
--- a/runsc/boot/user_test.go
+++ /dev/null
@@ -1,253 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package boot
-
-import (
- "io/ioutil"
- "os"
- "path/filepath"
- "strings"
- "syscall"
- "testing"
-
- specs "github.com/opencontainers/runtime-spec/specs-go"
- "gvisor.dev/gvisor/pkg/sentry/context/contexttest"
- "gvisor.dev/gvisor/pkg/sentry/fs"
-)
-
-func setupTempDir() (string, error) {
- tmpDir, err := ioutil.TempDir(os.TempDir(), "exec-user-test")
- if err != nil {
- return "", err
- }
- return tmpDir, nil
-}
-
-func setupPasswd(contents string, perms os.FileMode) func() (string, error) {
- return func() (string, error) {
- tmpDir, err := setupTempDir()
- if err != nil {
- return "", err
- }
-
- if err := os.Mkdir(filepath.Join(tmpDir, "etc"), 0777); err != nil {
- return "", err
- }
-
- f, err := os.Create(filepath.Join(tmpDir, "etc", "passwd"))
- if err != nil {
- return "", err
- }
- defer f.Close()
-
- _, err = f.WriteString(contents)
- if err != nil {
- return "", err
- }
-
- err = f.Chmod(perms)
- if err != nil {
- return "", err
- }
- return tmpDir, nil
- }
-}
-
-// TestGetExecUserHome tests the getExecUserHome function.
-func TestGetExecUserHome(t *testing.T) {
- tests := map[string]struct {
- uid uint32
- createRoot func() (string, error)
- expected string
- }{
- "success": {
- uid: 1000,
- createRoot: setupPasswd("adin::1000:1111::/home/adin:/bin/sh", 0666),
- expected: "/home/adin",
- },
- "no_passwd": {
- uid: 1000,
- createRoot: setupTempDir,
- expected: "/",
- },
- "no_perms": {
- uid: 1000,
- createRoot: setupPasswd("adin::1000:1111::/home/adin:/bin/sh", 0000),
- expected: "/",
- },
- "directory": {
- uid: 1000,
- createRoot: func() (string, error) {
- tmpDir, err := setupTempDir()
- if err != nil {
- return "", err
- }
-
- if err := os.Mkdir(filepath.Join(tmpDir, "etc"), 0777); err != nil {
- return "", err
- }
-
- if err := syscall.Mkdir(filepath.Join(tmpDir, "etc", "passwd"), 0666); err != nil {
- return "", err
- }
-
- return tmpDir, nil
- },
- expected: "/",
- },
- // Currently we don't allow named pipes.
- "named_pipe": {
- uid: 1000,
- createRoot: func() (string, error) {
- tmpDir, err := setupTempDir()
- if err != nil {
- return "", err
- }
-
- if err := os.Mkdir(filepath.Join(tmpDir, "etc"), 0777); err != nil {
- return "", err
- }
-
- if err := syscall.Mkfifo(filepath.Join(tmpDir, "etc", "passwd"), 0666); err != nil {
- return "", err
- }
-
- return tmpDir, nil
- },
- expected: "/",
- },
- }
-
- for name, tc := range tests {
- t.Run(name, func(t *testing.T) {
- tmpDir, err := tc.createRoot()
- if err != nil {
- t.Fatalf("failed to create root dir: %v", err)
- }
-
- sandEnd, cleanup, err := startGofer(tmpDir)
- if err != nil {
- t.Fatalf("failed to create gofer: %v", err)
- }
- defer cleanup()
-
- ctx := contexttest.Context(t)
- conf := &Config{
- RootDir: "unused_root_dir",
- Network: NetworkNone,
- DisableSeccomp: true,
- }
-
- spec := &specs.Spec{
- Root: &specs.Root{
- Path: tmpDir,
- Readonly: true,
- },
- // Add /proc mount as tmpfs to avoid needing a kernel.
- Mounts: []specs.Mount{
- {
- Destination: "/proc",
- Type: "tmpfs",
- },
- },
- }
-
- mntr := newContainerMounter(spec, []int{sandEnd}, nil, &podMountHints{})
- mns, err := mntr.createMountNamespace(ctx, conf)
- if err != nil {
- t.Fatalf("failed to create mount namespace: %v", err)
- }
- ctx = fs.WithRoot(ctx, mns.Root())
- if err := mntr.mountSubmounts(ctx, conf, mns); err != nil {
- t.Fatalf("failed to create mount namespace: %v", err)
- }
-
- got, err := getExecUserHome(ctx, mns, tc.uid)
- if err != nil {
- t.Fatalf("failed to get user home: %v", err)
- }
-
- if got != tc.expected {
- t.Fatalf("expected %v, got: %v", tc.expected, got)
- }
- })
- }
-}
-
-// TestFindHomeInPasswd tests the findHomeInPasswd function's passwd file parsing.
-func TestFindHomeInPasswd(t *testing.T) {
- tests := map[string]struct {
- uid uint32
- passwd string
- expected string
- def string
- }{
- "empty": {
- uid: 1000,
- passwd: "",
- expected: "/",
- def: "/",
- },
- "whitespace": {
- uid: 1000,
- passwd: " ",
- expected: "/",
- def: "/",
- },
- "full": {
- uid: 1000,
- passwd: "adin::1000:1111::/home/adin:/bin/sh",
- expected: "/home/adin",
- def: "/",
- },
- // For better or worse, this is how runc works.
- "partial": {
- uid: 1000,
- passwd: "adin::1000:1111:",
- expected: "",
- def: "/",
- },
- "multiple": {
- uid: 1001,
- passwd: "adin::1000:1111::/home/adin:/bin/sh\nian::1001:1111::/home/ian:/bin/sh",
- expected: "/home/ian",
- def: "/",
- },
- "duplicate": {
- uid: 1000,
- passwd: "adin::1000:1111::/home/adin:/bin/sh\nian::1000:1111::/home/ian:/bin/sh",
- expected: "/home/adin",
- def: "/",
- },
- "empty_lines": {
- uid: 1001,
- passwd: "adin::1000:1111::/home/adin:/bin/sh\n\n\nian::1001:1111::/home/ian:/bin/sh",
- expected: "/home/ian",
- def: "/",
- },
- }
-
- for name, tc := range tests {
- t.Run(name, func(t *testing.T) {
- got, err := findHomeInPasswd(tc.uid, strings.NewReader(tc.passwd), tc.def)
- if err != nil {
- t.Fatalf("error parsing passwd: %v", err)
- }
- if tc.expected != got {
- t.Fatalf("expected %v, got: %v", tc.expected, got)
- }
- })
- }
-}
diff --git a/runsc/cgroup/BUILD b/runsc/cgroup/BUILD
deleted file mode 100644
index d6165f9e5..000000000
--- a/runsc/cgroup/BUILD
+++ /dev/null
@@ -1,24 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "cgroup",
- srcs = ["cgroup.go"],
- importpath = "gvisor.dev/gvisor/runsc/cgroup",
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/log",
- "//runsc/specutils",
- "@com_github_cenkalti_backoff//:go_default_library",
- "@com_github_opencontainers_runtime-spec//specs-go:go_default_library",
- ],
-)
-
-go_test(
- name = "cgroup_test",
- size = "small",
- srcs = ["cgroup_test.go"],
- embed = [":cgroup"],
- tags = ["local"],
-)
diff --git a/runsc/cgroup/cgroup_test.go b/runsc/cgroup/cgroup_test.go
deleted file mode 100644
index 548c80e9a..000000000
--- a/runsc/cgroup/cgroup_test.go
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package cgroup
-
-import (
- "testing"
-)
-
-func TestUninstallEnoent(t *testing.T) {
- c := Cgroup{
- // set a non-existent name
- Name: "runsc-test-uninstall-656e6f656e740a",
- Own: true,
- }
- if err := c.Uninstall(); err != nil {
- t.Errorf("Uninstall() failed: %v", err)
- }
-}
-
-func TestCountCpuset(t *testing.T) {
- for _, tc := range []struct {
- str string
- want int
- error bool
- }{
- {str: "0", want: 1},
- {str: "0,1,2,8,9,10", want: 6},
- {str: "0-1", want: 2},
- {str: "0-7", want: 8},
- {str: "0-7,16,32-39,64,65", want: 19},
- {str: "a", error: true},
- {str: "5-a", error: true},
- {str: "a-5", error: true},
- {str: "-10", error: true},
- {str: "15-", error: true},
- {str: "-", error: true},
- {str: "--", error: true},
- } {
- t.Run(tc.str, func(t *testing.T) {
- got, err := countCpuset(tc.str)
- if tc.error {
- if err == nil {
- t.Errorf("countCpuset(%q) should have failed", tc.str)
- }
- } else {
- if err != nil {
- t.Errorf("countCpuset(%q) failed: %v", tc.str, err)
- }
- if tc.want != got {
- t.Errorf("countCpuset(%q) want: %d, got: %d", tc.str, tc.want, got)
- }
- }
- })
- }
-}
diff --git a/runsc/cmd/BUILD b/runsc/cmd/BUILD
deleted file mode 100644
index 250845ad7..000000000
--- a/runsc/cmd/BUILD
+++ /dev/null
@@ -1,91 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "cmd",
- srcs = [
- "boot.go",
- "capability.go",
- "checkpoint.go",
- "chroot.go",
- "cmd.go",
- "create.go",
- "debug.go",
- "delete.go",
- "do.go",
- "error.go",
- "events.go",
- "exec.go",
- "gofer.go",
- "help.go",
- "install.go",
- "kill.go",
- "list.go",
- "path.go",
- "pause.go",
- "ps.go",
- "restore.go",
- "resume.go",
- "run.go",
- "spec.go",
- "start.go",
- "state.go",
- "syscalls.go",
- "wait.go",
- ],
- importpath = "gvisor.dev/gvisor/runsc/cmd",
- visibility = [
- "//runsc:__subpackages__",
- ],
- deps = [
- "//pkg/log",
- "//pkg/p9",
- "//pkg/sentry/control",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/unet",
- "//pkg/urpc",
- "//runsc/boot",
- "//runsc/boot/platforms",
- "//runsc/console",
- "//runsc/container",
- "//runsc/fsgofer",
- "//runsc/fsgofer/filter",
- "//runsc/specutils",
- "@com_github_google_subcommands//:go_default_library",
- "@com_github_opencontainers_runtime-spec//specs-go:go_default_library",
- "@com_github_syndtr_gocapability//capability:go_default_library",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-go_test(
- name = "cmd_test",
- size = "small",
- srcs = [
- "capability_test.go",
- "delete_test.go",
- "exec_test.go",
- "gofer_test.go",
- ],
- data = [
- "//runsc",
- ],
- embed = [":cmd"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/log",
- "//pkg/sentry/control",
- "//pkg/sentry/kernel/auth",
- "//pkg/urpc",
- "//runsc/boot",
- "//runsc/container",
- "//runsc/specutils",
- "//runsc/testutil",
- "@com_github_google_go-cmp//cmp:go_default_library",
- "@com_github_google_go-cmp//cmp/cmpopts:go_default_library",
- "@com_github_opencontainers_runtime-spec//specs-go:go_default_library",
- "@com_github_syndtr_gocapability//capability:go_default_library",
- ],
-)
diff --git a/runsc/cmd/capability_test.go b/runsc/cmd/capability_test.go
deleted file mode 100644
index 0c27f7313..000000000
--- a/runsc/cmd/capability_test.go
+++ /dev/null
@@ -1,128 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package cmd
-
-import (
- "flag"
- "fmt"
- "os"
- "testing"
-
- specs "github.com/opencontainers/runtime-spec/specs-go"
- "github.com/syndtr/gocapability/capability"
- "gvisor.dev/gvisor/pkg/log"
- "gvisor.dev/gvisor/runsc/boot"
- "gvisor.dev/gvisor/runsc/container"
- "gvisor.dev/gvisor/runsc/specutils"
- "gvisor.dev/gvisor/runsc/testutil"
-)
-
-func init() {
- log.SetLevel(log.Debug)
- if err := testutil.ConfigureExePath(); err != nil {
- panic(err.Error())
- }
-}
-
-func checkProcessCaps(pid int, wantCaps *specs.LinuxCapabilities) error {
- curCaps, err := capability.NewPid2(pid)
- if err != nil {
- return fmt.Errorf("capability.NewPid2(%d) failed: %v", pid, err)
- }
- if err := curCaps.Load(); err != nil {
- return fmt.Errorf("unable to load capabilities: %v", err)
- }
- fmt.Printf("Capabilities (PID: %d): %v\n", pid, curCaps)
-
- for _, c := range allCapTypes {
- if err := checkCaps(c, curCaps, wantCaps); err != nil {
- return err
- }
- }
- return nil
-}
-
-func checkCaps(which capability.CapType, curCaps capability.Capabilities, wantCaps *specs.LinuxCapabilities) error {
- wantNames := getCaps(which, wantCaps)
- for name, c := range capFromName {
- want := specutils.ContainsStr(wantNames, name)
- got := curCaps.Get(which, c)
- if want != got {
- if want {
- return fmt.Errorf("capability %v:%s should be set", which, name)
- }
- return fmt.Errorf("capability %v:%s should NOT be set", which, name)
- }
- }
- return nil
-}
-
-func TestCapabilities(t *testing.T) {
- stop := testutil.StartReaper()
- defer stop()
-
- spec := testutil.NewSpecWithArgs("/bin/sleep", "10000")
- caps := []string{
- "CAP_CHOWN",
- "CAP_SYS_PTRACE", // ptrace is added due to the platform choice.
- }
- spec.Process.Capabilities = &specs.LinuxCapabilities{
- Permitted: caps,
- Bounding: caps,
- Effective: caps,
- Inheritable: caps,
- }
-
- conf := testutil.TestConfig()
-
- // Use --network=host to make sandbox use spec's capabilities.
- conf.Network = boot.NetworkHost
-
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- // Create and start the container.
- args := container.Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- }
- c, err := container.New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- defer c.Destroy()
- if err := c.Start(conf); err != nil {
- t.Fatalf("error starting container: %v", err)
- }
-
- // Check that sandbox and gofer have the proper capabilities.
- if err := checkProcessCaps(c.Sandbox.Pid, spec.Process.Capabilities); err != nil {
- t.Error(err)
- }
- if err := checkProcessCaps(c.GoferPid, goferCaps); err != nil {
- t.Error(err)
- }
-}
-
-func TestMain(m *testing.M) {
- flag.Parse()
- specutils.MaybeRunAsRoot()
- os.Exit(m.Run())
-}
diff --git a/runsc/cmd/delete_test.go b/runsc/cmd/delete_test.go
deleted file mode 100644
index cb59516a3..000000000
--- a/runsc/cmd/delete_test.go
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package cmd
-
-import (
- "io/ioutil"
- "testing"
-
- "gvisor.dev/gvisor/runsc/boot"
-)
-
-func TestNotFound(t *testing.T) {
- ids := []string{"123"}
- dir, err := ioutil.TempDir("", "metadata")
- if err != nil {
- t.Fatalf("error creating dir: %v", err)
- }
- conf := &boot.Config{RootDir: dir}
-
- d := Delete{}
- if err := d.execute(ids, conf); err == nil {
- t.Error("Deleting non-existent container should have failed")
- }
-
- d = Delete{force: true}
- if err := d.execute(ids, conf); err != nil {
- t.Errorf("Deleting non-existent container with --force should NOT have failed: %v", err)
- }
-}
diff --git a/runsc/cmd/exec_test.go b/runsc/cmd/exec_test.go
deleted file mode 100644
index eb38a431f..000000000
--- a/runsc/cmd/exec_test.go
+++ /dev/null
@@ -1,154 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package cmd
-
-import (
- "os"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- specs "github.com/opencontainers/runtime-spec/specs-go"
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/sentry/control"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/pkg/urpc"
-)
-
-func TestUser(t *testing.T) {
- testCases := []struct {
- input string
- want user
- wantErr bool
- }{
- {input: "0", want: user{kuid: 0, kgid: 0}},
- {input: "7", want: user{kuid: 7, kgid: 0}},
- {input: "49:343", want: user{kuid: 49, kgid: 343}},
- {input: "0:2401", want: user{kuid: 0, kgid: 2401}},
- {input: "", wantErr: true},
- {input: "foo", wantErr: true},
- {input: ":123", wantErr: true},
- {input: "1:2:3", wantErr: true},
- }
-
- for _, tc := range testCases {
- var u user
- if err := u.Set(tc.input); err != nil && tc.wantErr {
- // We got an error and wanted one.
- continue
- } else if err == nil && tc.wantErr {
- t.Errorf("user.Set(%s): got no error, but wanted one", tc.input)
- } else if err != nil && !tc.wantErr {
- t.Errorf("user.Set(%s): got error %v, but wanted none", tc.input, err)
- } else if u != tc.want {
- t.Errorf("user.Set(%s): got %+v, but wanted %+v", tc.input, u, tc.want)
- }
- }
-}
-
-func TestCLIArgs(t *testing.T) {
- testCases := []struct {
- ex Exec
- argv []string
- expected control.ExecArgs
- }{
- {
- ex: Exec{
- cwd: "/foo/bar",
- user: user{kuid: 0, kgid: 0},
- extraKGIDs: []string{"1", "2", "3"},
- caps: []string{"CAP_DAC_OVERRIDE"},
- processPath: "",
- },
- argv: []string{"ls", "/"},
- expected: control.ExecArgs{
- Argv: []string{"ls", "/"},
- WorkingDirectory: "/foo/bar",
- FilePayload: urpc.FilePayload{Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}},
- KUID: 0,
- KGID: 0,
- ExtraKGIDs: []auth.KGID{1, 2, 3},
- Capabilities: &auth.TaskCapabilities{
- BoundingCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE),
- EffectiveCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE),
- InheritableCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE),
- PermittedCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE),
- },
- },
- },
- }
-
- for _, tc := range testCases {
- e, err := tc.ex.argsFromCLI(tc.argv)
- if err != nil {
- t.Errorf("argsFromCLI(%+v): got error: %+v", tc.ex, err)
- } else if !cmp.Equal(*e, tc.expected, cmpopts.IgnoreUnexported(os.File{})) {
- t.Errorf("argsFromCLI(%+v): got %+v, but expected %+v", tc.ex, *e, tc.expected)
- }
- }
-}
-
-func TestJSONArgs(t *testing.T) {
- testCases := []struct {
- // ex is provided to make sure it is overridden by p.
- ex Exec
- p specs.Process
- expected control.ExecArgs
- }{
- {
- ex: Exec{
- cwd: "/baz/quux",
- user: user{kuid: 1, kgid: 1},
- extraKGIDs: []string{"4", "5", "6"},
- caps: []string{"CAP_SETGID"},
- processPath: "/bin/foo",
- },
- p: specs.Process{
- User: specs.User{UID: 0, GID: 0, AdditionalGids: []uint32{1, 2, 3}},
- Args: []string{"ls", "/"},
- Cwd: "/foo/bar",
- Capabilities: &specs.LinuxCapabilities{
- Bounding: []string{"CAP_DAC_OVERRIDE"},
- Effective: []string{"CAP_DAC_OVERRIDE"},
- Inheritable: []string{"CAP_DAC_OVERRIDE"},
- Permitted: []string{"CAP_DAC_OVERRIDE"},
- },
- },
- expected: control.ExecArgs{
- Argv: []string{"ls", "/"},
- WorkingDirectory: "/foo/bar",
- FilePayload: urpc.FilePayload{Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}},
- KUID: 0,
- KGID: 0,
- ExtraKGIDs: []auth.KGID{1, 2, 3},
- Capabilities: &auth.TaskCapabilities{
- BoundingCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE),
- EffectiveCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE),
- InheritableCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE),
- PermittedCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE),
- },
- },
- },
- }
-
- for _, tc := range testCases {
- e, err := argsFromProcess(&tc.p)
- if err != nil {
- t.Errorf("argsFromProcess(%+v): got error: %+v", tc.p, err)
- } else if !cmp.Equal(*e, tc.expected, cmpopts.IgnoreUnexported(os.File{})) {
- t.Errorf("argsFromProcess(%+v): got %+v, but expected %+v", tc.p, *e, tc.expected)
- }
- }
-}
diff --git a/runsc/cmd/gofer_test.go b/runsc/cmd/gofer_test.go
deleted file mode 100644
index cbea7f127..000000000
--- a/runsc/cmd/gofer_test.go
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package cmd
-
-import (
- "fmt"
- "io/ioutil"
- "os"
- "path"
- "path/filepath"
- "testing"
-)
-
-func tmpDir() string {
- dir := os.Getenv("TEST_TMPDIR")
- if dir == "" {
- dir = "/tmp"
- }
- return dir
-}
-
-type dir struct {
- rel string
- link string
-}
-
-func construct(root string, dirs []dir) error {
- for _, d := range dirs {
- p := path.Join(root, d.rel)
- if d.link == "" {
- if err := os.MkdirAll(p, 0755); err != nil {
- return fmt.Errorf("error creating dir: %v", err)
- }
- } else {
- if err := os.MkdirAll(path.Dir(p), 0755); err != nil {
- return fmt.Errorf("error creating dir: %v", err)
- }
- if err := os.Symlink(d.link, p); err != nil {
- return fmt.Errorf("error creating symlink: %v", err)
- }
- }
- }
- return nil
-}
-
-func TestResolveSymlinks(t *testing.T) {
- root, err := ioutil.TempDir(tmpDir(), "root")
- if err != nil {
- t.Fatal("ioutil.TempDir() failed:", err)
- }
- dirs := []dir{
- {"dir1/dir11/dir111/dir1111", ""}, // Just a boring dir
- {"dir1/lnk12", "dir11"}, // Link to sibling
- {"dir1/lnk13", "./dir11"}, // Link to sibling through self
- {"dir1/lnk14", "../dir1/dir11"}, // Link to sibling through parent
- {"dir1/dir15/lnk151", ".."}, // Link to parent
- {"dir1/lnk16", "dir11/dir111"}, // Link to child
- {"dir1/lnk17", "."}, // Link to self
- {"dir1/lnk18", "lnk13"}, // Link to link
- {"lnk2", "dir1/lnk13"}, // Link to link to link
- {"dir3/dir21/lnk211", "../.."}, // Link to root relative
- {"dir3/lnk22", "/"}, // Link to root absolute
- {"dir3/lnk23", "/dir1"}, // Link to dir absolute
- {"dir3/lnk24", "/dir1/lnk12"}, // Link to link absolute
- {"lnk5", "../../.."}, // Link outside root
- }
- if err := construct(root, dirs); err != nil {
- t.Fatal("construct failed:", err)
- }
-
- tests := []struct {
- name string
- rel string
- want string
- compareHost bool
- }{
- {name: "root", rel: "/", want: "/", compareHost: true},
- {name: "basic dir", rel: "/dir1/dir11/dir111", want: "/dir1/dir11/dir111", compareHost: true},
- {name: "dot 1", rel: "/dir1/dir11/./dir111", want: "/dir1/dir11/dir111", compareHost: true},
- {name: "dot 2", rel: "/dir1/././dir11/./././././dir111/.", want: "/dir1/dir11/dir111", compareHost: true},
- {name: "dotdot 1", rel: "/dir1/dir11/../dir15", want: "/dir1/dir15", compareHost: true},
- {name: "dotdot 2", rel: "/dir1/dir11/dir1111/../..", want: "/dir1", compareHost: true},
-
- {name: "link sibling", rel: "/dir1/lnk12", want: "/dir1/dir11", compareHost: true},
- {name: "link sibling + dir", rel: "/dir1/lnk12/dir111", want: "/dir1/dir11/dir111", compareHost: true},
- {name: "link sibling through self", rel: "/dir1/lnk13", want: "/dir1/dir11", compareHost: true},
- {name: "link sibling through parent", rel: "/dir1/lnk14", want: "/dir1/dir11", compareHost: true},
-
- {name: "link parent", rel: "/dir1/dir15/lnk151", want: "/dir1", compareHost: true},
- {name: "link parent + dir", rel: "/dir1/dir15/lnk151/dir11", want: "/dir1/dir11", compareHost: true},
- {name: "link child", rel: "/dir1/lnk16", want: "/dir1/dir11/dir111", compareHost: true},
- {name: "link child + dir", rel: "/dir1/lnk16/dir1111", want: "/dir1/dir11/dir111/dir1111", compareHost: true},
- {name: "link self", rel: "/dir1/lnk17", want: "/dir1", compareHost: true},
- {name: "link self + dir", rel: "/dir1/lnk17/dir11", want: "/dir1/dir11", compareHost: true},
-
- {name: "link^2", rel: "/dir1/lnk18", want: "/dir1/dir11", compareHost: true},
- {name: "link^2 + dir", rel: "/dir1/lnk18/dir111", want: "/dir1/dir11/dir111", compareHost: true},
- {name: "link^3", rel: "/lnk2", want: "/dir1/dir11", compareHost: true},
- {name: "link^3 + dir", rel: "/lnk2/dir111", want: "/dir1/dir11/dir111", compareHost: true},
-
- {name: "link abs", rel: "/dir3/lnk23", want: "/dir1"},
- {name: "link abs + dir", rel: "/dir3/lnk23/dir11", want: "/dir1/dir11"},
- {name: "link^2 abs", rel: "/dir3/lnk24", want: "/dir1/dir11"},
- {name: "link^2 abs + dir", rel: "/dir3/lnk24/dir111", want: "/dir1/dir11/dir111"},
-
- {name: "root link rel", rel: "/dir3/dir21/lnk211", want: "/", compareHost: true},
- {name: "root link abs", rel: "/dir3/lnk22", want: "/"},
- {name: "root contain link", rel: "/lnk5/dir1", want: "/dir1"},
- {name: "root contain dotdot", rel: "/dir1/dir11/../../../../../../../..", want: "/"},
-
- {name: "crazy", rel: "/dir3/dir21/lnk211/dir3/lnk22/dir1/dir11/../../lnk5/dir3/../dir3/lnk24/dir111/dir1111/..", want: "/dir1/dir11/dir111"},
- }
- for _, tst := range tests {
- t.Run(tst.name, func(t *testing.T) {
- got, err := resolveSymlinks(root, tst.rel)
- if err != nil {
- t.Errorf("resolveSymlinks(root, %q) failed: %v", tst.rel, err)
- }
- want := path.Join(root, tst.want)
- if got != want {
- t.Errorf("resolveSymlinks(root, %q) got: %q, want: %q", tst.rel, got, want)
- }
- if tst.compareHost {
- // Check that host got to the same end result.
- host, err := filepath.EvalSymlinks(path.Join(root, tst.rel))
- if err != nil {
- t.Errorf("path.EvalSymlinks(root, %q) failed: %v", tst.rel, err)
- }
- if host != got {
- t.Errorf("resolveSymlinks(root, %q) got: %q, want: %q", tst.rel, host, got)
- }
- }
- })
- }
-}
-
-func TestResolveSymlinksLoop(t *testing.T) {
- root, err := ioutil.TempDir(tmpDir(), "root")
- if err != nil {
- t.Fatal("ioutil.TempDir() failed:", err)
- }
- dirs := []dir{
- {"loop1", "loop2"},
- {"loop2", "loop1"},
- }
- if err := construct(root, dirs); err != nil {
- t.Fatal("construct failed:", err)
- }
- if _, err := resolveSymlinks(root, "loop1"); err == nil {
- t.Errorf("resolveSymlinks() should have failed")
- }
-}
diff --git a/runsc/console/BUILD b/runsc/console/BUILD
deleted file mode 100644
index e623c1a0f..000000000
--- a/runsc/console/BUILD
+++ /dev/null
@@ -1,18 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "console",
- srcs = [
- "console.go",
- ],
- importpath = "gvisor.dev/gvisor/runsc/console",
- visibility = [
- "//runsc:__subpackages__",
- ],
- deps = [
- "@com_github_kr_pty//:go_default_library",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
diff --git a/runsc/container/BUILD b/runsc/container/BUILD
deleted file mode 100644
index bc1fa25e3..000000000
--- a/runsc/container/BUILD
+++ /dev/null
@@ -1,65 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "container",
- srcs = [
- "container.go",
- "hook.go",
- "status.go",
- ],
- importpath = "gvisor.dev/gvisor/runsc/container",
- visibility = [
- "//runsc:__subpackages__",
- "//test:__subpackages__",
- ],
- deps = [
- "//pkg/log",
- "//pkg/sentry/control",
- "//runsc/boot",
- "//runsc/cgroup",
- "//runsc/sandbox",
- "//runsc/specutils",
- "@com_github_cenkalti_backoff//:go_default_library",
- "@com_github_gofrs_flock//:go_default_library",
- "@com_github_opencontainers_runtime-spec//specs-go:go_default_library",
- ],
-)
-
-go_test(
- name = "container_test",
- size = "medium",
- srcs = [
- "console_test.go",
- "container_test.go",
- "multi_container_test.go",
- "shared_volume_test.go",
- ],
- data = [
- "//runsc",
- "//runsc/container/test_app",
- ],
- embed = [":container"],
- shard_count = 5,
- tags = [
- "requires-kvm",
- ],
- deps = [
- "//pkg/abi/linux",
- "//pkg/log",
- "//pkg/sentry/control",
- "//pkg/sentry/kernel",
- "//pkg/sentry/kernel/auth",
- "//pkg/unet",
- "//pkg/urpc",
- "//runsc/boot",
- "//runsc/boot/platforms",
- "//runsc/specutils",
- "//runsc/testutil",
- "@com_github_cenkalti_backoff//:go_default_library",
- "@com_github_kr_pty//:go_default_library",
- "@com_github_opencontainers_runtime-spec//specs-go:go_default_library",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
diff --git a/runsc/container/console_test.go b/runsc/container/console_test.go
deleted file mode 100644
index 7d67c3a75..000000000
--- a/runsc/container/console_test.go
+++ /dev/null
@@ -1,483 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package container
-
-import (
- "bytes"
- "fmt"
- "io"
- "os"
- "path/filepath"
- "sync"
- "syscall"
- "testing"
- "time"
-
- "github.com/kr/pty"
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/sentry/control"
- "gvisor.dev/gvisor/pkg/unet"
- "gvisor.dev/gvisor/pkg/urpc"
- "gvisor.dev/gvisor/runsc/testutil"
-)
-
-// socketPath creates a path inside bundleDir and ensures that the returned
-// path is under 108 charactors (the unix socket path length limit),
-// relativizing the path if necessary.
-func socketPath(bundleDir string) (string, error) {
- path := filepath.Join(bundleDir, "socket")
- cwd, err := os.Getwd()
- if err != nil {
- return "", fmt.Errorf("error getting cwd: %v", err)
- }
- relPath, err := filepath.Rel(cwd, path)
- if err != nil {
- return "", fmt.Errorf("error getting relative path for %q from cwd %q: %v", path, cwd, err)
- }
- if len(path) > len(relPath) {
- path = relPath
- }
- const maxPathLen = 108
- if len(path) > maxPathLen {
- return "", fmt.Errorf("could not get socket path under length limit %d: %s", maxPathLen, path)
- }
- return path, nil
-}
-
-// createConsoleSocket creates a socket at the given path that will receive a
-// console fd from the sandbox. If no error occurs, it returns the server
-// socket and a cleanup function.
-func createConsoleSocket(path string) (*unet.ServerSocket, func() error, error) {
- srv, err := unet.BindAndListen(path, false)
- if err != nil {
- return nil, nil, fmt.Errorf("error binding and listening to socket %q: %v", path, err)
- }
-
- cleanup := func() error {
- if err := srv.Close(); err != nil {
- return fmt.Errorf("error closing socket %q: %v", path, err)
- }
- if err := os.Remove(path); err != nil {
- return fmt.Errorf("error removing socket %q: %v", path, err)
- }
- return nil
- }
-
- return srv, cleanup, nil
-}
-
-// receiveConsolePTY accepts a connection on the server socket and reads fds.
-// It fails if more than one FD is received, or if the FD is not a PTY. It
-// returns the PTY master file.
-func receiveConsolePTY(srv *unet.ServerSocket) (*os.File, error) {
- sock, err := srv.Accept()
- if err != nil {
- return nil, fmt.Errorf("error accepting socket connection: %v", err)
- }
-
- // Allow 3 fds to be received. We only expect 1.
- r := sock.Reader(true /* blocking */)
- r.EnableFDs(1)
-
- // The socket is closed right after sending the FD, so EOF is
- // an allowed error.
- b := [][]byte{{}}
- if _, err := r.ReadVec(b); err != nil && err != io.EOF {
- return nil, fmt.Errorf("error reading from socket connection: %v", err)
- }
-
- // We should have gotten a control message.
- fds, err := r.ExtractFDs()
- if err != nil {
- return nil, fmt.Errorf("error extracting fds from socket connection: %v", err)
- }
- if len(fds) != 1 {
- return nil, fmt.Errorf("got %d fds from socket, wanted 1", len(fds))
- }
-
- // Verify that the fd is a terminal.
- if _, err := unix.IoctlGetTermios(fds[0], unix.TCGETS); err != nil {
- return nil, fmt.Errorf("fd is not a terminal (ioctl TGGETS got %v)", err)
- }
-
- return os.NewFile(uintptr(fds[0]), "pty_master"), nil
-}
-
-// Test that an pty FD is sent over the console socket if one is provided.
-func TestConsoleSocket(t *testing.T) {
- for _, conf := range configs(all...) {
- t.Logf("Running test with conf: %+v", conf)
- spec := testutil.NewSpecWithArgs("true")
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- sock, err := socketPath(bundleDir)
- if err != nil {
- t.Fatalf("error getting socket path: %v", err)
- }
- srv, cleanup, err := createConsoleSocket(sock)
- if err != nil {
- t.Fatalf("error creating socket at %q: %v", sock, err)
- }
- defer cleanup()
-
- // Create the container and pass the socket name.
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- ConsoleSocket: sock,
- }
- c, err := New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- defer c.Destroy()
-
- // Make sure we get a console PTY.
- ptyMaster, err := receiveConsolePTY(srv)
- if err != nil {
- t.Fatalf("error receiving console FD: %v", err)
- }
- ptyMaster.Close()
- }
-}
-
-// Test that job control signals work on a console created with "exec -ti".
-func TestJobControlSignalExec(t *testing.T) {
- spec := testutil.NewSpecWithArgs("/bin/sleep", "10000")
- conf := testutil.TestConfig()
-
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- // Create and start the container.
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- }
- c, err := New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- defer c.Destroy()
- if err := c.Start(conf); err != nil {
- t.Fatalf("error starting container: %v", err)
- }
-
- // Create a pty master/slave. The slave will be passed to the exec
- // process.
- ptyMaster, ptySlave, err := pty.Open()
- if err != nil {
- t.Fatalf("error opening pty: %v", err)
- }
- defer ptyMaster.Close()
- defer ptySlave.Close()
-
- // Exec bash and attach a terminal.
- execArgs := &control.ExecArgs{
- Filename: "/bin/bash",
- // Don't let bash execute from profile or rc files, otherwise
- // our PID counts get messed up.
- Argv: []string{"/bin/bash", "--noprofile", "--norc"},
- // Pass the pty slave as FD 0, 1, and 2.
- FilePayload: urpc.FilePayload{
- Files: []*os.File{ptySlave, ptySlave, ptySlave},
- },
- StdioIsPty: true,
- }
-
- pid, err := c.Execute(execArgs)
- if err != nil {
- t.Fatalf("error executing: %v", err)
- }
- if pid != 2 {
- t.Fatalf("exec got pid %d, wanted %d", pid, 2)
- }
-
- // Make sure all the processes are running.
- expectedPL := []*control.Process{
- // Root container process.
- {PID: 1, Cmd: "sleep"},
- // Bash from exec process.
- {PID: 2, Cmd: "bash"},
- }
- if err := waitForProcessList(c, expectedPL); err != nil {
- t.Error(err)
- }
-
- // Execute sleep.
- ptyMaster.Write([]byte("sleep 100\n"))
-
- // Wait for it to start. Sleep's PPID is bash's PID.
- expectedPL = append(expectedPL, &control.Process{PID: 3, PPID: 2, Cmd: "sleep"})
- if err := waitForProcessList(c, expectedPL); err != nil {
- t.Error(err)
- }
-
- // Send a SIGTERM to the foreground process for the exec PID. Note that
- // although we pass in the PID of "bash", it should actually terminate
- // "sleep", since that is the foreground process.
- if err := c.Sandbox.SignalProcess(c.ID, pid, syscall.SIGTERM, true /* fgProcess */); err != nil {
- t.Fatalf("error signaling container: %v", err)
- }
-
- // Sleep process should be gone.
- expectedPL = expectedPL[:len(expectedPL)-1]
- if err := waitForProcessList(c, expectedPL); err != nil {
- t.Error(err)
- }
-
- // Sleep is dead, but it may take more time for bash to notice and
- // change the foreground process back to itself. We know it is done
- // when bash writes "Terminated" to the pty.
- if err := testutil.WaitUntilRead(ptyMaster, "Terminated", nil, 5*time.Second); err != nil {
- t.Fatalf("bash did not take over pty: %v", err)
- }
-
- // Send a SIGKILL to the foreground process again. This time "bash"
- // should be killed. We use SIGKILL instead of SIGTERM or SIGINT
- // because bash ignores those.
- if err := c.Sandbox.SignalProcess(c.ID, pid, syscall.SIGKILL, true /* fgProcess */); err != nil {
- t.Fatalf("error signaling container: %v", err)
- }
- expectedPL = expectedPL[:1]
- if err := waitForProcessList(c, expectedPL); err != nil {
- t.Error(err)
- }
-
- // Make sure the process indicates it was killed by a SIGKILL.
- ws, err := c.WaitPID(pid)
- if err != nil {
- t.Errorf("waiting on container failed: %v", err)
- }
- if !ws.Signaled() {
- t.Error("ws.Signaled() got false, want true")
- }
- if got, want := ws.Signal(), syscall.SIGKILL; got != want {
- t.Errorf("ws.Signal() got %v, want %v", got, want)
- }
-}
-
-// Test that job control signals work on a console created with "run -ti".
-func TestJobControlSignalRootContainer(t *testing.T) {
- conf := testutil.TestConfig()
- // Don't let bash execute from profile or rc files, otherwise our PID
- // counts get messed up.
- spec := testutil.NewSpecWithArgs("/bin/bash", "--noprofile", "--norc")
- spec.Process.Terminal = true
-
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- sock, err := socketPath(bundleDir)
- if err != nil {
- t.Fatalf("error getting socket path: %v", err)
- }
- srv, cleanup, err := createConsoleSocket(sock)
- if err != nil {
- t.Fatalf("error creating socket at %q: %v", sock, err)
- }
- defer cleanup()
-
- // Create the container and pass the socket name.
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- ConsoleSocket: sock,
- }
- c, err := New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- defer c.Destroy()
-
- // Get the PTY master.
- ptyMaster, err := receiveConsolePTY(srv)
- if err != nil {
- t.Fatalf("error receiving console FD: %v", err)
- }
- defer ptyMaster.Close()
-
- // Bash output as well as sandbox output will be written to the PTY
- // file. Writes after a certain point will block unless we drain the
- // PTY, so we must continually copy from it.
- //
- // We log the output to stdout for debugabilitly, and also to a buffer,
- // since we wait on particular output from bash below. We use a custom
- // blockingBuffer which is thread-safe and also blocks on Read calls,
- // which makes this a suitable Reader for WaitUntilRead.
- ptyBuf := newBlockingBuffer()
- tee := io.TeeReader(ptyMaster, ptyBuf)
- go io.Copy(os.Stdout, tee)
-
- // Start the container.
- if err := c.Start(conf); err != nil {
- t.Fatalf("error starting container: %v", err)
- }
-
- // Start waiting for the container to exit in a goroutine. We do this
- // very early, otherwise it might exit before we have a chance to call
- // Wait.
- var (
- ws syscall.WaitStatus
- wg sync.WaitGroup
- )
- wg.Add(1)
- go func() {
- var err error
- ws, err = c.Wait()
- if err != nil {
- t.Errorf("error waiting on container: %v", err)
- }
- wg.Done()
- }()
-
- // Wait for bash to start.
- expectedPL := []*control.Process{
- {PID: 1, Cmd: "bash"},
- }
- if err := waitForProcessList(c, expectedPL); err != nil {
- t.Fatal(err)
- }
-
- // Execute sleep via the terminal.
- ptyMaster.Write([]byte("sleep 100\n"))
-
- // Wait for sleep to start.
- expectedPL = append(expectedPL, &control.Process{PID: 2, PPID: 1, Cmd: "sleep"})
- if err := waitForProcessList(c, expectedPL); err != nil {
- t.Fatal(err)
- }
-
- // Reset the pty buffer, so there is less output for us to scan later.
- ptyBuf.Reset()
-
- // Send a SIGTERM to the foreground process. We pass PID=0, indicating
- // that the root process should be killed. However, by setting
- // fgProcess=true, the signal should actually be sent to sleep.
- if err := c.Sandbox.SignalProcess(c.ID, 0 /* PID */, syscall.SIGTERM, true /* fgProcess */); err != nil {
- t.Fatalf("error signaling container: %v", err)
- }
-
- // Sleep process should be gone.
- expectedPL = expectedPL[:len(expectedPL)-1]
- if err := waitForProcessList(c, expectedPL); err != nil {
- t.Error(err)
- }
-
- // Sleep is dead, but it may take more time for bash to notice and
- // change the foreground process back to itself. We know it is done
- // when bash writes "Terminated" to the pty.
- if err := testutil.WaitUntilRead(ptyBuf, "Terminated", nil, 5*time.Second); err != nil {
- t.Fatalf("bash did not take over pty: %v", err)
- }
-
- // Send a SIGKILL to the foreground process again. This time "bash"
- // should be killed. We use SIGKILL instead of SIGTERM or SIGINT
- // because bash ignores those.
- if err := c.Sandbox.SignalProcess(c.ID, 0 /* PID */, syscall.SIGKILL, true /* fgProcess */); err != nil {
- t.Fatalf("error signaling container: %v", err)
- }
-
- // Wait for the sandbox to exit. It should exit with a SIGKILL status.
- wg.Wait()
- if !ws.Signaled() {
- t.Error("ws.Signaled() got false, want true")
- }
- if got, want := ws.Signal(), syscall.SIGKILL; got != want {
- t.Errorf("ws.Signal() got %v, want %v", got, want)
- }
-}
-
-// blockingBuffer is a thread-safe buffer that blocks when reading if the
-// buffer is empty. It implements io.ReadWriter.
-type blockingBuffer struct {
- // A send to readCh indicates that a previously empty buffer now has
- // data for reading.
- readCh chan struct{}
-
- // mu protects buf.
- mu sync.Mutex
- buf bytes.Buffer
-}
-
-func newBlockingBuffer() *blockingBuffer {
- return &blockingBuffer{
- readCh: make(chan struct{}, 1),
- }
-}
-
-// Write implements Writer.Write.
-func (bb *blockingBuffer) Write(p []byte) (int, error) {
- bb.mu.Lock()
- defer bb.mu.Unlock()
- l := bb.buf.Len()
- n, err := bb.buf.Write(p)
- if l == 0 && n > 0 {
- // New data!
- bb.readCh <- struct{}{}
- }
- return n, err
-}
-
-// Read implements Reader.Read. It will block until data is available.
-func (bb *blockingBuffer) Read(p []byte) (int, error) {
- for {
- bb.mu.Lock()
- n, err := bb.buf.Read(p)
- if n > 0 || err != io.EOF {
- if bb.buf.Len() == 0 {
- // Reset the readCh.
- select {
- case <-bb.readCh:
- default:
- }
- }
- bb.mu.Unlock()
- return n, err
- }
- bb.mu.Unlock()
-
- // Wait for new data.
- <-bb.readCh
- }
-}
-
-// Reset resets the buffer.
-func (bb *blockingBuffer) Reset() {
- bb.mu.Lock()
- defer bb.mu.Unlock()
- bb.buf.Reset()
- // Reset the readCh.
- select {
- case <-bb.readCh:
- default:
- }
-}
diff --git a/runsc/container/container_test.go b/runsc/container/container_test.go
deleted file mode 100644
index 2ac12e5b6..000000000
--- a/runsc/container/container_test.go
+++ /dev/null
@@ -1,2073 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package container
-
-import (
- "bytes"
- "flag"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path"
- "path/filepath"
- "reflect"
- "strconv"
- "strings"
- "sync"
- "syscall"
- "testing"
- "time"
-
- "github.com/cenkalti/backoff"
- specs "github.com/opencontainers/runtime-spec/specs-go"
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/log"
- "gvisor.dev/gvisor/pkg/sentry/control"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/runsc/boot"
- "gvisor.dev/gvisor/runsc/boot/platforms"
- "gvisor.dev/gvisor/runsc/specutils"
- "gvisor.dev/gvisor/runsc/testutil"
-)
-
-// waitForProcessList waits for the given process list to show up in the container.
-func waitForProcessList(cont *Container, want []*control.Process) error {
- cb := func() error {
- got, err := cont.Processes()
- if err != nil {
- err = fmt.Errorf("error getting process data from container: %v", err)
- return &backoff.PermanentError{Err: err}
- }
- if !procListsEqual(got, want) {
- return fmt.Errorf("container got process list: %s, want: %s", procListToString(got), procListToString(want))
- }
- return nil
- }
- // Gives plenty of time as tests can run slow under --race.
- return testutil.Poll(cb, 30*time.Second)
-}
-
-func waitForProcessCount(cont *Container, want int) error {
- cb := func() error {
- pss, err := cont.Processes()
- if err != nil {
- err = fmt.Errorf("error getting process data from container: %v", err)
- return &backoff.PermanentError{Err: err}
- }
- if got := len(pss); got != want {
- return fmt.Errorf("wrong process count, got: %d, want: %d", got, want)
- }
- return nil
- }
- // Gives plenty of time as tests can run slow under --race.
- return testutil.Poll(cb, 30*time.Second)
-}
-
-func blockUntilWaitable(pid int) error {
- _, _, err := specutils.RetryEintr(func() (uintptr, uintptr, error) {
- var err error
- _, _, err1 := syscall.Syscall6(syscall.SYS_WAITID, 1, uintptr(pid), 0, syscall.WEXITED|syscall.WNOWAIT, 0, 0)
- if err1 != 0 {
- err = err1
- }
- return 0, 0, err
- })
- return err
-}
-
-// procListsEqual is used to check whether 2 Process lists are equal for all
-// implemented fields.
-func procListsEqual(got, want []*control.Process) bool {
- if len(got) != len(want) {
- return false
- }
- for i := range got {
- pd1 := got[i]
- pd2 := want[i]
- // Zero out unimplemented and timing dependant fields.
- pd1.Time = ""
- pd1.STime = ""
- pd1.C = 0
- if *pd1 != *pd2 {
- return false
- }
- }
- return true
-}
-
-// getAndCheckProcLists is similar to waitForProcessList, but does not wait and retry the
-// test for equality. This is because we already confirmed that exec occurred.
-func getAndCheckProcLists(cont *Container, want []*control.Process) error {
- got, err := cont.Processes()
- if err != nil {
- return fmt.Errorf("error getting process data from container: %v", err)
- }
- if procListsEqual(got, want) {
- return nil
- }
- return fmt.Errorf("container got process list: %s, want: %s", procListToString(got), procListToString(want))
-}
-
-func procListToString(pl []*control.Process) string {
- strs := make([]string, 0, len(pl))
- for _, p := range pl {
- strs = append(strs, fmt.Sprintf("%+v", p))
- }
- return fmt.Sprintf("[%s]", strings.Join(strs, ","))
-}
-
-// createWriteableOutputFile creates an output file that can be read and
-// written to in the sandbox.
-func createWriteableOutputFile(path string) (*os.File, error) {
- outputFile, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666)
- if err != nil {
- return nil, fmt.Errorf("error creating file: %q, %v", path, err)
- }
-
- // Chmod to allow writing after umask.
- if err := outputFile.Chmod(0666); err != nil {
- return nil, fmt.Errorf("error chmoding file: %q, %v", path, err)
- }
- return outputFile, nil
-}
-
-func waitForFile(f *os.File) error {
- op := func() error {
- fi, err := f.Stat()
- if err != nil {
- return err
- }
- if fi.Size() == 0 {
- return fmt.Errorf("file %q is empty", f.Name())
- }
- return nil
- }
-
- return testutil.Poll(op, 30*time.Second)
-}
-
-// readOutputNum reads a file at given filepath and returns the int at the
-// requested position.
-func readOutputNum(file string, position int) (int, error) {
- f, err := os.Open(file)
- if err != nil {
- return 0, fmt.Errorf("error opening file: %q, %v", file, err)
- }
-
- // Ensure that there is content in output file.
- if err := waitForFile(f); err != nil {
- return 0, fmt.Errorf("error waiting for output file: %v", err)
- }
-
- b, err := ioutil.ReadAll(f)
- if err != nil {
- return 0, fmt.Errorf("error reading file: %v", err)
- }
- if len(b) == 0 {
- return 0, fmt.Errorf("error no content was read")
- }
-
- // Strip leading null bytes caused by file offset not being 0 upon restore.
- b = bytes.Trim(b, "\x00")
- nums := strings.Split(string(b), "\n")
-
- if position >= len(nums) {
- return 0, fmt.Errorf("position %v is not within the length of content %v", position, nums)
- }
- if position == -1 {
- // Expectation of newline at the end of last position.
- position = len(nums) - 2
- }
- num, err := strconv.Atoi(nums[position])
- if err != nil {
- return 0, fmt.Errorf("error getting number from file: %v", err)
- }
- return num, nil
-}
-
-// run starts the sandbox and waits for it to exit, checking that the
-// application succeeded.
-func run(spec *specs.Spec, conf *boot.Config) error {
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- return fmt.Errorf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- // Create, start and wait for the container.
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- Attached: true,
- }
- ws, err := Run(conf, args)
- if err != nil {
- return fmt.Errorf("running container: %v", err)
- }
- if !ws.Exited() || ws.ExitStatus() != 0 {
- return fmt.Errorf("container failed, waitStatus: %v", ws)
- }
- return nil
-}
-
-type configOption int
-
-const (
- overlay configOption = iota
- kvm
- nonExclusiveFS
-)
-
-var noOverlay = []configOption{kvm, nonExclusiveFS}
-var all = append(noOverlay, overlay)
-
-// configs generates different configurations to run tests.
-func configs(opts ...configOption) []*boot.Config {
- // Always load the default config.
- cs := []*boot.Config{testutil.TestConfig()}
-
- for _, o := range opts {
- c := testutil.TestConfig()
- switch o {
- case overlay:
- c.Overlay = true
- case kvm:
- // TODO(b/112165693): KVM tests are flaky. Disable until fixed.
- continue
-
- c.Platform = platforms.KVM
- case nonExclusiveFS:
- c.FileAccess = boot.FileAccessShared
- default:
- panic(fmt.Sprintf("unknown config option %v", o))
-
- }
- cs = append(cs, c)
- }
- return cs
-}
-
-// TestLifecycle tests the basic Create/Start/Signal/Destroy container lifecycle.
-// It verifies after each step that the container can be loaded from disk, and
-// has the correct status.
-func TestLifecycle(t *testing.T) {
- // Start the child reaper.
- childReaper := &testutil.Reaper{}
- childReaper.Start()
- defer childReaper.Stop()
-
- for _, conf := range configs(all...) {
- t.Logf("Running test with conf: %+v", conf)
- // The container will just sleep for a long time. We will kill it before
- // it finishes sleeping.
- spec := testutil.NewSpecWithArgs("sleep", "100")
-
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- // expectedPL lists the expected process state of the container.
- expectedPL := []*control.Process{
- {
- UID: 0,
- PID: 1,
- PPID: 0,
- C: 0,
- Cmd: "sleep",
- },
- }
- // Create the container.
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- }
- c, err := New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- defer c.Destroy()
-
- // Load the container from disk and check the status.
- c, err = Load(rootDir, args.ID)
- if err != nil {
- t.Fatalf("error loading container: %v", err)
- }
- if got, want := c.Status, Created; got != want {
- t.Errorf("container status got %v, want %v", got, want)
- }
-
- // List should return the container id.
- ids, err := List(rootDir)
- if err != nil {
- t.Fatalf("error listing containers: %v", err)
- }
- if got, want := ids, []string{args.ID}; !reflect.DeepEqual(got, want) {
- t.Errorf("container list got %v, want %v", got, want)
- }
-
- // Start the container.
- if err := c.Start(conf); err != nil {
- t.Fatalf("error starting container: %v", err)
- }
-
- // Load the container from disk and check the status.
- c, err = Load(rootDir, args.ID)
- if err != nil {
- t.Fatalf("error loading container: %v", err)
- }
- if got, want := c.Status, Running; got != want {
- t.Errorf("container status got %v, want %v", got, want)
- }
-
- // Verify that "sleep 100" is running.
- if err := waitForProcessList(c, expectedPL); err != nil {
- t.Error(err)
- }
-
- // Wait on the container.
- var wg sync.WaitGroup
- wg.Add(1)
- ch := make(chan struct{})
- go func() {
- ch <- struct{}{}
- ws, err := c.Wait()
- if err != nil {
- t.Fatalf("error waiting on container: %v", err)
- }
- if got, want := ws.Signal(), syscall.SIGTERM; got != want {
- t.Fatalf("got signal %v, want %v", got, want)
- }
- wg.Done()
- }()
-
- // Wait a bit to ensure that we've started waiting on the
- // container before we signal.
- <-ch
- time.Sleep(100 * time.Millisecond)
- // Send the container a SIGTERM which will cause it to stop.
- if err := c.SignalContainer(syscall.SIGTERM, false); err != nil {
- t.Fatalf("error sending signal %v to container: %v", syscall.SIGTERM, err)
- }
- // Wait for it to die.
- wg.Wait()
-
- // Load the container from disk and check the status.
- c, err = Load(rootDir, args.ID)
- if err != nil {
- t.Fatalf("error loading container: %v", err)
- }
- if got, want := c.Status, Stopped; got != want {
- t.Errorf("container status got %v, want %v", got, want)
- }
-
- // Destroy the container.
- if err := c.Destroy(); err != nil {
- t.Fatalf("error destroying container: %v", err)
- }
-
- // List should not return the container id.
- ids, err = List(rootDir)
- if err != nil {
- t.Fatalf("error listing containers: %v", err)
- }
- if len(ids) != 0 {
- t.Errorf("expected container list to be empty, but got %v", ids)
- }
-
- // Loading the container by id should fail.
- if _, err = Load(rootDir, args.ID); err == nil {
- t.Errorf("expected loading destroyed container to fail, but it did not")
- }
- }
-}
-
-// Test the we can execute the application with different path formats.
-func TestExePath(t *testing.T) {
- // Create two directories that will be prepended to PATH.
- firstPath, err := ioutil.TempDir(testutil.TmpDir(), "first")
- if err != nil {
- t.Fatal(err)
- }
- secondPath, err := ioutil.TempDir(testutil.TmpDir(), "second")
- if err != nil {
- t.Fatal(err)
- }
-
- // Create two minimal executables in the second path, two of which
- // will be masked by files in first path.
- for _, p := range []string{"unmasked", "masked1", "masked2"} {
- path := filepath.Join(secondPath, p)
- f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0777)
- if err != nil {
- t.Fatal(err)
- }
- defer f.Close()
- if _, err := io.WriteString(f, "#!/bin/true\n"); err != nil {
- t.Fatal(err)
- }
- }
-
- // Create a non-executable file in the first path which masks a healthy
- // executable in the second.
- nonExecutable := filepath.Join(firstPath, "masked1")
- f2, err := os.OpenFile(nonExecutable, os.O_CREATE|os.O_EXCL, 0666)
- if err != nil {
- t.Fatal(err)
- }
- f2.Close()
-
- // Create a non-regular file in the first path which masks a healthy
- // executable in the second.
- nonRegular := filepath.Join(firstPath, "masked2")
- if err := os.Mkdir(nonRegular, 0777); err != nil {
- t.Fatal(err)
- }
-
- for _, conf := range configs(overlay) {
- t.Logf("Running test with conf: %+v", conf)
- for _, test := range []struct {
- path string
- success bool
- }{
- {path: "true", success: true},
- {path: "bin/true", success: true},
- {path: "/bin/true", success: true},
- {path: "thisfiledoesntexit", success: false},
- {path: "bin/thisfiledoesntexit", success: false},
- {path: "/bin/thisfiledoesntexit", success: false},
-
- {path: "unmasked", success: true},
- {path: filepath.Join(firstPath, "unmasked"), success: false},
- {path: filepath.Join(secondPath, "unmasked"), success: true},
-
- {path: "masked1", success: true},
- {path: filepath.Join(firstPath, "masked1"), success: false},
- {path: filepath.Join(secondPath, "masked1"), success: true},
-
- {path: "masked2", success: true},
- {path: filepath.Join(firstPath, "masked2"), success: false},
- {path: filepath.Join(secondPath, "masked2"), success: true},
- } {
- spec := testutil.NewSpecWithArgs(test.path)
- spec.Process.Env = []string{
- fmt.Sprintf("PATH=%s:%s:%s", firstPath, secondPath, os.Getenv("PATH")),
- }
-
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("exec: %s, error setting up container: %v", test.path, err)
- }
-
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- Attached: true,
- }
- ws, err := Run(conf, args)
-
- os.RemoveAll(rootDir)
- os.RemoveAll(bundleDir)
-
- if test.success {
- if err != nil {
- t.Errorf("exec: %s, error running container: %v", test.path, err)
- }
- if ws.ExitStatus() != 0 {
- t.Errorf("exec: %s, got exit status %v want %v", test.path, ws.ExitStatus(), 0)
- }
- } else {
- if err == nil {
- t.Errorf("exec: %s, got: no error, want: error", test.path)
- }
- }
- }
- }
-}
-
-// Test the we can retrieve the application exit status from the container.
-func TestAppExitStatus(t *testing.T) {
- // First container will succeed.
- succSpec := testutil.NewSpecWithArgs("true")
- conf := testutil.TestConfig()
- rootDir, bundleDir, err := testutil.SetupContainer(succSpec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: succSpec,
- BundleDir: bundleDir,
- Attached: true,
- }
- ws, err := Run(conf, args)
- if err != nil {
- t.Fatalf("error running container: %v", err)
- }
- if ws.ExitStatus() != 0 {
- t.Errorf("got exit status %v want %v", ws.ExitStatus(), 0)
- }
-
- // Second container exits with non-zero status.
- wantStatus := 123
- errSpec := testutil.NewSpecWithArgs("bash", "-c", fmt.Sprintf("exit %d", wantStatus))
-
- rootDir2, bundleDir2, err := testutil.SetupContainer(errSpec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir2)
- defer os.RemoveAll(bundleDir2)
-
- args2 := Args{
- ID: testutil.UniqueContainerID(),
- Spec: errSpec,
- BundleDir: bundleDir2,
- Attached: true,
- }
- ws, err = Run(conf, args2)
- if err != nil {
- t.Fatalf("error running container: %v", err)
- }
- if ws.ExitStatus() != wantStatus {
- t.Errorf("got exit status %v want %v", ws.ExitStatus(), wantStatus)
- }
-}
-
-// TestExec verifies that a container can exec a new program.
-func TestExec(t *testing.T) {
- for _, conf := range configs(overlay) {
- t.Logf("Running test with conf: %+v", conf)
-
- const uid = 343
- spec := testutil.NewSpecWithArgs("sleep", "100")
-
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- // Create and start the container.
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- }
- cont, err := New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- defer cont.Destroy()
- if err := cont.Start(conf); err != nil {
- t.Fatalf("error starting container: %v", err)
- }
-
- // expectedPL lists the expected process state of the container.
- expectedPL := []*control.Process{
- {
- UID: 0,
- PID: 1,
- PPID: 0,
- C: 0,
- Cmd: "sleep",
- },
- {
- UID: uid,
- PID: 2,
- PPID: 0,
- C: 0,
- Cmd: "sleep",
- },
- }
-
- // Verify that "sleep 100" is running.
- if err := waitForProcessList(cont, expectedPL[:1]); err != nil {
- t.Error(err)
- }
-
- execArgs := &control.ExecArgs{
- Filename: "/bin/sleep",
- Argv: []string{"/bin/sleep", "5"},
- WorkingDirectory: "/",
- KUID: uid,
- }
-
- // Verify that "sleep 100" and "sleep 5" are running after exec.
- // First, start running exec (whick blocks).
- status := make(chan error, 1)
- go func() {
- exitStatus, err := cont.executeSync(execArgs)
- if err != nil {
- log.Debugf("error executing: %v", err)
- status <- err
- } else if exitStatus != 0 {
- log.Debugf("bad status: %d", exitStatus)
- status <- fmt.Errorf("failed with exit status: %v", exitStatus)
- } else {
- status <- nil
- }
- }()
-
- if err := waitForProcessList(cont, expectedPL); err != nil {
- t.Fatal(err)
- }
-
- // Ensure that exec finished without error.
- select {
- case <-time.After(10 * time.Second):
- t.Fatalf("container timed out waiting for exec to finish.")
- case st := <-status:
- if st != nil {
- t.Errorf("container failed to exec %v: %v", args, err)
- }
- }
- }
-}
-
-// TestKillPid verifies that we can signal individual exec'd processes.
-func TestKillPid(t *testing.T) {
- for _, conf := range configs(overlay) {
- t.Logf("Running test with conf: %+v", conf)
-
- app, err := testutil.FindFile("runsc/container/test_app/test_app")
- if err != nil {
- t.Fatal("error finding test_app:", err)
- }
-
- const nProcs = 4
- spec := testutil.NewSpecWithArgs(app, "task-tree", "--depth", strconv.Itoa(nProcs-1), "--width=1", "--pause=true")
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- // Create and start the container.
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- }
- cont, err := New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- defer cont.Destroy()
- if err := cont.Start(conf); err != nil {
- t.Fatalf("error starting container: %v", err)
- }
-
- // Verify that all processes are running.
- if err := waitForProcessCount(cont, nProcs); err != nil {
- t.Fatalf("timed out waiting for processes to start: %v", err)
- }
-
- // Kill the child process with the largest PID.
- procs, err := cont.Processes()
- if err != nil {
- t.Fatalf("failed to get process list: %v", err)
- }
- var pid int32
- for _, p := range procs {
- if pid < int32(p.PID) {
- pid = int32(p.PID)
- }
- }
- if err := cont.SignalProcess(syscall.SIGKILL, pid); err != nil {
- t.Fatalf("failed to signal process %d: %v", pid, err)
- }
-
- // Verify that one process is gone.
- if err := waitForProcessCount(cont, nProcs-1); err != nil {
- t.Fatal(err)
- }
-
- procs, err = cont.Processes()
- if err != nil {
- t.Fatalf("failed to get process list: %v", err)
- }
- for _, p := range procs {
- if pid == int32(p.PID) {
- t.Fatalf("pid %d is still alive, which should be killed", pid)
- }
- }
- }
-}
-
-// TestCheckpointRestore creates a container that continuously writes successive integers
-// to a file. To test checkpoint and restore functionality, the container is
-// checkpointed and the last number printed to the file is recorded. Then, it is restored in two
-// new containers and the first number printed from these containers is checked. Both should
-// be the next consecutive number after the last number from the checkpointed container.
-func TestCheckpointRestore(t *testing.T) {
- // Skip overlay because test requires writing to host file.
- for _, conf := range configs(noOverlay...) {
- t.Logf("Running test with conf: %+v", conf)
-
- dir, err := ioutil.TempDir(testutil.TmpDir(), "checkpoint-test")
- if err != nil {
- t.Fatalf("ioutil.TempDir failed: %v", err)
- }
- if err := os.Chmod(dir, 0777); err != nil {
- t.Fatalf("error chmoding file: %q, %v", dir, err)
- }
-
- outputPath := filepath.Join(dir, "output")
- outputFile, err := createWriteableOutputFile(outputPath)
- if err != nil {
- t.Fatalf("error creating output file: %v", err)
- }
- defer outputFile.Close()
-
- script := fmt.Sprintf("for ((i=0; ;i++)); do echo $i >> %q; sleep 1; done", outputPath)
- spec := testutil.NewSpecWithArgs("bash", "-c", script)
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- // Create and start the container.
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- }
- cont, err := New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- defer cont.Destroy()
- if err := cont.Start(conf); err != nil {
- t.Fatalf("error starting container: %v", err)
- }
-
- // Set the image path, which is where the checkpoint image will be saved.
- imagePath := filepath.Join(dir, "test-image-file")
-
- // Create the image file and open for writing.
- file, err := os.OpenFile(imagePath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0644)
- if err != nil {
- t.Fatalf("error opening new file at imagePath: %v", err)
- }
- defer file.Close()
-
- // Wait until application has ran.
- if err := waitForFile(outputFile); err != nil {
- t.Fatalf("Failed to wait for output file: %v", err)
- }
-
- // Checkpoint running container; save state into new file.
- if err := cont.Checkpoint(file); err != nil {
- t.Fatalf("error checkpointing container to empty file: %v", err)
- }
- defer os.RemoveAll(imagePath)
-
- lastNum, err := readOutputNum(outputPath, -1)
- if err != nil {
- t.Fatalf("error with outputFile: %v", err)
- }
-
- // Delete and recreate file before restoring.
- if err := os.Remove(outputPath); err != nil {
- t.Fatalf("error removing file")
- }
- outputFile2, err := createWriteableOutputFile(outputPath)
- if err != nil {
- t.Fatalf("error creating output file: %v", err)
- }
- defer outputFile2.Close()
-
- // Restore into a new container.
- args2 := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- }
- cont2, err := New(conf, args2)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- defer cont2.Destroy()
-
- if err := cont2.Restore(spec, conf, imagePath); err != nil {
- t.Fatalf("error restoring container: %v", err)
- }
-
- // Wait until application has ran.
- if err := waitForFile(outputFile2); err != nil {
- t.Fatalf("Failed to wait for output file: %v", err)
- }
-
- firstNum, err := readOutputNum(outputPath, 0)
- if err != nil {
- t.Fatalf("error with outputFile: %v", err)
- }
-
- // Check that lastNum is one less than firstNum and that the container picks
- // up from where it left off.
- if lastNum+1 != firstNum {
- t.Errorf("error numbers not in order, previous: %d, next: %d", lastNum, firstNum)
- }
- cont2.Destroy()
-
- // Restore into another container!
- // Delete and recreate file before restoring.
- if err := os.Remove(outputPath); err != nil {
- t.Fatalf("error removing file")
- }
- outputFile3, err := createWriteableOutputFile(outputPath)
- if err != nil {
- t.Fatalf("error creating output file: %v", err)
- }
- defer outputFile3.Close()
-
- // Restore into a new container.
- args3 := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- }
- cont3, err := New(conf, args3)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- defer cont3.Destroy()
-
- if err := cont3.Restore(spec, conf, imagePath); err != nil {
- t.Fatalf("error restoring container: %v", err)
- }
-
- // Wait until application has ran.
- if err := waitForFile(outputFile3); err != nil {
- t.Fatalf("Failed to wait for output file: %v", err)
- }
-
- firstNum2, err := readOutputNum(outputPath, 0)
- if err != nil {
- t.Fatalf("error with outputFile: %v", err)
- }
-
- // Check that lastNum is one less than firstNum and that the container picks
- // up from where it left off.
- if lastNum+1 != firstNum2 {
- t.Errorf("error numbers not in order, previous: %d, next: %d", lastNum, firstNum2)
- }
- cont3.Destroy()
- }
-}
-
-// TestUnixDomainSockets checks that Checkpoint/Restore works in cases
-// with filesystem Unix Domain Socket use.
-func TestUnixDomainSockets(t *testing.T) {
- // Skip overlay because test requires writing to host file.
- for _, conf := range configs(noOverlay...) {
- t.Logf("Running test with conf: %+v", conf)
-
- // UDS path is limited to 108 chars for compatibility with older systems.
- // Use '/tmp' (instead of testutil.TmpDir) to ensure the size limit is
- // not exceeded. Assumes '/tmp' exists in the system.
- dir, err := ioutil.TempDir("/tmp", "uds-test")
- if err != nil {
- t.Fatalf("ioutil.TempDir failed: %v", err)
- }
- defer os.RemoveAll(dir)
-
- outputPath := filepath.Join(dir, "uds_output")
- outputFile, err := os.OpenFile(outputPath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666)
- if err != nil {
- t.Fatalf("error creating output file: %v", err)
- }
- defer outputFile.Close()
-
- app, err := testutil.FindFile("runsc/container/test_app/test_app")
- if err != nil {
- t.Fatal("error finding test_app:", err)
- }
-
- socketPath := filepath.Join(dir, "uds_socket")
- defer os.Remove(socketPath)
-
- spec := testutil.NewSpecWithArgs(app, "uds", "--file", outputPath, "--socket", socketPath)
- spec.Process.User = specs.User{
- UID: uint32(os.Getuid()),
- GID: uint32(os.Getgid()),
- }
- spec.Mounts = []specs.Mount{{
- Type: "bind",
- Destination: dir,
- Source: dir,
- }}
-
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- // Create and start the container.
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- }
- cont, err := New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- defer cont.Destroy()
- if err := cont.Start(conf); err != nil {
- t.Fatalf("error starting container: %v", err)
- }
-
- // Set the image path, the location where the checkpoint image will be saved.
- imagePath := filepath.Join(dir, "test-image-file")
-
- // Create the image file and open for writing.
- file, err := os.OpenFile(imagePath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0644)
- if err != nil {
- t.Fatalf("error opening new file at imagePath: %v", err)
- }
- defer file.Close()
- defer os.RemoveAll(imagePath)
-
- // Wait until application has ran.
- if err := waitForFile(outputFile); err != nil {
- t.Fatalf("Failed to wait for output file: %v", err)
- }
-
- // Checkpoint running container; save state into new file.
- if err := cont.Checkpoint(file); err != nil {
- t.Fatalf("error checkpointing container to empty file: %v", err)
- }
-
- // Read last number outputted before checkpoint.
- lastNum, err := readOutputNum(outputPath, -1)
- if err != nil {
- t.Fatalf("error with outputFile: %v", err)
- }
-
- // Delete and recreate file before restoring.
- if err := os.Remove(outputPath); err != nil {
- t.Fatalf("error removing file")
- }
- outputFile2, err := os.OpenFile(outputPath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666)
- if err != nil {
- t.Fatalf("error creating output file: %v", err)
- }
- defer outputFile2.Close()
-
- // Restore into a new container.
- argsRestore := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- }
- contRestore, err := New(conf, argsRestore)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- defer contRestore.Destroy()
-
- if err := contRestore.Restore(spec, conf, imagePath); err != nil {
- t.Fatalf("error restoring container: %v", err)
- }
-
- // Wait until application has ran.
- if err := waitForFile(outputFile2); err != nil {
- t.Fatalf("Failed to wait for output file: %v", err)
- }
-
- // Read first number outputted after restore.
- firstNum, err := readOutputNum(outputPath, 0)
- if err != nil {
- t.Fatalf("error with outputFile: %v", err)
- }
-
- // Check that lastNum is one less than firstNum.
- if lastNum+1 != firstNum {
- t.Errorf("error numbers not consecutive, previous: %d, next: %d", lastNum, firstNum)
- }
- contRestore.Destroy()
- }
-}
-
-// TestPauseResume tests that we can successfully pause and resume a container.
-// It checks starts running sleep and executes another sleep. It pauses and checks
-// that both processes are still running: sleep will be paused and still exist.
-// It will then unpause and confirm that both processes are running. Then it will
-// wait until one sleep completes and check to make sure the other is running.
-func TestPauseResume(t *testing.T) {
- for _, conf := range configs(noOverlay...) {
- t.Logf("Running test with conf: %+v", conf)
- const uid = 343
- spec := testutil.NewSpecWithArgs("sleep", "20")
-
- lock, err := ioutil.TempFile(testutil.TmpDir(), "lock")
- if err != nil {
- t.Fatalf("error creating output file: %v", err)
- }
- defer lock.Close()
-
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- // Create and start the container.
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- }
- cont, err := New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- defer cont.Destroy()
- if err := cont.Start(conf); err != nil {
- t.Fatalf("error starting container: %v", err)
- }
-
- // expectedPL lists the expected process state of the container.
- expectedPL := []*control.Process{
- {
- UID: 0,
- PID: 1,
- PPID: 0,
- C: 0,
- Cmd: "sleep",
- },
- {
- UID: uid,
- PID: 2,
- PPID: 0,
- C: 0,
- Cmd: "bash",
- },
- }
-
- script := fmt.Sprintf("while [[ -f %q ]]; do sleep 0.1; done", lock.Name())
- execArgs := &control.ExecArgs{
- Filename: "/bin/bash",
- Argv: []string{"bash", "-c", script},
- WorkingDirectory: "/",
- KUID: uid,
- }
-
- // First, start running exec.
- _, err = cont.Execute(execArgs)
- if err != nil {
- t.Fatalf("error executing: %v", err)
- }
-
- // Verify that "sleep 5" is running.
- if err := waitForProcessList(cont, expectedPL); err != nil {
- t.Fatal(err)
- }
-
- // Pause the running container.
- if err := cont.Pause(); err != nil {
- t.Errorf("error pausing container: %v", err)
- }
- if got, want := cont.Status, Paused; got != want {
- t.Errorf("container status got %v, want %v", got, want)
- }
-
- if err := os.Remove(lock.Name()); err != nil {
- t.Fatalf("os.Remove(lock) failed: %v", err)
- }
- // Script loops and sleeps for 100ms. Give a bit a time for it to exit in
- // case pause didn't work.
- time.Sleep(200 * time.Millisecond)
-
- // Verify that the two processes still exist.
- if err := getAndCheckProcLists(cont, expectedPL); err != nil {
- t.Fatal(err)
- }
-
- // Resume the running container.
- if err := cont.Resume(); err != nil {
- t.Errorf("error pausing container: %v", err)
- }
- if got, want := cont.Status, Running; got != want {
- t.Errorf("container status got %v, want %v", got, want)
- }
-
- expectedPL2 := []*control.Process{
- {
- UID: 0,
- PID: 1,
- PPID: 0,
- C: 0,
- Cmd: "sleep",
- },
- }
-
- // Verify that deleting the file triggered the process to exit.
- if err := waitForProcessList(cont, expectedPL2); err != nil {
- t.Fatal(err)
- }
- }
-}
-
-// TestPauseResumeStatus makes sure that the statuses are set correctly
-// with calls to pause and resume and that pausing and resuming only
-// occurs given the correct state.
-func TestPauseResumeStatus(t *testing.T) {
- spec := testutil.NewSpecWithArgs("sleep", "20")
- conf := testutil.TestConfig()
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- // Create and start the container.
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- }
- cont, err := New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- defer cont.Destroy()
- if err := cont.Start(conf); err != nil {
- t.Fatalf("error starting container: %v", err)
- }
-
- // Pause the running container.
- if err := cont.Pause(); err != nil {
- t.Errorf("error pausing container: %v", err)
- }
- if got, want := cont.Status, Paused; got != want {
- t.Errorf("container status got %v, want %v", got, want)
- }
-
- // Try to Pause again. Should cause error.
- if err := cont.Pause(); err == nil {
- t.Errorf("error pausing container that was already paused: %v", err)
- }
- if got, want := cont.Status, Paused; got != want {
- t.Errorf("container status got %v, want %v", got, want)
- }
-
- // Resume the running container.
- if err := cont.Resume(); err != nil {
- t.Errorf("error resuming container: %v", err)
- }
- if got, want := cont.Status, Running; got != want {
- t.Errorf("container status got %v, want %v", got, want)
- }
-
- // Try to resume again. Should cause error.
- if err := cont.Resume(); err == nil {
- t.Errorf("error resuming container already running: %v", err)
- }
- if got, want := cont.Status, Running; got != want {
- t.Errorf("container status got %v, want %v", got, want)
- }
-}
-
-// TestCapabilities verifies that:
-// - Running exec as non-root UID and GID will result in an error (because the
-// executable file can't be read).
-// - Running exec as non-root with CAP_DAC_OVERRIDE succeeds because it skips
-// this check.
-func TestCapabilities(t *testing.T) {
- // Pick uid/gid different than ours.
- uid := auth.KUID(os.Getuid() + 1)
- gid := auth.KGID(os.Getgid() + 1)
-
- for _, conf := range configs(all...) {
- t.Logf("Running test with conf: %+v", conf)
-
- spec := testutil.NewSpecWithArgs("sleep", "100")
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- // Create and start the container.
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- }
- cont, err := New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- defer cont.Destroy()
- if err := cont.Start(conf); err != nil {
- t.Fatalf("error starting container: %v", err)
- }
-
- // expectedPL lists the expected process state of the container.
- expectedPL := []*control.Process{
- {
- UID: 0,
- PID: 1,
- PPID: 0,
- C: 0,
- Cmd: "sleep",
- },
- {
- UID: uid,
- PID: 2,
- PPID: 0,
- C: 0,
- Cmd: "exe",
- },
- }
- if err := waitForProcessList(cont, expectedPL[:1]); err != nil {
- t.Fatalf("Failed to wait for sleep to start, err: %v", err)
- }
-
- // Create an executable that can't be run with the specified UID:GID.
- // This shouldn't be callable within the container until we add the
- // CAP_DAC_OVERRIDE capability to skip the access check.
- exePath := filepath.Join(rootDir, "exe")
- if err := ioutil.WriteFile(exePath, []byte("#!/bin/sh\necho hello"), 0770); err != nil {
- t.Fatalf("couldn't create executable: %v", err)
- }
- defer os.Remove(exePath)
-
- // Need to traverse the intermediate directory.
- os.Chmod(rootDir, 0755)
-
- execArgs := &control.ExecArgs{
- Filename: exePath,
- Argv: []string{exePath},
- WorkingDirectory: "/",
- KUID: uid,
- KGID: gid,
- Capabilities: &auth.TaskCapabilities{},
- }
-
- // "exe" should fail because we don't have the necessary permissions.
- if _, err := cont.executeSync(execArgs); err == nil {
- t.Fatalf("container executed without error, but an error was expected")
- }
-
- // Now we run with the capability enabled and should succeed.
- execArgs.Capabilities = &auth.TaskCapabilities{
- EffectiveCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE),
- }
- // "exe" should not fail this time.
- if _, err := cont.executeSync(execArgs); err != nil {
- t.Fatalf("container failed to exec %v: %v", args, err)
- }
- }
-}
-
-// TestRunNonRoot checks that sandbox can be configured when running as
-// non-privileged user.
-func TestRunNonRoot(t *testing.T) {
- for _, conf := range configs(noOverlay...) {
- t.Logf("Running test with conf: %+v", conf)
-
- spec := testutil.NewSpecWithArgs("/bin/true")
-
- // Set a random user/group with no access to "blocked" dir.
- spec.Process.User.UID = 343
- spec.Process.User.GID = 2401
- spec.Process.Capabilities = nil
-
- // User running inside container can't list '$TMP/blocked' and would fail to
- // mount it.
- dir, err := ioutil.TempDir(testutil.TmpDir(), "blocked")
- if err != nil {
- t.Fatalf("ioutil.TempDir() failed: %v", err)
- }
- if err := os.Chmod(dir, 0700); err != nil {
- t.Fatalf("os.MkDir(%q) failed: %v", dir, err)
- }
- dir = path.Join(dir, "test")
- if err := os.Mkdir(dir, 0755); err != nil {
- t.Fatalf("os.MkDir(%q) failed: %v", dir, err)
- }
-
- src, err := ioutil.TempDir(testutil.TmpDir(), "src")
- if err != nil {
- t.Fatalf("ioutil.TempDir() failed: %v", err)
- }
-
- spec.Mounts = append(spec.Mounts, specs.Mount{
- Destination: dir,
- Source: src,
- Type: "bind",
- })
-
- if err := run(spec, conf); err != nil {
- t.Fatalf("error running sandbox: %v", err)
- }
- }
-}
-
-// TestMountNewDir checks that runsc will create destination directory if it
-// doesn't exit.
-func TestMountNewDir(t *testing.T) {
- for _, conf := range configs(overlay) {
- t.Logf("Running test with conf: %+v", conf)
-
- root, err := ioutil.TempDir(testutil.TmpDir(), "root")
- if err != nil {
- t.Fatal("ioutil.TempDir() failed:", err)
- }
-
- srcDir := path.Join(root, "src", "dir", "anotherdir")
- if err := os.MkdirAll(srcDir, 0755); err != nil {
- t.Fatalf("os.MkDir(%q) failed: %v", srcDir, err)
- }
-
- mountDir := path.Join(root, "dir", "anotherdir")
-
- spec := testutil.NewSpecWithArgs("/bin/ls", mountDir)
- spec.Mounts = append(spec.Mounts, specs.Mount{
- Destination: mountDir,
- Source: srcDir,
- Type: "bind",
- })
-
- if err := run(spec, conf); err != nil {
- t.Fatalf("error running sandbox: %v", err)
- }
- }
-}
-
-func TestReadonlyRoot(t *testing.T) {
- for _, conf := range configs(overlay) {
- t.Logf("Running test with conf: %+v", conf)
-
- spec := testutil.NewSpecWithArgs("/bin/touch", "/foo")
- spec.Root.Readonly = true
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- // Create, start and wait for the container.
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- }
- c, err := New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- defer c.Destroy()
- if err := c.Start(conf); err != nil {
- t.Fatalf("error starting container: %v", err)
- }
-
- ws, err := c.Wait()
- if err != nil {
- t.Fatalf("error waiting on container: %v", err)
- }
- if !ws.Exited() || syscall.Errno(ws.ExitStatus()) != syscall.EPERM {
- t.Fatalf("container failed, waitStatus: %v", ws)
- }
- }
-}
-
-func TestUIDMap(t *testing.T) {
- for _, conf := range configs(noOverlay...) {
- t.Logf("Running test with conf: %+v", conf)
- testDir, err := ioutil.TempDir(testutil.TmpDir(), "test-mount")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(testDir)
- testFile := path.Join(testDir, "testfile")
-
- spec := testutil.NewSpecWithArgs("touch", "/tmp/testfile")
- uid := os.Getuid()
- gid := os.Getgid()
- spec.Linux = &specs.Linux{
- Namespaces: []specs.LinuxNamespace{
- {Type: specs.UserNamespace},
- {Type: specs.PIDNamespace},
- {Type: specs.MountNamespace},
- },
- UIDMappings: []specs.LinuxIDMapping{
- {
- ContainerID: 0,
- HostID: uint32(uid),
- Size: 1,
- },
- },
- GIDMappings: []specs.LinuxIDMapping{
- {
- ContainerID: 0,
- HostID: uint32(gid),
- Size: 1,
- },
- },
- }
-
- spec.Mounts = append(spec.Mounts, specs.Mount{
- Destination: "/tmp",
- Source: testDir,
- Type: "bind",
- })
-
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- // Create, start and wait for the container.
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- }
- c, err := New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- defer c.Destroy()
- if err := c.Start(conf); err != nil {
- t.Fatalf("error starting container: %v", err)
- }
-
- ws, err := c.Wait()
- if err != nil {
- t.Fatalf("error waiting on container: %v", err)
- }
- if !ws.Exited() || ws.ExitStatus() != 0 {
- t.Fatalf("container failed, waitStatus: %v", ws)
- }
- st := syscall.Stat_t{}
- if err := syscall.Stat(testFile, &st); err != nil {
- t.Fatalf("error stat /testfile: %v", err)
- }
-
- if st.Uid != uint32(uid) || st.Gid != uint32(gid) {
- t.Fatalf("UID: %d (%d) GID: %d (%d)", st.Uid, uid, st.Gid, gid)
- }
- }
-}
-
-func TestReadonlyMount(t *testing.T) {
- for _, conf := range configs(overlay) {
- t.Logf("Running test with conf: %+v", conf)
-
- dir, err := ioutil.TempDir(testutil.TmpDir(), "ro-mount")
- spec := testutil.NewSpecWithArgs("/bin/touch", path.Join(dir, "file"))
- if err != nil {
- t.Fatalf("ioutil.TempDir() failed: %v", err)
- }
- spec.Mounts = append(spec.Mounts, specs.Mount{
- Destination: dir,
- Source: dir,
- Type: "bind",
- Options: []string{"ro"},
- })
- spec.Root.Readonly = false
-
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- // Create, start and wait for the container.
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- }
- c, err := New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- defer c.Destroy()
- if err := c.Start(conf); err != nil {
- t.Fatalf("error starting container: %v", err)
- }
-
- ws, err := c.Wait()
- if err != nil {
- t.Fatalf("error waiting on container: %v", err)
- }
- if !ws.Exited() || syscall.Errno(ws.ExitStatus()) != syscall.EPERM {
- t.Fatalf("container failed, waitStatus: %v", ws)
- }
- }
-}
-
-// TestAbbreviatedIDs checks that runsc supports using abbreviated container
-// IDs in place of full IDs.
-func TestAbbreviatedIDs(t *testing.T) {
- rootDir, err := testutil.SetupRootDir()
- if err != nil {
- t.Fatalf("error creating root dir: %v", err)
- }
- defer os.RemoveAll(rootDir)
-
- conf := testutil.TestConfigWithRoot(rootDir)
-
- cids := []string{
- "foo-" + testutil.UniqueContainerID(),
- "bar-" + testutil.UniqueContainerID(),
- "baz-" + testutil.UniqueContainerID(),
- }
- for _, cid := range cids {
- spec := testutil.NewSpecWithArgs("sleep", "100")
- bundleDir, err := testutil.SetupBundleDir(spec)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(bundleDir)
-
- // Create and start the container.
- args := Args{
- ID: cid,
- Spec: spec,
- BundleDir: bundleDir,
- }
- cont, err := New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- defer cont.Destroy()
- }
-
- // These should all be unambigious.
- unambiguous := map[string]string{
- "f": cids[0],
- cids[0]: cids[0],
- "bar": cids[1],
- cids[1]: cids[1],
- "baz": cids[2],
- cids[2]: cids[2],
- }
- for shortid, longid := range unambiguous {
- if _, err := Load(rootDir, shortid); err != nil {
- t.Errorf("%q should resolve to %q: %v", shortid, longid, err)
- }
- }
-
- // These should be ambiguous.
- ambiguous := []string{
- "b",
- "ba",
- }
- for _, shortid := range ambiguous {
- if s, err := Load(rootDir, shortid); err == nil {
- t.Errorf("%q should be ambiguous, but resolved to %q", shortid, s.ID)
- }
- }
-}
-
-func TestGoferExits(t *testing.T) {
- spec := testutil.NewSpecWithArgs("/bin/sleep", "10000")
- conf := testutil.TestConfig()
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- // Create and start the container.
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- }
- c, err := New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- defer c.Destroy()
- if err := c.Start(conf); err != nil {
- t.Fatalf("error starting container: %v", err)
- }
-
- // Kill sandbox and expect gofer to exit on its own.
- sandboxProc, err := os.FindProcess(c.Sandbox.Pid)
- if err != nil {
- t.Fatalf("error finding sandbox process: %v", err)
- }
- if err := sandboxProc.Kill(); err != nil {
- t.Fatalf("error killing sandbox process: %v", err)
- }
-
- err = blockUntilWaitable(c.GoferPid)
- if err != nil && err != syscall.ECHILD {
- t.Errorf("error waiting for gofer to exit: %v", err)
- }
-}
-
-func TestRootNotMount(t *testing.T) {
- appSym, err := testutil.FindFile("runsc/container/test_app/test_app")
- if err != nil {
- t.Fatal("error finding test_app:", err)
- }
-
- app, err := filepath.EvalSymlinks(appSym)
- if err != nil {
- t.Fatalf("error resolving %q symlink: %v", appSym, err)
- }
- log.Infof("App path %q is a symlink to %q", appSym, app)
-
- static, err := testutil.IsStatic(app)
- if err != nil {
- t.Fatalf("error reading application binary: %v", err)
- }
- if !static {
- // This happens during race builds; we cannot map in shared
- // libraries also, so we need to skip the test.
- t.Skip()
- }
-
- root := filepath.Dir(app)
- exe := "/" + filepath.Base(app)
- log.Infof("Executing %q in %q", exe, root)
-
- spec := testutil.NewSpecWithArgs(exe, "help")
- spec.Root.Path = root
- spec.Root.Readonly = true
- spec.Mounts = nil
-
- conf := testutil.TestConfig()
- if err := run(spec, conf); err != nil {
- t.Fatalf("error running sandbox: %v", err)
- }
-}
-
-func TestUserLog(t *testing.T) {
- app, err := testutil.FindFile("runsc/container/test_app/test_app")
- if err != nil {
- t.Fatal("error finding test_app:", err)
- }
-
- // sched_rr_get_interval = 148 - not implemented in gvisor.
- spec := testutil.NewSpecWithArgs(app, "syscall", "--syscall=148")
- conf := testutil.TestConfig()
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- dir, err := ioutil.TempDir(testutil.TmpDir(), "user_log_test")
- if err != nil {
- t.Fatalf("error creating tmp dir: %v", err)
- }
- userLog := filepath.Join(dir, "user.log")
-
- // Create, start and wait for the container.
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- UserLog: userLog,
- Attached: true,
- }
- ws, err := Run(conf, args)
- if err != nil {
- t.Fatalf("error running container: %v", err)
- }
- if !ws.Exited() || ws.ExitStatus() != 0 {
- t.Fatalf("container failed, waitStatus: %v", ws)
- }
-
- out, err := ioutil.ReadFile(userLog)
- if err != nil {
- t.Fatalf("error opening user log file %q: %v", userLog, err)
- }
- if want := "Unsupported syscall: sched_rr_get_interval"; !strings.Contains(string(out), want) {
- t.Errorf("user log file doesn't contain %q, out: %s", want, string(out))
- }
-}
-
-func TestWaitOnExitedSandbox(t *testing.T) {
- for _, conf := range configs(all...) {
- t.Logf("Running test with conf: %+v", conf)
-
- // Run a shell that sleeps for 1 second and then exits with a
- // non-zero code.
- const wantExit = 17
- cmd := fmt.Sprintf("sleep 1; exit %d", wantExit)
- spec := testutil.NewSpecWithArgs("/bin/sh", "-c", cmd)
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- // Create and Start the container.
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- }
- c, err := New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- defer c.Destroy()
- if err := c.Start(conf); err != nil {
- t.Fatalf("error starting container: %v", err)
- }
-
- // Wait on the sandbox. This will make an RPC to the sandbox
- // and get the actual exit status of the application.
- ws, err := c.Wait()
- if err != nil {
- t.Fatalf("error waiting on container: %v", err)
- }
- if got := ws.ExitStatus(); got != wantExit {
- t.Errorf("got exit status %d, want %d", got, wantExit)
- }
-
- // Now the sandbox has exited, but the zombie sandbox process
- // still exists. Calling Wait() now will return the sandbox
- // exit status.
- ws, err = c.Wait()
- if err != nil {
- t.Fatalf("error waiting on container: %v", err)
- }
- if got := ws.ExitStatus(); got != wantExit {
- t.Errorf("got exit status %d, want %d", got, wantExit)
- }
- }
-}
-
-func TestDestroyNotStarted(t *testing.T) {
- spec := testutil.NewSpecWithArgs("/bin/sleep", "100")
- conf := testutil.TestConfig()
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- // Create the container and check that it can be destroyed.
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- }
- c, err := New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- if err := c.Destroy(); err != nil {
- t.Fatalf("deleting non-started container failed: %v", err)
- }
-}
-
-// TestDestroyStarting attempts to force a race between start and destroy.
-func TestDestroyStarting(t *testing.T) {
- for i := 0; i < 10; i++ {
- spec := testutil.NewSpecWithArgs("/bin/sleep", "100")
- conf := testutil.TestConfig()
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- // Create the container and check that it can be destroyed.
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- }
- c, err := New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
-
- // Container is not thread safe, so load another instance to run in
- // concurrently.
- startCont, err := Load(rootDir, args.ID)
- if err != nil {
- t.Fatalf("error loading container: %v", err)
- }
- wg := sync.WaitGroup{}
- wg.Add(1)
- go func() {
- defer wg.Done()
- // Ignore failures, start can fail if destroy runs first.
- startCont.Start(conf)
- }()
-
- wg.Add(1)
- go func() {
- defer wg.Done()
- if err := c.Destroy(); err != nil {
- t.Errorf("deleting non-started container failed: %v", err)
- }
- }()
- wg.Wait()
- }
-}
-
-func TestCreateWorkingDir(t *testing.T) {
- for _, conf := range configs(overlay) {
- t.Logf("Running test with conf: %+v", conf)
-
- tmpDir, err := ioutil.TempDir(testutil.TmpDir(), "cwd-create")
- if err != nil {
- t.Fatalf("ioutil.TempDir() failed: %v", err)
- }
- dir := path.Join(tmpDir, "new/working/dir")
-
- // touch will fail if the directory doesn't exist.
- spec := testutil.NewSpecWithArgs("/bin/touch", path.Join(dir, "file"))
- spec.Process.Cwd = dir
- spec.Root.Readonly = true
-
- if err := run(spec, conf); err != nil {
- t.Fatalf("Error running container: %v", err)
- }
- }
-}
-
-// TestMountPropagation verifies that mount propagates to slave but not to
-// private mounts.
-func TestMountPropagation(t *testing.T) {
- // Setup dir structure:
- // - src: is mounted as shared and is used as source for both private and
- // slave mounts
- // - dir: will be bind mounted inside src and should propagate to slave
- tmpDir, err := ioutil.TempDir(testutil.TmpDir(), "mount")
- if err != nil {
- t.Fatalf("ioutil.TempDir() failed: %v", err)
- }
- src := filepath.Join(tmpDir, "src")
- srcMnt := filepath.Join(src, "mnt")
- dir := filepath.Join(tmpDir, "dir")
- for _, path := range []string{src, srcMnt, dir} {
- if err := os.MkdirAll(path, 0777); err != nil {
- t.Fatalf("MkdirAll(%q): %v", path, err)
- }
- }
- dirFile := filepath.Join(dir, "file")
- f, err := os.Create(dirFile)
- if err != nil {
- t.Fatalf("os.Create(%q): %v", dirFile, err)
- }
- f.Close()
-
- // Setup src as a shared mount.
- if err := syscall.Mount(src, src, "bind", syscall.MS_BIND, ""); err != nil {
- t.Fatalf("mount(%q, %q, MS_BIND): %v", dir, srcMnt, err)
- }
- if err := syscall.Mount("", src, "", syscall.MS_SHARED, ""); err != nil {
- t.Fatalf("mount(%q, MS_SHARED): %v", srcMnt, err)
- }
-
- spec := testutil.NewSpecWithArgs("sleep", "1000")
-
- priv := filepath.Join(tmpDir, "priv")
- slave := filepath.Join(tmpDir, "slave")
- spec.Mounts = []specs.Mount{
- {
- Source: src,
- Destination: priv,
- Type: "bind",
- Options: []string{"private"},
- },
- {
- Source: src,
- Destination: slave,
- Type: "bind",
- Options: []string{"slave"},
- },
- }
-
- conf := testutil.TestConfig()
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- }
- cont, err := New(conf, args)
- if err != nil {
- t.Fatalf("creating container: %v", err)
- }
- defer cont.Destroy()
-
- if err := cont.Start(conf); err != nil {
- t.Fatalf("starting container: %v", err)
- }
-
- // After the container is started, mount dir inside source and check what
- // happens to both destinations.
- if err := syscall.Mount(dir, srcMnt, "bind", syscall.MS_BIND, ""); err != nil {
- t.Fatalf("mount(%q, %q, MS_BIND): %v", dir, srcMnt, err)
- }
-
- // Check that mount didn't propagate to private mount.
- privFile := filepath.Join(priv, "mnt", "file")
- execArgs := &control.ExecArgs{
- Filename: "/usr/bin/test",
- Argv: []string{"test", "!", "-f", privFile},
- }
- if ws, err := cont.executeSync(execArgs); err != nil || ws != 0 {
- t.Fatalf("exec: test ! -f %q, ws: %v, err: %v", privFile, ws, err)
- }
-
- // Check that mount propagated to slave mount.
- slaveFile := filepath.Join(slave, "mnt", "file")
- execArgs = &control.ExecArgs{
- Filename: "/usr/bin/test",
- Argv: []string{"test", "-f", slaveFile},
- }
- if ws, err := cont.executeSync(execArgs); err != nil || ws != 0 {
- t.Fatalf("exec: test -f %q, ws: %v, err: %v", privFile, ws, err)
- }
-}
-
-func TestMountSymlink(t *testing.T) {
- for _, conf := range configs(overlay) {
- t.Logf("Running test with conf: %+v", conf)
-
- dir, err := ioutil.TempDir(testutil.TmpDir(), "mount-symlink")
- if err != nil {
- t.Fatalf("ioutil.TempDir() failed: %v", err)
- }
-
- source := path.Join(dir, "source")
- target := path.Join(dir, "target")
- for _, path := range []string{source, target} {
- if err := os.MkdirAll(path, 0777); err != nil {
- t.Fatalf("os.MkdirAll(): %v", err)
- }
- }
- f, err := os.Create(path.Join(source, "file"))
- if err != nil {
- t.Fatalf("os.Create(): %v", err)
- }
- f.Close()
-
- link := path.Join(dir, "link")
- if err := os.Symlink(target, link); err != nil {
- t.Fatalf("os.Symlink(%q, %q): %v", target, link, err)
- }
-
- spec := testutil.NewSpecWithArgs("/bin/sleep", "1000")
-
- // Mount to a symlink to ensure the mount code will follow it and mount
- // at the symlink target.
- spec.Mounts = append(spec.Mounts, specs.Mount{
- Type: "bind",
- Destination: link,
- Source: source,
- })
-
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- }
- cont, err := New(conf, args)
- if err != nil {
- t.Fatalf("creating container: %v", err)
- }
- defer cont.Destroy()
-
- if err := cont.Start(conf); err != nil {
- t.Fatalf("starting container: %v", err)
- }
-
- // Check that symlink was resolved and mount was created where the symlink
- // is pointing to.
- file := path.Join(target, "file")
- execArgs := &control.ExecArgs{
- Filename: "/usr/bin/test",
- Argv: []string{"test", "-f", file},
- }
- if ws, err := cont.executeSync(execArgs); err != nil || ws != 0 {
- t.Fatalf("exec: test -f %q, ws: %v, err: %v", file, ws, err)
- }
- }
-}
-
-// executeSync synchronously executes a new process.
-func (cont *Container) executeSync(args *control.ExecArgs) (syscall.WaitStatus, error) {
- pid, err := cont.Execute(args)
- if err != nil {
- return 0, fmt.Errorf("error executing: %v", err)
- }
- ws, err := cont.WaitPID(pid)
- if err != nil {
- return 0, fmt.Errorf("error waiting: %v", err)
- }
- return ws, nil
-}
-
-func TestMain(m *testing.M) {
- log.SetLevel(log.Debug)
- flag.Parse()
- if err := testutil.ConfigureExePath(); err != nil {
- panic(err.Error())
- }
- specutils.MaybeRunAsRoot()
- os.Exit(m.Run())
-}
diff --git a/runsc/container/multi_container_test.go b/runsc/container/multi_container_test.go
deleted file mode 100644
index bd45a5118..000000000
--- a/runsc/container/multi_container_test.go
+++ /dev/null
@@ -1,1548 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package container
-
-import (
- "fmt"
- "io/ioutil"
- "math"
- "os"
- "path"
- "path/filepath"
- "strings"
- "sync"
- "syscall"
- "testing"
- "time"
-
- specs "github.com/opencontainers/runtime-spec/specs-go"
- "gvisor.dev/gvisor/pkg/sentry/control"
- "gvisor.dev/gvisor/pkg/sentry/kernel"
- "gvisor.dev/gvisor/runsc/boot"
- "gvisor.dev/gvisor/runsc/specutils"
- "gvisor.dev/gvisor/runsc/testutil"
-)
-
-func createSpecs(cmds ...[]string) ([]*specs.Spec, []string) {
- var specs []*specs.Spec
- var ids []string
- rootID := testutil.UniqueContainerID()
-
- for i, cmd := range cmds {
- spec := testutil.NewSpecWithArgs(cmd...)
- if i == 0 {
- spec.Annotations = map[string]string{
- specutils.ContainerdContainerTypeAnnotation: specutils.ContainerdContainerTypeSandbox,
- }
- ids = append(ids, rootID)
- } else {
- spec.Annotations = map[string]string{
- specutils.ContainerdContainerTypeAnnotation: specutils.ContainerdContainerTypeContainer,
- specutils.ContainerdSandboxIDAnnotation: rootID,
- }
- ids = append(ids, testutil.UniqueContainerID())
- }
- specs = append(specs, spec)
- }
- return specs, ids
-}
-
-func startContainers(conf *boot.Config, specs []*specs.Spec, ids []string) ([]*Container, func(), error) {
- // Setup root dir if one hasn't been provided.
- if len(conf.RootDir) == 0 {
- rootDir, err := testutil.SetupRootDir()
- if err != nil {
- return nil, nil, fmt.Errorf("error creating root dir: %v", err)
- }
- conf.RootDir = rootDir
- }
-
- var containers []*Container
- var bundles []string
- cleanup := func() {
- for _, c := range containers {
- c.Destroy()
- }
- for _, b := range bundles {
- os.RemoveAll(b)
- }
- os.RemoveAll(conf.RootDir)
- }
- for i, spec := range specs {
- bundleDir, err := testutil.SetupBundleDir(spec)
- if err != nil {
- cleanup()
- return nil, nil, fmt.Errorf("error setting up container: %v", err)
- }
- bundles = append(bundles, bundleDir)
-
- args := Args{
- ID: ids[i],
- Spec: spec,
- BundleDir: bundleDir,
- }
- cont, err := New(conf, args)
- if err != nil {
- cleanup()
- return nil, nil, fmt.Errorf("error creating container: %v", err)
- }
- containers = append(containers, cont)
-
- if err := cont.Start(conf); err != nil {
- cleanup()
- return nil, nil, fmt.Errorf("error starting container: %v", err)
- }
- }
- return containers, cleanup, nil
-}
-
-type execDesc struct {
- c *Container
- cmd []string
- want int
- desc string
-}
-
-func execMany(execs []execDesc) error {
- for _, exec := range execs {
- args := &control.ExecArgs{Argv: exec.cmd}
- if ws, err := exec.c.executeSync(args); err != nil {
- return fmt.Errorf("error executing %+v: %v", args, err)
- } else if ws.ExitStatus() != exec.want {
- return fmt.Errorf("%q: exec %q got exit status: %d, want: %d", exec.desc, exec.cmd, ws.ExitStatus(), exec.want)
- }
- }
- return nil
-}
-
-func createSharedMount(mount specs.Mount, name string, pod ...*specs.Spec) {
- for _, spec := range pod {
- spec.Annotations[path.Join(boot.MountPrefix, name, "source")] = mount.Source
- spec.Annotations[path.Join(boot.MountPrefix, name, "type")] = mount.Type
- spec.Annotations[path.Join(boot.MountPrefix, name, "share")] = "pod"
- if len(mount.Options) > 0 {
- spec.Annotations[path.Join(boot.MountPrefix, name, "options")] = strings.Join(mount.Options, ",")
- }
- }
-}
-
-// TestMultiContainerSanity checks that it is possible to run 2 dead-simple
-// containers in the same sandbox.
-func TestMultiContainerSanity(t *testing.T) {
- for _, conf := range configs(all...) {
- t.Logf("Running test with conf: %+v", conf)
-
- // Setup the containers.
- sleep := []string{"sleep", "100"}
- specs, ids := createSpecs(sleep, sleep)
- containers, cleanup, err := startContainers(conf, specs, ids)
- if err != nil {
- t.Fatalf("error starting containers: %v", err)
- }
- defer cleanup()
-
- // Check via ps that multiple processes are running.
- expectedPL := []*control.Process{
- {PID: 1, Cmd: "sleep"},
- }
- if err := waitForProcessList(containers[0], expectedPL); err != nil {
- t.Errorf("failed to wait for sleep to start: %v", err)
- }
- expectedPL = []*control.Process{
- {PID: 2, Cmd: "sleep"},
- }
- if err := waitForProcessList(containers[1], expectedPL); err != nil {
- t.Errorf("failed to wait for sleep to start: %v", err)
- }
- }
-}
-
-// TestMultiPIDNS checks that it is possible to run 2 dead-simple
-// containers in the same sandbox with different pidns.
-func TestMultiPIDNS(t *testing.T) {
- for _, conf := range configs(all...) {
- t.Logf("Running test with conf: %+v", conf)
-
- // Setup the containers.
- sleep := []string{"sleep", "100"}
- testSpecs, ids := createSpecs(sleep, sleep)
- testSpecs[1].Linux = &specs.Linux{
- Namespaces: []specs.LinuxNamespace{
- {
- Type: "pid",
- },
- },
- }
-
- containers, cleanup, err := startContainers(conf, testSpecs, ids)
- if err != nil {
- t.Fatalf("error starting containers: %v", err)
- }
- defer cleanup()
-
- // Check via ps that multiple processes are running.
- expectedPL := []*control.Process{
- {PID: 1, Cmd: "sleep"},
- }
- if err := waitForProcessList(containers[0], expectedPL); err != nil {
- t.Errorf("failed to wait for sleep to start: %v", err)
- }
- expectedPL = []*control.Process{
- {PID: 1, Cmd: "sleep"},
- }
- if err := waitForProcessList(containers[1], expectedPL); err != nil {
- t.Errorf("failed to wait for sleep to start: %v", err)
- }
- }
-}
-
-// TestMultiPIDNSPath checks the pidns path.
-func TestMultiPIDNSPath(t *testing.T) {
- for _, conf := range configs(all...) {
- t.Logf("Running test with conf: %+v", conf)
-
- // Setup the containers.
- sleep := []string{"sleep", "100"}
- testSpecs, ids := createSpecs(sleep, sleep, sleep)
- testSpecs[0].Linux = &specs.Linux{
- Namespaces: []specs.LinuxNamespace{
- {
- Type: "pid",
- Path: "/proc/1/ns/pid",
- },
- },
- }
- testSpecs[1].Linux = &specs.Linux{
- Namespaces: []specs.LinuxNamespace{
- {
- Type: "pid",
- Path: "/proc/1/ns/pid",
- },
- },
- }
- testSpecs[2].Linux = &specs.Linux{
- Namespaces: []specs.LinuxNamespace{
- {
- Type: "pid",
- Path: "/proc/2/ns/pid",
- },
- },
- }
-
- containers, cleanup, err := startContainers(conf, testSpecs, ids)
- if err != nil {
- t.Fatalf("error starting containers: %v", err)
- }
- defer cleanup()
-
- // Check via ps that multiple processes are running.
- expectedPL := []*control.Process{
- {PID: 1, Cmd: "sleep"},
- }
- if err := waitForProcessList(containers[0], expectedPL); err != nil {
- t.Errorf("failed to wait for sleep to start: %v", err)
- }
- if err := waitForProcessList(containers[2], expectedPL); err != nil {
- t.Errorf("failed to wait for sleep to start: %v", err)
- }
-
- expectedPL = []*control.Process{
- {PID: 2, Cmd: "sleep"},
- }
- if err := waitForProcessList(containers[1], expectedPL); err != nil {
- t.Errorf("failed to wait for sleep to start: %v", err)
- }
- }
-}
-
-func TestMultiContainerWait(t *testing.T) {
- // The first container should run the entire duration of the test.
- cmd1 := []string{"sleep", "100"}
- // We'll wait on the second container, which is much shorter lived.
- cmd2 := []string{"sleep", "1"}
- specs, ids := createSpecs(cmd1, cmd2)
-
- conf := testutil.TestConfig()
- containers, cleanup, err := startContainers(conf, specs, ids)
- if err != nil {
- t.Fatalf("error starting containers: %v", err)
- }
- defer cleanup()
-
- // Check via ps that multiple processes are running.
- expectedPL := []*control.Process{
- {PID: 2, Cmd: "sleep"},
- }
- if err := waitForProcessList(containers[1], expectedPL); err != nil {
- t.Errorf("failed to wait for sleep to start: %v", err)
- }
-
- // Wait on the short lived container from multiple goroutines.
- wg := sync.WaitGroup{}
- for i := 0; i < 3; i++ {
- wg.Add(1)
- go func(c *Container) {
- defer wg.Done()
- if ws, err := c.Wait(); err != nil {
- t.Errorf("failed to wait for process %s: %v", c.Spec.Process.Args, err)
- } else if es := ws.ExitStatus(); es != 0 {
- t.Errorf("process %s exited with non-zero status %d", c.Spec.Process.Args, es)
- }
- if _, err := c.Wait(); err != nil {
- t.Errorf("wait for stopped container %s shouldn't fail: %v", c.Spec.Process.Args, err)
- }
- }(containers[1])
- }
-
- // Also wait via PID.
- for i := 0; i < 3; i++ {
- wg.Add(1)
- go func(c *Container) {
- defer wg.Done()
- const pid = 2
- if ws, err := c.WaitPID(pid); err != nil {
- t.Errorf("failed to wait for PID %d: %v", pid, err)
- } else if es := ws.ExitStatus(); es != 0 {
- t.Errorf("PID %d exited with non-zero status %d", pid, es)
- }
- if _, err := c.WaitPID(pid); err == nil {
- t.Errorf("wait for stopped PID %d should fail", pid)
- }
- }(containers[1])
- }
-
- wg.Wait()
-
- // After Wait returns, ensure that the root container is running and
- // the child has finished.
- expectedPL = []*control.Process{
- {PID: 1, Cmd: "sleep"},
- }
- if err := waitForProcessList(containers[0], expectedPL); err != nil {
- t.Errorf("failed to wait for %q to start: %v", strings.Join(containers[0].Spec.Process.Args, " "), err)
- }
-}
-
-// TestExecWait ensures what we can wait containers and individual processes in the
-// sandbox that have already exited.
-func TestExecWait(t *testing.T) {
- rootDir, err := testutil.SetupRootDir()
- if err != nil {
- t.Fatalf("error creating root dir: %v", err)
- }
- defer os.RemoveAll(rootDir)
-
- // The first container should run the entire duration of the test.
- cmd1 := []string{"sleep", "100"}
- // We'll wait on the second container, which is much shorter lived.
- cmd2 := []string{"sleep", "1"}
- specs, ids := createSpecs(cmd1, cmd2)
- conf := testutil.TestConfig()
- containers, cleanup, err := startContainers(conf, specs, ids)
- if err != nil {
- t.Fatalf("error starting containers: %v", err)
- }
- defer cleanup()
-
- // Check via ps that process is running.
- expectedPL := []*control.Process{
- {PID: 2, Cmd: "sleep"},
- }
- if err := waitForProcessList(containers[1], expectedPL); err != nil {
- t.Fatalf("failed to wait for sleep to start: %v", err)
- }
-
- // Wait for the second container to finish.
- if err := waitForProcessCount(containers[1], 0); err != nil {
- t.Fatalf("failed to wait for second container to stop: %v", err)
- }
-
- // Get the second container exit status.
- if ws, err := containers[1].Wait(); err != nil {
- t.Fatalf("failed to wait for process %s: %v", containers[1].Spec.Process.Args, err)
- } else if es := ws.ExitStatus(); es != 0 {
- t.Fatalf("process %s exited with non-zero status %d", containers[1].Spec.Process.Args, es)
- }
- if _, err := containers[1].Wait(); err != nil {
- t.Fatalf("wait for stopped container %s shouldn't fail: %v", containers[1].Spec.Process.Args, err)
- }
-
- // Execute another process in the first container.
- args := &control.ExecArgs{
- Filename: "/bin/sleep",
- Argv: []string{"/bin/sleep", "1"},
- WorkingDirectory: "/",
- KUID: 0,
- }
- pid, err := containers[0].Execute(args)
- if err != nil {
- t.Fatalf("error executing: %v", err)
- }
-
- // Wait for the exec'd process to exit.
- expectedPL = []*control.Process{
- {PID: 1, Cmd: "sleep"},
- }
- if err := waitForProcessList(containers[0], expectedPL); err != nil {
- t.Fatalf("failed to wait for second container to stop: %v", err)
- }
-
- // Get the exit status from the exec'd process.
- if ws, err := containers[0].WaitPID(pid); err != nil {
- t.Fatalf("failed to wait for process %+v with pid %d: %v", args, pid, err)
- } else if es := ws.ExitStatus(); es != 0 {
- t.Fatalf("process %+v exited with non-zero status %d", args, es)
- }
- if _, err := containers[0].WaitPID(pid); err == nil {
- t.Fatalf("wait for stopped process %+v should fail", args)
- }
-}
-
-// TestMultiContainerMount tests that bind mounts can be used with multiple
-// containers.
-func TestMultiContainerMount(t *testing.T) {
- cmd1 := []string{"sleep", "100"}
-
- // 'src != dst' ensures that 'dst' doesn't exist in the host and must be
- // properly mapped inside the container to work.
- src, err := ioutil.TempDir(testutil.TmpDir(), "container")
- if err != nil {
- t.Fatal("ioutil.TempDir failed:", err)
- }
- dst := src + ".dst"
- cmd2 := []string{"touch", filepath.Join(dst, "file")}
-
- sps, ids := createSpecs(cmd1, cmd2)
- sps[1].Mounts = append(sps[1].Mounts, specs.Mount{
- Source: src,
- Destination: dst,
- Type: "bind",
- })
-
- // Setup the containers.
- conf := testutil.TestConfig()
- containers, cleanup, err := startContainers(conf, sps, ids)
- if err != nil {
- t.Fatalf("error starting containers: %v", err)
- }
- defer cleanup()
-
- ws, err := containers[1].Wait()
- if err != nil {
- t.Error("error waiting on container:", err)
- }
- if !ws.Exited() || ws.ExitStatus() != 0 {
- t.Error("container failed, waitStatus:", ws)
- }
-}
-
-// TestMultiContainerSignal checks that it is possible to signal individual
-// containers without killing the entire sandbox.
-func TestMultiContainerSignal(t *testing.T) {
- for _, conf := range configs(all...) {
- t.Logf("Running test with conf: %+v", conf)
-
- // Setup the containers.
- sleep := []string{"sleep", "100"}
- specs, ids := createSpecs(sleep, sleep)
- containers, cleanup, err := startContainers(conf, specs, ids)
- if err != nil {
- t.Fatalf("error starting containers: %v", err)
- }
- defer cleanup()
-
- // Check via ps that container 1 process is running.
- expectedPL := []*control.Process{
- {PID: 2, Cmd: "sleep"},
- }
-
- if err := waitForProcessList(containers[1], expectedPL); err != nil {
- t.Errorf("failed to wait for sleep to start: %v", err)
- }
-
- // Kill process 2.
- if err := containers[1].SignalContainer(syscall.SIGKILL, false); err != nil {
- t.Errorf("failed to kill process 2: %v", err)
- }
-
- // Make sure process 1 is still running.
- expectedPL = []*control.Process{
- {PID: 1, Cmd: "sleep"},
- }
- if err := waitForProcessList(containers[0], expectedPL); err != nil {
- t.Errorf("failed to wait for sleep to start: %v", err)
- }
-
- // goferPid is reset when container is destroyed.
- goferPid := containers[1].GoferPid
-
- // Destroy container and ensure container's gofer process has exited.
- if err := containers[1].Destroy(); err != nil {
- t.Errorf("failed to destroy container: %v", err)
- }
- _, _, err = specutils.RetryEintr(func() (uintptr, uintptr, error) {
- cpid, err := syscall.Wait4(goferPid, nil, 0, nil)
- return uintptr(cpid), 0, err
- })
- if err != syscall.ECHILD {
- t.Errorf("error waiting for gofer to exit: %v", err)
- }
- // Make sure process 1 is still running.
- if err := waitForProcessList(containers[0], expectedPL); err != nil {
- t.Errorf("failed to wait for sleep to start: %v", err)
- }
-
- // Now that process 2 is gone, ensure we get an error trying to
- // signal it again.
- if err := containers[1].SignalContainer(syscall.SIGKILL, false); err == nil {
- t.Errorf("container %q shouldn't exist, but we were able to signal it", containers[1].ID)
- }
-
- // Kill process 1.
- if err := containers[0].SignalContainer(syscall.SIGKILL, false); err != nil {
- t.Errorf("failed to kill process 1: %v", err)
- }
-
- // Ensure that container's gofer and sandbox process are no more.
- err = blockUntilWaitable(containers[0].GoferPid)
- if err != nil && err != syscall.ECHILD {
- t.Errorf("error waiting for gofer to exit: %v", err)
- }
-
- err = blockUntilWaitable(containers[0].Sandbox.Pid)
- if err != nil && err != syscall.ECHILD {
- t.Errorf("error waiting for sandbox to exit: %v", err)
- }
-
- // The sentry should be gone, so signaling should yield an error.
- if err := containers[0].SignalContainer(syscall.SIGKILL, false); err == nil {
- t.Errorf("sandbox %q shouldn't exist, but we were able to signal it", containers[0].Sandbox.ID)
- }
-
- if err := containers[0].Destroy(); err != nil {
- t.Errorf("failed to destroy container: %v", err)
- }
- }
-}
-
-// TestMultiContainerDestroy checks that container are properly cleaned-up when
-// they are destroyed.
-func TestMultiContainerDestroy(t *testing.T) {
- app, err := testutil.FindFile("runsc/container/test_app/test_app")
- if err != nil {
- t.Fatal("error finding test_app:", err)
- }
-
- for _, conf := range configs(all...) {
- t.Logf("Running test with conf: %+v", conf)
-
- // First container will remain intact while the second container is killed.
- podSpecs, ids := createSpecs(
- []string{"sleep", "100"},
- []string{app, "fork-bomb"})
-
- // Run the fork bomb in a PID namespace to prevent processes to be
- // re-parented to PID=1 in the root container.
- podSpecs[1].Linux = &specs.Linux{
- Namespaces: []specs.LinuxNamespace{{Type: "pid"}},
- }
- containers, cleanup, err := startContainers(conf, podSpecs, ids)
- if err != nil {
- t.Fatalf("error starting containers: %v", err)
- }
- defer cleanup()
-
- // Exec more processes to ensure signal all works for exec'd processes too.
- args := &control.ExecArgs{
- Filename: app,
- Argv: []string{app, "fork-bomb"},
- }
- if _, err := containers[1].Execute(args); err != nil {
- t.Fatalf("error exec'ing: %v", err)
- }
-
- // Let it brew...
- time.Sleep(500 * time.Millisecond)
-
- if err := containers[1].Destroy(); err != nil {
- t.Fatalf("error destroying container: %v", err)
- }
-
- // Check that destroy killed all processes belonging to the container and
- // waited for them to exit before returning.
- pss, err := containers[0].Sandbox.Processes("")
- if err != nil {
- t.Fatalf("error getting process data from sandbox: %v", err)
- }
- expectedPL := []*control.Process{{PID: 1, Cmd: "sleep"}}
- if !procListsEqual(pss, expectedPL) {
- t.Errorf("container got process list: %s, want: %s", procListToString(pss), procListToString(expectedPL))
- }
-
- // Check that cont.Destroy is safe to call multiple times.
- if err := containers[1].Destroy(); err != nil {
- t.Errorf("error destroying container: %v", err)
- }
- }
-}
-
-func TestMultiContainerProcesses(t *testing.T) {
- // Note: use curly braces to keep 'sh' process around. Otherwise, shell
- // will just execve into 'sleep' and both containers will look the
- // same.
- specs, ids := createSpecs(
- []string{"sleep", "100"},
- []string{"sh", "-c", "{ sleep 100; }"})
- conf := testutil.TestConfig()
- containers, cleanup, err := startContainers(conf, specs, ids)
- if err != nil {
- t.Fatalf("error starting containers: %v", err)
- }
- defer cleanup()
-
- // Check root's container process list doesn't include other containers.
- expectedPL0 := []*control.Process{
- {PID: 1, Cmd: "sleep"},
- }
- if err := waitForProcessList(containers[0], expectedPL0); err != nil {
- t.Errorf("failed to wait for process to start: %v", err)
- }
-
- // Same for the other container.
- expectedPL1 := []*control.Process{
- {PID: 2, Cmd: "sh"},
- {PID: 3, PPID: 2, Cmd: "sleep"},
- }
- if err := waitForProcessList(containers[1], expectedPL1); err != nil {
- t.Errorf("failed to wait for process to start: %v", err)
- }
-
- // Now exec into the second container and verify it shows up in the container.
- args := &control.ExecArgs{
- Filename: "/bin/sleep",
- Argv: []string{"/bin/sleep", "100"},
- }
- if _, err := containers[1].Execute(args); err != nil {
- t.Fatalf("error exec'ing: %v", err)
- }
- expectedPL1 = append(expectedPL1, &control.Process{PID: 4, Cmd: "sleep"})
- if err := waitForProcessList(containers[1], expectedPL1); err != nil {
- t.Errorf("failed to wait for process to start: %v", err)
- }
- // Root container should remain unchanged.
- if err := waitForProcessList(containers[0], expectedPL0); err != nil {
- t.Errorf("failed to wait for process to start: %v", err)
- }
-}
-
-// TestMultiContainerKillAll checks that all process that belong to a container
-// are killed when SIGKILL is sent to *all* processes in that container.
-func TestMultiContainerKillAll(t *testing.T) {
- for _, tc := range []struct {
- killContainer bool
- }{
- {killContainer: true},
- {killContainer: false},
- } {
- app, err := testutil.FindFile("runsc/container/test_app/test_app")
- if err != nil {
- t.Fatal("error finding test_app:", err)
- }
-
- // First container will remain intact while the second container is killed.
- specs, ids := createSpecs(
- []string{app, "task-tree", "--depth=2", "--width=2"},
- []string{app, "task-tree", "--depth=4", "--width=2"})
- conf := testutil.TestConfig()
- containers, cleanup, err := startContainers(conf, specs, ids)
- if err != nil {
- t.Fatalf("error starting containers: %v", err)
- }
- defer cleanup()
-
- // Wait until all processes are created.
- rootProcCount := int(math.Pow(2, 3) - 1)
- if err := waitForProcessCount(containers[0], rootProcCount); err != nil {
- t.Fatal(err)
- }
- procCount := int(math.Pow(2, 5) - 1)
- if err := waitForProcessCount(containers[1], procCount); err != nil {
- t.Fatal(err)
- }
-
- // Exec more processes to ensure signal works for exec'd processes too.
- args := &control.ExecArgs{
- Filename: app,
- Argv: []string{app, "task-tree", "--depth=2", "--width=2"},
- }
- if _, err := containers[1].Execute(args); err != nil {
- t.Fatalf("error exec'ing: %v", err)
- }
- // Wait for these new processes to start.
- procCount += int(math.Pow(2, 3) - 1)
- if err := waitForProcessCount(containers[1], procCount); err != nil {
- t.Fatal(err)
- }
-
- if tc.killContainer {
- // First kill the init process to make the container be stopped with
- // processes still running inside.
- containers[1].SignalContainer(syscall.SIGKILL, false)
- op := func() error {
- c, err := Load(conf.RootDir, ids[1])
- if err != nil {
- return err
- }
- if c.Status != Stopped {
- return fmt.Errorf("container is not stopped")
- }
- return nil
- }
- if err := testutil.Poll(op, 5*time.Second); err != nil {
- t.Fatalf("container did not stop %q: %v", containers[1].ID, err)
- }
- }
-
- c, err := Load(conf.RootDir, ids[1])
- if err != nil {
- t.Fatalf("failed to load child container %q: %v", c.ID, err)
- }
- // Kill'Em All
- if err := c.SignalContainer(syscall.SIGKILL, true); err != nil {
- t.Fatalf("failed to send SIGKILL to container %q: %v", c.ID, err)
- }
-
- // Check that all processes are gone.
- if err := waitForProcessCount(containers[1], 0); err != nil {
- t.Fatal(err)
- }
- // Check that root container was not affected.
- if err := waitForProcessCount(containers[0], rootProcCount); err != nil {
- t.Fatal(err)
- }
- }
-}
-
-func TestMultiContainerDestroyNotStarted(t *testing.T) {
- specs, ids := createSpecs(
- []string{"/bin/sleep", "100"},
- []string{"/bin/sleep", "100"})
- rootDir, err := testutil.SetupRootDir()
- if err != nil {
- t.Fatalf("error creating root dir: %v", err)
- }
- defer os.RemoveAll(rootDir)
-
- conf := testutil.TestConfigWithRoot(rootDir)
-
- // Create and start root container.
- rootBundleDir, err := testutil.SetupBundleDir(specs[0])
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootBundleDir)
-
- rootArgs := Args{
- ID: ids[0],
- Spec: specs[0],
- BundleDir: rootBundleDir,
- }
- root, err := New(conf, rootArgs)
- if err != nil {
- t.Fatalf("error creating root container: %v", err)
- }
- defer root.Destroy()
- if err := root.Start(conf); err != nil {
- t.Fatalf("error starting root container: %v", err)
- }
-
- // Create and destroy sub-container.
- bundleDir, err := testutil.SetupBundleDir(specs[1])
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(bundleDir)
-
- args := Args{
- ID: ids[1],
- Spec: specs[1],
- BundleDir: bundleDir,
- }
- cont, err := New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
-
- // Check that container can be destroyed.
- if err := cont.Destroy(); err != nil {
- t.Fatalf("deleting non-started container failed: %v", err)
- }
-}
-
-// TestMultiContainerDestroyStarting attempts to force a race between start
-// and destroy.
-func TestMultiContainerDestroyStarting(t *testing.T) {
- cmds := make([][]string, 10)
- for i := range cmds {
- cmds[i] = []string{"/bin/sleep", "100"}
- }
- specs, ids := createSpecs(cmds...)
-
- rootDir, err := testutil.SetupRootDir()
- if err != nil {
- t.Fatalf("error creating root dir: %v", err)
- }
- defer os.RemoveAll(rootDir)
-
- conf := testutil.TestConfigWithRoot(rootDir)
-
- // Create and start root container.
- rootBundleDir, err := testutil.SetupBundleDir(specs[0])
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootBundleDir)
-
- rootArgs := Args{
- ID: ids[0],
- Spec: specs[0],
- BundleDir: rootBundleDir,
- }
- root, err := New(conf, rootArgs)
- if err != nil {
- t.Fatalf("error creating root container: %v", err)
- }
- defer root.Destroy()
- if err := root.Start(conf); err != nil {
- t.Fatalf("error starting root container: %v", err)
- }
-
- wg := sync.WaitGroup{}
- for i := range cmds {
- if i == 0 {
- continue // skip root container
- }
-
- bundleDir, err := testutil.SetupBundleDir(specs[i])
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(bundleDir)
-
- rootArgs := Args{
- ID: ids[i],
- Spec: specs[i],
- BundleDir: rootBundleDir,
- }
- cont, err := New(conf, rootArgs)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
-
- // Container is not thread safe, so load another instance to run in
- // concurrently.
- startCont, err := Load(rootDir, ids[i])
- if err != nil {
- t.Fatalf("error loading container: %v", err)
- }
- wg.Add(1)
- go func() {
- defer wg.Done()
- startCont.Start(conf) // ignore failures, start can fail if destroy runs first.
- }()
-
- wg.Add(1)
- go func() {
- defer wg.Done()
- if err := cont.Destroy(); err != nil {
- t.Errorf("deleting non-started container failed: %v", err)
- }
- }()
- }
- wg.Wait()
-}
-
-// TestMultiContainerDifferentFilesystems tests that different containers have
-// different root filesystems.
-func TestMultiContainerDifferentFilesystems(t *testing.T) {
- filename := "/foo"
- // Root container will create file and then sleep.
- cmdRoot := []string{"sh", "-c", fmt.Sprintf("touch %q && sleep 100", filename)}
-
- // Child containers will assert that the file does not exist, and will
- // then create it.
- script := fmt.Sprintf("if [ -f %q ]; then exit 1; else touch %q; fi", filename, filename)
- cmd := []string{"sh", "-c", script}
-
- // Make sure overlay is enabled, and none of the root filesystems are
- // read-only, otherwise we won't be able to create the file.
- conf := testutil.TestConfig()
- conf.Overlay = true
- specs, ids := createSpecs(cmdRoot, cmd, cmd)
- for _, s := range specs {
- s.Root.Readonly = false
- }
-
- containers, cleanup, err := startContainers(conf, specs, ids)
- if err != nil {
- t.Fatalf("error starting containers: %v", err)
- }
- defer cleanup()
-
- // Both child containers should exit successfully.
- for i, c := range containers {
- if i == 0 {
- // Don't wait on the root.
- continue
- }
- if ws, err := c.Wait(); err != nil {
- t.Errorf("failed to wait for process %s: %v", c.Spec.Process.Args, err)
- } else if es := ws.ExitStatus(); es != 0 {
- t.Errorf("process %s exited with non-zero status %d", c.Spec.Process.Args, es)
- }
- }
-}
-
-// TestMultiContainerContainerDestroyStress tests that IO operations continue
-// to work after containers have been stopped and gofers killed.
-func TestMultiContainerContainerDestroyStress(t *testing.T) {
- app, err := testutil.FindFile("runsc/container/test_app/test_app")
- if err != nil {
- t.Fatal("error finding test_app:", err)
- }
-
- // Setup containers. Root container just reaps children, while the others
- // perform some IOs. Children are executed in 3 batches of 10. Within the
- // batch there is overlap between containers starting and being destroyed. In
- // between batches all containers stop before starting another batch.
- cmds := [][]string{{app, "reaper"}}
- const batchSize = 10
- for i := 0; i < 3*batchSize; i++ {
- dir, err := ioutil.TempDir(testutil.TmpDir(), "gofer-stop-test")
- if err != nil {
- t.Fatal("ioutil.TempDir failed:", err)
- }
- defer os.RemoveAll(dir)
-
- cmd := "find /bin -type f | head | xargs -I SRC cp SRC " + dir
- cmds = append(cmds, []string{"sh", "-c", cmd})
- }
- allSpecs, allIDs := createSpecs(cmds...)
-
- rootDir, err := testutil.SetupRootDir()
- if err != nil {
- t.Fatalf("error creating root dir: %v", err)
- }
- defer os.RemoveAll(rootDir)
-
- // Split up the specs and IDs.
- rootSpec := allSpecs[0]
- rootID := allIDs[0]
- childrenSpecs := allSpecs[1:]
- childrenIDs := allIDs[1:]
-
- bundleDir, err := testutil.SetupBundleDir(rootSpec)
- if err != nil {
- t.Fatalf("error setting up bundle dir: %v", err)
- }
- defer os.RemoveAll(bundleDir)
-
- // Start root container.
- conf := testutil.TestConfigWithRoot(rootDir)
- rootArgs := Args{
- ID: rootID,
- Spec: rootSpec,
- BundleDir: bundleDir,
- }
- root, err := New(conf, rootArgs)
- if err != nil {
- t.Fatalf("error creating root container: %v", err)
- }
- if err := root.Start(conf); err != nil {
- t.Fatalf("error starting root container: %v", err)
- }
- defer root.Destroy()
-
- // Run batches. Each batch starts containers in parallel, then wait and
- // destroy them before starting another batch.
- for i := 0; i < len(childrenSpecs); i += batchSize {
- t.Logf("Starting batch from %d to %d", i, i+batchSize)
- specs := childrenSpecs[i : i+batchSize]
- ids := childrenIDs[i : i+batchSize]
-
- var children []*Container
- for j, spec := range specs {
- bundleDir, err := testutil.SetupBundleDir(spec)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(bundleDir)
-
- args := Args{
- ID: ids[j],
- Spec: spec,
- BundleDir: bundleDir,
- }
- child, err := New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- children = append(children, child)
-
- if err := child.Start(conf); err != nil {
- t.Fatalf("error starting container: %v", err)
- }
-
- // Give a small gap between containers.
- time.Sleep(50 * time.Millisecond)
- }
- for _, child := range children {
- ws, err := child.Wait()
- if err != nil {
- t.Fatalf("waiting for container: %v", err)
- }
- if !ws.Exited() || ws.ExitStatus() != 0 {
- t.Fatalf("container failed, waitStatus: %x (%d)", ws, ws.ExitStatus())
- }
- if err := child.Destroy(); err != nil {
- t.Fatalf("error destroying container: %v", err)
- }
- }
- }
-}
-
-// Test that pod shared mounts are properly mounted in 2 containers and that
-// changes from one container is reflected in the other.
-func TestMultiContainerSharedMount(t *testing.T) {
- for _, conf := range configs(all...) {
- t.Logf("Running test with conf: %+v", conf)
-
- // Setup the containers.
- sleep := []string{"sleep", "100"}
- podSpec, ids := createSpecs(sleep, sleep)
- mnt0 := specs.Mount{
- Destination: "/mydir/test",
- Source: "/some/dir",
- Type: "tmpfs",
- Options: nil,
- }
- podSpec[0].Mounts = append(podSpec[0].Mounts, mnt0)
-
- mnt1 := mnt0
- mnt1.Destination = "/mydir2/test2"
- podSpec[1].Mounts = append(podSpec[1].Mounts, mnt1)
-
- createSharedMount(mnt0, "test-mount", podSpec...)
-
- containers, cleanup, err := startContainers(conf, podSpec, ids)
- if err != nil {
- t.Fatalf("error starting containers: %v", err)
- }
- defer cleanup()
-
- file0 := path.Join(mnt0.Destination, "abc")
- file1 := path.Join(mnt1.Destination, "abc")
- execs := []execDesc{
- {
- c: containers[0],
- cmd: []string{"/usr/bin/test", "-d", mnt0.Destination},
- desc: "directory is mounted in container0",
- },
- {
- c: containers[1],
- cmd: []string{"/usr/bin/test", "-d", mnt1.Destination},
- desc: "directory is mounted in container1",
- },
- {
- c: containers[0],
- cmd: []string{"/usr/bin/touch", file0},
- desc: "create file in container0",
- },
- {
- c: containers[0],
- cmd: []string{"/usr/bin/test", "-f", file0},
- desc: "file appears in container0",
- },
- {
- c: containers[1],
- cmd: []string{"/usr/bin/test", "-f", file1},
- desc: "file appears in container1",
- },
- {
- c: containers[1],
- cmd: []string{"/bin/rm", file1},
- desc: "file removed from container1",
- },
- {
- c: containers[0],
- cmd: []string{"/usr/bin/test", "!", "-f", file0},
- desc: "file removed from container0",
- },
- {
- c: containers[1],
- cmd: []string{"/usr/bin/test", "!", "-f", file1},
- desc: "file removed from container1",
- },
- {
- c: containers[1],
- cmd: []string{"/bin/mkdir", file1},
- desc: "create directory in container1",
- },
- {
- c: containers[0],
- cmd: []string{"/usr/bin/test", "-d", file0},
- desc: "dir appears in container0",
- },
- {
- c: containers[1],
- cmd: []string{"/usr/bin/test", "-d", file1},
- desc: "dir appears in container1",
- },
- {
- c: containers[0],
- cmd: []string{"/bin/rmdir", file0},
- desc: "create directory in container0",
- },
- {
- c: containers[0],
- cmd: []string{"/usr/bin/test", "!", "-d", file0},
- desc: "dir removed from container0",
- },
- {
- c: containers[1],
- cmd: []string{"/usr/bin/test", "!", "-d", file1},
- desc: "dir removed from container1",
- },
- }
- if err := execMany(execs); err != nil {
- t.Fatal(err.Error())
- }
- }
-}
-
-// Test that pod mounts are mounted as readonly when requested.
-func TestMultiContainerSharedMountReadonly(t *testing.T) {
- for _, conf := range configs(all...) {
- t.Logf("Running test with conf: %+v", conf)
-
- // Setup the containers.
- sleep := []string{"sleep", "100"}
- podSpec, ids := createSpecs(sleep, sleep)
- mnt0 := specs.Mount{
- Destination: "/mydir/test",
- Source: "/some/dir",
- Type: "tmpfs",
- Options: []string{"ro"},
- }
- podSpec[0].Mounts = append(podSpec[0].Mounts, mnt0)
-
- mnt1 := mnt0
- mnt1.Destination = "/mydir2/test2"
- podSpec[1].Mounts = append(podSpec[1].Mounts, mnt1)
-
- createSharedMount(mnt0, "test-mount", podSpec...)
-
- containers, cleanup, err := startContainers(conf, podSpec, ids)
- if err != nil {
- t.Fatalf("error starting containers: %v", err)
- }
- defer cleanup()
-
- file0 := path.Join(mnt0.Destination, "abc")
- file1 := path.Join(mnt1.Destination, "abc")
- execs := []execDesc{
- {
- c: containers[0],
- cmd: []string{"/usr/bin/test", "-d", mnt0.Destination},
- desc: "directory is mounted in container0",
- },
- {
- c: containers[1],
- cmd: []string{"/usr/bin/test", "-d", mnt1.Destination},
- desc: "directory is mounted in container1",
- },
- {
- c: containers[0],
- cmd: []string{"/usr/bin/touch", file0},
- want: 1,
- desc: "fails to write to container0",
- },
- {
- c: containers[1],
- cmd: []string{"/usr/bin/touch", file1},
- want: 1,
- desc: "fails to write to container1",
- },
- }
- if err := execMany(execs); err != nil {
- t.Fatal(err.Error())
- }
- }
-}
-
-// Test that shared pod mounts continue to work after container is restarted.
-func TestMultiContainerSharedMountRestart(t *testing.T) {
- for _, conf := range configs(all...) {
- t.Logf("Running test with conf: %+v", conf)
-
- // Setup the containers.
- sleep := []string{"sleep", "100"}
- podSpec, ids := createSpecs(sleep, sleep)
- mnt0 := specs.Mount{
- Destination: "/mydir/test",
- Source: "/some/dir",
- Type: "tmpfs",
- Options: nil,
- }
- podSpec[0].Mounts = append(podSpec[0].Mounts, mnt0)
-
- mnt1 := mnt0
- mnt1.Destination = "/mydir2/test2"
- podSpec[1].Mounts = append(podSpec[1].Mounts, mnt1)
-
- createSharedMount(mnt0, "test-mount", podSpec...)
-
- containers, cleanup, err := startContainers(conf, podSpec, ids)
- if err != nil {
- t.Fatalf("error starting containers: %v", err)
- }
- defer cleanup()
-
- file0 := path.Join(mnt0.Destination, "abc")
- file1 := path.Join(mnt1.Destination, "abc")
- execs := []execDesc{
- {
- c: containers[0],
- cmd: []string{"/usr/bin/touch", file0},
- desc: "create file in container0",
- },
- {
- c: containers[0],
- cmd: []string{"/usr/bin/test", "-f", file0},
- desc: "file appears in container0",
- },
- {
- c: containers[1],
- cmd: []string{"/usr/bin/test", "-f", file1},
- desc: "file appears in container1",
- },
- }
- if err := execMany(execs); err != nil {
- t.Fatal(err.Error())
- }
-
- containers[1].Destroy()
-
- bundleDir, err := testutil.SetupBundleDir(podSpec[1])
- if err != nil {
- t.Fatalf("error restarting container: %v", err)
- }
- defer os.RemoveAll(bundleDir)
-
- args := Args{
- ID: ids[1],
- Spec: podSpec[1],
- BundleDir: bundleDir,
- }
- containers[1], err = New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- if err := containers[1].Start(conf); err != nil {
- t.Fatalf("error starting container: %v", err)
- }
-
- execs = []execDesc{
- {
- c: containers[0],
- cmd: []string{"/usr/bin/test", "-f", file0},
- desc: "file is still in container0",
- },
- {
- c: containers[1],
- cmd: []string{"/usr/bin/test", "-f", file1},
- desc: "file is still in container1",
- },
- {
- c: containers[1],
- cmd: []string{"/bin/rm", file1},
- desc: "file removed from container1",
- },
- {
- c: containers[0],
- cmd: []string{"/usr/bin/test", "!", "-f", file0},
- desc: "file removed from container0",
- },
- {
- c: containers[1],
- cmd: []string{"/usr/bin/test", "!", "-f", file1},
- desc: "file removed from container1",
- },
- }
- if err := execMany(execs); err != nil {
- t.Fatal(err.Error())
- }
- }
-}
-
-// Test that one container can send an FD to another container, even though
-// they have distinct MountNamespaces.
-func TestMultiContainerMultiRootCanHandleFDs(t *testing.T) {
- app, err := testutil.FindFile("runsc/container/test_app/test_app")
- if err != nil {
- t.Fatal("error finding test_app:", err)
- }
-
- // We set up two containers with one shared mount that is used for a
- // shared socket. The first container will send an FD over the socket
- // to the second container. The FD corresponds to a file in the first
- // container's mount namespace that is not part of the second
- // container's mount namespace. However, the second container still
- // should be able to read the FD.
-
- // Create a shared mount where we will put the socket.
- sharedMnt := specs.Mount{
- Destination: "/mydir/test",
- Type: "tmpfs",
- // Shared mounts need a Source, even for tmpfs. It is only used
- // to match up different shared mounts inside the pod.
- Source: "/some/dir",
- }
- socketPath := filepath.Join(sharedMnt.Destination, "socket")
-
- // Create a writeable tmpfs mount where the FD sender app will create
- // files to send. This will only be mounted in the FD sender.
- writeableMnt := specs.Mount{
- Destination: "/tmp",
- Type: "tmpfs",
- }
-
- // Create the specs.
- specs, ids := createSpecs(
- []string{"sleep", "1000"},
- []string{app, "fd_sender", "--socket", socketPath},
- []string{app, "fd_receiver", "--socket", socketPath},
- )
- createSharedMount(sharedMnt, "shared-mount", specs...)
- specs[1].Mounts = append(specs[2].Mounts, sharedMnt, writeableMnt)
- specs[2].Mounts = append(specs[1].Mounts, sharedMnt)
-
- conf := testutil.TestConfig()
- containers, cleanup, err := startContainers(conf, specs, ids)
- if err != nil {
- t.Fatalf("error starting containers: %v", err)
- }
- defer cleanup()
-
- // Both containers should exit successfully.
- for _, c := range containers[1:] {
- if ws, err := c.Wait(); err != nil {
- t.Errorf("failed to wait for process %s: %v", c.Spec.Process.Args, err)
- } else if es := ws.ExitStatus(); es != 0 {
- t.Errorf("process %s exited with non-zero status %d", c.Spec.Process.Args, es)
- }
- }
-}
-
-// Test that container is destroyed when Gofer is killed.
-func TestMultiContainerGoferKilled(t *testing.T) {
- sleep := []string{"sleep", "100"}
- specs, ids := createSpecs(sleep, sleep, sleep)
- conf := testutil.TestConfig()
- containers, cleanup, err := startContainers(conf, specs, ids)
- if err != nil {
- t.Fatalf("error starting containers: %v", err)
- }
- defer cleanup()
-
- // Ensure container is running
- c := containers[2]
- expectedPL := []*control.Process{
- {PID: 3, Cmd: "sleep"},
- }
- if err := waitForProcessList(c, expectedPL); err != nil {
- t.Errorf("failed to wait for sleep to start: %v", err)
- }
-
- // Kill container's gofer.
- if err := syscall.Kill(c.GoferPid, syscall.SIGKILL); err != nil {
- t.Fatalf("syscall.Kill(%d, SIGKILL)=%v", c.GoferPid, err)
- }
-
- // Wait until container stops.
- if err := waitForProcessList(c, nil); err != nil {
- t.Errorf("Container %q was not stopped after gofer death: %v", c.ID, err)
- }
-
- // Check that container isn't running anymore.
- args := &control.ExecArgs{Argv: []string{"/bin/true"}}
- if _, err := c.executeSync(args); err == nil {
- t.Fatalf("Container %q was not stopped after gofer death", c.ID)
- }
-
- // Check that other containers are unaffected.
- for i, c := range containers {
- if i == 2 {
- continue // container[2] has been killed.
- }
- pl := []*control.Process{
- {PID: kernel.ThreadID(i + 1), Cmd: "sleep"},
- }
- if err := waitForProcessList(c, pl); err != nil {
- t.Errorf("Container %q was affected by another container: %v", c.ID, err)
- }
- args := &control.ExecArgs{Argv: []string{"/bin/true"}}
- if _, err := c.executeSync(args); err != nil {
- t.Fatalf("Container %q was affected by another container: %v", c.ID, err)
- }
- }
-
- // Kill root container's gofer to bring entire sandbox down.
- c = containers[0]
- if err := syscall.Kill(c.GoferPid, syscall.SIGKILL); err != nil {
- t.Fatalf("syscall.Kill(%d, SIGKILL)=%v", c.GoferPid, err)
- }
-
- // Wait until sandbox stops. waitForProcessList will loop until sandbox exits
- // and RPC errors out.
- impossiblePL := []*control.Process{
- {PID: 100, Cmd: "non-existent-process"},
- }
- if err := waitForProcessList(c, impossiblePL); err == nil {
- t.Fatalf("Sandbox was not killed after gofer death")
- }
-
- // Check that entire sandbox isn't running anymore.
- for _, c := range containers {
- args := &control.ExecArgs{Argv: []string{"/bin/true"}}
- if _, err := c.executeSync(args); err == nil {
- t.Fatalf("Container %q was not stopped after gofer death", c.ID)
- }
- }
-}
-
-func TestMultiContainerLoadSandbox(t *testing.T) {
- sleep := []string{"sleep", "100"}
- specs, ids := createSpecs(sleep, sleep, sleep)
- conf := testutil.TestConfig()
-
- // Create containers for the sandbox.
- wants, cleanup, err := startContainers(conf, specs, ids)
- if err != nil {
- t.Fatalf("error starting containers: %v", err)
- }
- defer cleanup()
-
- // Then create unrelated containers.
- for i := 0; i < 3; i++ {
- specs, ids = createSpecs(sleep, sleep, sleep)
- _, cleanup, err = startContainers(conf, specs, ids)
- if err != nil {
- t.Fatalf("error starting containers: %v", err)
- }
- defer cleanup()
- }
-
- // Create an unrelated directory under root.
- dir := filepath.Join(conf.RootDir, "not-a-container")
- if err := os.MkdirAll(dir, 0755); err != nil {
- t.Fatalf("os.MkdirAll(%q)=%v", dir, err)
- }
-
- // Create a valid but empty container directory.
- randomCID := testutil.UniqueContainerID()
- dir = filepath.Join(conf.RootDir, randomCID)
- if err := os.MkdirAll(dir, 0755); err != nil {
- t.Fatalf("os.MkdirAll(%q)=%v", dir, err)
- }
-
- // Load the sandbox and check that the correct containers were returned.
- id := wants[0].Sandbox.ID
- gots, err := loadSandbox(conf.RootDir, id)
- if err != nil {
- t.Fatalf("loadSandbox()=%v", err)
- }
- wantIDs := make(map[string]struct{})
- for _, want := range wants {
- wantIDs[want.ID] = struct{}{}
- }
- for _, got := range gots {
- if got.Sandbox.ID != id {
- t.Errorf("wrong sandbox ID, got: %v, want: %v", got.Sandbox.ID, id)
- }
- if _, ok := wantIDs[got.ID]; !ok {
- t.Errorf("wrong container ID, got: %v, wants: %v", got.ID, wantIDs)
- }
- delete(wantIDs, got.ID)
- }
- if len(wantIDs) != 0 {
- t.Errorf("containers not found: %v", wantIDs)
- }
-}
-
-// TestMultiContainerRunNonRoot checks that child container can be configured
-// when running as non-privileged user.
-func TestMultiContainerRunNonRoot(t *testing.T) {
- cmdRoot := []string{"/bin/sleep", "100"}
- cmdSub := []string{"/bin/true"}
- podSpecs, ids := createSpecs(cmdRoot, cmdSub)
-
- // User running inside container can't list '$TMP/blocked' and would fail to
- // mount it.
- blocked, err := ioutil.TempDir(testutil.TmpDir(), "blocked")
- if err != nil {
- t.Fatalf("ioutil.TempDir() failed: %v", err)
- }
- if err := os.Chmod(blocked, 0700); err != nil {
- t.Fatalf("os.MkDir(%q) failed: %v", blocked, err)
- }
- dir := path.Join(blocked, "test")
- if err := os.Mkdir(dir, 0755); err != nil {
- t.Fatalf("os.MkDir(%q) failed: %v", dir, err)
- }
-
- src, err := ioutil.TempDir(testutil.TmpDir(), "src")
- if err != nil {
- t.Fatalf("ioutil.TempDir() failed: %v", err)
- }
-
- // Set a random user/group with no access to "blocked" dir.
- podSpecs[1].Process.User.UID = 343
- podSpecs[1].Process.User.GID = 2401
- podSpecs[1].Process.Capabilities = nil
-
- podSpecs[1].Mounts = append(podSpecs[1].Mounts, specs.Mount{
- Destination: dir,
- Source: src,
- Type: "bind",
- })
-
- conf := testutil.TestConfig()
- pod, cleanup, err := startContainers(conf, podSpecs, ids)
- if err != nil {
- t.Fatalf("error starting containers: %v", err)
- }
- defer cleanup()
-
- // Once all containers are started, wait for the child container to exit.
- // This means that the volume was mounted properly.
- ws, err := pod[1].Wait()
- if err != nil {
- t.Fatalf("running child container: %v", err)
- }
- if !ws.Exited() || ws.ExitStatus() != 0 {
- t.Fatalf("child container failed, waitStatus: %v", ws)
- }
-}
diff --git a/runsc/container/shared_volume_test.go b/runsc/container/shared_volume_test.go
deleted file mode 100644
index dc4194134..000000000
--- a/runsc/container/shared_volume_test.go
+++ /dev/null
@@ -1,277 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package container
-
-import (
- "bytes"
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sentry/control"
- "gvisor.dev/gvisor/pkg/sentry/kernel/auth"
- "gvisor.dev/gvisor/runsc/boot"
- "gvisor.dev/gvisor/runsc/testutil"
-)
-
-// TestSharedVolume checks that modifications to a volume mount are propagated
-// into and out of the sandbox.
-func TestSharedVolume(t *testing.T) {
- conf := testutil.TestConfig()
- conf.FileAccess = boot.FileAccessShared
- t.Logf("Running test with conf: %+v", conf)
-
- // Main process just sleeps. We will use "exec" to probe the state of
- // the filesystem.
- spec := testutil.NewSpecWithArgs("sleep", "1000")
-
- dir, err := ioutil.TempDir(testutil.TmpDir(), "shared-volume-test")
- if err != nil {
- t.Fatalf("TempDir failed: %v", err)
- }
-
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- // Create and start the container.
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- }
- c, err := New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- defer c.Destroy()
- if err := c.Start(conf); err != nil {
- t.Fatalf("error starting container: %v", err)
- }
-
- // File that will be used to check consistency inside/outside sandbox.
- filename := filepath.Join(dir, "file")
-
- // File does not exist yet. Reading from the sandbox should fail.
- argsTestFile := &control.ExecArgs{
- Filename: "/usr/bin/test",
- Argv: []string{"test", "-f", filename},
- }
- if ws, err := c.executeSync(argsTestFile); err != nil {
- t.Fatalf("unexpected error testing file %q: %v", filename, err)
- } else if ws.ExitStatus() == 0 {
- t.Errorf("test %q exited with code %v, wanted not zero", ws.ExitStatus(), err)
- }
-
- // Create the file from outside of the sandbox.
- if err := ioutil.WriteFile(filename, []byte("foobar"), 0777); err != nil {
- t.Fatalf("error writing to file %q: %v", filename, err)
- }
-
- // Now we should be able to test the file from within the sandbox.
- if ws, err := c.executeSync(argsTestFile); err != nil {
- t.Fatalf("unexpected error testing file %q: %v", filename, err)
- } else if ws.ExitStatus() != 0 {
- t.Errorf("test %q exited with code %v, wanted zero", filename, ws.ExitStatus())
- }
-
- // Rename the file from outside of the sandbox.
- newFilename := filepath.Join(dir, "newfile")
- if err := os.Rename(filename, newFilename); err != nil {
- t.Fatalf("os.Rename(%q, %q) failed: %v", filename, newFilename, err)
- }
-
- // File should no longer exist at the old path within the sandbox.
- if ws, err := c.executeSync(argsTestFile); err != nil {
- t.Fatalf("unexpected error testing file %q: %v", filename, err)
- } else if ws.ExitStatus() == 0 {
- t.Errorf("test %q exited with code %v, wanted not zero", filename, ws.ExitStatus())
- }
-
- // We should be able to test the new filename from within the sandbox.
- argsTestNewFile := &control.ExecArgs{
- Filename: "/usr/bin/test",
- Argv: []string{"test", "-f", newFilename},
- }
- if ws, err := c.executeSync(argsTestNewFile); err != nil {
- t.Fatalf("unexpected error testing file %q: %v", newFilename, err)
- } else if ws.ExitStatus() != 0 {
- t.Errorf("test %q exited with code %v, wanted zero", newFilename, ws.ExitStatus())
- }
-
- // Delete the renamed file from outside of the sandbox.
- if err := os.Remove(newFilename); err != nil {
- t.Fatalf("error removing file %q: %v", filename, err)
- }
-
- // Renamed file should no longer exist at the old path within the sandbox.
- if ws, err := c.executeSync(argsTestNewFile); err != nil {
- t.Fatalf("unexpected error testing file %q: %v", newFilename, err)
- } else if ws.ExitStatus() == 0 {
- t.Errorf("test %q exited with code %v, wanted not zero", newFilename, ws.ExitStatus())
- }
-
- // Now create the file from WITHIN the sandbox.
- argsTouch := &control.ExecArgs{
- Filename: "/usr/bin/touch",
- Argv: []string{"touch", filename},
- KUID: auth.KUID(os.Getuid()),
- KGID: auth.KGID(os.Getgid()),
- }
- if ws, err := c.executeSync(argsTouch); err != nil {
- t.Fatalf("unexpected error touching file %q: %v", filename, err)
- } else if ws.ExitStatus() != 0 {
- t.Errorf("touch %q exited with code %v, wanted zero", filename, ws.ExitStatus())
- }
-
- // File should exist outside the sandbox.
- if _, err := os.Stat(filename); err != nil {
- t.Errorf("stat %q got error %v, wanted nil", filename, err)
- }
-
- // File should exist outside the sandbox.
- if _, err := os.Stat(filename); err != nil {
- t.Errorf("stat %q got error %v, wanted nil", filename, err)
- }
-
- // Delete the file from within the sandbox.
- argsRemove := &control.ExecArgs{
- Filename: "/bin/rm",
- Argv: []string{"rm", filename},
- }
- if ws, err := c.executeSync(argsRemove); err != nil {
- t.Fatalf("unexpected error removing file %q: %v", filename, err)
- } else if ws.ExitStatus() != 0 {
- t.Errorf("remove %q exited with code %v, wanted zero", filename, ws.ExitStatus())
- }
-
- // File should not exist outside the sandbox.
- if _, err := os.Stat(filename); !os.IsNotExist(err) {
- t.Errorf("stat %q got error %v, wanted ErrNotExist", filename, err)
- }
-}
-
-func checkFile(c *Container, filename string, want []byte) error {
- cpy := filename + ".copy"
- argsCp := &control.ExecArgs{
- Filename: "/bin/cp",
- Argv: []string{"cp", "-f", filename, cpy},
- }
- if _, err := c.executeSync(argsCp); err != nil {
- return fmt.Errorf("unexpected error copying file %q to %q: %v", filename, cpy, err)
- }
- got, err := ioutil.ReadFile(cpy)
- if err != nil {
- return fmt.Errorf("Error reading file %q: %v", filename, err)
- }
- if !bytes.Equal(got, want) {
- return fmt.Errorf("file content inside the sandbox is wrong, got: %q, want: %q", got, want)
- }
- return nil
-}
-
-// TestSharedVolumeFile tests that changes to file content outside the sandbox
-// is reflected inside.
-func TestSharedVolumeFile(t *testing.T) {
- conf := testutil.TestConfig()
- conf.FileAccess = boot.FileAccessShared
- t.Logf("Running test with conf: %+v", conf)
-
- // Main process just sleeps. We will use "exec" to probe the state of
- // the filesystem.
- spec := testutil.NewSpecWithArgs("sleep", "1000")
-
- dir, err := ioutil.TempDir(testutil.TmpDir(), "shared-volume-test")
- if err != nil {
- t.Fatalf("TempDir failed: %v", err)
- }
-
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- // Create and start the container.
- args := Args{
- ID: testutil.UniqueContainerID(),
- Spec: spec,
- BundleDir: bundleDir,
- }
- c, err := New(conf, args)
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- defer c.Destroy()
- if err := c.Start(conf); err != nil {
- t.Fatalf("error starting container: %v", err)
- }
-
- // File that will be used to check consistency inside/outside sandbox.
- filename := filepath.Join(dir, "file")
-
- // Write file from outside the container and check that the same content is
- // read inside.
- want := []byte("host-")
- if err := ioutil.WriteFile(filename, []byte(want), 0666); err != nil {
- t.Fatalf("Error writing to %q: %v", filename, err)
- }
- if err := checkFile(c, filename, want); err != nil {
- t.Fatal(err.Error())
- }
-
- // Append to file inside the container and check that content is not lost.
- argsAppend := &control.ExecArgs{
- Filename: "/bin/bash",
- Argv: []string{"bash", "-c", "echo -n sandbox- >> " + filename},
- }
- if _, err := c.executeSync(argsAppend); err != nil {
- t.Fatalf("unexpected error appending file %q: %v", filename, err)
- }
- want = []byte("host-sandbox-")
- if err := checkFile(c, filename, want); err != nil {
- t.Fatal(err.Error())
- }
-
- // Write again from outside the container and check that the same content is
- // read inside.
- f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0)
- if err != nil {
- t.Fatalf("Error openning file %q: %v", filename, err)
- }
- defer f.Close()
- if _, err := f.Write([]byte("host")); err != nil {
- t.Fatalf("Error writing to file %q: %v", filename, err)
- }
- want = []byte("host-sandbox-host")
- if err := checkFile(c, filename, want); err != nil {
- t.Fatal(err.Error())
- }
-
- // Shrink file outside and check that the same content is read inside.
- if err := f.Truncate(5); err != nil {
- t.Fatalf("Error truncating file %q: %v", filename, err)
- }
- want = want[:5]
- if err := checkFile(c, filename, want); err != nil {
- t.Fatal(err.Error())
- }
-}
diff --git a/runsc/container/test_app/BUILD b/runsc/container/test_app/BUILD
deleted file mode 100644
index 9bf9e6e9d..000000000
--- a/runsc/container/test_app/BUILD
+++ /dev/null
@@ -1,19 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary")
-
-package(licenses = ["notice"])
-
-go_binary(
- name = "test_app",
- testonly = 1,
- srcs = [
- "fds.go",
- "test_app.go",
- ],
- pure = "on",
- visibility = ["//runsc/container:__pkg__"],
- deps = [
- "//pkg/unet",
- "//runsc/testutil",
- "@com_github_google_subcommands//:go_default_library",
- ],
-)
diff --git a/runsc/container/test_app/fds.go b/runsc/container/test_app/fds.go
deleted file mode 100644
index a90cc1662..000000000
--- a/runsc/container/test_app/fds.go
+++ /dev/null
@@ -1,185 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package main
-
-import (
- "context"
- "io/ioutil"
- "log"
- "os"
- "time"
-
- "flag"
- "github.com/google/subcommands"
- "gvisor.dev/gvisor/pkg/unet"
- "gvisor.dev/gvisor/runsc/testutil"
-)
-
-const fileContents = "foobarbaz"
-
-// fdSender will open a file and send the FD over a unix domain socket.
-type fdSender struct {
- socketPath string
-}
-
-// Name implements subcommands.Command.Name.
-func (*fdSender) Name() string {
- return "fd_sender"
-}
-
-// Synopsis implements subcommands.Command.Synopsys.
-func (*fdSender) Synopsis() string {
- return "creates a file and sends the FD over the socket"
-}
-
-// Usage implements subcommands.Command.Usage.
-func (*fdSender) Usage() string {
- return "fd_sender <flags>"
-}
-
-// SetFlags implements subcommands.Command.SetFlags.
-func (fds *fdSender) SetFlags(f *flag.FlagSet) {
- f.StringVar(&fds.socketPath, "socket", "", "path to socket")
-}
-
-// Execute implements subcommands.Command.Execute.
-func (fds *fdSender) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
- if fds.socketPath == "" {
- log.Fatalf("socket flag must be set")
- }
-
- dir, err := ioutil.TempDir("", "")
- if err != nil {
- log.Fatalf("TempDir failed: %v", err)
- }
-
- fileToSend, err := ioutil.TempFile(dir, "")
- if err != nil {
- log.Fatalf("TempFile failed: %v", err)
- }
- defer fileToSend.Close()
-
- if _, err := fileToSend.WriteString(fileContents); err != nil {
- log.Fatalf("Write(%q) failed: %v", fileContents, err)
- }
-
- // Receiver may not be started yet, so try connecting in a poll loop.
- var s *unet.Socket
- if err := testutil.Poll(func() error {
- var err error
- s, err = unet.Connect(fds.socketPath, true /* SEQPACKET, so we can send empty message with FD */)
- return err
- }, 10*time.Second); err != nil {
- log.Fatalf("Error connecting to socket %q: %v", fds.socketPath, err)
- }
- defer s.Close()
-
- w := s.Writer(true)
- w.ControlMessage.PackFDs(int(fileToSend.Fd()))
- if _, err := w.WriteVec([][]byte{[]byte{'a'}}); err != nil {
- log.Fatalf("Error sending FD %q over socket %q: %v", fileToSend.Fd(), fds.socketPath, err)
- }
-
- log.Print("FD SENDER exiting successfully")
- return subcommands.ExitSuccess
-}
-
-// fdReceiver receives an FD from a unix domain socket and does things to it.
-type fdReceiver struct {
- socketPath string
-}
-
-// Name implements subcommands.Command.Name.
-func (*fdReceiver) Name() string {
- return "fd_receiver"
-}
-
-// Synopsis implements subcommands.Command.Synopsys.
-func (*fdReceiver) Synopsis() string {
- return "reads an FD from a unix socket, and then does things to it"
-}
-
-// Usage implements subcommands.Command.Usage.
-func (*fdReceiver) Usage() string {
- return "fd_receiver <flags>"
-}
-
-// SetFlags implements subcommands.Command.SetFlags.
-func (fdr *fdReceiver) SetFlags(f *flag.FlagSet) {
- f.StringVar(&fdr.socketPath, "socket", "", "path to socket")
-}
-
-// Execute implements subcommands.Command.Execute.
-func (fdr *fdReceiver) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
- if fdr.socketPath == "" {
- log.Fatalf("Flags cannot be empty, given: socket: %q", fdr.socketPath)
- }
-
- ss, err := unet.BindAndListen(fdr.socketPath, true /* packet */)
- if err != nil {
- log.Fatalf("BindAndListen(%q) failed: %v", fdr.socketPath, err)
- }
- defer ss.Close()
-
- var s *unet.Socket
- c := make(chan error, 1)
- go func() {
- var err error
- s, err = ss.Accept()
- c <- err
- }()
-
- select {
- case err := <-c:
- if err != nil {
- log.Fatalf("Accept() failed: %v", err)
- }
- case <-time.After(10 * time.Second):
- log.Fatalf("Timeout waiting for accept")
- }
-
- r := s.Reader(true)
- r.EnableFDs(1)
- b := [][]byte{{'a'}}
- if n, err := r.ReadVec(b); n != 1 || err != nil {
- log.Fatalf("ReadVec got n=%d err %v (wanted 0, nil)", n, err)
- }
-
- fds, err := r.ExtractFDs()
- if err != nil {
- log.Fatalf("ExtractFD() got err %v", err)
- }
- if len(fds) != 1 {
- log.Fatalf("ExtractFD() got %d FDs, wanted 1", len(fds))
- }
- fd := fds[0]
-
- file := os.NewFile(uintptr(fd), "received file")
- defer file.Close()
- if _, err := file.Seek(0, os.SEEK_SET); err != nil {
- log.Fatalf("Seek(0, 0) failed: %v", err)
- }
-
- got, err := ioutil.ReadAll(file)
- if err != nil {
- log.Fatalf("ReadAll failed: %v", err)
- }
- if string(got) != fileContents {
- log.Fatalf("ReadAll got %q want %q", string(got), fileContents)
- }
-
- log.Print("FD RECEIVER exiting successfully")
- return subcommands.ExitSuccess
-}
diff --git a/runsc/container/test_app/test_app.go b/runsc/container/test_app/test_app.go
deleted file mode 100644
index 7f735c254..000000000
--- a/runsc/container/test_app/test_app.go
+++ /dev/null
@@ -1,289 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Binary test_app is like a swiss knife for tests that need to run anything
-// inside the sandbox. New functionality can be added with new commands.
-package main
-
-import (
- "context"
- "fmt"
- "log"
- "net"
- "os"
- "os/exec"
- "strconv"
- sys "syscall"
- "time"
-
- "flag"
- "github.com/google/subcommands"
- "gvisor.dev/gvisor/runsc/testutil"
-)
-
-func main() {
- subcommands.Register(subcommands.HelpCommand(), "")
- subcommands.Register(subcommands.FlagsCommand(), "")
- subcommands.Register(new(fdReceiver), "")
- subcommands.Register(new(fdSender), "")
- subcommands.Register(new(forkBomb), "")
- subcommands.Register(new(reaper), "")
- subcommands.Register(new(syscall), "")
- subcommands.Register(new(taskTree), "")
- subcommands.Register(new(uds), "")
-
- flag.Parse()
-
- exitCode := subcommands.Execute(context.Background())
- os.Exit(int(exitCode))
-}
-
-type uds struct {
- fileName string
- socketPath string
-}
-
-// Name implements subcommands.Command.Name.
-func (*uds) Name() string {
- return "uds"
-}
-
-// Synopsis implements subcommands.Command.Synopsys.
-func (*uds) Synopsis() string {
- return "creates unix domain socket client and server. Client sends a contant flow of sequential numbers. Server prints them to --file"
-}
-
-// Usage implements subcommands.Command.Usage.
-func (*uds) Usage() string {
- return "uds <flags>"
-}
-
-// SetFlags implements subcommands.Command.SetFlags.
-func (c *uds) SetFlags(f *flag.FlagSet) {
- f.StringVar(&c.fileName, "file", "", "name of output file")
- f.StringVar(&c.socketPath, "socket", "", "path to socket")
-}
-
-// Execute implements subcommands.Command.Execute.
-func (c *uds) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
- if c.fileName == "" || c.socketPath == "" {
- log.Fatalf("Flags cannot be empty, given: fileName: %q, socketPath: %q", c.fileName, c.socketPath)
- return subcommands.ExitFailure
- }
- outputFile, err := os.OpenFile(c.fileName, os.O_WRONLY|os.O_CREATE, 0666)
- if err != nil {
- log.Fatal("error opening output file:", err)
- }
-
- defer os.Remove(c.socketPath)
-
- listener, err := net.Listen("unix", c.socketPath)
- if err != nil {
- log.Fatal("error listening on socket %q:", c.socketPath, err)
- }
-
- go server(listener, outputFile)
- for i := 0; ; i++ {
- conn, err := net.Dial("unix", c.socketPath)
- if err != nil {
- log.Fatal("error dialing:", err)
- }
- if _, err := conn.Write([]byte(strconv.Itoa(i))); err != nil {
- log.Fatal("error writing:", err)
- }
- conn.Close()
- time.Sleep(100 * time.Millisecond)
- }
-}
-
-func server(listener net.Listener, out *os.File) {
- buf := make([]byte, 16)
-
- for {
- c, err := listener.Accept()
- if err != nil {
- log.Fatal("error accepting connection:", err)
- }
- nr, err := c.Read(buf)
- if err != nil {
- log.Fatal("error reading from buf:", err)
- }
- data := buf[0:nr]
- fmt.Fprint(out, string(data)+"\n")
- }
-}
-
-type taskTree struct {
- depth int
- width int
- pause bool
-}
-
-// Name implements subcommands.Command.
-func (*taskTree) Name() string {
- return "task-tree"
-}
-
-// Synopsis implements subcommands.Command.
-func (*taskTree) Synopsis() string {
- return "creates a tree of tasks"
-}
-
-// Usage implements subcommands.Command.
-func (*taskTree) Usage() string {
- return "task-tree <flags>"
-}
-
-// SetFlags implements subcommands.Command.
-func (c *taskTree) SetFlags(f *flag.FlagSet) {
- f.IntVar(&c.depth, "depth", 1, "number of levels to create")
- f.IntVar(&c.width, "width", 1, "number of tasks at each level")
- f.BoolVar(&c.pause, "pause", false, "whether the tasks should pause perpetually")
-}
-
-// Execute implements subcommands.Command.
-func (c *taskTree) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
- stop := testutil.StartReaper()
- defer stop()
-
- if c.depth == 0 {
- log.Printf("Child sleeping, PID: %d\n", os.Getpid())
- select {}
- }
- log.Printf("Parent %d sleeping, PID: %d\n", c.depth, os.Getpid())
-
- var cmds []*exec.Cmd
- for i := 0; i < c.width; i++ {
- cmd := exec.Command(
- "/proc/self/exe", c.Name(),
- "--depth", strconv.Itoa(c.depth-1),
- "--width", strconv.Itoa(c.width),
- "--pause", strconv.FormatBool(c.pause))
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
-
- if err := cmd.Start(); err != nil {
- log.Fatal("failed to call self:", err)
- }
- cmds = append(cmds, cmd)
- }
-
- for _, c := range cmds {
- c.Wait()
- }
-
- if c.pause {
- select {}
- }
-
- return subcommands.ExitSuccess
-}
-
-type forkBomb struct {
- delay time.Duration
-}
-
-// Name implements subcommands.Command.
-func (*forkBomb) Name() string {
- return "fork-bomb"
-}
-
-// Synopsis implements subcommands.Command.
-func (*forkBomb) Synopsis() string {
- return "creates child process until the end of times"
-}
-
-// Usage implements subcommands.Command.
-func (*forkBomb) Usage() string {
- return "fork-bomb <flags>"
-}
-
-// SetFlags implements subcommands.Command.
-func (c *forkBomb) SetFlags(f *flag.FlagSet) {
- f.DurationVar(&c.delay, "delay", 100*time.Millisecond, "amount of time to delay creation of child")
-}
-
-// Execute implements subcommands.Command.
-func (c *forkBomb) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
- time.Sleep(c.delay)
-
- cmd := exec.Command("/proc/self/exe", c.Name())
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- if err := cmd.Run(); err != nil {
- log.Fatal("failed to call self:", err)
- }
- return subcommands.ExitSuccess
-}
-
-type reaper struct{}
-
-// Name implements subcommands.Command.
-func (*reaper) Name() string {
- return "reaper"
-}
-
-// Synopsis implements subcommands.Command.
-func (*reaper) Synopsis() string {
- return "reaps all children in a loop"
-}
-
-// Usage implements subcommands.Command.
-func (*reaper) Usage() string {
- return "reaper <flags>"
-}
-
-// SetFlags implements subcommands.Command.
-func (*reaper) SetFlags(*flag.FlagSet) {}
-
-// Execute implements subcommands.Command.
-func (c *reaper) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
- stop := testutil.StartReaper()
- defer stop()
- select {}
-}
-
-type syscall struct {
- sysno uint64
-}
-
-// Name implements subcommands.Command.
-func (*syscall) Name() string {
- return "syscall"
-}
-
-// Synopsis implements subcommands.Command.
-func (*syscall) Synopsis() string {
- return "syscall makes a syscall"
-}
-
-// Usage implements subcommands.Command.
-func (*syscall) Usage() string {
- return "syscall <flags>"
-}
-
-// SetFlags implements subcommands.Command.
-func (s *syscall) SetFlags(f *flag.FlagSet) {
- f.Uint64Var(&s.sysno, "syscall", 0, "syscall to call")
-}
-
-// Execute implements subcommands.Command.
-func (s *syscall) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
- if _, _, errno := sys.Syscall(uintptr(s.sysno), 0, 0, 0); errno != 0 {
- fmt.Printf("syscall(%d, 0, 0...) failed: %v\n", s.sysno, errno)
- } else {
- fmt.Printf("syscall(%d, 0, 0...) success\n", s.sysno)
- }
- return subcommands.ExitSuccess
-}
diff --git a/runsc/criutil/BUILD b/runsc/criutil/BUILD
deleted file mode 100644
index 558133a0e..000000000
--- a/runsc/criutil/BUILD
+++ /dev/null
@@ -1,12 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "criutil",
- testonly = 1,
- srcs = ["criutil.go"],
- importpath = "gvisor.dev/gvisor/runsc/criutil",
- visibility = ["//:sandbox"],
- deps = ["//runsc/testutil"],
-)
diff --git a/runsc/criutil/criutil.go b/runsc/criutil/criutil.go
deleted file mode 100644
index c8ddf5a9a..000000000
--- a/runsc/criutil/criutil.go
+++ /dev/null
@@ -1,246 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package criutil contains utility functions for interacting with the
-// Container Runtime Interface (CRI), principally via the crictl command line
-// tool. This requires critools to be installed on the local system.
-package criutil
-
-import (
- "encoding/json"
- "fmt"
- "os"
- "os/exec"
- "strings"
- "time"
-
- "gvisor.dev/gvisor/runsc/testutil"
-)
-
-const endpointPrefix = "unix://"
-
-// Crictl contains information required to run the crictl utility.
-type Crictl struct {
- executable string
- timeout time.Duration
- imageEndpoint string
- runtimeEndpoint string
-}
-
-// NewCrictl returns a Crictl configured with a timeout and an endpoint over
-// which it will talk to containerd.
-func NewCrictl(timeout time.Duration, endpoint string) *Crictl {
- // Bazel doesn't pass PATH through, assume the location of crictl
- // unless specified by environment variable.
- executable := os.Getenv("CRICTL_PATH")
- if executable == "" {
- executable = "/usr/local/bin/crictl"
- }
- return &Crictl{
- executable: executable,
- timeout: timeout,
- imageEndpoint: endpointPrefix + endpoint,
- runtimeEndpoint: endpointPrefix + endpoint,
- }
-}
-
-// Pull pulls an container image. It corresponds to `crictl pull`.
-func (cc *Crictl) Pull(imageName string) error {
- _, err := cc.run("pull", imageName)
- return err
-}
-
-// RunPod creates a sandbox. It corresponds to `crictl runp`.
-func (cc *Crictl) RunPod(sbSpecFile string) (string, error) {
- podID, err := cc.run("runp", sbSpecFile)
- if err != nil {
- return "", fmt.Errorf("runp failed: %v", err)
- }
- // Strip the trailing newline from crictl output.
- return strings.TrimSpace(podID), nil
-}
-
-// Create creates a container within a sandbox. It corresponds to `crictl
-// create`.
-func (cc *Crictl) Create(podID, contSpecFile, sbSpecFile string) (string, error) {
- podID, err := cc.run("create", podID, contSpecFile, sbSpecFile)
- if err != nil {
- return "", fmt.Errorf("create failed: %v", err)
- }
- // Strip the trailing newline from crictl output.
- return strings.TrimSpace(podID), nil
-}
-
-// Start starts a container. It corresponds to `crictl start`.
-func (cc *Crictl) Start(contID string) (string, error) {
- output, err := cc.run("start", contID)
- if err != nil {
- return "", fmt.Errorf("start failed: %v", err)
- }
- return output, nil
-}
-
-// Stop stops a container. It corresponds to `crictl stop`.
-func (cc *Crictl) Stop(contID string) error {
- _, err := cc.run("stop", contID)
- return err
-}
-
-// Exec execs a program inside a container. It corresponds to `crictl exec`.
-func (cc *Crictl) Exec(contID string, args ...string) (string, error) {
- a := []string{"exec", contID}
- a = append(a, args...)
- output, err := cc.run(a...)
- if err != nil {
- return "", fmt.Errorf("exec failed: %v", err)
- }
- return output, nil
-}
-
-// Rm removes a container. It corresponds to `crictl rm`.
-func (cc *Crictl) Rm(contID string) error {
- _, err := cc.run("rm", contID)
- return err
-}
-
-// StopPod stops a pod. It corresponds to `crictl stopp`.
-func (cc *Crictl) StopPod(podID string) error {
- _, err := cc.run("stopp", podID)
- return err
-}
-
-// containsConfig is a minimal copy of
-// https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/apis/cri/runtime/v1alpha2/api.proto
-// It only contains fields needed for testing.
-type containerConfig struct {
- Status containerStatus
-}
-
-type containerStatus struct {
- Network containerNetwork
-}
-
-type containerNetwork struct {
- IP string
-}
-
-// PodIP returns a pod's IP address.
-func (cc *Crictl) PodIP(podID string) (string, error) {
- output, err := cc.run("inspectp", podID)
- if err != nil {
- return "", err
- }
- conf := &containerConfig{}
- if err := json.Unmarshal([]byte(output), conf); err != nil {
- return "", fmt.Errorf("failed to unmarshal JSON: %v, %s", err, output)
- }
- if conf.Status.Network.IP == "" {
- return "", fmt.Errorf("no IP found in config: %s", output)
- }
- return conf.Status.Network.IP, nil
-}
-
-// RmPod removes a container. It corresponds to `crictl rmp`.
-func (cc *Crictl) RmPod(podID string) error {
- _, err := cc.run("rmp", podID)
- return err
-}
-
-// StartPodAndContainer pulls an image, then starts a sandbox and container in
-// that sandbox. It returns the pod ID and container ID.
-func (cc *Crictl) StartPodAndContainer(image, sbSpec, contSpec string) (string, string, error) {
- if err := cc.Pull(image); err != nil {
- return "", "", fmt.Errorf("failed to pull %s: %v", image, err)
- }
-
- // Write the specs to files that can be read by crictl.
- sbSpecFile, err := testutil.WriteTmpFile("sbSpec", sbSpec)
- if err != nil {
- return "", "", fmt.Errorf("failed to write sandbox spec: %v", err)
- }
- contSpecFile, err := testutil.WriteTmpFile("contSpec", contSpec)
- if err != nil {
- return "", "", fmt.Errorf("failed to write container spec: %v", err)
- }
-
- podID, err := cc.RunPod(sbSpecFile)
- if err != nil {
- return "", "", err
- }
-
- contID, err := cc.Create(podID, contSpecFile, sbSpecFile)
- if err != nil {
- return "", "", fmt.Errorf("failed to create container in pod %q: %v", podID, err)
- }
-
- if _, err := cc.Start(contID); err != nil {
- return "", "", fmt.Errorf("failed to start container %q in pod %q: %v", contID, podID, err)
- }
-
- return podID, contID, nil
-}
-
-// StopPodAndContainer stops a container and pod.
-func (cc *Crictl) StopPodAndContainer(podID, contID string) error {
- if err := cc.Stop(contID); err != nil {
- return fmt.Errorf("failed to stop container %q in pod %q: %v", contID, podID, err)
- }
-
- if err := cc.Rm(contID); err != nil {
- return fmt.Errorf("failed to remove container %q in pod %q: %v", contID, podID, err)
- }
-
- if err := cc.StopPod(podID); err != nil {
- return fmt.Errorf("failed to stop pod %q: %v", podID, err)
- }
-
- if err := cc.RmPod(podID); err != nil {
- return fmt.Errorf("failed to remove pod %q: %v", podID, err)
- }
-
- return nil
-}
-
-// run runs crictl with the given args and returns an error if it takes longer
-// than cc.Timeout to run.
-func (cc *Crictl) run(args ...string) (string, error) {
- defaultArgs := []string{
- "--image-endpoint", cc.imageEndpoint,
- "--runtime-endpoint", cc.runtimeEndpoint,
- }
- cmd := exec.Command(cc.executable, append(defaultArgs, args...)...)
-
- // Run the command with a timeout.
- done := make(chan string)
- errCh := make(chan error)
- go func() {
- output, err := cmd.CombinedOutput()
- if err != nil {
- errCh <- fmt.Errorf("error: \"%v\", output: %s", err, string(output))
- return
- }
- done <- string(output)
- }()
- select {
- case output := <-done:
- return output, nil
- case err := <-errCh:
- return "", err
- case <-time.After(cc.timeout):
- if err := testutil.KillCommand(cmd); err != nil {
- return "", fmt.Errorf("timed out, then couldn't kill process %+v: %v", cmd, err)
- }
- return "", fmt.Errorf("timed out: %+v", cmd)
- }
-}
diff --git a/runsc/debian/description b/runsc/debian/description
deleted file mode 100644
index 6e3b1b2c0..000000000
--- a/runsc/debian/description
+++ /dev/null
@@ -1,5 +0,0 @@
-gVisor is a user-space kernel, written in Go, that implements a substantial
-portion of the Linux system surface. It includes an Open Container Initiative
-(OCI) runtime called runsc that provides an isolation boundary between the
-application and the host kernel. The runsc runtime integrates with Docker and
-Kubernetes, making it simple to run sandboxed containers.
diff --git a/runsc/debian/postinst.sh b/runsc/debian/postinst.sh
deleted file mode 100755
index dc7aeee87..000000000
--- a/runsc/debian/postinst.sh
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/sh -e
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-if [ "$1" != configure ]; then
- exit 0
-fi
-
-if [ -f /etc/docker/daemon.json ]; then
- runsc install
- systemctl restart docker || echo "unable to restart docker; you must do so manually." >&2
-fi
diff --git a/runsc/dockerutil/BUILD b/runsc/dockerutil/BUILD
deleted file mode 100644
index 0e0423504..000000000
--- a/runsc/dockerutil/BUILD
+++ /dev/null
@@ -1,15 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "dockerutil",
- testonly = 1,
- srcs = ["dockerutil.go"],
- importpath = "gvisor.dev/gvisor/runsc/dockerutil",
- visibility = ["//:sandbox"],
- deps = [
- "//runsc/testutil",
- "@com_github_kr_pty//:go_default_library",
- ],
-)
diff --git a/runsc/dockerutil/dockerutil.go b/runsc/dockerutil/dockerutil.go
deleted file mode 100644
index c073d8f75..000000000
--- a/runsc/dockerutil/dockerutil.go
+++ /dev/null
@@ -1,452 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package dockerutil is a collection of utility functions, primarily for
-// testing.
-package dockerutil
-
-import (
- "encoding/json"
- "flag"
- "fmt"
- "io/ioutil"
- "log"
- "os"
- "os/exec"
- "path"
- "regexp"
- "strconv"
- "strings"
- "syscall"
- "time"
-
- "github.com/kr/pty"
- "gvisor.dev/gvisor/runsc/testutil"
-)
-
-var (
- runtime = flag.String("runtime", "runsc", "specify which runtime to use")
- config = flag.String("config_path", "/etc/docker/daemon.json", "configuration file for reading paths")
-)
-
-// EnsureSupportedDockerVersion checks if correct docker is installed.
-func EnsureSupportedDockerVersion() {
- cmd := exec.Command("docker", "version")
- out, err := cmd.CombinedOutput()
- if err != nil {
- log.Fatalf("Error running %q: %v", "docker version", err)
- }
- re := regexp.MustCompile(`Version:\s+(\d+)\.(\d+)\.\d.*`)
- matches := re.FindStringSubmatch(string(out))
- if len(matches) != 3 {
- log.Fatalf("Invalid docker output: %s", out)
- }
- major, _ := strconv.Atoi(matches[1])
- minor, _ := strconv.Atoi(matches[2])
- if major < 17 || (major == 17 && minor < 9) {
- log.Fatalf("Docker version 17.09.0 or greater is required, found: %02d.%02d", major, minor)
- }
-}
-
-// RuntimePath returns the binary path for the current runtime.
-func RuntimePath() (string, error) {
- // Read the configuration data; the file must exist.
- configBytes, err := ioutil.ReadFile(*config)
- if err != nil {
- return "", err
- }
-
- // Unmarshal the configuration.
- c := make(map[string]interface{})
- if err := json.Unmarshal(configBytes, &c); err != nil {
- return "", err
- }
-
- // Decode the expected configuration.
- r, ok := c["runtimes"]
- if !ok {
- return "", fmt.Errorf("no runtimes declared: %v", c)
- }
- rs, ok := r.(map[string]interface{})
- if !ok {
- // The runtimes are not a map.
- return "", fmt.Errorf("unexpected format: %v", c)
- }
- r, ok = rs[*runtime]
- if !ok {
- // The expected runtime is not declared.
- return "", fmt.Errorf("runtime %q not found: %v", *runtime, c)
- }
- rs, ok = r.(map[string]interface{})
- if !ok {
- // The runtime is not a map.
- return "", fmt.Errorf("unexpected format: %v", c)
- }
- p, ok := rs["path"].(string)
- if !ok {
- // The runtime does not declare a path.
- return "", fmt.Errorf("unexpected format: %v", c)
- }
- return p, nil
-}
-
-// MountMode describes if the mount should be ro or rw.
-type MountMode int
-
-const (
- // ReadOnly is what the name says.
- ReadOnly MountMode = iota
- // ReadWrite is what the name says.
- ReadWrite
-)
-
-// String returns the mount mode argument for this MountMode.
-func (m MountMode) String() string {
- switch m {
- case ReadOnly:
- return "ro"
- case ReadWrite:
- return "rw"
- }
- panic(fmt.Sprintf("invalid mode: %d", m))
-}
-
-// MountArg formats the volume argument to mount in the container.
-func MountArg(source, target string, mode MountMode) string {
- return fmt.Sprintf("-v=%s:%s:%v", source, target, mode)
-}
-
-// LinkArg formats the link argument.
-func LinkArg(source *Docker, target string) string {
- return fmt.Sprintf("--link=%s:%s", source.Name, target)
-}
-
-// PrepareFiles creates temp directory to copy files there. The sandbox doesn't
-// have access to files in the test dir.
-func PrepareFiles(names ...string) (string, error) {
- dir, err := ioutil.TempDir("", "image-test")
- if err != nil {
- return "", fmt.Errorf("ioutil.TempDir failed: %v", err)
- }
- if err := os.Chmod(dir, 0777); err != nil {
- return "", fmt.Errorf("os.Chmod(%q, 0777) failed: %v", dir, err)
- }
- for _, name := range names {
- src := getLocalPath(name)
- dst := path.Join(dir, name)
- if err := testutil.Copy(src, dst); err != nil {
- return "", fmt.Errorf("testutil.Copy(%q, %q) failed: %v", src, dst, err)
- }
- }
- return dir, nil
-}
-
-func getLocalPath(file string) string {
- return path.Join(".", file)
-}
-
-// do executes docker command.
-func do(args ...string) (string, error) {
- log.Printf("Running: docker %s\n", args)
- cmd := exec.Command("docker", args...)
- out, err := cmd.CombinedOutput()
- if err != nil {
- return "", fmt.Errorf("error executing docker %s: %v\nout: %s", args, err, out)
- }
- return string(out), nil
-}
-
-// doWithPty executes docker command with stdio attached to a pty.
-func doWithPty(args ...string) (*exec.Cmd, *os.File, error) {
- log.Printf("Running with pty: docker %s\n", args)
- cmd := exec.Command("docker", args...)
- ptmx, err := pty.Start(cmd)
- if err != nil {
- return nil, nil, fmt.Errorf("error executing docker %s with a pty: %v", args, err)
- }
- return cmd, ptmx, nil
-}
-
-// Pull pulls a docker image. This is used in tests to isolate the
-// time to pull the image off the network from the time to actually
-// start the container, to avoid timeouts over slow networks.
-func Pull(image string) error {
- _, err := do("pull", image)
- return err
-}
-
-// Docker contains the name and the runtime of a docker container.
-type Docker struct {
- Runtime string
- Name string
-}
-
-// MakeDocker sets up the struct for a Docker container.
-// Names of containers will be unique.
-func MakeDocker(namePrefix string) Docker {
- return Docker{
- Name: testutil.RandomName(namePrefix),
- Runtime: *runtime,
- }
-}
-
-// logDockerID logs a container id, which is needed to find container runsc logs.
-func (d *Docker) logDockerID() {
- id, err := d.ID()
- if err != nil {
- log.Printf("%v\n", err)
- }
- log.Printf("Name: %s ID: %v\n", d.Name, id)
-}
-
-// Create calls 'docker create' with the arguments provided.
-func (d *Docker) Create(args ...string) error {
- a := []string{"create", "--runtime", d.Runtime, "--name", d.Name}
- a = append(a, args...)
- _, err := do(a...)
- if err == nil {
- d.logDockerID()
- }
- return err
-}
-
-// Start calls 'docker start'.
-func (d *Docker) Start() error {
- if _, err := do("start", d.Name); err != nil {
- return fmt.Errorf("error starting container %q: %v", d.Name, err)
- }
- return nil
-}
-
-// Stop calls 'docker stop'.
-func (d *Docker) Stop() error {
- if _, err := do("stop", d.Name); err != nil {
- return fmt.Errorf("error stopping container %q: %v", d.Name, err)
- }
- return nil
-}
-
-// Run calls 'docker run' with the arguments provided. The container starts
-// running in the background and the call returns immediately.
-func (d *Docker) Run(args ...string) error {
- a := d.runArgs("-d")
- a = append(a, args...)
- _, err := do(a...)
- if err == nil {
- d.logDockerID()
- }
- return err
-}
-
-// RunWithPty is like Run but with an attached pty.
-func (d *Docker) RunWithPty(args ...string) (*exec.Cmd, *os.File, error) {
- a := d.runArgs("-it")
- a = append(a, args...)
- return doWithPty(a...)
-}
-
-// RunFg calls 'docker run' with the arguments provided in the foreground. It
-// blocks until the container exits and returns the output.
-func (d *Docker) RunFg(args ...string) (string, error) {
- a := d.runArgs(args...)
- out, err := do(a...)
- if err == nil {
- d.logDockerID()
- }
- return string(out), err
-}
-
-func (d *Docker) runArgs(args ...string) []string {
- // Environment variable RUNSC_TEST_NAME is picked up by the runtime and added
- // to the log name, so one can easily identify the corresponding logs for
- // this test.
- rv := []string{"run", "--runtime", d.Runtime, "--name", d.Name, "-e", "RUNSC_TEST_NAME=" + d.Name}
- return append(rv, args...)
-}
-
-// Logs calls 'docker logs'.
-func (d *Docker) Logs() (string, error) {
- return do("logs", d.Name)
-}
-
-// Exec calls 'docker exec' with the arguments provided.
-func (d *Docker) Exec(args ...string) (string, error) {
- a := []string{"exec", d.Name}
- a = append(a, args...)
- return do(a...)
-}
-
-// ExecWithTerminal calls 'docker exec -it' with the arguments provided and
-// attaches a pty to stdio.
-func (d *Docker) ExecWithTerminal(args ...string) (*exec.Cmd, *os.File, error) {
- a := []string{"exec", "-it", d.Name}
- a = append(a, args...)
- return doWithPty(a...)
-}
-
-// Pause calls 'docker pause'.
-func (d *Docker) Pause() error {
- if _, err := do("pause", d.Name); err != nil {
- return fmt.Errorf("error pausing container %q: %v", d.Name, err)
- }
- return nil
-}
-
-// Unpause calls 'docker pause'.
-func (d *Docker) Unpause() error {
- if _, err := do("unpause", d.Name); err != nil {
- return fmt.Errorf("error unpausing container %q: %v", d.Name, err)
- }
- return nil
-}
-
-// Checkpoint calls 'docker checkpoint'.
-func (d *Docker) Checkpoint(name string) error {
- if _, err := do("checkpoint", "create", d.Name, name); err != nil {
- return fmt.Errorf("error pausing container %q: %v", d.Name, err)
- }
- return nil
-}
-
-// Restore calls 'docker start --checkname [name]'.
-func (d *Docker) Restore(name string) error {
- if _, err := do("start", "--checkpoint", name, d.Name); err != nil {
- return fmt.Errorf("error starting container %q: %v", d.Name, err)
- }
- return nil
-}
-
-// Remove calls 'docker rm'.
-func (d *Docker) Remove() error {
- if _, err := do("rm", d.Name); err != nil {
- return fmt.Errorf("error deleting container %q: %v", d.Name, err)
- }
- return nil
-}
-
-// CleanUp kills and deletes the container (best effort).
-func (d *Docker) CleanUp() {
- d.logDockerID()
- if _, err := do("kill", d.Name); err != nil {
- if strings.Contains(err.Error(), "is not running") {
- // Nothing to kill. Don't log the error in this case.
- } else {
- log.Printf("error killing container %q: %v", d.Name, err)
- }
- }
- if err := d.Remove(); err != nil {
- log.Print(err)
- }
-}
-
-// FindPort returns the host port that is mapped to 'sandboxPort'. This calls
-// docker to allocate a free port in the host and prevent conflicts.
-func (d *Docker) FindPort(sandboxPort int) (int, error) {
- format := fmt.Sprintf(`{{ (index (index .NetworkSettings.Ports "%d/tcp") 0).HostPort }}`, sandboxPort)
- out, err := do("inspect", "-f", format, d.Name)
- if err != nil {
- return -1, fmt.Errorf("error retrieving port: %v", err)
- }
- port, err := strconv.Atoi(strings.TrimSuffix(string(out), "\n"))
- if err != nil {
- return -1, fmt.Errorf("error parsing port %q: %v", out, err)
- }
- return port, nil
-}
-
-// SandboxPid returns the PID to the sandbox process.
-func (d *Docker) SandboxPid() (int, error) {
- out, err := do("inspect", "-f={{.State.Pid}}", d.Name)
- if err != nil {
- return -1, fmt.Errorf("error retrieving pid: %v", err)
- }
- pid, err := strconv.Atoi(strings.TrimSuffix(string(out), "\n"))
- if err != nil {
- return -1, fmt.Errorf("error parsing pid %q: %v", out, err)
- }
- return pid, nil
-}
-
-// ID returns the container ID.
-func (d *Docker) ID() (string, error) {
- out, err := do("inspect", "-f={{.Id}}", d.Name)
- if err != nil {
- return "", fmt.Errorf("error retrieving ID: %v", err)
- }
- return strings.TrimSpace(string(out)), nil
-}
-
-// Wait waits for container to exit, up to the given timeout. Returns error if
-// wait fails or timeout is hit. Returns the application return code otherwise.
-// Note that the application may have failed even if err == nil, always check
-// the exit code.
-func (d *Docker) Wait(timeout time.Duration) (syscall.WaitStatus, error) {
- timeoutChan := time.After(timeout)
- waitChan := make(chan (syscall.WaitStatus))
- errChan := make(chan (error))
-
- go func() {
- out, err := do("wait", d.Name)
- if err != nil {
- errChan <- fmt.Errorf("error waiting for container %q: %v", d.Name, err)
- }
- exit, err := strconv.Atoi(strings.TrimSuffix(string(out), "\n"))
- if err != nil {
- errChan <- fmt.Errorf("error parsing exit code %q: %v", out, err)
- }
- waitChan <- syscall.WaitStatus(uint32(exit))
- }()
-
- select {
- case ws := <-waitChan:
- return ws, nil
- case err := <-errChan:
- return syscall.WaitStatus(1), err
- case <-timeoutChan:
- return syscall.WaitStatus(1), fmt.Errorf("timeout waiting for container %q", d.Name)
- }
-}
-
-// WaitForOutput calls 'docker logs' to retrieve containers output and searches
-// for the given pattern.
-func (d *Docker) WaitForOutput(pattern string, timeout time.Duration) (string, error) {
- matches, err := d.WaitForOutputSubmatch(pattern, timeout)
- if err != nil {
- return "", err
- }
- if len(matches) == 0 {
- return "", nil
- }
- return matches[0], nil
-}
-
-// WaitForOutputSubmatch calls 'docker logs' to retrieve containers output and
-// searches for the given pattern. It returns any regexp submatches as well.
-func (d *Docker) WaitForOutputSubmatch(pattern string, timeout time.Duration) ([]string, error) {
- re := regexp.MustCompile(pattern)
- var out string
- for exp := time.Now().Add(timeout); time.Now().Before(exp); {
- var err error
- out, err = d.Logs()
- if err != nil {
- return nil, err
- }
- if matches := re.FindStringSubmatch(out); matches != nil {
- // Success!
- return matches, nil
- }
- time.Sleep(100 * time.Millisecond)
- }
- return nil, fmt.Errorf("timeout waiting for output %q: %s", re.String(), out)
-}
diff --git a/runsc/fsgofer/BUILD b/runsc/fsgofer/BUILD
deleted file mode 100644
index 80a4aa2fe..000000000
--- a/runsc/fsgofer/BUILD
+++ /dev/null
@@ -1,35 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "fsgofer",
- srcs = [
- "fsgofer.go",
- "fsgofer_unsafe.go",
- ],
- importpath = "gvisor.dev/gvisor/runsc/fsgofer",
- visibility = [
- "//runsc:__subpackages__",
- ],
- deps = [
- "//pkg/abi/linux",
- "//pkg/fd",
- "//pkg/log",
- "//pkg/p9",
- "//pkg/syserr",
- "//runsc/specutils",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-go_test(
- name = "fsgofer_test",
- size = "small",
- srcs = ["fsgofer_test.go"],
- embed = [":fsgofer"],
- deps = [
- "//pkg/log",
- "//pkg/p9",
- ],
-)
diff --git a/runsc/fsgofer/filter/BUILD b/runsc/fsgofer/filter/BUILD
deleted file mode 100644
index 02168ad1b..000000000
--- a/runsc/fsgofer/filter/BUILD
+++ /dev/null
@@ -1,25 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "filter",
- srcs = [
- "config.go",
- "extra_filters.go",
- "extra_filters_msan.go",
- "extra_filters_race.go",
- "filter.go",
- ],
- importpath = "gvisor.dev/gvisor/runsc/fsgofer/filter",
- visibility = [
- "//runsc:__subpackages__",
- ],
- deps = [
- "//pkg/abi/linux",
- "//pkg/flipcall",
- "//pkg/log",
- "//pkg/seccomp",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
diff --git a/runsc/fsgofer/fsgofer_test.go b/runsc/fsgofer/fsgofer_test.go
deleted file mode 100644
index cbbe71019..000000000
--- a/runsc/fsgofer/fsgofer_test.go
+++ /dev/null
@@ -1,692 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fsgofer
-
-import (
- "fmt"
- "io/ioutil"
- "net"
- "os"
- "path"
- "path/filepath"
- "syscall"
- "testing"
-
- "gvisor.dev/gvisor/pkg/log"
- "gvisor.dev/gvisor/pkg/p9"
-)
-
-func init() {
- log.SetLevel(log.Debug)
-
- allConfs = append(allConfs, rwConfs...)
- allConfs = append(allConfs, roConfs...)
-
- if err := OpenProcSelfFD(); err != nil {
- panic(err)
- }
-}
-
-func assertPanic(t *testing.T, f func()) {
- defer func() {
- if r := recover(); r == nil {
- t.Errorf("function did not panic")
- }
- }()
- f()
-}
-
-func testReadWrite(f p9.File, flags p9.OpenFlags, content []byte) error {
- want := make([]byte, len(content))
- copy(want, content)
-
- b := []byte("test-1-2-3")
- w, err := f.WriteAt(b, uint64(len(content)))
- if flags == p9.WriteOnly || flags == p9.ReadWrite {
- if err != nil {
- return fmt.Errorf("WriteAt(): %v", err)
- }
- if w != len(b) {
- return fmt.Errorf("WriteAt() was partial, got: %d, want: %d", w, len(b))
- }
- want = append(want, b...)
- } else {
- if e, ok := err.(syscall.Errno); !ok || e != syscall.EBADF {
- return fmt.Errorf("WriteAt() should have failed, got: %d, want: EBADFD", err)
- }
- }
-
- rBuf := make([]byte, len(want))
- r, err := f.ReadAt(rBuf, 0)
- if flags == p9.ReadOnly || flags == p9.ReadWrite {
- if err != nil {
- return fmt.Errorf("ReadAt(): %v", err)
- }
- if r != len(rBuf) {
- return fmt.Errorf("ReadAt() was partial, got: %d, want: %d", r, len(rBuf))
- }
- if string(rBuf) != string(want) {
- return fmt.Errorf("ReadAt() wrong data, got: %s, want: %s", string(rBuf), want)
- }
- } else {
- if e, ok := err.(syscall.Errno); !ok || e != syscall.EBADF {
- return fmt.Errorf("ReadAt() should have failed, got: %d, want: EBADFD", err)
- }
- }
- return nil
-}
-
-var allOpenFlags = []p9.OpenFlags{p9.ReadOnly, p9.WriteOnly, p9.ReadWrite}
-
-var (
- allTypes = []fileType{regular, directory, symlink}
-
- // allConfs is set in init() above.
- allConfs []Config
-
- rwConfs = []Config{{ROMount: false}}
- roConfs = []Config{{ROMount: true}}
-)
-
-type state struct {
- root *localFile
- file *localFile
- conf Config
- ft fileType
-}
-
-func (s state) String() string {
- return fmt.Sprintf("type(%v)", s.ft)
-}
-
-func runAll(t *testing.T, test func(*testing.T, state)) {
- runCustom(t, allTypes, allConfs, test)
-}
-
-func runCustom(t *testing.T, types []fileType, confs []Config, test func(*testing.T, state)) {
- for _, c := range confs {
- t.Logf("Config: %+v", c)
-
- for _, ft := range types {
- t.Logf("File type: %v", ft)
-
- path, name, err := setup(ft)
- if err != nil {
- t.Fatalf("%v", err)
- }
- defer os.RemoveAll(path)
-
- a, err := NewAttachPoint(path, c)
- if err != nil {
- t.Fatalf("NewAttachPoint failed: %v", err)
- }
- root, err := a.Attach()
- if err != nil {
- t.Fatalf("Attach failed, err: %v", err)
- }
-
- _, file, err := root.Walk([]string{name})
- if err != nil {
- root.Close()
- t.Fatalf("root.Walk({%q}) failed, err: %v", "symlink", err)
- }
-
- st := state{root: root.(*localFile), file: file.(*localFile), conf: c, ft: ft}
- test(t, st)
- file.Close()
- root.Close()
- }
- }
-}
-
-func setup(ft fileType) (string, string, error) {
- path, err := ioutil.TempDir("", "root-")
- if err != nil {
- return "", "", fmt.Errorf("ioutil.TempDir() failed, err: %v", err)
- }
-
- // First attach with writable configuration to setup tree.
- a, err := NewAttachPoint(path, Config{})
- if err != nil {
- return "", "", err
- }
- root, err := a.Attach()
- if err != nil {
- return "", "", fmt.Errorf("Attach failed, err: %v", err)
- }
- defer root.Close()
-
- var name string
- switch ft {
- case regular:
- name = "file"
- _, f, _, _, err := root.Create(name, p9.ReadWrite, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid()))
- if err != nil {
- return "", "", fmt.Errorf("createFile(root, %q) failed, err: %v", "test", err)
- }
- defer f.Close()
- case directory:
- name = "dir"
- if _, err := root.Mkdir(name, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != nil {
- return "", "", fmt.Errorf("root.MkDir(%q) failed, err: %v", name, err)
- }
- case symlink:
- name = "symlink"
- if _, err := root.Symlink("/some/target", name, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != nil {
- return "", "", fmt.Errorf("root.Symlink(%q) failed, err: %v", name, err)
- }
- default:
- panic(fmt.Sprintf("unknown file type %v", ft))
- }
- return path, name, nil
-}
-
-func createFile(dir *localFile, name string) (*localFile, error) {
- _, f, _, _, err := dir.Create(name, p9.ReadWrite, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid()))
- if err != nil {
- return nil, err
- }
- return f.(*localFile), nil
-}
-
-func TestReadWrite(t *testing.T) {
- runCustom(t, []fileType{directory}, rwConfs, func(t *testing.T, s state) {
- child, err := createFile(s.file, "test")
- if err != nil {
- t.Fatalf("%v: createFile() failed, err: %v", s, err)
- }
- defer child.Close()
- want := []byte("foobar")
- w, err := child.WriteAt(want, 0)
- if err != nil {
- t.Fatalf("%v: Write() failed, err: %v", s, err)
- }
- if w != len(want) {
- t.Fatalf("%v: Write() was partial, got: %d, expected: %d", s, w, len(want))
- }
- for _, flags := range allOpenFlags {
- _, l, err := s.file.Walk([]string{"test"})
- if err != nil {
- t.Fatalf("%v: Walk(%s) failed, err: %v", s, "test", err)
- }
- if _, _, _, err := l.Open(flags); err != nil {
- t.Fatalf("%v: Open(%v) failed, err: %v", s, flags, err)
- }
- if err := testReadWrite(l, flags, want); err != nil {
- t.Fatalf("%v: testReadWrite(%v) failed: %v", s, flags, err)
- }
- }
- })
-}
-
-func TestCreate(t *testing.T) {
- runCustom(t, []fileType{directory}, rwConfs, func(t *testing.T, s state) {
- for i, flags := range allOpenFlags {
- _, l, _, _, err := s.file.Create(fmt.Sprintf("test-%d", i), flags, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid()))
- if err != nil {
- t.Fatalf("%v, %v: WriteAt() failed, err: %v", s, flags, err)
- }
-
- if err := testReadWrite(l, flags, []byte{}); err != nil {
- t.Fatalf("%v: testReadWrite(%v) failed: %v", s, flags, err)
- }
- }
- })
-}
-
-// TestReadWriteDup tests that a file opened in any mode can be dup'ed and
-// reopened in any other mode.
-func TestReadWriteDup(t *testing.T) {
- runCustom(t, []fileType{directory}, rwConfs, func(t *testing.T, s state) {
- child, err := createFile(s.file, "test")
- if err != nil {
- t.Fatalf("%v: createFile() failed, err: %v", s, err)
- }
- defer child.Close()
- want := []byte("foobar")
- w, err := child.WriteAt(want, 0)
- if err != nil {
- t.Fatalf("%v: Write() failed, err: %v", s, err)
- }
- if w != len(want) {
- t.Fatalf("%v: Write() was partial, got: %d, expected: %d", s, w, len(want))
- }
- for _, flags := range allOpenFlags {
- _, l, err := s.file.Walk([]string{"test"})
- if err != nil {
- t.Fatalf("%v: Walk(%s) failed, err: %v", s, "test", err)
- }
- defer l.Close()
- if _, _, _, err := l.Open(flags); err != nil {
- t.Fatalf("%v: Open(%v) failed, err: %v", s, flags, err)
- }
- for _, dupFlags := range allOpenFlags {
- t.Logf("Original flags: %v, dup flags: %v", flags, dupFlags)
- _, dup, err := l.Walk([]string{})
- if err != nil {
- t.Fatalf("%v: Walk(<empty>) failed: %v", s, err)
- }
- defer dup.Close()
- if _, _, _, err := dup.Open(dupFlags); err != nil {
- t.Fatalf("%v: Open(%v) failed: %v", s, flags, err)
- }
- if err := testReadWrite(dup, dupFlags, want); err != nil {
- t.Fatalf("%v: testReadWrite(%v) failed: %v", s, dupFlags, err)
- }
- }
- }
- })
-}
-
-func TestUnopened(t *testing.T) {
- runCustom(t, []fileType{regular}, allConfs, func(t *testing.T, s state) {
- b := []byte("foobar")
- if _, err := s.file.WriteAt(b, 0); err != syscall.EBADF {
- t.Errorf("%v: WriteAt() should have failed, got: %v, expected: syscall.EBADF", s, err)
- }
- if _, err := s.file.ReadAt(b, 0); err != syscall.EBADF {
- t.Errorf("%v: ReadAt() should have failed, got: %v, expected: syscall.EBADF", s, err)
- }
- if _, err := s.file.Readdir(0, 100); err != syscall.EBADF {
- t.Errorf("%v: Readdir() should have failed, got: %v, expected: syscall.EBADF", s, err)
- }
- if err := s.file.FSync(); err != syscall.EBADF {
- t.Errorf("%v: FSync() should have failed, got: %v, expected: syscall.EBADF", s, err)
- }
- })
-}
-
-func SetGetAttr(l *localFile, valid p9.SetAttrMask, attr p9.SetAttr) (p9.Attr, error) {
- if err := l.SetAttr(valid, attr); err != nil {
- return p9.Attr{}, err
- }
- _, _, a, err := l.GetAttr(p9.AttrMask{})
- if err != nil {
- return p9.Attr{}, err
- }
- return a, nil
-}
-
-func TestSetAttrPerm(t *testing.T) {
- runCustom(t, allTypes, rwConfs, func(t *testing.T, s state) {
- valid := p9.SetAttrMask{Permissions: true}
- attr := p9.SetAttr{Permissions: 0777}
- got, err := SetGetAttr(s.file, valid, attr)
- if s.ft == symlink {
- if err == nil {
- t.Fatalf("%v: SetGetAttr(valid, %v) should have failed", s, attr.Permissions)
- }
- } else {
- if err != nil {
- t.Fatalf("%v: SetGetAttr(valid, %v) failed, err: %v", s, attr.Permissions, err)
- }
- if got.Mode.Permissions() != attr.Permissions {
- t.Errorf("%v: wrong permission, got: %v, expected: %v", s, got.Mode.Permissions(), attr.Permissions)
- }
- }
- })
-}
-
-func TestSetAttrSize(t *testing.T) {
- runCustom(t, allTypes, rwConfs, func(t *testing.T, s state) {
- for _, size := range []uint64{1024, 0, 1024 * 1024} {
- valid := p9.SetAttrMask{Size: true}
- attr := p9.SetAttr{Size: size}
- got, err := SetGetAttr(s.file, valid, attr)
- if s.ft == symlink || s.ft == directory {
- if err == nil {
- t.Fatalf("%v: SetGetAttr(valid, %v) should have failed", s, attr.Permissions)
- }
- // Run for one size only, they will all fail the same way.
- return
- }
- if err != nil {
- t.Fatalf("%v: SetGetAttr(valid, %v) failed, err: %v", s, attr.Size, err)
- }
- if got.Size != size {
- t.Errorf("%v: wrong size, got: %v, expected: %v", s, got.Size, size)
- }
- }
- })
-}
-
-func TestSetAttrTime(t *testing.T) {
- runCustom(t, allTypes, rwConfs, func(t *testing.T, s state) {
- valid := p9.SetAttrMask{ATime: true, ATimeNotSystemTime: true}
- attr := p9.SetAttr{ATimeSeconds: 123, ATimeNanoSeconds: 456}
- got, err := SetGetAttr(s.file, valid, attr)
- if err != nil {
- t.Fatalf("%v: SetGetAttr(valid, %v:%v) failed, err: %v", s, attr.ATimeSeconds, attr.ATimeNanoSeconds, err)
- }
- if got.ATimeSeconds != 123 {
- t.Errorf("%v: wrong ATimeSeconds, got: %v, expected: %v", s, got.ATimeSeconds, 123)
- }
- if got.ATimeNanoSeconds != 456 {
- t.Errorf("%v: wrong ATimeNanoSeconds, got: %v, expected: %v", s, got.ATimeNanoSeconds, 456)
- }
-
- valid = p9.SetAttrMask{MTime: true, MTimeNotSystemTime: true}
- attr = p9.SetAttr{MTimeSeconds: 789, MTimeNanoSeconds: 012}
- got, err = SetGetAttr(s.file, valid, attr)
- if err != nil {
- t.Fatalf("%v: SetGetAttr(valid, %v:%v) failed, err: %v", s, attr.MTimeSeconds, attr.MTimeNanoSeconds, err)
- }
- if got.MTimeSeconds != 789 {
- t.Errorf("%v: wrong MTimeSeconds, got: %v, expected: %v", s, got.MTimeSeconds, 789)
- }
- if got.MTimeNanoSeconds != 012 {
- t.Errorf("%v: wrong MTimeNanoSeconds, got: %v, expected: %v", s, got.MTimeNanoSeconds, 012)
- }
- })
-}
-
-func TestSetAttrOwner(t *testing.T) {
- if os.Getuid() != 0 {
- t.Skipf("SetAttr(owner) test requires CAP_CHOWN, running as %d", os.Getuid())
- }
-
- runCustom(t, allTypes, rwConfs, func(t *testing.T, s state) {
- newUID := os.Getuid() + 1
- valid := p9.SetAttrMask{UID: true}
- attr := p9.SetAttr{UID: p9.UID(newUID)}
- got, err := SetGetAttr(s.file, valid, attr)
- if err != nil {
- t.Fatalf("%v: SetGetAttr(valid, %v) failed, err: %v", s, attr.UID, err)
- }
- if got.UID != p9.UID(newUID) {
- t.Errorf("%v: wrong uid, got: %v, expected: %v", s, got.UID, newUID)
- }
- })
-}
-
-func TestLink(t *testing.T) {
- if os.Getuid() != 0 {
- t.Skipf("Link test requires CAP_DAC_READ_SEARCH, running as %d", os.Getuid())
- }
- runCustom(t, allTypes, rwConfs, func(t *testing.T, s state) {
- const dirName = "linkdir"
- const linkFile = "link"
- if _, err := s.root.Mkdir(dirName, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != nil {
- t.Fatalf("%v: MkDir(%s) failed, err: %v", s, dirName, err)
- }
- _, dir, err := s.root.Walk([]string{dirName})
- if err != nil {
- t.Fatalf("%v: Walk({%s}) failed, err: %v", s, dirName, err)
- }
-
- err = dir.Link(s.file, linkFile)
- if s.ft == directory {
- if err != syscall.EPERM {
- t.Errorf("%v: Link(target, %s) should have failed, got: %v, expected: syscall.EPERM", s, linkFile, err)
- }
- return
- }
- if err != nil {
- t.Errorf("%v: Link(target, %s) failed, err: %v", s, linkFile, err)
- }
- })
-}
-
-func TestROMountChecks(t *testing.T) {
- runCustom(t, allTypes, roConfs, func(t *testing.T, s state) {
- if _, _, _, _, err := s.file.Create("some_file", p9.ReadWrite, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != syscall.EBADF {
- t.Errorf("%v: Create() should have failed, got: %v, expected: syscall.EBADF", s, err)
- }
- if _, err := s.file.Mkdir("some_dir", 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != syscall.EBADF {
- t.Errorf("%v: MkDir() should have failed, got: %v, expected: syscall.EBADF", s, err)
- }
- if err := s.file.RenameAt("some_file", s.file, "other_file"); err != syscall.EBADF {
- t.Errorf("%v: Rename() should have failed, got: %v, expected: syscall.EBADF", s, err)
- }
- if _, err := s.file.Symlink("some_place", "some_symlink", p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != syscall.EBADF {
- t.Errorf("%v: Symlink() should have failed, got: %v, expected: syscall.EBADF", s, err)
- }
- if err := s.file.UnlinkAt("some_file", 0); err != syscall.EBADF {
- t.Errorf("%v: UnlinkAt() should have failed, got: %v, expected: syscall.EBADF", s, err)
- }
- if err := s.file.Link(s.file, "some_link"); err != syscall.EBADF {
- t.Errorf("%v: Link() should have failed, got: %v, expected: syscall.EBADF", s, err)
- }
-
- valid := p9.SetAttrMask{Size: true}
- attr := p9.SetAttr{Size: 0}
- if err := s.file.SetAttr(valid, attr); err != syscall.EBADF {
- t.Errorf("%v: SetAttr() should have failed, got: %v, expected: syscall.EBADF", s, err)
- }
- })
-}
-
-func TestROMountPanics(t *testing.T) {
- conf := Config{ROMount: true, PanicOnWrite: true}
- runCustom(t, allTypes, []Config{conf}, func(t *testing.T, s state) {
- assertPanic(t, func() { s.file.Create("some_file", p9.ReadWrite, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())) })
- assertPanic(t, func() { s.file.Mkdir("some_dir", 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())) })
- assertPanic(t, func() { s.file.RenameAt("some_file", s.file, "other_file") })
- assertPanic(t, func() { s.file.Symlink("some_place", "some_symlink", p9.UID(os.Getuid()), p9.GID(os.Getgid())) })
- assertPanic(t, func() { s.file.UnlinkAt("some_file", 0) })
- assertPanic(t, func() { s.file.Link(s.file, "some_link") })
-
- valid := p9.SetAttrMask{Size: true}
- attr := p9.SetAttr{Size: 0}
- assertPanic(t, func() { s.file.SetAttr(valid, attr) })
- })
-}
-
-func TestWalkNotFound(t *testing.T) {
- runCustom(t, []fileType{directory}, allConfs, func(t *testing.T, s state) {
- if _, _, err := s.file.Walk([]string{"nobody-here"}); err != syscall.ENOENT {
- t.Errorf("%v: Walk(%q) should have failed, got: %v, expected: syscall.ENOENT", s, "nobody-here", err)
- }
- })
-}
-
-func TestWalkDup(t *testing.T) {
- runAll(t, func(t *testing.T, s state) {
- _, dup, err := s.file.Walk([]string{})
- if err != nil {
- t.Fatalf("%v: Walk(nil) failed, err: %v", s, err)
- }
- // Check that 'dup' is usable.
- if _, _, _, err := dup.GetAttr(p9.AttrMask{}); err != nil {
- t.Errorf("%v: GetAttr() failed, err: %v", s, err)
- }
- })
-}
-
-func TestReaddir(t *testing.T) {
- runCustom(t, []fileType{directory}, rwConfs, func(t *testing.T, s state) {
- name := "dir"
- if _, err := s.file.Mkdir(name, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != nil {
- t.Fatalf("%v: MkDir(%s) failed, err: %v", s, name, err)
- }
- name = "symlink"
- if _, err := s.file.Symlink("/some/target", name, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != nil {
- t.Fatalf("%v: Symlink(%q) failed, err: %v", s, name, err)
- }
- name = "file"
- _, f, _, _, err := s.file.Create(name, p9.ReadWrite, 0555, p9.UID(os.Getuid()), p9.GID(os.Getgid()))
- if err != nil {
- t.Fatalf("%v: createFile(root, %q) failed, err: %v", s, name, err)
- }
- f.Close()
-
- if _, _, _, err := s.file.Open(p9.ReadOnly); err != nil {
- t.Fatalf("%v: Open(ReadOnly) failed, err: %v", s, err)
- }
-
- dirents, err := s.file.Readdir(0, 10)
- if err != nil {
- t.Fatalf("%v: Readdir(0, 10) failed, err: %v", s, err)
- }
- if len(dirents) != 3 {
- t.Fatalf("%v: Readdir(0, 10) wrong number of items, got: %v, expected: 3", s, len(dirents))
- }
- var dir, symlink, file bool
- for _, d := range dirents {
- switch d.Name {
- case "dir":
- if d.Type != p9.TypeDir {
- t.Errorf("%v: dirent.Type got: %v, expected: %v", s, d.Type, p9.TypeDir)
- }
- dir = true
- case "symlink":
- if d.Type != p9.TypeSymlink {
- t.Errorf("%v: dirent.Type got: %v, expected: %v", s, d.Type, p9.TypeSymlink)
- }
- symlink = true
- case "file":
- if d.Type != p9.TypeRegular {
- t.Errorf("%v: dirent.Type got: %v, expected: %v", s, d.Type, p9.TypeRegular)
- }
- file = true
- default:
- t.Errorf("%v: dirent.Name got: %v", s, d.Name)
- }
-
- _, f, err := s.file.Walk([]string{d.Name})
- if err != nil {
- t.Fatalf("%v: Walk({%s}) failed, err: %v", s, d.Name, err)
- }
- _, _, a, err := f.GetAttr(p9.AttrMask{})
- if err != nil {
- t.Fatalf("%v: GetAttr() failed, err: %v", s, err)
- }
- if d.Type != a.Mode.QIDType() {
- t.Errorf("%v: dirent.Type different than GetAttr().Mode.QIDType(), got: %v, expected: %v", s, d.Type, a.Mode.QIDType())
- }
- }
- if !dir || !symlink || !file {
- t.Errorf("%v: Readdir(0, 10) wrong files returned, dir: %v, symlink: %v, file: %v", s, dir, symlink, file)
- }
- })
-}
-
-// Test that attach point can be written to when it points to a file, e.g.
-// /etc/hosts.
-func TestAttachFile(t *testing.T) {
- conf := Config{ROMount: false}
- dir, err := ioutil.TempDir("", "root-")
- if err != nil {
- t.Fatalf("ioutil.TempDir() failed, err: %v", err)
- }
- defer os.RemoveAll(dir)
-
- path := path.Join(dir, "test")
- if _, err := os.Create(path); err != nil {
- t.Fatalf("os.Create(%q) failed, err: %v", path, err)
- }
-
- a, err := NewAttachPoint(path, conf)
- if err != nil {
- t.Fatalf("NewAttachPoint failed: %v", err)
- }
- root, err := a.Attach()
- if err != nil {
- t.Fatalf("Attach failed, err: %v", err)
- }
-
- if _, _, _, err := root.Open(p9.ReadWrite); err != nil {
- t.Fatalf("Open(ReadWrite) failed, err: %v", err)
- }
- defer root.Close()
-
- b := []byte("foobar")
- w, err := root.WriteAt(b, 0)
- if err != nil {
- t.Fatalf("Write() failed, err: %v", err)
- }
- if w != len(b) {
- t.Fatalf("Write() was partial, got: %d, expected: %d", w, len(b))
- }
- rBuf := make([]byte, len(b))
- r, err := root.ReadAt(rBuf, 0)
- if err != nil {
- t.Fatalf("ReadAt() failed, err: %v", err)
- }
- if r != len(rBuf) {
- t.Fatalf("ReadAt() was partial, got: %d, expected: %d", r, len(rBuf))
- }
- if string(rBuf) != "foobar" {
- t.Fatalf("ReadAt() wrong data, got: %s, expected: %s", string(rBuf), "foobar")
- }
-}
-
-func TestAttachInvalidType(t *testing.T) {
- dir, err := ioutil.TempDir("", "attach-")
- if err != nil {
- t.Fatalf("ioutil.TempDir() failed, err: %v", err)
- }
- defer os.RemoveAll(dir)
-
- fifo := filepath.Join(dir, "fifo")
- if err := syscall.Mkfifo(fifo, 0755); err != nil {
- t.Fatalf("Mkfifo(%q): %v", fifo, err)
- }
-
- dirFile, err := os.Open(dir)
- if err != nil {
- t.Fatalf("Open(%s): %v", dir, err)
- }
- defer dirFile.Close()
-
- // Bind a socket via /proc to be sure that a length of a socket path
- // is less than UNIX_PATH_MAX.
- socket := filepath.Join(fmt.Sprintf("/proc/self/fd/%d", dirFile.Fd()), "socket")
- l, err := net.Listen("unix", socket)
- if err != nil {
- t.Fatalf("net.Listen(unix, %q): %v", socket, err)
- }
- defer l.Close()
-
- for _, tc := range []struct {
- name string
- path string
- }{
- {name: "fifo", path: fifo},
- {name: "socket", path: socket},
- } {
- t.Run(tc.name, func(t *testing.T) {
- conf := Config{ROMount: false}
- a, err := NewAttachPoint(tc.path, conf)
- if err != nil {
- t.Fatalf("NewAttachPoint failed: %v", err)
- }
- f, err := a.Attach()
- if f != nil || err == nil {
- t.Fatalf("Attach should have failed, got (%v, nil)", f)
- }
- })
- }
-}
-
-func TestDoubleAttachError(t *testing.T) {
- conf := Config{ROMount: false}
- root, err := ioutil.TempDir("", "root-")
- if err != nil {
- t.Fatalf("ioutil.TempDir() failed, err: %v", err)
- }
- defer os.RemoveAll(root)
- a, err := NewAttachPoint(root, conf)
- if err != nil {
- t.Fatalf("NewAttachPoint failed: %v", err)
- }
-
- if _, err := a.Attach(); err != nil {
- t.Fatalf("Attach failed: %v", err)
- }
- if _, err := a.Attach(); err == nil {
- t.Fatalf("Attach should have failed, got %v want non-nil", err)
- }
-}
diff --git a/runsc/sandbox/BUILD b/runsc/sandbox/BUILD
deleted file mode 100644
index 7fdceaab6..000000000
--- a/runsc/sandbox/BUILD
+++ /dev/null
@@ -1,34 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "sandbox",
- srcs = [
- "network.go",
- "network_unsafe.go",
- "sandbox.go",
- ],
- importpath = "gvisor.dev/gvisor/runsc/sandbox",
- visibility = [
- "//runsc:__subpackages__",
- ],
- deps = [
- "//pkg/control/client",
- "//pkg/control/server",
- "//pkg/log",
- "//pkg/sentry/control",
- "//pkg/sentry/platform",
- "//pkg/urpc",
- "//runsc/boot",
- "//runsc/boot/platforms",
- "//runsc/cgroup",
- "//runsc/console",
- "//runsc/specutils",
- "@com_github_cenkalti_backoff//:go_default_library",
- "@com_github_opencontainers_runtime-spec//specs-go:go_default_library",
- "@com_github_syndtr_gocapability//capability:go_default_library",
- "@com_github_vishvananda_netlink//:go_default_library",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
diff --git a/runsc/specutils/BUILD b/runsc/specutils/BUILD
deleted file mode 100644
index fbfb8e2f8..000000000
--- a/runsc/specutils/BUILD
+++ /dev/null
@@ -1,31 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "specutils",
- srcs = [
- "fs.go",
- "namespace.go",
- "specutils.go",
- ],
- importpath = "gvisor.dev/gvisor/runsc/specutils",
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/log",
- "//pkg/sentry/kernel/auth",
- "@com_github_cenkalti_backoff//:go_default_library",
- "@com_github_opencontainers_runtime-spec//specs-go:go_default_library",
- "@com_github_syndtr_gocapability//capability:go_default_library",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-go_test(
- name = "specutils_test",
- size = "small",
- srcs = ["specutils_test.go"],
- embed = [":specutils"],
- deps = ["@com_github_opencontainers_runtime-spec//specs-go:go_default_library"],
-)
diff --git a/runsc/specutils/specutils_test.go b/runsc/specutils/specutils_test.go
deleted file mode 100644
index 2c86fffe8..000000000
--- a/runsc/specutils/specutils_test.go
+++ /dev/null
@@ -1,265 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package specutils
-
-import (
- "fmt"
- "os/exec"
- "strings"
- "testing"
- "time"
-
- specs "github.com/opencontainers/runtime-spec/specs-go"
-)
-
-func TestWaitForReadyHappy(t *testing.T) {
- cmd := exec.Command("/bin/sleep", "1000")
- if err := cmd.Start(); err != nil {
- t.Fatalf("cmd.Start() failed, err: %v", err)
- }
- defer cmd.Wait()
-
- var count int
- err := WaitForReady(cmd.Process.Pid, 5*time.Second, func() (bool, error) {
- if count < 3 {
- count++
- return false, nil
- }
- return true, nil
- })
- if err != nil {
- t.Errorf("ProcessWaitReady got: %v, expected: nil", err)
- }
- cmd.Process.Kill()
-}
-
-func TestWaitForReadyFail(t *testing.T) {
- cmd := exec.Command("/bin/sleep", "1000")
- if err := cmd.Start(); err != nil {
- t.Fatalf("cmd.Start() failed, err: %v", err)
- }
- defer cmd.Wait()
-
- var count int
- err := WaitForReady(cmd.Process.Pid, 5*time.Second, func() (bool, error) {
- if count < 3 {
- count++
- return false, nil
- }
- return false, fmt.Errorf("Fake error")
- })
- if err == nil {
- t.Errorf("ProcessWaitReady got: nil, expected: error")
- }
- cmd.Process.Kill()
-}
-
-func TestWaitForReadyNotRunning(t *testing.T) {
- cmd := exec.Command("/bin/true")
- if err := cmd.Start(); err != nil {
- t.Fatalf("cmd.Start() failed, err: %v", err)
- }
- defer cmd.Wait()
-
- err := WaitForReady(cmd.Process.Pid, 5*time.Second, func() (bool, error) {
- return false, nil
- })
- if err != nil && !strings.Contains(err.Error(), "terminated") {
- t.Errorf("ProcessWaitReady got: %v, expected: process terminated", err)
- }
- if err == nil {
- t.Errorf("ProcessWaitReady incorrectly succeeded")
- }
-}
-
-func TestWaitForReadyTimeout(t *testing.T) {
- cmd := exec.Command("/bin/sleep", "1000")
- if err := cmd.Start(); err != nil {
- t.Fatalf("cmd.Start() failed, err: %v", err)
- }
- defer cmd.Wait()
-
- err := WaitForReady(cmd.Process.Pid, 50*time.Millisecond, func() (bool, error) {
- return false, nil
- })
- if !strings.Contains(err.Error(), "not running yet") {
- t.Errorf("ProcessWaitReady got: %v, expected: not running yet", err)
- }
- cmd.Process.Kill()
-}
-
-func TestSpecInvalid(t *testing.T) {
- for _, test := range []struct {
- name string
- spec specs.Spec
- error string
- }{
- {
- name: "valid",
- spec: specs.Spec{
- Root: &specs.Root{Path: "/"},
- Process: &specs.Process{
- Args: []string{"/bin/true"},
- },
- Mounts: []specs.Mount{
- {
- Source: "src",
- Destination: "/dst",
- },
- },
- },
- error: "",
- },
- {
- name: "valid+warning",
- spec: specs.Spec{
- Root: &specs.Root{Path: "/"},
- Process: &specs.Process{
- Args: []string{"/bin/true"},
- // This is normally set by docker and will just cause warnings to be logged.
- ApparmorProfile: "someprofile",
- },
- // This is normally set by docker and will just cause warnings to be logged.
- Linux: &specs.Linux{Seccomp: &specs.LinuxSeccomp{}},
- },
- error: "",
- },
- {
- name: "no root",
- spec: specs.Spec{
- Process: &specs.Process{
- Args: []string{"/bin/true"},
- },
- },
- error: "must be defined",
- },
- {
- name: "empty root",
- spec: specs.Spec{
- Root: &specs.Root{},
- Process: &specs.Process{
- Args: []string{"/bin/true"},
- },
- },
- error: "must be defined",
- },
- {
- name: "no process",
- spec: specs.Spec{
- Root: &specs.Root{Path: "/"},
- },
- error: "must be defined",
- },
- {
- name: "empty args",
- spec: specs.Spec{
- Root: &specs.Root{Path: "/"},
- Process: &specs.Process{},
- },
- error: "must be defined",
- },
- {
- name: "selinux",
- spec: specs.Spec{
- Root: &specs.Root{Path: "/"},
- Process: &specs.Process{
- Args: []string{"/bin/true"},
- SelinuxLabel: "somelabel",
- },
- },
- error: "is not supported",
- },
- {
- name: "solaris",
- spec: specs.Spec{
- Root: &specs.Root{Path: "/"},
- Process: &specs.Process{
- Args: []string{"/bin/true"},
- },
- Solaris: &specs.Solaris{},
- },
- error: "is not supported",
- },
- {
- name: "windows",
- spec: specs.Spec{
- Root: &specs.Root{Path: "/"},
- Process: &specs.Process{
- Args: []string{"/bin/true"},
- },
- Windows: &specs.Windows{},
- },
- error: "is not supported",
- },
- {
- name: "relative mount destination",
- spec: specs.Spec{
- Root: &specs.Root{Path: "/"},
- Process: &specs.Process{
- Args: []string{"/bin/true"},
- },
- Mounts: []specs.Mount{
- {
- Source: "src",
- Destination: "dst",
- },
- },
- },
- error: "must be an absolute path",
- },
- {
- name: "invalid mount option",
- spec: specs.Spec{
- Root: &specs.Root{Path: "/"},
- Process: &specs.Process{
- Args: []string{"/bin/true"},
- },
- Mounts: []specs.Mount{
- {
- Source: "/src",
- Destination: "/dst",
- Type: "bind",
- Options: []string{"shared"},
- },
- },
- },
- error: "is not supported",
- },
- {
- name: "invalid rootfs propagation",
- spec: specs.Spec{
- Root: &specs.Root{Path: "/"},
- Process: &specs.Process{
- Args: []string{"/bin/true"},
- },
- Linux: &specs.Linux{
- RootfsPropagation: "foo",
- },
- },
- error: "root mount propagation option must specify private or slave",
- },
- } {
- err := ValidateSpec(&test.spec)
- if len(test.error) == 0 {
- if err != nil {
- t.Errorf("ValidateSpec(%q) failed, err: %v", test.name, err)
- }
- } else {
- if err == nil || !strings.Contains(err.Error(), test.error) {
- t.Errorf("ValidateSpec(%q) wrong error, got: %v, want: .*%s.*", test.name, err, test.error)
- }
- }
- }
-}
diff --git a/runsc/testutil/BUILD b/runsc/testutil/BUILD
deleted file mode 100644
index d44ebc906..000000000
--- a/runsc/testutil/BUILD
+++ /dev/null
@@ -1,17 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "testutil",
- testonly = 1,
- srcs = ["testutil.go"],
- importpath = "gvisor.dev/gvisor/runsc/testutil",
- visibility = ["//:sandbox"],
- deps = [
- "//runsc/boot",
- "//runsc/specutils",
- "@com_github_cenkalti_backoff//:go_default_library",
- "@com_github_opencontainers_runtime-spec//specs-go:go_default_library",
- ],
-)
diff --git a/runsc/testutil/testutil.go b/runsc/testutil/testutil.go
deleted file mode 100644
index 57ab73d97..000000000
--- a/runsc/testutil/testutil.go
+++ /dev/null
@@ -1,440 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package testutil contains utility functions for runsc tests.
-package testutil
-
-import (
- "bufio"
- "context"
- "debug/elf"
- "encoding/base32"
- "encoding/json"
- "flag"
- "fmt"
- "io"
- "io/ioutil"
- "log"
- "math/rand"
- "net/http"
- "os"
- "os/exec"
- "os/signal"
- "path/filepath"
- "strings"
- "sync"
- "sync/atomic"
- "syscall"
- "time"
-
- "github.com/cenkalti/backoff"
- specs "github.com/opencontainers/runtime-spec/specs-go"
- "gvisor.dev/gvisor/runsc/boot"
- "gvisor.dev/gvisor/runsc/specutils"
-)
-
-var (
- checkpoint = flag.Bool("checkpoint", true, "control checkpoint/restore support")
-)
-
-func init() {
- rand.Seed(time.Now().UnixNano())
-}
-
-// IsCheckpointSupported returns the relevant command line flag.
-func IsCheckpointSupported() bool {
- return *checkpoint
-}
-
-// TmpDir returns the absolute path to a writable directory that can be used as
-// scratch by the test.
-func TmpDir() string {
- dir := os.Getenv("TEST_TMPDIR")
- if dir == "" {
- dir = "/tmp"
- }
- return dir
-}
-
-// ConfigureExePath configures the executable for runsc in the test environment.
-func ConfigureExePath() error {
- path, err := FindFile("runsc/runsc")
- if err != nil {
- return err
- }
- specutils.ExePath = path
- return nil
-}
-
-// FindFile searchs for a file inside the test run environment. It returns the
-// full path to the file. It fails if none or more than one file is found.
-func FindFile(path string) (string, error) {
- wd, err := os.Getwd()
- if err != nil {
- return "", err
- }
-
- // The test root is demarcated by a path element called "__main__". Search for
- // it backwards from the working directory.
- root := wd
- for {
- dir, name := filepath.Split(root)
- if name == "__main__" {
- break
- }
- if len(dir) == 0 {
- return "", fmt.Errorf("directory __main__ not found in %q", wd)
- }
- // Remove ending slash to loop around.
- root = dir[:len(dir)-1]
- }
-
- // Annoyingly, bazel adds the build type to the directory path for go
- // binaries, but not for c++ binaries. We use two different patterns to
- // to find our file.
- patterns := []string{
- // Try the obvious path first.
- filepath.Join(root, path),
- // If it was a go binary, use a wildcard to match the build
- // type. The pattern is: /test-path/__main__/directories/*/file.
- filepath.Join(root, filepath.Dir(path), "*", filepath.Base(path)),
- }
-
- for _, p := range patterns {
- matches, err := filepath.Glob(p)
- if err != nil {
- // "The only possible returned error is ErrBadPattern,
- // when pattern is malformed." -godoc
- return "", fmt.Errorf("error globbing %q: %v", p, err)
- }
- switch len(matches) {
- case 0:
- // Try the next pattern.
- case 1:
- // We found it.
- return matches[0], nil
- default:
- return "", fmt.Errorf("more than one match found for %q: %s", path, matches)
- }
- }
- return "", fmt.Errorf("file %q not found", path)
-}
-
-// TestConfig returns the default configuration to use in tests. Note that
-// 'RootDir' must be set by caller if required.
-func TestConfig() *boot.Config {
- return &boot.Config{
- Debug: true,
- LogFormat: "text",
- DebugLogFormat: "text",
- AlsoLogToStderr: true,
- LogPackets: true,
- Network: boot.NetworkNone,
- Strace: true,
- Platform: "ptrace",
- FileAccess: boot.FileAccessExclusive,
- TestOnlyAllowRunAsCurrentUserWithoutChroot: true,
- NumNetworkChannels: 1,
- }
-}
-
-// TestConfigWithRoot returns the default configuration to use in tests.
-func TestConfigWithRoot(rootDir string) *boot.Config {
- conf := TestConfig()
- conf.RootDir = rootDir
- return conf
-}
-
-// NewSpecWithArgs creates a simple spec with the given args suitable for use
-// in tests.
-func NewSpecWithArgs(args ...string) *specs.Spec {
- return &specs.Spec{
- // The host filesystem root is the container root.
- Root: &specs.Root{
- Path: "/",
- Readonly: true,
- },
- Process: &specs.Process{
- Args: args,
- Env: []string{
- "PATH=" + os.Getenv("PATH"),
- },
- Capabilities: specutils.AllCapabilities(),
- },
- Mounts: []specs.Mount{
- // Root is readonly, but many tests want to write to tmpdir.
- // This creates a writable mount inside the root. Also, when tmpdir points
- // to "/tmp", it makes the the actual /tmp to be mounted and not a tmpfs
- // inside the sentry.
- {
- Type: "bind",
- Destination: TmpDir(),
- Source: TmpDir(),
- },
- },
- Hostname: "runsc-test-hostname",
- }
-}
-
-// SetupRootDir creates a root directory for containers.
-func SetupRootDir() (string, error) {
- rootDir, err := ioutil.TempDir(TmpDir(), "containers")
- if err != nil {
- return "", fmt.Errorf("error creating root dir: %v", err)
- }
- return rootDir, nil
-}
-
-// SetupContainer creates a bundle and root dir for the container, generates a
-// test config, and writes the spec to config.json in the bundle dir.
-func SetupContainer(spec *specs.Spec, conf *boot.Config) (rootDir, bundleDir string, err error) {
- rootDir, err = SetupRootDir()
- if err != nil {
- return "", "", err
- }
- conf.RootDir = rootDir
- bundleDir, err = SetupBundleDir(spec)
- return rootDir, bundleDir, err
-}
-
-// SetupBundleDir creates a bundle dir and writes the spec to config.json.
-func SetupBundleDir(spec *specs.Spec) (bundleDir string, err error) {
- bundleDir, err = ioutil.TempDir(TmpDir(), "bundle")
- if err != nil {
- return "", fmt.Errorf("error creating bundle dir: %v", err)
- }
-
- if err = writeSpec(bundleDir, spec); err != nil {
- return "", fmt.Errorf("error writing spec: %v", err)
- }
- return bundleDir, nil
-}
-
-// writeSpec writes the spec to disk in the given directory.
-func writeSpec(dir string, spec *specs.Spec) error {
- b, err := json.Marshal(spec)
- if err != nil {
- return err
- }
- return ioutil.WriteFile(filepath.Join(dir, "config.json"), b, 0755)
-}
-
-// UniqueContainerID generates a unique container id for each test.
-//
-// The container id is used to create an abstract unix domain socket, which must
-// be unique. While the container forbids creating two containers with the same
-// name, sometimes between test runs the socket does not get cleaned up quickly
-// enough, causing container creation to fail.
-func UniqueContainerID() string {
- // Read 20 random bytes.
- b := make([]byte, 20)
- // "[Read] always returns len(p) and a nil error." --godoc
- if _, err := rand.Read(b); err != nil {
- panic("rand.Read failed: " + err.Error())
- }
- // base32 encode the random bytes, so that the name is a valid
- // container id and can be used as a socket name in the filesystem.
- return fmt.Sprintf("test-container-%s", base32.StdEncoding.EncodeToString(b))
-}
-
-// Copy copies file from src to dst.
-func Copy(src, dst string) error {
- in, err := os.Open(src)
- if err != nil {
- return err
- }
- defer in.Close()
-
- out, err := os.Create(dst)
- if err != nil {
- return err
- }
- defer out.Close()
-
- _, err = io.Copy(out, in)
- return err
-}
-
-// Poll is a shorthand function to poll for something with given timeout.
-func Poll(cb func() error, timeout time.Duration) error {
- ctx, cancel := context.WithTimeout(context.Background(), timeout)
- defer cancel()
- b := backoff.WithContext(backoff.NewConstantBackOff(100*time.Millisecond), ctx)
- return backoff.Retry(cb, b)
-}
-
-// WaitForHTTP tries GET requests on a port until the call succeeds or timeout.
-func WaitForHTTP(port int, timeout time.Duration) error {
- cb := func() error {
- c := &http.Client{
- // Calculate timeout to be able to do minimum 5 attempts.
- Timeout: timeout / 5,
- }
- url := fmt.Sprintf("http://localhost:%d/", port)
- resp, err := c.Get(url)
- if err != nil {
- log.Printf("Waiting %s: %v", url, err)
- return err
- }
- resp.Body.Close()
- return nil
- }
- return Poll(cb, timeout)
-}
-
-// Reaper reaps child processes.
-type Reaper struct {
- // mu protects ch, which will be nil if the reaper is not running.
- mu sync.Mutex
- ch chan os.Signal
-}
-
-// Start starts reaping child processes.
-func (r *Reaper) Start() {
- r.mu.Lock()
- defer r.mu.Unlock()
-
- if r.ch != nil {
- panic("reaper.Start called on a running reaper")
- }
-
- r.ch = make(chan os.Signal, 1)
- signal.Notify(r.ch, syscall.SIGCHLD)
-
- go func() {
- for {
- r.mu.Lock()
- ch := r.ch
- r.mu.Unlock()
- if ch == nil {
- return
- }
-
- _, ok := <-ch
- if !ok {
- // Channel closed.
- return
- }
- for {
- cpid, _ := syscall.Wait4(-1, nil, syscall.WNOHANG, nil)
- if cpid < 1 {
- break
- }
- }
- }
- }()
-}
-
-// Stop stops reaping child processes.
-func (r *Reaper) Stop() {
- r.mu.Lock()
- defer r.mu.Unlock()
-
- if r.ch == nil {
- panic("reaper.Stop called on a stopped reaper")
- }
-
- signal.Stop(r.ch)
- close(r.ch)
- r.ch = nil
-}
-
-// StartReaper is a helper that starts a new Reaper and returns a function to
-// stop it.
-func StartReaper() func() {
- r := &Reaper{}
- r.Start()
- return r.Stop
-}
-
-// WaitUntilRead reads from the given reader until the wanted string is found
-// or until timeout.
-func WaitUntilRead(r io.Reader, want string, split bufio.SplitFunc, timeout time.Duration) error {
- sc := bufio.NewScanner(r)
- if split != nil {
- sc.Split(split)
- }
- // done must be accessed atomically. A value greater than 0 indicates
- // that the read loop can exit.
- var done uint32
- doneCh := make(chan struct{})
- go func() {
- for sc.Scan() {
- t := sc.Text()
- if strings.Contains(t, want) {
- atomic.StoreUint32(&done, 1)
- close(doneCh)
- break
- }
- if atomic.LoadUint32(&done) > 0 {
- break
- }
- }
- }()
- select {
- case <-time.After(timeout):
- atomic.StoreUint32(&done, 1)
- return fmt.Errorf("timeout waiting to read %q", want)
- case <-doneCh:
- return nil
- }
-}
-
-// KillCommand kills the process running cmd unless it hasn't been started. It
-// returns an error if it cannot kill the process unless the reason is that the
-// process has already exited.
-func KillCommand(cmd *exec.Cmd) error {
- if cmd.Process == nil {
- return nil
- }
- if err := cmd.Process.Kill(); err != nil {
- if !strings.Contains(err.Error(), "process already finished") {
- return fmt.Errorf("failed to kill process %v: %v", cmd, err)
- }
- }
- return nil
-}
-
-// WriteTmpFile writes text to a temporary file, closes the file, and returns
-// the name of the file.
-func WriteTmpFile(pattern, text string) (string, error) {
- file, err := ioutil.TempFile(TmpDir(), pattern)
- if err != nil {
- return "", err
- }
- defer file.Close()
- if _, err := file.Write([]byte(text)); err != nil {
- return "", err
- }
- return file.Name(), nil
-}
-
-// RandomName create a name with a 6 digit random number appended to it.
-func RandomName(prefix string) string {
- return fmt.Sprintf("%s-%06d", prefix, rand.Int31n(1000000))
-}
-
-// IsStatic returns true iff the given file is a static binary.
-func IsStatic(filename string) (bool, error) {
- f, err := elf.Open(filename)
- if err != nil {
- return false, err
- }
- for _, prog := range f.Progs {
- if prog.Type == elf.PT_INTERP {
- return false, nil // Has interpreter.
- }
- }
- return true, nil
-}
diff --git a/runsc/version_test.sh b/runsc/version_test.sh
deleted file mode 100755
index cc0ca3f05..000000000
--- a/runsc/version_test.sh
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/bin/bash
-
-# Copyright 2018 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -euf -x -o pipefail
-
-readonly runsc="${TEST_SRCDIR}/__main__/runsc/linux_amd64_pure_stripped/runsc"
-readonly version=$($runsc --version)
-
-# Version should should not match VERSION, which is the default and which will
-# also appear if something is wrong with workspace_status.sh script.
-if [[ $version =~ "VERSION" ]]; then
- echo "FAIL: Got bad version $version"
- exit 1
-fi
-
-# Version should contain at least one number.
-if [[ ! $version =~ [0-9] ]]; then
- echo "FAIL: Got bad version $version"
- exit 1
-fi
-
-echo "PASS: Got OK version $version"
-exit 0
diff --git a/scripts/build.sh b/scripts/build.sh
deleted file mode 100755
index d73eaee77..000000000
--- a/scripts/build.sh
+++ /dev/null
@@ -1,79 +0,0 @@
-#!/bin/bash
-
-# Copyright 2018 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-source $(dirname $0)/common.sh
-
-# Install required packages for make_repository.sh et al.
-sudo apt-get update && sudo apt-get install -y dpkg-sig coreutils apt-utils
-
-# Build runsc.
-runsc=$(build -c opt //runsc)
-
-# Build packages.
-pkg=$(build -c opt --host_force_python=py2 //runsc:runsc-debian)
-
-# Build a repository, if the key is available.
-if [[ -v KOKORO_REPO_KEY ]]; then
- repo=$(tools/make_repository.sh "${KOKORO_KEYSTORE_DIR}/${KOKORO_REPO_KEY}" gvisor-bot@google.com ${pkg})
-fi
-
-# Install installs artifacts.
-install() {
- local -r binaries_dir="$1"
- local -r repo_dir="$2"
- mkdir -p "${binaries_dir}"
- cp -f "${runsc}" "${binaries_dir}"/runsc
- sha512sum "${binaries_dir}"/runsc | awk '{print $1 " runsc"}' > "${binaries_dir}"/runsc.sha512
- if [[ -v repo ]]; then
- rm -rf "${repo_dir}" && mkdir -p "$(dirname "${repo_dir}")"
- cp -a "${repo}" "${repo_dir}"
- fi
-}
-
-# Move the runsc binary into "latest" directory, and also a directory with the
-# current date. If the current commit happens to correpond to a tag, then we
-# will also move everything into a directory named after the given tag.
-if [[ -v KOKORO_ARTIFACTS_DIR ]]; then
- if [[ "${KOKORO_BUILD_NIGHTLY}" == "true" ]]; then
- # The "latest" directory and current date.
- stamp="$(date -Idate)"
- install "${KOKORO_ARTIFACTS_DIR}/nightly/latest" \
- "${KOKORO_ARTIFACTS_DIR}/dists/nightly/main"
- install "${KOKORO_ARTIFACTS_DIR}/nightly/${stamp}" \
- "${KOKORO_ARTIFACTS_DIR}/dists/nightly/${stamp}"
- else
- # Is it a tagged release? Build that instead. In that case, we also try to
- # update the base release directory, in case this is an update. Finally, we
- # update the "release" directory, which has the last released version.
- tags="$(git tag --points-at HEAD)"
- if ! [[ -z "${tags}" ]]; then
- # Note that a given commit can match any number of tags. We have to
- # iterate through all possible tags and produce associated artifacts.
- for tag in ${tags}; do
- name=$(echo "${tag}" | cut -d'-' -f2)
- base=$(echo "${name}" | cut -d'.' -f1)
- install "${KOKORO_ARTIFACTS_DIR}/release/${name}" \
- "${KOKORO_ARTIFACTS_DIR}/dists/${name}/main"
- if [[ "${base}" != "${tag}" ]]; then
- install "${KOKORO_ARTIFACTS_DIR}/release/${base}" \
- "${KOKORO_ARTIFACTS_DIR}/dists/${base}/main"
- fi
- install "${KOKORO_ARTIFACTS_DIR}/release/latest" \
- "${KOKORO_ARTIFACTS_DIR}/dists/latest/main"
- done
- fi
- fi
-fi
diff --git a/scripts/common.sh b/scripts/common.sh
deleted file mode 100755
index 6dabad141..000000000
--- a/scripts/common.sh
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -xeou pipefail
-
-if [[ -f $(dirname $0)/common_google.sh ]]; then
- source $(dirname $0)/common_google.sh
-else
- source $(dirname $0)/common_bazel.sh
-fi
-
-# Ensure it attempts to collect logs in all cases.
-trap collect_logs EXIT
-
-function set_runtime() {
- RUNTIME=${1:-runsc}
- RUNSC_BIN=/tmp/"${RUNTIME}"/runsc
- RUNSC_LOGS_DIR="$(dirname ${RUNSC_BIN})"/logs
- RUNSC_LOGS="${RUNSC_LOGS_DIR}"/runsc.log.%TEST%.%TIMESTAMP%.%COMMAND%
-}
-
-function test_runsc() {
- test --test_arg=--runtime=${RUNTIME} "$@"
-}
-
-function install_runsc_for_test() {
- local -r test_name=$1
- shift
- if [[ -z "${test_name}" ]]; then
- echo "Missing mandatory test name"
- exit 1
- fi
-
- # Add test to the name, so it doesn't conflict with other runtimes.
- set_runtime $(find_branch_name)_"${test_name}"
-
- # ${RUNSC_TEST_NAME} is set by tests (see dockerutil) to pass the test name
- # down to the runtime.
- install_runsc "${RUNTIME}" \
- --TESTONLY-test-name-env=RUNSC_TEST_NAME \
- --debug \
- --strace \
- --log-packets \
- "$@"
-}
-
-# Installs the runsc with given runtime name. set_runtime must have been called
-# to set runtime and logs location.
-function install_runsc() {
- local -r runtime=$1
- shift
-
- # Prepare the runtime binary.
- local -r output=$(build //runsc)
- mkdir -p "$(dirname ${RUNSC_BIN})"
- cp -f "${output}" "${RUNSC_BIN}"
- chmod 0755 "${RUNSC_BIN}"
-
- # Install the runtime.
- sudo "${RUNSC_BIN}" install --experimental=true --runtime="${runtime}" -- --debug-log "${RUNSC_LOGS}" "$@"
-
- # Clear old logs files that may exist.
- sudo rm -f "${RUNSC_LOGS_DIR}"/*
-
- # Restart docker to pick up the new runtime configuration.
- sudo systemctl restart docker
-}
diff --git a/scripts/common_bazel.sh b/scripts/common_bazel.sh
deleted file mode 100755
index dde0b51ed..000000000
--- a/scripts/common_bazel.sh
+++ /dev/null
@@ -1,92 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Install the latest version of Bazel and log the version.
-(which use_bazel.sh && use_bazel.sh latest) || which bazel
-bazel version
-
-# Switch into the workspace; only necessary if run with kokoro.
-if [[ -v KOKORO_GIT_COMMIT ]] && [[ -d git/repo ]]; then
- cd git/repo
-elif [[ -v KOKORO_GIT_COMMIT ]] && [[ -d github/repo ]]; then
- cd github/repo
-fi
-
-# Set the standard bazel flags.
-declare -r BAZEL_FLAGS=(
- "--show_timestamps"
- "--test_output=errors"
- "--keep_going"
- "--verbose_failures=true"
-)
-if [[ -v KOKORO_BAZEL_AUTH_CREDENTIAL ]] || [[ -v RBE_PROJECT_ID ]]; then
- declare -r RBE_PROJECT_ID="${RBE_PROJECT_ID:-gvisor-rbe}"
- declare -r BAZEL_RBE_FLAGS=(
- "--config=remote"
- "--project_id=${RBE_PROJECT_ID}"
- "--remote_instance_name=projects/${RBE_PROJECT_ID}/instances/default_instance"
- )
-fi
-if [[ -v KOKORO_BAZEL_AUTH_CREDENTIAL ]]; then
- declare -r BAZEL_RBE_AUTH_FLAGS=(
- "--auth_credentials=${KOKORO_BAZEL_AUTH_CREDENTIAL}"
- )
-fi
-
-# Wrap bazel.
-function build() {
- bazel build "${BAZEL_RBE_FLAGS[@]}" "${BAZEL_RBE_AUTH_FLAGS[@]}" "${BAZEL_FLAGS[@]}" "$@" 2>&1 |
- tee /dev/fd/2 | grep -E '^ bazel-bin/' | awk '{ print $1; }'
-}
-
-function test() {
- bazel test "${BAZEL_RBE_FLAGS[@]}" "${BAZEL_RBE_AUTH_FLAGS[@]}" "${BAZEL_FLAGS[@]}" "$@"
-}
-
-function run() {
- local binary=$1
- shift
- bazel run "${binary}" -- "$@"
-}
-
-function run_as_root() {
- local binary=$1
- shift
- bazel run --run_under="sudo" "${binary}" -- "$@"
-}
-
-function collect_logs() {
- # Zip out everything into a convenient form.
- if [[ -v KOKORO_ARTIFACTS_DIR ]] && [[ -e bazel-testlogs ]]; then
- # Move test logs to Kokoro directory. tar is used to conveniently perform
- # renames while moving files.
- find -L "bazel-testlogs" -name "test.xml" -o -name "test.log" -o -name "outputs.zip" |
- tar --create --files-from - --transform 's/test\./sponge_log./' |
- tar --extract --directory ${KOKORO_ARTIFACTS_DIR}
-
- # Collect sentry logs, if any.
- if [[ -v RUNSC_LOGS_DIR ]] && [[ -d "${RUNSC_LOGS_DIR}" ]]; then
- local -r logs=$(ls "${RUNSC_LOGS_DIR}")
- if [[ -z "${logs}" ]]; then
- tar --create --gzip --file="${KOKORO_ARTIFACTS_DIR}/${RUNTIME}.tar.gz" -C "${RUNSC_LOGS_DIR}" .
- fi
- fi
- fi
-}
-
-function find_branch_name() {
- git branch --show-current || git rev-parse HEAD || bazel info workspace | xargs basename
-}
diff --git a/scripts/dev.sh b/scripts/dev.sh
deleted file mode 100755
index 64151c558..000000000
--- a/scripts/dev.sh
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-source $(dirname $0)/common.sh
-
-# common.sh sets '-x', but it's annoying to see so much output.
-set +x
-
-# Defaults
-declare -i REFRESH=0
-declare NAME=$(find_branch_name)
-
-while [[ $# -gt 0 ]]; do
- case "$1" in
- --refresh)
- REFRESH=1
- ;;
- --help)
- echo "Use this script to build and install runsc with Docker."
- echo
- echo "usage: $0 [--refresh] [runtime_name]"
- exit 1
- ;;
- *)
- NAME=$1
- ;;
- esac
- shift
-done
-
-set_runtime "${NAME}"
-echo
-echo "Using runtime=${RUNTIME}"
-echo
-
-echo Building runsc...
-# Build first and fail on error. $() prevents "set -e" from reporting errors.
-build //runsc
-declare OUTPUT="$(build //runsc)"
-
-if [[ ${REFRESH} -eq 0 ]]; then
- install_runsc "${RUNTIME}" --net-raw
- install_runsc "${RUNTIME}-d" --net-raw --debug --strace --log-packets
-
- echo
- echo "Runtimes ${RUNTIME} and ${RUNTIME}-d (debug enabled) setup."
- echo "Use --runtime="${RUNTIME}" with your Docker command."
- echo " docker run --rm --runtime="${RUNTIME}" --rm hello-world"
- echo
- echo "If you rebuild, use $0 --refresh."
-
-else
- cp -f ${OUTPUT} "${RUNSC_BIN}"
-
- echo
- echo "Runtime ${RUNTIME} refreshed."
-fi
-
-echo "Logs are in: ${RUNSC_LOGS_DIR}"
diff --git a/scripts/do_tests.sh b/scripts/do_tests.sh
deleted file mode 100755
index a3a387c37..000000000
--- a/scripts/do_tests.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-source $(dirname $0)/common.sh
-
-# Build runsc.
-build //runsc
-
-# run runsc do without root privileges.
-run //runsc --rootless do true
-run //runsc --rootless --network=none do true
-
-# run runsc do with root privileges.
-run_as_root //runsc do true
diff --git a/scripts/docker_tests.sh b/scripts/docker_tests.sh
deleted file mode 100755
index 72ba05260..000000000
--- a/scripts/docker_tests.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-source $(dirname $0)/common.sh
-
-install_runsc_for_test docker
-test_runsc //test/image:image_test //test/e2e:integration_test
diff --git a/scripts/go.sh b/scripts/go.sh
deleted file mode 100755
index 0dbfb7747..000000000
--- a/scripts/go.sh
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-source $(dirname $0)/common.sh
-
-# Build the go path.
-build :gopath
-
-# Build the synthetic branch.
-tools/go_branch.sh
-
-# Checkout the new branch.
-git checkout go && git clean -f
-
-# Build everything.
-go build ./...
-
-# Push, if required.
-if [[ -v KOKORO_GO_PUSH ]] && [[ "${KOKORO_GO_PUSH}" == "true" ]]; then
- if [[ -v KOKORO_GITHUB_ACCESS_TOKEN ]]; then
- git config --global credential.helper cache
- git credential approve <<EOF
-protocol=https
-host=github.com
-username=$(cat "${KOKORO_KEYSTORE_DIR}/${KOKORO_GITHUB_ACCESS_TOKEN}")
-password=x-oauth-basic
-EOF
- fi
- git push origin go:go
-fi
diff --git a/scripts/hostnet_tests.sh b/scripts/hostnet_tests.sh
deleted file mode 100755
index 41298293d..000000000
--- a/scripts/hostnet_tests.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-source $(dirname $0)/common.sh
-
-# Install the runtime and perform basic tests.
-install_runsc_for_test hostnet --network=host
-test_runsc --test_arg=-checkpoint=false //test/image:image_test //test/e2e:integration_test
diff --git a/scripts/kvm_tests.sh b/scripts/kvm_tests.sh
deleted file mode 100755
index 5662401df..000000000
--- a/scripts/kvm_tests.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-source $(dirname $0)/common.sh
-
-# Ensure that KVM is loaded, and we can use it.
-(lsmod | grep -E '^(kvm_intel|kvm_amd)') || sudo modprobe kvm
-sudo chmod a+rw /dev/kvm
-
-# Run all KVM platform tests (locally).
-run_as_root //pkg/sentry/platform/kvm:kvm_test
-
-# Install the KVM runtime and run all integration tests.
-install_runsc_for_test kvm --platform=kvm
-test_runsc //test/image:image_test //test/e2e:integration_test
diff --git a/scripts/make_tests.sh b/scripts/make_tests.sh
deleted file mode 100755
index 0fa1248be..000000000
--- a/scripts/make_tests.sh
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-source $(dirname $0)/common.sh
-
-top_level=$(git rev-parse --show-toplevel 2>/dev/null)
-[[ $? -eq 0 ]] && cd "${top_level}" || exit 1
-
-make
-make runsc
-make bazel-shutdown
diff --git a/scripts/overlay_tests.sh b/scripts/overlay_tests.sh
deleted file mode 100755
index 2a1f12c0b..000000000
--- a/scripts/overlay_tests.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-source $(dirname $0)/common.sh
-
-# Install the runtime and perform basic tests.
-install_runsc_for_test overlay --overlay
-test_runsc //test/image:image_test //test/e2e:integration_test
diff --git a/scripts/release.sh b/scripts/release.sh
deleted file mode 100755
index b936bcc77..000000000
--- a/scripts/release.sh
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/bin/bash
-
-# Copyright 2018 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-source $(dirname $0)/common.sh
-
-# Tag a release only if provided.
-if ! [[ -v KOKORO_RELEASE_COMMIT ]]; then
- echo "No KOKORO_RELEASE_COMMIT provided." >&2
- exit 1
-fi
-if ! [[ -v KOKORO_RELEASE_TAG ]]; then
- echo "No KOKORO_RELEASE_TAG provided." >&2
- exit 1
-fi
-
-# Unless an explicit releaser is provided, use the bot e-mail.
-declare -r KOKORO_RELEASE_AUTHOR=${KOKORO_RELEASE_AUTHOR:-gvisor-bot}
-declare -r EMAIL=${EMAIL:-${KOKORO_RELEASE_AUTHOR}@google.com}
-
-# Ensure we have an appropriate configuration for the tag.
-git config --get user.name || git config user.name "gVisor-bot"
-git config --get user.email || git config user.email "${EMAIL}"
-
-# Run the release tool, which pushes to the origin repository.
-tools/tag_release.sh "${KOKORO_RELEASE_COMMIT}" "${KOKORO_RELEASE_TAG}"
diff --git a/scripts/root_tests.sh b/scripts/root_tests.sh
deleted file mode 100755
index 4e4fcc76b..000000000
--- a/scripts/root_tests.sh
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-source $(dirname $0)/common.sh
-
-# Reinstall the latest containerd shim.
-declare -r base="https://storage.googleapis.com/cri-containerd-staging/gvisor-containerd-shim"
-declare -r latest=$(mktemp --tmpdir gvisor-containerd-shim-latest.XXXXXX)
-declare -r shim_path=$(mktemp --tmpdir gvisor-containerd-shim.XXXXXX)
-wget --no-verbose "${base}"/latest -O ${latest}
-wget --no-verbose "${base}"/gvisor-containerd-shim-$(cat ${latest}) -O ${shim_path}
-chmod +x ${shim_path}
-sudo mv ${shim_path} /usr/local/bin/gvisor-containerd-shim
-
-# Run the tests that require root.
-install_runsc_for_test root
-run_as_root //test/root:root_test --runtime=${RUNTIME}
-
diff --git a/scripts/simple_tests.sh b/scripts/simple_tests.sh
deleted file mode 100755
index 585216aae..000000000
--- a/scripts/simple_tests.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-source $(dirname $0)/common.sh
-
-# Run all simple tests (locally).
-test //pkg/... //runsc/... //tools/...
diff --git a/scripts/syscall_tests.sh b/scripts/syscall_tests.sh
deleted file mode 100755
index a131b2d50..000000000
--- a/scripts/syscall_tests.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-source $(dirname $0)/common.sh
-
-# Run all ptrace-variants of the system call tests.
-test --test_tag_filters=runsc_ptrace //test/syscalls/...
diff --git a/test/BUILD b/test/BUILD
deleted file mode 100644
index 01fa01f2e..000000000
--- a/test/BUILD
+++ /dev/null
@@ -1,44 +0,0 @@
-package(licenses = ["notice"]) # Apache 2.0
-
-# We need to define a bazel platform and toolchain to specify dockerPrivileged
-# and dockerRunAsRoot options, they are required to run tests on the RBE
-# cluster in Kokoro.
-alias(
- name = "rbe_ubuntu1604",
- actual = ":rbe_ubuntu1604_r346485",
-)
-
-platform(
- name = "rbe_ubuntu1604_r346485",
- constraint_values = [
- "@bazel_tools//platforms:x86_64",
- "@bazel_tools//platforms:linux",
- "@bazel_tools//tools/cpp:clang",
- "@bazel_toolchains//constraints:xenial",
- "@bazel_toolchains//constraints/sanitizers:support_msan",
- ],
- remote_execution_properties = """
- properties: {
- name: "container-image"
- value:"docker://gcr.io/cloud-marketplace/google/rbe-ubuntu16-04@sha256:69c9f1652941d64a46f6f7358a44c1718f25caa5cb1ced4a58ccc5281cd183b5"
- }
- properties: {
- name: "dockerAddCapabilities"
- value: "SYS_ADMIN"
- }
- properties: {
- name: "dockerPrivileged"
- value: "true"
- }
- """,
-)
-
-toolchain(
- name = "cc-toolchain-clang-x86_64-default",
- exec_compatible_with = [
- ],
- target_compatible_with = [
- ],
- toolchain = "@bazel_toolchains//configs/ubuntu16_04_clang/9.0.0/bazel_0.28.0/cc:cc-compiler-k8",
- toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
-)
diff --git a/test/README.md b/test/README.md
deleted file mode 100644
index 09c36b461..000000000
--- a/test/README.md
+++ /dev/null
@@ -1,18 +0,0 @@
-# Tests
-
-The tests defined under this path are verifying functionality beyond what unit
-tests can cover, e.g. integration and end to end tests. Due to their nature,
-they may need extra setup in the test machine and extra configuration to run.
-
-- **syscalls**: system call tests use a local runner, and do not require
- additional configuration in the machine.
-- **integration:** defines integration tests that uses `docker run` to test
- functionality.
-- **image:** basic end to end test for popular images. These require the same
- setup as integration tests.
-- **root:** tests that require to be run as root.
-- **util:** utilities library to support the tests.
-
-For the above noted cases, the relevant runtime must be installed via `runsc
-install` before running. This is handled automatically by the test scripts in
-the `kokoro` directory.
diff --git a/test/e2e/BUILD b/test/e2e/BUILD
deleted file mode 100644
index 99442cffb..000000000
--- a/test/e2e/BUILD
+++ /dev/null
@@ -1,31 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
-
-package(licenses = ["notice"])
-
-go_test(
- name = "integration_test",
- size = "large",
- srcs = [
- "exec_test.go",
- "integration_test.go",
- "regression_test.go",
- ],
- embed = [":integration"],
- tags = [
- # Requires docker and runsc to be configured before the test runs.
- "manual",
- "local",
- ],
- visibility = ["//:sandbox"],
- deps = [
- "//pkg/abi/linux",
- "//runsc/dockerutil",
- "//runsc/testutil",
- ],
-)
-
-go_library(
- name = "integration",
- srcs = ["integration.go"],
- importpath = "gvisor.dev/gvisor/test/integration",
-)
diff --git a/test/e2e/exec_test.go b/test/e2e/exec_test.go
deleted file mode 100644
index ce2c4f689..000000000
--- a/test/e2e/exec_test.go
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package image provides end-to-end integration tests for runsc. These tests
-// require docker and runsc to be installed on the machine.
-//
-// Each test calls docker commands to start up a container, and tests that it
-// is behaving properly, with various runsc commands. The container is killed
-// and deleted at the end.
-
-package integration
-
-import (
- "fmt"
- "strconv"
- "strings"
- "syscall"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/runsc/dockerutil"
-)
-
-func TestExecCapabilities(t *testing.T) {
- if err := dockerutil.Pull("alpine"); err != nil {
- t.Fatalf("docker pull failed: %v", err)
- }
- d := dockerutil.MakeDocker("exec-test")
-
- // Start the container.
- if err := d.Run("alpine", "sh", "-c", "cat /proc/self/status; sleep 100"); err != nil {
- t.Fatalf("docker run failed: %v", err)
- }
- defer d.CleanUp()
-
- matches, err := d.WaitForOutputSubmatch("CapEff:\t([0-9a-f]+)\n", 5*time.Second)
- if err != nil {
- t.Fatalf("WaitForOutputSubmatch() timeout: %v", err)
- }
- if len(matches) != 2 {
- t.Fatalf("There should be a match for the whole line and the capability bitmask")
- }
- capString := matches[1]
- t.Log("Root capabilities:", capString)
-
- // CAP_NET_RAW was in the capability set for the container, but was
- // removed. However, `exec` does not remove it. Verify that it's not
- // set in the container, then re-add it for comparison.
- caps, err := strconv.ParseUint(capString, 16, 64)
- if err != nil {
- t.Fatalf("failed to convert capabilities %q: %v", capString, err)
- }
- if caps&(1<<uint64(linux.CAP_NET_RAW)) != 0 {
- t.Fatalf("CAP_NET_RAW should be filtered, but is set in the container: %x", caps)
- }
- caps |= 1 << uint64(linux.CAP_NET_RAW)
- want := fmt.Sprintf("CapEff:\t%016x\n", caps)
-
- // Now check that exec'd process capabilities match the root.
- got, err := d.Exec("grep", "CapEff:", "/proc/self/status")
- if err != nil {
- t.Fatalf("docker exec failed: %v", err)
- }
- if got != want {
- t.Errorf("wrong capabilities, got: %q, want: %q", got, want)
- }
-}
-
-func TestExecJobControl(t *testing.T) {
- if err := dockerutil.Pull("alpine"); err != nil {
- t.Fatalf("docker pull failed: %v", err)
- }
- d := dockerutil.MakeDocker("exec-job-control-test")
-
- // Start the container.
- if err := d.Run("alpine", "sleep", "1000"); err != nil {
- t.Fatalf("docker run failed: %v", err)
- }
- defer d.CleanUp()
-
- // Exec 'sh' with an attached pty.
- cmd, ptmx, err := d.ExecWithTerminal("sh")
- if err != nil {
- t.Fatalf("docker exec failed: %v", err)
- }
- defer ptmx.Close()
-
- // Call "sleep 100 | cat" in the shell. We pipe to cat so that there
- // will be two processes in the foreground process group.
- if _, err := ptmx.Write([]byte("sleep 100 | cat\n")); err != nil {
- t.Fatalf("error writing to pty: %v", err)
- }
-
- // Give shell a few seconds to start executing the sleep.
- time.Sleep(2 * time.Second)
-
- // Send a ^C to the pty, which should kill sleep and cat, but not the
- // shell. \x03 is ASCII "end of text", which is the same as ^C.
- if _, err := ptmx.Write([]byte{'\x03'}); err != nil {
- t.Fatalf("error writing to pty: %v", err)
- }
-
- // The shell should still be alive at this point. Sleep should have
- // exited with code 2+128=130. We'll exit with 10 plus that number, so
- // that we can be sure that the shell did not get signalled.
- if _, err := ptmx.Write([]byte("exit $(expr $? + 10)\n")); err != nil {
- t.Fatalf("error writing to pty: %v", err)
- }
-
- // Exec process should exit with code 10+130=140.
- ps, err := cmd.Process.Wait()
- if err != nil {
- t.Fatalf("error waiting for exec process: %v", err)
- }
- ws := ps.Sys().(syscall.WaitStatus)
- if !ws.Exited() {
- t.Errorf("ws.Exited got false, want true")
- }
- if got, want := ws.ExitStatus(), 140; got != want {
- t.Errorf("ws.ExitedStatus got %d, want %d", got, want)
- }
-}
-
-// Test that failure to exec returns proper error message.
-func TestExecError(t *testing.T) {
- if err := dockerutil.Pull("alpine"); err != nil {
- t.Fatalf("docker pull failed: %v", err)
- }
- d := dockerutil.MakeDocker("exec-error-test")
-
- // Start the container.
- if err := d.Run("alpine", "sleep", "1000"); err != nil {
- t.Fatalf("docker run failed: %v", err)
- }
- defer d.CleanUp()
-
- _, err := d.Exec("no_can_find")
- if err == nil {
- t.Fatalf("docker exec didn't fail")
- }
- if want := `error finding executable "no_can_find" in PATH`; !strings.Contains(err.Error(), want) {
- t.Fatalf("docker exec wrong error, got: %s, want: .*%s.*", err.Error(), want)
- }
-}
diff --git a/test/e2e/integration.go b/test/e2e/integration.go
deleted file mode 100644
index 4cd5f6c24..000000000
--- a/test/e2e/integration.go
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package integration is empty. See integration_test.go for description.
-package integration
diff --git a/test/e2e/integration_test.go b/test/e2e/integration_test.go
deleted file mode 100644
index 7cc0de129..000000000
--- a/test/e2e/integration_test.go
+++ /dev/null
@@ -1,348 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package integration provides end-to-end integration tests for runsc.
-//
-// Each test calls docker commands to start up a container, and tests that it is
-// behaving properly, with various runsc commands. The container is killed and
-// deleted at the end.
-//
-// Setup instruction in test/README.md.
-package integration
-
-import (
- "flag"
- "fmt"
- "net"
- "net/http"
- "os"
- "strconv"
- "strings"
- "syscall"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/runsc/dockerutil"
- "gvisor.dev/gvisor/runsc/testutil"
-)
-
-// httpRequestSucceeds sends a request to a given url and checks that the status is OK.
-func httpRequestSucceeds(client http.Client, server string, port int) error {
- url := fmt.Sprintf("http://%s:%d", server, port)
- // Ensure that content is being served.
- resp, err := client.Get(url)
- if err != nil {
- return fmt.Errorf("error reaching http server: %v", err)
- }
- if want := http.StatusOK; resp.StatusCode != want {
- return fmt.Errorf("wrong response code, got: %d, want: %d", resp.StatusCode, want)
- }
- return nil
-}
-
-// TestLifeCycle tests a basic Create/Start/Stop docker container life cycle.
-func TestLifeCycle(t *testing.T) {
- if err := dockerutil.Pull("nginx"); err != nil {
- t.Fatal("docker pull failed:", err)
- }
- d := dockerutil.MakeDocker("lifecycle-test")
- if err := d.Create("-p", "80", "nginx"); err != nil {
- t.Fatal("docker create failed:", err)
- }
- if err := d.Start(); err != nil {
- d.CleanUp()
- t.Fatal("docker start failed:", err)
- }
-
- // Test that container is working
- port, err := d.FindPort(80)
- if err != nil {
- t.Fatal("docker.FindPort(80) failed: ", err)
- }
- if err := testutil.WaitForHTTP(port, 30*time.Second); err != nil {
- t.Fatal("WaitForHTTP() timeout:", err)
- }
- client := http.Client{Timeout: time.Duration(2 * time.Second)}
- if err := httpRequestSucceeds(client, "localhost", port); err != nil {
- t.Error("http request failed:", err)
- }
-
- if err := d.Stop(); err != nil {
- d.CleanUp()
- t.Fatal("docker stop failed:", err)
- }
- if err := d.Remove(); err != nil {
- t.Fatal("docker rm failed:", err)
- }
-}
-
-func TestPauseResume(t *testing.T) {
- const img = "gcr.io/gvisor-presubmit/python-hello"
- if !testutil.IsCheckpointSupported() {
- t.Log("Checkpoint is not supported, skipping test.")
- return
- }
-
- if err := dockerutil.Pull(img); err != nil {
- t.Fatal("docker pull failed:", err)
- }
- d := dockerutil.MakeDocker("pause-resume-test")
- if err := d.Run("-p", "8080", img); err != nil {
- t.Fatalf("docker run failed: %v", err)
- }
- defer d.CleanUp()
-
- // Find where port 8080 is mapped to.
- port, err := d.FindPort(8080)
- if err != nil {
- t.Fatal("docker.FindPort(8080) failed:", err)
- }
-
- // Wait until it's up and running.
- if err := testutil.WaitForHTTP(port, 30*time.Second); err != nil {
- t.Fatal("WaitForHTTP() timeout:", err)
- }
-
- // Check that container is working.
- client := http.Client{Timeout: time.Duration(2 * time.Second)}
- if err := httpRequestSucceeds(client, "localhost", port); err != nil {
- t.Error("http request failed:", err)
- }
-
- if err := d.Pause(); err != nil {
- t.Fatal("docker pause failed:", err)
- }
-
- // Check if container is paused.
- switch _, err := client.Get(fmt.Sprintf("http://localhost:%d", port)); v := err.(type) {
- case nil:
- t.Errorf("http req expected to fail but it succeeded")
- case net.Error:
- if !v.Timeout() {
- t.Errorf("http req got error %v, wanted timeout", v)
- }
- default:
- t.Errorf("http req got unexpected error %v", v)
- }
-
- if err := d.Unpause(); err != nil {
- t.Fatal("docker unpause failed:", err)
- }
-
- // Wait until it's up and running.
- if err := testutil.WaitForHTTP(port, 30*time.Second); err != nil {
- t.Fatal("WaitForHTTP() timeout:", err)
- }
-
- // Check if container is working again.
- if err := httpRequestSucceeds(client, "localhost", port); err != nil {
- t.Error("http request failed:", err)
- }
-}
-
-func TestCheckpointRestore(t *testing.T) {
- const img = "gcr.io/gvisor-presubmit/python-hello"
- if !testutil.IsCheckpointSupported() {
- t.Log("Pause/resume is not supported, skipping test.")
- return
- }
-
- if err := dockerutil.Pull(img); err != nil {
- t.Fatal("docker pull failed:", err)
- }
- d := dockerutil.MakeDocker("save-restore-test")
- if err := d.Run("-p", "8080", img); err != nil {
- t.Fatalf("docker run failed: %v", err)
- }
- defer d.CleanUp()
-
- if err := d.Checkpoint("test"); err != nil {
- t.Fatal("docker checkpoint failed:", err)
- }
-
- if _, err := d.Wait(30 * time.Second); err != nil {
- t.Fatal(err)
- }
-
- if err := d.Restore("test"); err != nil {
- t.Fatal("docker restore failed:", err)
- }
-
- // Find where port 8080 is mapped to.
- port, err := d.FindPort(8080)
- if err != nil {
- t.Fatal("docker.FindPort(8080) failed:", err)
- }
-
- // Wait until it's up and running.
- if err := testutil.WaitForHTTP(port, 30*time.Second); err != nil {
- t.Fatal("WaitForHTTP() timeout:", err)
- }
-
- // Check if container is working again.
- client := http.Client{Timeout: time.Duration(2 * time.Second)}
- if err := httpRequestSucceeds(client, "localhost", port); err != nil {
- t.Error("http request failed:", err)
- }
-}
-
-// Create client and server that talk to each other using the local IP.
-func TestConnectToSelf(t *testing.T) {
- d := dockerutil.MakeDocker("connect-to-self-test")
-
- // Creates server that replies "server" and exists. Sleeps at the end because
- // 'docker exec' gets killed if the init process exists before it can finish.
- if err := d.Run("ubuntu:trusty", "/bin/sh", "-c", "echo server | nc -l -p 8080 && sleep 1"); err != nil {
- t.Fatal("docker run failed:", err)
- }
- defer d.CleanUp()
-
- // Finds IP address for host.
- ip, err := d.Exec("/bin/sh", "-c", "cat /etc/hosts | grep ${HOSTNAME} | awk '{print $1}'")
- if err != nil {
- t.Fatal("docker exec failed:", err)
- }
- ip = strings.TrimRight(ip, "\n")
-
- // Runs client that sends "client" to the server and exits.
- reply, err := d.Exec("/bin/sh", "-c", fmt.Sprintf("echo client | nc %s 8080", ip))
- if err != nil {
- t.Fatal("docker exec failed:", err)
- }
-
- // Ensure both client and server got the message from each other.
- if want := "server\n"; reply != want {
- t.Errorf("Error on server, want: %q, got: %q", want, reply)
- }
- if _, err := d.WaitForOutput("^client\n$", 1*time.Second); err != nil {
- t.Fatal("docker.WaitForOutput(client) timeout:", err)
- }
-}
-
-func TestMemLimit(t *testing.T) {
- if err := dockerutil.Pull("alpine"); err != nil {
- t.Fatal("docker pull failed:", err)
- }
- d := dockerutil.MakeDocker("cgroup-test")
- cmd := "cat /proc/meminfo | grep MemTotal: | awk '{print $2}'"
- out, err := d.RunFg("--memory=500MB", "alpine", "sh", "-c", cmd)
- if err != nil {
- t.Fatal("docker run failed:", err)
- }
- defer d.CleanUp()
-
- // Remove warning message that swap isn't present.
- if strings.HasPrefix(out, "WARNING") {
- lines := strings.Split(out, "\n")
- if len(lines) != 3 {
- t.Fatalf("invalid output: %s", out)
- }
- out = lines[1]
- }
-
- got, err := strconv.ParseUint(strings.TrimSpace(out), 10, 64)
- if err != nil {
- t.Fatalf("failed to parse %q: %v", out, err)
- }
- if want := uint64(500 * 1024); got != want {
- t.Errorf("MemTotal got: %d, want: %d", got, want)
- }
-}
-
-func TestNumCPU(t *testing.T) {
- if err := dockerutil.Pull("alpine"); err != nil {
- t.Fatal("docker pull failed:", err)
- }
- d := dockerutil.MakeDocker("cgroup-test")
- cmd := "cat /proc/cpuinfo | grep 'processor.*:' | wc -l"
- out, err := d.RunFg("--cpuset-cpus=0", "alpine", "sh", "-c", cmd)
- if err != nil {
- t.Fatal("docker run failed:", err)
- }
- defer d.CleanUp()
-
- got, err := strconv.Atoi(strings.TrimSpace(out))
- if err != nil {
- t.Fatalf("failed to parse %q: %v", out, err)
- }
- if want := 1; got != want {
- t.Errorf("MemTotal got: %d, want: %d", got, want)
- }
-}
-
-// TestJobControl tests that job control characters are handled properly.
-func TestJobControl(t *testing.T) {
- if err := dockerutil.Pull("alpine"); err != nil {
- t.Fatalf("docker pull failed: %v", err)
- }
- d := dockerutil.MakeDocker("job-control-test")
-
- // Start the container with an attached PTY.
- _, ptmx, err := d.RunWithPty("alpine", "sh")
- if err != nil {
- t.Fatalf("docker run failed: %v", err)
- }
- defer ptmx.Close()
- defer d.CleanUp()
-
- // Call "sleep 100" in the shell.
- if _, err := ptmx.Write([]byte("sleep 100\n")); err != nil {
- t.Fatalf("error writing to pty: %v", err)
- }
-
- // Give shell a few seconds to start executing the sleep.
- time.Sleep(2 * time.Second)
-
- // Send a ^C to the pty, which should kill sleep, but not the shell.
- // \x03 is ASCII "end of text", which is the same as ^C.
- if _, err := ptmx.Write([]byte{'\x03'}); err != nil {
- t.Fatalf("error writing to pty: %v", err)
- }
-
- // The shell should still be alive at this point. Sleep should have
- // exited with code 2+128=130. We'll exit with 10 plus that number, so
- // that we can be sure that the shell did not get signalled.
- if _, err := ptmx.Write([]byte("exit $(expr $? + 10)\n")); err != nil {
- t.Fatalf("error writing to pty: %v", err)
- }
-
- // Wait for the container to exit.
- got, err := d.Wait(5 * time.Second)
- if err != nil {
- t.Fatalf("error getting exit code: %v", err)
- }
- // Container should exit with code 10+130=140.
- if want := syscall.WaitStatus(140); got != want {
- t.Errorf("container exited with code %d want %d", got, want)
- }
-}
-
-// TestTmpFile checks that files inside '/tmp' are not overridden. In addition,
-// it checks that working dir is created if it doesn't exit.
-func TestTmpFile(t *testing.T) {
- if err := dockerutil.Pull("alpine"); err != nil {
- t.Fatal("docker pull failed:", err)
- }
- d := dockerutil.MakeDocker("tmp-file-test")
- if err := d.Run("-w=/tmp/foo/bar", "--read-only", "alpine", "touch", "/tmp/foo/bar/file"); err != nil {
- t.Fatal("docker run failed:", err)
- }
- defer d.CleanUp()
-}
-
-func TestMain(m *testing.M) {
- dockerutil.EnsureSupportedDockerVersion()
- flag.Parse()
- os.Exit(m.Run())
-}
diff --git a/test/e2e/regression_test.go b/test/e2e/regression_test.go
deleted file mode 100644
index 2488be383..000000000
--- a/test/e2e/regression_test.go
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package integration
-
-import (
- "strings"
- "testing"
-
- "gvisor.dev/gvisor/runsc/dockerutil"
-)
-
-// Test that UDS can be created using overlay when parent directory is in lower
-// layer only (b/134090485).
-//
-// Prerequisite: the directory where the socket file is created must not have
-// been open for write before bind(2) is called.
-func TestBindOverlay(t *testing.T) {
- if err := dockerutil.Pull("ubuntu:trusty"); err != nil {
- t.Fatal("docker pull failed:", err)
- }
- d := dockerutil.MakeDocker("bind-overlay-test")
-
- cmd := "nc -l -U /var/run/sock & p=$! && sleep 1 && echo foobar-asdf | nc -U /var/run/sock && wait $p"
- got, err := d.RunFg("ubuntu:trusty", "bash", "-c", cmd)
- if err != nil {
- t.Fatal("docker run failed:", err)
- }
-
- if want := "foobar-asdf"; !strings.Contains(got, want) {
- t.Fatalf("docker run output is missing %q: %s", want, got)
- }
- defer d.CleanUp()
-}
diff --git a/test/image/BUILD b/test/image/BUILD
deleted file mode 100644
index 09b0a0ad5..000000000
--- a/test/image/BUILD
+++ /dev/null
@@ -1,34 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
-
-package(licenses = ["notice"])
-
-go_test(
- name = "image_test",
- size = "large",
- srcs = [
- "image_test.go",
- ],
- data = [
- "latin10k.txt",
- "mysql.sql",
- "ruby.rb",
- "ruby.sh",
- ],
- embed = [":image"],
- tags = [
- # Requires docker and runsc to be configured before the test runs.
- "manual",
- "local",
- ],
- visibility = ["//:sandbox"],
- deps = [
- "//runsc/dockerutil",
- "//runsc/testutil",
- ],
-)
-
-go_library(
- name = "image",
- srcs = ["image.go"],
- importpath = "gvisor.dev/gvisor/test/image",
-)
diff --git a/test/image/image.go b/test/image/image.go
deleted file mode 100644
index 297f1ab92..000000000
--- a/test/image/image.go
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package image is empty. See image_test.go for description.
-package image
diff --git a/test/image/image_test.go b/test/image/image_test.go
deleted file mode 100644
index d0dcb1861..000000000
--- a/test/image/image_test.go
+++ /dev/null
@@ -1,353 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package image provides end-to-end image tests for runsc.
-
-// Each test calls docker commands to start up a container, and tests that it
-// is behaving properly, like connecting to a port or looking at the output.
-// The container is killed and deleted at the end.
-//
-// Setup instruction in test/README.md.
-package image
-
-import (
- "flag"
- "fmt"
- "io/ioutil"
- "log"
- "net/http"
- "os"
- "path/filepath"
- "strings"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/runsc/dockerutil"
- "gvisor.dev/gvisor/runsc/testutil"
-)
-
-func TestHelloWorld(t *testing.T) {
- d := dockerutil.MakeDocker("hello-test")
- if err := d.Run("hello-world"); err != nil {
- t.Fatalf("docker run failed: %v", err)
- }
- defer d.CleanUp()
-
- if _, err := d.WaitForOutput("Hello from Docker!", 5*time.Second); err != nil {
- t.Fatalf("docker didn't say hello: %v", err)
- }
-}
-
-func runHTTPRequest(port int) error {
- url := fmt.Sprintf("http://localhost:%d/not-found", port)
- resp, err := http.Get(url)
- if err != nil {
- return fmt.Errorf("error reaching http server: %v", err)
- }
- if want := http.StatusNotFound; resp.StatusCode != want {
- return fmt.Errorf("Wrong response code, got: %d, want: %d", resp.StatusCode, want)
- }
-
- url = fmt.Sprintf("http://localhost:%d/latin10k.txt", port)
- resp, err = http.Get(url)
- if err != nil {
- return fmt.Errorf("Error reaching http server: %v", err)
- }
- if want := http.StatusOK; resp.StatusCode != want {
- return fmt.Errorf("Wrong response code, got: %d, want: %d", resp.StatusCode, want)
- }
-
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- return fmt.Errorf("Error reading http response: %v", err)
- }
- defer resp.Body.Close()
-
- // READALL is the last word in the file. Ensures everything was read.
- if want := "READALL"; strings.HasSuffix(string(body), want) {
- return fmt.Errorf("response doesn't contain %q, resp: %q", want, body)
- }
- return nil
-}
-
-func testHTTPServer(t *testing.T, port int) {
- const requests = 10
- ch := make(chan error, requests)
- for i := 0; i < requests; i++ {
- go func() {
- start := time.Now()
- err := runHTTPRequest(port)
- log.Printf("Response time %v: %v", time.Since(start).String(), err)
- ch <- err
- }()
- }
-
- for i := 0; i < requests; i++ {
- err := <-ch
- if err != nil {
- t.Errorf("testHTTPServer(%d) failed: %v", port, err)
- }
- }
-}
-
-func TestHttpd(t *testing.T) {
- if err := dockerutil.Pull("httpd"); err != nil {
- t.Fatalf("docker pull failed: %v", err)
- }
- d := dockerutil.MakeDocker("http-test")
-
- dir, err := dockerutil.PrepareFiles("latin10k.txt")
- if err != nil {
- t.Fatalf("PrepareFiles() failed: %v", err)
- }
-
- // Start the container.
- mountArg := dockerutil.MountArg(dir, "/usr/local/apache2/htdocs", dockerutil.ReadOnly)
- if err := d.Run("-p", "80", mountArg, "httpd"); err != nil {
- t.Fatalf("docker run failed: %v", err)
- }
- defer d.CleanUp()
-
- // Find where port 80 is mapped to.
- port, err := d.FindPort(80)
- if err != nil {
- t.Fatalf("docker.FindPort(80) failed: %v", err)
- }
-
- // Wait until it's up and running.
- if err := testutil.WaitForHTTP(port, 30*time.Second); err != nil {
- t.Errorf("WaitForHTTP() timeout: %v", err)
- }
-
- testHTTPServer(t, port)
-}
-
-func TestNginx(t *testing.T) {
- if err := dockerutil.Pull("nginx"); err != nil {
- t.Fatalf("docker pull failed: %v", err)
- }
- d := dockerutil.MakeDocker("net-test")
-
- dir, err := dockerutil.PrepareFiles("latin10k.txt")
- if err != nil {
- t.Fatalf("PrepareFiles() failed: %v", err)
- }
-
- // Start the container.
- mountArg := dockerutil.MountArg(dir, "/usr/share/nginx/html", dockerutil.ReadOnly)
- if err := d.Run("-p", "80", mountArg, "nginx"); err != nil {
- t.Fatalf("docker run failed: %v", err)
- }
- defer d.CleanUp()
-
- // Find where port 80 is mapped to.
- port, err := d.FindPort(80)
- if err != nil {
- t.Fatalf("docker.FindPort(80) failed: %v", err)
- }
-
- // Wait until it's up and running.
- if err := testutil.WaitForHTTP(port, 30*time.Second); err != nil {
- t.Errorf("WaitForHTTP() timeout: %v", err)
- }
-
- testHTTPServer(t, port)
-}
-
-func TestMysql(t *testing.T) {
- if err := dockerutil.Pull("mysql"); err != nil {
- t.Fatalf("docker pull failed: %v", err)
- }
- d := dockerutil.MakeDocker("mysql-test")
-
- // Start the container.
- if err := d.Run("-e", "MYSQL_ROOT_PASSWORD=foobar123", "mysql"); err != nil {
- t.Fatalf("docker run failed: %v", err)
- }
- defer d.CleanUp()
-
- // Wait until it's up and running.
- if _, err := d.WaitForOutput("port: 3306 MySQL Community Server", 3*time.Minute); err != nil {
- t.Fatalf("docker.WaitForOutput() timeout: %v", err)
- }
-
- client := dockerutil.MakeDocker("mysql-client-test")
- dir, err := dockerutil.PrepareFiles("mysql.sql")
- if err != nil {
- t.Fatalf("PrepareFiles() failed: %v", err)
- }
-
- // Tell mysql client to connect to the server and execute the file in verbose
- // mode to verify the output.
- args := []string{
- dockerutil.LinkArg(&d, "mysql"),
- dockerutil.MountArg(dir, "/sql", dockerutil.ReadWrite),
- "mysql",
- "mysql", "-hmysql", "-uroot", "-pfoobar123", "-v", "-e", "source /sql/mysql.sql",
- }
- if err := client.Run(args...); err != nil {
- t.Fatalf("docker run failed: %v", err)
- }
- defer client.CleanUp()
-
- // Ensure file executed to the end and shutdown mysql.
- if _, err := client.WaitForOutput("--------------\nshutdown\n--------------", 15*time.Second); err != nil {
- t.Fatalf("docker.WaitForOutput() timeout: %v", err)
- }
- if _, err := d.WaitForOutput("mysqld: Shutdown complete", 30*time.Second); err != nil {
- t.Fatalf("docker.WaitForOutput() timeout: %v", err)
- }
-}
-
-func TestPythonHello(t *testing.T) {
- // TODO(b/136503277): Once we have more complete python runtime tests,
- // we can drop this one.
- const img = "gcr.io/gvisor-presubmit/python-hello"
- if err := dockerutil.Pull(img); err != nil {
- t.Fatalf("docker pull failed: %v", err)
- }
- d := dockerutil.MakeDocker("python-hello-test")
- if err := d.Run("-p", "8080", img); err != nil {
- t.Fatalf("docker run failed: %v", err)
- }
- defer d.CleanUp()
-
- // Find where port 8080 is mapped to.
- port, err := d.FindPort(8080)
- if err != nil {
- t.Fatalf("docker.FindPort(8080) failed: %v", err)
- }
-
- // Wait until it's up and running.
- if err := testutil.WaitForHTTP(port, 30*time.Second); err != nil {
- t.Fatalf("WaitForHTTP() timeout: %v", err)
- }
-
- // Ensure that content is being served.
- url := fmt.Sprintf("http://localhost:%d", port)
- resp, err := http.Get(url)
- if err != nil {
- t.Errorf("Error reaching http server: %v", err)
- }
- if want := http.StatusOK; resp.StatusCode != want {
- t.Errorf("Wrong response code, got: %d, want: %d", resp.StatusCode, want)
- }
-}
-
-func TestTomcat(t *testing.T) {
- if err := dockerutil.Pull("tomcat:8.0"); err != nil {
- t.Fatalf("docker pull failed: %v", err)
- }
- d := dockerutil.MakeDocker("tomcat-test")
- if err := d.Run("-p", "8080", "tomcat:8.0"); err != nil {
- t.Fatalf("docker run failed: %v", err)
- }
- defer d.CleanUp()
-
- // Find where port 8080 is mapped to.
- port, err := d.FindPort(8080)
- if err != nil {
- t.Fatalf("docker.FindPort(8080) failed: %v", err)
- }
-
- // Wait until it's up and running.
- if err := testutil.WaitForHTTP(port, 30*time.Second); err != nil {
- t.Fatalf("WaitForHTTP() timeout: %v", err)
- }
-
- // Ensure that content is being served.
- url := fmt.Sprintf("http://localhost:%d", port)
- resp, err := http.Get(url)
- if err != nil {
- t.Errorf("Error reaching http server: %v", err)
- }
- if want := http.StatusOK; resp.StatusCode != want {
- t.Errorf("Wrong response code, got: %d, want: %d", resp.StatusCode, want)
- }
-}
-
-func TestRuby(t *testing.T) {
- if err := dockerutil.Pull("ruby"); err != nil {
- t.Fatalf("docker pull failed: %v", err)
- }
- d := dockerutil.MakeDocker("ruby-test")
-
- dir, err := dockerutil.PrepareFiles("ruby.rb", "ruby.sh")
- if err != nil {
- t.Fatalf("PrepareFiles() failed: %v", err)
- }
- if err := os.Chmod(filepath.Join(dir, "ruby.sh"), 0333); err != nil {
- t.Fatalf("os.Chmod(%q, 0333) failed: %v", dir, err)
- }
-
- if err := d.Run("-p", "8080", dockerutil.MountArg(dir, "/src", dockerutil.ReadOnly), "ruby", "/src/ruby.sh"); err != nil {
- t.Fatalf("docker run failed: %v", err)
- }
- defer d.CleanUp()
-
- // Find where port 8080 is mapped to.
- port, err := d.FindPort(8080)
- if err != nil {
- t.Fatalf("docker.FindPort(8080) failed: %v", err)
- }
-
- // Wait until it's up and running, 'gem install' can take some time.
- if err := testutil.WaitForHTTP(port, 1*time.Minute); err != nil {
- t.Fatalf("WaitForHTTP() timeout: %v", err)
- }
-
- // Ensure that content is being served.
- url := fmt.Sprintf("http://localhost:%d", port)
- resp, err := http.Get(url)
- if err != nil {
- t.Errorf("error reaching http server: %v", err)
- }
- if want := http.StatusOK; resp.StatusCode != want {
- t.Errorf("wrong response code, got: %d, want: %d", resp.StatusCode, want)
- }
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- t.Fatalf("error reading body: %v", err)
- }
- if got, want := string(body), "Hello World"; !strings.Contains(got, want) {
- t.Errorf("invalid body content, got: %q, want: %q", got, want)
- }
-}
-
-func TestStdio(t *testing.T) {
- if err := dockerutil.Pull("alpine"); err != nil {
- t.Fatalf("docker pull failed: %v", err)
- }
- d := dockerutil.MakeDocker("stdio-test")
-
- wantStdout := "hello stdout"
- wantStderr := "bonjour stderr"
- cmd := fmt.Sprintf("echo %q; echo %q 1>&2;", wantStdout, wantStderr)
- if err := d.Run("alpine", "/bin/sh", "-c", cmd); err != nil {
- t.Fatalf("docker run failed: %v", err)
- }
- defer d.CleanUp()
-
- for _, want := range []string{wantStdout, wantStderr} {
- if _, err := d.WaitForOutput(want, 5*time.Second); err != nil {
- t.Fatalf("docker didn't get output %q : %v", want, err)
- }
- }
-}
-
-func TestMain(m *testing.M) {
- dockerutil.EnsureSupportedDockerVersion()
- flag.Parse()
- os.Exit(m.Run())
-}
diff --git a/test/image/latin10k.txt b/test/image/latin10k.txt
deleted file mode 100644
index 61341e00b..000000000
--- a/test/image/latin10k.txt
+++ /dev/null
@@ -1,33 +0,0 @@
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras ut placerat felis. Maecenas urna est, auctor a efficitur sit amet, egestas et augue. Curabitur dignissim scelerisque nunc vel cursus. Ut vehicula est pretium, consectetur nunc non, pharetra ligula. Curabitur ut ultricies metus. Suspendisse pulvinar, orci sed fermentum vestibulum, eros turpis molestie lectus, nec elementum risus dolor mattis felis. Donec ultrices ipsum sem, at pretium lacus convallis at. Mauris nulla enim, tincidunt non bibendum at, vehicula pulvinar mauris.
-
-Duis in dapibus turpis. Pellentesque maximus magna odio, ac congue libero laoreet quis. Maecenas euismod risus in justo aliquam accumsan. Nunc quis ornare arcu, sit amet sodales elit. Phasellus nec scelerisque nisl, a tincidunt arcu. Proin ornare est nunc, sed suscipit orci interdum et. Suspendisse condimentum venenatis diam in tempor. Aliquam egestas lectus in rutrum tempus. Donec id egestas eros. Donec molestie consequat purus, sed posuere odio venenatis vitae. Nunc placerat augue id vehicula varius. In hac habitasse platea dictumst. Proin at est accumsan, venenatis quam a, fermentum risus. Phasellus posuere pellentesque enim, id suscipit magna consequat ut. Quisque ut tortor ante.
-
-Cras ut vulputate metus, a laoreet lectus. Vivamus ultrices molestie odio in tristique. Morbi faucibus mi eget sollicitudin fringilla. Fusce vitae lacinia ligula. Sed egestas sed diam eu posuere. Maecenas justo nisl, venenatis vel nibh vel, cursus aliquam velit. Praesent lacinia dui id erat venenatis rhoncus. Morbi gravida felis ante, sit amet vehicula orci rhoncus vitae.
-
-Sed finibus sagittis dictum. Proin auctor suscipit sem et mattis. Phasellus libero ligula, pellentesque ut felis porttitor, fermentum sollicitudin orci. Nulla eu nulla nibh. Fusce a eros risus. Proin vel magna risus. Donec nec elit eleifend, scelerisque sapien vitae, pharetra quam. Donec porttitor mauris scelerisque, tempus orci hendrerit, dapibus felis. Nullam libero elit, sollicitudin a aliquam at, ultrices in erat. Mauris eget ligula sodales, porta turpis et, scelerisque odio. Mauris mollis leo vitae purus gravida, in tempor nunc efficitur. Nulla facilisis posuere augue, nec pellentesque lectus eleifend ac. Vestibulum convallis est a feugiat tincidunt. Donec vitae enim volutpat, tincidunt eros eu, malesuada nibh.
-
-Quisque molestie, magna ornare elementum convallis, erat enim sagittis ipsum, eget porttitor sapien arcu id purus. Donec ut cursus diam. Nulla rutrum nulla et mi fermentum, vel tempus tellus posuere. Proin vitae pharetra nulla, nec ornare ex. Nulla consequat, augue a accumsan euismod, turpis leo ornare ligula, a pulvinar enim dolor ut augue. Quisque volutpat, lectus a varius mollis, nisl eros feugiat sem, at egestas lacus justo eu elit. Vestibulum scelerisque mauris est, sagittis interdum nunc accumsan sit amet. Maecenas aliquet ex ut lacus ornare, eu sagittis nibh imperdiet. Duis ultrices nisi velit, sed sodales risus sollicitudin et. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Etiam a accumsan augue, vitae pulvinar nulla. Pellentesque euismod sodales magna, nec luctus eros mattis eget. Sed lacinia suscipit lectus, eget consectetur dui pellentesque sed. Nullam nec mattis tellus.
-
-Aliquam erat volutpat. Praesent lobortis massa porttitor eros tincidunt, nec consequat diam pharetra. Duis efficitur non lorem sed mattis. Suspendisse justo nunc, pulvinar eu porttitor at, facilisis id eros. Suspendisse potenti. Cras molestie aliquet orci ut fermentum. In tempus aliquet eros nec suscipit. Suspendisse in mauris ut lectus ultrices blandit sit amet vitae est. Nam magna massa, porttitor ut semper id, feugiat vel quam. Suspendisse dignissim posuere scelerisque. Donec scelerisque lorem efficitur suscipit suscipit. Nunc luctus ligula et scelerisque lacinia.
-
-Suspendisse potenti. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed ultrices, sem in venenatis scelerisque, tellus ipsum porttitor urna, et iaculis lectus odio ac nisi. Integer luctus dui urna, at sollicitudin elit dapibus eu. Praesent nibh ante, porttitor a ante in, ullamcorper pretium felis. Aliquam vel tortor imperdiet, imperdiet lorem et, cursus mi. Proin tempus velit est, ut hendrerit metus gravida sed. Sed nibh sapien, faucibus quis ipsum in, scelerisque lacinia elit. In nec magna eu magna laoreet rhoncus. Donec vitae rutrum mauris. Integer urna felis, consequat at rhoncus vitae, auctor quis elit. Duis a pulvinar sem, nec gravida nisl. Nam non dapibus purus. Praesent vestibulum turpis nec erat porttitor, a scelerisque purus tincidunt.
-
-Nam fringilla leo nisi, nec placerat nisl luctus eget. Aenean malesuada nunc porta sapien sodales convallis. Suspendisse ut massa tempor, ullamcorper mi ut, faucibus turpis. Vivamus at sagittis metus. Donec varius ac mi eget sodales. Nulla feugiat, nulla eu fringilla fringilla, nunc lorem sollicitudin quam, vitae lacinia velit lorem eu orci. Mauris leo urna, pellentesque ac posuere non, pellentesque sit amet quam.
-
-Vestibulum porta diam urna, a aliquet nibh vestibulum et. Proin interdum bibendum nisl sed rhoncus. Sed vel diam hendrerit, faucibus ante et, hendrerit diam. Nunc dolor augue, mattis non dolor vel, luctus sodales neque. Cras malesuada fermentum dolor eu lobortis. Integer dapibus volutpat consequat. Maecenas posuere feugiat nunc. Donec vel mollis elit, volutpat consequat enim. Nulla id nisi finibus orci imperdiet elementum. Phasellus ultrices, elit vitae consequat rutrum, nisl est congue massa, quis condimentum justo nisi vitae turpis. Maecenas aliquet risus sit amet accumsan elementum. Proin non finibus elit, sit amet lobortis augue.
-
-Morbi pretium pulvinar sem vel sollicitudin. Proin imperdiet fringilla leo, non pellentesque lacus gravida nec. Vivamus ullamcorper consectetur ligula eu consectetur. Curabitur sit amet tempus purus. Curabitur quam quam, tincidunt eu tempus vel, volutpat at ipsum. Maecenas lobortis elit ac justo interdum, sit amet mattis ligula mollis. Sed posuere ligula et felis convallis tempor. Aliquam nec mollis velit. Donec varius sit amet erat at imperdiet. Nulla ipsum justo, tempor non sollicitudin gravida, dignissim vel orci. In hac habitasse platea dictumst. Cras cursus tellus id arcu aliquet accumsan. Phasellus ac erat dui.
-
-Duis mollis metus at mi luctus aliquam. Duis varius eget erat ac porttitor. Phasellus lobortis sagittis lacinia. Etiam sagittis eget erat in pulvinar. Phasellus sodales risus nec vulputate accumsan. Cras sit amet pellentesque dui. Praesent consequat felis mi, at vulputate diam convallis a. Donec hendrerit nibh vel justo consequat dictum. In euismod, dui sit amet malesuada suscipit, mauris ex rhoncus eros, sed ornare arcu nunc eu urna. Pellentesque eget erat augue. Integer rutrum mauris sem, nec sodales nulla cursus vel. Vivamus porta, urna vel varius vulputate, nulla arcu malesuada dui, a ultrices magna ante sed nibh.
-
-Morbi ultricies aliquam lorem id bibendum. Donec sit amet nunc vitae massa gravida eleifend hendrerit vel libero. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla vestibulum tempus condimentum. Aliquam dolor ipsum, condimentum in sapien et, tempor iaculis nulla. Aenean non pharetra augue. Maecenas mattis dignissim maximus. Fusce elementum tincidunt massa sit amet lobortis. Phasellus nec pharetra dui, et malesuada ante. Nullam commodo pretium tellus. Praesent sollicitudin, enim eget imperdiet scelerisque, odio felis vulputate dolor, eget auctor neque tellus ac lorem.
-
-In consectetur augue et sapien feugiat varius. Nam tortor mi, consectetur ac felis non, elementum venenatis augue. Suspendisse ut tellus in est sagittis cursus. Quisque faucibus, neque sit amet semper congue, nibh augue finibus odio, vitae interdum dolor arcu eget arcu. Curabitur dictum risus massa, non tincidunt urna molestie non. Maecenas eu quam purus. Donec vulputate, dui eu accumsan blandit, mauris tortor tristique mi, sed blandit leo quam id quam. Ut venenatis sagittis malesuada. Integer non auctor orci. Duis consectetur massa felis. Fusce euismod est sit amet bibendum finibus. Vestibulum dolor ex, tempor at elit in, iaculis cursus dui. Nunc sed neque ac risus rutrum tempus sit amet at ante. In hac habitasse platea dictumst.
-
-Donec rutrum, velit nec viverra tincidunt, est velit viverra neque, quis auctor leo ex at lectus. Morbi eget purus nisi. Aliquam lacus dui, interdum vitae elit at, venenatis dignissim est. Duis ac mollis lorem. Vivamus a vestibulum quam. Maecenas non metus dolor. Praesent tortor nunc, tristique at nisl molestie, vulputate eleifend diam. Integer ultrices lacus odio, vel imperdiet enim accumsan id. Sed ligula tortor, interdum eu velit eget, pharetra pulvinar magna. Sed non lacus in eros tincidunt sagittis ac vel justo. Donec vitae leo sagittis, accumsan ante sit amet, accumsan odio. Ut volutpat ultricies tortor. Vestibulum tempus purus et est tristique sagittis quis vitae turpis.
-
-Nam iaculis neque lacus, eget euismod turpis blandit eget. In hac habitasse platea dictumst. Phasellus justo neque, scelerisque sit amet risus ut, pretium commodo nisl. Phasellus auctor sapien sed ex bibendum fermentum. Proin maximus odio a ante ornare, a feugiat lorem egestas. Etiam efficitur tortor a ante tincidunt interdum. Nullam non est ac massa congue efficitur sit amet nec eros. Nullam at ipsum vel mauris tincidunt efficitur. Duis pulvinar nisl elit, id auctor risus laoreet ac. Sed nunc mauris, tristique id leo ut, condimentum congue nunc. Sed ultricies, mauris et convallis faucibus, justo ex faucibus est, at lobortis purus justo non arcu. Integer vel facilisis elit, dapibus imperdiet mauris.
-
-Pellentesque non mattis turpis, eget bibendum velit. Fusce sollicitudin ante ac tincidunt rhoncus. Praesent porta scelerisque consequat. Donec eleifend faucibus sollicitudin. Quisque vitae purus eget tortor tempor ultrices. Maecenas mauris diam, semper vitae est non, imperdiet tempor magna. Duis elit lacus, auctor vestibulum enim eget, rhoncus porttitor tortor.
-
-Donec non rhoncus nibh. Cras dapibus justo vitae nunc accumsan, id congue erat egestas. Aenean at ante ante. Duis eleifend imperdiet dREADALL
diff --git a/test/image/mysql.sql b/test/image/mysql.sql
deleted file mode 100644
index 51554b98d..000000000
--- a/test/image/mysql.sql
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright 2018 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-SHOW databases;
-USE mysql;
-
-CREATE TABLE foo (id int);
-INSERT INTO foo VALUES(1);
-SELECT * FROM foo;
-DROP TABLE foo;
-
-shutdown;
diff --git a/test/image/ruby.rb b/test/image/ruby.rb
deleted file mode 100644
index aced49c6d..000000000
--- a/test/image/ruby.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright 2018 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-require 'sinatra'
-
-set :bind, "0.0.0.0"
-set :port, 8080
-
-get '/' do
- 'Hello World'
-end
-
diff --git a/test/image/ruby.sh b/test/image/ruby.sh
deleted file mode 100644
index ebe8d5b0e..000000000
--- a/test/image/ruby.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-
-# Copyright 2018 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -e
-
-gem install sinatra
-ruby /src/ruby.rb
diff --git a/test/root/BUILD b/test/root/BUILD
deleted file mode 100644
index d5dd9bca2..000000000
--- a/test/root/BUILD
+++ /dev/null
@@ -1,44 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "root",
- srcs = ["root.go"],
- importpath = "gvisor.dev/gvisor/test/root",
-)
-
-go_test(
- name = "root_test",
- size = "small",
- srcs = [
- "cgroup_test.go",
- "chroot_test.go",
- "crictl_test.go",
- "main_test.go",
- "oom_score_adj_test.go",
- ],
- data = [
- "//runsc",
- ],
- embed = [":root"],
- tags = [
- # Requires docker and runsc to be configured before the test runs.
- # Also test only runs as root.
- "manual",
- "local",
- ],
- visibility = ["//:sandbox"],
- deps = [
- "//runsc/boot",
- "//runsc/cgroup",
- "//runsc/container",
- "//runsc/criutil",
- "//runsc/dockerutil",
- "//runsc/specutils",
- "//runsc/testutil",
- "//test/root/testdata",
- "@com_github_opencontainers_runtime-spec//specs-go:go_default_library",
- "@com_github_syndtr_gocapability//capability:go_default_library",
- ],
-)
diff --git a/test/root/cgroup_test.go b/test/root/cgroup_test.go
deleted file mode 100644
index 76f1e4f2a..000000000
--- a/test/root/cgroup_test.go
+++ /dev/null
@@ -1,232 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package root
-
-import (
- "bufio"
- "fmt"
- "io/ioutil"
- "os"
- "os/exec"
- "path/filepath"
- "strconv"
- "strings"
- "testing"
-
- "gvisor.dev/gvisor/runsc/cgroup"
- "gvisor.dev/gvisor/runsc/dockerutil"
- "gvisor.dev/gvisor/runsc/testutil"
-)
-
-func verifyPid(pid int, path string) error {
- f, err := os.Open(path)
- if err != nil {
- return err
- }
- defer f.Close()
-
- var gots []int
- scanner := bufio.NewScanner(f)
- for scanner.Scan() {
- got, err := strconv.Atoi(scanner.Text())
- if err != nil {
- return err
- }
- if got == pid {
- return nil
- }
- gots = append(gots, got)
- }
- if scanner.Err() != nil {
- return scanner.Err()
- }
- return fmt.Errorf("got: %s, want: %d", gots, pid)
-}
-
-// TestCgroup sets cgroup options and checks that cgroup was properly configured.
-func TestCgroup(t *testing.T) {
- if err := dockerutil.Pull("alpine"); err != nil {
- t.Fatal("docker pull failed:", err)
- }
- d := dockerutil.MakeDocker("cgroup-test")
-
- // This is not a comprehensive list of attributes.
- //
- // Note that we are specifically missing cpusets, which fail if specified.
- // In any case, it's unclear if cpusets can be reliably tested here: these
- // are often run on a single core virtual machine, and there is only a single
- // CPU available in our current set, and every container's set.
- attrs := []struct {
- arg string
- ctrl string
- file string
- want string
- skipIfNotFound bool
- }{
- {
- arg: "--cpu-shares=1000",
- ctrl: "cpu",
- file: "cpu.shares",
- want: "1000",
- },
- {
- arg: "--cpu-period=2000",
- ctrl: "cpu",
- file: "cpu.cfs_period_us",
- want: "2000",
- },
- {
- arg: "--cpu-quota=3000",
- ctrl: "cpu",
- file: "cpu.cfs_quota_us",
- want: "3000",
- },
- {
- arg: "--kernel-memory=100MB",
- ctrl: "memory",
- file: "memory.kmem.limit_in_bytes",
- want: "104857600",
- },
- {
- arg: "--memory=1GB",
- ctrl: "memory",
- file: "memory.limit_in_bytes",
- want: "1073741824",
- },
- {
- arg: "--memory-reservation=500MB",
- ctrl: "memory",
- file: "memory.soft_limit_in_bytes",
- want: "524288000",
- },
- {
- arg: "--memory-swap=2GB",
- ctrl: "memory",
- file: "memory.memsw.limit_in_bytes",
- want: "2147483648",
- skipIfNotFound: true, // swap may be disabled on the machine.
- },
- {
- arg: "--memory-swappiness=5",
- ctrl: "memory",
- file: "memory.swappiness",
- want: "5",
- },
- {
- arg: "--blkio-weight=750",
- ctrl: "blkio",
- file: "blkio.weight",
- want: "750",
- },
- }
-
- args := make([]string, 0, len(attrs))
- for _, attr := range attrs {
- args = append(args, attr.arg)
- }
-
- args = append(args, "alpine", "sleep", "10000")
- if err := d.Run(args...); err != nil {
- t.Fatal("docker create failed:", err)
- }
- defer d.CleanUp()
-
- gid, err := d.ID()
- if err != nil {
- t.Fatalf("Docker.ID() failed: %v", err)
- }
- t.Logf("cgroup ID: %s", gid)
-
- // Check list of attributes defined above.
- for _, attr := range attrs {
- path := filepath.Join("/sys/fs/cgroup", attr.ctrl, "docker", gid, attr.file)
- out, err := ioutil.ReadFile(path)
- if err != nil {
- if os.IsNotExist(err) && attr.skipIfNotFound {
- t.Logf("skipped %s/%s", attr.ctrl, attr.file)
- continue
- }
- t.Fatalf("failed to read %q: %v", path, err)
- }
- if got := strings.TrimSpace(string(out)); got != attr.want {
- t.Errorf("arg: %q, cgroup attribute %s/%s, got: %q, want: %q", attr.arg, attr.ctrl, attr.file, got, attr.want)
- }
- }
-
- // Check that sandbox is inside cgroup.
- controllers := []string{
- "blkio",
- "cpu",
- "cpuset",
- "memory",
- "net_cls",
- "net_prio",
- "devices",
- "freezer",
- "perf_event",
- "pids",
- "systemd",
- }
- pid, err := d.SandboxPid()
- if err != nil {
- t.Fatalf("SandboxPid: %v", err)
- }
- for _, ctrl := range controllers {
- path := filepath.Join("/sys/fs/cgroup", ctrl, "docker", gid, "cgroup.procs")
- if err := verifyPid(pid, path); err != nil {
- t.Errorf("cgroup control %q processes: %v", ctrl, err)
- }
- }
-}
-
-func TestCgroupParent(t *testing.T) {
- if err := dockerutil.Pull("alpine"); err != nil {
- t.Fatal("docker pull failed:", err)
- }
- d := dockerutil.MakeDocker("cgroup-test")
-
- parent := testutil.RandomName("runsc")
- if err := d.Run("--cgroup-parent", parent, "alpine", "sleep", "10000"); err != nil {
- t.Fatal("docker create failed:", err)
- }
- defer d.CleanUp()
- gid, err := d.ID()
- if err != nil {
- t.Fatalf("Docker.ID() failed: %v", err)
- }
- t.Logf("cgroup ID: %s", gid)
-
- // Check that sandbox is inside cgroup.
- pid, err := d.SandboxPid()
- if err != nil {
- t.Fatalf("SandboxPid: %v", err)
- }
-
- // Finds cgroup for the sandbox's parent process to check that cgroup is
- // created in the right location relative to the parent.
- cmd := fmt.Sprintf("grep PPid: /proc/%d/status | sed 's/PPid:\\s//'", pid)
- ppid, err := exec.Command("bash", "-c", cmd).CombinedOutput()
- if err != nil {
- t.Fatalf("Executing %q: %v", cmd, err)
- }
- cgroups, err := cgroup.LoadPaths(strings.TrimSpace(string(ppid)))
- if err != nil {
- t.Fatalf("cgroup.LoadPath(%s): %v", ppid, err)
- }
- path := filepath.Join("/sys/fs/cgroup/memory", cgroups["memory"], parent, gid, "cgroup.procs")
- if err := verifyPid(pid, path); err != nil {
- t.Errorf("cgroup control %q processes: %v", "memory", err)
- }
-}
diff --git a/test/root/chroot_test.go b/test/root/chroot_test.go
deleted file mode 100644
index be0f63d18..000000000
--- a/test/root/chroot_test.go
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package root is used for tests that requires sysadmin privileges run.
-package root
-
-import (
- "fmt"
- "io/ioutil"
- "os/exec"
- "path/filepath"
- "strconv"
- "strings"
- "testing"
-
- "gvisor.dev/gvisor/runsc/dockerutil"
-)
-
-// TestChroot verifies that the sandbox is chroot'd and that mounts are cleaned
-// up after the sandbox is destroyed.
-func TestChroot(t *testing.T) {
- d := dockerutil.MakeDocker("chroot-test")
- if err := d.Run("alpine", "sleep", "10000"); err != nil {
- t.Fatalf("docker run failed: %v", err)
- }
- defer d.CleanUp()
-
- pid, err := d.SandboxPid()
- if err != nil {
- t.Fatalf("Docker.SandboxPid(): %v", err)
- }
-
- // Check that sandbox is chroot'ed.
- procRoot := filepath.Join("/proc", strconv.Itoa(pid), "root")
- chroot, err := filepath.EvalSymlinks(procRoot)
- if err != nil {
- t.Fatalf("error resolving /proc/<pid>/root symlink: %v", err)
- }
- if chroot != "/" {
- t.Errorf("sandbox is not chroot'd, it should be inside: /, got: %q", chroot)
- }
-
- path, err := filepath.EvalSymlinks(filepath.Join("/proc", strconv.Itoa(pid), "cwd"))
- if err != nil {
- t.Fatalf("error resolving /proc/<pid>/cwd symlink: %v", err)
- }
- if chroot != path {
- t.Errorf("sandbox current dir is wrong, want: %q, got: %q", chroot, path)
- }
-
- fi, err := ioutil.ReadDir(procRoot)
- if err != nil {
- t.Fatalf("error listing %q: %v", chroot, err)
- }
- if want, got := 1, len(fi); want != got {
- t.Fatalf("chroot dir got %d entries, want %d", got, want)
- }
-
- // chroot dir is prepared by runsc and should contains only /proc.
- if fi[0].Name() != "proc" {
- t.Errorf("chroot got children %v, want %v", fi[0].Name(), "proc")
- }
-
- d.CleanUp()
-}
-
-func TestChrootGofer(t *testing.T) {
- d := dockerutil.MakeDocker("chroot-test")
- if err := d.Run("alpine", "sleep", "10000"); err != nil {
- t.Fatalf("docker run failed: %v", err)
- }
- defer d.CleanUp()
-
- // It's tricky to find gofers. Get sandbox PID first, then find parent. From
- // parent get all immediate children, remove the sandbox, and everything else
- // are gofers.
- sandPID, err := d.SandboxPid()
- if err != nil {
- t.Fatalf("Docker.SandboxPid(): %v", err)
- }
-
- // Find sandbox's parent PID.
- cmd := fmt.Sprintf("grep PPid /proc/%d/status | awk '{print $2}'", sandPID)
- parent, err := exec.Command("sh", "-c", cmd).CombinedOutput()
- if err != nil {
- t.Fatalf("failed to fetch runsc (%d) parent PID: %v, out:\n%s", sandPID, err, string(parent))
- }
- parentPID, err := strconv.Atoi(strings.TrimSpace(string(parent)))
- if err != nil {
- t.Fatalf("failed to parse PPID %q: %v", string(parent), err)
- }
-
- // Get all children from parent.
- childrenOut, err := exec.Command("/usr/bin/pgrep", "-P", strconv.Itoa(parentPID)).CombinedOutput()
- if err != nil {
- t.Fatalf("failed to fetch containerd-shim children: %v", err)
- }
- children := strings.Split(strings.TrimSpace(string(childrenOut)), "\n")
-
- // This where the root directory is mapped on the host and that's where the
- // gofer must have chroot'd to.
- root := "/root"
-
- for _, child := range children {
- childPID, err := strconv.Atoi(child)
- if err != nil {
- t.Fatalf("failed to parse child PID %q: %v", child, err)
- }
- if childPID == sandPID {
- // Skip the sandbox, all other immediate children are gofers.
- continue
- }
-
- // Check that gofer is chroot'ed.
- chroot, err := filepath.EvalSymlinks(filepath.Join("/proc", child, "root"))
- if err != nil {
- t.Fatalf("error resolving /proc/<pid>/root symlink: %v", err)
- }
- if root != chroot {
- t.Errorf("gofer chroot is wrong, want: %q, got: %q", root, chroot)
- }
-
- path, err := filepath.EvalSymlinks(filepath.Join("/proc", child, "cwd"))
- if err != nil {
- t.Fatalf("error resolving /proc/<pid>/cwd symlink: %v", err)
- }
- if root != path {
- t.Errorf("gofer current dir is wrong, want: %q, got: %q", root, path)
- }
- }
-}
diff --git a/test/root/crictl_test.go b/test/root/crictl_test.go
deleted file mode 100644
index d597664f5..000000000
--- a/test/root/crictl_test.go
+++ /dev/null
@@ -1,242 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package root
-
-import (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "log"
- "net/http"
- "os"
- "os/exec"
- "path"
- "path/filepath"
- "strings"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/runsc/criutil"
- "gvisor.dev/gvisor/runsc/dockerutil"
- "gvisor.dev/gvisor/runsc/specutils"
- "gvisor.dev/gvisor/runsc/testutil"
- "gvisor.dev/gvisor/test/root/testdata"
-)
-
-// Tests for crictl have to be run as root (rather than in a user namespace)
-// because crictl creates named network namespaces in /var/run/netns/.
-
-// TestCrictlSanity refers to b/112433158.
-func TestCrictlSanity(t *testing.T) {
- // Setup containerd and crictl.
- crictl, cleanup, err := setup(t)
- if err != nil {
- t.Fatalf("failed to setup crictl: %v", err)
- }
- defer cleanup()
- podID, contID, err := crictl.StartPodAndContainer("httpd", testdata.Sandbox, testdata.Httpd)
- if err != nil {
- t.Fatal(err)
- }
-
- // Look for the httpd page.
- if err = httpGet(crictl, podID, "index.html"); err != nil {
- t.Fatalf("failed to get page: %v", err)
- }
-
- // Stop everything.
- if err := crictl.StopPodAndContainer(podID, contID); err != nil {
- t.Fatal(err)
- }
-}
-
-// TestMountPaths refers to b/117635704.
-func TestMountPaths(t *testing.T) {
- // Setup containerd and crictl.
- crictl, cleanup, err := setup(t)
- if err != nil {
- t.Fatalf("failed to setup crictl: %v", err)
- }
- defer cleanup()
- podID, contID, err := crictl.StartPodAndContainer("httpd", testdata.Sandbox, testdata.HttpdMountPaths)
- if err != nil {
- t.Fatal(err)
- }
-
- // Look for the directory available at /test.
- if err = httpGet(crictl, podID, "test"); err != nil {
- t.Fatalf("failed to get page: %v", err)
- }
-
- // Stop everything.
- if err := crictl.StopPodAndContainer(podID, contID); err != nil {
- t.Fatal(err)
- }
-}
-
-// TestMountPaths refers to b/118728671.
-func TestMountOverSymlinks(t *testing.T) {
- // Setup containerd and crictl.
- crictl, cleanup, err := setup(t)
- if err != nil {
- t.Fatalf("failed to setup crictl: %v", err)
- }
- defer cleanup()
- podID, contID, err := crictl.StartPodAndContainer("k8s.gcr.io/busybox", testdata.Sandbox, testdata.MountOverSymlink)
- if err != nil {
- t.Fatal(err)
- }
-
- out, err := crictl.Exec(contID, "readlink", "/etc/resolv.conf")
- if err != nil {
- t.Fatal(err)
- }
- if want := "/tmp/resolv.conf"; !strings.Contains(string(out), want) {
- t.Fatalf("/etc/resolv.conf is not pointing to %q: %q", want, string(out))
- }
-
- etc, err := crictl.Exec(contID, "cat", "/etc/resolv.conf")
- if err != nil {
- t.Fatal(err)
- }
- tmp, err := crictl.Exec(contID, "cat", "/tmp/resolv.conf")
- if err != nil {
- t.Fatal(err)
- }
- if tmp != etc {
- t.Fatalf("file content doesn't match:\n\t/etc/resolv.conf: %s\n\t/tmp/resolv.conf: %s", string(etc), string(tmp))
- }
-
- // Stop everything.
- if err := crictl.StopPodAndContainer(podID, contID); err != nil {
- t.Fatal(err)
- }
-}
-
-// setup sets up before a test. Specifically it:
-// * Creates directories and a socket for containerd to utilize.
-// * Runs containerd and waits for it to reach a "ready" state for testing.
-// * Returns a cleanup function that should be called at the end of the test.
-func setup(t *testing.T) (*criutil.Crictl, func(), error) {
- var cleanups []func()
- cleanupFunc := func() {
- for i := len(cleanups) - 1; i >= 0; i-- {
- cleanups[i]()
- }
- }
- cleanup := specutils.MakeCleanup(cleanupFunc)
- defer cleanup.Clean()
-
- // Create temporary containerd root and state directories, and a socket
- // via which crictl and containerd communicate.
- containerdRoot, err := ioutil.TempDir(testutil.TmpDir(), "containerd-root")
- if err != nil {
- t.Fatalf("failed to create containerd root: %v", err)
- }
- cleanups = append(cleanups, func() { os.RemoveAll(containerdRoot) })
- containerdState, err := ioutil.TempDir(testutil.TmpDir(), "containerd-state")
- if err != nil {
- t.Fatalf("failed to create containerd state: %v", err)
- }
- cleanups = append(cleanups, func() { os.RemoveAll(containerdState) })
- sockAddr := filepath.Join(testutil.TmpDir(), "containerd-test.sock")
-
- // We rewrite a configuration. This is based on the current docker
- // configuration for the runtime under test.
- runtime, err := dockerutil.RuntimePath()
- if err != nil {
- t.Fatalf("error discovering runtime path: %v", err)
- }
- config, err := testutil.WriteTmpFile("containerd-config", testdata.ContainerdConfig(runtime))
- if err != nil {
- t.Fatalf("failed to write containerd config")
- }
- cleanups = append(cleanups, func() { os.RemoveAll(config) })
-
- // Start containerd.
- containerd := exec.Command(getContainerd(),
- "--config", config,
- "--log-level", "debug",
- "--root", containerdRoot,
- "--state", containerdState,
- "--address", sockAddr)
- cleanups = append(cleanups, func() {
- if err := testutil.KillCommand(containerd); err != nil {
- log.Printf("error killing containerd: %v", err)
- }
- })
- containerdStderr, err := containerd.StderrPipe()
- if err != nil {
- t.Fatalf("failed to get containerd stderr: %v", err)
- }
- containerdStdout, err := containerd.StdoutPipe()
- if err != nil {
- t.Fatalf("failed to get containerd stdout: %v", err)
- }
- if err := containerd.Start(); err != nil {
- t.Fatalf("failed running containerd: %v", err)
- }
-
- // Wait for containerd to boot. Then put all containerd output into a
- // buffer to be logged at the end of the test.
- testutil.WaitUntilRead(containerdStderr, "Start streaming server", nil, 10*time.Second)
- stdoutBuf := &bytes.Buffer{}
- stderrBuf := &bytes.Buffer{}
- go func() { io.Copy(stdoutBuf, containerdStdout) }()
- go func() { io.Copy(stderrBuf, containerdStderr) }()
- cleanups = append(cleanups, func() {
- t.Logf("containerd stdout: %s", string(stdoutBuf.Bytes()))
- t.Logf("containerd stderr: %s", string(stderrBuf.Bytes()))
- })
-
- cleanup.Release()
- return criutil.NewCrictl(20*time.Second, sockAddr), cleanupFunc, nil
-}
-
-// httpGet GETs the contents of a file served from a pod on port 80.
-func httpGet(crictl *criutil.Crictl, podID, filePath string) error {
- // Get the IP of the httpd server.
- ip, err := crictl.PodIP(podID)
- if err != nil {
- return fmt.Errorf("failed to get IP from pod %q: %v", podID, err)
- }
-
- // GET the page. We may be waiting for the server to start, so retry
- // with a timeout.
- var resp *http.Response
- cb := func() error {
- r, err := http.Get(fmt.Sprintf("http://%s", path.Join(ip, filePath)))
- resp = r
- return err
- }
- if err := testutil.Poll(cb, 20*time.Second); err != nil {
- return err
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != 200 {
- return fmt.Errorf("bad status returned: %d", resp.StatusCode)
- }
- return nil
-}
-
-func getContainerd() string {
- // Use the local path if it exists, otherwise, use the system one.
- if _, err := os.Stat("/usr/local/bin/containerd"); err == nil {
- return "/usr/local/bin/containerd"
- }
- return "/usr/bin/containerd"
-}
diff --git a/test/root/main_test.go b/test/root/main_test.go
deleted file mode 100644
index d74dec85f..000000000
--- a/test/root/main_test.go
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package root
-
-import (
- "flag"
- "fmt"
- "os"
- "testing"
-
- "github.com/syndtr/gocapability/capability"
- "gvisor.dev/gvisor/runsc/dockerutil"
- "gvisor.dev/gvisor/runsc/specutils"
-)
-
-// TestMain is the main function for root tests. This function checks the
-// supported docker version, required capabilities, and configures the executable
-// path for runsc.
-func TestMain(m *testing.M) {
- flag.Parse()
-
- if !specutils.HasCapabilities(capability.CAP_SYS_ADMIN, capability.CAP_DAC_OVERRIDE) {
- fmt.Println("Test requires sysadmin privileges to run. Try again with sudo.")
- os.Exit(1)
- }
-
- dockerutil.EnsureSupportedDockerVersion()
-
- // Configure exe for tests.
- path, err := dockerutil.RuntimePath()
- if err != nil {
- panic(err.Error())
- }
- specutils.ExePath = path
-
- os.Exit(m.Run())
-}
diff --git a/test/root/oom_score_adj_test.go b/test/root/oom_score_adj_test.go
deleted file mode 100644
index 6cd378a1b..000000000
--- a/test/root/oom_score_adj_test.go
+++ /dev/null
@@ -1,376 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package root
-
-import (
- "fmt"
- "os"
- "testing"
-
- specs "github.com/opencontainers/runtime-spec/specs-go"
- "gvisor.dev/gvisor/runsc/boot"
- "gvisor.dev/gvisor/runsc/container"
- "gvisor.dev/gvisor/runsc/specutils"
- "gvisor.dev/gvisor/runsc/testutil"
-)
-
-var (
- maxOOMScoreAdj = 1000
- highOOMScoreAdj = 500
- lowOOMScoreAdj = -500
- minOOMScoreAdj = -1000
-)
-
-// Tests for oom_score_adj have to be run as root (rather than in a user
-// namespace) because we need to adjust oom_score_adj for PIDs other than our
-// own and test values below 0.
-
-// TestOOMScoreAdjSingle tests that oom_score_adj is set properly in a
-// single container sandbox.
-func TestOOMScoreAdjSingle(t *testing.T) {
- ppid, err := specutils.GetParentPid(os.Getpid())
- if err != nil {
- t.Fatalf("getting parent pid: %v", err)
- }
- parentOOMScoreAdj, err := specutils.GetOOMScoreAdj(ppid)
- if err != nil {
- t.Fatalf("getting parent oom_score_adj: %v", err)
- }
-
- testCases := []struct {
- Name string
-
- // OOMScoreAdj is the oom_score_adj set to the OCI spec. If nil then
- // no value is set.
- OOMScoreAdj *int
- }{
- {
- Name: "max",
- OOMScoreAdj: &maxOOMScoreAdj,
- },
- {
- Name: "high",
- OOMScoreAdj: &highOOMScoreAdj,
- },
- {
- Name: "low",
- OOMScoreAdj: &lowOOMScoreAdj,
- },
- {
- Name: "min",
- OOMScoreAdj: &minOOMScoreAdj,
- },
- {
- Name: "nil",
- OOMScoreAdj: &parentOOMScoreAdj,
- },
- }
-
- for _, testCase := range testCases {
- t.Run(testCase.Name, func(t *testing.T) {
- id := testutil.UniqueContainerID()
- s := testutil.NewSpecWithArgs("sleep", "1000")
- s.Process.OOMScoreAdj = testCase.OOMScoreAdj
-
- conf := testutil.TestConfig()
- containers, cleanup, err := startContainers(conf, []*specs.Spec{s}, []string{id})
- if err != nil {
- t.Fatalf("error starting containers: %v", err)
- }
- defer cleanup()
-
- c := containers[0]
-
- // Verify the gofer's oom_score_adj
- if testCase.OOMScoreAdj != nil {
- goferScore, err := specutils.GetOOMScoreAdj(c.GoferPid)
- if err != nil {
- t.Fatalf("error reading gofer oom_score_adj: %v", err)
- }
- if goferScore != *testCase.OOMScoreAdj {
- t.Errorf("gofer oom_score_adj got: %d, want: %d", goferScore, *testCase.OOMScoreAdj)
- }
-
- // Verify the sandbox's oom_score_adj.
- //
- // The sandbox should be the same for all containers so just use
- // the first one.
- sandboxPid := c.Sandbox.Pid
- sandboxScore, err := specutils.GetOOMScoreAdj(sandboxPid)
- if err != nil {
- t.Fatalf("error reading sandbox oom_score_adj: %v", err)
- }
- if sandboxScore != *testCase.OOMScoreAdj {
- t.Errorf("sandbox oom_score_adj got: %d, want: %d", sandboxScore, *testCase.OOMScoreAdj)
- }
- }
- })
- }
-}
-
-// TestOOMScoreAdjMulti tests that oom_score_adj is set properly in a
-// multi-container sandbox.
-func TestOOMScoreAdjMulti(t *testing.T) {
- ppid, err := specutils.GetParentPid(os.Getpid())
- if err != nil {
- t.Fatalf("getting parent pid: %v", err)
- }
- parentOOMScoreAdj, err := specutils.GetOOMScoreAdj(ppid)
- if err != nil {
- t.Fatalf("getting parent oom_score_adj: %v", err)
- }
-
- testCases := []struct {
- Name string
-
- // OOMScoreAdj is the oom_score_adj set to the OCI spec. If nil then
- // no value is set. One value for each container. The first value is the
- // root container.
- OOMScoreAdj []*int
-
- // Expected is the expected oom_score_adj of the sandbox. If nil, then
- // this value is ignored.
- Expected *int
-
- // Remove is a set of container indexes to remove from the sandbox.
- Remove []int
-
- // ExpectedAfterRemove is the expected oom_score_adj of the sandbox
- // after containers are removed. Ignored if nil.
- ExpectedAfterRemove *int
- }{
- // A single container CRI test case. This should not happen in
- // practice as there should be at least one container besides the pause
- // container. However, we include a test case to ensure sane behavior.
- {
- Name: "single",
- OOMScoreAdj: []*int{&highOOMScoreAdj},
- Expected: &parentOOMScoreAdj,
- },
- {
- Name: "multi_no_value",
- OOMScoreAdj: []*int{nil, nil, nil},
- Expected: &parentOOMScoreAdj,
- },
- {
- Name: "multi_non_nil_root",
- OOMScoreAdj: []*int{&minOOMScoreAdj, nil, nil},
- Expected: &parentOOMScoreAdj,
- },
- {
- Name: "multi_value",
- OOMScoreAdj: []*int{&minOOMScoreAdj, &highOOMScoreAdj, &lowOOMScoreAdj},
- // The lowest value excluding the root container is expected.
- Expected: &lowOOMScoreAdj,
- },
- {
- Name: "multi_min_value",
- OOMScoreAdj: []*int{&minOOMScoreAdj, &lowOOMScoreAdj},
- // The lowest value excluding the root container is expected.
- Expected: &lowOOMScoreAdj,
- },
- {
- Name: "multi_max_value",
- OOMScoreAdj: []*int{&minOOMScoreAdj, &maxOOMScoreAdj, &highOOMScoreAdj},
- // The lowest value excluding the root container is expected.
- Expected: &highOOMScoreAdj,
- },
- {
- Name: "remove_adjusted",
- OOMScoreAdj: []*int{&minOOMScoreAdj, &maxOOMScoreAdj, &highOOMScoreAdj},
- // The lowest value excluding the root container is expected.
- Expected: &highOOMScoreAdj,
- // Remove highOOMScoreAdj container.
- Remove: []int{2},
- ExpectedAfterRemove: &maxOOMScoreAdj,
- },
- {
- // This test removes all non-root sandboxes with a specified oomScoreAdj.
- Name: "remove_to_nil",
- OOMScoreAdj: []*int{&minOOMScoreAdj, nil, &lowOOMScoreAdj},
- Expected: &lowOOMScoreAdj,
- // Remove lowOOMScoreAdj container.
- Remove: []int{2},
- // The oom_score_adj expected after remove is that of the parent process.
- ExpectedAfterRemove: &parentOOMScoreAdj,
- },
- {
- Name: "remove_no_effect",
- OOMScoreAdj: []*int{&minOOMScoreAdj, &maxOOMScoreAdj, &highOOMScoreAdj},
- // The lowest value excluding the root container is expected.
- Expected: &highOOMScoreAdj,
- // Remove the maxOOMScoreAdj container.
- Remove: []int{1},
- ExpectedAfterRemove: &highOOMScoreAdj,
- },
- }
-
- for _, testCase := range testCases {
- t.Run(testCase.Name, func(t *testing.T) {
- var cmds [][]string
- var oomScoreAdj []*int
- var toRemove []string
-
- for _, oomScore := range testCase.OOMScoreAdj {
- oomScoreAdj = append(oomScoreAdj, oomScore)
- cmds = append(cmds, []string{"sleep", "100"})
- }
-
- specs, ids := createSpecs(cmds...)
- for i, spec := range specs {
- // Ensure the correct value is set, including no value.
- spec.Process.OOMScoreAdj = oomScoreAdj[i]
-
- for _, j := range testCase.Remove {
- if i == j {
- toRemove = append(toRemove, ids[i])
- }
- }
- }
-
- conf := testutil.TestConfig()
- containers, cleanup, err := startContainers(conf, specs, ids)
- if err != nil {
- t.Fatalf("error starting containers: %v", err)
- }
- defer cleanup()
-
- for i, c := range containers {
- if oomScoreAdj[i] != nil {
- // Verify the gofer's oom_score_adj
- score, err := specutils.GetOOMScoreAdj(c.GoferPid)
- if err != nil {
- t.Fatalf("error reading gofer oom_score_adj: %v", err)
- }
- if score != *oomScoreAdj[i] {
- t.Errorf("gofer oom_score_adj got: %d, want: %d", score, *oomScoreAdj[i])
- }
- }
- }
-
- // Verify the sandbox's oom_score_adj.
- //
- // The sandbox should be the same for all containers so just use
- // the first one.
- sandboxPid := containers[0].Sandbox.Pid
- if testCase.Expected != nil {
- score, err := specutils.GetOOMScoreAdj(sandboxPid)
- if err != nil {
- t.Fatalf("error reading sandbox oom_score_adj: %v", err)
- }
- if score != *testCase.Expected {
- t.Errorf("sandbox oom_score_adj got: %d, want: %d", score, *testCase.Expected)
- }
- }
-
- if len(toRemove) == 0 {
- return
- }
-
- // Remove containers.
- for _, removeID := range toRemove {
- for _, c := range containers {
- if c.ID == removeID {
- c.Destroy()
- }
- }
- }
-
- // Check the new adjusted oom_score_adj.
- if testCase.ExpectedAfterRemove != nil {
- scoreAfterRemove, err := specutils.GetOOMScoreAdj(sandboxPid)
- if err != nil {
- t.Fatalf("error reading sandbox oom_score_adj: %v", err)
- }
- if scoreAfterRemove != *testCase.ExpectedAfterRemove {
- t.Errorf("sandbox oom_score_adj got: %d, want: %d", scoreAfterRemove, *testCase.ExpectedAfterRemove)
- }
- }
- })
- }
-}
-
-func createSpecs(cmds ...[]string) ([]*specs.Spec, []string) {
- var specs []*specs.Spec
- var ids []string
- rootID := testutil.UniqueContainerID()
-
- for i, cmd := range cmds {
- spec := testutil.NewSpecWithArgs(cmd...)
- if i == 0 {
- spec.Annotations = map[string]string{
- specutils.ContainerdContainerTypeAnnotation: specutils.ContainerdContainerTypeSandbox,
- }
- ids = append(ids, rootID)
- } else {
- spec.Annotations = map[string]string{
- specutils.ContainerdContainerTypeAnnotation: specutils.ContainerdContainerTypeContainer,
- specutils.ContainerdSandboxIDAnnotation: rootID,
- }
- ids = append(ids, testutil.UniqueContainerID())
- }
- specs = append(specs, spec)
- }
- return specs, ids
-}
-
-func startContainers(conf *boot.Config, specs []*specs.Spec, ids []string) ([]*container.Container, func(), error) {
- // Setup root dir if one hasn't been provided.
- if len(conf.RootDir) == 0 {
- rootDir, err := testutil.SetupRootDir()
- if err != nil {
- return nil, nil, fmt.Errorf("error creating root dir: %v", err)
- }
- conf.RootDir = rootDir
- }
-
- var containers []*container.Container
- var bundles []string
- cleanup := func() {
- for _, c := range containers {
- c.Destroy()
- }
- for _, b := range bundles {
- os.RemoveAll(b)
- }
- os.RemoveAll(conf.RootDir)
- }
- for i, spec := range specs {
- bundleDir, err := testutil.SetupBundleDir(spec)
- if err != nil {
- cleanup()
- return nil, nil, fmt.Errorf("error setting up container: %v", err)
- }
- bundles = append(bundles, bundleDir)
-
- args := container.Args{
- ID: ids[i],
- Spec: spec,
- BundleDir: bundleDir,
- }
- cont, err := container.New(conf, args)
- if err != nil {
- cleanup()
- return nil, nil, fmt.Errorf("error creating container: %v", err)
- }
- containers = append(containers, cont)
-
- if err := cont.Start(conf); err != nil {
- cleanup()
- return nil, nil, fmt.Errorf("error starting container: %v", err)
- }
- }
- return containers, cleanup, nil
-}
diff --git a/test/root/root.go b/test/root/root.go
deleted file mode 100644
index 0f1d29faf..000000000
--- a/test/root/root.go
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package root is used for tests that requires sysadmin privileges run. First,
-// follow the setup instruction in runsc/test/README.md. You should also have
-// docker, containerd, and crictl installed. To run these tests from the
-// project root directory:
-//
-// ./scripts/root_tests.sh
-package root
diff --git a/test/root/testdata/BUILD b/test/root/testdata/BUILD
deleted file mode 100644
index 14c19ef1e..000000000
--- a/test/root/testdata/BUILD
+++ /dev/null
@@ -1,18 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "testdata",
- srcs = [
- "busybox.go",
- "containerd_config.go",
- "httpd.go",
- "httpd_mount_paths.go",
- "sandbox.go",
- ],
- importpath = "gvisor.dev/gvisor/test/root/testdata",
- visibility = [
- "//visibility:public",
- ],
-)
diff --git a/test/root/testdata/busybox.go b/test/root/testdata/busybox.go
deleted file mode 100644
index e4dbd2843..000000000
--- a/test/root/testdata/busybox.go
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package testdata
-
-// MountOverSymlink is a JSON config for a container that /etc/resolv.conf is a
-// symlink to /tmp/resolv.conf.
-var MountOverSymlink = `
-{
- "metadata": {
- "name": "busybox"
- },
- "image": {
- "image": "k8s.gcr.io/busybox"
- },
- "command": [
- "sleep",
- "1000"
- ]
-}
-`
diff --git a/test/root/testdata/containerd_config.go b/test/root/testdata/containerd_config.go
deleted file mode 100644
index e12f1ec88..000000000
--- a/test/root/testdata/containerd_config.go
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package testdata contains data required for root tests.
-package testdata
-
-import "fmt"
-
-// containerdConfigTemplate is a .toml config for containerd. It contains a
-// formatting verb so the runtime field can be set via fmt.Sprintf.
-const containerdConfigTemplate = `
-disabled_plugins = ["restart"]
-[plugins.linux]
- runtime = "%s"
- runtime_root = "/tmp/test-containerd/runsc"
- shim = "/usr/local/bin/gvisor-containerd-shim"
- shim_debug = true
-
-[plugins.cri.containerd.runtimes.runsc]
- runtime_type = "io.containerd.runtime.v1.linux"
- runtime_engine = "%s"
-`
-
-// ContainerdConfig returns a containerd config file with the specified
-// runtime.
-func ContainerdConfig(runtime string) string {
- return fmt.Sprintf(containerdConfigTemplate, runtime, runtime)
-}
diff --git a/test/root/testdata/httpd.go b/test/root/testdata/httpd.go
deleted file mode 100644
index 45d5e33d4..000000000
--- a/test/root/testdata/httpd.go
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package testdata
-
-// Httpd is a JSON config for an httpd container.
-const Httpd = `
-{
- "metadata": {
- "name": "httpd"
- },
- "image":{
- "image": "httpd"
- },
- "mounts": [
- ],
- "linux": {
- },
- "log_path": "httpd.log"
-}
-`
diff --git a/test/root/testdata/httpd_mount_paths.go b/test/root/testdata/httpd_mount_paths.go
deleted file mode 100644
index ac3f4446a..000000000
--- a/test/root/testdata/httpd_mount_paths.go
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package testdata
-
-// HttpdMountPaths is a JSON config for an httpd container with additional
-// mounts.
-const HttpdMountPaths = `
-{
- "metadata": {
- "name": "httpd"
- },
- "image":{
- "image": "httpd"
- },
- "mounts": [
- {
- "container_path": "/var/run/secrets/kubernetes.io/serviceaccount",
- "host_path": "/var/lib/kubelet/pods/82bae206-cdf5-11e8-b245-8cdcd43ac064/volumes/kubernetes.io~secret/default-token-2rpfx",
- "readonly": true
- },
- {
- "container_path": "/etc/hosts",
- "host_path": "/var/lib/kubelet/pods/82bae206-cdf5-11e8-b245-8cdcd43ac064/etc-hosts",
- "readonly": false
- },
- {
- "container_path": "/dev/termination-log",
- "host_path": "/var/lib/kubelet/pods/82bae206-cdf5-11e8-b245-8cdcd43ac064/containers/httpd/d1709580",
- "readonly": false
- },
- {
- "container_path": "/usr/local/apache2/htdocs/test",
- "host_path": "/var/lib/kubelet/pods/82bae206-cdf5-11e8-b245-8cdcd43ac064",
- "readonly": true
- }
- ],
- "linux": {
- },
- "log_path": "httpd.log"
-}
-`
diff --git a/test/root/testdata/sandbox.go b/test/root/testdata/sandbox.go
deleted file mode 100644
index 0db210370..000000000
--- a/test/root/testdata/sandbox.go
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package testdata
-
-// Sandbox is a default JSON config for a sandbox.
-const Sandbox = `
-{
- "metadata": {
- "name": "default-sandbox",
- "namespace": "default",
- "attempt": 1,
- "uid": "hdishd83djaidwnduwk28bcsb"
- },
- "linux": {
- },
- "log_directory": "/tmp"
-}
-`
diff --git a/test/runtimes/BUILD b/test/runtimes/BUILD
deleted file mode 100644
index 5616a8b7b..000000000
--- a/test/runtimes/BUILD
+++ /dev/null
@@ -1,25 +0,0 @@
-# These packages are used to run language runtime tests inside gVisor sandboxes.
-
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-load("//test/runtimes:build_defs.bzl", "runtime_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "runtimes",
- srcs = ["runtimes.go"],
- importpath = "gvisor.dev/gvisor/test/runtimes",
-)
-
-runtime_test(
- name = "runtimes_test",
- size = "small",
- srcs = ["runtimes_test.go"],
- embed = [":runtimes"],
- tags = [
- # Requires docker and runsc to be configured before the test runs.
- "manual",
- "local",
- ],
- deps = ["//runsc/testutil"],
-)
diff --git a/test/runtimes/README.md b/test/runtimes/README.md
deleted file mode 100644
index 34d3507be..000000000
--- a/test/runtimes/README.md
+++ /dev/null
@@ -1,40 +0,0 @@
-# Runtimes Tests Dockerfiles
-
-The Dockerfiles defined under this path are configured to host the execution of
-the runtimes language tests. Each Dockerfile can support the language indicated
-by its directory.
-
-The following runtimes are currently supported:
-
-- Go 1.12
-- Java 11
-- Node.js 12
-- PHP 7.3
-- Python 3.7
-
-#### Prerequisites:
-
-1) [Install and configure Docker](https://docs.docker.com/install/)
-
-2) Build each Docker container from the runtimes directory:
-
-```bash
-$ docker build -f $LANG/Dockerfile [-t $NAME] .
-```
-
-### Testing:
-
-If the prerequisites have been fulfilled, you can run the tests with the
-following command:
-
-```bash
-$ docker run --rm -it $NAME [FLAG]
-```
-
-Running the command with no flags will cause all the available tests to execute.
-
-Flags can be added for additional functionality:
-
-- --list: Print a list of all available tests
-- --test &lt;name&gt;: Run a single test from the list of available tests
-- --v: Print the language version
diff --git a/test/runtimes/build_defs.bzl b/test/runtimes/build_defs.bzl
deleted file mode 100644
index ac28cc037..000000000
--- a/test/runtimes/build_defs.bzl
+++ /dev/null
@@ -1,19 +0,0 @@
-"""Defines a rule for runsc test targets."""
-
-load("@io_bazel_rules_go//go:def.bzl", _go_test = "go_test")
-
-# runtime_test is a macro that will create targets to run the given test target
-# with different runtime options.
-def runtime_test(**kwargs):
- """Runs the given test target with different runtime options."""
- name = kwargs["name"]
- _go_test(**kwargs)
- kwargs["name"] = name + "_hostnet"
- kwargs["args"] = ["--runtime-type=hostnet"]
- _go_test(**kwargs)
- kwargs["name"] = name + "_kvm"
- kwargs["args"] = ["--runtime-type=kvm"]
- _go_test(**kwargs)
- kwargs["name"] = name + "_overlay"
- kwargs["args"] = ["--runtime-type=overlay"]
- _go_test(**kwargs)
diff --git a/test/runtimes/common/BUILD b/test/runtimes/common/BUILD
deleted file mode 100644
index b4740bb97..000000000
--- a/test/runtimes/common/BUILD
+++ /dev/null
@@ -1,20 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "common",
- srcs = ["common.go"],
- importpath = "gvisor.dev/gvisor/test/runtimes/common",
- visibility = ["//:sandbox"],
-)
-
-go_test(
- name = "common_test",
- size = "small",
- srcs = ["common_test.go"],
- deps = [
- ":common",
- "//runsc/testutil",
- ],
-)
diff --git a/test/runtimes/common/common.go b/test/runtimes/common/common.go
deleted file mode 100644
index 0ff87fa8b..000000000
--- a/test/runtimes/common/common.go
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package common executes functions for proctor binaries.
-package common
-
-import (
- "flag"
- "fmt"
- "os"
- "path/filepath"
- "regexp"
-)
-
-var (
- list = flag.Bool("list", false, "list all available tests")
- test = flag.String("test", "", "run a single test from the list of available tests")
- version = flag.Bool("v", false, "print out the version of node that is installed")
-)
-
-// TestRunner is an interface to be implemented in each proctor binary.
-type TestRunner interface {
- // ListTests returns a string slice of tests available to run.
- ListTests() ([]string, error)
-
- // RunTest runs a single test.
- RunTest(test string) error
-}
-
-// LaunchFunc parses flags passed by a proctor binary and calls the requested behavior.
-func LaunchFunc(tr TestRunner) error {
- flag.Parse()
-
- if *list && *test != "" {
- flag.PrintDefaults()
- return fmt.Errorf("cannot specify 'list' and 'test' flags simultaneously")
- }
- if *list {
- tests, err := tr.ListTests()
- if err != nil {
- return fmt.Errorf("failed to list tests: %v", err)
- }
- for _, test := range tests {
- fmt.Println(test)
- }
- return nil
- }
- if *version {
- fmt.Println(os.Getenv("LANG_NAME"), "version:", os.Getenv("LANG_VER"), "is installed.")
- return nil
- }
- if *test != "" {
- if err := tr.RunTest(*test); err != nil {
- return fmt.Errorf("test %q failed to run: %v", *test, err)
- }
- return nil
- }
-
- if err := runAllTests(tr); err != nil {
- return fmt.Errorf("error running all tests: %v", err)
- }
- return nil
-}
-
-// Search uses filepath.Walk to perform a search of the disk for test files
-// and returns a string slice of tests.
-func Search(root string, testFilter *regexp.Regexp) ([]string, error) {
- var testSlice []string
-
- err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
- name := filepath.Base(path)
-
- if info.IsDir() || !testFilter.MatchString(name) {
- return nil
- }
-
- relPath, err := filepath.Rel(root, path)
- if err != nil {
- return err
- }
- testSlice = append(testSlice, relPath)
- return nil
- })
-
- if err != nil {
- return nil, fmt.Errorf("walking %q: %v", root, err)
- }
-
- return testSlice, nil
-}
-
-func runAllTests(tr TestRunner) error {
- tests, err := tr.ListTests()
- if err != nil {
- return fmt.Errorf("failed to list tests: %v", err)
- }
- for _, test := range tests {
- if err := tr.RunTest(test); err != nil {
- return fmt.Errorf("test %q failed to run: %v", test, err)
- }
- }
- return nil
-}
diff --git a/test/runtimes/common/common_test.go b/test/runtimes/common/common_test.go
deleted file mode 100644
index 65875b41b..000000000
--- a/test/runtimes/common/common_test.go
+++ /dev/null
@@ -1,128 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package common_test
-
-import (
- "io/ioutil"
- "os"
- "path/filepath"
- "reflect"
- "regexp"
- "strings"
- "testing"
-
- "gvisor.dev/gvisor/runsc/testutil"
- "gvisor.dev/gvisor/test/runtimes/common"
-)
-
-func touch(t *testing.T, name string) {
- t.Helper()
- f, err := os.Create(name)
- if err != nil {
- t.Fatal(err)
- }
- if err := f.Close(); err != nil {
- t.Fatal(err)
- }
-}
-
-func TestSearchEmptyDir(t *testing.T) {
- td, err := ioutil.TempDir(testutil.TmpDir(), "searchtest")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(td)
-
- var want []string
-
- testFilter := regexp.MustCompile(`^test-[^-].+\.tc$`)
- got, err := common.Search(td, testFilter)
- if err != nil {
- t.Errorf("Search error: %v", err)
- }
-
- if !reflect.DeepEqual(got, want) {
- t.Errorf("Found %#v; want %#v", got, want)
- }
-}
-
-func TestSearch(t *testing.T) {
- td, err := ioutil.TempDir(testutil.TmpDir(), "searchtest")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(td)
-
- // Creating various files similar to the test filter regex.
- files := []string{
- "emp/",
- "tee/",
- "test-foo.tc",
- "test-foo.tc",
- "test-bar.tc",
- "test-sam.tc",
- "Test-que.tc",
- "test-brett",
- "test--abc.tc",
- "test---xyz.tc",
- "test-bool.TC",
- "--test-gvs.tc",
- " test-pew.tc",
- "dir/test_baz.tc",
- "dir/testsnap.tc",
- "dir/test-luk.tc",
- "dir/nest/test-ok.tc",
- "dir/dip/diz/goog/test-pack.tc",
- "dir/dip/diz/wobble/thud/test-cas.e",
- "dir/dip/diz/wobble/thud/test-cas.tc",
- }
- want := []string{
- "dir/dip/diz/goog/test-pack.tc",
- "dir/dip/diz/wobble/thud/test-cas.tc",
- "dir/nest/test-ok.tc",
- "dir/test-luk.tc",
- "test-bar.tc",
- "test-foo.tc",
- "test-sam.tc",
- }
-
- for _, item := range files {
- if strings.HasSuffix(item, "/") {
- // This item is a directory, create it.
- if err := os.MkdirAll(filepath.Join(td, item), 0755); err != nil {
- t.Fatal(err)
- }
- } else {
- // This item is a file, create the directory and touch file.
- // Create directory in which file should be created
- fullDirPath := filepath.Join(td, filepath.Dir(item))
- if err := os.MkdirAll(fullDirPath, 0755); err != nil {
- t.Fatal(err)
- }
- // Create file with full path to file.
- touch(t, filepath.Join(td, item))
- }
- }
-
- testFilter := regexp.MustCompile(`^test-[^-].+\.tc$`)
- got, err := common.Search(td, testFilter)
- if err != nil {
- t.Errorf("Search error: %v", err)
- }
-
- if !reflect.DeepEqual(got, want) {
- t.Errorf("Found %#v; want %#v", got, want)
- }
-}
diff --git a/test/runtimes/go/BUILD b/test/runtimes/go/BUILD
deleted file mode 100644
index ce971ee9d..000000000
--- a/test/runtimes/go/BUILD
+++ /dev/null
@@ -1,9 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary")
-
-package(licenses = ["notice"])
-
-go_binary(
- name = "proctor-go",
- srcs = ["proctor-go.go"],
- deps = ["//test/runtimes/common"],
-)
diff --git a/test/runtimes/go/Dockerfile b/test/runtimes/go/Dockerfile
deleted file mode 100644
index 2d3477392..000000000
--- a/test/runtimes/go/Dockerfile
+++ /dev/null
@@ -1,35 +0,0 @@
-FROM ubuntu:bionic
-ENV LANG_VER=1.12.5
-ENV LANG_NAME=Go
-
-RUN apt-get update && apt-get install -y \
- curl \
- gcc \
- git
-
-WORKDIR /root
-
-# Download Go 1.4 to use as a bootstrap for building Go from the source.
-RUN curl -o go1.4.linux-amd64.tar.gz https://dl.google.com/go/go1.4.linux-amd64.tar.gz
-RUN curl -LJO https://github.com/golang/go/archive/go${LANG_VER}.tar.gz
-RUN mkdir bootstr
-RUN tar -C bootstr -xzf go1.4.linux-amd64.tar.gz
-RUN tar -xzf go-go${LANG_VER}.tar.gz
-RUN mv go-go${LANG_VER} go
-
-ENV GOROOT=/root/go
-ENV GOROOT_BOOTSTRAP=/root/bootstr/go
-ENV LANG_DIR=${GOROOT}
-
-WORKDIR ${LANG_DIR}/src
-RUN ./make.bash
-# Pre-compile the tests for faster execution
-RUN ["/root/go/bin/go", "tool", "dist", "test", "-compile-only"]
-
-WORKDIR ${LANG_DIR}
-
-COPY common /root/go/src/gvisor.dev/gvisor/test/runtimes/common/common
-COPY go/proctor-go.go ${LANG_DIR}
-RUN ["/root/go/bin/go", "build", "-o", "/root/go/bin/proctor", "proctor-go.go"]
-
-ENTRYPOINT ["/root/go/bin/proctor"]
diff --git a/test/runtimes/go/proctor-go.go b/test/runtimes/go/proctor-go.go
deleted file mode 100644
index 3eb24576e..000000000
--- a/test/runtimes/go/proctor-go.go
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Binary proctor-go is a utility that facilitates language testing for Go.
-
-// There are two types of Go tests: "Go tool tests" and "Go tests on disk".
-// "Go tool tests" are found and executed using `go tool dist test`.
-// "Go tests on disk" are found in the /test directory and are
-// executed using `go run run.go`.
-package main
-
-import (
- "fmt"
- "log"
- "os"
- "os/exec"
- "path/filepath"
- "regexp"
- "strings"
-
- "gvisor.dev/gvisor/test/runtimes/common"
-)
-
-var (
- dir = os.Getenv("LANG_DIR")
- goBin = filepath.Join(dir, "bin/go")
- testDir = filepath.Join(dir, "test")
- testRegEx = regexp.MustCompile(`^.+\.go$`)
-
- // Directories with .dir contain helper files for tests.
- // Exclude benchmarks and stress tests.
- dirFilter = regexp.MustCompile(`^(bench|stress)\/.+$|^.+\.dir.+$`)
-)
-
-type goRunner struct {
-}
-
-func main() {
- if err := common.LaunchFunc(goRunner{}); err != nil {
- log.Fatalf("Failed to start: %v", err)
- }
-}
-
-func (g goRunner) ListTests() ([]string, error) {
- // Go tool dist test tests.
- args := []string{"tool", "dist", "test", "-list"}
- cmd := exec.Command(filepath.Join(dir, "bin/go"), args...)
- cmd.Stderr = os.Stderr
- out, err := cmd.Output()
- if err != nil {
- return nil, fmt.Errorf("failed to list: %v", err)
- }
- var toolSlice []string
- for _, test := range strings.Split(string(out), "\n") {
- toolSlice = append(toolSlice, test)
- }
-
- // Go tests on disk.
- diskSlice, err := common.Search(testDir, testRegEx)
- if err != nil {
- return nil, err
- }
- // Remove items from /bench/, /stress/ and .dir files
- diskFiltered := diskSlice[:0]
- for _, file := range diskSlice {
- if !dirFilter.MatchString(file) {
- diskFiltered = append(diskFiltered, file)
- }
- }
-
- return append(toolSlice, diskFiltered...), nil
-}
-
-func (g goRunner) RunTest(test string) error {
- // Check if test exists on disk by searching for file of the same name.
- // This will determine whether or not it is a Go test on disk.
- if strings.HasSuffix(test, ".go") {
- // Test has suffix ".go" which indicates a disk test, run it as such.
- cmd := exec.Command(goBin, "run", "run.go", "-v", "--", test)
- cmd.Dir = testDir
- cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
- if err := cmd.Run(); err != nil {
- return fmt.Errorf("failed to run test: %v", err)
- }
- } else {
- // No ".go" suffix, run as a tool test.
- cmd := exec.Command(goBin, "tool", "dist", "test", "-run", test)
- cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
- if err := cmd.Run(); err != nil {
- return fmt.Errorf("failed to run test: %v", err)
- }
- }
- return nil
-}
diff --git a/test/runtimes/java/BUILD b/test/runtimes/java/BUILD
deleted file mode 100644
index 8c39d39ec..000000000
--- a/test/runtimes/java/BUILD
+++ /dev/null
@@ -1,9 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary")
-
-package(licenses = ["notice"])
-
-go_binary(
- name = "proctor-java",
- srcs = ["proctor-java.go"],
- deps = ["//test/runtimes/common"],
-)
diff --git a/test/runtimes/java/Dockerfile b/test/runtimes/java/Dockerfile
deleted file mode 100644
index 1a61d9d8f..000000000
--- a/test/runtimes/java/Dockerfile
+++ /dev/null
@@ -1,36 +0,0 @@
-FROM ubuntu:bionic
-# This hash is associated with a specific JDK release and needed for ensuring
-# the same version is downloaded every time.
-ENV LANG_HASH=76072a077ee1
-ENV LANG_VER=11
-ENV LANG_NAME=Java
-
-RUN apt-get update && apt-get install -y \
- autoconf \
- build-essential \
- curl \
- make \
- openjdk-${LANG_VER}-jdk \
- unzip \
- zip
-
-WORKDIR /root
-RUN curl -o go.tar.gz https://dl.google.com/go/go1.12.6.linux-amd64.tar.gz
-RUN tar -zxf go.tar.gz
-
-# Download the JDK test library.
-RUN set -ex \
- && curl -fsSL --retry 10 -o /tmp/jdktests.tar.gz http://hg.openjdk.java.net/jdk/jdk${LANG_VER}/archive/${LANG_HASH}.tar.gz/test \
- && tar -xzf /tmp/jdktests.tar.gz -C /root \
- && rm -f /tmp/jdktests.tar.gz
-
-RUN curl -o jtreg.tar.gz https://ci.adoptopenjdk.net/view/Dependencies/job/jtreg/lastSuccessfulBuild/artifact/jtreg-4.2.0-tip.tar.gz
-RUN tar -xzf jtreg.tar.gz
-
-ENV LANG_DIR=/root
-
-COPY common /root/go/src/gvisor.dev/gvisor/test/runtimes/common/common
-COPY java/proctor-java.go ${LANG_DIR}
-RUN ["/root/go/bin/go", "build", "-o", "/root/go/bin/proctor", "proctor-java.go"]
-
-ENTRYPOINT ["/root/go/bin/proctor"]
diff --git a/test/runtimes/java/proctor-java.go b/test/runtimes/java/proctor-java.go
deleted file mode 100644
index 7f6a66f4f..000000000
--- a/test/runtimes/java/proctor-java.go
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Binary proctor-java is a utility that facilitates language testing for Java.
-package main
-
-import (
- "fmt"
- "log"
- "os"
- "os/exec"
- "path/filepath"
- "regexp"
- "strings"
-
- "gvisor.dev/gvisor/test/runtimes/common"
-)
-
-var (
- dir = os.Getenv("LANG_DIR")
- hash = os.Getenv("LANG_HASH")
- jtreg = filepath.Join(dir, "jtreg/bin/jtreg")
- exclDirs = regexp.MustCompile(`(^(sun\/security)|(java\/util\/stream)|(java\/time)| )`)
-)
-
-type javaRunner struct {
-}
-
-func main() {
- if err := common.LaunchFunc(javaRunner{}); err != nil {
- log.Fatalf("Failed to start: %v", err)
- }
-}
-
-func (j javaRunner) ListTests() ([]string, error) {
- args := []string{
- "-dir:/root/jdk11-" + hash + "/test/jdk",
- "-ignore:quiet",
- "-a",
- "-listtests",
- ":jdk_core",
- ":jdk_svc",
- ":jdk_sound",
- ":jdk_imageio",
- }
- cmd := exec.Command(jtreg, args...)
- cmd.Stderr = os.Stderr
- out, err := cmd.Output()
- if err != nil {
- return nil, fmt.Errorf("jtreg -listtests : %v", err)
- }
- var testSlice []string
- for _, test := range strings.Split(string(out), "\n") {
- if !exclDirs.MatchString(test) {
- testSlice = append(testSlice, test)
- }
- }
- return testSlice, nil
-}
-
-func (j javaRunner) RunTest(test string) error {
- args := []string{"-noreport", "-dir:/root/jdk11-" + hash + "/test/jdk", test}
- cmd := exec.Command(jtreg, args...)
- cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
- if err := cmd.Run(); err != nil {
- return fmt.Errorf("failed to run: %v", err)
- }
- return nil
-}
diff --git a/test/runtimes/nodejs/BUILD b/test/runtimes/nodejs/BUILD
deleted file mode 100644
index 0594c250b..000000000
--- a/test/runtimes/nodejs/BUILD
+++ /dev/null
@@ -1,9 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary")
-
-package(licenses = ["notice"])
-
-go_binary(
- name = "proctor-nodejs",
- srcs = ["proctor-nodejs.go"],
- deps = ["//test/runtimes/common"],
-)
diff --git a/test/runtimes/nodejs/Dockerfile b/test/runtimes/nodejs/Dockerfile
deleted file mode 100644
index ce2943af8..000000000
--- a/test/runtimes/nodejs/Dockerfile
+++ /dev/null
@@ -1,31 +0,0 @@
-FROM ubuntu:bionic
-ENV LANG_VER=12.4.0
-ENV LANG_NAME=Node
-
-RUN apt-get update && apt-get install -y \
- curl \
- dumb-init \
- g++ \
- make \
- python
-
-WORKDIR /root
-RUN curl -o go.tar.gz https://dl.google.com/go/go1.12.6.linux-amd64.tar.gz
-RUN tar -zxf go.tar.gz
-
-RUN curl -o node-v${LANG_VER}.tar.gz https://nodejs.org/dist/v${LANG_VER}/node-v${LANG_VER}.tar.gz
-RUN tar -zxf node-v${LANG_VER}.tar.gz
-ENV LANG_DIR=/root/node-v${LANG_VER}
-
-WORKDIR ${LANG_DIR}
-RUN ./configure
-RUN make
-RUN make test-build
-
-COPY common /root/go/src/gvisor.dev/gvisor/test/runtimes/common/common
-COPY nodejs/proctor-nodejs.go ${LANG_DIR}
-RUN ["/root/go/bin/go", "build", "-o", "/root/go/bin/proctor", "proctor-nodejs.go"]
-
-# Including dumb-init emulates the Linux "init" process, preventing the failure
-# of tests involving worker processes.
-ENTRYPOINT ["/usr/bin/dumb-init", "/root/go/bin/proctor"]
diff --git a/test/runtimes/nodejs/proctor-nodejs.go b/test/runtimes/nodejs/proctor-nodejs.go
deleted file mode 100644
index 0624f6a0d..000000000
--- a/test/runtimes/nodejs/proctor-nodejs.go
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Binary proctor-nodejs is a utility that facilitates language testing for NodeJS.
-package main
-
-import (
- "fmt"
- "log"
- "os"
- "os/exec"
- "path/filepath"
- "regexp"
-
- "gvisor.dev/gvisor/test/runtimes/common"
-)
-
-var (
- dir = os.Getenv("LANG_DIR")
- testDir = filepath.Join(dir, "test")
- testRegEx = regexp.MustCompile(`^test-[^-].+\.js$`)
-)
-
-type nodejsRunner struct {
-}
-
-func main() {
- if err := common.LaunchFunc(nodejsRunner{}); err != nil {
- log.Fatalf("Failed to start: %v", err)
- }
-}
-
-func (n nodejsRunner) ListTests() ([]string, error) {
- testSlice, err := common.Search(testDir, testRegEx)
- if err != nil {
- return nil, err
- }
- return testSlice, nil
-}
-
-func (n nodejsRunner) RunTest(test string) error {
- args := []string{filepath.Join(dir, "tools", "test.py"), test}
- cmd := exec.Command("/usr/bin/python", args...)
- cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
- if err := cmd.Run(); err != nil {
- return fmt.Errorf("failed to run: %v", err)
- }
- return nil
-}
diff --git a/test/runtimes/php/BUILD b/test/runtimes/php/BUILD
deleted file mode 100644
index 31799b77a..000000000
--- a/test/runtimes/php/BUILD
+++ /dev/null
@@ -1,9 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary")
-
-package(licenses = ["notice"])
-
-go_binary(
- name = "proctor-php",
- srcs = ["proctor-php.go"],
- deps = ["//test/runtimes/common"],
-)
diff --git a/test/runtimes/php/Dockerfile b/test/runtimes/php/Dockerfile
deleted file mode 100644
index d79babe58..000000000
--- a/test/runtimes/php/Dockerfile
+++ /dev/null
@@ -1,31 +0,0 @@
-FROM ubuntu:bionic
-ENV LANG_VER=7.3.6
-ENV LANG_NAME=PHP
-
-RUN apt-get update && apt-get install -y \
- autoconf \
- automake \
- bison \
- build-essential \
- curl \
- libtool \
- libxml2-dev \
- re2c
-
-WORKDIR /root
-RUN curl -o go.tar.gz https://dl.google.com/go/go1.12.6.linux-amd64.tar.gz
-RUN tar -zxf go.tar.gz
-
-RUN curl -o php-${LANG_VER}.tar.gz https://www.php.net/distributions/php-${LANG_VER}.tar.gz
-RUN tar -zxf php-${LANG_VER}.tar.gz
-ENV LANG_DIR=/root/php-${LANG_VER}
-
-WORKDIR ${LANG_DIR}
-RUN ./configure
-RUN make
-
-COPY common /root/go/src/gvisor.dev/gvisor/test/runtimes/common/common
-COPY php/proctor-php.go ${LANG_DIR}
-RUN ["/root/go/bin/go", "build", "-o", "/root/go/bin/proctor", "proctor-php.go"]
-
-ENTRYPOINT ["/root/go/bin/proctor"]
diff --git a/test/runtimes/php/proctor-php.go b/test/runtimes/php/proctor-php.go
deleted file mode 100644
index e6c5fabdf..000000000
--- a/test/runtimes/php/proctor-php.go
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Binary proctor-php is a utility that facilitates language testing for PHP.
-package main
-
-import (
- "fmt"
- "log"
- "os"
- "os/exec"
- "regexp"
-
- "gvisor.dev/gvisor/test/runtimes/common"
-)
-
-var (
- dir = os.Getenv("LANG_DIR")
- testRegEx = regexp.MustCompile(`^.+\.phpt$`)
-)
-
-type phpRunner struct {
-}
-
-func main() {
- if err := common.LaunchFunc(phpRunner{}); err != nil {
- log.Fatalf("Failed to start: %v", err)
- }
-}
-
-func (p phpRunner) ListTests() ([]string, error) {
- testSlice, err := common.Search(dir, testRegEx)
- if err != nil {
- return nil, err
- }
- return testSlice, nil
-}
-
-func (p phpRunner) RunTest(test string) error {
- args := []string{"test", "TESTS=" + test}
- cmd := exec.Command("make", args...)
- cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
- if err := cmd.Run(); err != nil {
- return fmt.Errorf("failed to run: %v", err)
- }
- return nil
-}
diff --git a/test/runtimes/python/BUILD b/test/runtimes/python/BUILD
deleted file mode 100644
index 37fd6a0f2..000000000
--- a/test/runtimes/python/BUILD
+++ /dev/null
@@ -1,9 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary")
-
-package(licenses = ["notice"])
-
-go_binary(
- name = "proctor-python",
- srcs = ["proctor-python.go"],
- deps = ["//test/runtimes/common"],
-)
diff --git a/test/runtimes/python/Dockerfile b/test/runtimes/python/Dockerfile
deleted file mode 100644
index 5ae328890..000000000
--- a/test/runtimes/python/Dockerfile
+++ /dev/null
@@ -1,33 +0,0 @@
-FROM ubuntu:bionic
-ENV LANG_VER=3.7.3
-ENV LANG_NAME=Python
-
-RUN apt-get update && apt-get install -y \
- curl \
- gcc \
- libbz2-dev \
- libffi-dev \
- liblzma-dev \
- libreadline-dev \
- libssl-dev \
- make \
- zlib1g-dev
-
-WORKDIR /root
-RUN curl -o go.tar.gz https://dl.google.com/go/go1.12.6.linux-amd64.tar.gz
-RUN tar -zxf go.tar.gz
-
-# Use flags -LJO to follow the html redirect and download .tar.gz.
-RUN curl -LJO https://github.com/python/cpython/archive/v${LANG_VER}.tar.gz
-RUN tar -zxf cpython-${LANG_VER}.tar.gz
-ENV LANG_DIR=/root/cpython-${LANG_VER}
-
-WORKDIR ${LANG_DIR}
-RUN ./configure --with-pydebug
-RUN make -s -j2
-
-COPY common /root/go/src/gvisor.dev/gvisor/test/runtimes/common/common
-COPY python/proctor-python.go ${LANG_DIR}
-RUN ["/root/go/bin/go", "build", "-o", "/root/go/bin/proctor", "proctor-python.go"]
-
-ENTRYPOINT ["/root/go/bin/proctor"]
diff --git a/test/runtimes/python/proctor-python.go b/test/runtimes/python/proctor-python.go
deleted file mode 100644
index 35e28a7df..000000000
--- a/test/runtimes/python/proctor-python.go
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Binary proctor-python is a utility that facilitates language testing for Pyhton.
-package main
-
-import (
- "fmt"
- "log"
- "os"
- "os/exec"
- "path/filepath"
- "strings"
-
- "gvisor.dev/gvisor/test/runtimes/common"
-)
-
-var (
- dir = os.Getenv("LANG_DIR")
-)
-
-type pythonRunner struct {
-}
-
-func main() {
- if err := common.LaunchFunc(pythonRunner{}); err != nil {
- log.Fatalf("Failed to start: %v", err)
- }
-}
-
-func (p pythonRunner) ListTests() ([]string, error) {
- args := []string{"-m", "test", "--list-tests"}
- cmd := exec.Command(filepath.Join(dir, "python"), args...)
- cmd.Stderr = os.Stderr
- out, err := cmd.Output()
- if err != nil {
- return nil, fmt.Errorf("failed to list: %v", err)
- }
- var toolSlice []string
- for _, test := range strings.Split(string(out), "\n") {
- toolSlice = append(toolSlice, test)
- }
- return toolSlice, nil
-}
-
-func (p pythonRunner) RunTest(test string) error {
- args := []string{"-m", "test", test}
- cmd := exec.Command(filepath.Join(dir, "python"), args...)
- cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
- if err := cmd.Run(); err != nil {
- return fmt.Errorf("failed to run: %v", err)
- }
- return nil
-}
diff --git a/test/runtimes/runtimes.go b/test/runtimes/runtimes.go
deleted file mode 100644
index 2568e07fe..000000000
--- a/test/runtimes/runtimes.go
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package runtimes provides language tests for runsc runtimes.
-// Each test calls docker commands to start up a container for each supported runtime,
-// and tests that its respective language tests are behaving as expected, like
-// connecting to a port or looking at the output. The container is killed and deleted
-// at the end.
-package runtimes
diff --git a/test/runtimes/runtimes_test.go b/test/runtimes/runtimes_test.go
deleted file mode 100644
index 0ff5dda02..000000000
--- a/test/runtimes/runtimes_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package runtimes
-
-import (
- "strings"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/runsc/testutil"
-)
-
-// Wait time for each test to run.
-const timeout = 180 * time.Second
-
-// Helper function to execute the docker container associated with the
-// language passed. Captures the output of the list function and executes
-// each test individually, supplying any errors recieved.
-func testLang(t *testing.T, lang string) {
- t.Helper()
-
- img := "gcr.io/gvisor-presubmit/" + lang
- if err := testutil.Pull(img); err != nil {
- t.Fatalf("docker pull failed: %v", err)
- }
-
- c := testutil.MakeDocker("gvisor-list")
-
- list, err := c.RunFg(img, "--list")
- if err != nil {
- t.Fatalf("docker run failed: %v", err)
- }
- c.CleanUp()
-
- tests := strings.Fields(list)
-
- for _, tc := range tests {
- tc := tc
- t.Run(tc, func(t *testing.T) {
- d := testutil.MakeDocker("gvisor-test")
- if err := d.Run(img, "--test", tc); err != nil {
- t.Fatalf("docker test %q failed to run: %v", tc, err)
- }
- defer d.CleanUp()
-
- status, err := d.Wait(timeout)
- if err != nil {
- t.Fatalf("docker test %q failed to wait: %v", tc, err)
- }
- if status == 0 {
- t.Logf("test %q passed", tc)
- return
- }
- logs, err := d.Logs()
- if err != nil {
- t.Fatalf("docker test %q failed to supply logs: %v", tc, err)
- }
- t.Errorf("test %q failed: %v", tc, logs)
- })
- }
-}
-
-func TestGo(t *testing.T) {
- testLang(t, "go")
-}
-
-func TestJava(t *testing.T) {
- testLang(t, "java")
-}
-
-func TestNodejs(t *testing.T) {
- testLang(t, "nodejs")
-}
-
-func TestPhp(t *testing.T) {
- testLang(t, "php")
-}
-
-func TestPython(t *testing.T) {
- testLang(t, "python")
-}
diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD
deleted file mode 100644
index 0135435ea..000000000
--- a/test/syscalls/BUILD
+++ /dev/null
@@ -1,711 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary")
-load("//test/syscalls:build_defs.bzl", "syscall_test")
-
-package(licenses = ["notice"])
-
-syscall_test(test = "//test/syscalls/linux:32bit_test")
-
-syscall_test(test = "//test/syscalls/linux:accept_bind_stream_test")
-
-syscall_test(
- size = "large",
- shard_count = 10,
- test = "//test/syscalls/linux:accept_bind_test",
-)
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:access_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:affinity_test")
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:aio_test",
-)
-
-syscall_test(
- size = "medium",
- shard_count = 5,
- test = "//test/syscalls/linux:alarm_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:arch_prctl_test")
-
-syscall_test(test = "//test/syscalls/linux:bad_test")
-
-syscall_test(
- size = "large",
- add_overlay = True,
- test = "//test/syscalls/linux:bind_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:brk_test")
-
-syscall_test(test = "//test/syscalls/linux:socket_test")
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:chdir_test",
-)
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:chmod_test",
-)
-
-syscall_test(
- size = "medium",
- add_overlay = True,
- test = "//test/syscalls/linux:chown_test",
- use_tmpfs = True, # chwon tests require gofer to be running as root.
-)
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:chroot_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:clock_getres_test")
-
-syscall_test(
- size = "medium",
- test = "//test/syscalls/linux:clock_gettime_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:clock_nanosleep_test")
-
-syscall_test(test = "//test/syscalls/linux:concurrency_test")
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:creat_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:dev_test")
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:dup_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:epoll_test")
-
-syscall_test(test = "//test/syscalls/linux:eventfd_test")
-
-syscall_test(test = "//test/syscalls/linux:exceptions_test")
-
-syscall_test(
- size = "medium",
- add_overlay = True,
- test = "//test/syscalls/linux:exec_test",
-)
-
-syscall_test(
- size = "medium",
- add_overlay = True,
- test = "//test/syscalls/linux:exec_binary_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:exit_test")
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:fadvise64_test",
-)
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:fallocate_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:fault_test")
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:fchdir_test",
-)
-
-syscall_test(
- size = "medium",
- test = "//test/syscalls/linux:fcntl_test",
-)
-
-syscall_test(
- size = "medium",
- add_overlay = True,
- test = "//test/syscalls/linux:flock_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:fork_test")
-
-syscall_test(test = "//test/syscalls/linux:fpsig_fork_test")
-
-syscall_test(test = "//test/syscalls/linux:fpsig_nested_test")
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:fsync_test",
-)
-
-syscall_test(
- size = "medium",
- shard_count = 5,
- test = "//test/syscalls/linux:futex_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:getcpu_host_test")
-
-syscall_test(test = "//test/syscalls/linux:getcpu_test")
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:getdents_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:getrandom_test")
-
-syscall_test(test = "//test/syscalls/linux:getrusage_test")
-
-syscall_test(
- size = "medium",
- add_overlay = False, # TODO(gvisor.dev/issue/317): enable when fixed.
- test = "//test/syscalls/linux:inotify_test",
-)
-
-syscall_test(
- size = "medium",
- add_overlay = True,
- test = "//test/syscalls/linux:ioctl_test",
-)
-
-syscall_test(
- test = "//test/syscalls/linux:iptables_test",
-)
-
-syscall_test(
- size = "medium",
- shard_count = 5,
- test = "//test/syscalls/linux:itimer_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:kill_test")
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:link_test",
- use_tmpfs = True, # gofer needs CAP_DAC_READ_SEARCH to use AT_EMPTY_PATH with linkat(2)
-)
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:lseek_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:madvise_test")
-
-syscall_test(test = "//test/syscalls/linux:memory_accounting_test")
-
-syscall_test(test = "//test/syscalls/linux:mempolicy_test")
-
-syscall_test(test = "//test/syscalls/linux:mincore_test")
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:mkdir_test",
-)
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:mknod_test",
- use_tmpfs = True, # mknod is not supported over gofer.
-)
-
-syscall_test(
- size = "medium",
- shard_count = 5,
- test = "//test/syscalls/linux:mmap_test",
-)
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:mount_test",
-)
-
-syscall_test(
- size = "medium",
- test = "//test/syscalls/linux:mremap_test",
-)
-
-syscall_test(
- size = "medium",
- test = "//test/syscalls/linux:msync_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:munmap_test")
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:open_create_test",
-)
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:open_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:packet_socket_raw_test")
-
-syscall_test(test = "//test/syscalls/linux:packet_socket_test")
-
-syscall_test(test = "//test/syscalls/linux:partial_bad_buffer_test")
-
-syscall_test(test = "//test/syscalls/linux:pause_test")
-
-syscall_test(
- size = "large",
- add_overlay = True,
- shard_count = 5,
- test = "//test/syscalls/linux:pipe_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:poll_test")
-
-syscall_test(
- size = "medium",
- test = "//test/syscalls/linux:ppoll_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:prctl_setuid_test")
-
-syscall_test(test = "//test/syscalls/linux:prctl_test")
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:pread64_test",
-)
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:preadv_test",
-)
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:preadv2_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:priority_test")
-
-syscall_test(
- size = "medium",
- test = "//test/syscalls/linux:proc_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:proc_pid_uid_gid_map_test")
-
-syscall_test(test = "//test/syscalls/linux:proc_net_test")
-
-syscall_test(
- size = "medium",
- test = "//test/syscalls/linux:pselect_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:ptrace_test")
-
-syscall_test(
- size = "medium",
- shard_count = 5,
- test = "//test/syscalls/linux:pty_test",
-)
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:pwritev2_test",
-)
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:pwrite64_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:raw_socket_hdrincl_test")
-
-syscall_test(test = "//test/syscalls/linux:raw_socket_icmp_test")
-
-syscall_test(test = "//test/syscalls/linux:raw_socket_ipv4_test")
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:read_test",
-)
-
-syscall_test(
- size = "medium",
- shard_count = 5,
- test = "//test/syscalls/linux:readv_socket_test",
-)
-
-syscall_test(
- size = "medium",
- add_overlay = True,
- test = "//test/syscalls/linux:readv_test",
-)
-
-syscall_test(
- size = "medium",
- add_overlay = True,
- test = "//test/syscalls/linux:rename_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:rlimits_test")
-
-syscall_test(test = "//test/syscalls/linux:rtsignal_test")
-
-syscall_test(test = "//test/syscalls/linux:sched_test")
-
-syscall_test(test = "//test/syscalls/linux:sched_yield_test")
-
-syscall_test(test = "//test/syscalls/linux:seccomp_test")
-
-syscall_test(test = "//test/syscalls/linux:select_test")
-
-syscall_test(
- shard_count = 20,
- test = "//test/syscalls/linux:semaphore_test",
-)
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:sendfile_socket_test",
-)
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:sendfile_test",
-)
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:splice_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:sigaction_test")
-
-# TODO(b/119826902): Enable once the test passes in runsc.
-# syscall_test(test = "//test/syscalls/linux:sigaltstack_test")
-
-syscall_test(test = "//test/syscalls/linux:sigiret_test")
-
-syscall_test(test = "//test/syscalls/linux:sigprocmask_test")
-
-syscall_test(
- size = "medium",
- test = "//test/syscalls/linux:sigstop_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:sigtimedwait_test")
-
-syscall_test(
- size = "medium",
- test = "//test/syscalls/linux:shm_test",
-)
-
-syscall_test(
- size = "medium",
- test = "//test/syscalls/linux:socket_abstract_non_blocking_test",
-)
-
-syscall_test(
- size = "large",
- shard_count = 10,
- test = "//test/syscalls/linux:socket_abstract_test",
-)
-
-syscall_test(
- size = "medium",
- test = "//test/syscalls/linux:socket_domain_non_blocking_test",
-)
-
-syscall_test(
- size = "large",
- shard_count = 10,
- test = "//test/syscalls/linux:socket_domain_test",
-)
-
-syscall_test(
- size = "medium",
- add_overlay = True,
- test = "//test/syscalls/linux:socket_filesystem_non_blocking_test",
-)
-
-syscall_test(
- size = "large",
- add_overlay = True,
- shard_count = 10,
- test = "//test/syscalls/linux:socket_filesystem_test",
-)
-
-syscall_test(
- size = "large",
- shard_count = 10,
- test = "//test/syscalls/linux:socket_inet_loopback_test",
-)
-
-syscall_test(
- size = "large",
- shard_count = 10,
- test = "//test/syscalls/linux:socket_ip_tcp_generic_loopback_test",
-)
-
-syscall_test(
- size = "medium",
- test = "//test/syscalls/linux:socket_ip_tcp_loopback_non_blocking_test",
-)
-
-syscall_test(
- size = "large",
- shard_count = 10,
- test = "//test/syscalls/linux:socket_ip_tcp_loopback_test",
-)
-
-syscall_test(
- size = "medium",
- shard_count = 10,
- test = "//test/syscalls/linux:socket_ip_tcp_udp_generic_loopback_test",
-)
-
-syscall_test(
- size = "medium",
- test = "//test/syscalls/linux:socket_ip_udp_loopback_non_blocking_test",
-)
-
-syscall_test(
- size = "large",
- shard_count = 10,
- test = "//test/syscalls/linux:socket_ip_udp_loopback_test",
-)
-
-syscall_test(
- size = "medium",
- test = "//test/syscalls/linux:socket_ipv4_udp_unbound_loopback_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:socket_netdevice_test")
-
-syscall_test(test = "//test/syscalls/linux:socket_netlink_route_test")
-
-syscall_test(test = "//test/syscalls/linux:socket_blocking_local_test")
-
-syscall_test(test = "//test/syscalls/linux:socket_blocking_ip_test")
-
-syscall_test(test = "//test/syscalls/linux:socket_non_stream_blocking_local_test")
-
-syscall_test(test = "//test/syscalls/linux:socket_non_stream_blocking_udp_test")
-
-syscall_test(
- size = "large",
- test = "//test/syscalls/linux:socket_stream_blocking_local_test",
-)
-
-syscall_test(
- size = "large",
- test = "//test/syscalls/linux:socket_stream_blocking_tcp_test",
-)
-
-syscall_test(
- size = "medium",
- test = "//test/syscalls/linux:socket_stream_local_test",
-)
-
-syscall_test(
- size = "medium",
- test = "//test/syscalls/linux:socket_stream_nonblock_local_test",
-)
-
-syscall_test(
- # NOTE(b/116636318): Large sendmsg may stall a long time.
- size = "enormous",
- shard_count = 5,
- test = "//test/syscalls/linux:socket_unix_dgram_local_test",
-)
-
-syscall_test(
- size = "medium",
- test = "//test/syscalls/linux:socket_unix_dgram_non_blocking_test",
-)
-
-syscall_test(
- size = "large",
- add_overlay = True,
- shard_count = 10,
- test = "//test/syscalls/linux:socket_unix_pair_test",
-)
-
-syscall_test(
- # NOTE(b/116636318): Large sendmsg may stall a long time.
- size = "enormous",
- shard_count = 5,
- test = "//test/syscalls/linux:socket_unix_seqpacket_local_test",
-)
-
-syscall_test(
- size = "medium",
- test = "//test/syscalls/linux:socket_unix_stream_test",
-)
-
-syscall_test(
- size = "medium",
- test = "//test/syscalls/linux:socket_unix_unbound_abstract_test",
-)
-
-syscall_test(
- size = "medium",
- test = "//test/syscalls/linux:socket_unix_unbound_dgram_test",
-)
-
-syscall_test(
- size = "medium",
- test = "//test/syscalls/linux:socket_unix_unbound_filesystem_test",
-)
-
-syscall_test(
- size = "medium",
- shard_count = 10,
- test = "//test/syscalls/linux:socket_unix_unbound_seqpacket_test",
-)
-
-syscall_test(
- size = "large",
- shard_count = 10,
- test = "//test/syscalls/linux:socket_unix_unbound_stream_test",
-)
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:statfs_test",
-)
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:stat_test",
-)
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:stat_times_test",
-)
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:sticky_test",
-)
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:symlink_test",
-)
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:sync_test",
-)
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:sync_file_range_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:sysinfo_test")
-
-syscall_test(test = "//test/syscalls/linux:syslog_test")
-
-syscall_test(test = "//test/syscalls/linux:sysret_test")
-
-syscall_test(
- size = "medium",
- shard_count = 10,
- test = "//test/syscalls/linux:tcp_socket_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:tgkill_test")
-
-syscall_test(test = "//test/syscalls/linux:timerfd_test")
-
-syscall_test(test = "//test/syscalls/linux:timers_test")
-
-syscall_test(test = "//test/syscalls/linux:time_test")
-
-syscall_test(test = "//test/syscalls/linux:tkill_test")
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:truncate_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:udp_bind_test")
-
-syscall_test(
- size = "medium",
- shard_count = 10,
- test = "//test/syscalls/linux:udp_socket_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:uidgid_test")
-
-syscall_test(test = "//test/syscalls/linux:uname_test")
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:unlink_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:unshare_test")
-
-syscall_test(test = "//test/syscalls/linux:utimes_test")
-
-syscall_test(
- size = "medium",
- test = "//test/syscalls/linux:vdso_clock_gettime_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:vdso_test")
-
-syscall_test(test = "//test/syscalls/linux:vsyscall_test")
-
-syscall_test(test = "//test/syscalls/linux:vfork_test")
-
-syscall_test(
- size = "medium",
- shard_count = 5,
- test = "//test/syscalls/linux:wait_test",
-)
-
-syscall_test(
- add_overlay = True,
- test = "//test/syscalls/linux:write_test",
-)
-
-syscall_test(test = "//test/syscalls/linux:proc_net_unix_test")
-
-syscall_test(test = "//test/syscalls/linux:proc_net_tcp_test")
-
-syscall_test(test = "//test/syscalls/linux:proc_net_udp_test")
-
-go_binary(
- name = "syscall_test_runner",
- testonly = 1,
- srcs = ["syscall_test_runner.go"],
- data = [
- "//runsc",
- ],
- deps = [
- "//pkg/log",
- "//runsc/specutils",
- "//runsc/testutil",
- "//test/syscalls/gtest",
- "@com_github_opencontainers_runtime-spec//specs-go:go_default_library",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
diff --git a/test/syscalls/README.md b/test/syscalls/README.md
deleted file mode 100644
index 9e0991940..000000000
--- a/test/syscalls/README.md
+++ /dev/null
@@ -1,107 +0,0 @@
-# gVisor system call test suite
-
-This is a test suite for Linux system calls. It runs under both gVisor and
-Linux, and ensures compatibility between the two.
-
-When adding support for a new syscall (or syscall argument) to gVisor, a
-corresponding syscall test should be added. It's usually recommended to write
-the test first and make sure that it passes on Linux before making changes to
-gVisor.
-
-This document outlines the general guidelines for tests and specific rules that
-must be followed for new tests.
-
-## Running the tests
-
-Each test file generates three different test targets that run in different
-environments:
-
-* a `native` target that runs directly on the host machine,
-* a `runsc_ptrace` target that runs inside runsc using the ptrace platform, and
-* a `runsc_kvm` target that runs inside runsc using the KVM platform.
-
-For example, the test in `access_test.cc` generates the following targets:
-
-* `//test/syscalls:access_test_native`
-* `//test/syscalls:access_test_runsc_ptrace`
-* `//test/syscalls:access_test_runsc_kvm`
-
-Any of these targets can be run directly via `bazel test`.
-
-```bash
-$ bazel test //test/syscalls:access_test_native
-$ bazel test //test/syscalls:access_test_runsc_ptrace
-$ bazel test //test/syscalls:access_test_runsc_kvm
-```
-
-To run all the tests on a particular platform, you can filter by the platform
-tag:
-
-```bash
-# Run all tests in native environment:
-$ bazel test --test_tag_filters=native //test/syscalls/...
-
-# Run all tests in runsc with ptrace:
-$ bazel test --test_tag_filters=runsc_ptrace //test/syscalls/...
-
-# Run all tests in runsc with kvm:
-$ bazel test --test_tag_filters=runsc_kvm //test/syscalls/...
-```
-
-You can also run all the tests on every platform. (Warning, this may take a
-while to run.)
-
-```bash
-# Run all tests on every platform:
-$ bazel test //test/syscalls/...
-```
-
-## Writing new tests
-
-Whenever we add support for a new syscall, or add support for a new argument or
-option for a syscall, we should always add a new test (perhaps many new tests).
-
-In general, it is best to write the test first and make sure it passes on Linux
-by running the test on the `native` platform on a Linux machine. This ensures
-that the gVisor implementation matches actual Linux behavior. Sometimes man
-pages contain errors, so always check the actual Linux behavior.
-
-gVisor uses the [Google Test][googletest] test framework, with a few custom
-matchers and guidelines, described below.
-
-### Syscall matchers
-
-When testing an individual system call, use the following syscall matchers,
-which will match the value returned by the syscall and the errno.
-
-```cc
-SyscallSucceeds()
-SyscallSucceedsWithValue(...)
-SyscallFails()
-SyscallFailsWithErrno(...)
-```
-
-### Use test utilities (RAII classes)
-
-The test utilties are written as RAII classes. These utilities should be
-preferred over custom test harnesses.
-
-Local class instances should be preferred, wherever possible, over full test
-fixtures.
-
-A test utility should be created when there is more than one test that requires
-that same functionality, otherwise the class should be test local.
-
-## Save/Restore support in tests
-
-gVisor supports save/restore, and our syscall tests are written in a way to
-enable saving/restoring at certain points. Hence, there are calls to
-`MaybeSave`, and certain tests that should not trigger saves are named with
-`NoSave`.
-
-However, the current open-source test runner does not yet support triggering
-save/restore, so these functions and annotations have no effect on the tests. We
-plan on extending the test runner to trigger save/restore. Until then, these
-functions and annotations should be ignored.
-
-[googletest]: https://github.com/abseil/googletest
diff --git a/test/syscalls/build_defs.bzl b/test/syscalls/build_defs.bzl
deleted file mode 100644
index e94ef5602..000000000
--- a/test/syscalls/build_defs.bzl
+++ /dev/null
@@ -1,128 +0,0 @@
-"""Defines a rule for syscall test targets."""
-
-# syscall_test is a macro that will create targets to run the given test target
-# on the host (native) and runsc.
-def syscall_test(
- test,
- shard_count = 5,
- size = "small",
- use_tmpfs = False,
- add_overlay = False,
- tags = None):
- _syscall_test(
- test = test,
- shard_count = shard_count,
- size = size,
- platform = "native",
- use_tmpfs = False,
- tags = tags,
- )
-
- _syscall_test(
- test = test,
- shard_count = shard_count,
- size = size,
- platform = "kvm",
- use_tmpfs = use_tmpfs,
- tags = tags,
- )
-
- _syscall_test(
- test = test,
- shard_count = shard_count,
- size = size,
- platform = "ptrace",
- use_tmpfs = use_tmpfs,
- tags = tags,
- )
-
- if add_overlay:
- _syscall_test(
- test = test,
- shard_count = shard_count,
- size = size,
- platform = "ptrace",
- use_tmpfs = False, # overlay is adding a writable tmpfs on top of root.
- tags = tags,
- overlay = True,
- )
-
- if not use_tmpfs:
- # Also test shared gofer access.
- _syscall_test(
- test = test,
- shard_count = shard_count,
- size = size,
- platform = "ptrace",
- use_tmpfs = use_tmpfs,
- tags = tags,
- file_access = "shared",
- )
-
-def _syscall_test(
- test,
- shard_count,
- size,
- platform,
- use_tmpfs,
- tags,
- file_access = "exclusive",
- overlay = False):
- test_name = test.split(":")[1]
-
- # Prepend "runsc" to non-native platform names.
- full_platform = platform if platform == "native" else "runsc_" + platform
-
- name = test_name + "_" + full_platform
- if file_access == "shared":
- name += "_shared"
- if overlay:
- name += "_overlay"
-
- if tags == None:
- tags = []
-
- # Add the full_platform and file access in a tag to make it easier to run
- # all the tests on a specific flavor. Use --test_tag_filters=ptrace,file_shared.
- tags += [full_platform, "file_" + file_access]
-
- # Add tag to prevent the tests from running in a Bazel sandbox.
- # TODO(b/120560048): Make the tests run without this tag.
- tags.append("no-sandbox")
-
- # TODO(b/112165693): KVM tests are tagged "manual" to until the platform is
- # more stable.
- if platform == "kvm":
- tags += ["manual"]
- tags += ["requires-kvm"]
-
- args = [
- # Arguments are passed directly to syscall_test_runner binary.
- "--test-name=" + test_name,
- "--platform=" + platform,
- "--use-tmpfs=" + str(use_tmpfs),
- "--file-access=" + file_access,
- "--overlay=" + str(overlay),
- ]
-
- sh_test(
- srcs = ["syscall_test_runner.sh"],
- name = name,
- data = [
- ":syscall_test_runner",
- test,
- ],
- args = args,
- size = size,
- tags = tags,
- shard_count = shard_count,
- )
-
-def sh_test(**kwargs):
- """Wraps the standard sh_test."""
- native.sh_test(
- **kwargs
- )
-
-def select_for_linux(for_linux, for_others = []):
- return for_linux
diff --git a/test/syscalls/gtest/BUILD b/test/syscalls/gtest/BUILD
deleted file mode 100644
index 9293f25cb..000000000
--- a/test/syscalls/gtest/BUILD
+++ /dev/null
@@ -1,12 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "gtest",
- srcs = ["gtest.go"],
- importpath = "gvisor.dev/gvisor/test/syscalls/gtest",
- visibility = [
- "//test:__subpackages__",
- ],
-)
diff --git a/test/syscalls/gtest/gtest.go b/test/syscalls/gtest/gtest.go
deleted file mode 100644
index bdec8eb07..000000000
--- a/test/syscalls/gtest/gtest.go
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package gtest contains helpers for running google-test tests from Go.
-package gtest
-
-import (
- "fmt"
- "os/exec"
- "strings"
-)
-
-var (
- // ListTestFlag is the flag that will list tests in gtest binaries.
- ListTestFlag = "--gtest_list_tests"
-
- // FilterTestFlag is the flag that will filter tests in gtest binaries.
- FilterTestFlag = "--gtest_filter"
-)
-
-// TestCase is a single gtest test case.
-type TestCase struct {
- // Suite is the suite for this test.
- Suite string
-
- // Name is the name of this individual test.
- Name string
-}
-
-// FullName returns the name of the test including the suite. It is suitable to
-// pass to "-gtest_filter".
-func (tc TestCase) FullName() string {
- return fmt.Sprintf("%s.%s", tc.Suite, tc.Name)
-}
-
-// ParseTestCases calls a gtest test binary to list its test and returns a
-// slice with the name and suite of each test.
-func ParseTestCases(testBin string, extraArgs ...string) ([]TestCase, error) {
- args := append([]string{ListTestFlag}, extraArgs...)
- cmd := exec.Command(testBin, args...)
- out, err := cmd.Output()
- if err != nil {
- exitErr, ok := err.(*exec.ExitError)
- if !ok {
- return nil, fmt.Errorf("could not enumerate gtest tests: %v", err)
- }
- return nil, fmt.Errorf("could not enumerate gtest tests: %v\nstderr:\n%s", err, exitErr.Stderr)
- }
-
- var t []TestCase
- var suite string
- for _, line := range strings.Split(string(out), "\n") {
- // Strip comments.
- line = strings.Split(line, "#")[0]
-
- // New suite?
- if !strings.HasPrefix(line, " ") {
- suite = strings.TrimSuffix(strings.TrimSpace(line), ".")
- continue
- }
-
- // Individual test.
- name := strings.TrimSpace(line)
-
- // Do we have a suite yet?
- if suite == "" {
- return nil, fmt.Errorf("test without a suite: %v", name)
- }
-
- // Add this individual test.
- t = append(t, TestCase{
- Suite: suite,
- Name: name,
- })
-
- }
-
- if len(t) == 0 {
- return nil, fmt.Errorf("no tests parsed from %v", testBin)
- }
- return t, nil
-}
diff --git a/test/syscalls/linux/32bit.cc b/test/syscalls/linux/32bit.cc
deleted file mode 100644
index a7cbee06b..000000000
--- a/test/syscalls/linux/32bit.cc
+++ /dev/null
@@ -1,226 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <string.h>
-#include <sys/mman.h>
-
-#include "test/util/memory_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/test_util.h"
-#include "gtest/gtest.h"
-
-#ifndef __x86_64__
-#error "This test is x86-64 specific."
-#endif
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-constexpr char kInt3 = '\xcc';
-
-constexpr char kInt80[2] = {'\xcd', '\x80'};
-constexpr char kSyscall[2] = {'\x0f', '\x05'};
-constexpr char kSysenter[2] = {'\x0f', '\x34'};
-
-void ExitGroup32(const char instruction[2], int code) {
- const Mapping m = ASSERT_NO_ERRNO_AND_VALUE(
- Mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE | PROT_EXEC,
- MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0));
-
- // Fill with INT 3 in case we execute too far.
- memset(m.ptr(), kInt3, m.len());
-
- memcpy(m.ptr(), instruction, 2);
-
- // We're playing *extremely* fast-and-loose with the various syscall ABIs
- // here, which we can more-or-less get away with since exit_group doesn't
- // return.
- //
- // SYSENTER expects the user stack in (%ebp) and arg6 in 0(%ebp). The kernel
- // will unconditionally dereference %ebp for arg6, so we must pass a valid
- // address or it will return EFAULT.
- //
- // SYSENTER also unconditionally returns to thread_info->sysenter_return which
- // is ostensibly a stub in the 32-bit VDSO. But a 64-bit binary doesn't have
- // the 32-bit VDSO mapped, so sysenter_return will simply be the value
- // inherited from the most recent 32-bit ancestor, or NULL if there is none.
- // As a result, return would not return from SYSENTER.
- asm volatile(
- "movl $252, %%eax\n" // exit_group
- "movl %[code], %%ebx\n" // code
- "movl %%edx, %%ebp\n" // SYSENTER: user stack (use IP as a valid addr)
- "leaq -20(%%rsp), %%rsp\n"
- "movl $0x2b, 16(%%rsp)\n" // SS = CPL3 data segment
- "movl $0,12(%%rsp)\n" // ESP = nullptr (unused)
- "movl $0, 8(%%rsp)\n" // EFLAGS
- "movl $0x23, 4(%%rsp)\n" // CS = CPL3 32-bit code segment
- "movl %%edx, 0(%%rsp)\n" // EIP
- "iretl\n"
- "int $3\n"
- :
- : [code] "m"(code), [ip] "d"(m.ptr())
- : "rax", "rbx", "rsp");
-}
-
-constexpr int kExitCode = 42;
-
-TEST(Syscall32Bit, Int80) {
- switch (GvisorPlatform()) {
- case Platform::kKVM:
- // TODO(b/111805002): 32-bit segments are broken (but not explictly
- // disabled).
- return;
- case Platform::kPtrace:
- // TODO(gvisor.dev/issue/167): The ptrace platform does not have a
- // consistent story here.
- return;
- case Platform::kNative:
- break;
- }
-
- // Upstream Linux. 32-bit syscalls allowed.
- EXPECT_EXIT(ExitGroup32(kInt80, kExitCode), ::testing::ExitedWithCode(42),
- "");
-}
-
-TEST(Syscall32Bit, Sysenter) {
- switch (GvisorPlatform()) {
- case Platform::kKVM:
- // TODO(b/111805002): See above.
- return;
- case Platform::kPtrace:
- // TODO(gvisor.dev/issue/167): See above.
- return;
- case Platform::kNative:
- break;
- }
-
- if (GetCPUVendor() == CPUVendor::kAMD) {
- // SYSENTER is an illegal instruction in compatibility mode on AMD.
- EXPECT_EXIT(ExitGroup32(kSysenter, kExitCode),
- ::testing::KilledBySignal(SIGILL), "");
- return;
- }
-
- // Upstream Linux on !AMD, 32-bit syscalls allowed.
- EXPECT_EXIT(ExitGroup32(kSysenter, kExitCode), ::testing::ExitedWithCode(42),
- "");
-}
-
-TEST(Syscall32Bit, Syscall) {
- switch (GvisorPlatform()) {
- case Platform::kKVM:
- // TODO(b/111805002): See above.
- return;
- case Platform::kPtrace:
- // TODO(gvisor.dev/issue/167): See above.
- return;
- case Platform::kNative:
- break;
- }
-
- if (GetCPUVendor() == CPUVendor::kIntel) {
- // SYSCALL is an illegal instruction in compatibility mode on Intel.
- EXPECT_EXIT(ExitGroup32(kSyscall, kExitCode),
- ::testing::KilledBySignal(SIGILL), "");
- return;
- }
-
- // Upstream Linux on !Intel, 32-bit syscalls allowed.
- EXPECT_EXIT(ExitGroup32(kSyscall, kExitCode), ::testing::ExitedWithCode(42),
- "");
-}
-
-// Far call code called below.
-//
-// Input stack layout:
-//
-// %esp+12 lcall segment
-// %esp+8 lcall address offset
-// %esp+0 return address
-//
-// The lcall will enter compatibility mode and jump to the call address (the
-// address of the lret). The lret will return to 64-bit mode at the retq, which
-// will return to the external caller of this function.
-//
-// Since this enters compatibility mode, it must be mapped in a 32-bit region of
-// address space and have a 32-bit stack pointer.
-constexpr char kFarCall[] = {
- '\x67', '\xff', '\x5c', '\x24', '\x08', // lcall *8(%esp)
- '\xc3', // retq
- '\xcb', // lret
-};
-
-void FarCall32() {
- const Mapping m = ASSERT_NO_ERRNO_AND_VALUE(
- Mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE | PROT_EXEC,
- MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0));
-
- // Fill with INT 3 in case we execute too far.
- memset(m.ptr(), kInt3, m.len());
-
- // 32-bit code.
- memcpy(m.ptr(), kFarCall, sizeof(kFarCall));
-
- // Use the end of the code page as its stack.
- uintptr_t stack = m.endaddr();
-
- uintptr_t lcall = m.addr();
- uintptr_t lret = m.addr() + sizeof(kFarCall) - 1;
-
- // N.B. We must save and restore RSP manually. GCC can do so automatically
- // with an "rsp" clobber, but clang cannot.
- asm volatile(
- // Place the address of lret (%edx) and the 32-bit code segment (0x23) on
- // the 32-bit stack for lcall.
- "subl $0x8, %%ecx\n"
- "movl $0x23, 4(%%ecx)\n"
- "movl %%edx, 0(%%ecx)\n"
-
- // Save the current stack and switch to 32-bit stack.
- "pushq %%rbp\n"
- "movq %%rsp, %%rbp\n"
- "movq %%rcx, %%rsp\n"
-
- // Run the lcall code.
- "callq *%%rbx\n"
-
- // Restore the old stack.
- "leaveq\n"
- : "+c"(stack)
- : "b"(lcall), "d"(lret));
-}
-
-TEST(Call32Bit, Disallowed) {
- switch (GvisorPlatform()) {
- case Platform::kKVM:
- // TODO(b/111805002): See above.
- return;
- case Platform::kPtrace:
- // The ptrace platform cannot prevent switching to compatibility mode.
- ABSL_FALLTHROUGH_INTENDED;
- case Platform::kNative:
- break;
- }
-
- // Shouldn't crash.
- FarCall32();
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD
deleted file mode 100644
index df00d2c14..000000000
--- a/test/syscalls/linux/BUILD
+++ /dev/null
@@ -1,3480 +0,0 @@
-load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
-load("//test/syscalls:build_defs.bzl", "select_for_linux")
-
-package(
- default_visibility = ["//:sandbox"],
- licenses = ["notice"],
-)
-
-cc_binary(
- name = "sigaltstack_check",
- testonly = 1,
- srcs = ["sigaltstack_check.cc"],
- deps = ["//test/util:logging"],
-)
-
-cc_binary(
- name = "exec_assert_closed_workload",
- testonly = 1,
- srcs = ["exec_assert_closed_workload.cc"],
- deps = [
- "@com_google_absl//absl/strings",
- ],
-)
-
-cc_binary(
- name = "exec_basic_workload",
- testonly = 1,
- srcs = [
- "exec.h",
- "exec_basic_workload.cc",
- ],
-)
-
-cc_binary(
- name = "exec_proc_exe_workload",
- testonly = 1,
- srcs = ["exec_proc_exe_workload.cc"],
- deps = [
- "//test/util:fs_util",
- "//test/util:posix_error",
- ],
-)
-
-cc_binary(
- name = "exec_state_workload",
- testonly = 1,
- srcs = ["exec_state_workload.cc"],
- deps = ["@com_google_absl//absl/strings"],
-)
-
-sh_binary(
- name = "exit_script",
- testonly = 1,
- srcs = [
- "exit_script.sh",
- ],
-)
-
-cc_binary(
- name = "priority_execve",
- testonly = 1,
- srcs = [
- "priority_execve.cc",
- ],
-)
-
-cc_library(
- name = "base_poll_test",
- testonly = 1,
- srcs = ["base_poll_test.cc"],
- hdrs = ["base_poll_test.h"],
- deps = [
- "//test/util:logging",
- "//test/util:signal_util",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/memory",
- "@com_google_absl//absl/synchronization",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_library(
- name = "file_base",
- testonly = 1,
- hdrs = ["file_base.h"],
- deps = [
- "//test/util:file_descriptor",
- "//test/util:posix_error",
- "//test/util:temp_path",
- "//test/util:test_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_library(
- name = "socket_netlink_util",
- testonly = 1,
- srcs = ["socket_netlink_util.cc"],
- hdrs = ["socket_netlink_util.h"],
- deps = [
- ":socket_test_util",
- "//test/util:file_descriptor",
- "//test/util:posix_error",
- "@com_google_absl//absl/strings",
- ],
-)
-
-cc_library(
- name = "socket_test_util",
- testonly = 1,
- srcs = [
- "socket_test_util.cc",
- ] + select_for_linux(
- [
- "socket_test_util_impl.cc",
- ],
- ),
- hdrs = ["socket_test_util.h"],
- deps = [
- "@com_google_googletest//:gtest",
- "@com_google_absl//absl/memory",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/strings:str_format",
- "@com_google_absl//absl/time",
- "//test/util:file_descriptor",
- "//test/util:posix_error",
- "//test/util:temp_path",
- "//test/util:test_util",
- "//test/util:thread_util",
- ] + select_for_linux([
- ]),
-)
-
-cc_library(
- name = "temp_umask",
- hdrs = ["temp_umask.h"],
-)
-
-cc_library(
- name = "unix_domain_socket_test_util",
- testonly = 1,
- srcs = ["unix_domain_socket_test_util.cc"],
- hdrs = ["unix_domain_socket_test_util.h"],
- deps = [
- ":socket_test_util",
- "//test/util:test_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_library(
- name = "ip_socket_test_util",
- testonly = 1,
- srcs = ["ip_socket_test_util.cc"],
- hdrs = ["ip_socket_test_util.h"],
- deps = [
- ":socket_test_util",
- "@com_google_absl//absl/strings",
- ],
-)
-
-cc_binary(
- name = "clock_nanosleep_test",
- testonly = 1,
- srcs = ["clock_nanosleep.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:cleanup",
- "//test/util:posix_error",
- "//test/util:signal_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "//test/util:timer_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "32bit_test",
- testonly = 1,
- srcs = ["32bit.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:memory_util",
- "//test/util:posix_error",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "accept_bind_test",
- testonly = 1,
- srcs = ["accept_bind.cc"],
- linkstatic = 1,
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:file_descriptor",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "accept_bind_stream_test",
- testonly = 1,
- srcs = ["accept_bind_stream.cc"],
- linkstatic = 1,
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:file_descriptor",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "access_test",
- testonly = 1,
- srcs = ["access.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:fs_util",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "affinity_test",
- testonly = 1,
- srcs = ["affinity.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:cleanup",
- "//test/util:fs_util",
- "//test/util:posix_error",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "aio_test",
- testonly = 1,
- srcs = [
- "aio.cc",
- "file_base.h",
- ],
- linkstatic = 1,
- deps = [
- # The heapchecker doesn't recognize that io_destroy munmaps.
- "@com_google_googletest//:gtest",
- "@com_google_absl//absl/strings",
- "//test/util:cleanup",
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:memory_util",
- "//test/util:posix_error",
- "//test/util:proc_util",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "alarm_test",
- testonly = 1,
- srcs = ["alarm.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:logging",
- "//test/util:signal_util",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "bad_test",
- testonly = 1,
- srcs = ["bad.cc"],
- linkstatic = 1,
- visibility = [
- "//:sandbox",
- ],
- deps = [
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "bind_test",
- testonly = 1,
- srcs = ["bind.cc"],
- linkstatic = 1,
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "socket_test",
- testonly = 1,
- srcs = ["socket.cc"],
- linkstatic = 1,
- deps = [
- ":socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "brk_test",
- testonly = 1,
- srcs = ["brk.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "chdir_test",
- testonly = 1,
- srcs = ["chdir.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "chmod_test",
- testonly = 1,
- srcs = ["chmod.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "chown_test",
- testonly = 1,
- srcs = ["chown.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:posix_error",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/synchronization",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "sticky_test",
- testonly = 1,
- srcs = ["sticky.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "chroot_test",
- testonly = 1,
- srcs = ["chroot.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:cleanup",
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:mount_util",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "clock_getres_test",
- testonly = 1,
- srcs = ["clock_getres.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "clock_gettime_test",
- testonly = 1,
- srcs = ["clock_gettime.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "concurrency_test",
- testonly = 1,
- srcs = ["concurrency.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "creat_test",
- testonly = 1,
- srcs = ["creat.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:fs_util",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "dev_test",
- testonly = 1,
- srcs = ["dev.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "dup_test",
- testonly = 1,
- srcs = ["dup.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:eventfd_util",
- "//test/util:file_descriptor",
- "//test/util:posix_error",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "epoll_test",
- testonly = 1,
- srcs = ["epoll.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:epoll_util",
- "//test/util:eventfd_util",
- "//test/util:file_descriptor",
- "//test/util:posix_error",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "eventfd_test",
- testonly = 1,
- srcs = ["eventfd.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:epoll_util",
- "//test/util:eventfd_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "exceptions_test",
- testonly = 1,
- srcs = ["exceptions.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:logging",
- "//test/util:signal_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "getcpu_test",
- testonly = 1,
- srcs = ["getcpu.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "getcpu_host_test",
- testonly = 1,
- srcs = ["getcpu.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "getrusage_test",
- testonly = 1,
- srcs = ["getrusage.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:logging",
- "//test/util:memory_util",
- "//test/util:signal_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "exec_binary_test",
- testonly = 1,
- srcs = ["exec_binary.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:cleanup",
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:multiprocess_util",
- "//test/util:posix_error",
- "//test/util:proc_util",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "exec_test",
- testonly = 1,
- srcs = [
- "exec.cc",
- "exec.h",
- ],
- data = [
- ":exec_assert_closed_workload",
- ":exec_basic_workload",
- ":exec_proc_exe_workload",
- ":exec_state_workload",
- ":exit_script",
- ":priority_execve",
- ],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:multiprocess_util",
- "//test/util:posix_error",
- "//test/util:temp_path",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/synchronization",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "exit_test",
- testonly = 1,
- srcs = ["exit.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:time_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "fallocate_test",
- testonly = 1,
- srcs = ["fallocate.cc"],
- linkstatic = 1,
- deps = [
- ":file_base",
- "//test/util:cleanup",
- "//test/util:file_descriptor",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "fault_test",
- testonly = 1,
- srcs = ["fault.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "fchdir_test",
- testonly = 1,
- srcs = ["fchdir.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "fcntl_test",
- testonly = 1,
- srcs = ["fcntl.cc"],
- linkstatic = 1,
- deps = [
- ":socket_test_util",
- "//test/util:cleanup",
- "//test/util:eventfd_util",
- "//test/util:multiprocess_util",
- "//test/util:posix_error",
- "//test/util:temp_path",
- "//test/util:test_util",
- "//test/util:timer_util",
- "@com_google_absl//absl/base:core_headers",
- "@com_google_absl//absl/memory",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "flock_test",
- testonly = 1,
- srcs = [
- "file_base.h",
- "flock.cc",
- ],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:posix_error",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "//test/util:timer_util",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "fork_test",
- testonly = 1,
- srcs = ["fork.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:logging",
- "//test/util:memory_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "fpsig_fork_test",
- testonly = 1,
- srcs = ["fpsig_fork.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:logging",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "fpsig_nested_test",
- testonly = 1,
- srcs = ["fpsig_nested.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "sync_file_range_test",
- testonly = 1,
- srcs = ["sync_file_range.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "fsync_test",
- testonly = 1,
- srcs = ["fsync.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "futex_test",
- testonly = 1,
- srcs = ["futex.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:cleanup",
- "//test/util:file_descriptor",
- "//test/util:memory_util",
- "//test/util:save_util",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "//test/util:time_util",
- "//test/util:timer_util",
- "@com_google_absl//absl/memory",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "getdents_test",
- testonly = 1,
- srcs = ["getdents.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:eventfd_util",
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:posix_error",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "getrandom_test",
- testonly = 1,
- srcs = ["getrandom.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "inotify_test",
- testonly = 1,
- srcs = ["inotify.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:epoll_util",
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/strings:str_format",
- "@com_google_absl//absl/time",
- ],
-)
-
-cc_binary(
- name = "ioctl_test",
- testonly = 1,
- srcs = ["ioctl.cc"],
- linkstatic = 1,
- deps = [
- ":ip_socket_test_util",
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:file_descriptor",
- "//test/util:signal_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_library(
- name = "iptables_types",
- testonly = 1,
- hdrs = [
- "iptables.h",
- ],
-)
-
-cc_binary(
- name = "iptables_test",
- testonly = 1,
- srcs = [
- "iptables.cc",
- ],
- linkstatic = 1,
- deps = [
- ":iptables_types",
- ":socket_test_util",
- "//test/util:capability_util",
- "//test/util:file_descriptor",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "itimer_test",
- testonly = 1,
- srcs = ["itimer.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:logging",
- "//test/util:multiprocess_util",
- "//test/util:posix_error",
- "//test/util:signal_util",
- "//test/util:test_util",
- "//test/util:thread_util",
- "//test/util:timer_util",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "kill_test",
- testonly = 1,
- srcs = ["kill.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:file_descriptor",
- "//test/util:logging",
- "//test/util:signal_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/synchronization",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "link_test",
- testonly = 1,
- srcs = ["link.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:posix_error",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "lseek_test",
- testonly = 1,
- srcs = ["lseek.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "madvise_test",
- testonly = 1,
- srcs = ["madvise.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:logging",
- "//test/util:memory_util",
- "//test/util:multiprocess_util",
- "//test/util:posix_error",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "mempolicy_test",
- testonly = 1,
- srcs = ["mempolicy.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:cleanup",
- "//test/util:memory_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/memory",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "mincore_test",
- testonly = 1,
- srcs = ["mincore.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:memory_util",
- "//test/util:posix_error",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "mkdir_test",
- testonly = 1,
- srcs = ["mkdir.cc"],
- linkstatic = 1,
- deps = [
- ":temp_umask",
- "//test/util:capability_util",
- "//test/util:fs_util",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "mknod_test",
- testonly = 1,
- srcs = ["mknod.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "mlock_test",
- testonly = 1,
- srcs = ["mlock.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:cleanup",
- "//test/util:memory_util",
- "//test/util:multiprocess_util",
- "//test/util:rlimit_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "mmap_test",
- testonly = 1,
- srcs = ["mmap.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:cleanup",
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:memory_util",
- "//test/util:multiprocess_util",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "mount_test",
- testonly = 1,
- srcs = ["mount.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:mount_util",
- "//test/util:multiprocess_util",
- "//test/util:posix_error",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "mremap_test",
- testonly = 1,
- srcs = ["mremap.cc"],
- linkstatic = 1,
- deps = [
- # The heap check fails due to MremapDeathTest
- "@com_google_googletest//:gtest",
- "@com_google_absl//absl/strings",
- "//test/util:file_descriptor",
- "//test/util:logging",
- "//test/util:memory_util",
- "//test/util:multiprocess_util",
- "//test/util:posix_error",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "msync_test",
- testonly = 1,
- srcs = ["msync.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:memory_util",
- "//test/util:posix_error",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "munmap_test",
- testonly = 1,
- srcs = ["munmap.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "open_test",
- testonly = 1,
- srcs = [
- "file_base.h",
- "open.cc",
- ],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:cleanup",
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:posix_error",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/memory",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "open_create_test",
- testonly = 1,
- srcs = ["open_create.cc"],
- linkstatic = 1,
- deps = [
- ":temp_umask",
- "//test/util:capability_util",
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "packet_socket_raw_test",
- testonly = 1,
- srcs = ["packet_socket_raw.cc"],
- linkstatic = 1,
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:capability_util",
- "//test/util:file_descriptor",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/base:core_headers",
- "@com_google_absl//absl/base:endian",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "packet_socket_test",
- testonly = 1,
- srcs = ["packet_socket.cc"],
- linkstatic = 1,
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:capability_util",
- "//test/util:file_descriptor",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/base:core_headers",
- "@com_google_absl//absl/base:endian",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "pty_test",
- testonly = 1,
- srcs = ["pty.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:posix_error",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/base:core_headers",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/synchronization",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "partial_bad_buffer_test",
- testonly = 1,
- srcs = ["partial_bad_buffer.cc"],
- linkstatic = 1,
- deps = [
- "//test/syscalls/linux:socket_test_util",
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:posix_error",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "pause_test",
- testonly = 1,
- srcs = ["pause.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:signal_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/synchronization",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "pipe_test",
- testonly = 1,
- srcs = ["pipe.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:posix_error",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/synchronization",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "poll_test",
- testonly = 1,
- srcs = ["poll.cc"],
- linkstatic = 1,
- deps = [
- ":base_poll_test",
- "//test/util:eventfd_util",
- "//test/util:file_descriptor",
- "//test/util:logging",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/synchronization",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "ppoll_test",
- testonly = 1,
- srcs = ["ppoll.cc"],
- linkstatic = 1,
- deps = [
- ":base_poll_test",
- "//test/util:signal_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "arch_prctl_test",
- testonly = 1,
- srcs = ["arch_prctl.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "prctl_test",
- testonly = 1,
- srcs = ["prctl.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:cleanup",
- "//test/util:multiprocess_util",
- "//test/util:posix_error",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "prctl_setuid_test",
- testonly = 1,
- srcs = ["prctl_setuid.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:logging",
- "//test/util:multiprocess_util",
- "//test/util:posix_error",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "pread64_test",
- testonly = 1,
- srcs = ["pread64.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "preadv_test",
- testonly = 1,
- srcs = ["preadv.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:logging",
- "//test/util:memory_util",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "//test/util:timer_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "preadv2_test",
- testonly = 1,
- srcs = [
- "file_base.h",
- "preadv2.cc",
- ],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:posix_error",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/memory",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "priority_test",
- testonly = 1,
- srcs = ["priority.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:fs_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "proc_test",
- testonly = 1,
- srcs = ["proc.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:cleanup",
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:memory_util",
- "//test/util:posix_error",
- "//test/util:temp_path",
- "//test/util:test_util",
- "//test/util:thread_util",
- "//test/util:time_util",
- "//test/util:timer_util",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/synchronization",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "proc_net_test",
- testonly = 1,
- srcs = ["proc_net.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "proc_pid_smaps_test",
- testonly = 1,
- srcs = ["proc_pid_smaps.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:memory_util",
- "//test/util:posix_error",
- "//test/util:proc_util",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/container:flat_hash_set",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/strings:str_format",
- "@com_google_absl//absl/types:optional",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "proc_pid_uid_gid_map_test",
- testonly = 1,
- srcs = ["proc_pid_uid_gid_map.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:cleanup",
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:logging",
- "//test/util:multiprocess_util",
- "//test/util:posix_error",
- "//test/util:save_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:time_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "pselect_test",
- testonly = 1,
- srcs = ["pselect.cc"],
- linkstatic = 1,
- deps = [
- ":base_poll_test",
- "//test/util:signal_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "ptrace_test",
- testonly = 1,
- srcs = ["ptrace.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:logging",
- "//test/util:multiprocess_util",
- "//test/util:signal_util",
- "//test/util:test_util",
- "//test/util:thread_util",
- "//test/util:time_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "pwrite64_test",
- testonly = 1,
- srcs = ["pwrite64.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "pwritev2_test",
- testonly = 1,
- srcs = [
- "pwritev2.cc",
- ],
- linkstatic = 1,
- deps = [
- ":file_base",
- "//test/util:file_descriptor",
- "//test/util:posix_error",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "raw_socket_hdrincl_test",
- testonly = 1,
- srcs = ["raw_socket_hdrincl.cc"],
- linkstatic = 1,
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:capability_util",
- "//test/util:file_descriptor",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/base:core_headers",
- "@com_google_absl//absl/base:endian",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "raw_socket_ipv4_test",
- testonly = 1,
- srcs = ["raw_socket_ipv4.cc"],
- linkstatic = 1,
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:capability_util",
- "//test/util:file_descriptor",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/base:core_headers",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "raw_socket_icmp_test",
- testonly = 1,
- srcs = ["raw_socket_icmp.cc"],
- linkstatic = 1,
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:capability_util",
- "//test/util:file_descriptor",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/base:core_headers",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "read_test",
- testonly = 1,
- srcs = ["read.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "readv_test",
- testonly = 1,
- srcs = [
- "file_base.h",
- "readv.cc",
- "readv_common.cc",
- "readv_common.h",
- ],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:posix_error",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:timer_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "readv_socket_test",
- testonly = 1,
- srcs = [
- "file_base.h",
- "readv_common.cc",
- "readv_common.h",
- "readv_socket.cc",
- ],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:posix_error",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "rename_test",
- testonly = 1,
- srcs = ["rename.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:cleanup",
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "rlimits_test",
- testonly = 1,
- srcs = ["rlimits.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "rtsignal_test",
- testonly = 1,
- srcs = ["rtsignal.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:cleanup",
- "//test/util:logging",
- "//test/util:posix_error",
- "//test/util:signal_util",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "sched_test",
- testonly = 1,
- srcs = ["sched.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "sched_yield_test",
- testonly = 1,
- srcs = ["sched_yield.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "seccomp_test",
- testonly = 1,
- srcs = ["seccomp.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:logging",
- "//test/util:memory_util",
- "//test/util:multiprocess_util",
- "//test/util:posix_error",
- "//test/util:proc_util",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/base:core_headers",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "select_test",
- testonly = 1,
- srcs = ["select.cc"],
- linkstatic = 1,
- deps = [
- ":base_poll_test",
- "//test/util:file_descriptor",
- "//test/util:multiprocess_util",
- "//test/util:posix_error",
- "//test/util:rlimit_util",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "sendfile_test",
- testonly = 1,
- srcs = ["sendfile.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "sendfile_socket_test",
- testonly = 1,
- srcs = ["sendfile_socket.cc"],
- linkstatic = 1,
- deps = [
- ":socket_test_util",
- "//test/util:file_descriptor",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "splice_test",
- testonly = 1,
- srcs = ["splice.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "sigaction_test",
- testonly = 1,
- srcs = ["sigaction.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "sigaltstack_test",
- testonly = 1,
- srcs = ["sigaltstack.cc"],
- data = [
- ":sigaltstack_check",
- ],
- linkstatic = 1,
- deps = [
- "//test/util:cleanup",
- "//test/util:fs_util",
- "//test/util:multiprocess_util",
- "//test/util:posix_error",
- "//test/util:signal_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "sigiret_test",
- testonly = 1,
- srcs = ["sigiret.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:logging",
- "//test/util:signal_util",
- "//test/util:test_util",
- "//test/util:timer_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "sigprocmask_test",
- testonly = 1,
- srcs = ["sigprocmask.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:signal_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "sigstop_test",
- testonly = 1,
- srcs = ["sigstop.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:multiprocess_util",
- "//test/util:posix_error",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "sigtimedwait_test",
- testonly = 1,
- srcs = ["sigtimedwait.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:logging",
- "//test/util:signal_util",
- "//test/util:test_util",
- "//test/util:thread_util",
- "//test/util:timer_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_library(
- name = "socket_generic_test_cases",
- testonly = 1,
- srcs = [
- "socket_generic.cc",
- ],
- hdrs = [
- "socket_generic.h",
- ],
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_util",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/strings:str_format",
- "@com_google_googletest//:gtest",
- ],
- alwayslink = 1,
-)
-
-cc_library(
- name = "socket_unix_dgram_test_cases",
- testonly = 1,
- srcs = ["socket_unix_dgram.cc"],
- hdrs = ["socket_unix_dgram.h"],
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
- alwayslink = 1,
-)
-
-cc_library(
- name = "socket_unix_seqpacket_test_cases",
- testonly = 1,
- srcs = ["socket_unix_seqpacket.cc"],
- hdrs = ["socket_unix_seqpacket.h"],
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
- alwayslink = 1,
-)
-
-cc_library(
- name = "socket_ip_tcp_generic_test_cases",
- testonly = 1,
- srcs = [
- "socket_ip_tcp_generic.cc",
- ],
- hdrs = [
- "socket_ip_tcp_generic.h",
- ],
- deps = [
- ":socket_test_util",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
- alwayslink = 1,
-)
-
-cc_library(
- name = "socket_non_blocking_test_cases",
- testonly = 1,
- srcs = [
- "socket_non_blocking.cc",
- ],
- hdrs = [
- "socket_non_blocking.h",
- ],
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
- alwayslink = 1,
-)
-
-cc_library(
- name = "socket_unix_non_stream_test_cases",
- testonly = 1,
- srcs = [
- "socket_unix_non_stream.cc",
- ],
- hdrs = [
- "socket_unix_non_stream.h",
- ],
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:memory_util",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
- alwayslink = 1,
-)
-
-cc_library(
- name = "socket_non_stream_test_cases",
- testonly = 1,
- srcs = [
- "socket_non_stream.cc",
- ],
- hdrs = [
- "socket_non_stream.h",
- ],
- deps = [
- ":ip_socket_test_util",
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
- alwayslink = 1,
-)
-
-cc_library(
- name = "socket_ip_udp_test_cases",
- testonly = 1,
- srcs = [
- "socket_ip_udp_generic.cc",
- ],
- hdrs = [
- "socket_ip_udp_generic.h",
- ],
- deps = [
- ":ip_socket_test_util",
- ":socket_test_util",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
- alwayslink = 1,
-)
-
-cc_library(
- name = "socket_ipv4_udp_unbound_test_cases",
- testonly = 1,
- srcs = [
- "socket_ipv4_udp_unbound.cc",
- ],
- hdrs = [
- "socket_ipv4_udp_unbound.h",
- ],
- deps = [
- ":ip_socket_test_util",
- ":socket_test_util",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
- alwayslink = 1,
-)
-
-cc_library(
- name = "socket_ipv4_udp_unbound_external_networking_test_cases",
- testonly = 1,
- srcs = [
- "socket_ipv4_udp_unbound_external_networking.cc",
- ],
- hdrs = [
- "socket_ipv4_udp_unbound_external_networking.h",
- ],
- deps = [
- ":ip_socket_test_util",
- ":socket_test_util",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
- alwayslink = 1,
-)
-
-cc_library(
- name = "socket_ipv4_tcp_unbound_external_networking_test_cases",
- testonly = 1,
- srcs = [
- "socket_ipv4_tcp_unbound_external_networking.cc",
- ],
- hdrs = [
- "socket_ipv4_tcp_unbound_external_networking.h",
- ],
- deps = [
- ":ip_socket_test_util",
- ":socket_test_util",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
- alwayslink = 1,
-)
-
-cc_binary(
- name = "socket_abstract_test",
- testonly = 1,
- srcs = [
- "socket_abstract.cc",
- ],
- linkstatic = 1,
- deps = [
- ":socket_generic_test_cases",
- ":socket_test_util",
- ":socket_unix_cmsg_test_cases",
- ":socket_unix_test_cases",
- ":unix_domain_socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "socket_abstract_non_blocking_test",
- testonly = 1,
- srcs = [
- "socket_unix_abstract_nonblock.cc",
- ],
- linkstatic = 1,
- deps = [
- ":socket_non_blocking_test_cases",
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "socket_unix_dgram_local_test",
- testonly = 1,
- srcs = ["socket_unix_dgram_local.cc"],
- linkstatic = 1,
- deps = [
- ":socket_non_stream_test_cases",
- ":socket_test_util",
- ":socket_unix_dgram_test_cases",
- ":socket_unix_non_stream_test_cases",
- ":unix_domain_socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "socket_unix_dgram_non_blocking_test",
- testonly = 1,
- srcs = ["socket_unix_dgram_non_blocking.cc"],
- linkstatic = 1,
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "socket_unix_seqpacket_local_test",
- testonly = 1,
- srcs = [
- "socket_unix_seqpacket_local.cc",
- ],
- linkstatic = 1,
- deps = [
- ":socket_non_stream_test_cases",
- ":socket_test_util",
- ":socket_unix_non_stream_test_cases",
- ":socket_unix_seqpacket_test_cases",
- ":unix_domain_socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "socket_unix_stream_test",
- testonly = 1,
- srcs = ["socket_unix_stream.cc"],
- linkstatic = 1,
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "socket_ip_tcp_generic_loopback_test",
- testonly = 1,
- srcs = [
- "socket_ip_tcp_generic_loopback.cc",
- ],
- linkstatic = 1,
- deps = [
- ":ip_socket_test_util",
- ":socket_ip_tcp_generic_test_cases",
- ":socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "socket_ip_tcp_udp_generic_loopback_test",
- testonly = 1,
- srcs = [
- "socket_ip_tcp_udp_generic.cc",
- ],
- linkstatic = 1,
- deps = [
- ":ip_socket_test_util",
- ":socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "socket_ip_tcp_loopback_test",
- testonly = 1,
- srcs = [
- "socket_ip_tcp_loopback.cc",
- ],
- linkstatic = 1,
- deps = [
- ":ip_socket_test_util",
- ":socket_generic_test_cases",
- ":socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "socket_ip_tcp_loopback_non_blocking_test",
- testonly = 1,
- srcs = [
- "socket_ip_tcp_loopback_nonblock.cc",
- ],
- linkstatic = 1,
- deps = [
- ":ip_socket_test_util",
- ":socket_non_blocking_test_cases",
- ":socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "socket_ip_udp_loopback_test",
- testonly = 1,
- srcs = [
- "socket_ip_udp_loopback.cc",
- ],
- linkstatic = 1,
- deps = [
- ":ip_socket_test_util",
- ":socket_generic_test_cases",
- ":socket_ip_udp_test_cases",
- ":socket_non_stream_test_cases",
- ":socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "socket_ipv4_udp_unbound_external_networking_test",
- testonly = 1,
- srcs = [
- "socket_ipv4_udp_unbound_external_networking_test.cc",
- ],
- linkstatic = 1,
- deps = [
- ":ip_socket_test_util",
- ":socket_ipv4_udp_unbound_external_networking_test_cases",
- ":socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "socket_ipv4_tcp_unbound_external_networking_test",
- testonly = 1,
- srcs = [
- "socket_ipv4_tcp_unbound_external_networking_test.cc",
- ],
- linkstatic = 1,
- deps = [
- ":ip_socket_test_util",
- ":socket_ipv4_tcp_unbound_external_networking_test_cases",
- ":socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "socket_ip_udp_loopback_non_blocking_test",
- testonly = 1,
- srcs = [
- "socket_ip_udp_loopback_nonblock.cc",
- ],
- linkstatic = 1,
- deps = [
- ":ip_socket_test_util",
- ":socket_non_blocking_test_cases",
- ":socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "socket_ipv4_udp_unbound_loopback_test",
- testonly = 1,
- srcs = [
- "socket_ipv4_udp_unbound_loopback.cc",
- ],
- linkstatic = 1,
- deps = [
- ":ip_socket_test_util",
- ":socket_ipv4_udp_unbound_test_cases",
- ":socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "socket_domain_test",
- testonly = 1,
- srcs = [
- "socket_unix_domain.cc",
- ],
- linkstatic = 1,
- deps = [
- ":socket_generic_test_cases",
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "socket_domain_non_blocking_test",
- testonly = 1,
- srcs = [
- "socket_unix_pair_nonblock.cc",
- ],
- linkstatic = 1,
- deps = [
- ":socket_non_blocking_test_cases",
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "socket_filesystem_test",
- testonly = 1,
- srcs = [
- "socket_filesystem.cc",
- ],
- linkstatic = 1,
- deps = [
- ":socket_generic_test_cases",
- ":socket_test_util",
- ":socket_unix_cmsg_test_cases",
- ":socket_unix_test_cases",
- ":unix_domain_socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "socket_filesystem_non_blocking_test",
- testonly = 1,
- srcs = [
- "socket_unix_filesystem_nonblock.cc",
- ],
- linkstatic = 1,
- deps = [
- ":socket_non_blocking_test_cases",
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "socket_inet_loopback_test",
- testonly = 1,
- srcs = ["socket_inet_loopback.cc"],
- linkstatic = 1,
- deps = [
- ":socket_test_util",
- "//test/util:file_descriptor",
- "//test/util:posix_error",
- "//test/util:save_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/memory",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "socket_netlink_route_test",
- testonly = 1,
- srcs = ["socket_netlink_route.cc"],
- linkstatic = 1,
- deps = [
- ":socket_netlink_util",
- ":socket_test_util",
- "//test/util:cleanup",
- "//test/util:file_descriptor",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/strings:str_format",
- "@com_google_googletest//:gtest",
- ],
-)
-
-# These socket tests are in a library because the test cases are shared
-# across several test build targets.
-cc_library(
- name = "socket_stream_test_cases",
- testonly = 1,
- srcs = [
- "socket_stream.cc",
- ],
- hdrs = [
- "socket_stream.h",
- ],
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
- alwayslink = 1,
-)
-
-cc_library(
- name = "socket_blocking_test_cases",
- testonly = 1,
- srcs = [
- "socket_blocking.cc",
- ],
- hdrs = [
- "socket_blocking.h",
- ],
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_util",
- "//test/util:thread_util",
- "//test/util:timer_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
- alwayslink = 1,
-)
-
-cc_library(
- name = "socket_unix_test_cases",
- testonly = 1,
- srcs = [
- "socket_unix.cc",
- ],
- hdrs = [
- "socket_unix.h",
- ],
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
- alwayslink = 1,
-)
-
-cc_library(
- name = "socket_unix_cmsg_test_cases",
- testonly = 1,
- srcs = [
- "socket_unix_cmsg.cc",
- ],
- hdrs = [
- "socket_unix_cmsg.h",
- ],
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
- alwayslink = 1,
-)
-
-cc_library(
- name = "socket_stream_blocking_test_cases",
- testonly = 1,
- srcs = [
- "socket_stream_blocking.cc",
- ],
- hdrs = [
- "socket_stream_blocking.h",
- ],
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_util",
- "//test/util:thread_util",
- "//test/util:timer_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
- alwayslink = 1,
-)
-
-cc_library(
- name = "socket_stream_nonblocking_test_cases",
- testonly = 1,
- srcs = [
- "socket_stream_nonblock.cc",
- ],
- hdrs = [
- "socket_stream_nonblock.h",
- ],
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
- alwayslink = 1,
-)
-
-cc_library(
- name = "socket_non_stream_blocking_test_cases",
- testonly = 1,
- srcs = [
- "socket_non_stream_blocking.cc",
- ],
- hdrs = [
- "socket_non_stream_blocking.h",
- ],
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_util",
- "//test/util:thread_util",
- "//test/util:timer_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
- alwayslink = 1,
-)
-
-cc_binary(
- name = "socket_stream_local_test",
- testonly = 1,
- srcs = [
- "socket_unix_stream_local.cc",
- ],
- linkstatic = 1,
- deps = [
- ":socket_stream_test_cases",
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "socket_stream_blocking_local_test",
- testonly = 1,
- srcs = [
- "socket_unix_stream_blocking_local.cc",
- ],
- linkstatic = 1,
- deps = [
- ":socket_stream_blocking_test_cases",
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "socket_stream_blocking_tcp_test",
- testonly = 1,
- srcs = [
- "socket_ip_tcp_loopback_blocking.cc",
- ],
- linkstatic = 1,
- deps = [
- ":ip_socket_test_util",
- ":socket_stream_blocking_test_cases",
- ":socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "socket_stream_nonblock_local_test",
- testonly = 1,
- srcs = [
- "socket_unix_stream_nonblock_local.cc",
- ],
- linkstatic = 1,
- deps = [
- ":socket_stream_nonblocking_test_cases",
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
- name = "socket_unix_unbound_dgram_test",
- testonly = 1,
- srcs = ["socket_unix_unbound_dgram.cc"],
- linkstatic = 1,
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "socket_unix_unbound_abstract_test",
- testonly = 1,
- srcs = ["socket_unix_unbound_abstract.cc"],
- linkstatic = 1,
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "socket_unix_unbound_filesystem_test",
- testonly = 1,
- srcs = ["socket_unix_unbound_filesystem.cc"],
- linkstatic = 1,
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "socket_blocking_local_test",
- testonly = 1,
- srcs = [
- "socket_unix_blocking_local.cc",
- ],
- linkstatic = 1,
- deps = [
- ":socket_blocking_test_cases",
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "socket_blocking_ip_test",
- testonly = 1,
- srcs = [
- "socket_ip_loopback_blocking.cc",
- ],
- linkstatic = 1,
- deps = [
- ":ip_socket_test_util",
- ":socket_blocking_test_cases",
- ":socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "socket_non_stream_blocking_local_test",
- testonly = 1,
- srcs = [
- "socket_unix_non_stream_blocking_local.cc",
- ],
- linkstatic = 1,
- deps = [
- ":socket_non_stream_blocking_test_cases",
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "socket_non_stream_blocking_udp_test",
- testonly = 1,
- srcs = [
- "socket_ip_udp_loopback_blocking.cc",
- ],
- linkstatic = 1,
- deps = [
- ":ip_socket_test_util",
- ":socket_non_stream_blocking_test_cases",
- ":socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "socket_unix_pair_test",
- testonly = 1,
- srcs = [
- "socket_unix_pair.cc",
- ],
- linkstatic = 1,
- deps = [
- ":socket_test_util",
- ":socket_unix_cmsg_test_cases",
- ":socket_unix_test_cases",
- ":unix_domain_socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "socket_unix_unbound_seqpacket_test",
- testonly = 1,
- srcs = ["socket_unix_unbound_seqpacket.cc"],
- linkstatic = 1,
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "socket_unix_unbound_stream_test",
- testonly = 1,
- srcs = ["socket_unix_unbound_stream.cc"],
- linkstatic = 1,
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "socket_netdevice_test",
- testonly = 1,
- srcs = ["socket_netdevice.cc"],
- linkstatic = 1,
- deps = [
- ":socket_netlink_util",
- ":socket_test_util",
- "//test/util:file_descriptor",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/base:endian",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "stat_test",
- testonly = 1,
- srcs = [
- "file_base.h",
- "stat.cc",
- ],
- linkstatic = 1,
- deps = [
- "//test/util:cleanup",
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:posix_error",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "stat_times_test",
- testonly = 1,
- srcs = ["stat_times.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "statfs_test",
- testonly = 1,
- srcs = [
- "file_base.h",
- "statfs.cc",
- ],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:posix_error",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "symlink_test",
- testonly = 1,
- srcs = ["symlink.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "sync_test",
- testonly = 1,
- srcs = ["sync.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "sysinfo_test",
- testonly = 1,
- srcs = ["sysinfo.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "syslog_test",
- testonly = 1,
- srcs = ["syslog.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "sysret_test",
- testonly = 1,
- srcs = ["sysret.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:logging",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "tcp_socket_test",
- testonly = 1,
- srcs = ["tcp_socket.cc"],
- linkstatic = 1,
- # FIXME(b/135470853)
- tags = ["flaky"],
- deps = [
- ":socket_test_util",
- "//test/util:file_descriptor",
- "//test/util:posix_error",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "tgkill_test",
- testonly = 1,
- srcs = ["tgkill.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:signal_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "time_test",
- testonly = 1,
- srcs = ["time.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:proc_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "timerfd_test",
- testonly = 1,
- srcs = ["timerfd.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:posix_error",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/time",
- ],
-)
-
-cc_binary(
- name = "timers_test",
- testonly = 1,
- srcs = ["timers.cc"],
- linkstatic = 1,
- # FIXME(b/136599201)
- tags = ["flaky"],
- deps = [
- "//test/util:cleanup",
- "//test/util:logging",
- "//test/util:multiprocess_util",
- "//test/util:posix_error",
- "//test/util:signal_util",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "tkill_test",
- testonly = 1,
- srcs = ["tkill.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:logging",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "truncate_test",
- testonly = 1,
- srcs = ["truncate.cc"],
- linkstatic = 1,
- deps = [
- ":file_base",
- "//test/util:capability_util",
- "//test/util:cleanup",
- "//test/util:file_descriptor",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "udp_socket_test",
- testonly = 1,
- srcs = ["udp_socket.cc"],
- linkstatic = 1,
- deps = [
- ":socket_test_util",
- ":unix_domain_socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/base:core_headers",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "udp_bind_test",
- testonly = 1,
- srcs = ["udp_bind.cc"],
- linkstatic = 1,
- deps = [
- ":socket_test_util",
- "//test/util:file_descriptor",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "uidgid_test",
- testonly = 1,
- srcs = ["uidgid.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:posix_error",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "uname_test",
- testonly = 1,
- srcs = ["uname.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "unlink_test",
- testonly = 1,
- srcs = ["unlink.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "unshare_test",
- testonly = 1,
- srcs = ["unshare.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/synchronization",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "utimes_test",
- testonly = 1,
- srcs = ["utimes.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/time",
- ],
-)
-
-cc_binary(
- name = "vdso_test",
- testonly = 1,
- srcs = ["vdso.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:fs_util",
- "//test/util:posix_error",
- "//test/util:proc_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "vfork_test",
- testonly = 1,
- srcs = ["vfork.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:logging",
- "//test/util:multiprocess_util",
- "//test/util:test_util",
- "//test/util:time_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "wait_test",
- testonly = 1,
- srcs = ["wait.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:cleanup",
- "//test/util:file_descriptor",
- "//test/util:logging",
- "//test/util:multiprocess_util",
- "//test/util:posix_error",
- "//test/util:signal_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "//test/util:time_util",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/synchronization",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "write_test",
- testonly = 1,
- srcs = ["write.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:cleanup",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "memory_accounting_test",
- testonly = 1,
- srcs = ["memory_accounting.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:fs_util",
- "//test/util:posix_error",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/strings:str_format",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "semaphore_test",
- testonly = 1,
- srcs = ["semaphore.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:capability_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "//test/util:thread_util",
- "@com_google_absl//absl/base:core_headers",
- "@com_google_absl//absl/memory",
- "@com_google_absl//absl/synchronization",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "shm_test",
- testonly = 1,
- srcs = ["shm.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:multiprocess_util",
- "//test/util:posix_error",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/time",
- ],
-)
-
-cc_binary(
- name = "fadvise64_test",
- testonly = 1,
- srcs = ["fadvise64.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "vdso_clock_gettime_test",
- testonly = 1,
- srcs = ["vdso_clock_gettime.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "vsyscall_test",
- testonly = 1,
- srcs = ["vsyscall.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:proc_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "proc_net_unix_test",
- testonly = 1,
- srcs = ["proc_net_unix.cc"],
- linkstatic = 1,
- deps = [
- ":unix_domain_socket_test_util",
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/strings:str_format",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "memfd_test",
- testonly = 1,
- srcs = ["memfd.cc"],
- linkstatic = 1,
- deps = [
- "//test/util:file_descriptor",
- "//test/util:fs_util",
- "//test/util:memory_util",
- "//test/util:multiprocess_util",
- "//test/util:temp_path",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "proc_net_tcp_test",
- testonly = 1,
- srcs = ["proc_net_tcp.cc"],
- linkstatic = 1,
- deps = [
- ":ip_socket_test_util",
- "//test/util:file_descriptor",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_binary(
- name = "proc_net_udp_test",
- testonly = 1,
- srcs = ["proc_net_udp.cc"],
- linkstatic = 1,
- deps = [
- ":ip_socket_test_util",
- "//test/util:file_descriptor",
- "//test/util:test_main",
- "//test/util:test_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
diff --git a/test/syscalls/linux/accept_bind.cc b/test/syscalls/linux/accept_bind.cc
deleted file mode 100644
index 1122ea240..000000000
--- a/test/syscalls/linux/accept_bind.cc
+++ /dev/null
@@ -1,586 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <stdio.h>
-#include <sys/un.h>
-#include <algorithm>
-#include <vector>
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST_P(AllSocketPairTest, Listen) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(sockets->first_fd(), /* backlog = */ 5),
- SyscallSucceeds());
-}
-
-TEST_P(AllSocketPairTest, ListenIncreaseBacklog) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(sockets->first_fd(), /* backlog = */ 5),
- SyscallSucceeds());
- ASSERT_THAT(listen(sockets->first_fd(), /* backlog = */ 10),
- SyscallSucceeds());
-}
-
-TEST_P(AllSocketPairTest, ListenDecreaseBacklog) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(sockets->first_fd(), /* backlog = */ 5),
- SyscallSucceeds());
- ASSERT_THAT(listen(sockets->first_fd(), /* backlog = */ 1),
- SyscallSucceeds());
-}
-
-TEST_P(AllSocketPairTest, ListenWithoutBind) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- ASSERT_THAT(listen(sockets->first_fd(), 0), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_P(AllSocketPairTest, DoubleBind) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->second_addr(),
- sockets->second_addr_size()),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_P(AllSocketPairTest, BindListenBind) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->second_addr(),
- sockets->second_addr_size()),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_P(AllSocketPairTest, DoubleListen) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
-
- ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
-}
-
-TEST_P(AllSocketPairTest, DoubleConnect) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
-
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallFailsWithErrno(EISCONN));
-}
-
-TEST_P(AllSocketPairTest, Connect) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
-
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-}
-
-TEST_P(AllSocketPairTest, ConnectToFilePath) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct sockaddr_un addr = {};
- addr.sun_family = AF_UNIX;
- constexpr char kPath[] = "/tmp";
- memcpy(addr.sun_path, kPath, sizeof(kPath));
-
- ASSERT_THAT(
- connect(sockets->second_fd(),
- reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)),
- SyscallFailsWithErrno(ECONNREFUSED));
-}
-
-TEST_P(AllSocketPairTest, ConnectToInvalidAbstractPath) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct sockaddr_un addr = {};
- addr.sun_family = AF_UNIX;
- constexpr char kPath[] = "\0nonexistent";
- memcpy(addr.sun_path, kPath, sizeof(kPath));
-
- ASSERT_THAT(
- connect(sockets->second_fd(),
- reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)),
- SyscallFailsWithErrno(ECONNREFUSED));
-}
-
-TEST_P(AllSocketPairTest, SelfConnect) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
-
- ASSERT_THAT(connect(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_P(AllSocketPairTest, ConnectWithoutListen) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallFailsWithErrno(ECONNREFUSED));
-}
-
-TEST_P(AllSocketPairTest, Accept) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
-
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- int accepted = -1;
- ASSERT_THAT(accepted = accept(sockets->first_fd(), nullptr, nullptr),
- SyscallSucceeds());
- ASSERT_THAT(close(accepted), SyscallSucceeds());
-}
-
-TEST_P(AllSocketPairTest, AcceptValidAddrLen) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
-
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- int accepted = -1;
- struct sockaddr_un addr = {};
- socklen_t addr_len = sizeof(addr);
- ASSERT_THAT(
- accepted = accept(sockets->first_fd(),
- reinterpret_cast<struct sockaddr*>(&addr), &addr_len),
- SyscallSucceeds());
- ASSERT_THAT(close(accepted), SyscallSucceeds());
-}
-
-TEST_P(AllSocketPairTest, AcceptNegativeAddrLen) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
-
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- // With a negative addr_len, accept returns EINVAL,
- struct sockaddr_un addr = {};
- socklen_t addr_len = -1;
- ASSERT_THAT(accept(sockets->first_fd(),
- reinterpret_cast<struct sockaddr*>(&addr), &addr_len),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_P(AllSocketPairTest, AcceptLargePositiveAddrLen) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
-
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- // With a large (positive) addr_len, accept does not return EINVAL.
- int accepted = -1;
- char addr_buf[200];
- socklen_t addr_len = sizeof(addr_buf);
- ASSERT_THAT(accepted = accept(sockets->first_fd(),
- reinterpret_cast<struct sockaddr*>(addr_buf),
- &addr_len),
- SyscallSucceeds());
- // addr_len should have been updated by accept().
- EXPECT_LT(addr_len, sizeof(addr_buf));
- ASSERT_THAT(close(accepted), SyscallSucceeds());
-}
-
-TEST_P(AllSocketPairTest, AcceptVeryLargePositiveAddrLen) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
-
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- // With a large (positive) addr_len, accept does not return EINVAL.
- int accepted = -1;
- char addr_buf[2000];
- socklen_t addr_len = sizeof(addr_buf);
- ASSERT_THAT(accepted = accept(sockets->first_fd(),
- reinterpret_cast<struct sockaddr*>(addr_buf),
- &addr_len),
- SyscallSucceeds());
- // addr_len should have been updated by accept().
- EXPECT_LT(addr_len, sizeof(addr_buf));
- ASSERT_THAT(close(accepted), SyscallSucceeds());
-}
-
-TEST_P(AllSocketPairTest, AcceptWithoutBind) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(accept(sockets->first_fd(), nullptr, nullptr),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_P(AllSocketPairTest, AcceptWithoutListen) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
- ASSERT_THAT(accept(sockets->first_fd(), nullptr, nullptr),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_P(AllSocketPairTest, GetRemoteAddress) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
-
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- socklen_t addr_len = sockets->first_addr_size();
- struct sockaddr_storage addr = {};
- ASSERT_THAT(
- getpeername(sockets->second_fd(), (struct sockaddr*)(&addr), &addr_len),
- SyscallSucceeds());
- EXPECT_EQ(addr_len, sockets->first_addr_len());
- EXPECT_EQ(0, memcmp(&addr, sockets->first_addr(), sockets->first_addr_len()));
-}
-
-TEST_P(AllSocketPairTest, UnboundGetLocalAddress) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
-
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- socklen_t addr_len = sockets->first_addr_size();
- struct sockaddr_storage addr = {};
- ASSERT_THAT(
- getsockname(sockets->second_fd(), (struct sockaddr*)(&addr), &addr_len),
- SyscallSucceeds());
- EXPECT_EQ(addr_len, 2);
- EXPECT_EQ(
- memcmp(&addr, sockets->second_addr(),
- std::min((size_t)addr_len, (size_t)sockets->second_addr_len())),
- 0);
-}
-
-TEST_P(AllSocketPairTest, BoundGetLocalAddress) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
-
- ASSERT_THAT(bind(sockets->second_fd(), sockets->second_addr(),
- sockets->second_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- socklen_t addr_len = sockets->first_addr_size();
- struct sockaddr_storage addr = {};
- ASSERT_THAT(
- getsockname(sockets->second_fd(), (struct sockaddr*)(&addr), &addr_len),
- SyscallSucceeds());
- EXPECT_EQ(addr_len, sockets->second_addr_len());
- EXPECT_EQ(
- memcmp(&addr, sockets->second_addr(),
- std::min((size_t)addr_len, (size_t)sockets->second_addr_len())),
- 0);
-}
-
-TEST_P(AllSocketPairTest, BoundConnector) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
-
- ASSERT_THAT(bind(sockets->second_fd(), sockets->second_addr(),
- sockets->second_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-}
-
-TEST_P(AllSocketPairTest, UnboundSenderAddr) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
-
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- int accepted = -1;
- ASSERT_THAT(accepted = accept(sockets->first_fd(), nullptr, nullptr),
- SyscallSucceeds());
- FileDescriptor accepted_fd(accepted);
-
- int i = 0;
- ASSERT_THAT(RetryEINTR(send)(sockets->second_fd(), &i, sizeof(i), 0),
- SyscallSucceedsWithValue(sizeof(i)));
-
- struct sockaddr_storage addr;
- socklen_t addr_len = sizeof(addr);
- ASSERT_THAT(
- RetryEINTR(recvfrom)(accepted_fd.get(), &i, sizeof(i), 0,
- reinterpret_cast<sockaddr*>(&addr), &addr_len),
- SyscallSucceedsWithValue(sizeof(i)));
- EXPECT_EQ(addr_len, 0);
-}
-
-TEST_P(AllSocketPairTest, BoundSenderAddr) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
-
- ASSERT_THAT(bind(sockets->second_fd(), sockets->second_addr(),
- sockets->second_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- int accepted = -1;
- ASSERT_THAT(accepted = accept(sockets->first_fd(), nullptr, nullptr),
- SyscallSucceeds());
- FileDescriptor accepted_fd(accepted);
-
- int i = 0;
- ASSERT_THAT(RetryEINTR(send)(sockets->second_fd(), &i, sizeof(i), 0),
- SyscallSucceedsWithValue(sizeof(i)));
-
- struct sockaddr_storage addr;
- socklen_t addr_len = sizeof(addr);
- ASSERT_THAT(
- RetryEINTR(recvfrom)(accepted_fd.get(), &i, sizeof(i), 0,
- reinterpret_cast<sockaddr*>(&addr), &addr_len),
- SyscallSucceedsWithValue(sizeof(i)));
- EXPECT_EQ(addr_len, sockets->second_addr_len());
- EXPECT_EQ(
- memcmp(&addr, sockets->second_addr(),
- std::min((size_t)addr_len, (size_t)sockets->second_addr_len())),
- 0);
-}
-
-TEST_P(AllSocketPairTest, BindAfterConnectSenderAddr) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
-
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(bind(sockets->second_fd(), sockets->second_addr(),
- sockets->second_addr_size()),
- SyscallSucceeds());
-
- int accepted = -1;
- ASSERT_THAT(accepted = accept(sockets->first_fd(), nullptr, nullptr),
- SyscallSucceeds());
- FileDescriptor accepted_fd(accepted);
-
- int i = 0;
- ASSERT_THAT(RetryEINTR(send)(sockets->second_fd(), &i, sizeof(i), 0),
- SyscallSucceedsWithValue(sizeof(i)));
-
- struct sockaddr_storage addr;
- socklen_t addr_len = sizeof(addr);
- ASSERT_THAT(
- RetryEINTR(recvfrom)(accepted_fd.get(), &i, sizeof(i), 0,
- reinterpret_cast<sockaddr*>(&addr), &addr_len),
- SyscallSucceedsWithValue(sizeof(i)));
- EXPECT_EQ(addr_len, sockets->second_addr_len());
- EXPECT_EQ(
- memcmp(&addr, sockets->second_addr(),
- std::min((size_t)addr_len, (size_t)sockets->second_addr_len())),
- 0);
-}
-
-TEST_P(AllSocketPairTest, BindAfterAcceptSenderAddr) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
-
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- int accepted = -1;
- ASSERT_THAT(accepted = accept(sockets->first_fd(), nullptr, nullptr),
- SyscallSucceeds());
- FileDescriptor accepted_fd(accepted);
-
- ASSERT_THAT(bind(sockets->second_fd(), sockets->second_addr(),
- sockets->second_addr_size()),
- SyscallSucceeds());
-
- int i = 0;
- ASSERT_THAT(RetryEINTR(send)(sockets->second_fd(), &i, sizeof(i), 0),
- SyscallSucceedsWithValue(sizeof(i)));
-
- struct sockaddr_storage addr;
- socklen_t addr_len = sizeof(addr);
- ASSERT_THAT(
- RetryEINTR(recvfrom)(accepted_fd.get(), &i, sizeof(i), 0,
- reinterpret_cast<sockaddr*>(&addr), &addr_len),
- SyscallSucceedsWithValue(sizeof(i)));
- EXPECT_EQ(addr_len, sockets->second_addr_len());
- EXPECT_EQ(
- memcmp(&addr, sockets->second_addr(),
- std::min((size_t)addr_len, (size_t)sockets->second_addr_len())),
- 0);
-}
-
-INSTANTIATE_TEST_SUITE_P(
- AllUnixDomainSockets, AllSocketPairTest,
- ::testing::ValuesIn(VecCat<SocketPairKind>(
- ApplyVec<SocketPairKind>(
- FilesystemUnboundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_SEQPACKET},
- List<int>{0, SOCK_NONBLOCK})),
- ApplyVec<SocketPairKind>(
- AbstractUnboundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_SEQPACKET},
- List<int>{0, SOCK_NONBLOCK})))));
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/accept_bind_stream.cc b/test/syscalls/linux/accept_bind_stream.cc
deleted file mode 100644
index b6cdb3f4f..000000000
--- a/test/syscalls/linux/accept_bind_stream.cc
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <stdio.h>
-#include <sys/un.h>
-#include <algorithm>
-#include <vector>
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST_P(AllSocketPairTest, BoundSenderAddrCoalesced) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
-
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- int accepted = -1;
- ASSERT_THAT(accepted = accept(sockets->first_fd(), nullptr, nullptr),
- SyscallSucceeds());
- FileDescriptor closer(accepted);
-
- int i = 0;
- ASSERT_THAT(RetryEINTR(send)(sockets->second_fd(), &i, sizeof(i), 0),
- SyscallSucceedsWithValue(sizeof(i)));
-
- ASSERT_THAT(bind(sockets->second_fd(), sockets->second_addr(),
- sockets->second_addr_size()),
- SyscallSucceeds());
-
- i = 0;
- ASSERT_THAT(RetryEINTR(send)(sockets->second_fd(), &i, sizeof(i), 0),
- SyscallSucceedsWithValue(sizeof(i)));
-
- int ri[2] = {0, 0};
- struct sockaddr_storage addr;
- socklen_t addr_len = sizeof(addr);
- ASSERT_THAT(
- RetryEINTR(recvfrom)(accepted, ri, sizeof(ri), 0,
- reinterpret_cast<sockaddr*>(&addr), &addr_len),
- SyscallSucceedsWithValue(sizeof(ri)));
- EXPECT_EQ(addr_len, sockets->second_addr_len());
-
- EXPECT_EQ(
- memcmp(&addr, sockets->second_addr(),
- std::min((size_t)addr_len, (size_t)sockets->second_addr_len())),
- 0);
-}
-
-INSTANTIATE_TEST_SUITE_P(
- AllUnixDomainSockets, AllSocketPairTest,
- ::testing::ValuesIn(VecCat<SocketPairKind>(
- ApplyVec<SocketPairKind>(FilesystemUnboundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM},
- List<int>{
- 0, SOCK_NONBLOCK})),
- ApplyVec<SocketPairKind>(
- AbstractUnboundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM},
- List<int>{0, SOCK_NONBLOCK})))));
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/access.cc b/test/syscalls/linux/access.cc
deleted file mode 100644
index bcc25cef4..000000000
--- a/test/syscalls/linux/access.cc
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "test/util/capability_util.h"
-#include "test/util/fs_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-using ::testing::Ge;
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-class AccessTest : public ::testing::Test {
- public:
- std::string CreateTempFile(int perm) {
- const std::string path = NewTempAbsPath();
- const int fd = open(path.c_str(), O_CREAT | O_RDONLY, perm);
- TEST_PCHECK(fd > 0);
- TEST_PCHECK(close(fd) == 0);
- return path;
- }
-
- protected:
- // SetUp creates various configurations of files.
- void SetUp() override {
- // Move to the temporary directory. This allows us to reason more easily
- // about absolute and relative paths.
- ASSERT_THAT(chdir(GetAbsoluteTestTmpdir().c_str()), SyscallSucceeds());
-
- // Create an empty file, standard permissions.
- relfile_ = NewTempRelPath();
- int fd;
- ASSERT_THAT(fd = open(relfile_.c_str(), O_CREAT | O_TRUNC, 0644),
- SyscallSucceedsWithValue(Ge(0)));
- ASSERT_THAT(close(fd), SyscallSucceeds());
- absfile_ = GetAbsoluteTestTmpdir() + "/" + relfile_;
-
- // Create an empty directory, no writable permissions.
- absdir_ = NewTempAbsPath();
- reldir_ = JoinPath(Basename(absdir_), "");
- ASSERT_THAT(mkdir(reldir_.c_str(), 0555), SyscallSucceeds());
-
- // This file doesn't exist.
- relnone_ = NewTempRelPath();
- absnone_ = GetAbsoluteTestTmpdir() + "/" + relnone_;
- }
-
- // TearDown unlinks created files.
- void TearDown() override {
- ASSERT_THAT(unlink(absfile_.c_str()), SyscallSucceeds());
- ASSERT_THAT(rmdir(absdir_.c_str()), SyscallSucceeds());
- }
-
- std::string relfile_;
- std::string reldir_;
-
- std::string absfile_;
- std::string absdir_;
-
- std::string relnone_;
- std::string absnone_;
-};
-
-TEST_F(AccessTest, RelativeFile) {
- EXPECT_THAT(access(relfile_.c_str(), R_OK), SyscallSucceeds());
-}
-
-TEST_F(AccessTest, RelativeDir) {
- EXPECT_THAT(access(reldir_.c_str(), R_OK | X_OK), SyscallSucceeds());
-}
-
-TEST_F(AccessTest, AbsFile) {
- EXPECT_THAT(access(absfile_.c_str(), R_OK), SyscallSucceeds());
-}
-
-TEST_F(AccessTest, AbsDir) {
- EXPECT_THAT(access(absdir_.c_str(), R_OK | X_OK), SyscallSucceeds());
-}
-
-TEST_F(AccessTest, RelDoesNotExist) {
- EXPECT_THAT(access(relnone_.c_str(), R_OK), SyscallFailsWithErrno(ENOENT));
-}
-
-TEST_F(AccessTest, AbsDoesNotExist) {
- EXPECT_THAT(access(absnone_.c_str(), R_OK), SyscallFailsWithErrno(ENOENT));
-}
-
-TEST_F(AccessTest, InvalidMode) {
- EXPECT_THAT(access(relfile_.c_str(), 0xffffffff),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_F(AccessTest, NoPerms) {
- // Drop capabilities that allow us to override permissions. We must drop
- // PERMITTED because access() checks those instead of EFFECTIVE.
- ASSERT_NO_ERRNO(DropPermittedCapability(CAP_DAC_OVERRIDE));
- ASSERT_NO_ERRNO(DropPermittedCapability(CAP_DAC_READ_SEARCH));
-
- EXPECT_THAT(access(absdir_.c_str(), W_OK), SyscallFailsWithErrno(EACCES));
-}
-
-TEST_F(AccessTest, InvalidName) {
- EXPECT_THAT(access(reinterpret_cast<char*>(0x1234), W_OK),
- SyscallFailsWithErrno(EFAULT));
-}
-
-TEST_F(AccessTest, UsrReadOnly) {
- // Drop capabilities that allow us to override permissions. We must drop
- // PERMITTED because access() checks those instead of EFFECTIVE.
- ASSERT_NO_ERRNO(DropPermittedCapability(CAP_DAC_OVERRIDE));
- ASSERT_NO_ERRNO(DropPermittedCapability(CAP_DAC_READ_SEARCH));
-
- const std::string filename = CreateTempFile(0400);
- EXPECT_THAT(access(filename.c_str(), R_OK), SyscallSucceeds());
- EXPECT_THAT(access(filename.c_str(), W_OK), SyscallFailsWithErrno(EACCES));
- EXPECT_THAT(access(filename.c_str(), X_OK), SyscallFailsWithErrno(EACCES));
- EXPECT_THAT(unlink(filename.c_str()), SyscallSucceeds());
-}
-
-TEST_F(AccessTest, UsrReadExec) {
- // Drop capabilities that allow us to override permissions. We must drop
- // PERMITTED because access() checks those instead of EFFECTIVE.
- ASSERT_NO_ERRNO(DropPermittedCapability(CAP_DAC_OVERRIDE));
- ASSERT_NO_ERRNO(DropPermittedCapability(CAP_DAC_READ_SEARCH));
-
- const std::string filename = CreateTempFile(0500);
- EXPECT_THAT(access(filename.c_str(), R_OK | X_OK), SyscallSucceeds());
- EXPECT_THAT(access(filename.c_str(), W_OK), SyscallFailsWithErrno(EACCES));
- EXPECT_THAT(unlink(filename.c_str()), SyscallSucceeds());
-}
-
-TEST_F(AccessTest, UsrReadWrite) {
- const std::string filename = CreateTempFile(0600);
- EXPECT_THAT(access(filename.c_str(), R_OK | W_OK), SyscallSucceeds());
- EXPECT_THAT(access(filename.c_str(), X_OK), SyscallFailsWithErrno(EACCES));
- EXPECT_THAT(unlink(filename.c_str()), SyscallSucceeds());
-}
-
-TEST_F(AccessTest, UsrReadWriteExec) {
- const std::string filename = CreateTempFile(0700);
- EXPECT_THAT(access(filename.c_str(), R_OK | W_OK | X_OK), SyscallSucceeds());
- EXPECT_THAT(unlink(filename.c_str()), SyscallSucceeds());
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/affinity.cc b/test/syscalls/linux/affinity.cc
deleted file mode 100644
index 128364c34..000000000
--- a/test/syscalls/linux/affinity.cc
+++ /dev/null
@@ -1,242 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sched.h>
-#include <sys/syscall.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "absl/strings/str_split.h"
-#include "test/util/cleanup.h"
-#include "test/util/fs_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-// These tests are for both the sched_getaffinity(2) and sched_setaffinity(2)
-// syscalls.
-class AffinityTest : public ::testing::Test {
- protected:
- void SetUp() override {
- EXPECT_THAT(
- // Needs use the raw syscall to get the actual size.
- cpuset_size_ = syscall(SYS_sched_getaffinity, /*pid=*/0,
- sizeof(cpu_set_t), &mask_),
- SyscallSucceeds());
- // Lots of tests rely on having more than 1 logical processor available.
- EXPECT_GT(CPU_COUNT(&mask_), 1);
- }
-
- static PosixError ClearLowestBit(cpu_set_t* mask, size_t cpus) {
- const size_t mask_size = CPU_ALLOC_SIZE(cpus);
- for (size_t n = 0; n < cpus; ++n) {
- if (CPU_ISSET_S(n, mask_size, mask)) {
- CPU_CLR_S(n, mask_size, mask);
- return NoError();
- }
- }
- return PosixError(EINVAL, "No bit to clear, mask is empty");
- }
-
- PosixError ClearLowestBit() { return ClearLowestBit(&mask_, CPU_SETSIZE); }
-
- // Stores the initial cpu mask for this process.
- cpu_set_t mask_ = {};
- int cpuset_size_ = 0;
-};
-
-// sched_getaffinity(2) is implemented.
-TEST_F(AffinityTest, SchedGetAffinityImplemented) {
- EXPECT_THAT(sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &mask_),
- SyscallSucceeds());
-}
-
-// PID is not found.
-TEST_F(AffinityTest, SchedGetAffinityInvalidPID) {
- // Flaky, but it's tough to avoid a race condition when finding an unused pid
- EXPECT_THAT(sched_getaffinity(/*pid=*/INT_MAX - 1, sizeof(cpu_set_t), &mask_),
- SyscallFailsWithErrno(ESRCH));
-}
-
-// PID is not found.
-TEST_F(AffinityTest, SchedSetAffinityInvalidPID) {
- // Flaky, but it's tough to avoid a race condition when finding an unused pid
- EXPECT_THAT(sched_setaffinity(/*pid=*/INT_MAX - 1, sizeof(cpu_set_t), &mask_),
- SyscallFailsWithErrno(ESRCH));
-}
-
-TEST_F(AffinityTest, SchedSetAffinityZeroMask) {
- CPU_ZERO(&mask_);
- EXPECT_THAT(sched_setaffinity(/*pid=*/0, sizeof(cpu_set_t), &mask_),
- SyscallFailsWithErrno(EINVAL));
-}
-
-// N.B. This test case relies on cpuset_size_ larger than the actual number of
-// of all existing CPUs. Check your machine if the test fails.
-TEST_F(AffinityTest, SchedSetAffinityNonexistentCPUDropped) {
- cpu_set_t mask = mask_;
- // Add a nonexistent CPU.
- //
- // The number needs to be larger than the possible number of CPU available,
- // but smaller than the number of the CPU that the kernel claims to support --
- // it's implicitly returned by raw sched_getaffinity syscall.
- CPU_SET(cpuset_size_ * 8 - 1, &mask);
- EXPECT_THAT(
- // Use raw syscall because it will be rejected by the libc wrapper
- // otherwise.
- syscall(SYS_sched_setaffinity, /*pid=*/0, sizeof(cpu_set_t), &mask),
- SyscallSucceeds())
- << "failed with cpumask : " << CPUSetToString(mask)
- << ", cpuset_size_ : " << cpuset_size_;
- cpu_set_t newmask;
- EXPECT_THAT(sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &newmask),
- SyscallSucceeds());
- EXPECT_TRUE(CPU_EQUAL(&mask_, &newmask))
- << "got: " << CPUSetToString(newmask)
- << " != expected: " << CPUSetToString(mask_);
-}
-
-TEST_F(AffinityTest, SchedSetAffinityOnlyNonexistentCPUFails) {
- // Make an empty cpu set.
- CPU_ZERO(&mask_);
- // Add a nonexistent CPU.
- //
- // The number needs to be larger than the possible number of CPU available,
- // but smaller than the number of the CPU that the kernel claims to support --
- // it's implicitly returned by raw sched_getaffinity syscall.
- int cpu = cpuset_size_ * 8 - 1;
- if (cpu <= NumCPUs()) {
- GTEST_SKIP() << "Skipping test: cpu " << cpu << " exists";
- }
- CPU_SET(cpu, &mask_);
- EXPECT_THAT(
- // Use raw syscall because it will be rejected by the libc wrapper
- // otherwise.
- syscall(SYS_sched_setaffinity, /*pid=*/0, sizeof(cpu_set_t), &mask_),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_F(AffinityTest, SchedSetAffinityInvalidSize) {
- EXPECT_GT(cpuset_size_, 0);
- // Not big enough.
- EXPECT_THAT(sched_getaffinity(/*pid=*/0, cpuset_size_ - 1, &mask_),
- SyscallFailsWithErrno(EINVAL));
- // Not a multiple of word size.
- EXPECT_THAT(sched_getaffinity(/*pid=*/0, cpuset_size_ + 1, &mask_),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_F(AffinityTest, Sanity) {
- ASSERT_NO_ERRNO(ClearLowestBit());
- EXPECT_THAT(sched_setaffinity(/*pid=*/0, sizeof(cpu_set_t), &mask_),
- SyscallSucceeds());
- cpu_set_t newmask;
- EXPECT_THAT(sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &newmask),
- SyscallSucceeds());
- EXPECT_TRUE(CPU_EQUAL(&mask_, &newmask))
- << "got: " << CPUSetToString(newmask)
- << " != expected: " << CPUSetToString(mask_);
-}
-
-TEST_F(AffinityTest, NewThread) {
- SKIP_IF(CPU_COUNT(&mask_) < 3);
- ASSERT_NO_ERRNO(ClearLowestBit());
- ASSERT_NO_ERRNO(ClearLowestBit());
- EXPECT_THAT(sched_setaffinity(/*pid=*/0, sizeof(cpu_set_t), &mask_),
- SyscallSucceeds());
- ScopedThread([this]() {
- cpu_set_t child_mask;
- ASSERT_THAT(sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &child_mask),
- SyscallSucceeds());
- ASSERT_TRUE(CPU_EQUAL(&child_mask, &mask_))
- << "child cpu mask: " << CPUSetToString(child_mask)
- << " != parent cpu mask: " << CPUSetToString(mask_);
- });
-}
-
-TEST_F(AffinityTest, ConsistentWithProcCpuInfo) {
- // Count how many cpus are shown in /proc/cpuinfo.
- std::string cpuinfo = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/cpuinfo"));
- int count = 0;
- for (auto const& line : absl::StrSplit(cpuinfo, '\n')) {
- if (absl::StartsWith(line, "processor")) {
- count++;
- }
- }
- EXPECT_GE(count, CPU_COUNT(&mask_));
-}
-
-TEST_F(AffinityTest, ConsistentWithProcStat) {
- // Count how many cpus are shown in /proc/stat.
- std::string stat = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/stat"));
- int count = 0;
- for (auto const& line : absl::StrSplit(stat, '\n')) {
- if (absl::StartsWith(line, "cpu") && !absl::StartsWith(line, "cpu ")) {
- count++;
- }
- }
- EXPECT_GE(count, CPU_COUNT(&mask_));
-}
-
-TEST_F(AffinityTest, SmallCpuMask) {
- const int num_cpus = NumCPUs();
- const size_t mask_size = CPU_ALLOC_SIZE(num_cpus);
- cpu_set_t* mask = CPU_ALLOC(num_cpus);
- ASSERT_NE(mask, nullptr);
- const auto free_mask = Cleanup([&] { CPU_FREE(mask); });
-
- CPU_ZERO_S(mask_size, mask);
- ASSERT_THAT(sched_getaffinity(0, mask_size, mask), SyscallSucceeds());
-}
-
-TEST_F(AffinityTest, LargeCpuMask) {
- // Allocate mask bigger than cpu_set_t normally allocates.
- const size_t cpus = CPU_SETSIZE * 8;
- const size_t mask_size = CPU_ALLOC_SIZE(cpus);
-
- cpu_set_t* large_mask = CPU_ALLOC(cpus);
- auto free_mask = Cleanup([large_mask] { CPU_FREE(large_mask); });
- CPU_ZERO_S(mask_size, large_mask);
-
- // Check that get affinity with large mask works as expected.
- ASSERT_THAT(sched_getaffinity(/*pid=*/0, mask_size, large_mask),
- SyscallSucceeds());
- EXPECT_TRUE(CPU_EQUAL(&mask_, large_mask))
- << "got: " << CPUSetToString(*large_mask, cpus)
- << " != expected: " << CPUSetToString(mask_);
-
- // Check that set affinity with large mask works as expected.
- ASSERT_NO_ERRNO(ClearLowestBit(large_mask, cpus));
- EXPECT_THAT(sched_setaffinity(/*pid=*/0, mask_size, large_mask),
- SyscallSucceeds());
-
- cpu_set_t* new_mask = CPU_ALLOC(cpus);
- auto free_new_mask = Cleanup([new_mask] { CPU_FREE(new_mask); });
- CPU_ZERO_S(mask_size, new_mask);
- EXPECT_THAT(sched_getaffinity(/*pid=*/0, mask_size, new_mask),
- SyscallSucceeds());
-
- EXPECT_TRUE(CPU_EQUAL_S(mask_size, large_mask, new_mask))
- << "got: " << CPUSetToString(*new_mask, cpus)
- << " != expected: " << CPUSetToString(*large_mask, cpus);
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/aio.cc b/test/syscalls/linux/aio.cc
deleted file mode 100644
index b27d4e10a..000000000
--- a/test/syscalls/linux/aio.cc
+++ /dev/null
@@ -1,424 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <linux/aio_abi.h>
-#include <sys/mman.h>
-#include <sys/syscall.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <algorithm>
-#include <string>
-
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/file_base.h"
-#include "test/util/cleanup.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/memory_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/proc_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-using ::testing::_;
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-// Returns the size of the VMA containing the given address.
-PosixErrorOr<size_t> VmaSizeAt(uintptr_t addr) {
- ASSIGN_OR_RETURN_ERRNO(std::string proc_self_maps,
- GetContents("/proc/self/maps"));
- ASSIGN_OR_RETURN_ERRNO(auto entries, ParseProcMaps(proc_self_maps));
- // Use binary search to find the first VMA that might contain addr.
- ProcMapsEntry target = {};
- target.end = addr;
- auto it =
- std::upper_bound(entries.begin(), entries.end(), target,
- [](const ProcMapsEntry& x, const ProcMapsEntry& y) {
- return x.end < y.end;
- });
- // Check that it actually contains addr.
- if (it == entries.end() || addr < it->start) {
- return PosixError(ENOENT, absl::StrCat("no VMA contains address ", addr));
- }
- return it->end - it->start;
-}
-
-constexpr char kData[] = "hello world!";
-
-int SubmitCtx(aio_context_t ctx, long nr, struct iocb** iocbpp) {
- return syscall(__NR_io_submit, ctx, nr, iocbpp);
-}
-
-class AIOTest : public FileTest {
- public:
- AIOTest() : ctx_(0) {}
-
- int SetupContext(unsigned int nr) {
- return syscall(__NR_io_setup, nr, &ctx_);
- }
-
- int Submit(long nr, struct iocb** iocbpp) {
- return SubmitCtx(ctx_, nr, iocbpp);
- }
-
- int GetEvents(long min, long max, struct io_event* events,
- struct timespec* timeout) {
- return RetryEINTR(syscall)(__NR_io_getevents, ctx_, min, max, events,
- timeout);
- }
-
- int DestroyContext() { return syscall(__NR_io_destroy, ctx_); }
-
- void TearDown() override {
- FileTest::TearDown();
- if (ctx_ != 0) {
- ASSERT_THAT(DestroyContext(), SyscallSucceeds());
- }
- }
-
- struct iocb CreateCallback() {
- struct iocb cb = {};
- cb.aio_data = 0x123;
- cb.aio_fildes = test_file_fd_.get();
- cb.aio_lio_opcode = IOCB_CMD_PWRITE;
- cb.aio_buf = reinterpret_cast<uint64_t>(kData);
- cb.aio_offset = 0;
- cb.aio_nbytes = strlen(kData);
- return cb;
- }
-
- protected:
- aio_context_t ctx_;
-};
-
-TEST_F(AIOTest, BasicWrite) {
- // Copied from fs/aio.c.
- constexpr unsigned AIO_RING_MAGIC = 0xa10a10a1;
- struct aio_ring {
- unsigned id;
- unsigned nr;
- unsigned head;
- unsigned tail;
- unsigned magic;
- unsigned compat_features;
- unsigned incompat_features;
- unsigned header_length;
- struct io_event io_events[0];
- };
-
- // Setup a context that is 128 entries deep.
- ASSERT_THAT(SetupContext(128), SyscallSucceeds());
-
- // Check that 'ctx_' points to a valid address. libaio uses it to check if
- // aio implementation uses aio_ring. gVisor doesn't and returns all zeroes.
- // Linux implements aio_ring, so skip the zeroes check.
- //
- // TODO(b/65486370): Remove when gVisor implements aio_ring.
- auto ring = reinterpret_cast<struct aio_ring*>(ctx_);
- auto magic = IsRunningOnGvisor() ? 0 : AIO_RING_MAGIC;
- EXPECT_EQ(ring->magic, magic);
-
- struct iocb cb = CreateCallback();
- struct iocb* cbs[1] = {&cb};
-
- // Submit the request.
- ASSERT_THAT(Submit(1, cbs), SyscallSucceedsWithValue(1));
-
- // Get the reply.
- struct io_event events[1];
- ASSERT_THAT(GetEvents(1, 1, events, nullptr), SyscallSucceedsWithValue(1));
-
- // Verify that it is as expected.
- EXPECT_EQ(events[0].data, 0x123);
- EXPECT_EQ(events[0].obj, reinterpret_cast<long>(&cb));
- EXPECT_EQ(events[0].res, strlen(kData));
-
- // Verify that the file contains the contents.
- char verify_buf[sizeof(kData)] = {};
- ASSERT_THAT(read(test_file_fd_.get(), verify_buf, sizeof(kData)),
- SyscallSucceedsWithValue(strlen(kData)));
- EXPECT_STREQ(verify_buf, kData);
-}
-
-TEST_F(AIOTest, BadWrite) {
- // Create a pipe and immediately close the read end.
- int pipefd[2];
- ASSERT_THAT(pipe(pipefd), SyscallSucceeds());
-
- FileDescriptor rfd(pipefd[0]);
- FileDescriptor wfd(pipefd[1]);
-
- rfd.reset(); // Close the read end.
-
- // Setup a context that is 128 entries deep.
- ASSERT_THAT(SetupContext(128), SyscallSucceeds());
-
- struct iocb cb = CreateCallback();
- // Try to write to the read end.
- cb.aio_fildes = wfd.get();
- struct iocb* cbs[1] = {&cb};
-
- // Submit the request.
- ASSERT_THAT(Submit(1, cbs), SyscallSucceedsWithValue(1));
-
- // Get the reply.
- struct io_event events[1];
- ASSERT_THAT(GetEvents(1, 1, events, nullptr), SyscallSucceedsWithValue(1));
-
- // Verify that it fails with the right error code.
- EXPECT_EQ(events[0].data, 0x123);
- EXPECT_EQ(events[0].obj, reinterpret_cast<uint64_t>(&cb));
- EXPECT_LT(events[0].res, 0);
-}
-
-TEST_F(AIOTest, ExitWithPendingIo) {
- // Setup a context that is 5 entries deep.
- ASSERT_THAT(SetupContext(5), SyscallSucceeds());
-
- struct iocb cb = CreateCallback();
- struct iocb* cbs[] = {&cb};
-
- // Submit a request but don't complete it to make it pending.
- EXPECT_THAT(Submit(1, cbs), SyscallSucceeds());
-}
-
-int Submitter(void* arg) {
- auto test = reinterpret_cast<AIOTest*>(arg);
-
- struct iocb cb = test->CreateCallback();
- struct iocb* cbs[1] = {&cb};
-
- // Submit the request.
- TEST_CHECK(test->Submit(1, cbs) == 1);
- return 0;
-}
-
-TEST_F(AIOTest, CloneVm) {
- // Setup a context that is 128 entries deep.
- ASSERT_THAT(SetupContext(128), SyscallSucceeds());
-
- const size_t kStackSize = 5 * kPageSize;
- std::unique_ptr<char[]> stack(new char[kStackSize]);
- char* bp = stack.get() + kStackSize;
- pid_t child;
- ASSERT_THAT(child = clone(Submitter, bp, CLONE_VM | SIGCHLD,
- reinterpret_cast<void*>(this)),
- SyscallSucceeds());
-
- // Get the reply.
- struct io_event events[1];
- ASSERT_THAT(GetEvents(1, 1, events, nullptr), SyscallSucceedsWithValue(1));
-
- // Verify that it is as expected.
- EXPECT_EQ(events[0].data, 0x123);
- EXPECT_EQ(events[0].res, strlen(kData));
-
- // Verify that the file contains the contents.
- char verify_buf[32] = {};
- ASSERT_THAT(read(test_file_fd_.get(), &verify_buf[0], strlen(kData)),
- SyscallSucceeds());
- EXPECT_EQ(strcmp(kData, &verify_buf[0]), 0);
-
- int status;
- ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0),
- SyscallSucceedsWithValue(child));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << " status " << status;
-}
-
-// Tests that AIO context can be remapped to a different address.
-TEST_F(AIOTest, Mremap) {
- // Setup a context that is 128 entries deep.
- ASSERT_THAT(SetupContext(128), SyscallSucceeds());
- const size_t ctx_size =
- ASSERT_NO_ERRNO_AND_VALUE(VmaSizeAt(reinterpret_cast<uintptr_t>(ctx_)));
-
- struct iocb cb = CreateCallback();
- struct iocb* cbs[1] = {&cb};
-
- // Reserve address space for the mremap target so we have something safe to
- // map over.
- Mapping dst =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(ctx_size, PROT_READ, MAP_PRIVATE));
-
- // Remap context 'handle' to a different address.
- ASSERT_THAT(Mremap(reinterpret_cast<void*>(ctx_), ctx_size, dst.len(),
- MREMAP_FIXED | MREMAP_MAYMOVE, dst.ptr()),
- IsPosixErrorOkAndHolds(dst.ptr()));
- aio_context_t old_ctx = ctx_;
- ctx_ = reinterpret_cast<aio_context_t>(dst.addr());
- // io_destroy() will unmap dst now.
- dst.release();
-
- // Check that submitting the request with the old 'ctx_' fails.
- ASSERT_THAT(SubmitCtx(old_ctx, 1, cbs), SyscallFailsWithErrno(EINVAL));
-
- // Submit the request with the new 'ctx_'.
- ASSERT_THAT(Submit(1, cbs), SyscallSucceedsWithValue(1));
-
- // Remap again.
- dst = ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(ctx_size, PROT_READ, MAP_PRIVATE));
- ASSERT_THAT(Mremap(reinterpret_cast<void*>(ctx_), ctx_size, dst.len(),
- MREMAP_FIXED | MREMAP_MAYMOVE, dst.ptr()),
- IsPosixErrorOkAndHolds(dst.ptr()));
- ctx_ = reinterpret_cast<aio_context_t>(dst.addr());
- dst.release();
-
- // Get the reply with yet another 'ctx_' and verify it.
- struct io_event events[1];
- ASSERT_THAT(GetEvents(1, 1, events, nullptr), SyscallSucceedsWithValue(1));
- EXPECT_EQ(events[0].data, 0x123);
- EXPECT_EQ(events[0].obj, reinterpret_cast<long>(&cb));
- EXPECT_EQ(events[0].res, strlen(kData));
-
- // Verify that the file contains the contents.
- char verify_buf[sizeof(kData)] = {};
- ASSERT_THAT(read(test_file_fd_.get(), verify_buf, sizeof(kData)),
- SyscallSucceedsWithValue(strlen(kData)));
- EXPECT_STREQ(verify_buf, kData);
-}
-
-// Tests that AIO context cannot be expanded with mremap.
-TEST_F(AIOTest, MremapExpansion) {
- // Setup a context that is 128 entries deep.
- ASSERT_THAT(SetupContext(128), SyscallSucceeds());
- const size_t ctx_size =
- ASSERT_NO_ERRNO_AND_VALUE(VmaSizeAt(reinterpret_cast<uintptr_t>(ctx_)));
-
- // Reserve address space for the mremap target so we have something safe to
- // map over.
- Mapping dst = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(ctx_size + kPageSize, PROT_NONE, MAP_PRIVATE));
-
- // Test that remapping to a larger address range fails.
- ASSERT_THAT(Mremap(reinterpret_cast<void*>(ctx_), ctx_size, dst.len(),
- MREMAP_FIXED | MREMAP_MAYMOVE, dst.ptr()),
- PosixErrorIs(EFAULT, _));
-
- // mm/mremap.c:sys_mremap() => mremap_to() does do_munmap() of the destination
- // before it hits the VM_DONTEXPAND check in vma_to_resize(), so we should no
- // longer munmap it (another thread may have created a mapping there).
- dst.release();
-}
-
-// Tests that AIO calls fail if context's address is inaccessible.
-TEST_F(AIOTest, Mprotect) {
- // Setup a context that is 128 entries deep.
- ASSERT_THAT(SetupContext(128), SyscallSucceeds());
-
- struct iocb cb = CreateCallback();
- struct iocb* cbs[1] = {&cb};
-
- ASSERT_THAT(Submit(1, cbs), SyscallSucceedsWithValue(1));
-
- // Makes the context 'handle' inaccessible and check that all subsequent
- // calls fail.
- ASSERT_THAT(mprotect(reinterpret_cast<void*>(ctx_), kPageSize, PROT_NONE),
- SyscallSucceeds());
- struct io_event events[1];
- EXPECT_THAT(GetEvents(1, 1, events, nullptr), SyscallFailsWithErrno(EINVAL));
- ASSERT_THAT(Submit(1, cbs), SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(DestroyContext(), SyscallFailsWithErrno(EINVAL));
-
- // Prevent TearDown from attempting to destroy the context and fail.
- ctx_ = 0;
-}
-
-TEST_F(AIOTest, Timeout) {
- // Setup a context that is 128 entries deep.
- ASSERT_THAT(SetupContext(128), SyscallSucceeds());
-
- struct timespec timeout;
- timeout.tv_sec = 0;
- timeout.tv_nsec = 10;
- struct io_event events[1];
- ASSERT_THAT(GetEvents(1, 1, events, &timeout), SyscallSucceedsWithValue(0));
-}
-
-class AIOReadWriteParamTest : public AIOTest,
- public ::testing::WithParamInterface<int> {};
-
-TEST_P(AIOReadWriteParamTest, BadOffset) {
- // Setup a context that is 128 entries deep.
- ASSERT_THAT(SetupContext(128), SyscallSucceeds());
-
- struct iocb cb = CreateCallback();
- struct iocb* cbs[1] = {&cb};
-
- // Create a buffer that we can write to.
- char buf[] = "hello world!";
- cb.aio_buf = reinterpret_cast<uint64_t>(buf);
-
- // Set the operation on the callback and give a negative offset.
- const int opcode = GetParam();
- cb.aio_lio_opcode = opcode;
-
- iovec iov = {};
- if (opcode == IOCB_CMD_PREADV || opcode == IOCB_CMD_PWRITEV) {
- // Create a valid iovec and set it in the callback.
- iov.iov_base = reinterpret_cast<void*>(buf);
- iov.iov_len = 1;
- cb.aio_buf = reinterpret_cast<uint64_t>(&iov);
- // aio_nbytes is the number of iovecs.
- cb.aio_nbytes = 1;
- }
-
- // Pass a negative offset.
- cb.aio_offset = -1;
-
- // Should get error on submission.
- ASSERT_THAT(Submit(1, cbs), SyscallFailsWithErrno(EINVAL));
-}
-
-INSTANTIATE_TEST_SUITE_P(BadOffset, AIOReadWriteParamTest,
- ::testing::Values(IOCB_CMD_PREAD, IOCB_CMD_PWRITE,
- IOCB_CMD_PREADV, IOCB_CMD_PWRITEV));
-
-class AIOVectorizedParamTest : public AIOTest,
- public ::testing::WithParamInterface<int> {};
-
-TEST_P(AIOVectorizedParamTest, BadIOVecs) {
- // Setup a context that is 128 entries deep.
- ASSERT_THAT(SetupContext(128), SyscallSucceeds());
-
- struct iocb cb = CreateCallback();
- struct iocb* cbs[1] = {&cb};
-
- // Modify the callback to use the operation from the param.
- cb.aio_lio_opcode = GetParam();
-
- // Create an iovec with address in kernel range, and pass that as the buffer.
- iovec iov = {};
- iov.iov_base = reinterpret_cast<void*>(0xFFFFFFFF00000000);
- iov.iov_len = 1;
- cb.aio_buf = reinterpret_cast<uint64_t>(&iov);
- // aio_nbytes is the number of iovecs.
- cb.aio_nbytes = 1;
-
- // Should get error on submission.
- ASSERT_THAT(Submit(1, cbs), SyscallFailsWithErrno(EFAULT));
-}
-
-INSTANTIATE_TEST_SUITE_P(BadIOVecs, AIOVectorizedParamTest,
- ::testing::Values(IOCB_CMD_PREADV, IOCB_CMD_PWRITEV));
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/alarm.cc b/test/syscalls/linux/alarm.cc
deleted file mode 100644
index d89269985..000000000
--- a/test/syscalls/linux/alarm.cc
+++ /dev/null
@@ -1,193 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <signal.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/logging.h"
-#include "test/util/signal_util.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// N.B. Below, main blocks SIGALRM. Test cases must unblock it if they want
-// delivery.
-
-void do_nothing_handler(int sig, siginfo_t* siginfo, void* arg) {}
-
-// No random save as the test relies on alarm timing. Cooperative save tests
-// already cover the save between alarm and read.
-TEST(AlarmTest, Interrupt_NoRandomSave) {
- int pipe_fds[2];
- ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds());
-
- FileDescriptor read_fd(pipe_fds[0]);
- FileDescriptor write_fd(pipe_fds[1]);
-
- // Use a signal handler that interrupts but does nothing rather than using the
- // default terminate action.
- struct sigaction sa;
- sa.sa_sigaction = do_nothing_handler;
- sigfillset(&sa.sa_mask);
- sa.sa_flags = 0;
- auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGALRM, sa));
-
- // Actually allow SIGALRM delivery.
- auto mask_cleanup =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGALRM));
-
- // Alarm in 20 second, which should be well after read blocks below.
- ASSERT_THAT(alarm(20), SyscallSucceeds());
-
- char buf;
- ASSERT_THAT(read(read_fd.get(), &buf, 1), SyscallFailsWithErrno(EINTR));
-}
-
-/* Count of the number of SIGALARMS handled. */
-static volatile int alarms_received = 0;
-
-void inc_alarms_handler(int sig, siginfo_t* siginfo, void* arg) {
- alarms_received++;
-}
-
-// No random save as the test relies on alarm timing. Cooperative save tests
-// already cover the save between alarm and read.
-TEST(AlarmTest, Restart_NoRandomSave) {
- alarms_received = 0;
-
- int pipe_fds[2];
- ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds());
-
- FileDescriptor read_fd(pipe_fds[0]);
- // Write end closed by thread below.
-
- struct sigaction sa;
- sa.sa_sigaction = inc_alarms_handler;
- sigfillset(&sa.sa_mask);
- sa.sa_flags = SA_RESTART;
- auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGALRM, sa));
-
- // Spawn a thread to eventually unblock the read below.
- ScopedThread t([pipe_fds] {
- absl::SleepFor(absl::Seconds(30));
- EXPECT_THAT(close(pipe_fds[1]), SyscallSucceeds());
- });
-
- // Actually allow SIGALRM delivery.
- auto mask_cleanup =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGALRM));
-
- // Alarm in 20 second, which should be well after read blocks below, but
- // before it returns.
- ASSERT_THAT(alarm(20), SyscallSucceeds());
-
- // Read and eventually get an EOF from the writer closing. If SA_RESTART
- // didn't work, then the alarm would not have fired and we wouldn't increment
- // our alarms_received count in our signal handler, or we would have not
- // restarted the syscall gracefully, which we expect below in order to be
- // able to get the final EOF on the pipe.
- char buf;
- ASSERT_THAT(read(read_fd.get(), &buf, 1), SyscallSucceeds());
- EXPECT_EQ(alarms_received, 1);
-
- t.Join();
-}
-
-// No random save as the test relies on alarm timing. Cooperative save tests
-// already cover the save between alarm and pause.
-TEST(AlarmTest, SaSiginfo_NoRandomSave) {
- // Use a signal handler that interrupts but does nothing rather than using the
- // default terminate action.
- struct sigaction sa;
- sa.sa_sigaction = do_nothing_handler;
- sigfillset(&sa.sa_mask);
- sa.sa_flags = SA_SIGINFO;
- auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGALRM, sa));
-
- // Actually allow SIGALRM delivery.
- auto mask_cleanup =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGALRM));
-
- // Alarm in 20 second, which should be well after pause blocks below.
- ASSERT_THAT(alarm(20), SyscallSucceeds());
- ASSERT_THAT(pause(), SyscallFailsWithErrno(EINTR));
-}
-
-// No random save as the test relies on alarm timing. Cooperative save tests
-// already cover the save between alarm and pause.
-TEST(AlarmTest, SaInterrupt_NoRandomSave) {
- // Use a signal handler that interrupts but does nothing rather than using the
- // default terminate action.
- struct sigaction sa;
- sa.sa_sigaction = do_nothing_handler;
- sigfillset(&sa.sa_mask);
- sa.sa_flags = SA_INTERRUPT;
- auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGALRM, sa));
-
- // Actually allow SIGALRM delivery.
- auto mask_cleanup =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGALRM));
-
- // Alarm in 20 second, which should be well after pause blocks below.
- ASSERT_THAT(alarm(20), SyscallSucceeds());
- ASSERT_THAT(pause(), SyscallFailsWithErrno(EINTR));
-}
-
-TEST(AlarmTest, UserModeSpinning) {
- alarms_received = 0;
-
- struct sigaction sa = {};
- sa.sa_sigaction = inc_alarms_handler;
- sigfillset(&sa.sa_mask);
- sa.sa_flags = SA_SIGINFO;
- auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGALRM, sa));
-
- // Actually allow SIGALRM delivery.
- auto mask_cleanup =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGALRM));
-
- // Alarm in 20 second, which should be well into the loop below.
- ASSERT_THAT(alarm(20), SyscallSucceeds());
- // Make sure that the signal gets delivered even if we are spinning in user
- // mode when it arrives.
- while (!alarms_received) {
- }
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
-
-int main(int argc, char** argv) {
- // These tests depend on delivering SIGALRM to the main thread. Block SIGALRM
- // so that any other threads created by TestInit will also have SIGALRM
- // blocked.
- sigset_t set;
- sigemptyset(&set);
- sigaddset(&set, SIGALRM);
- TEST_PCHECK(sigprocmask(SIG_BLOCK, &set, nullptr) == 0);
-
- gvisor::testing::TestInit(&argc, &argv);
-
- return RUN_ALL_TESTS();
-}
diff --git a/test/syscalls/linux/arch_prctl.cc b/test/syscalls/linux/arch_prctl.cc
deleted file mode 100644
index 81bf5a775..000000000
--- a/test/syscalls/linux/arch_prctl.cc
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <asm/prctl.h>
-#include <sys/prctl.h>
-
-#include "gtest/gtest.h"
-#include "test/util/test_util.h"
-
-// glibc does not provide a prototype for arch_prctl() so declare it here.
-extern "C" int arch_prctl(int code, uintptr_t addr);
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(ArchPrctlTest, GetSetFS) {
- uintptr_t orig;
- const uintptr_t kNonCanonicalFsbase = 0x4141414142424242;
-
- // Get the original FS.base and then set it to the same value (this is
- // intentional because FS.base is the TLS pointer so we cannot change it
- // arbitrarily).
- ASSERT_THAT(arch_prctl(ARCH_GET_FS, reinterpret_cast<uintptr_t>(&orig)),
- SyscallSucceeds());
- ASSERT_THAT(arch_prctl(ARCH_SET_FS, orig), SyscallSucceeds());
-
- // Trying to set FS.base to a non-canonical value should return an error.
- ASSERT_THAT(arch_prctl(ARCH_SET_FS, kNonCanonicalFsbase),
- SyscallFailsWithErrno(EPERM));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/bad.cc b/test/syscalls/linux/bad.cc
deleted file mode 100644
index f246a799e..000000000
--- a/test/syscalls/linux/bad.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sys/syscall.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(BadSyscallTest, NotImplemented) {
- // get_kernel_syms is not supported in Linux > 2.6, and not implemented in
- // gVisor.
- EXPECT_THAT(syscall(SYS_get_kernel_syms), SyscallFailsWithErrno(ENOSYS));
-}
-
-TEST(BadSyscallTest, NegativeOne) {
- EXPECT_THAT(syscall(-1), SyscallFailsWithErrno(ENOSYS));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/base_poll_test.cc b/test/syscalls/linux/base_poll_test.cc
deleted file mode 100644
index ab7a19dd0..000000000
--- a/test/syscalls/linux/base_poll_test.cc
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/base_poll_test.h"
-
-#include <sys/syscall.h>
-#include <sys/types.h>
-#include <syscall.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "absl/memory/memory.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-static volatile int timer_fired = 0;
-static void SigAlarmHandler(int, siginfo_t*, void*) { timer_fired = 1; }
-
-BasePollTest::BasePollTest() {
- // Register our SIGALRM handler, but save the original so we can restore in
- // the destructor.
- struct sigaction sa = {};
- sa.sa_sigaction = SigAlarmHandler;
- sigfillset(&sa.sa_mask);
- TEST_PCHECK(sigaction(SIGALRM, &sa, &original_alarm_sa_) == 0);
-}
-
-BasePollTest::~BasePollTest() {
- ClearTimer();
- TEST_PCHECK(sigaction(SIGALRM, &original_alarm_sa_, nullptr) == 0);
-}
-
-void BasePollTest::SetTimer(absl::Duration duration) {
- pid_t tgid = getpid();
- pid_t tid = gettid();
- ClearTimer();
-
- // Create a new timer thread.
- timer_ = absl::make_unique<TimerThread>(absl::Now() + duration, tgid, tid);
-}
-
-bool BasePollTest::TimerFired() const { return timer_fired; }
-
-void BasePollTest::ClearTimer() {
- timer_.reset();
- timer_fired = 0;
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/base_poll_test.h b/test/syscalls/linux/base_poll_test.h
deleted file mode 100644
index 0d4a6701e..000000000
--- a/test/syscalls/linux/base_poll_test.h
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_BASE_POLL_TEST_H_
-#define GVISOR_TEST_SYSCALLS_BASE_POLL_TEST_H_
-
-#include <signal.h>
-#include <sys/syscall.h>
-#include <sys/types.h>
-#include <syscall.h>
-#include <time.h>
-#include <unistd.h>
-
-#include <memory>
-
-#include "gtest/gtest.h"
-#include "absl/synchronization/mutex.h"
-#include "absl/time/time.h"
-#include "test/util/logging.h"
-#include "test/util/signal_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// TimerThread is a cancelable timer.
-class TimerThread {
- public:
- TimerThread(absl::Time deadline, pid_t tgid, pid_t tid)
- : thread_([=] {
- mu_.Lock();
- mu_.AwaitWithDeadline(absl::Condition(&cancel_), deadline);
- if (!cancel_) {
- TEST_PCHECK(tgkill(tgid, tid, SIGALRM) == 0);
- }
- mu_.Unlock();
- }) {}
-
- ~TimerThread() { Cancel(); }
-
- void Cancel() {
- absl::MutexLock ml(&mu_);
- cancel_ = true;
- }
-
- private:
- mutable absl::Mutex mu_;
- bool cancel_ ABSL_GUARDED_BY(mu_) = false;
-
- // Must be last to ensure that the destructor for the thread is run before
- // any other member of the object is destroyed.
- ScopedThread thread_;
-};
-
-// Base test fixture for poll, select, ppoll, and pselect tests.
-//
-// This fixture makes use of SIGALRM. The handler is saved in SetUp() and
-// restored in TearDown().
-class BasePollTest : public ::testing::Test {
- protected:
- BasePollTest();
- ~BasePollTest() override;
-
- // Sets a timer that will send a signal to the calling thread after
- // `duration`.
- void SetTimer(absl::Duration duration);
-
- // Returns true if the timer has fired.
- bool TimerFired() const;
-
- // Stops the pending timer (if any) and clear the "fired" state.
- void ClearTimer();
-
- private:
- // Thread that implements the timer. If the timer is stopped, timer_ is null.
- //
- // We have to use a thread for this purpose because tests using this fixture
- // expect to be interrupted by the timer signal, but itimers/alarm(2) send
- // thread-group-directed signals, which may be handled by any thread in the
- // test process.
- std::unique_ptr<TimerThread> timer_;
-
- // The original SIGALRM handler, to restore in destructor.
- struct sigaction original_alarm_sa_;
-};
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_BASE_POLL_TEST_H_
diff --git a/test/syscalls/linux/bind.cc b/test/syscalls/linux/bind.cc
deleted file mode 100644
index de8cca53b..000000000
--- a/test/syscalls/linux/bind.cc
+++ /dev/null
@@ -1,146 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <stdio.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST_P(AllSocketPairTest, Bind) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-}
-
-TEST_P(AllSocketPairTest, BindTooLong) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- // first_addr is a sockaddr_storage being used as a sockaddr_un. Use the full
- // length which is longer than expected for a Unix socket.
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sizeof(sockaddr_storage)),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_P(AllSocketPairTest, DoubleBindSocket) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- EXPECT_THAT(
- bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- // Linux 4.09 returns EINVAL here, but some time before 4.19 it switched
- // to EADDRINUSE.
- AnyOf(SyscallFailsWithErrno(EADDRINUSE), SyscallFailsWithErrno(EINVAL)));
-}
-
-TEST_P(AllSocketPairTest, GetLocalAddr) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
- socklen_t addressLength = sockets->first_addr_size();
- struct sockaddr_storage address = {};
- ASSERT_THAT(getsockname(sockets->first_fd(), (struct sockaddr*)(&address),
- &addressLength),
- SyscallSucceeds());
- EXPECT_EQ(
- 0, memcmp(&address, sockets->first_addr(), sockets->first_addr_size()));
-}
-
-TEST_P(AllSocketPairTest, GetLocalAddrWithoutBind) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- socklen_t addressLength = sockets->first_addr_size();
- struct sockaddr_storage received_address = {};
- ASSERT_THAT(
- getsockname(sockets->first_fd(), (struct sockaddr*)(&received_address),
- &addressLength),
- SyscallSucceeds());
- struct sockaddr_storage want_address = {};
- want_address.ss_family = sockets->first_addr()->sa_family;
- EXPECT_EQ(0, memcmp(&received_address, &want_address, addressLength));
-}
-
-TEST_P(AllSocketPairTest, GetRemoteAddressWithoutConnect) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- socklen_t addressLength = sockets->first_addr_size();
- struct sockaddr_storage address = {};
- ASSERT_THAT(getpeername(sockets->second_fd(), (struct sockaddr*)(&address),
- &addressLength),
- SyscallFailsWithErrno(ENOTCONN));
-}
-
-TEST_P(AllSocketPairTest, DoubleBindAddress) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- EXPECT_THAT(bind(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallFailsWithErrno(EADDRINUSE));
-}
-
-TEST_P(AllSocketPairTest, Unbind) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
- ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds());
-
- // Filesystem Unix sockets do not release their address when closed.
- if (sockets->first_addr()->sa_data[0] != 0) {
- ASSERT_THAT(bind(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallFailsWithErrno(EADDRINUSE));
- return;
- }
-
- ASSERT_THAT(bind(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
- ASSERT_THAT(close(sockets->release_second_fd()), SyscallSucceeds());
-}
-
-INSTANTIATE_TEST_SUITE_P(
- AllUnixDomainSockets, AllSocketPairTest,
- ::testing::ValuesIn(VecCat<SocketPairKind>(
- ApplyVec<SocketPairKind>(
- FilesystemUnboundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_DGRAM,
- SOCK_SEQPACKET},
- List<int>{0, SOCK_NONBLOCK})),
- ApplyVec<SocketPairKind>(
- AbstractUnboundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_DGRAM,
- SOCK_SEQPACKET},
- List<int>{0, SOCK_NONBLOCK})))));
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/brk.cc b/test/syscalls/linux/brk.cc
deleted file mode 100644
index a03a44465..000000000
--- a/test/syscalls/linux/brk.cc
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <stdint.h>
-#include <sys/syscall.h>
-#include <unistd.h>
-
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-TEST(BrkTest, BrkSyscallReturnsOldBrkOnFailure) {
- auto old_brk = sbrk(0);
- EXPECT_THAT(syscall(SYS_brk, reinterpret_cast<void*>(-1)),
- SyscallSucceedsWithValue(reinterpret_cast<uintptr_t>(old_brk)));
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/chdir.cc b/test/syscalls/linux/chdir.cc
deleted file mode 100644
index 3182c228b..000000000
--- a/test/syscalls/linux/chdir.cc
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <linux/limits.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/un.h>
-
-#include "gtest/gtest.h"
-#include "test/util/capability_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(ChdirTest, Success) {
- auto old_dir = GetAbsoluteTestTmpdir();
- auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- EXPECT_THAT(chdir(temp_dir.path().c_str()), SyscallSucceeds());
- // Temp path destructor deletes the newly created tmp dir and Sentry rejects
- // saving when its current dir is still pointing to the path. Switch to a
- // permanent path here.
- EXPECT_THAT(chdir(old_dir.c_str()), SyscallSucceeds());
-}
-
-TEST(ChdirTest, PermissionDenied) {
- // Drop capabilities that allow us to override directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
-
- auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0666 /* mode */));
- EXPECT_THAT(chdir(temp_dir.path().c_str()), SyscallFailsWithErrno(EACCES));
-}
-
-TEST(ChdirTest, NotDir) {
- auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- EXPECT_THAT(chdir(temp_file.path().c_str()), SyscallFailsWithErrno(ENOTDIR));
-}
-
-TEST(ChdirTest, NotExist) {
- EXPECT_THAT(chdir("/foo/bar"), SyscallFailsWithErrno(ENOENT));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/chmod.cc b/test/syscalls/linux/chmod.cc
deleted file mode 100644
index 7e918b9b2..000000000
--- a/test/syscalls/linux/chmod.cc
+++ /dev/null
@@ -1,263 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <string>
-
-#include "gtest/gtest.h"
-#include "test/util/capability_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(ChmodTest, ChmodFileSucceeds) {
- // Drop capabilities that allow us to override file permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
-
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- ASSERT_THAT(chmod(file.path().c_str(), 0466), SyscallSucceeds());
- EXPECT_THAT(open(file.path().c_str(), O_RDWR), SyscallFailsWithErrno(EACCES));
-}
-
-TEST(ChmodTest, ChmodDirSucceeds) {
- // Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
-
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const std::string fileInDir = NewTempAbsPathInDir(dir.path());
-
- ASSERT_THAT(chmod(dir.path().c_str(), 0466), SyscallSucceeds());
- EXPECT_THAT(open(fileInDir.c_str(), O_RDONLY), SyscallFailsWithErrno(EACCES));
-}
-
-TEST(ChmodTest, FchmodFileSucceeds_NoRandomSave) {
- // Drop capabilities that allow us to file directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
-
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0666));
- int fd;
- ASSERT_THAT(fd = open(file.path().c_str(), O_RDWR), SyscallSucceeds());
-
- {
- const DisableSave ds; // File permissions are reduced.
- ASSERT_THAT(fchmod(fd, 0444), SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
- }
-
- EXPECT_THAT(open(file.path().c_str(), O_RDWR), SyscallFailsWithErrno(EACCES));
-}
-
-TEST(ChmodTest, FchmodDirSucceeds_NoRandomSave) {
- // Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
-
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- int fd;
- ASSERT_THAT(fd = open(dir.path().c_str(), O_RDONLY | O_DIRECTORY),
- SyscallSucceeds());
-
- {
- const DisableSave ds; // File permissions are reduced.
- ASSERT_THAT(fchmod(fd, 0), SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
- }
-
- EXPECT_THAT(open(dir.path().c_str(), O_RDONLY),
- SyscallFailsWithErrno(EACCES));
-}
-
-TEST(ChmodTest, FchmodBadF) {
- ASSERT_THAT(fchmod(-1, 0444), SyscallFailsWithErrno(EBADF));
-}
-
-TEST(ChmodTest, FchmodatBadF) {
- ASSERT_THAT(fchmodat(-1, "foo", 0444, 0), SyscallFailsWithErrno(EBADF));
-}
-
-TEST(ChmodTest, FchmodatNotDir) {
- ASSERT_THAT(fchmodat(-1, "", 0444, 0), SyscallFailsWithErrno(ENOENT));
-}
-
-TEST(ChmodTest, FchmodatFileAbsolutePath) {
- // Drop capabilities that allow us to override file permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
-
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- ASSERT_THAT(fchmodat(-1, file.path().c_str(), 0444, 0), SyscallSucceeds());
- EXPECT_THAT(open(file.path().c_str(), O_RDWR), SyscallFailsWithErrno(EACCES));
-}
-
-TEST(ChmodTest, FchmodatDirAbsolutePath) {
- // Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
-
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- int fd;
- ASSERT_THAT(fd = open(dir.path().c_str(), O_RDONLY | O_DIRECTORY),
- SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
-
- ASSERT_THAT(fchmodat(-1, dir.path().c_str(), 0, 0), SyscallSucceeds());
- EXPECT_THAT(open(dir.path().c_str(), O_RDONLY),
- SyscallFailsWithErrno(EACCES));
-}
-
-TEST(ChmodTest, FchmodatFile) {
- // Drop capabilities that allow us to override file permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
-
- auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- int parent_fd;
- ASSERT_THAT(
- parent_fd = open(GetAbsoluteTestTmpdir().c_str(), O_RDONLY | O_DIRECTORY),
- SyscallSucceeds());
-
- ASSERT_THAT(
- fchmodat(parent_fd, std::string(Basename(temp_file.path())).c_str(), 0444,
- 0),
- SyscallSucceeds());
- EXPECT_THAT(close(parent_fd), SyscallSucceeds());
-
- EXPECT_THAT(open(temp_file.path().c_str(), O_RDWR),
- SyscallFailsWithErrno(EACCES));
-}
-
-TEST(ChmodTest, FchmodatDir) {
- // Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
-
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- int parent_fd;
- ASSERT_THAT(
- parent_fd = open(GetAbsoluteTestTmpdir().c_str(), O_RDONLY | O_DIRECTORY),
- SyscallSucceeds());
-
- int fd;
- ASSERT_THAT(fd = open(dir.path().c_str(), O_RDONLY | O_DIRECTORY),
- SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
-
- ASSERT_THAT(
- fchmodat(parent_fd, std::string(Basename(dir.path())).c_str(), 0, 0),
- SyscallSucceeds());
- EXPECT_THAT(close(parent_fd), SyscallSucceeds());
-
- EXPECT_THAT(open(dir.path().c_str(), O_RDONLY | O_DIRECTORY),
- SyscallFailsWithErrno(EACCES));
-}
-
-TEST(ChmodTest, ChmodDowngradeWritability_NoRandomSave) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0666));
-
- int fd;
- ASSERT_THAT(fd = open(file.path().c_str(), O_RDWR), SyscallSucceeds());
-
- const DisableSave ds; // Permissions are dropped.
- ASSERT_THAT(chmod(file.path().c_str(), 0444), SyscallSucceeds());
- EXPECT_THAT(write(fd, "hello", 5), SyscallSucceedsWithValue(5));
-
- EXPECT_THAT(close(fd), SyscallSucceeds());
-}
-
-TEST(ChmodTest, ChmodFileToNoPermissionsSucceeds) {
- // Drop capabilities that allow us to override file permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
-
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0666));
-
- ASSERT_THAT(chmod(file.path().c_str(), 0), SyscallSucceeds());
-
- EXPECT_THAT(open(file.path().c_str(), O_RDONLY),
- SyscallFailsWithErrno(EACCES));
-}
-
-TEST(ChmodTest, FchmodDowngradeWritability_NoRandomSave) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- int fd;
- ASSERT_THAT(fd = open(file.path().c_str(), O_RDWR | O_CREAT, 0666),
- SyscallSucceeds());
-
- const DisableSave ds; // Permissions are dropped.
- ASSERT_THAT(fchmod(fd, 0444), SyscallSucceeds());
- EXPECT_THAT(write(fd, "hello", 5), SyscallSucceedsWithValue(5));
-
- EXPECT_THAT(close(fd), SyscallSucceeds());
-}
-
-TEST(ChmodTest, FchmodFileToNoPermissionsSucceeds_NoRandomSave) {
- // Drop capabilities that allow us to override file permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
-
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0666));
-
- int fd;
- ASSERT_THAT(fd = open(file.path().c_str(), O_RDWR), SyscallSucceeds());
-
- {
- const DisableSave ds; // Permissions are dropped.
- ASSERT_THAT(fchmod(fd, 0), SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
- }
-
- EXPECT_THAT(open(file.path().c_str(), O_RDONLY),
- SyscallFailsWithErrno(EACCES));
-}
-
-// Verify that we can get a RW FD after chmod, even if a RO fd is left open.
-TEST(ChmodTest, ChmodWritableWithOpenFD) {
- // FIXME(b/72455313): broken on hostfs.
- if (IsRunningOnGvisor()) {
- return;
- }
-
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0444));
-
- FileDescriptor fd1 = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY));
-
- ASSERT_THAT(fchmod(fd1.get(), 0644), SyscallSucceeds());
-
- // This FD is writable, even though fd1 has a read-only reference to the file.
- FileDescriptor fd2 = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
-
- // fd1 is not writable, but fd2 is.
- char c = 'a';
- EXPECT_THAT(WriteFd(fd1.get(), &c, 1), SyscallFailsWithErrno(EBADF));
- EXPECT_THAT(WriteFd(fd2.get(), &c, 1), SyscallSucceedsWithValue(1));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/chown.cc b/test/syscalls/linux/chown.cc
deleted file mode 100644
index 2e82f0b3a..000000000
--- a/test/syscalls/linux/chown.cc
+++ /dev/null
@@ -1,200 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <grp.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <vector>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/synchronization/notification.h"
-#include "test/util/capability_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-DEFINE_int32(scratch_uid1, 65534, "first scratch UID");
-DEFINE_int32(scratch_uid2, 65533, "second scratch UID");
-DEFINE_int32(scratch_gid, 65534, "first scratch GID");
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(ChownTest, FchownBadF) {
- ASSERT_THAT(fchown(-1, 0, 0), SyscallFailsWithErrno(EBADF));
-}
-
-TEST(ChownTest, FchownatBadF) {
- ASSERT_THAT(fchownat(-1, "fff", 0, 0, 0), SyscallFailsWithErrno(EBADF));
-}
-
-TEST(ChownTest, FchownatEmptyPath) {
- const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const auto fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY | O_RDONLY));
- ASSERT_THAT(fchownat(fd.get(), "", 0, 0, 0), SyscallFailsWithErrno(ENOENT));
-}
-
-using Chown =
- std::function<PosixError(const std::string&, uid_t owner, gid_t group)>;
-
-class ChownParamTest : public ::testing::TestWithParam<Chown> {};
-
-TEST_P(ChownParamTest, ChownFileSucceeds) {
- if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_CHOWN))) {
- ASSERT_NO_ERRNO(SetCapability(CAP_CHOWN, false));
- }
-
- const auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- // At least *try* setting to a group other than the EGID.
- gid_t gid;
- EXPECT_THAT(gid = getegid(), SyscallSucceeds());
- int num_groups;
- EXPECT_THAT(num_groups = getgroups(0, nullptr), SyscallSucceeds());
- if (num_groups > 0) {
- std::vector<gid_t> list(num_groups);
- EXPECT_THAT(getgroups(list.size(), list.data()), SyscallSucceeds());
- gid = list[0];
- }
-
- EXPECT_NO_ERRNO(GetParam()(file.path(), geteuid(), gid));
-
- struct stat s = {};
- ASSERT_THAT(stat(file.path().c_str(), &s), SyscallSucceeds());
- EXPECT_EQ(s.st_uid, geteuid());
- EXPECT_EQ(s.st_gid, gid);
-}
-
-TEST_P(ChownParamTest, ChownFilePermissionDenied) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID)));
-
- const auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0777));
-
- // Drop privileges and change IDs only in child thread, or else this parent
- // thread won't be able to open some log files after the test ends.
- ScopedThread([&] {
- // Drop privileges.
- if (HaveCapability(CAP_CHOWN).ValueOrDie()) {
- EXPECT_NO_ERRNO(SetCapability(CAP_CHOWN, false));
- }
-
- // Change EUID and EGID.
- //
- // See note about POSIX below.
- EXPECT_THAT(syscall(SYS_setresgid, -1, FLAGS_scratch_gid, -1),
- SyscallSucceeds());
- EXPECT_THAT(syscall(SYS_setresuid, -1, FLAGS_scratch_uid1, -1),
- SyscallSucceeds());
-
- EXPECT_THAT(GetParam()(file.path(), geteuid(), getegid()),
- PosixErrorIs(EPERM, ::testing::ContainsRegex("chown")));
- });
-}
-
-TEST_P(ChownParamTest, ChownFileSucceedsAsRoot) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_CHOWN))));
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_SETUID))));
-
- const std::string filename = NewTempAbsPath();
-
- absl::Notification fileCreated, fileChowned;
- // Change UID only in child thread, or else this parent thread won't be able
- // to open some log files after the test ends.
- ScopedThread t([&] {
- // POSIX requires that all threads in a process share the same UIDs, so
- // the NPTL setresuid wrappers use signals to make all threads execute the
- // setresuid syscall. However, we want this thread to have its own set of
- // credentials different from the parent process, so we use the raw
- // syscall.
- EXPECT_THAT(syscall(SYS_setresuid, -1, FLAGS_scratch_uid2, -1),
- SyscallSucceeds());
-
- // Create file and immediately close it.
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_CREAT | O_RDWR, 0644));
- fd.reset(); // Close the fd.
-
- fileCreated.Notify();
- fileChowned.WaitForNotification();
-
- EXPECT_THAT(open(filename.c_str(), O_RDWR), SyscallFailsWithErrno(EACCES));
- FileDescriptor fd2 = ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_RDONLY));
- });
-
- fileCreated.WaitForNotification();
-
- // Set file's owners to someone different.
- EXPECT_NO_ERRNO(GetParam()(filename, FLAGS_scratch_uid1, FLAGS_scratch_gid));
-
- struct stat s;
- EXPECT_THAT(stat(filename.c_str(), &s), SyscallSucceeds());
- EXPECT_EQ(s.st_uid, FLAGS_scratch_uid1);
- EXPECT_EQ(s.st_gid, FLAGS_scratch_gid);
-
- fileChowned.Notify();
-}
-
-PosixError errorFromReturn(const std::string& name, int ret) {
- if (ret == -1) {
- return PosixError(errno, absl::StrCat(name, " failed"));
- }
- return NoError();
-}
-
-INSTANTIATE_TEST_SUITE_P(
- ChownKinds, ChownParamTest,
- ::testing::Values(
- [](const std::string& path, uid_t owner, gid_t group) -> PosixError {
- int rc = chown(path.c_str(), owner, group);
- MaybeSave();
- return errorFromReturn("chown", rc);
- },
- [](const std::string& path, uid_t owner, gid_t group) -> PosixError {
- int rc = lchown(path.c_str(), owner, group);
- MaybeSave();
- return errorFromReturn("lchown", rc);
- },
- [](const std::string& path, uid_t owner, gid_t group) -> PosixError {
- ASSIGN_OR_RETURN_ERRNO(auto fd, Open(path, O_RDWR));
- int rc = fchown(fd.get(), owner, group);
- MaybeSave();
- return errorFromReturn("fchown", rc);
- },
- [](const std::string& path, uid_t owner, gid_t group) -> PosixError {
- ASSIGN_OR_RETURN_ERRNO(auto fd, Open(path, O_RDWR));
- int rc = fchownat(fd.get(), "", owner, group, AT_EMPTY_PATH);
- MaybeSave();
- return errorFromReturn("fchownat-fd", rc);
- },
- [](const std::string& path, uid_t owner, gid_t group) -> PosixError {
- ASSIGN_OR_RETURN_ERRNO(auto dirfd, Open(std::string(Dirname(path)),
- O_DIRECTORY | O_RDONLY));
- int rc = fchownat(dirfd.get(), std::string(Basename(path)).c_str(),
- owner, group, 0);
- MaybeSave();
- return errorFromReturn("fchownat-dirfd", rc);
- }));
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/chroot.cc b/test/syscalls/linux/chroot.cc
deleted file mode 100644
index 498c45f16..000000000
--- a/test/syscalls/linux/chroot.cc
+++ /dev/null
@@ -1,366 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <fcntl.h>
-#include <stddef.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <syscall.h>
-#include <unistd.h>
-#include <string>
-#include <vector>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "absl/strings/str_cat.h"
-#include "absl/strings/str_split.h"
-#include "absl/strings/string_view.h"
-#include "test/util/capability_util.h"
-#include "test/util/cleanup.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/mount_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-using ::testing::HasSubstr;
-using ::testing::Not;
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(ChrootTest, Success) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));
-
- auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- EXPECT_THAT(chroot(temp_dir.path().c_str()), SyscallSucceeds());
-}
-
-TEST(ChrootTest, PermissionDenied) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));
-
- // CAP_DAC_READ_SEARCH and CAP_DAC_OVERRIDE may override Execute permission on
- // directories.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
-
- auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0666 /* mode */));
- EXPECT_THAT(chroot(temp_dir.path().c_str()), SyscallFailsWithErrno(EACCES));
-}
-
-TEST(ChrootTest, NotDir) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));
-
- auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- EXPECT_THAT(chroot(temp_file.path().c_str()), SyscallFailsWithErrno(ENOTDIR));
-}
-
-TEST(ChrootTest, NotExist) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));
-
- EXPECT_THAT(chroot("/foo/bar"), SyscallFailsWithErrno(ENOENT));
-}
-
-TEST(ChrootTest, WithoutCapability) {
- // Unset CAP_SYS_CHROOT.
- ASSERT_NO_ERRNO(SetCapability(CAP_SYS_CHROOT, false));
-
- auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- EXPECT_THAT(chroot(temp_dir.path().c_str()), SyscallFailsWithErrno(EPERM));
-}
-
-TEST(ChrootTest, CreatesNewRoot) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));
-
- // Grab the initial cwd.
- char initial_cwd[1024];
- ASSERT_THAT(syscall(__NR_getcwd, initial_cwd, sizeof(initial_cwd)),
- SyscallSucceeds());
-
- auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto file_in_new_root =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(new_root.path()));
-
- // chroot into new_root.
- ASSERT_THAT(chroot(new_root.path().c_str()), SyscallSucceeds());
-
- // getcwd should return "(unreachable)" followed by the initial_cwd.
- char cwd[1024];
- ASSERT_THAT(syscall(__NR_getcwd, cwd, sizeof(cwd)), SyscallSucceeds());
- std::string expected_cwd = "(unreachable)";
- expected_cwd += initial_cwd;
- EXPECT_STREQ(cwd, expected_cwd.c_str());
-
- // Should not be able to stat file by its full path.
- struct stat statbuf;
- EXPECT_THAT(stat(file_in_new_root.path().c_str(), &statbuf),
- SyscallFailsWithErrno(ENOENT));
-
- // Should be able to stat file at new rooted path.
- auto basename = std::string(Basename(file_in_new_root.path()));
- auto rootedFile = "/" + basename;
- ASSERT_THAT(stat(rootedFile.c_str(), &statbuf), SyscallSucceeds());
-
- // Should be able to stat cwd at '.' even though it's outside root.
- ASSERT_THAT(stat(".", &statbuf), SyscallSucceeds());
-
- // chdir into new root.
- ASSERT_THAT(chdir("/"), SyscallSucceeds());
-
- // getcwd should return "/".
- EXPECT_THAT(syscall(__NR_getcwd, cwd, sizeof(cwd)), SyscallSucceeds());
- EXPECT_STREQ(cwd, "/");
-
- // Statting '.', '..', '/', and '/..' all return the same dev and inode.
- struct stat statbuf_dot;
- ASSERT_THAT(stat(".", &statbuf_dot), SyscallSucceeds());
- struct stat statbuf_dotdot;
- ASSERT_THAT(stat("..", &statbuf_dotdot), SyscallSucceeds());
- EXPECT_EQ(statbuf_dot.st_dev, statbuf_dotdot.st_dev);
- EXPECT_EQ(statbuf_dot.st_ino, statbuf_dotdot.st_ino);
- struct stat statbuf_slash;
- ASSERT_THAT(stat("/", &statbuf_slash), SyscallSucceeds());
- EXPECT_EQ(statbuf_dot.st_dev, statbuf_slash.st_dev);
- EXPECT_EQ(statbuf_dot.st_ino, statbuf_slash.st_ino);
- struct stat statbuf_slashdotdot;
- ASSERT_THAT(stat("/..", &statbuf_slashdotdot), SyscallSucceeds());
- EXPECT_EQ(statbuf_dot.st_dev, statbuf_slashdotdot.st_dev);
- EXPECT_EQ(statbuf_dot.st_ino, statbuf_slashdotdot.st_ino);
-}
-
-TEST(ChrootTest, DotDotFromOpenFD) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));
-
- auto dir_outside_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto fd = ASSERT_NO_ERRNO_AND_VALUE(
- Open(dir_outside_root.path(), O_RDONLY | O_DIRECTORY));
- auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- // chroot into new_root.
- ASSERT_THAT(chroot(new_root.path().c_str()), SyscallSucceeds());
-
- // openat on fd with path .. will succeed.
- int other_fd;
- ASSERT_THAT(other_fd = openat(fd.get(), "..", O_RDONLY), SyscallSucceeds());
- EXPECT_THAT(close(other_fd), SyscallSucceeds());
-
- // getdents on fd should not error.
- char buf[1024];
- ASSERT_THAT(syscall(SYS_getdents, fd.get(), buf, sizeof(buf)),
- SyscallSucceeds());
-}
-
-// Test that link resolution in a chroot can escape the root by following an
-// open proc fd.
-TEST(ChrootTest, ProcFdLinkResolutionInChroot) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));
-
- const TempPath file_outside_chroot =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file_outside_chroot.path(), O_RDONLY));
-
- const FileDescriptor proc_fd = ASSERT_NO_ERRNO_AND_VALUE(
- Open("/proc", O_DIRECTORY | O_RDONLY | O_CLOEXEC));
-
- auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- ASSERT_THAT(chroot(temp_dir.path().c_str()), SyscallSucceeds());
-
- // Opening relative to an already open fd to a node outside the chroot works.
- const FileDescriptor proc_self_fd = ASSERT_NO_ERRNO_AND_VALUE(
- OpenAt(proc_fd.get(), "self/fd", O_DIRECTORY | O_RDONLY | O_CLOEXEC));
-
- // Proc fd symlinks can escape the chroot if the fd the symlink refers to
- // refers to an object outside the chroot.
- struct stat s = {};
- EXPECT_THAT(
- fstatat(proc_self_fd.get(), absl::StrCat(fd.get()).c_str(), &s, 0),
- SyscallSucceeds());
-
- // Try to stat the stdin fd. Internally, this is handled differently from a
- // proc fd entry pointing to a file, since stdin is backed by a host fd, and
- // isn't a walkable path on the filesystem inside the sandbox.
- EXPECT_THAT(fstatat(proc_self_fd.get(), "0", &s, 0), SyscallSucceeds());
-}
-
-// This test will verify that when you hold a fd to proc before entering
-// a chroot that any files inside the chroot will appear rooted to the
-// base chroot when examining /proc/self/fd/{num}.
-TEST(ChrootTest, ProcMemSelfFdsNoEscapeProcOpen) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));
-
- // Get a FD to /proc before we enter the chroot.
- const FileDescriptor proc =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/proc", O_RDONLY));
-
- // Create and enter a chroot directory.
- const auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- ASSERT_THAT(chroot(temp_dir.path().c_str()), SyscallSucceeds());
-
- // Open a file inside the chroot at /foo.
- const FileDescriptor foo =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/foo", O_CREAT | O_RDONLY, 0644));
-
- // Examine /proc/self/fd/{foo_fd} to see if it exposes the fact that we're
- // inside a chroot, the path should be /foo and NOT {chroot_dir}/foo.
- const std::string fd_path = absl::StrCat("self/fd/", foo.get());
- char buf[1024] = {};
- size_t bytes_read = 0;
- ASSERT_THAT(bytes_read =
- readlinkat(proc.get(), fd_path.c_str(), buf, sizeof(buf) - 1),
- SyscallSucceeds());
-
- // The link should resolve to something.
- ASSERT_GT(bytes_read, 0);
-
- // Assert that the link doesn't contain the chroot path and is only /foo.
- EXPECT_STREQ(buf, "/foo");
-}
-
-// This test will verify that a file inside a chroot when mmapped will not
-// expose the full file path via /proc/self/maps and instead honor the chroot.
-TEST(ChrootTest, ProcMemSelfMapsNoEscapeProcOpen) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));
-
- // Get a FD to /proc before we enter the chroot.
- const FileDescriptor proc =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/proc", O_RDONLY));
-
- // Create and enter a chroot directory.
- const auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- ASSERT_THAT(chroot(temp_dir.path().c_str()), SyscallSucceeds());
-
- // Open a file inside the chroot at /foo.
- const FileDescriptor foo =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/foo", O_CREAT | O_RDONLY, 0644));
-
- // Mmap the newly created file.
- void* foo_map = mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE,
- foo.get(), 0);
- ASSERT_THAT(reinterpret_cast<int64_t>(foo_map), SyscallSucceeds());
-
- // Always unmap.
- auto cleanup_map = Cleanup(
- [&] { EXPECT_THAT(munmap(foo_map, kPageSize), SyscallSucceeds()); });
-
- // Examine /proc/self/maps to be sure that /foo doesn't appear to be
- // mapped with the full chroot path.
- const FileDescriptor maps =
- ASSERT_NO_ERRNO_AND_VALUE(OpenAt(proc.get(), "self/maps", O_RDONLY));
-
- size_t bytes_read = 0;
- char buf[8 * 1024] = {};
- ASSERT_THAT(bytes_read = ReadFd(maps.get(), buf, sizeof(buf)),
- SyscallSucceeds());
-
- // The maps file should have something.
- ASSERT_GT(bytes_read, 0);
-
- // Finally we want to make sure the maps don't contain the chroot path
- ASSERT_EQ(std::string(buf, bytes_read).find(temp_dir.path()),
- std::string::npos);
-}
-
-// Test that mounts outside the chroot will not appear in /proc/self/mounts or
-// /proc/self/mountinfo.
-TEST(ChrootTest, ProcMountsMountinfoNoEscape) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));
-
- // We are going to create some mounts and then chroot. In order to be able to
- // unmount the mounts after the test run, we must chdir to the root and use
- // relative paths for all mounts. That way, as long as we never chdir into
- // the new root, we can access the mounts via relative paths and unmount them.
- ASSERT_THAT(chdir("/"), SyscallSucceeds());
-
- // Create nested tmpfs mounts. Note the use of relative paths in Mount calls.
- auto const outer_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto const outer_mount = ASSERT_NO_ERRNO_AND_VALUE(Mount(
- "none", JoinPath(".", outer_dir.path()), "tmpfs", 0, "mode=0700", 0));
-
- auto const inner_dir =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(outer_dir.path()));
- auto const inner_mount = ASSERT_NO_ERRNO_AND_VALUE(Mount(
- "none", JoinPath(".", inner_dir.path()), "tmpfs", 0, "mode=0700", 0));
-
- // Filenames that will be checked for mounts, all relative to /proc dir.
- std::string paths[3] = {"mounts", "self/mounts", "self/mountinfo"};
-
- for (const std::string& path : paths) {
- // We should have both inner and outer mounts.
- const std::string contents =
- ASSERT_NO_ERRNO_AND_VALUE(GetContents(JoinPath("/proc", path)));
- EXPECT_THAT(contents, AllOf(HasSubstr(outer_dir.path()),
- HasSubstr(inner_dir.path())));
- // We better have at least two mounts: the mounts we created plus the root.
- std::vector<absl::string_view> submounts =
- absl::StrSplit(contents, '\n', absl::SkipWhitespace());
- EXPECT_GT(submounts.size(), 2);
- }
-
- // Get a FD to /proc before we enter the chroot.
- const FileDescriptor proc =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/proc", O_RDONLY));
-
- // Chroot to outer mount.
- ASSERT_THAT(chroot(outer_dir.path().c_str()), SyscallSucceeds());
-
- for (const std::string& path : paths) {
- const FileDescriptor proc_file =
- ASSERT_NO_ERRNO_AND_VALUE(OpenAt(proc.get(), path, O_RDONLY));
-
- // Only two mounts visible from this chroot: the inner and outer. Both
- // paths should be relative to the new chroot.
- const std::string contents =
- ASSERT_NO_ERRNO_AND_VALUE(GetContentsFD(proc_file.get()));
- EXPECT_THAT(contents,
- AllOf(HasSubstr(absl::StrCat(Basename(inner_dir.path()))),
- Not(HasSubstr(outer_dir.path())),
- Not(HasSubstr(inner_dir.path()))));
- std::vector<absl::string_view> submounts =
- absl::StrSplit(contents, '\n', absl::SkipWhitespace());
- EXPECT_EQ(submounts.size(), 2);
- }
-
- // Chroot to inner mount. We must use an absolute path accessible to our
- // chroot.
- const std::string inner_dir_basename =
- absl::StrCat("/", Basename(inner_dir.path()));
- ASSERT_THAT(chroot(inner_dir_basename.c_str()), SyscallSucceeds());
-
- for (const std::string& path : paths) {
- const FileDescriptor proc_file =
- ASSERT_NO_ERRNO_AND_VALUE(OpenAt(proc.get(), path, O_RDONLY));
- const std::string contents =
- ASSERT_NO_ERRNO_AND_VALUE(GetContentsFD(proc_file.get()));
-
- // Only the inner mount visible from this chroot.
- std::vector<absl::string_view> submounts =
- absl::StrSplit(contents, '\n', absl::SkipWhitespace());
- EXPECT_EQ(submounts.size(), 1);
- }
-
- // Chroot back to ".".
- ASSERT_THAT(chroot("."), SyscallSucceeds());
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/clock_getres.cc b/test/syscalls/linux/clock_getres.cc
deleted file mode 100644
index c408b936c..000000000
--- a/test/syscalls/linux/clock_getres.cc
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sys/time.h>
-#include <time.h>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// clock_getres works regardless of whether or not a timespec is passed.
-TEST(ClockGetres, Timespec) {
- struct timespec ts;
- EXPECT_THAT(clock_getres(CLOCK_MONOTONIC, &ts), SyscallSucceeds());
- EXPECT_THAT(clock_getres(CLOCK_MONOTONIC, nullptr), SyscallSucceeds());
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/clock_gettime.cc b/test/syscalls/linux/clock_gettime.cc
deleted file mode 100644
index c9e3ed6b2..000000000
--- a/test/syscalls/linux/clock_gettime.cc
+++ /dev/null
@@ -1,167 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <pthread.h>
-#include <sys/time.h>
-#include <cerrno>
-#include <cstdint>
-#include <ctime>
-#include <list>
-#include <memory>
-#include <string>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-int64_t clock_gettime_nsecs(clockid_t id) {
- struct timespec ts;
- TEST_PCHECK(clock_gettime(id, &ts) == 0);
- return (ts.tv_sec * 1000000000 + ts.tv_nsec);
-}
-
-// Spin on the CPU for at least ns nanoseconds, based on
-// CLOCK_THREAD_CPUTIME_ID.
-void spin_ns(int64_t ns) {
- int64_t start = clock_gettime_nsecs(CLOCK_THREAD_CPUTIME_ID);
- int64_t end = start + ns;
-
- do {
- constexpr int kLoopCount = 1000000; // large and arbitrary
- // volatile to prevent the compiler from skipping this loop.
- for (volatile int i = 0; i < kLoopCount; i++) {
- }
- } while (clock_gettime_nsecs(CLOCK_THREAD_CPUTIME_ID) < end);
-}
-
-// Test that CLOCK_PROCESS_CPUTIME_ID is a superset of CLOCK_THREAD_CPUTIME_ID.
-TEST(ClockGettime, CputimeId) {
- // TODO(b/128871825,golang.org/issue/10958): Test times out when there is a
- // small number of core because one goroutine starves the others.
- printf("CPUS: %d\n", std::thread::hardware_concurrency());
- SKIP_IF(std::thread::hardware_concurrency() <= 2);
-
- constexpr int kNumThreads = 13; // arbitrary
-
- absl::Duration spin_time = absl::Seconds(1);
-
- // Start off the worker threads and compute the aggregate time spent by
- // the workers. Note that we test CLOCK_PROCESS_CPUTIME_ID by having the
- // workers execute in parallel and verifying that CLOCK_PROCESS_CPUTIME_ID
- // accumulates the runtime of all threads.
- int64_t start = clock_gettime_nsecs(CLOCK_PROCESS_CPUTIME_ID);
-
- // Create a kNumThreads threads.
- std::list<ScopedThread> threads;
- for (int i = 0; i < kNumThreads; i++) {
- threads.emplace_back(
- [spin_time] { spin_ns(absl::ToInt64Nanoseconds(spin_time)); });
- }
- for (auto& t : threads) {
- t.Join();
- }
-
- int64_t end = clock_gettime_nsecs(CLOCK_PROCESS_CPUTIME_ID);
-
- // The aggregate time spent in the worker threads must be at least
- // 'kNumThreads' times the time each thread spun.
- ASSERT_GE(end - start, kNumThreads * absl::ToInt64Nanoseconds(spin_time));
-}
-
-TEST(ClockGettime, JavaThreadTime) {
- clockid_t clockid;
- ASSERT_EQ(0, pthread_getcpuclockid(pthread_self(), &clockid));
- struct timespec tp;
- ASSERT_THAT(clock_getres(clockid, &tp), SyscallSucceeds());
- EXPECT_TRUE(tp.tv_sec > 0 || tp.tv_nsec > 0);
- // A thread cputime is updated each 10msec and there is no approximation
- // if a task is running.
- do {
- ASSERT_THAT(clock_gettime(clockid, &tp), SyscallSucceeds());
- } while (tp.tv_sec == 0 && tp.tv_nsec == 0);
- EXPECT_TRUE(tp.tv_sec > 0 || tp.tv_nsec > 0);
-}
-
-// There is not much to test here, since CLOCK_REALTIME may be discontiguous.
-TEST(ClockGettime, RealtimeWorks) {
- struct timespec tp;
- EXPECT_THAT(clock_gettime(CLOCK_REALTIME, &tp), SyscallSucceeds());
-}
-
-class MonotonicClockTest : public ::testing::TestWithParam<clockid_t> {};
-
-TEST_P(MonotonicClockTest, IsMonotonic) {
- auto end = absl::Now() + absl::Seconds(5);
-
- struct timespec tp;
- EXPECT_THAT(clock_gettime(GetParam(), &tp), SyscallSucceeds());
-
- auto prev = absl::TimeFromTimespec(tp);
- while (absl::Now() < end) {
- EXPECT_THAT(clock_gettime(GetParam(), &tp), SyscallSucceeds());
- auto now = absl::TimeFromTimespec(tp);
- EXPECT_GE(now, prev);
- prev = now;
- }
-}
-
-std::string PrintClockId(::testing::TestParamInfo<clockid_t> info) {
- switch (info.param) {
- case CLOCK_MONOTONIC:
- return "CLOCK_MONOTONIC";
- case CLOCK_MONOTONIC_COARSE:
- return "CLOCK_MONOTONIC_COARSE";
- case CLOCK_MONOTONIC_RAW:
- return "CLOCK_MONOTONIC_RAW";
- case CLOCK_BOOTTIME:
- // CLOCK_BOOTTIME is a monotonic clock.
- return "CLOCK_BOOTTIME";
- default:
- return absl::StrCat(info.param);
- }
-}
-
-INSTANTIATE_TEST_SUITE_P(ClockGettime, MonotonicClockTest,
- ::testing::Values(CLOCK_MONOTONIC,
- CLOCK_MONOTONIC_COARSE,
- CLOCK_MONOTONIC_RAW, CLOCK_BOOTTIME),
- PrintClockId);
-
-TEST(ClockGettime, UnimplementedReturnsEINVAL) {
- SKIP_IF(!IsRunningOnGvisor());
-
- struct timespec tp;
- EXPECT_THAT(clock_gettime(CLOCK_REALTIME_ALARM, &tp),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(clock_gettime(CLOCK_BOOTTIME_ALARM, &tp),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(ClockGettime, InvalidClockIDReturnsEINVAL) {
- struct timespec tp;
- EXPECT_THAT(clock_gettime(-1, &tp), SyscallFailsWithErrno(EINVAL));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/clock_nanosleep.cc b/test/syscalls/linux/clock_nanosleep.cc
deleted file mode 100644
index 52a69d230..000000000
--- a/test/syscalls/linux/clock_nanosleep.cc
+++ /dev/null
@@ -1,153 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <time.h>
-
-#include <atomic>
-#include <utility>
-
-#include "gtest/gtest.h"
-#include "absl/time/time.h"
-#include "test/util/cleanup.h"
-#include "test/util/posix_error.h"
-#include "test/util/signal_util.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-#include "test/util/timer_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// sys_clock_nanosleep is defined because the glibc clock_nanosleep returns
-// error numbers directly and does not set errno. This makes our Syscall
-// matchers look a little weird when expecting failure:
-// "SyscallSucceedsWithValue(ERRNO)".
-int sys_clock_nanosleep(clockid_t clkid, int flags,
- const struct timespec* request,
- struct timespec* remain) {
- return syscall(SYS_clock_nanosleep, clkid, flags, request, remain);
-}
-
-PosixErrorOr<absl::Time> GetTime(clockid_t clk) {
- struct timespec ts = {};
- int rc = clock_gettime(clk, &ts);
- MaybeSave();
- if (rc < 0) {
- return PosixError(errno, "clock_gettime");
- }
- return absl::TimeFromTimespec(ts);
-}
-
-class WallClockNanosleepTest : public ::testing::TestWithParam<clockid_t> {};
-
-TEST_P(WallClockNanosleepTest, InvalidValues) {
- const struct timespec invalid[] = {
- {.tv_sec = -1, .tv_nsec = -1}, {.tv_sec = 0, .tv_nsec = INT32_MIN},
- {.tv_sec = 0, .tv_nsec = INT32_MAX}, {.tv_sec = 0, .tv_nsec = -1},
- {.tv_sec = -1, .tv_nsec = 0},
- };
-
- for (auto const ts : invalid) {
- EXPECT_THAT(sys_clock_nanosleep(GetParam(), 0, &ts, nullptr),
- SyscallFailsWithErrno(EINVAL));
- }
-}
-
-TEST_P(WallClockNanosleepTest, SleepOneSecond) {
- absl::Duration const duration = absl::Seconds(1);
- struct timespec dur = absl::ToTimespec(duration);
-
- absl::Time const before = ASSERT_NO_ERRNO_AND_VALUE(GetTime(GetParam()));
- EXPECT_THAT(RetryEINTR(sys_clock_nanosleep)(GetParam(), 0, &dur, &dur),
- SyscallSucceeds());
- absl::Time const after = ASSERT_NO_ERRNO_AND_VALUE(GetTime(GetParam()));
-
- EXPECT_GE(after - before, duration);
-}
-
-TEST_P(WallClockNanosleepTest, InterruptedNanosleep) {
- absl::Duration const duration = absl::Seconds(60);
- struct timespec dur = absl::ToTimespec(duration);
-
- // Install no-op signal handler for SIGALRM.
- struct sigaction sa = {};
- sigfillset(&sa.sa_mask);
- sa.sa_handler = +[](int signo) {};
- auto const cleanup_sa =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGALRM, sa));
-
- // Measure time since setting the alarm, since the alarm will interrupt the
- // sleep and hence determine how long we sleep.
- absl::Time const before = ASSERT_NO_ERRNO_AND_VALUE(GetTime(GetParam()));
-
- // Set an alarm to go off while sleeping.
- struct itimerval timer = {};
- timer.it_value.tv_sec = 1;
- timer.it_value.tv_usec = 0;
- timer.it_interval.tv_sec = 1;
- timer.it_interval.tv_usec = 0;
- auto const cleanup =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedItimer(ITIMER_REAL, timer));
-
- EXPECT_THAT(sys_clock_nanosleep(GetParam(), 0, &dur, &dur),
- SyscallFailsWithErrno(EINTR));
- absl::Time const after = ASSERT_NO_ERRNO_AND_VALUE(GetTime(GetParam()));
-
- absl::Duration const remaining = absl::DurationFromTimespec(dur);
- EXPECT_GE(after - before + remaining, duration);
-}
-
-TEST_P(WallClockNanosleepTest, SleepUntil) {
- absl::Time const now = ASSERT_NO_ERRNO_AND_VALUE(GetTime(GetParam()));
- absl::Time const until = now + absl::Seconds(2);
- struct timespec ts = absl::ToTimespec(until);
-
- EXPECT_THAT(
- RetryEINTR(sys_clock_nanosleep)(GetParam(), TIMER_ABSTIME, &ts, nullptr),
- SyscallSucceeds());
- absl::Time const after = ASSERT_NO_ERRNO_AND_VALUE(GetTime(GetParam()));
-
- EXPECT_GE(after, until);
-}
-
-INSTANTIATE_TEST_SUITE_P(Sleepers, WallClockNanosleepTest,
- ::testing::Values(CLOCK_REALTIME, CLOCK_MONOTONIC));
-
-TEST(ClockNanosleepProcessTest, SleepFiveSeconds) {
- absl::Duration const kDuration = absl::Seconds(5);
- struct timespec dur = absl::ToTimespec(kDuration);
-
- // Ensure that CLOCK_PROCESS_CPUTIME_ID advances.
- std::atomic<bool> done(false);
- ScopedThread t([&] {
- while (!done.load()) {
- }
- });
- auto const cleanup_done = Cleanup([&] { done.store(true); });
-
- absl::Time const before =
- ASSERT_NO_ERRNO_AND_VALUE(GetTime(CLOCK_PROCESS_CPUTIME_ID));
- EXPECT_THAT(
- RetryEINTR(sys_clock_nanosleep)(CLOCK_PROCESS_CPUTIME_ID, 0, &dur, &dur),
- SyscallSucceeds());
- absl::Time const after =
- ASSERT_NO_ERRNO_AND_VALUE(GetTime(CLOCK_PROCESS_CPUTIME_ID));
- EXPECT_GE(after - before, kDuration);
-}
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/concurrency.cc b/test/syscalls/linux/concurrency.cc
deleted file mode 100644
index 4e0a13f8b..000000000
--- a/test/syscalls/linux/concurrency.cc
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <signal.h>
-#include <atomic>
-
-#include "gtest/gtest.h"
-#include "absl/strings/string_view.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-// Test that a thread that never yields to the OS does not prevent other threads
-// from running.
-TEST(ConcurrencyTest, SingleProcessMultithreaded) {
- std::atomic<int> a(0);
-
- ScopedThread t([&a]() {
- while (!a.load()) {
- }
- });
-
- absl::SleepFor(absl::Seconds(1));
-
- // We are still able to execute code in this thread. The other hasn't
- // permanently hung execution in both threads.
- a.store(1);
-}
-
-// Test that multiple threads in this process continue to execute in parallel,
-// even if an unrelated second process is spawned.
-TEST(ConcurrencyTest, MultiProcessMultithreaded) {
- // In PID 1, start TIDs 1 and 2, and put both to sleep.
- //
- // Start PID 3, which spins for 5 seconds, then exits.
- //
- // TIDs 1 and 2 wake and attempt to Activate, which cannot occur until PID 3
- // exits.
- //
- // Both TIDs 1 and 2 should be woken. If they are not both woken, the test
- // hangs.
- //
- // This is all fundamentally racy. If we are failing to wake all threads, the
- // expectation is that this test becomes flaky, rather than consistently
- // failing.
- //
- // If additional background threads fail to block, we may never schedule the
- // child, at which point this test effectively becomes
- // MultiProcessConcurrency. That's not expected to occur.
-
- std::atomic<int> a(0);
- ScopedThread t([&a]() {
- // Block so that PID 3 can execute and we can wait on its exit.
- absl::SleepFor(absl::Seconds(1));
- while (!a.load()) {
- }
- });
-
- pid_t child_pid = fork();
- if (child_pid == 0) {
- // Busy wait without making any blocking syscalls.
- auto end = absl::Now() + absl::Seconds(5);
- while (absl::Now() < end) {
- }
- _exit(0);
- }
- ASSERT_THAT(child_pid, SyscallSucceeds());
-
- absl::SleepFor(absl::Seconds(1));
-
- // If only TID 1 is woken, thread.Join will hang.
- // If only TID 2 is woken, both will hang.
- a.store(1);
- t.Join();
-
- int status = 0;
- EXPECT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(status));
- EXPECT_EQ(WEXITSTATUS(status), 0);
-}
-
-// Test that multiple processes can execute concurrently, even if one process
-// never yields.
-TEST(ConcurrencyTest, MultiProcessConcurrency) {
-
- pid_t child_pid = fork();
- if (child_pid == 0) {
- while (true) {
- }
- }
- ASSERT_THAT(child_pid, SyscallSucceeds());
-
- absl::SleepFor(absl::Seconds(5));
-
- // We are still able to execute code in this process. The other hasn't
- // permanently hung execution in both processes.
- ASSERT_THAT(kill(child_pid, SIGKILL), SyscallSucceeds());
- int status = 0;
-
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
- ASSERT_TRUE(WIFSIGNALED(status));
- ASSERT_EQ(WTERMSIG(status), SIGKILL);
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/creat.cc b/test/syscalls/linux/creat.cc
deleted file mode 100644
index 3c270d6da..000000000
--- a/test/syscalls/linux/creat.cc
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include <string>
-
-#include "gtest/gtest.h"
-#include "test/util/fs_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-constexpr int kMode = 0666;
-
-TEST(CreatTest, CreatCreatesNewFile) {
- std::string const path = NewTempAbsPath();
- struct stat buf;
- int fd;
- ASSERT_THAT(stat(path.c_str(), &buf), SyscallFailsWithErrno(ENOENT));
- ASSERT_THAT(fd = creat(path.c_str(), kMode), SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
- EXPECT_THAT(stat(path.c_str(), &buf), SyscallSucceeds());
-}
-
-TEST(CreatTest, CreatTruncatesExistingFile) {
- auto temp_path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- int fd;
- ASSERT_NO_ERRNO(SetContents(temp_path.path(), "non-empty"));
- ASSERT_THAT(fd = creat(temp_path.path().c_str(), kMode), SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
- std::string new_contents;
- ASSERT_NO_ERRNO(GetContents(temp_path.path(), &new_contents));
- EXPECT_EQ("", new_contents);
-}
-
-TEST(CreatTest, CreatWithNameTooLong) {
- // Start with a unique name, and pad it to NAME_MAX + 1;
- std::string name = NewTempRelPath();
- int padding = (NAME_MAX + 1) - name.size();
- name.append(padding, 'x');
- const std::string& path = JoinPath(GetAbsoluteTestTmpdir(), name);
-
- // Creation should return ENAMETOOLONG.
- ASSERT_THAT(creat(path.c_str(), kMode), SyscallFailsWithErrno(ENAMETOOLONG));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/dev.cc b/test/syscalls/linux/dev.cc
deleted file mode 100644
index 4dd302eed..000000000
--- a/test/syscalls/linux/dev.cc
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <vector>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(DevTest, LseekDevUrandom) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/urandom", O_RDONLY));
- EXPECT_THAT(lseek(fd.get(), -10, SEEK_CUR), SyscallSucceeds());
- EXPECT_THAT(lseek(fd.get(), -10, SEEK_SET), SyscallSucceeds());
- EXPECT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceeds());
-}
-
-TEST(DevTest, LseekDevNull) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/null", O_RDONLY));
- EXPECT_THAT(lseek(fd.get(), -10, SEEK_CUR), SyscallSucceeds());
- EXPECT_THAT(lseek(fd.get(), -10, SEEK_SET), SyscallSucceeds());
- EXPECT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceeds());
- EXPECT_THAT(lseek(fd.get(), 0, SEEK_END), SyscallSucceeds());
-}
-
-TEST(DevTest, LseekDevZero) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDONLY));
- EXPECT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceeds());
- EXPECT_THAT(lseek(fd.get(), 0, SEEK_END), SyscallSucceeds());
-}
-
-TEST(DevTest, LseekDevFull) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/full", O_RDONLY));
- EXPECT_THAT(lseek(fd.get(), 123, SEEK_SET), SyscallSucceedsWithValue(0));
- EXPECT_THAT(lseek(fd.get(), 123, SEEK_CUR), SyscallSucceedsWithValue(0));
- EXPECT_THAT(lseek(fd.get(), 123, SEEK_END), SyscallSucceedsWithValue(0));
-}
-
-TEST(DevTest, LseekDevNullFreshFile) {
- // Seeks to /dev/null always return 0.
- const FileDescriptor fd1 =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/null", O_RDONLY));
- const FileDescriptor fd2 =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/null", O_RDONLY));
-
- EXPECT_THAT(lseek(fd1.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0));
- EXPECT_THAT(lseek(fd1.get(), 1000, SEEK_CUR), SyscallSucceedsWithValue(0));
- EXPECT_THAT(lseek(fd2.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0));
-
- const FileDescriptor fd3 =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/null", O_RDONLY));
- EXPECT_THAT(lseek(fd3.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0));
-}
-
-TEST(DevTest, OpenTruncate) {
- // Truncation is ignored on linux and gvisor for device files.
- ASSERT_NO_ERRNO_AND_VALUE(
- Open("/dev/null", O_CREAT | O_TRUNC | O_WRONLY, 0644));
- ASSERT_NO_ERRNO_AND_VALUE(
- Open("/dev/zero", O_CREAT | O_TRUNC | O_WRONLY, 0644));
- ASSERT_NO_ERRNO_AND_VALUE(
- Open("/dev/full", O_CREAT | O_TRUNC | O_WRONLY, 0644));
-}
-
-TEST(DevTest, Pread64DevNull) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/null", O_RDONLY));
- char buf[1];
- EXPECT_THAT(pread64(fd.get(), buf, 1, 0), SyscallSucceedsWithValue(0));
-}
-
-TEST(DevTest, Pread64DevZero) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDONLY));
- char buf[1];
- EXPECT_THAT(pread64(fd.get(), buf, 1, 0), SyscallSucceedsWithValue(1));
-}
-
-TEST(DevTest, Pread64DevFull) {
- // /dev/full behaves like /dev/zero with respect to reads.
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/full", O_RDONLY));
- char buf[1];
- EXPECT_THAT(pread64(fd.get(), buf, 1, 0), SyscallSucceedsWithValue(1));
-}
-
-TEST(DevTest, ReadDevNull) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/null", O_RDONLY));
- std::vector<char> buf(1);
- EXPECT_THAT(ReadFd(fd.get(), buf.data(), 1), SyscallSucceeds());
-}
-
-// Do not allow random save as it could lead to partial reads.
-TEST(DevTest, ReadDevZero_NoRandomSave) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDONLY));
-
- constexpr int kReadSize = 128 * 1024;
- std::vector<char> buf(kReadSize, 1);
- EXPECT_THAT(ReadFd(fd.get(), buf.data(), kReadSize),
- SyscallSucceedsWithValue(kReadSize));
- EXPECT_EQ(std::vector<char>(kReadSize, 0), buf);
-}
-
-TEST(DevTest, WriteDevNull) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/null", O_WRONLY));
- EXPECT_THAT(WriteFd(fd.get(), "a", 1), SyscallSucceedsWithValue(1));
-}
-
-TEST(DevTest, WriteDevZero) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_WRONLY));
- EXPECT_THAT(WriteFd(fd.get(), "a", 1), SyscallSucceedsWithValue(1));
-}
-
-TEST(DevTest, WriteDevFull) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/full", O_WRONLY));
- EXPECT_THAT(WriteFd(fd.get(), "a", 1), SyscallFailsWithErrno(ENOSPC));
-}
-
-TEST(DevTest, TTYExists) {
- struct stat statbuf = {};
- ASSERT_THAT(stat("/dev/tty", &statbuf), SyscallSucceeds());
- // Check that it's a character device with rw-rw-rw- permissions.
- EXPECT_EQ(statbuf.st_mode, S_IFCHR | 0666);
-}
-
-} // namespace
-} // namespace testing
-
-} // namespace gvisor
diff --git a/test/syscalls/linux/dup.cc b/test/syscalls/linux/dup.cc
deleted file mode 100644
index 4f773bc75..000000000
--- a/test/syscalls/linux/dup.cc
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "test/util/eventfd_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/posix_error.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-PosixErrorOr<FileDescriptor> Dup2(const FileDescriptor& fd, int target_fd) {
- int new_fd = dup2(fd.get(), target_fd);
- if (new_fd < 0) {
- return PosixError(errno, "Dup2");
- }
- return FileDescriptor(new_fd);
-}
-
-PosixErrorOr<FileDescriptor> Dup3(const FileDescriptor& fd, int target_fd,
- int flags) {
- int new_fd = dup3(fd.get(), target_fd, flags);
- if (new_fd < 0) {
- return PosixError(errno, "Dup2");
- }
- return FileDescriptor(new_fd);
-}
-
-void CheckSameFile(const FileDescriptor& fd1, const FileDescriptor& fd2) {
- struct stat stat_result1, stat_result2;
- ASSERT_THAT(fstat(fd1.get(), &stat_result1), SyscallSucceeds());
- ASSERT_THAT(fstat(fd2.get(), &stat_result2), SyscallSucceeds());
- EXPECT_EQ(stat_result1.st_dev, stat_result2.st_dev);
- EXPECT_EQ(stat_result1.st_ino, stat_result2.st_ino);
-}
-
-TEST(DupTest, Dup) {
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDONLY));
-
- // Dup the descriptor and make sure it's the same file.
- FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup());
- ASSERT_NE(fd.get(), nfd.get());
- CheckSameFile(fd, nfd);
-}
-
-TEST(DupTest, DupClearsCloExec) {
- // Open an eventfd file descriptor with FD_CLOEXEC descriptor flag set.
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_CLOEXEC));
- EXPECT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC));
-
- // Duplicate the descriptor. Ensure that it doesn't have FD_CLOEXEC set.
- FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup());
- ASSERT_NE(fd.get(), nfd.get());
- CheckSameFile(fd, nfd);
- EXPECT_THAT(fcntl(nfd.get(), F_GETFD), SyscallSucceedsWithValue(0));
-}
-
-TEST(DupTest, Dup2) {
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDONLY));
-
- // Regular dup once.
- FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup());
-
- ASSERT_NE(fd.get(), nfd.get());
- CheckSameFile(fd, nfd);
-
- // Dup over the file above.
- int target_fd = nfd.release();
- FileDescriptor nfd2 = ASSERT_NO_ERRNO_AND_VALUE(Dup2(fd, target_fd));
- EXPECT_EQ(target_fd, nfd2.get());
- CheckSameFile(fd, nfd2);
-}
-
-TEST(DupTest, Dup2SameFD) {
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDONLY));
-
- // Should succeed.
- ASSERT_THAT(dup2(fd.get(), fd.get()), SyscallSucceedsWithValue(fd.get()));
-}
-
-TEST(DupTest, Dup3) {
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDONLY));
-
- // Regular dup once.
- FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup());
- ASSERT_NE(fd.get(), nfd.get());
- CheckSameFile(fd, nfd);
-
- // Dup over the file above, check that it has no CLOEXEC.
- nfd = ASSERT_NO_ERRNO_AND_VALUE(Dup3(fd, nfd.release(), 0));
- CheckSameFile(fd, nfd);
- EXPECT_THAT(fcntl(nfd.get(), F_GETFD), SyscallSucceedsWithValue(0));
-
- // Dup over the file again, check that it does not CLOEXEC.
- nfd = ASSERT_NO_ERRNO_AND_VALUE(Dup3(fd, nfd.release(), O_CLOEXEC));
- CheckSameFile(fd, nfd);
- EXPECT_THAT(fcntl(nfd.get(), F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC));
-}
-
-TEST(DupTest, Dup3FailsSameFD) {
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDONLY));
-
- // Only dup3 fails if the new and old fd are the same.
- ASSERT_THAT(dup3(fd.get(), fd.get(), 0), SyscallFailsWithErrno(EINVAL));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/epoll.cc b/test/syscalls/linux/epoll.cc
deleted file mode 100644
index a4f8f3cec..000000000
--- a/test/syscalls/linux/epoll.cc
+++ /dev/null
@@ -1,432 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <limits.h>
-#include <pthread.h>
-#include <signal.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/epoll.h>
-#include <sys/eventfd.h>
-#include <time.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "test/util/epoll_util.h"
-#include "test/util/eventfd_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/posix_error.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-constexpr int kFDsPerEpoll = 3;
-constexpr uint64_t kMagicConstant = 0x0102030405060708;
-
-uint64_t ms_elapsed(const struct timespec* begin, const struct timespec* end) {
- return (end->tv_sec - begin->tv_sec) * 1000 +
- (end->tv_nsec - begin->tv_nsec) / 1000000;
-}
-
-TEST(EpollTest, AllWritable) {
- auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
- std::vector<FileDescriptor> eventfds;
- for (int i = 0; i < kFDsPerEpoll; i++) {
- eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD()));
- ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfds[i].get(),
- EPOLLIN | EPOLLOUT, kMagicConstant + i));
- }
-
- struct epoll_event result[kFDsPerEpoll];
- ASSERT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1),
- SyscallSucceedsWithValue(kFDsPerEpoll));
- // TODO(edahlgren): Why do some tests check epoll_event::data, and others
- // don't? Does Linux actually guarantee that, in any of these test cases,
- // epoll_wait will necessarily write out the epoll_events in the order that
- // they were registered?
- for (int i = 0; i < kFDsPerEpoll; i++) {
- ASSERT_EQ(result[i].events, EPOLLOUT);
- }
-}
-
-TEST(EpollTest, LastReadable) {
- auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
- std::vector<FileDescriptor> eventfds;
- for (int i = 0; i < kFDsPerEpoll; i++) {
- eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD()));
- ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfds[i].get(),
- EPOLLIN | EPOLLOUT, kMagicConstant + i));
- }
-
- uint64_t tmp = 1;
- ASSERT_THAT(WriteFd(eventfds[kFDsPerEpoll - 1].get(), &tmp, sizeof(tmp)),
- SyscallSucceedsWithValue(sizeof(tmp)));
-
- struct epoll_event result[kFDsPerEpoll];
- ASSERT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1),
- SyscallSucceedsWithValue(kFDsPerEpoll));
-
- int i;
- for (i = 0; i < kFDsPerEpoll - 1; i++) {
- EXPECT_EQ(result[i].events, EPOLLOUT);
- }
- EXPECT_EQ(result[i].events, EPOLLOUT | EPOLLIN);
- EXPECT_EQ(result[i].data.u64, kMagicConstant + i);
-}
-
-TEST(EpollTest, LastNonWritable) {
- auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
- std::vector<FileDescriptor> eventfds;
- for (int i = 0; i < kFDsPerEpoll; i++) {
- eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD()));
- ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfds[i].get(),
- EPOLLIN | EPOLLOUT, kMagicConstant + i));
- }
-
- // Write the maximum value to the event fd so that writing to it again would
- // block.
- uint64_t tmp = ULLONG_MAX - 1;
- ASSERT_THAT(WriteFd(eventfds[kFDsPerEpoll - 1].get(), &tmp, sizeof(tmp)),
- SyscallSucceedsWithValue(sizeof(tmp)));
-
- struct epoll_event result[kFDsPerEpoll];
- ASSERT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1),
- SyscallSucceedsWithValue(kFDsPerEpoll));
-
- int i;
- for (i = 0; i < kFDsPerEpoll - 1; i++) {
- EXPECT_EQ(result[i].events, EPOLLOUT);
- }
- EXPECT_EQ(result[i].events, EPOLLIN);
- EXPECT_THAT(ReadFd(eventfds[kFDsPerEpoll - 1].get(), &tmp, sizeof(tmp)),
- sizeof(tmp));
- EXPECT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1),
- SyscallSucceedsWithValue(kFDsPerEpoll));
-
- for (i = 0; i < kFDsPerEpoll; i++) {
- EXPECT_EQ(result[i].events, EPOLLOUT);
- }
-}
-
-TEST(EpollTest, Timeout_NoRandomSave) {
- auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
- std::vector<FileDescriptor> eventfds;
- for (int i = 0; i < kFDsPerEpoll; i++) {
- eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD()));
- ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfds[i].get(), EPOLLIN,
- kMagicConstant + i));
- }
-
- constexpr int kTimeoutMs = 200;
- struct timespec begin;
- struct timespec end;
- struct epoll_event result[kFDsPerEpoll];
-
- {
- const DisableSave ds; // Timing-related.
- EXPECT_THAT(clock_gettime(CLOCK_MONOTONIC, &begin), SyscallSucceeds());
-
- ASSERT_THAT(
- RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, kTimeoutMs),
- SyscallSucceedsWithValue(0));
- EXPECT_THAT(clock_gettime(CLOCK_MONOTONIC, &end), SyscallSucceeds());
- }
-
- // Check the lower bound on the timeout. Checking for an upper bound is
- // fragile because Linux can overrun the timeout due to scheduling delays.
- EXPECT_GT(ms_elapsed(&begin, &end), kTimeoutMs - 1);
-}
-
-void* writer(void* arg) {
- int fd = *reinterpret_cast<int*>(arg);
- uint64_t tmp = 1;
-
- usleep(200000);
- if (WriteFd(fd, &tmp, sizeof(tmp)) != sizeof(tmp)) {
- fprintf(stderr, "writer failed: errno %s\n", strerror(errno));
- }
-
- return nullptr;
-}
-
-TEST(EpollTest, WaitThenUnblock) {
- auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
- std::vector<FileDescriptor> eventfds;
- for (int i = 0; i < kFDsPerEpoll; i++) {
- eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD()));
- ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfds[i].get(), EPOLLIN,
- kMagicConstant + i));
- }
-
- // Fire off a thread that will make at least one of the event fds readable.
- pthread_t thread;
- int make_readable = eventfds[0].get();
- ASSERT_THAT(pthread_create(&thread, nullptr, writer, &make_readable),
- SyscallSucceedsWithValue(0));
-
- struct epoll_event result[kFDsPerEpoll];
- EXPECT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1),
- SyscallSucceedsWithValue(1));
- EXPECT_THAT(pthread_detach(thread), SyscallSucceeds());
-}
-
-void sighandler(int s) {}
-
-void* signaler(void* arg) {
- pthread_t* t = reinterpret_cast<pthread_t*>(arg);
- // Repeatedly send the real-time signal until we are detached, because it's
- // difficult to know exactly when epoll_wait on another thread (which this
- // is intending to interrupt) has started blocking.
- while (1) {
- usleep(200000);
- pthread_kill(*t, SIGRTMIN);
- }
- return nullptr;
-}
-
-TEST(EpollTest, UnblockWithSignal) {
- auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
- std::vector<FileDescriptor> eventfds;
- for (int i = 0; i < kFDsPerEpoll; i++) {
- eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD()));
- ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfds[i].get(), EPOLLIN,
- kMagicConstant + i));
- }
-
- signal(SIGRTMIN, sighandler);
- // Unblock the real time signals that InitGoogle blocks :(
- sigset_t unblock;
- sigemptyset(&unblock);
- sigaddset(&unblock, SIGRTMIN);
- ASSERT_THAT(sigprocmask(SIG_UNBLOCK, &unblock, nullptr), SyscallSucceeds());
-
- pthread_t thread;
- pthread_t cur = pthread_self();
- ASSERT_THAT(pthread_create(&thread, nullptr, signaler, &cur),
- SyscallSucceedsWithValue(0));
-
- struct epoll_event result[kFDsPerEpoll];
- EXPECT_THAT(epoll_wait(epollfd.get(), result, kFDsPerEpoll, -1),
- SyscallFailsWithErrno(EINTR));
- EXPECT_THAT(pthread_cancel(thread), SyscallSucceeds());
- EXPECT_THAT(pthread_detach(thread), SyscallSucceeds());
-}
-
-TEST(EpollTest, TimeoutNoFds) {
- auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
- struct epoll_event result[kFDsPerEpoll];
- EXPECT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, 100),
- SyscallSucceedsWithValue(0));
-}
-
-struct addr_ctx {
- int epollfd;
- int eventfd;
-};
-
-void* fd_adder(void* arg) {
- struct addr_ctx* actx = reinterpret_cast<struct addr_ctx*>(arg);
- struct epoll_event event;
- event.events = EPOLLIN | EPOLLOUT;
- event.data.u64 = 0xdeadbeeffacefeed;
-
- usleep(200000);
- if (epoll_ctl(actx->epollfd, EPOLL_CTL_ADD, actx->eventfd, &event) == -1) {
- fprintf(stderr, "epoll_ctl failed: %s\n", strerror(errno));
- }
-
- return nullptr;
-}
-
-TEST(EpollTest, UnblockWithNewFD) {
- auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
- auto eventfd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD());
-
- pthread_t thread;
- struct addr_ctx actx = {epollfd.get(), eventfd.get()};
- ASSERT_THAT(pthread_create(&thread, nullptr, fd_adder, &actx),
- SyscallSucceedsWithValue(0));
-
- struct epoll_event result[kFDsPerEpoll];
- // Wait while no FDs are ready, but after 200ms fd_adder will add a ready FD
- // to epoll which will wake us up.
- EXPECT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1),
- SyscallSucceedsWithValue(1));
- EXPECT_THAT(pthread_detach(thread), SyscallSucceeds());
- EXPECT_EQ(result[0].data.u64, 0xdeadbeeffacefeed);
-}
-
-TEST(EpollTest, Oneshot) {
- auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
- std::vector<FileDescriptor> eventfds;
- for (int i = 0; i < kFDsPerEpoll; i++) {
- eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD()));
- ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfds[i].get(), EPOLLIN,
- kMagicConstant + i));
- }
-
- struct epoll_event event;
- event.events = EPOLLOUT | EPOLLONESHOT;
- event.data.u64 = kMagicConstant;
- ASSERT_THAT(
- epoll_ctl(epollfd.get(), EPOLL_CTL_MOD, eventfds[0].get(), &event),
- SyscallSucceeds());
-
- struct epoll_event result[kFDsPerEpoll];
- // One-shot entry means that the first epoll_wait should succeed.
- ASSERT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1),
- SyscallSucceedsWithValue(1));
- EXPECT_EQ(result[0].data.u64, kMagicConstant);
-
- // One-shot entry means that the second epoll_wait should timeout.
- EXPECT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, 100),
- SyscallSucceedsWithValue(0));
-}
-
-TEST(EpollTest, EdgeTriggered_NoRandomSave) {
- // Test edge-triggered entry: make it edge-triggered, first wait should
- // return it, second one should time out, make it writable again, third wait
- // should return it, fourth wait should timeout.
- auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
- auto eventfd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD());
- ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfd.get(),
- EPOLLOUT | EPOLLET, kMagicConstant));
-
- struct epoll_event result[kFDsPerEpoll];
-
- {
- const DisableSave ds; // May trigger spurious event.
-
- // Edge-triggered entry means that the first epoll_wait should return the
- // event.
- ASSERT_THAT(epoll_wait(epollfd.get(), result, kFDsPerEpoll, -1),
- SyscallSucceedsWithValue(1));
- EXPECT_EQ(result[0].data.u64, kMagicConstant);
-
- // Edge-triggered entry means that the second epoll_wait should time out.
- ASSERT_THAT(epoll_wait(epollfd.get(), result, kFDsPerEpoll, 100),
- SyscallSucceedsWithValue(0));
- }
-
- uint64_t tmp = ULLONG_MAX - 1;
-
- // Make an fd non-writable.
- ASSERT_THAT(WriteFd(eventfd.get(), &tmp, sizeof(tmp)),
- SyscallSucceedsWithValue(sizeof(tmp)));
-
- // Make the same fd non-writable to trigger a change, which will trigger an
- // edge-triggered event.
- ASSERT_THAT(ReadFd(eventfd.get(), &tmp, sizeof(tmp)),
- SyscallSucceedsWithValue(sizeof(tmp)));
-
- {
- const DisableSave ds; // May trigger spurious event.
-
- // An edge-triggered event should now be returned.
- ASSERT_THAT(epoll_wait(epollfd.get(), result, kFDsPerEpoll, -1),
- SyscallSucceedsWithValue(1));
- EXPECT_EQ(result[0].data.u64, kMagicConstant);
-
- // The edge-triggered event had been consumed above, we don't expect to
- // get it again.
- ASSERT_THAT(epoll_wait(epollfd.get(), result, kFDsPerEpoll, 100),
- SyscallSucceedsWithValue(0));
- }
-}
-
-TEST(EpollTest, OneshotAndEdgeTriggered) {
- auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
- auto eventfd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD());
- ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfd.get(),
- EPOLLOUT | EPOLLET | EPOLLONESHOT,
- kMagicConstant));
-
- struct epoll_event result[kFDsPerEpoll];
- // First time one shot edge-triggered entry means that epoll_wait should
- // return the event.
- ASSERT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1),
- SyscallSucceedsWithValue(1));
- EXPECT_EQ(result[0].data.u64, kMagicConstant);
-
- // Edge-triggered entry means that the second epoll_wait should time out.
- ASSERT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, 100),
- SyscallSucceedsWithValue(0));
-
- uint64_t tmp = ULLONG_MAX - 1;
- // Make an fd non-writable.
- ASSERT_THAT(WriteFd(eventfd.get(), &tmp, sizeof(tmp)),
- SyscallSucceedsWithValue(sizeof(tmp)));
- // Make the same fd non-writable to trigger a change, which will not trigger
- // an edge-triggered event because we've also included EPOLLONESHOT.
- ASSERT_THAT(ReadFd(eventfd.get(), &tmp, sizeof(tmp)),
- SyscallSucceedsWithValue(sizeof(tmp)));
- ASSERT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, 100),
- SyscallSucceedsWithValue(0));
-}
-
-TEST(EpollTest, CycleOfOneDisallowed) {
- auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
-
- struct epoll_event event;
- event.events = EPOLLOUT;
- event.data.u64 = kMagicConstant;
-
- ASSERT_THAT(epoll_ctl(epollfd.get(), EPOLL_CTL_ADD, epollfd.get(), &event),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(EpollTest, CycleOfThreeDisallowed) {
- auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
- auto epollfd1 = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
- auto epollfd2 = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
-
- ASSERT_NO_ERRNO(
- RegisterEpollFD(epollfd.get(), epollfd1.get(), EPOLLIN, kMagicConstant));
- ASSERT_NO_ERRNO(
- RegisterEpollFD(epollfd1.get(), epollfd2.get(), EPOLLIN, kMagicConstant));
-
- struct epoll_event event;
- event.events = EPOLLIN;
- event.data.u64 = kMagicConstant;
- EXPECT_THAT(epoll_ctl(epollfd2.get(), EPOLL_CTL_ADD, epollfd.get(), &event),
- SyscallFailsWithErrno(ELOOP));
-}
-
-TEST(EpollTest, CloseFile) {
- auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
- auto eventfd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD());
- ASSERT_NO_ERRNO(
- RegisterEpollFD(epollfd.get(), eventfd.get(), EPOLLOUT, kMagicConstant));
-
- struct epoll_event result[kFDsPerEpoll];
- ASSERT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1),
- SyscallSucceedsWithValue(1));
- EXPECT_EQ(result[0].data.u64, kMagicConstant);
-
- // Close the event fd early.
- eventfd.reset();
-
- EXPECT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, 100),
- SyscallSucceedsWithValue(0));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/eventfd.cc b/test/syscalls/linux/eventfd.cc
deleted file mode 100644
index 367682c3d..000000000
--- a/test/syscalls/linux/eventfd.cc
+++ /dev/null
@@ -1,180 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <pthread.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/epoll.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "test/util/epoll_util.h"
-#include "test/util/eventfd_util.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(EventfdTest, Nonblock) {
- FileDescriptor efd =
- ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_NONBLOCK | EFD_SEMAPHORE));
-
- uint64_t l;
- ASSERT_THAT(read(efd.get(), &l, sizeof(l)), SyscallFailsWithErrno(EAGAIN));
-
- l = 1;
- ASSERT_THAT(write(efd.get(), &l, sizeof(l)), SyscallSucceeds());
-
- l = 0;
- ASSERT_THAT(read(efd.get(), &l, sizeof(l)), SyscallSucceeds());
- EXPECT_EQ(l, 1);
-
- ASSERT_THAT(read(efd.get(), &l, sizeof(l)), SyscallFailsWithErrno(EAGAIN));
-}
-
-void* read_three_times(void* arg) {
- int efd = *reinterpret_cast<int*>(arg);
- uint64_t l;
- EXPECT_THAT(read(efd, &l, sizeof(l)), SyscallSucceedsWithValue(sizeof(l)));
- EXPECT_THAT(read(efd, &l, sizeof(l)), SyscallSucceedsWithValue(sizeof(l)));
- EXPECT_THAT(read(efd, &l, sizeof(l)), SyscallSucceedsWithValue(sizeof(l)));
- return nullptr;
-}
-
-TEST(EventfdTest, BlockingWrite) {
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_SEMAPHORE));
- int efd = fd.get();
-
- pthread_t p;
- ASSERT_THAT(pthread_create(&p, nullptr, read_three_times,
- reinterpret_cast<void*>(&efd)),
- SyscallSucceeds());
-
- uint64_t l = 1;
- ASSERT_THAT(write(efd, &l, sizeof(l)), SyscallSucceeds());
- EXPECT_EQ(l, 1);
-
- ASSERT_THAT(write(efd, &l, sizeof(l)), SyscallSucceeds());
- EXPECT_EQ(l, 1);
-
- ASSERT_THAT(write(efd, &l, sizeof(l)), SyscallSucceeds());
- EXPECT_EQ(l, 1);
-
- ASSERT_THAT(pthread_join(p, nullptr), SyscallSucceeds());
-}
-
-TEST(EventfdTest, SmallWrite) {
- FileDescriptor efd =
- ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_NONBLOCK | EFD_SEMAPHORE));
-
- uint64_t l = 16;
- ASSERT_THAT(write(efd.get(), &l, 4), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(EventfdTest, SmallRead) {
- FileDescriptor efd =
- ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_NONBLOCK | EFD_SEMAPHORE));
-
- uint64_t l = 1;
- ASSERT_THAT(write(efd.get(), &l, sizeof(l)), SyscallSucceeds());
-
- l = 0;
- ASSERT_THAT(read(efd.get(), &l, 4), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(EventfdTest, BigWrite) {
- FileDescriptor efd =
- ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_NONBLOCK | EFD_SEMAPHORE));
-
- uint64_t big[16];
- big[0] = 16;
- ASSERT_THAT(write(efd.get(), big, sizeof(big)), SyscallSucceeds());
-}
-
-TEST(EventfdTest, BigRead) {
- FileDescriptor efd =
- ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_NONBLOCK | EFD_SEMAPHORE));
-
- uint64_t l = 1;
- ASSERT_THAT(write(efd.get(), &l, sizeof(l)), SyscallSucceeds());
-
- uint64_t big[16];
- ASSERT_THAT(read(efd.get(), big, sizeof(big)), SyscallSucceeds());
- EXPECT_EQ(big[0], 1);
-}
-
-TEST(EventfdTest, BigWriteBigRead) {
- FileDescriptor efd =
- ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_NONBLOCK | EFD_SEMAPHORE));
-
- uint64_t l[16];
- l[0] = 16;
- ASSERT_THAT(write(efd.get(), l, sizeof(l)), SyscallSucceeds());
- ASSERT_THAT(read(efd.get(), l, sizeof(l)), SyscallSucceeds());
- EXPECT_EQ(l[0], 1);
-}
-
-// NotifyNonZero is inherently racy, so random save is disabled.
-TEST(EventfdTest, NotifyNonZero_NoRandomSave) {
- // Waits will time out at 10 seconds.
- constexpr int kEpollTimeoutMs = 10000;
- // Create an eventfd descriptor.
- FileDescriptor efd =
- ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(7, EFD_NONBLOCK | EFD_SEMAPHORE));
- // Create an epoll fd to listen to efd.
- FileDescriptor epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
- // Add efd to epoll.
- ASSERT_NO_ERRNO(
- RegisterEpollFD(epollfd.get(), efd.get(), EPOLLIN | EPOLLET, efd.get()));
-
- // Use epoll to get a value from efd.
- struct epoll_event out_ev;
- int wait_out = epoll_wait(epollfd.get(), &out_ev, 1, kEpollTimeoutMs);
- EXPECT_EQ(wait_out, 1);
- EXPECT_EQ(efd.get(), out_ev.data.fd);
- uint64_t val = 0;
- ASSERT_THAT(read(efd.get(), &val, sizeof(val)), SyscallSucceeds());
- EXPECT_EQ(val, 1);
-
- // Start a thread that, after this thread blocks on epoll_wait, will write to
- // efd. This is racy -- it's possible that this write will happen after
- // epoll_wait times out.
- ScopedThread t([&efd] {
- sleep(5);
- uint64_t val = 1;
- EXPECT_THAT(write(efd.get(), &val, sizeof(val)),
- SyscallSucceedsWithValue(sizeof(val)));
- });
-
- // epoll_wait should return once the thread writes.
- wait_out = epoll_wait(epollfd.get(), &out_ev, 1, kEpollTimeoutMs);
- EXPECT_EQ(wait_out, 1);
- EXPECT_EQ(efd.get(), out_ev.data.fd);
-
- val = 0;
- ASSERT_THAT(read(efd.get(), &val, sizeof(val)), SyscallSucceeds());
- EXPECT_EQ(val, 1);
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/exceptions.cc b/test/syscalls/linux/exceptions.cc
deleted file mode 100644
index 370e85166..000000000
--- a/test/syscalls/linux/exceptions.cc
+++ /dev/null
@@ -1,184 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <signal.h>
-
-#include "gtest/gtest.h"
-#include "test/util/logging.h"
-#include "test/util/signal_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-void inline Halt() { asm("hlt\r\n"); }
-
-void inline SetAlignmentCheck() {
- asm("subq $128, %%rsp\r\n" // Avoid potential red zone clobber
- "pushf\r\n"
- "pop %%rax\r\n"
- "or $0x40000, %%rax\r\n"
- "push %%rax\r\n"
- "popf\r\n"
- "addq $128, %%rsp\r\n"
- :
- :
- : "ax");
-}
-
-void inline ClearAlignmentCheck() {
- asm("subq $128, %%rsp\r\n" // Avoid potential red zone clobber
- "pushf\r\n"
- "pop %%rax\r\n"
- "mov $0x40000, %%rbx\r\n"
- "not %%rbx\r\n"
- "and %%rbx, %%rax\r\n"
- "push %%rax\r\n"
- "popf\r\n"
- "addq $128, %%rsp\r\n"
- :
- :
- : "ax", "bx");
-}
-
-void inline Int3Normal() { asm(".byte 0xcd, 0x03\r\n"); }
-
-void inline Int3Compact() { asm(".byte 0xcc\r\n"); }
-
-void InIOHelper(int width, int value) {
- EXPECT_EXIT(
- {
- switch (width) {
- case 1:
- asm volatile("inb %%dx, %%al" ::"d"(value) : "%eax");
- break;
- case 2:
- asm volatile("inw %%dx, %%ax" ::"d"(value) : "%eax");
- break;
- case 4:
- asm volatile("inl %%dx, %%eax" ::"d"(value) : "%eax");
- break;
- default:
- FAIL() << "invalid input width, only 1, 2 or 4 is allowed";
- }
- },
- ::testing::KilledBySignal(SIGSEGV), "");
-}
-
-TEST(ExceptionTest, Halt) {
- // In order to prevent the regular handler from messing with things (and
- // perhaps refaulting until some other signal occurs), we reset the handler to
- // the default action here and ensure that it dies correctly.
- struct sigaction sa = {};
- sa.sa_handler = SIG_DFL;
- auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGSEGV, sa));
-
- EXPECT_EXIT(Halt(), ::testing::KilledBySignal(SIGSEGV), "");
-}
-
-TEST(ExceptionTest, DivideByZero) {
- // See above.
- struct sigaction sa = {};
- sa.sa_handler = SIG_DFL;
- auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGFPE, sa));
-
- EXPECT_EXIT(
- {
- uint32_t remainder;
- uint32_t quotient;
- uint32_t divisor = 0;
- uint64_t value = 1;
- asm("divl 0(%2)\r\n"
- : "=d"(remainder), "=a"(quotient)
- : "r"(&divisor), "d"(value >> 32), "a"(value));
- TEST_CHECK(quotient > 0); // Force dependency.
- },
- ::testing::KilledBySignal(SIGFPE), "");
-}
-
-TEST(ExceptionTest, IOAccessFault) {
- // See above.
- struct sigaction sa = {};
- sa.sa_handler = SIG_DFL;
- auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGSEGV, sa));
-
- InIOHelper(1, 0x0);
- InIOHelper(2, 0x7);
- InIOHelper(4, 0x6);
- InIOHelper(1, 0xffff);
- InIOHelper(2, 0xffff);
- InIOHelper(4, 0xfffd);
-}
-
-TEST(ExceptionTest, Alignment) {
- SetAlignmentCheck();
- ClearAlignmentCheck();
-}
-
-TEST(ExceptionTest, AlignmentHalt) {
- // See above.
- struct sigaction sa = {};
- sa.sa_handler = SIG_DFL;
- auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGSEGV, sa));
-
- // Reported upstream. We need to ensure that bad flags are cleared even in
- // fault paths. Set the alignment flag and then generate an exception.
- EXPECT_EXIT(
- {
- SetAlignmentCheck();
- Halt();
- },
- ::testing::KilledBySignal(SIGSEGV), "");
-}
-
-TEST(ExceptionTest, AlignmentCheck) {
-
- // See above.
- struct sigaction sa = {};
- sa.sa_handler = SIG_DFL;
- auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGBUS, sa));
-
- EXPECT_EXIT(
- {
- char array[16];
- SetAlignmentCheck();
- for (int i = 0; i < 8; i++) {
- // At least 7/8 offsets will be unaligned here.
- uint64_t* ptr = reinterpret_cast<uint64_t*>(&array[i]);
- asm("mov %0, 0(%0)\r\n" : : "r"(ptr) : "ax");
- }
- },
- ::testing::KilledBySignal(SIGBUS), "");
-}
-
-TEST(ExceptionTest, Int3Normal) {
- // See above.
- struct sigaction sa = {};
- sa.sa_handler = SIG_DFL;
- auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGTRAP, sa));
-
- EXPECT_EXIT(Int3Normal(), ::testing::KilledBySignal(SIGTRAP), "");
-}
-
-TEST(ExceptionTest, Int3Compact) {
- // See above.
- struct sigaction sa = {};
- sa.sa_handler = SIG_DFL;
- auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGTRAP, sa));
-
- EXPECT_EXIT(Int3Compact(), ::testing::KilledBySignal(SIGTRAP), "");
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/exec.cc b/test/syscalls/linux/exec.cc
deleted file mode 100644
index 4c7c95321..000000000
--- a/test/syscalls/linux/exec.cc
+++ /dev/null
@@ -1,630 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/exec.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/eventfd.h>
-#include <sys/resource.h>
-#include <sys/time.h>
-#include <unistd.h>
-
-#include <iostream>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "absl/strings/match.h"
-#include "absl/strings/numbers.h"
-#include "absl/strings/str_cat.h"
-#include "absl/strings/str_split.h"
-#include "absl/strings/string_view.h"
-#include "absl/synchronization/mutex.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/multiprocess_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-constexpr char kBasicWorkload[] = "exec_basic_workload";
-constexpr char kExitScript[] = "exit_script";
-constexpr char kStateWorkload[] = "exec_state_workload";
-constexpr char kProcExeWorkload[] = "exec_proc_exe_workload";
-constexpr char kAssertClosedWorkload[] = "exec_assert_closed_workload";
-constexpr char kPriorityWorkload[] = "priority_execve";
-
-std::string WorkloadPath(absl::string_view binary) {
- std::string full_path;
- char* test_src = getenv("TEST_SRCDIR");
- if (test_src) {
- full_path = JoinPath(test_src, "__main__/test/syscalls/linux", binary);
- }
-
- TEST_CHECK(full_path.empty() == false);
- return full_path;
-}
-
-constexpr char kExit42[] = "--exec_exit_42";
-constexpr char kExecWithThread[] = "--exec_exec_with_thread";
-constexpr char kExecFromThread[] = "--exec_exec_from_thread";
-
-// Runs filename with argv and checks that the exit status is expect_status and
-// that stderr contains expect_stderr.
-void CheckOutput(const std::string& filename, const ExecveArray& argv,
- const ExecveArray& envv, int expect_status,
- const std::string& expect_stderr) {
- int pipe_fds[2];
- ASSERT_THAT(pipe2(pipe_fds, O_CLOEXEC), SyscallSucceeds());
-
- FileDescriptor read_fd(pipe_fds[0]);
- FileDescriptor write_fd(pipe_fds[1]);
-
- pid_t child;
- int execve_errno;
-
- const auto remap_stderr = [pipe_fds] {
- // Remap stdin and stdout to /dev/null.
- int fd = open("/dev/null", O_RDWR | O_CLOEXEC);
- if (fd < 0) {
- _exit(errno);
- }
-
- int ret = dup2(fd, 0);
- if (ret < 0) {
- _exit(errno);
- }
-
- ret = dup2(fd, 1);
- if (ret < 0) {
- _exit(errno);
- }
-
- // And stderr to the pipe.
- ret = dup2(pipe_fds[1], 2);
- if (ret < 0) {
- _exit(errno);
- }
-
- // Here, we'd ideally close all other FDs inherited from the parent.
- // However, that's not worth the effort and CloexecNormalFile and
- // CloexecEventfd depend on that not happening.
- };
-
- auto kill = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(filename, argv, envv, remap_stderr, &child, &execve_errno));
-
- ASSERT_EQ(0, execve_errno);
-
- // Not needed anymore.
- write_fd.reset();
-
- // Read stderr until the child exits.
- std::string output;
- constexpr int kSize = 128;
- char buf[kSize];
- int n;
- do {
- ASSERT_THAT(n = ReadFd(read_fd.get(), buf, kSize), SyscallSucceeds());
- if (n > 0) {
- output.append(buf, n);
- }
- } while (n > 0);
-
- int status;
- ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceeds());
- EXPECT_EQ(status, expect_status);
-
- // Process cleanup no longer needed.
- kill.Release();
-
- EXPECT_TRUE(absl::StrContains(output, expect_stderr)) << output;
-}
-
-TEST(ExecDeathTest, EmptyPath) {
- int execve_errno;
- ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec("", {}, {}, nullptr, &execve_errno));
- EXPECT_EQ(execve_errno, ENOENT);
-}
-
-TEST(ExecDeathTest, Basic) {
- CheckOutput(WorkloadPath(kBasicWorkload), {WorkloadPath(kBasicWorkload)}, {},
- ArgEnvExitStatus(0, 0),
- absl::StrCat(WorkloadPath(kBasicWorkload), "\n"));
-}
-
-TEST(ExecDeathTest, OneArg) {
- CheckOutput(WorkloadPath(kBasicWorkload), {WorkloadPath(kBasicWorkload), "1"},
- {}, ArgEnvExitStatus(1, 0),
- absl::StrCat(WorkloadPath(kBasicWorkload), "\n1\n"));
-}
-
-TEST(ExecDeathTest, FiveArg) {
- CheckOutput(WorkloadPath(kBasicWorkload),
- {WorkloadPath(kBasicWorkload), "1", "2", "3", "4", "5"}, {},
- ArgEnvExitStatus(5, 0),
- absl::StrCat(WorkloadPath(kBasicWorkload), "\n1\n2\n3\n4\n5\n"));
-}
-
-TEST(ExecDeathTest, OneEnv) {
- CheckOutput(WorkloadPath(kBasicWorkload), {WorkloadPath(kBasicWorkload)},
- {"1"}, ArgEnvExitStatus(0, 1),
- absl::StrCat(WorkloadPath(kBasicWorkload), "\n1\n"));
-}
-
-TEST(ExecDeathTest, FiveEnv) {
- CheckOutput(WorkloadPath(kBasicWorkload), {WorkloadPath(kBasicWorkload)},
- {"1", "2", "3", "4", "5"}, ArgEnvExitStatus(0, 5),
- absl::StrCat(WorkloadPath(kBasicWorkload), "\n1\n2\n3\n4\n5\n"));
-}
-
-TEST(ExecDeathTest, OneArgOneEnv) {
- CheckOutput(WorkloadPath(kBasicWorkload),
- {WorkloadPath(kBasicWorkload), "arg"}, {"env"},
- ArgEnvExitStatus(1, 1),
- absl::StrCat(WorkloadPath(kBasicWorkload), "\narg\nenv\n"));
-}
-
-TEST(ExecDeathTest, InterpreterScript) {
- CheckOutput(WorkloadPath(kExitScript), {WorkloadPath(kExitScript), "25"}, {},
- ArgEnvExitStatus(25, 0), "");
-}
-
-// Everything after the path in the interpreter script is a single argument.
-TEST(ExecDeathTest, InterpreterScriptArgSplit) {
- // Symlink through /tmp to ensure the path is short enough.
- TempPath link = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateSymlinkTo("/tmp", WorkloadPath(kBasicWorkload)));
-
- TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), absl::StrCat("#!", link.path(), " foo bar"),
- 0755));
-
- CheckOutput(script.path(), {script.path()}, {}, ArgEnvExitStatus(2, 0),
- absl::StrCat(link.path(), "\nfoo bar\n", script.path(), "\n"));
-}
-
-// Original argv[0] is replaced with the script path.
-TEST(ExecDeathTest, InterpreterScriptArgvZero) {
- // Symlink through /tmp to ensure the path is short enough.
- TempPath link = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateSymlinkTo("/tmp", WorkloadPath(kBasicWorkload)));
-
- TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), absl::StrCat("#!", link.path()), 0755));
-
- CheckOutput(script.path(), {"REPLACED"}, {}, ArgEnvExitStatus(1, 0),
- absl::StrCat(link.path(), "\n", script.path(), "\n"));
-}
-
-// Original argv[0] is replaced with the script path, exactly as passed to
-// execve.
-TEST(ExecDeathTest, InterpreterScriptArgvZeroRelative) {
- // Symlink through /tmp to ensure the path is short enough.
- TempPath link = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateSymlinkTo("/tmp", WorkloadPath(kBasicWorkload)));
-
- TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), absl::StrCat("#!", link.path()), 0755));
-
- auto cwd = ASSERT_NO_ERRNO_AND_VALUE(GetCWD());
- auto script_relative =
- ASSERT_NO_ERRNO_AND_VALUE(GetRelativePath(cwd, script.path()));
-
- CheckOutput(script_relative, {"REPLACED"}, {}, ArgEnvExitStatus(1, 0),
- absl::StrCat(link.path(), "\n", script_relative, "\n"));
-}
-
-// argv[0] is added as the script path, even if there was none.
-TEST(ExecDeathTest, InterpreterScriptArgvZeroAdded) {
- // Symlink through /tmp to ensure the path is short enough.
- TempPath link = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateSymlinkTo("/tmp", WorkloadPath(kBasicWorkload)));
-
- TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), absl::StrCat("#!", link.path()), 0755));
-
- CheckOutput(script.path(), {}, {}, ArgEnvExitStatus(1, 0),
- absl::StrCat(link.path(), "\n", script.path(), "\n"));
-}
-
-// A NUL byte in the script line ends parsing.
-TEST(ExecDeathTest, InterpreterScriptArgNUL) {
- // Symlink through /tmp to ensure the path is short enough.
- TempPath link = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateSymlinkTo("/tmp", WorkloadPath(kBasicWorkload)));
-
- TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(),
- absl::StrCat("#!", link.path(), " foo", std::string(1, '\0'), "bar"),
- 0755));
-
- CheckOutput(script.path(), {script.path()}, {}, ArgEnvExitStatus(2, 0),
- absl::StrCat(link.path(), "\nfoo\n", script.path(), "\n"));
-}
-
-// Trailing whitespace following interpreter path is ignored.
-TEST(ExecDeathTest, InterpreterScriptTrailingWhitespace) {
- // Symlink through /tmp to ensure the path is short enough.
- TempPath link = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateSymlinkTo("/tmp", WorkloadPath(kBasicWorkload)));
-
- TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), absl::StrCat("#!", link.path(), " "), 0755));
-
- CheckOutput(script.path(), {script.path()}, {}, ArgEnvExitStatus(1, 0),
- absl::StrCat(link.path(), "\n", script.path(), "\n"));
-}
-
-// Multiple whitespace characters between interpreter and arg allowed.
-TEST(ExecDeathTest, InterpreterScriptArgWhitespace) {
- // Symlink through /tmp to ensure the path is short enough.
- TempPath link = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateSymlinkTo("/tmp", WorkloadPath(kBasicWorkload)));
-
- TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), absl::StrCat("#!", link.path(), " foo"), 0755));
-
- CheckOutput(script.path(), {script.path()}, {}, ArgEnvExitStatus(2, 0),
- absl::StrCat(link.path(), "\nfoo\n", script.path(), "\n"));
-}
-
-TEST(ExecDeathTest, InterpreterScriptNoPath) {
- TempPath script = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateFileWith(GetAbsoluteTestTmpdir(), "#!", 0755));
-
- int execve_errno;
- ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(script.path(), {script.path()}, {}, nullptr, &execve_errno));
- EXPECT_EQ(execve_errno, ENOEXEC);
-}
-
-// AT_EXECFN is the path passed to execve.
-TEST(ExecDeathTest, ExecFn) {
- // Symlink through /tmp to ensure the path is short enough.
- TempPath link = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateSymlinkTo("/tmp", WorkloadPath(kStateWorkload)));
-
- TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), absl::StrCat("#!", link.path(), " PrintExecFn"),
- 0755));
-
- // Pass the script as a relative path and assert that is what appears in
- // AT_EXECFN.
- auto cwd = ASSERT_NO_ERRNO_AND_VALUE(GetCWD());
- auto script_relative =
- ASSERT_NO_ERRNO_AND_VALUE(GetRelativePath(cwd, script.path()));
-
- CheckOutput(script_relative, {script_relative}, {}, ArgEnvExitStatus(0, 0),
- absl::StrCat(script_relative, "\n"));
-}
-
-TEST(ExecDeathTest, ExecName) {
- std::string path = WorkloadPath(kStateWorkload);
-
- CheckOutput(path, {path, "PrintExecName"}, {}, ArgEnvExitStatus(0, 0),
- absl::StrCat(Basename(path).substr(0, 15), "\n"));
-}
-
-TEST(ExecDeathTest, ExecNameScript) {
- // Symlink through /tmp to ensure the path is short enough.
- TempPath link = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateSymlinkTo("/tmp", WorkloadPath(kStateWorkload)));
-
- TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(),
- absl::StrCat("#!", link.path(), " PrintExecName"), 0755));
-
- std::string script_path = script.path();
-
- CheckOutput(script_path, {script_path}, {}, ArgEnvExitStatus(0, 0),
- absl::StrCat(Basename(script_path).substr(0, 15), "\n"));
-}
-
-// execve may be called by a multithreaded process.
-TEST(ExecDeathTest, WithSiblingThread) {
- CheckOutput("/proc/self/exe", {"/proc/self/exe", kExecWithThread}, {},
- W_EXITCODE(42, 0), "");
-}
-
-// execve may be called from a thread other than the leader of a multithreaded
-// process.
-TEST(ExecDeathTest, FromSiblingThread) {
- CheckOutput("/proc/self/exe", {"/proc/self/exe", kExecFromThread}, {},
- W_EXITCODE(42, 0), "");
-}
-
-TEST(ExecTest, NotFound) {
- char* const argv[] = {nullptr};
- char* const envp[] = {nullptr};
- EXPECT_THAT(execve("/file/does/not/exist", argv, envp),
- SyscallFailsWithErrno(ENOENT));
-}
-
-TEST(ExecTest, NoExecPerm) {
- char* const argv[] = {nullptr};
- char* const envp[] = {nullptr};
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- EXPECT_THAT(execve(f.path().c_str(), argv, envp),
- SyscallFailsWithErrno(EACCES));
-}
-
-// A signal handler we never expect to be called.
-void SignalHandler(int signo) {
- std::cerr << "Signal " << signo << " raised." << std::endl;
- exit(1);
-}
-
-// Signal handlers are reset on execve(2), unless they have default or ignored
-// disposition.
-TEST(ExecStateDeathTest, HandlerReset) {
- struct sigaction sa;
- sa.sa_handler = SignalHandler;
- ASSERT_THAT(sigaction(SIGUSR1, &sa, nullptr), SyscallSucceeds());
-
- ExecveArray args = {
- WorkloadPath(kStateWorkload),
- "CheckSigHandler",
- absl::StrCat(SIGUSR1),
- absl::StrCat(absl::Hex(reinterpret_cast<uintptr_t>(SIG_DFL))),
- };
-
- CheckOutput(WorkloadPath(kStateWorkload), args, {}, W_EXITCODE(0, 0), "");
-}
-
-// Ignored signal dispositions are not reset.
-TEST(ExecStateDeathTest, IgnorePreserved) {
- struct sigaction sa;
- sa.sa_handler = SIG_IGN;
- ASSERT_THAT(sigaction(SIGUSR1, &sa, nullptr), SyscallSucceeds());
-
- ExecveArray args = {
- WorkloadPath(kStateWorkload),
- "CheckSigHandler",
- absl::StrCat(SIGUSR1),
- absl::StrCat(absl::Hex(reinterpret_cast<uintptr_t>(SIG_IGN))),
- };
-
- CheckOutput(WorkloadPath(kStateWorkload), args, {}, W_EXITCODE(0, 0), "");
-}
-
-// Signal masks are not reset on exec
-TEST(ExecStateDeathTest, SignalMask) {
- sigset_t s;
- sigemptyset(&s);
- sigaddset(&s, SIGUSR1);
- ASSERT_THAT(sigprocmask(SIG_BLOCK, &s, nullptr), SyscallSucceeds());
-
- ExecveArray args = {
- WorkloadPath(kStateWorkload),
- "CheckSigBlocked",
- absl::StrCat(SIGUSR1),
- };
-
- CheckOutput(WorkloadPath(kStateWorkload), args, {}, W_EXITCODE(0, 0), "");
-}
-
-// itimers persist across execve.
-// N.B. Timers created with timer_create(2) should not be preserved!
-TEST(ExecStateDeathTest, ItimerPreserved) {
- // The fork in ForkAndExec clears itimers, so only set them up after fork.
- auto setup_itimer = [] {
- // Ignore SIGALRM, as we don't actually care about timer
- // expirations.
- struct sigaction sa;
- sa.sa_handler = SIG_IGN;
- int ret = sigaction(SIGALRM, &sa, nullptr);
- if (ret < 0) {
- _exit(errno);
- }
-
- struct itimerval itv;
- itv.it_interval.tv_sec = 1;
- itv.it_interval.tv_usec = 0;
- itv.it_value.tv_sec = 1;
- itv.it_value.tv_usec = 0;
- ret = setitimer(ITIMER_REAL, &itv, nullptr);
- if (ret < 0) {
- _exit(errno);
- }
- };
-
- std::string filename = WorkloadPath(kStateWorkload);
- ExecveArray argv = {
- filename,
- "CheckItimerEnabled",
- absl::StrCat(ITIMER_REAL),
- };
-
- pid_t child;
- int execve_errno;
- auto kill = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(filename, argv, {}, setup_itimer, &child, &execve_errno));
- ASSERT_EQ(0, execve_errno);
-
- int status;
- ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceeds());
- EXPECT_EQ(0, status);
-
- // Process cleanup no longer needed.
- kill.Release();
-}
-
-TEST(ProcSelfExe, ChangesAcrossExecve) {
- // See exec_proc_exe_workload for more details. We simply
- // assert that the /proc/self/exe link changes across execve.
- CheckOutput(WorkloadPath(kProcExeWorkload),
- {WorkloadPath(kProcExeWorkload),
- ASSERT_NO_ERRNO_AND_VALUE(ProcessExePath(getpid()))},
- {}, W_EXITCODE(0, 0), "");
-}
-
-TEST(ExecTest, CloexecNormalFile) {
- TempPath tempFile = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateFileWith(GetAbsoluteTestTmpdir(), "bar", 0755));
- const FileDescriptor fd_closed_on_exec =
- ASSERT_NO_ERRNO_AND_VALUE(Open(tempFile.path(), O_RDONLY | O_CLOEXEC));
-
- CheckOutput(WorkloadPath(kAssertClosedWorkload),
- {WorkloadPath(kAssertClosedWorkload),
- absl::StrCat(fd_closed_on_exec.get())},
- {}, W_EXITCODE(0, 0), "");
-
- // The assert closed workload exits with code 2 if the file still exists. We
- // can use this to do a negative test.
- const FileDescriptor fd_open_on_exec =
- ASSERT_NO_ERRNO_AND_VALUE(Open(tempFile.path(), O_RDONLY));
-
- CheckOutput(WorkloadPath(kAssertClosedWorkload),
- {WorkloadPath(kAssertClosedWorkload),
- absl::StrCat(fd_open_on_exec.get())},
- {}, W_EXITCODE(2, 0), "");
-}
-
-TEST(ExecTest, CloexecEventfd) {
- int efd;
- ASSERT_THAT(efd = eventfd(0, EFD_CLOEXEC), SyscallSucceeds());
- FileDescriptor fd(efd);
-
- CheckOutput(WorkloadPath(kAssertClosedWorkload),
- {WorkloadPath(kAssertClosedWorkload), absl::StrCat(fd.get())}, {},
- W_EXITCODE(0, 0), "");
-}
-
-// Priority consistent across calls to execve()
-TEST(GetpriorityTest, ExecveMaintainsPriority) {
- int prio = 16;
- ASSERT_THAT(setpriority(PRIO_PROCESS, getpid(), prio), SyscallSucceeds());
-
- // To avoid trying to use negative exit values, check for
- // 20 - prio. Since prio should always be in the range [-20, 19],
- // this leave expected_exit_code in the range [1, 40].
- int expected_exit_code = 20 - prio;
-
- // Program run (priority_execve) will exit(X) where
- // X=getpriority(PRIO_PROCESS,0). Check that this exit value is prio.
- CheckOutput(WorkloadPath(kPriorityWorkload),
- {WorkloadPath(kPriorityWorkload)}, {},
- W_EXITCODE(expected_exit_code, 0), "");
-}
-
-void ExecWithThread() {
- // Used to ensure that the thread has actually started.
- absl::Mutex mu;
- bool started = false;
-
- ScopedThread t([&] {
- mu.Lock();
- started = true;
- mu.Unlock();
-
- while (true) {
- pause();
- }
- });
-
- mu.LockWhen(absl::Condition(&started));
- mu.Unlock();
-
- const ExecveArray argv = {"/proc/self/exe", kExit42};
- const ExecveArray envv;
-
- execve("/proc/self/exe", argv.get(), envv.get());
- exit(errno);
-}
-
-void ExecFromThread() {
- ScopedThread t([] {
- const ExecveArray argv = {"/proc/self/exe", kExit42};
- const ExecveArray envv;
-
- execve("/proc/self/exe", argv.get(), envv.get());
- exit(errno);
- });
-
- while (true) {
- pause();
- }
-}
-
-bool ValidateProcCmdlineVsArgv(const int argc, const char* const* argv) {
- auto contents_or = GetContents("/proc/self/cmdline");
- if (!contents_or.ok()) {
- std::cerr << "Unable to get /proc/self/cmdline: " << contents_or.error();
- return false;
- }
- auto contents = contents_or.ValueOrDie();
- if (contents.back() != '\0') {
- std::cerr << "Non-null terminated /proc/self/cmdline!";
- return false;
- }
- contents.pop_back();
- std::vector<std::string> procfs_cmdline = absl::StrSplit(contents, '\0');
-
- if (static_cast<int>(procfs_cmdline.size()) != argc) {
- std::cerr << "argc = " << argc << " != " << procfs_cmdline.size();
- return false;
- }
-
- for (int i = 0; i < argc; ++i) {
- if (procfs_cmdline[i] != argv[i]) {
- std::cerr << "Procfs command line argument " << i << " mismatch "
- << procfs_cmdline[i] << " != " << argv[i];
- return false;
- }
- }
- return true;
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
-
-int main(int argc, char** argv) {
- // Start by validating that the stack argv is consistent with procfs.
- if (!gvisor::testing::ValidateProcCmdlineVsArgv(argc, argv)) {
- return 1;
- }
-
- // Some of these tests require no background threads, so check for them before
- // TestInit.
- for (int i = 0; i < argc; i++) {
- absl::string_view arg(argv[i]);
-
- if (arg == gvisor::testing::kExit42) {
- return 42;
- }
- if (arg == gvisor::testing::kExecWithThread) {
- gvisor::testing::ExecWithThread();
- return 1;
- }
- if (arg == gvisor::testing::kExecFromThread) {
- gvisor::testing::ExecFromThread();
- return 1;
- }
- }
-
- gvisor::testing::TestInit(&argc, &argv);
-
- return RUN_ALL_TESTS();
-}
diff --git a/test/syscalls/linux/exec.h b/test/syscalls/linux/exec.h
deleted file mode 100644
index 5c0f7e654..000000000
--- a/test/syscalls/linux/exec.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_EXEC_H_
-#define GVISOR_TEST_SYSCALLS_EXEC_H_
-
-#include <sys/wait.h>
-
-namespace gvisor {
-namespace testing {
-
-// Returns the exit code used by exec_basic_workload.
-inline int ArgEnvExitCode(int args, int envs) { return args + envs * 10; }
-
-// Returns the exit status used by exec_basic_workload.
-inline int ArgEnvExitStatus(int args, int envs) {
- return W_EXITCODE(ArgEnvExitCode(args, envs), 0);
-}
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_EXEC_H_
diff --git a/test/syscalls/linux/exec_assert_closed_workload.cc b/test/syscalls/linux/exec_assert_closed_workload.cc
deleted file mode 100644
index 95643618d..000000000
--- a/test/syscalls/linux/exec_assert_closed_workload.cc
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <stdlib.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#include <iostream>
-
-#include "absl/strings/numbers.h"
-
-int main(int argc, char** argv) {
- if (argc != 2) {
- std::cerr << "need two arguments, got " << argc;
- exit(1);
- }
- int fd;
- if (!absl::SimpleAtoi(argv[1], &fd)) {
- std::cerr << "fd: " << argv[1] << " could not be parsed" << std::endl;
- exit(1);
- }
- struct stat s;
- if (fstat(fd, &s) == 0) {
- std::cerr << "fd: " << argv[1] << " should not be valid" << std::endl;
- exit(2);
- }
- if (errno != EBADF) {
- std::cerr << "fstat fd: " << argv[1] << " got errno: " << errno
- << " wanted: " << EBADF << std::endl;
- exit(1);
- }
- return 0;
-}
diff --git a/test/syscalls/linux/exec_basic_workload.cc b/test/syscalls/linux/exec_basic_workload.cc
deleted file mode 100644
index 1bbd6437e..000000000
--- a/test/syscalls/linux/exec_basic_workload.cc
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <stdlib.h>
-
-#include <iostream>
-
-#include "test/syscalls/linux/exec.h"
-
-int main(int argc, char** argv, char** envp) {
- int i;
- for (i = 0; i < argc; i++) {
- std::cerr << argv[i] << std::endl;
- }
- for (i = 0; envp[i] != nullptr; i++) {
- std::cerr << envp[i] << std::endl;
- }
- exit(gvisor::testing::ArgEnvExitCode(argc - 1, i));
- return 0;
-}
diff --git a/test/syscalls/linux/exec_binary.cc b/test/syscalls/linux/exec_binary.cc
deleted file mode 100644
index 91b55015c..000000000
--- a/test/syscalls/linux/exec_binary.cc
+++ /dev/null
@@ -1,1502 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <elf.h>
-#include <errno.h>
-#include <signal.h>
-#include <sys/ptrace.h>
-#include <sys/syscall.h>
-#include <sys/types.h>
-#include <sys/user.h>
-#include <unistd.h>
-#include <algorithm>
-#include <functional>
-#include <iterator>
-#include <tuple>
-#include <utility>
-#include <vector>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/strings/str_cat.h"
-#include "absl/strings/string_view.h"
-#include "test/util/cleanup.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/multiprocess_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/proc_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-using ::testing::AnyOf;
-using ::testing::Eq;
-
-#ifndef __x86_64__
-// The assembly stub and ELF internal details must be ported to other arches.
-#error "Test only supported on x86-64"
-#endif // __x86_64__
-
-// amd64 stub that calls PTRACE_TRACEME and sends itself SIGSTOP.
-const char kPtraceCode[] = {
- // movq $101, %rax /* ptrace */
- '\x48',
- '\xc7',
- '\xc0',
- '\x65',
- '\x00',
- '\x00',
- '\x00',
- // movq $0, %rsi /* PTRACE_TRACEME */
- '\x48',
- '\xc7',
- '\xc6',
- '\x00',
- '\x00',
- '\x00',
- '\x00',
- // movq $0, %rdi
- '\x48',
- '\xc7',
- '\xc7',
- '\x00',
- '\x00',
- '\x00',
- '\x00',
- // movq $0, %rdx
- '\x48',
- '\xc7',
- '\xc2',
- '\x00',
- '\x00',
- '\x00',
- '\x00',
- // movq $0, %r10
- '\x49',
- '\xc7',
- '\xc2',
- '\x00',
- '\x00',
- '\x00',
- '\x00',
- // syscall
- '\x0f',
- '\x05',
-
- // movq $39, %rax /* getpid */
- '\x48',
- '\xc7',
- '\xc0',
- '\x27',
- '\x00',
- '\x00',
- '\x00',
- // syscall
- '\x0f',
- '\x05',
-
- // movq %rax, %rdi /* pid */
- '\x48',
- '\x89',
- '\xc7',
- // movq $62, %rax /* kill */
- '\x48',
- '\xc7',
- '\xc0',
- '\x3e',
- '\x00',
- '\x00',
- '\x00',
- // movq $19, %rsi /* SIGSTOP */
- '\x48',
- '\xc7',
- '\xc6',
- '\x13',
- '\x00',
- '\x00',
- '\x00',
- // syscall
- '\x0f',
- '\x05',
-};
-
-// Size of a syscall instruction.
-constexpr int kSyscallSize = 2;
-
-// This test suite tests executable loading in the kernel (ELF and interpreter
-// scripts).
-
-// Parameterized ELF types for 64 and 32 bit.
-template <int Size>
-struct ElfTypes;
-
-template <>
-struct ElfTypes<64> {
- typedef Elf64_Ehdr ElfEhdr;
- typedef Elf64_Phdr ElfPhdr;
-};
-
-template <>
-struct ElfTypes<32> {
- typedef Elf32_Ehdr ElfEhdr;
- typedef Elf32_Phdr ElfPhdr;
-};
-
-template <int Size>
-struct ElfBinary {
- using ElfEhdr = typename ElfTypes<Size>::ElfEhdr;
- using ElfPhdr = typename ElfTypes<Size>::ElfPhdr;
-
- ElfEhdr header = {};
- std::vector<ElfPhdr> phdrs;
- std::vector<char> data;
-
- // UpdateOffsets updates p_offset, p_vaddr in all phdrs to account for the
- // space taken by the header and phdrs.
- //
- // It also updates header.e_phnum and adds the offset to header.e_entry to
- // account for the headers residing in the first PT_LOAD segment.
- //
- // Before calling UpdateOffsets each of those fields should be the appropriate
- // offset into data.
- void UpdateOffsets() {
- size_t offset = sizeof(header) + phdrs.size() * sizeof(ElfPhdr);
- header.e_entry += offset;
- header.e_phnum = phdrs.size();
- for (auto& p : phdrs) {
- p.p_offset += offset;
- p.p_vaddr += offset;
- }
- }
-
- // AddInterpreter adds a PT_INTERP segment with the passed contents.
- //
- // A later call to UpdateOffsets is required to make the new phdr valid.
- void AddInterpreter(std::vector<char> contents) {
- const int start = data.size();
- data.insert(data.end(), contents.begin(), contents.end());
- const int size = data.size() - start;
-
- ElfPhdr phdr = {};
- phdr.p_type = PT_INTERP;
- phdr.p_offset = start;
- phdr.p_filesz = size;
- phdr.p_memsz = size;
- // "If [PT_INTERP] is present, it must precede any loadable segment entry."
- phdrs.insert(phdrs.begin(), phdr);
- }
-
- // Writes the header, phdrs, and data to fd.
- PosixError Write(int fd) const {
- int ret = WriteFd(fd, &header, sizeof(header));
- if (ret < 0) {
- return PosixError(errno, "failed to write header");
- } else if (ret != sizeof(header)) {
- return PosixError(EIO, absl::StrCat("short write of header: ", ret));
- }
-
- for (auto const& p : phdrs) {
- ret = WriteFd(fd, &p, sizeof(p));
- if (ret < 0) {
- return PosixError(errno, "failed to write phdr");
- } else if (ret != sizeof(p)) {
- return PosixError(EIO, absl::StrCat("short write of phdr: ", ret));
- }
- }
-
- ret = WriteFd(fd, data.data(), data.size());
- if (ret < 0) {
- return PosixError(errno, "failed to write data");
- } else if (ret != static_cast<int>(data.size())) {
- return PosixError(EIO, absl::StrCat("short write of data: ", ret));
- }
-
- return NoError();
- }
-};
-
-// Creates a new temporary executable ELF file in parent with elf as the
-// contents.
-template <int Size>
-PosixErrorOr<TempPath> CreateElfWith(absl::string_view parent,
- ElfBinary<Size> const& elf) {
- ASSIGN_OR_RETURN_ERRNO(
- auto file, TempPath::CreateFileWith(parent, absl::string_view(), 0755));
- ASSIGN_OR_RETURN_ERRNO(auto fd, Open(file.path(), O_RDWR));
- RETURN_IF_ERRNO(elf.Write(fd.get()));
- return std::move(file);
-}
-
-// Creates a new temporary executable ELF file with elf as the contents.
-template <int Size>
-PosixErrorOr<TempPath> CreateElfWith(ElfBinary<Size> const& elf) {
- return CreateElfWith(GetAbsoluteTestTmpdir(), elf);
-}
-
-// Wait for pid to stop, and assert that it stopped via SIGSTOP.
-PosixError WaitStopped(pid_t pid) {
- int status;
- int ret = RetryEINTR(waitpid)(pid, &status, 0);
- MaybeSave();
- if (ret < 0) {
- return PosixError(errno, "wait failed");
- } else if (ret != pid) {
- return PosixError(ESRCH, absl::StrCat("wait got ", ret, " want ", pid));
- }
-
- if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) {
- return PosixError(EINVAL,
- absl::StrCat("pid did not SIGSTOP; status = ", status));
- }
-
- return NoError();
-}
-
-// Returns a valid ELF that PTRACE_TRACEME and SIGSTOPs itself.
-//
-// UpdateOffsets must be called before writing this ELF.
-ElfBinary<64> StandardElf() {
- ElfBinary<64> elf;
- elf.header.e_ident[EI_MAG0] = ELFMAG0;
- elf.header.e_ident[EI_MAG1] = ELFMAG1;
- elf.header.e_ident[EI_MAG2] = ELFMAG2;
- elf.header.e_ident[EI_MAG3] = ELFMAG3;
- elf.header.e_ident[EI_CLASS] = ELFCLASS64;
- elf.header.e_ident[EI_DATA] = ELFDATA2LSB;
- elf.header.e_ident[EI_VERSION] = EV_CURRENT;
- elf.header.e_type = ET_EXEC;
- elf.header.e_machine = EM_X86_64;
- elf.header.e_version = EV_CURRENT;
- elf.header.e_phoff = sizeof(elf.header);
- elf.header.e_phentsize = sizeof(decltype(elf)::ElfPhdr);
-
- // TODO(gvisor.dev/issue/153): Always include a PT_GNU_STACK segment to
- // disable executable stacks. With this omitted the stack (and all PROT_READ)
- // mappings should be executable, but gVisor doesn't support that.
- decltype(elf)::ElfPhdr phdr = {};
- phdr.p_type = PT_GNU_STACK;
- phdr.p_flags = PF_R | PF_W;
- elf.phdrs.push_back(phdr);
-
- phdr = {};
- phdr.p_type = PT_LOAD;
- phdr.p_flags = PF_R | PF_X;
- phdr.p_offset = 0;
- phdr.p_vaddr = 0x40000;
- phdr.p_filesz = sizeof(kPtraceCode);
- phdr.p_memsz = phdr.p_filesz;
- elf.phdrs.push_back(phdr);
-
- elf.header.e_entry = phdr.p_vaddr;
-
- elf.data.assign(kPtraceCode, kPtraceCode + sizeof(kPtraceCode));
-
- return elf;
-}
-
-// Test that a trivial binary executes.
-TEST(ElfTest, Execute) {
- ElfBinary<64> elf = StandardElf();
- elf.UpdateOffsets();
-
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno));
- ASSERT_EQ(execve_errno, 0);
-
- // Ensure it made it to SIGSTOP.
- ASSERT_NO_ERRNO(WaitStopped(child));
-
- struct user_regs_struct regs;
- ASSERT_THAT(ptrace(PTRACE_GETREGS, child, 0, &regs), SyscallSucceeds());
- // RIP is just beyond the final syscall instruction.
- EXPECT_EQ(regs.rip, elf.header.e_entry + sizeof(kPtraceCode));
-
- EXPECT_THAT(child, ContainsMappings(std::vector<ProcMapsEntry>({
- {0x40000, 0x41000, true, false, true, true, 0, 0, 0, 0,
- file.path().c_str()},
- })));
-}
-
-// StandardElf without data completes execve, but faults once running.
-TEST(ElfTest, MissingText) {
- ElfBinary<64> elf = StandardElf();
- elf.data.clear();
- elf.UpdateOffsets();
-
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno));
- ASSERT_EQ(execve_errno, 0);
-
- int status;
- ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0),
- SyscallSucceedsWithValue(child));
- // It runs off the end of the zeroes filling the end of the page.
- EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV) << status;
-}
-
-// Typical ELF with a data + bss segment
-TEST(ElfTest, DataSegment) {
- ElfBinary<64> elf = StandardElf();
-
- // Create a standard ELF, but extend to 1.5 pages. The second page will be the
- // beginning of a multi-page data + bss segment.
- elf.data.resize(kPageSize + kPageSize / 2);
-
- decltype(elf)::ElfPhdr phdr = {};
- phdr.p_type = PT_LOAD;
- phdr.p_flags = PF_R | PF_W;
- phdr.p_offset = kPageSize;
- phdr.p_vaddr = 0x41000;
- phdr.p_filesz = kPageSize / 2;
- // The header is going to push vaddr up by a few hundred bytes. Keep p_memsz a
- // bit less than 2 pages so this mapping doesn't extend beyond 0x43000.
- phdr.p_memsz = 2 * kPageSize - kPageSize / 2;
- elf.phdrs.push_back(phdr);
-
- elf.UpdateOffsets();
-
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno));
- ASSERT_EQ(execve_errno, 0);
-
- ASSERT_NO_ERRNO(WaitStopped(child));
-
- EXPECT_THAT(
- child, ContainsMappings(std::vector<ProcMapsEntry>({
- // text page.
- {0x40000, 0x41000, true, false, true, true, 0, 0, 0, 0,
- file.path().c_str()},
- // data + bss page from file.
- {0x41000, 0x42000, true, true, false, true, kPageSize, 0, 0, 0,
- file.path().c_str()},
- // bss page from anon.
- {0x42000, 0x43000, true, true, false, true, 0, 0, 0, 0, ""},
- })));
-}
-
-// Additonal pages beyond filesz are always RW.
-//
-// N.B. Linux uses set_brk -> vm_brk to additional pages beyond filesz (even
-// though start_brk itself will always be beyond memsz). As a result, the
-// segment permissions don't apply; the mapping is always RW.
-TEST(ElfTest, ExtraMemPages) {
- ElfBinary<64> elf = StandardElf();
-
- // Create a standard ELF, but extend to 1.5 pages. The second page will be the
- // beginning of a multi-page data + bss segment.
- elf.data.resize(kPageSize + kPageSize / 2);
-
- decltype(elf)::ElfPhdr phdr = {};
- phdr.p_type = PT_LOAD;
- // RWX segment. The extra anon page will be RW anyways.
- //
- // N.B. Linux uses clear_user to clear the end of the file-mapped page, which
- // respects the mapping protections. Thus if we map this RO with memsz >
- // (unaligned) filesz, then execve will fail with EFAULT. See padzero(elf_bss)
- // in fs/binfmt_elf.c:load_elf_binary.
- //
- // N.N.B.B. The above only applies to the last segment. For earlier segments,
- // the clear_user error is ignored.
- phdr.p_flags = PF_R | PF_W | PF_X;
- phdr.p_offset = kPageSize;
- phdr.p_vaddr = 0x41000;
- phdr.p_filesz = kPageSize / 2;
- // The header is going to push vaddr up by a few hundred bytes. Keep p_memsz a
- // bit less than 2 pages so this mapping doesn't extend beyond 0x43000.
- phdr.p_memsz = 2 * kPageSize - kPageSize / 2;
- elf.phdrs.push_back(phdr);
-
- elf.UpdateOffsets();
-
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno));
- ASSERT_EQ(execve_errno, 0);
-
- ASSERT_NO_ERRNO(WaitStopped(child));
-
- EXPECT_THAT(child,
- ContainsMappings(std::vector<ProcMapsEntry>({
- // text page.
- {0x40000, 0x41000, true, false, true, true, 0, 0, 0, 0,
- file.path().c_str()},
- // data + bss page from file.
- {0x41000, 0x42000, true, true, true, true, kPageSize, 0, 0, 0,
- file.path().c_str()},
- // extra page from anon.
- {0x42000, 0x43000, true, true, false, true, 0, 0, 0, 0, ""},
- })));
-}
-
-// An aligned segment with filesz == 0, memsz > 0 is anon-only.
-TEST(ElfTest, AnonOnlySegment) {
- ElfBinary<64> elf = StandardElf();
-
- decltype(elf)::ElfPhdr phdr = {};
- phdr.p_type = PT_LOAD;
- // RO segment. The extra anon page will be RW anyways.
- phdr.p_flags = PF_R;
- phdr.p_offset = 0;
- phdr.p_vaddr = 0x41000;
- phdr.p_filesz = 0;
- phdr.p_memsz = kPageSize - 0xe8;
- elf.phdrs.push_back(phdr);
-
- elf.UpdateOffsets();
-
- // UpdateOffsets adjusts p_vaddr and p_offset by the header size, but we need
- // a page-aligned p_vaddr to get a truly anon-only page.
- elf.phdrs[2].p_vaddr = 0x41000;
- // N.B. p_offset is now unaligned, but Linux doesn't care since this is
- // anon-only.
-
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno));
- ASSERT_EQ(execve_errno, 0);
-
- ASSERT_NO_ERRNO(WaitStopped(child));
-
- EXPECT_THAT(child,
- ContainsMappings(std::vector<ProcMapsEntry>({
- // text page.
- {0x40000, 0x41000, true, false, true, true, 0, 0, 0, 0,
- file.path().c_str()},
- // anon page.
- {0x41000, 0x42000, true, true, false, true, 0, 0, 0, 0, ""},
- })));
-}
-
-// p_offset must have the same alignment as p_vaddr.
-TEST(ElfTest, UnalignedOffset) {
- ElfBinary<64> elf = StandardElf();
-
- // Unaligned offset.
- elf.phdrs[1].p_offset += 1;
-
- elf.UpdateOffsets();
-
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno));
-
- // execve(2) return EINVAL, but behavior varies between Linux and gVisor.
- //
- // On Linux, the new mm is committed before attempting to map into it. By the
- // time we hit EINVAL in the segment mmap, the old mm is gone. Linux returns
- // to an empty mm, which immediately segfaults.
- //
- // OTOH, gVisor maps into the new mm before committing it. Thus when it hits
- // failure, the caller is still intact to receive the error.
- if (IsRunningOnGvisor()) {
- ASSERT_EQ(execve_errno, EINVAL);
- } else {
- ASSERT_EQ(execve_errno, 0);
-
- int status;
- ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0),
- SyscallSucceedsWithValue(child));
- EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV) << status;
- }
-}
-
-// Linux will allow PT_LOAD segments to overlap.
-TEST(ElfTest, DirectlyOverlappingSegments) {
- // NOTE(b/37289926): see PIEOutOfOrderSegments.
- SKIP_IF(IsRunningOnGvisor());
-
- ElfBinary<64> elf = StandardElf();
-
- // Same as the StandardElf mapping.
- decltype(elf)::ElfPhdr phdr = {};
- phdr.p_type = PT_LOAD;
- // Add PF_W so we can differentiate this mapping from the first.
- phdr.p_flags = PF_R | PF_W | PF_X;
- phdr.p_offset = 0;
- phdr.p_vaddr = 0x40000;
- phdr.p_filesz = sizeof(kPtraceCode);
- phdr.p_memsz = phdr.p_filesz;
- elf.phdrs.push_back(phdr);
-
- elf.UpdateOffsets();
-
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno));
- ASSERT_EQ(execve_errno, 0);
-
- ASSERT_NO_ERRNO(WaitStopped(child));
-
- EXPECT_THAT(child, ContainsMappings(std::vector<ProcMapsEntry>({
- {0x40000, 0x41000, true, true, true, true, 0, 0, 0, 0,
- file.path().c_str()},
- })));
-}
-
-// Linux allows out-of-order PT_LOAD segments.
-TEST(ElfTest, OutOfOrderSegments) {
- // NOTE(b/37289926): see PIEOutOfOrderSegments.
- SKIP_IF(IsRunningOnGvisor());
-
- ElfBinary<64> elf = StandardElf();
-
- decltype(elf)::ElfPhdr phdr = {};
- phdr.p_type = PT_LOAD;
- phdr.p_flags = PF_R | PF_X;
- phdr.p_offset = 0;
- phdr.p_vaddr = 0x20000;
- phdr.p_filesz = sizeof(kPtraceCode);
- phdr.p_memsz = phdr.p_filesz;
- elf.phdrs.push_back(phdr);
-
- elf.UpdateOffsets();
-
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno));
- ASSERT_EQ(execve_errno, 0);
-
- ASSERT_NO_ERRNO(WaitStopped(child));
-
- EXPECT_THAT(child, ContainsMappings(std::vector<ProcMapsEntry>({
- {0x20000, 0x21000, true, false, true, true, 0, 0, 0, 0,
- file.path().c_str()},
- {0x40000, 0x41000, true, false, true, true, 0, 0, 0, 0,
- file.path().c_str()},
- })));
-}
-
-// header.e_phoff is bound the end of the file.
-TEST(ElfTest, OutOfBoundsPhdrs) {
- ElfBinary<64> elf = StandardElf();
- elf.header.e_phoff = 0x100000;
- elf.UpdateOffsets();
-
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno));
- // On Linux 3.11, this caused EIO. On newer Linux, it causes ENOEXEC.
- EXPECT_THAT(execve_errno, AnyOf(Eq(ENOEXEC), Eq(EIO)));
-}
-
-// Claim there is a phdr beyond the end of the file, but don't include it.
-TEST(ElfTest, MissingPhdr) {
- ElfBinary<64> elf = StandardElf();
-
- // Clear data so the file ends immediately after the phdrs.
- // N.B. Per ElfTest.MissingData, StandardElf without data completes execve
- // without error.
- elf.data.clear();
- elf.UpdateOffsets();
-
- // Claim that there is another phdr just beyond the end of the file. Of
- // course, it isn't accessible.
- elf.header.e_phnum++;
-
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno));
- // On Linux 3.11, this caused EIO. On newer Linux, it causes ENOEXEC.
- EXPECT_THAT(execve_errno, AnyOf(Eq(ENOEXEC), Eq(EIO)));
-}
-
-// No headers at all, just the ELF magic.
-TEST(ElfTest, MissingHeader) {
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0755));
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
-
- const char kElfMagic[] = {0x7f, 'E', 'L', 'F'};
-
- ASSERT_THAT(WriteFd(fd.get(), &kElfMagic, sizeof(kElfMagic)),
- SyscallSucceeds());
- fd.reset();
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno));
- EXPECT_EQ(execve_errno, ENOEXEC);
-}
-
-// Load a PIE ELF with a data + bss segment.
-TEST(ElfTest, PIE) {
- ElfBinary<64> elf = StandardElf();
-
- elf.header.e_type = ET_DYN;
-
- // Create a standard ELF, but extend to 1.5 pages. The second page will be the
- // beginning of a multi-page data + bss segment.
- elf.data.resize(kPageSize + kPageSize / 2);
-
- elf.header.e_entry = 0x0;
-
- decltype(elf)::ElfPhdr phdr = {};
- phdr.p_type = PT_LOAD;
- phdr.p_flags = PF_R | PF_W;
- phdr.p_offset = kPageSize;
- // Put the data segment at a bit of an offset.
- phdr.p_vaddr = 0x20000;
- phdr.p_filesz = kPageSize / 2;
- // The header is going to push vaddr up by a few hundred bytes. Keep p_memsz a
- // bit less than 2 pages so this mapping doesn't extend beyond 0x43000.
- phdr.p_memsz = 2 * kPageSize - kPageSize / 2;
- elf.phdrs.push_back(phdr);
-
- elf.UpdateOffsets();
-
- // The first segment really needs to start at 0 for a normal PIE binary, and
- // thus includes the headers.
- const uint64_t offset = elf.phdrs[1].p_offset;
- elf.phdrs[1].p_offset = 0x0;
- elf.phdrs[1].p_vaddr = 0x0;
- elf.phdrs[1].p_filesz += offset;
- elf.phdrs[1].p_memsz += offset;
-
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno));
- ASSERT_EQ(execve_errno, 0);
-
- ASSERT_NO_ERRNO(WaitStopped(child));
-
- // RIP tells us which page the first segment was loaded into.
- struct user_regs_struct regs;
- ASSERT_THAT(ptrace(PTRACE_GETREGS, child, 0, &regs), SyscallSucceeds());
-
- const uint64_t load_addr = regs.rip & ~(kPageSize - 1);
-
- EXPECT_THAT(child, ContainsMappings(std::vector<ProcMapsEntry>({
- // text page.
- {load_addr, load_addr + 0x1000, true, false, true,
- true, 0, 0, 0, 0, file.path().c_str()},
- // data + bss page from file.
- {load_addr + 0x20000, load_addr + 0x21000, true, true,
- false, true, kPageSize, 0, 0, 0, file.path().c_str()},
- // bss page from anon.
- {load_addr + 0x21000, load_addr + 0x22000, true, true,
- false, true, 0, 0, 0, 0, ""},
- })));
-}
-
-// PIE binary with a non-zero start address.
-//
-// This is non-standard for a PIE binary, but valid. The binary is still loaded
-// at an arbitrary address, not the first PT_LOAD vaddr.
-//
-// N.B. Linux changed this behavior in d1fd836dcf00d2028c700c7e44d2c23404062c90.
-// Previously, with "randomization" enabled, PIE binaries with a non-zero start
-// address would be be loaded at the address they specified because mmap was
-// passed the load address, which wasn't 0 as expected.
-//
-// This change is present in kernel v4.1+.
-TEST(ElfTest, PIENonZeroStart) {
- // gVisor has the newer behavior.
- if (!IsRunningOnGvisor()) {
- auto version = ASSERT_NO_ERRNO_AND_VALUE(GetKernelVersion());
- SKIP_IF(version.major < 4 || (version.major == 4 && version.minor < 1));
- }
-
- ElfBinary<64> elf = StandardElf();
-
- elf.header.e_type = ET_DYN;
-
- // Create a standard ELF, but extend to 1.5 pages. The second page will be the
- // beginning of a multi-page data + bss segment.
- elf.data.resize(kPageSize + kPageSize / 2);
-
- decltype(elf)::ElfPhdr phdr = {};
- phdr.p_type = PT_LOAD;
- phdr.p_flags = PF_R | PF_W;
- phdr.p_offset = kPageSize;
- // Put the data segment at a bit of an offset.
- phdr.p_vaddr = 0x60000;
- phdr.p_filesz = kPageSize / 2;
- // The header is going to push vaddr up by a few hundred bytes. Keep p_memsz a
- // bit less than 2 pages so this mapping doesn't extend beyond 0x43000.
- phdr.p_memsz = 2 * kPageSize - kPageSize / 2;
- elf.phdrs.push_back(phdr);
-
- elf.UpdateOffsets();
-
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno));
- ASSERT_EQ(execve_errno, 0);
-
- ASSERT_NO_ERRNO(WaitStopped(child));
-
- // RIP tells us which page the first segment was loaded into.
- struct user_regs_struct regs;
- ASSERT_THAT(ptrace(PTRACE_GETREGS, child, 0, &regs), SyscallSucceeds());
-
- const uint64_t load_addr = regs.rip & ~(kPageSize - 1);
-
- // The ELF is loaded at an arbitrary address, not the first PT_LOAD vaddr.
- //
- // N.B. this is technically flaky, but Linux is *extremely* unlikely to pick
- // this as the start address, as it searches from the top down.
- EXPECT_NE(load_addr, 0x40000);
-
- EXPECT_THAT(child, ContainsMappings(std::vector<ProcMapsEntry>({
- // text page.
- {load_addr, load_addr + 0x1000, true, false, true,
- true, 0, 0, 0, 0, file.path().c_str()},
- // data + bss page from file.
- {load_addr + 0x20000, load_addr + 0x21000, true, true,
- false, true, kPageSize, 0, 0, 0, file.path().c_str()},
- // bss page from anon.
- {load_addr + 0x21000, load_addr + 0x22000, true, true,
- false, true, 0, 0, 0, 0, ""},
- })));
-}
-
-TEST(ElfTest, PIEOutOfOrderSegments) {
- // TODO(b/37289926): This triggers a bug in Linux where it computes the size
- // of the binary as 0x20000 - 0x40000 = 0xfffffffffffe0000, which obviously
- // fails to map.
- //
- // We test gVisor's behavior (of rejecting the binary) because I assert that
- // Linux is wrong and needs to be fixed.
- SKIP_IF(!IsRunningOnGvisor());
-
- ElfBinary<64> elf = StandardElf();
-
- elf.header.e_type = ET_DYN;
-
- // Create a standard ELF, but extend to 1.5 pages. The second page will be the
- // beginning of a multi-page data + bss segment.
- elf.data.resize(kPageSize + kPageSize / 2);
-
- decltype(elf)::ElfPhdr phdr = {};
- phdr.p_type = PT_LOAD;
- phdr.p_flags = PF_R | PF_W;
- phdr.p_offset = kPageSize;
- // Put the data segment *before* the first segment.
- phdr.p_vaddr = 0x20000;
- phdr.p_filesz = kPageSize / 2;
- // The header is going to push vaddr up by a few hundred bytes. Keep p_memsz a
- // bit less than 2 pages so this mapping doesn't extend beyond 0x43000.
- phdr.p_memsz = 2 * kPageSize - kPageSize / 2;
- elf.phdrs.push_back(phdr);
-
- elf.UpdateOffsets();
-
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno));
- EXPECT_EQ(execve_errno, ENOEXEC);
-}
-
-// Standard dynamically linked binary with an ELF interpreter.
-TEST(ElfTest, ELFInterpreter) {
- ElfBinary<64> interpreter = StandardElf();
- interpreter.header.e_type = ET_DYN;
- interpreter.header.e_entry = 0x0;
- interpreter.UpdateOffsets();
-
- // The first segment really needs to start at 0 for a normal PIE binary, and
- // thus includes the headers.
- uint64_t const offset = interpreter.phdrs[1].p_offset;
- interpreter.phdrs[1].p_offset = 0x0;
- interpreter.phdrs[1].p_vaddr = 0x0;
- interpreter.phdrs[1].p_filesz += offset;
- interpreter.phdrs[1].p_memsz += offset;
-
- TempPath interpreter_file =
- ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(interpreter));
-
- ElfBinary<64> binary = StandardElf();
-
- // Append the interpreter path.
- int const interp_data_start = binary.data.size();
- for (char const c : interpreter_file.path()) {
- binary.data.push_back(c);
- }
- // NUL-terminate.
- binary.data.push_back(0);
- int const interp_data_size = binary.data.size() - interp_data_start;
-
- decltype(binary)::ElfPhdr phdr = {};
- phdr.p_type = PT_INTERP;
- phdr.p_offset = interp_data_start;
- phdr.p_filesz = interp_data_size;
- phdr.p_memsz = interp_data_size;
- // "If [PT_INTERP] is present, it must precede any loadable segment entry."
- //
- // However, Linux allows it anywhere, so we just stick it at the end to make
- // sure out-of-order PT_INTERP is OK.
- binary.phdrs.push_back(phdr);
-
- binary.UpdateOffsets();
-
- TempPath binary_file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(binary));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec(
- binary_file.path(), {binary_file.path()}, {}, &child, &execve_errno));
- ASSERT_EQ(execve_errno, 0);
-
- ASSERT_NO_ERRNO(WaitStopped(child));
-
- // RIP tells us which page the first segment of the interpreter was loaded
- // into.
- struct user_regs_struct regs;
- ASSERT_THAT(ptrace(PTRACE_GETREGS, child, 0, &regs), SyscallSucceeds());
-
- const uint64_t interp_load_addr = regs.rip & ~(kPageSize - 1);
-
- EXPECT_THAT(child,
- ContainsMappings(std::vector<ProcMapsEntry>({
- // Main binary
- {0x40000, 0x41000, true, false, true, true, 0, 0, 0, 0,
- binary_file.path().c_str()},
- // Interpreter
- {interp_load_addr, interp_load_addr + 0x1000, true, false,
- true, true, 0, 0, 0, 0, interpreter_file.path().c_str()},
- })));
-}
-
-// Test parameter to ElfInterpterStaticTest cases. The first item is a suffix to
-// add to the end of the interpreter path in the PT_INTERP segment and the
-// second is the expected execve(2) errno.
-using ElfInterpreterStaticParam = std::tuple<std::vector<char>, int>;
-
-class ElfInterpreterStaticTest
- : public ::testing::TestWithParam<ElfInterpreterStaticParam> {};
-
-// Statically linked ELF with a statically linked ELF interpreter.
-TEST_P(ElfInterpreterStaticTest, Test) {
- const std::vector<char> segment_suffix = std::get<0>(GetParam());
- const int expected_errno = std::get<1>(GetParam());
-
- ElfBinary<64> interpreter = StandardElf();
- interpreter.UpdateOffsets();
- TempPath interpreter_file =
- ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(interpreter));
-
- ElfBinary<64> binary = StandardElf();
- // The PT_LOAD segment conflicts with the interpreter's PT_LOAD segment. The
- // interpreter's will be mapped directly over the binary's.
-
- // Interpreter path plus the parameterized suffix in the PT_INTERP segment.
- const std::string path = interpreter_file.path();
- std::vector<char> segment(path.begin(), path.end());
- segment.insert(segment.end(), segment_suffix.begin(), segment_suffix.end());
- binary.AddInterpreter(segment);
-
- binary.UpdateOffsets();
-
- TempPath binary_file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(binary));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec(
- binary_file.path(), {binary_file.path()}, {}, &child, &execve_errno));
- ASSERT_EQ(execve_errno, expected_errno);
-
- if (expected_errno == 0) {
- ASSERT_NO_ERRNO(WaitStopped(child));
-
- EXPECT_THAT(child, ContainsMappings(std::vector<ProcMapsEntry>({
- // Interpreter.
- {0x40000, 0x41000, true, false, true, true, 0, 0, 0,
- 0, interpreter_file.path().c_str()},
- })));
- }
-}
-
-INSTANTIATE_TEST_SUITE_P(
- Cases, ElfInterpreterStaticTest,
- ::testing::ValuesIn({
- // Simple NUL-terminator to run the interpreter as normal.
- std::make_tuple(std::vector<char>({'\0'}), 0),
- // Add some garbage to the segment followed by a NUL-terminator. This is
- // ignored.
- std::make_tuple(std::vector<char>({'\0', 'b', '\0'}), 0),
- // Add some garbage to the segment without a NUL-terminator. Linux will
- // reject
- // this.
- std::make_tuple(std::vector<char>({'\0', 'b'}), ENOEXEC),
- }));
-
-// Test parameter to ElfInterpterBadPathTest cases. The first item is the
-// contents of the PT_INTERP segment and the second is the expected execve(2)
-// errno.
-using ElfInterpreterBadPathParam = std::tuple<std::vector<char>, int>;
-
-class ElfInterpreterBadPathTest
- : public ::testing::TestWithParam<ElfInterpreterBadPathParam> {};
-
-TEST_P(ElfInterpreterBadPathTest, Test) {
- const std::vector<char> segment = std::get<0>(GetParam());
- const int expected_errno = std::get<1>(GetParam());
-
- ElfBinary<64> binary = StandardElf();
- binary.AddInterpreter(segment);
- binary.UpdateOffsets();
-
- TempPath binary_file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(binary));
-
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec(
- binary_file.path(), {binary_file.path()}, {}, nullptr, &execve_errno));
- EXPECT_EQ(execve_errno, expected_errno);
-}
-
-INSTANTIATE_TEST_SUITE_P(
- Cases, ElfInterpreterBadPathTest,
- ::testing::ValuesIn({
- // NUL-terminated fake path in the PT_INTERP segment.
- std::make_tuple(std::vector<char>({'/', 'f', '/', 'b', '\0'}), ENOENT),
- // ELF interpreter not NUL-terminated.
- std::make_tuple(std::vector<char>({'/', 'f', '/', 'b'}), ENOEXEC),
- // ELF interpreter path omitted entirely.
- //
- // fs/binfmt_elf.c:load_elf_binary returns ENOEXEC if p_filesz is < 2
- // bytes.
- std::make_tuple(std::vector<char>({'\0'}), ENOEXEC),
- // ELF interpreter path = "\0".
- //
- // fs/binfmt_elf.c:load_elf_binary returns ENOEXEC if p_filesz is < 2
- // bytes, so add an extra byte to pass that check.
- //
- // load_elf_binary -> open_exec -> do_open_execat fails to check that
- // name != '\0' before calling do_filp_open, which thus opens the
- // working directory. do_open_execat returns EACCES because the
- // directory is not a regular file.
- std::make_tuple(std::vector<char>({'\0', '\0'}), EACCES),
- }));
-
-// Relative path to ELF interpreter.
-TEST(ElfTest, ELFInterpreterRelative) {
- ElfBinary<64> interpreter = StandardElf();
- interpreter.header.e_type = ET_DYN;
- interpreter.header.e_entry = 0x0;
- interpreter.UpdateOffsets();
-
- // The first segment really needs to start at 0 for a normal PIE binary, and
- // thus includes the headers.
- uint64_t const offset = interpreter.phdrs[1].p_offset;
- interpreter.phdrs[1].p_offset = 0x0;
- interpreter.phdrs[1].p_vaddr = 0x0;
- interpreter.phdrs[1].p_filesz += offset;
- interpreter.phdrs[1].p_memsz += offset;
-
- TempPath interpreter_file =
- ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(interpreter));
- auto cwd = ASSERT_NO_ERRNO_AND_VALUE(GetCWD());
- auto interpreter_relative =
- ASSERT_NO_ERRNO_AND_VALUE(GetRelativePath(cwd, interpreter_file.path()));
-
- ElfBinary<64> binary = StandardElf();
-
- // NUL-terminated path in the PT_INTERP segment.
- std::vector<char> segment(interpreter_relative.begin(),
- interpreter_relative.end());
- segment.push_back(0);
- binary.AddInterpreter(segment);
-
- binary.UpdateOffsets();
-
- TempPath binary_file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(binary));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec(
- binary_file.path(), {binary_file.path()}, {}, &child, &execve_errno));
- ASSERT_EQ(execve_errno, 0);
-
- ASSERT_NO_ERRNO(WaitStopped(child));
-
- // RIP tells us which page the first segment of the interpreter was loaded
- // into.
- struct user_regs_struct regs;
- ASSERT_THAT(ptrace(PTRACE_GETREGS, child, 0, &regs), SyscallSucceeds());
-
- const uint64_t interp_load_addr = regs.rip & ~(kPageSize - 1);
-
- EXPECT_THAT(child,
- ContainsMappings(std::vector<ProcMapsEntry>({
- // Main binary
- {0x40000, 0x41000, true, false, true, true, 0, 0, 0, 0,
- binary_file.path().c_str()},
- // Interpreter
- {interp_load_addr, interp_load_addr + 0x1000, true, false,
- true, true, 0, 0, 0, 0, interpreter_file.path().c_str()},
- })));
-}
-
-// ELF interpreter architecture doesn't match the binary.
-TEST(ElfTest, ELFInterpreterWrongArch) {
- ElfBinary<64> interpreter = StandardElf();
- interpreter.header.e_machine = EM_PPC64;
- interpreter.header.e_type = ET_DYN;
- interpreter.header.e_entry = 0x0;
- interpreter.UpdateOffsets();
-
- // The first segment really needs to start at 0 for a normal PIE binary, and
- // thus includes the headers.
- uint64_t const offset = interpreter.phdrs[1].p_offset;
- interpreter.phdrs[1].p_offset = 0x0;
- interpreter.phdrs[1].p_vaddr = 0x0;
- interpreter.phdrs[1].p_filesz += offset;
- interpreter.phdrs[1].p_memsz += offset;
-
- TempPath interpreter_file =
- ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(interpreter));
-
- ElfBinary<64> binary = StandardElf();
-
- // NUL-terminated path in the PT_INTERP segment.
- const std::string path = interpreter_file.path();
- std::vector<char> segment(path.begin(), path.end());
- segment.push_back(0);
- binary.AddInterpreter(segment);
-
- binary.UpdateOffsets();
-
- TempPath binary_file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(binary));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec(
- binary_file.path(), {binary_file.path()}, {}, &child, &execve_errno));
- ASSERT_EQ(execve_errno, ELIBBAD);
-}
-
-// No execute permissions on the binary.
-TEST(ElfTest, NoExecute) {
- ElfBinary<64> elf = StandardElf();
- elf.UpdateOffsets();
-
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf));
-
- ASSERT_THAT(chmod(file.path().c_str(), 0644), SyscallSucceeds());
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno));
- EXPECT_EQ(execve_errno, EACCES);
-}
-
-// Execute, but no read permissions on the binary works just fine.
-TEST(ElfTest, NoRead) {
- // TODO(gvisor.dev/issue/160): gVisor's backing filesystem may prevent the
- // sentry from reading the executable.
- SKIP_IF(IsRunningOnGvisor());
-
- ElfBinary<64> elf = StandardElf();
- elf.UpdateOffsets();
-
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf));
-
- ASSERT_THAT(chmod(file.path().c_str(), 0111), SyscallSucceeds());
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno));
- ASSERT_EQ(execve_errno, 0);
-
- ASSERT_NO_ERRNO(WaitStopped(child));
-
- // TODO(gvisor.dev/issue/160): A task with a non-readable executable is marked
- // non-dumpable, preventing access to proc files. gVisor does not implement
- // this behavior.
-}
-
-// No execute permissions on the ELF interpreter.
-TEST(ElfTest, ElfInterpreterNoExecute) {
- ElfBinary<64> interpreter = StandardElf();
- interpreter.header.e_type = ET_DYN;
- interpreter.header.e_entry = 0x0;
- interpreter.UpdateOffsets();
-
- // The first segment really needs to start at 0 for a normal PIE binary, and
- // thus includes the headers.
- uint64_t const offset = interpreter.phdrs[1].p_offset;
- interpreter.phdrs[1].p_offset = 0x0;
- interpreter.phdrs[1].p_vaddr = 0x0;
- interpreter.phdrs[1].p_filesz += offset;
- interpreter.phdrs[1].p_memsz += offset;
-
- TempPath interpreter_file =
- ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(interpreter));
-
- ElfBinary<64> binary = StandardElf();
-
- // NUL-terminated path in the PT_INTERP segment.
- const std::string path = interpreter_file.path();
- std::vector<char> segment(path.begin(), path.end());
- segment.push_back(0);
- binary.AddInterpreter(segment);
-
- binary.UpdateOffsets();
-
- TempPath binary_file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(binary));
-
- ASSERT_THAT(chmod(interpreter_file.path().c_str(), 0644), SyscallSucceeds());
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(interpreter_file.path(), {interpreter_file.path()}, {},
- &child, &execve_errno));
- EXPECT_EQ(execve_errno, EACCES);
-}
-
-// Execute a basic interpreter script.
-TEST(InterpreterScriptTest, Execute) {
- ElfBinary<64> elf = StandardElf();
- elf.UpdateOffsets();
- // Use /tmp explicitly to ensure the path is short enough.
- TempPath binary = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith("/tmp", elf));
-
- TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), absl::StrCat("#!", binary.path()), 0755));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(script.path(), {script.path()}, {}, &child, &execve_errno));
- ASSERT_EQ(execve_errno, 0);
-
- EXPECT_NO_ERRNO(WaitStopped(child));
-}
-
-// Whitespace after #!.
-TEST(InterpreterScriptTest, Whitespace) {
- ElfBinary<64> elf = StandardElf();
- elf.UpdateOffsets();
- // Use /tmp explicitly to ensure the path is short enough.
- TempPath binary = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith("/tmp", elf));
-
- TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), absl::StrCat("#! \t \t", binary.path()), 0755));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(script.path(), {script.path()}, {}, &child, &execve_errno));
- ASSERT_EQ(execve_errno, 0);
-
- EXPECT_NO_ERRNO(WaitStopped(child));
-}
-
-// Interpreter script is missing execute permission.
-TEST(InterpreterScriptTest, InterpreterScriptNoExecute) {
- ElfBinary<64> elf = StandardElf();
- elf.UpdateOffsets();
- // Use /tmp explicitly to ensure the path is short enough.
- TempPath binary = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith("/tmp", elf));
-
- TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), absl::StrCat("#!", binary.path()), 0644));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(script.path(), {script.path()}, {}, &child, &execve_errno));
- ASSERT_EQ(execve_errno, EACCES);
-}
-
-// Binary interpreter script refers to is missing execute permission.
-TEST(InterpreterScriptTest, BinaryNoExecute) {
- ElfBinary<64> elf = StandardElf();
- elf.UpdateOffsets();
- // Use /tmp explicitly to ensure the path is short enough.
- TempPath binary = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith("/tmp", elf));
-
- ASSERT_THAT(chmod(binary.path().c_str(), 0644), SyscallSucceeds());
-
- TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), absl::StrCat("#!", binary.path()), 0755));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(script.path(), {script.path()}, {}, &child, &execve_errno));
- ASSERT_EQ(execve_errno, EACCES);
-}
-
-// Linux will load interpreter scripts five levels deep, but no more.
-TEST(InterpreterScriptTest, MaxRecursion) {
- ElfBinary<64> elf = StandardElf();
- elf.UpdateOffsets();
- // Use /tmp explicitly to ensure the path is short enough.
- TempPath binary = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith("/tmp", elf));
-
- TempPath script1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- "/tmp", absl::StrCat("#!", binary.path()), 0755));
- TempPath script2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- "/tmp", absl::StrCat("#!", script1.path()), 0755));
- TempPath script3 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- "/tmp", absl::StrCat("#!", script2.path()), 0755));
- TempPath script4 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- "/tmp", absl::StrCat("#!", script3.path()), 0755));
- TempPath script5 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- "/tmp", absl::StrCat("#!", script4.path()), 0755));
- TempPath script6 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- "/tmp", absl::StrCat("#!", script5.path()), 0755));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(script6.path(), {script6.path()}, {}, &child, &execve_errno));
- // Too many levels of recursion.
- EXPECT_EQ(execve_errno, ELOOP);
-
- // The next level up is OK.
- auto cleanup2 = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(script5.path(), {script5.path()}, {}, &child, &execve_errno));
- ASSERT_EQ(execve_errno, 0);
-
- EXPECT_NO_ERRNO(WaitStopped(child));
-}
-
-// Interpreter script with a relative path.
-TEST(InterpreterScriptTest, RelativePath) {
- ElfBinary<64> elf = StandardElf();
- elf.UpdateOffsets();
- TempPath binary = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith("/tmp", elf));
-
- auto cwd = ASSERT_NO_ERRNO_AND_VALUE(GetCWD());
- auto binary_relative =
- ASSERT_NO_ERRNO_AND_VALUE(GetRelativePath(cwd, binary.path()));
-
- TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), absl::StrCat("#!", binary_relative), 0755));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(script.path(), {script.path()}, {}, &child, &execve_errno));
- ASSERT_EQ(execve_errno, 0);
-
- EXPECT_NO_ERRNO(WaitStopped(child));
-}
-
-// Interpreter script with .. in a path component.
-TEST(InterpreterScriptTest, UncleanPath) {
- ElfBinary<64> elf = StandardElf();
- elf.UpdateOffsets();
- // Use /tmp explicitly to ensure the path is short enough.
- TempPath binary = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith("/tmp", elf));
-
- TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), absl::StrCat("#!/tmp/../", binary.path()),
- 0755));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(script.path(), {script.path()}, {}, &child, &execve_errno));
- ASSERT_EQ(execve_errno, 0);
-
- EXPECT_NO_ERRNO(WaitStopped(child));
-}
-
-// Passed interpreter script is a symlink.
-TEST(InterpreterScriptTest, Symlink) {
- ElfBinary<64> elf = StandardElf();
- elf.UpdateOffsets();
- // Use /tmp explicitly to ensure the path is short enough.
- TempPath binary = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith("/tmp", elf));
-
- TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), absl::StrCat("#!", binary.path()), 0755));
-
- TempPath link = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateSymlinkTo(GetAbsoluteTestTmpdir(), script.path()));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(link.path(), {link.path()}, {}, &child, &execve_errno));
- ASSERT_EQ(execve_errno, 0);
-
- EXPECT_NO_ERRNO(WaitStopped(child));
-}
-
-// Interpreter script points to a symlink loop.
-TEST(InterpreterScriptTest, SymlinkLoop) {
- std::string const link1 = NewTempAbsPathInDir("/tmp");
- std::string const link2 = NewTempAbsPathInDir("/tmp");
-
- ASSERT_THAT(symlink(link2.c_str(), link1.c_str()), SyscallSucceeds());
- auto remove_link1 = Cleanup(
- [&link1] { EXPECT_THAT(unlink(link1.c_str()), SyscallSucceeds()); });
-
- ASSERT_THAT(symlink(link1.c_str(), link2.c_str()), SyscallSucceeds());
- auto remove_link2 = Cleanup(
- [&link2] { EXPECT_THAT(unlink(link2.c_str()), SyscallSucceeds()); });
-
- TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), absl::StrCat("#!", link1), 0755));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(script.path(), {script.path()}, {}, &child, &execve_errno));
- EXPECT_EQ(execve_errno, ELOOP);
-}
-
-// Binary is a symlink loop.
-TEST(ExecveTest, SymlinkLoop) {
- std::string const link1 = NewTempAbsPathInDir("/tmp");
- std::string const link2 = NewTempAbsPathInDir("/tmp");
-
- ASSERT_THAT(symlink(link2.c_str(), link1.c_str()), SyscallSucceeds());
- auto remove_link = Cleanup(
- [&link1] { EXPECT_THAT(unlink(link1.c_str()), SyscallSucceeds()); });
-
- ASSERT_THAT(symlink(link1.c_str(), link2.c_str()), SyscallSucceeds());
- auto remove_link2 = Cleanup(
- [&link2] { EXPECT_THAT(unlink(link2.c_str()), SyscallSucceeds()); });
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(link1, {link1}, {}, &child, &execve_errno));
- EXPECT_EQ(execve_errno, ELOOP);
-}
-
-// Binary is a directory.
-TEST(ExecveTest, Directory) {
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec("/tmp", {"/tmp"}, {}, &child, &execve_errno));
- EXPECT_EQ(execve_errno, EACCES);
-}
-
-// Pass a valid binary as a directory (extra / on the end).
-TEST(ExecveTest, BinaryAsDirectory) {
- ElfBinary<64> elf = StandardElf();
- elf.UpdateOffsets();
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf));
-
- std::string const path = absl::StrCat(file.path(), "/");
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(path, {path}, {}, &child, &execve_errno));
- EXPECT_EQ(execve_errno, ENOTDIR);
-}
-
-// The initial brk value is after the page at the end of the binary.
-TEST(ExecveTest, BrkAfterBinary) {
- ElfBinary<64> elf = StandardElf();
- elf.UpdateOffsets();
-
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf));
-
- pid_t child;
- int execve_errno;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno));
- ASSERT_EQ(execve_errno, 0);
-
- // Ensure it made it to SIGSTOP.
- ASSERT_NO_ERRNO(WaitStopped(child));
-
- struct user_regs_struct regs;
- ASSERT_THAT(ptrace(PTRACE_GETREGS, child, 0, &regs), SyscallSucceeds());
-
- // RIP is just beyond the final syscall instruction. Rewind to execute a brk
- // syscall.
- regs.rip -= kSyscallSize;
- regs.rax = __NR_brk;
- regs.rdi = 0;
- ASSERT_THAT(ptrace(PTRACE_SETREGS, child, 0, &regs), SyscallSucceeds());
-
- // Resume the child, waiting for syscall entry.
- ASSERT_THAT(ptrace(PTRACE_SYSCALL, child, 0, 0), SyscallSucceeds());
- int status;
- ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0),
- SyscallSucceedsWithValue(child));
- ASSERT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP)
- << "status = " << status;
-
- // Execute the syscall.
- ASSERT_THAT(ptrace(PTRACE_SYSCALL, child, 0, 0), SyscallSucceeds());
- ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0),
- SyscallSucceedsWithValue(child));
- ASSERT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP)
- << "status = " << status;
-
- ASSERT_THAT(ptrace(PTRACE_GETREGS, child, 0, &regs), SyscallSucceeds());
-
- // brk is after the text page.
- //
- // The kernel does brk randomization, so we can't be sure what the exact
- // address will be, but it is always beyond the final page in the binary.
- // i.e., it does not start immediately after memsz in the middle of a page.
- // Userspace may expect to use that space.
- EXPECT_GE(regs.rax, 0x41000);
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/exec_proc_exe_workload.cc b/test/syscalls/linux/exec_proc_exe_workload.cc
deleted file mode 100644
index b790fe5be..000000000
--- a/test/syscalls/linux/exec_proc_exe_workload.cc
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <stdlib.h>
-#include <unistd.h>
-
-#include <iostream>
-
-#include "test/util/fs_util.h"
-#include "test/util/posix_error.h"
-
-int main(int argc, char** argv, char** envp) {
- std::string exe =
- gvisor::testing::ProcessExePath(getpid()).ValueOrDie();
- if (exe[0] != '/') {
- std::cerr << "relative path: " << exe << std::endl;
- exit(1);
- }
- if (exe.find(argv[1]) != std::string::npos) {
- std::cerr << "matching path: " << exe << std::endl;
- exit(1);
- }
-
- return 0;
-}
diff --git a/test/syscalls/linux/exec_state_workload.cc b/test/syscalls/linux/exec_state_workload.cc
deleted file mode 100644
index 028902b14..000000000
--- a/test/syscalls/linux/exec_state_workload.cc
+++ /dev/null
@@ -1,202 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <signal.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/auxv.h>
-#include <sys/prctl.h>
-#include <sys/time.h>
-
-#include <iostream>
-#include <ostream>
-#include <string>
-
-#include "absl/strings/numbers.h"
-
-// Pretty-print a sigset_t.
-std::ostream& operator<<(std::ostream& out, const sigset_t& s) {
- out << "{ ";
-
- for (int i = 0; i < NSIG; i++) {
- if (sigismember(&s, i)) {
- out << i << " ";
- }
- }
-
- out << "}";
- return out;
-}
-
-// Verify that the signo handler is handler.
-int CheckSigHandler(uint32_t signo, uintptr_t handler) {
- struct sigaction sa;
- int ret = sigaction(signo, nullptr, &sa);
- if (ret < 0) {
- perror("sigaction");
- return 1;
- }
-
- if (reinterpret_cast<void (*)(int)>(handler) != sa.sa_handler) {
- std::cerr << "signo " << signo << " handler got: " << sa.sa_handler
- << " expected: " << std::hex << handler;
- return 1;
- }
- return 0;
-}
-
-// Verify that the signo is blocked.
-int CheckSigBlocked(uint32_t signo) {
- sigset_t s;
- int ret = sigprocmask(SIG_SETMASK, nullptr, &s);
- if (ret < 0) {
- perror("sigprocmask");
- return 1;
- }
-
- if (!sigismember(&s, signo)) {
- std::cerr << "signal " << signo << " not blocked in signal mask: " << s
- << std::endl;
- return 1;
- }
- return 0;
-}
-
-// Verify that the itimer is enabled.
-int CheckItimerEnabled(uint32_t timer) {
- struct itimerval itv;
- int ret = getitimer(timer, &itv);
- if (ret < 0) {
- perror("getitimer");
- return 1;
- }
-
- if (!itv.it_value.tv_sec && !itv.it_value.tv_usec &&
- !itv.it_interval.tv_sec && !itv.it_interval.tv_usec) {
- std::cerr << "timer " << timer
- << " not enabled. value sec: " << itv.it_value.tv_sec
- << " usec: " << itv.it_value.tv_usec
- << " interval sec: " << itv.it_interval.tv_sec
- << " usec: " << itv.it_interval.tv_usec << std::endl;
- return 1;
- }
- return 0;
-}
-
-int PrintExecFn() {
- unsigned long execfn = getauxval(AT_EXECFN);
- if (!execfn) {
- std::cerr << "AT_EXECFN missing" << std::endl;
- return 1;
- }
-
- std::cerr << reinterpret_cast<const char*>(execfn) << std::endl;
- return 0;
-}
-
-int PrintExecName() {
- const size_t name_length = 20;
- char name[name_length] = {0};
- if (prctl(PR_GET_NAME, name) < 0) {
- std::cerr << "prctl(PR_GET_NAME) failed" << std::endl;
- return 1;
- }
-
- std::cerr << name << std::endl;
- return 0;
-}
-
-void usage(const std::string& prog) {
- std::cerr << "usage:\n"
- << "\t" << prog << " CheckSigHandler <signo> <handler addr (hex)>\n"
- << "\t" << prog << " CheckSigBlocked <signo>\n"
- << "\t" << prog << " CheckTimerDisabled <timer>\n"
- << "\t" << prog << " PrintExecFn\n"
- << "\t" << prog << " PrintExecName" << std::endl;
-}
-
-int main(int argc, char** argv) {
- if (argc < 2) {
- usage(argv[0]);
- return 1;
- }
-
- std::string func(argv[1]);
-
- if (func == "CheckSigHandler") {
- if (argc != 4) {
- usage(argv[0]);
- return 1;
- }
-
- uint32_t signo;
- if (!absl::SimpleAtoi(argv[2], &signo)) {
- std::cerr << "invalid signo: " << argv[2] << std::endl;
- return 1;
- }
-
- uintptr_t handler;
- if (!absl::numbers_internal::safe_strtoi_base(argv[3], &handler, 16)) {
- std::cerr << "invalid handler: " << std::hex << argv[3] << std::endl;
- return 1;
- }
-
- return CheckSigHandler(signo, handler);
- }
-
- if (func == "CheckSigBlocked") {
- if (argc != 3) {
- usage(argv[0]);
- return 1;
- }
-
- uint32_t signo;
- if (!absl::SimpleAtoi(argv[2], &signo)) {
- std::cerr << "invalid signo: " << argv[2] << std::endl;
- return 1;
- }
-
- return CheckSigBlocked(signo);
- }
-
- if (func == "CheckItimerEnabled") {
- if (argc != 3) {
- usage(argv[0]);
- return 1;
- }
-
- uint32_t timer;
- if (!absl::SimpleAtoi(argv[2], &timer)) {
- std::cerr << "invalid signo: " << argv[2] << std::endl;
- return 1;
- }
-
- return CheckItimerEnabled(timer);
- }
-
- if (func == "PrintExecFn") {
- // N.B. This will be called as an interpreter script, with the script passed
- // as the third argument. We don't care about that script.
- return PrintExecFn();
- }
-
- if (func == "PrintExecName") {
- // N.B. This may be called as an interpreter script like PrintExecFn.
- return PrintExecName();
- }
-
- std::cerr << "Invalid function: " << func << std::endl;
- return 1;
-}
diff --git a/test/syscalls/linux/exit.cc b/test/syscalls/linux/exit.cc
deleted file mode 100644
index d52ea786b..000000000
--- a/test/syscalls/linux/exit.cc
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "absl/time/time.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/test_util.h"
-#include "test/util/time_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-void TestExit(int code) {
- pid_t pid = fork();
- if (pid == 0) {
- _exit(code);
- }
-
- ASSERT_THAT(pid, SyscallSucceeds());
-
- int status;
- EXPECT_THAT(RetryEINTR(waitpid)(pid, &status, 0), SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == code) << status;
-}
-
-TEST(ExitTest, Success) { TestExit(0); }
-
-TEST(ExitTest, Failure) { TestExit(1); }
-
-// This test ensures that a process's file descriptors are closed when it calls
-// exit(). In order to test this, the parent tries to read from a pipe whose
-// write end is held by the child. While the read is blocking, the child exits,
-// which should cause the parent to read 0 bytes due to EOF.
-TEST(ExitTest, CloseFds) {
- int pipe_fds[2];
- ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds());
-
- FileDescriptor read_fd(pipe_fds[0]);
- FileDescriptor write_fd(pipe_fds[1]);
-
- pid_t pid = fork();
- if (pid == 0) {
- read_fd.reset();
-
- SleepSafe(absl::Seconds(10));
-
- _exit(0);
- }
-
- EXPECT_THAT(pid, SyscallSucceeds());
-
- write_fd.reset();
-
- char buf[10];
- EXPECT_THAT(ReadFd(read_fd.get(), buf, sizeof(buf)),
- SyscallSucceedsWithValue(0));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/exit_script.sh b/test/syscalls/linux/exit_script.sh
deleted file mode 100755
index 527518e06..000000000
--- a/test/syscalls/linux/exit_script.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-
-# Copyright 2018 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-if [ $# -ne 1 ]; then
- echo "Usage: $0 exit_code"
- exit 255
-fi
-
-exit $1
diff --git a/test/syscalls/linux/fadvise64.cc b/test/syscalls/linux/fadvise64.cc
deleted file mode 100644
index 2af7aa6d9..000000000
--- a/test/syscalls/linux/fadvise64.cc
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <syscall.h>
-#include <unistd.h>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-TEST(FAdvise64Test, Basic) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY));
-
- // fadvise64 is noop in gVisor, so just test that it succeeds.
- ASSERT_THAT(syscall(__NR_fadvise64, fd.get(), 0, 10, POSIX_FADV_NORMAL),
- SyscallSucceeds());
- ASSERT_THAT(syscall(__NR_fadvise64, fd.get(), 0, 10, POSIX_FADV_RANDOM),
- SyscallSucceeds());
- ASSERT_THAT(syscall(__NR_fadvise64, fd.get(), 0, 10, POSIX_FADV_SEQUENTIAL),
- SyscallSucceeds());
- ASSERT_THAT(syscall(__NR_fadvise64, fd.get(), 0, 10, POSIX_FADV_WILLNEED),
- SyscallSucceeds());
- ASSERT_THAT(syscall(__NR_fadvise64, fd.get(), 0, 10, POSIX_FADV_DONTNEED),
- SyscallSucceeds());
- ASSERT_THAT(syscall(__NR_fadvise64, fd.get(), 0, 10, POSIX_FADV_NOREUSE),
- SyscallSucceeds());
-}
-
-TEST(FAdvise64Test, InvalidArgs) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY));
-
- // Note: offset is allowed to be negative.
- ASSERT_THAT(syscall(__NR_fadvise64, fd.get(), 0, static_cast<off_t>(-1),
- POSIX_FADV_NORMAL),
- SyscallFailsWithErrno(EINVAL));
- ASSERT_THAT(syscall(__NR_fadvise64, fd.get(), 0, 10, 12345),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(FAdvise64Test, NoPipes) {
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
- const FileDescriptor read(fds[0]);
- const FileDescriptor write(fds[1]);
-
- ASSERT_THAT(syscall(__NR_fadvise64, read.get(), 0, 10, POSIX_FADV_NORMAL),
- SyscallFailsWithErrno(ESPIPE));
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/fallocate.cc b/test/syscalls/linux/fallocate.cc
deleted file mode 100644
index 1c3d00287..000000000
--- a/test/syscalls/linux/fallocate.cc
+++ /dev/null
@@ -1,141 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <sys/resource.h>
-#include <sys/stat.h>
-#include <syscall.h>
-#include <time.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/file_base.h"
-#include "test/util/cleanup.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-int fallocate(int fd, int mode, off_t offset, off_t len) {
- return syscall(__NR_fallocate, fd, mode, offset, len);
-}
-
-class AllocateTest : public FileTest {
- void SetUp() override { FileTest::SetUp(); }
-};
-
-TEST_F(AllocateTest, Fallocate) {
- // Check that it starts at size zero.
- struct stat buf;
- ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds());
- EXPECT_EQ(buf.st_size, 0);
-
- // Grow to ten bytes.
- EXPECT_THAT(fallocate(test_file_fd_.get(), 0, 0, 10), SyscallSucceeds());
- ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds());
- EXPECT_EQ(buf.st_size, 10);
-
- // Allocate to a smaller size should be noop.
- EXPECT_THAT(fallocate(test_file_fd_.get(), 0, 0, 5), SyscallSucceeds());
- ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds());
- EXPECT_EQ(buf.st_size, 10);
-
- // Grow again.
- EXPECT_THAT(fallocate(test_file_fd_.get(), 0, 0, 20), SyscallSucceeds());
- ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds());
- EXPECT_EQ(buf.st_size, 20);
-
- // Grow with offset.
- EXPECT_THAT(fallocate(test_file_fd_.get(), 0, 10, 20), SyscallSucceeds());
- ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds());
- EXPECT_EQ(buf.st_size, 30);
-
- // Grow with offset beyond EOF.
- EXPECT_THAT(fallocate(test_file_fd_.get(), 0, 39, 1), SyscallSucceeds());
- ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds());
- EXPECT_EQ(buf.st_size, 40);
-}
-
-TEST_F(AllocateTest, FallocateInvalid) {
- // Invalid FD
- EXPECT_THAT(fallocate(-1, 0, 0, 10), SyscallFailsWithErrno(EBADF));
-
- // Negative offset and size.
- EXPECT_THAT(fallocate(test_file_fd_.get(), 0, -1, 10),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(fallocate(test_file_fd_.get(), 0, 0, -1),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(fallocate(test_file_fd_.get(), 0, -1, -1),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_F(AllocateTest, FallocateReadonly) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY));
- EXPECT_THAT(fallocate(fd.get(), 0, 0, 10), SyscallFailsWithErrno(EBADF));
-}
-
-TEST_F(AllocateTest, FallocatePipe) {
- int pipes[2];
- EXPECT_THAT(pipe(pipes), SyscallSucceeds());
- auto cleanup = Cleanup([&pipes] {
- EXPECT_THAT(close(pipes[0]), SyscallSucceeds());
- EXPECT_THAT(close(pipes[1]), SyscallSucceeds());
- });
-
- EXPECT_THAT(fallocate(pipes[1], 0, 0, 10), SyscallFailsWithErrno(ESPIPE));
-}
-
-TEST_F(AllocateTest, FallocateChar) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/null", O_RDWR));
- EXPECT_THAT(fallocate(fd.get(), 0, 0, 10), SyscallFailsWithErrno(ENODEV));
-}
-
-TEST_F(AllocateTest, FallocateRlimit) {
- // Get the current rlimit and restore after test run.
- struct rlimit initial_lim;
- ASSERT_THAT(getrlimit(RLIMIT_FSIZE, &initial_lim), SyscallSucceeds());
- auto cleanup = Cleanup([&initial_lim] {
- EXPECT_THAT(setrlimit(RLIMIT_FSIZE, &initial_lim), SyscallSucceeds());
- });
-
- // Try growing past the file size limit.
- sigset_t new_mask;
- sigemptyset(&new_mask);
- sigaddset(&new_mask, SIGXFSZ);
- sigprocmask(SIG_BLOCK, &new_mask, nullptr);
-
- struct rlimit setlim = {};
- setlim.rlim_cur = 1024;
- setlim.rlim_max = RLIM_INFINITY;
- ASSERT_THAT(setrlimit(RLIMIT_FSIZE, &setlim), SyscallSucceeds());
-
- EXPECT_THAT(fallocate(test_file_fd_.get(), 0, 0, 1025),
- SyscallFailsWithErrno(EFBIG));
-
- struct timespec timelimit = {};
- timelimit.tv_sec = 10;
- EXPECT_EQ(sigtimedwait(&new_mask, nullptr, &timelimit), SIGXFSZ);
- ASSERT_THAT(sigprocmask(SIG_UNBLOCK, &new_mask, nullptr), SyscallSucceeds());
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/fault.cc b/test/syscalls/linux/fault.cc
deleted file mode 100644
index f6e19026f..000000000
--- a/test/syscalls/linux/fault.cc
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#define _GNU_SOURCE 1
-#include <signal.h>
-#include <ucontext.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-__attribute__((noinline)) void Fault(void) {
- volatile int* foo = nullptr;
- *foo = 0;
-}
-
-int GetPcFromUcontext(ucontext_t* uc, uintptr_t* pc) {
-#if defined(__x86_64__)
- *pc = uc->uc_mcontext.gregs[REG_RIP];
- return 1;
-#elif defined(__i386__)
- *pc = uc->uc_mcontext.gregs[REG_EIP];
- return 1;
-#else
- return 0;
-#endif
-}
-
-void sigact_handler(int sig, siginfo_t* siginfo, void* context) {
- uintptr_t pc;
- if (GetPcFromUcontext(reinterpret_cast<ucontext_t*>(context), &pc)) {
- /* Expect Fault() to be at most 64 bytes in size. */
- uintptr_t fault_addr = reinterpret_cast<uintptr_t>(&Fault);
- EXPECT_GE(pc, fault_addr);
- EXPECT_LT(pc, fault_addr + 64);
- exit(0);
- }
-}
-
-TEST(FaultTest, InRange) {
- // Reset the signal handler to do nothing so that it doesn't freak out
- // the test runner when we fire an alarm.
- struct sigaction sa = {};
- sa.sa_sigaction = sigact_handler;
- sigfillset(&sa.sa_mask);
- sa.sa_flags = SA_SIGINFO;
- ASSERT_THAT(sigaction(SIGSEGV, &sa, nullptr), SyscallSucceeds());
-
- Fault();
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/fchdir.cc b/test/syscalls/linux/fchdir.cc
deleted file mode 100644
index 08bcae1e8..000000000
--- a/test/syscalls/linux/fchdir.cc
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/un.h>
-
-#include "gtest/gtest.h"
-#include "test/util/capability_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(FchdirTest, Success) {
- auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- int fd;
- ASSERT_THAT(fd = open(temp_dir.path().c_str(), O_DIRECTORY | O_RDONLY),
- SyscallSucceeds());
-
- EXPECT_THAT(fchdir(fd), SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
- // Change CWD to a permanent location as temp dirs will be cleaned up.
- EXPECT_THAT(chdir("/"), SyscallSucceeds());
-}
-
-TEST(FchdirTest, InvalidFD) {
- EXPECT_THAT(fchdir(-1), SyscallFailsWithErrno(EBADF));
-}
-
-TEST(FchdirTest, PermissionDenied) {
- // Drop capabilities that allow us to override directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
-
- auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0666 /* mode */));
-
- int fd;
- ASSERT_THAT(fd = open(temp_dir.path().c_str(), O_DIRECTORY | O_RDONLY),
- SyscallSucceeds());
-
- EXPECT_THAT(fchdir(fd), SyscallFailsWithErrno(EACCES));
- EXPECT_THAT(close(fd), SyscallSucceeds());
-}
-
-TEST(FchdirTest, NotDir) {
- auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- int fd;
- ASSERT_THAT(fd = open(temp_file.path().c_str(), O_CREAT | O_RDONLY, 0777),
- SyscallSucceeds());
-
- EXPECT_THAT(fchdir(fd), SyscallFailsWithErrno(ENOTDIR));
- EXPECT_THAT(close(fd), SyscallSucceeds());
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/fcntl.cc b/test/syscalls/linux/fcntl.cc
deleted file mode 100644
index 2f8e7c9dd..000000000
--- a/test/syscalls/linux/fcntl.cc
+++ /dev/null
@@ -1,970 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <signal.h>
-#include <syscall.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "absl/base/macros.h"
-#include "absl/base/port.h"
-#include "absl/memory/memory.h"
-#include "absl/strings/str_cat.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/cleanup.h"
-#include "test/util/eventfd_util.h"
-#include "test/util/multiprocess_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-#include "test/util/timer_util.h"
-
-DEFINE_string(child_setlock_on, "",
- "Contains the path to try to set a file lock on.");
-DEFINE_bool(child_setlock_write, false,
- "Whether to set a writable lock (otherwise readable)");
-DEFINE_bool(blocking, false,
- "Whether to set a blocking lock (otherwise non-blocking).");
-DEFINE_bool(retry_eintr, false, "Whether to retry in the subprocess on EINTR.");
-DEFINE_uint64(child_setlock_start, 0, "The value of struct flock start");
-DEFINE_uint64(child_setlock_len, 0, "The value of struct flock len");
-DEFINE_int32(socket_fd, -1,
- "A socket to use for communicating more state back "
- "to the parent.");
-
-namespace gvisor {
-namespace testing {
-
-// O_LARGEFILE as defined by Linux. glibc tries to be clever by setting it to 0
-// because "it isn't needed", even though Linux can return it via F_GETFL.
-constexpr int kOLargeFile = 00100000;
-
-class FcntlLockTest : public ::testing::Test {
- public:
- void SetUp() override {
- // Let's make a socket pair.
- ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, fds_), SyscallSucceeds());
- }
-
- void TearDown() override {
- EXPECT_THAT(close(fds_[0]), SyscallSucceeds());
- EXPECT_THAT(close(fds_[1]), SyscallSucceeds());
- }
-
- int64_t GetSubprocessFcntlTimeInUsec() {
- int64_t ret = 0;
- EXPECT_THAT(ReadFd(fds_[0], reinterpret_cast<void*>(&ret), sizeof(ret)),
- SyscallSucceedsWithValue(sizeof(ret)));
- return ret;
- }
-
- // The first fd will remain with the process creating the subprocess
- // and the second will go to the subprocess.
- int fds_[2] = {};
-};
-
-namespace {
-
-PosixErrorOr<Cleanup> SubprocessLock(std::string const& path, bool for_write,
- bool blocking, bool retry_eintr, int fd,
- off_t start, off_t length, pid_t* child) {
- std::vector<std::string> args = {
- "/proc/self/exe", "--child_setlock_on", path,
- "--child_setlock_start", absl::StrCat(start), "--child_setlock_len",
- absl::StrCat(length), "--socket_fd", absl::StrCat(fd)};
-
- if (for_write) {
- args.push_back("--child_setlock_write");
- }
-
- if (blocking) {
- args.push_back("--blocking");
- }
-
- if (retry_eintr) {
- args.push_back("--retry_eintr");
- }
-
- int execve_errno = 0;
- ASSIGN_OR_RETURN_ERRNO(
- auto cleanup,
- ForkAndExec("/proc/self/exe", ExecveArray(args.begin(), args.end()), {},
- nullptr, child, &execve_errno));
-
- if (execve_errno != 0) {
- return PosixError(execve_errno, "execve");
- }
-
- return std::move(cleanup);
-}
-
-TEST(FcntlTest, SetCloExec) {
- // Open an eventfd file descriptor with FD_CLOEXEC descriptor flag not set.
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, 0));
- ASSERT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(0));
-
- // Set the FD_CLOEXEC flag.
- ASSERT_THAT(fcntl(fd.get(), F_SETFD, FD_CLOEXEC), SyscallSucceeds());
- ASSERT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC));
-}
-
-TEST(FcntlTest, ClearCloExec) {
- // Open an eventfd file descriptor with FD_CLOEXEC descriptor flag set.
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_CLOEXEC));
- ASSERT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC));
-
- // Clear the FD_CLOEXEC flag.
- ASSERT_THAT(fcntl(fd.get(), F_SETFD, 0), SyscallSucceeds());
- ASSERT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(0));
-}
-
-TEST(FcntlTest, IndependentDescriptorFlags) {
- // Open an eventfd file descriptor with FD_CLOEXEC descriptor flag not set.
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, 0));
- ASSERT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(0));
-
- // Duplicate the descriptor. Ensure that it also doesn't have FD_CLOEXEC.
- FileDescriptor newfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup());
- ASSERT_THAT(fcntl(newfd.get(), F_GETFD), SyscallSucceedsWithValue(0));
-
- // Set FD_CLOEXEC on the first FD.
- ASSERT_THAT(fcntl(fd.get(), F_SETFD, FD_CLOEXEC), SyscallSucceeds());
- ASSERT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC));
-
- // Ensure that the second FD is unaffected by the change on the first.
- ASSERT_THAT(fcntl(newfd.get(), F_GETFD), SyscallSucceedsWithValue(0));
-}
-
-// All file description flags passed to open appear in F_GETFL.
-TEST(FcntlTest, GetAllFlags) {
- TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- int flags = O_RDWR | O_DIRECT | O_SYNC | O_NONBLOCK | O_APPEND;
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), flags));
-
- // Linux forces O_LARGEFILE on all 64-bit kernels and gVisor's is 64-bit.
- int expected = flags | kOLargeFile;
-
- int rflags;
- EXPECT_THAT(rflags = fcntl(fd.get(), F_GETFL), SyscallSucceeds());
- EXPECT_EQ(rflags, expected);
-}
-
-TEST(FcntlTest, SetFlags) {
- TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), 0));
-
- int const flags = O_RDWR | O_DIRECT | O_SYNC | O_NONBLOCK | O_APPEND;
- EXPECT_THAT(fcntl(fd.get(), F_SETFL, flags), SyscallSucceeds());
-
- // Can't set O_RDWR or O_SYNC.
- // Linux forces O_LARGEFILE on all 64-bit kernels and gVisor's is 64-bit.
- int expected = O_DIRECT | O_NONBLOCK | O_APPEND | kOLargeFile;
-
- int rflags;
- EXPECT_THAT(rflags = fcntl(fd.get(), F_GETFL), SyscallSucceeds());
- EXPECT_EQ(rflags, expected);
-}
-
-TEST_F(FcntlLockTest, SetLockBadFd) {
- struct flock fl;
- fl.l_type = F_WRLCK;
- fl.l_whence = SEEK_SET;
- fl.l_start = 0;
- // len 0 has a special meaning: lock all bytes despite how
- // large the file grows.
- fl.l_len = 0;
- EXPECT_THAT(fcntl(-1, F_SETLK, &fl), SyscallFailsWithErrno(EBADF));
-}
-
-TEST_F(FcntlLockTest, SetLockPipe) {
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
-
- struct flock fl;
- fl.l_type = F_WRLCK;
- fl.l_whence = SEEK_SET;
- fl.l_start = 0;
- // Same as SetLockBadFd, but doesn't matter, we expect this to fail.
- fl.l_len = 0;
- EXPECT_THAT(fcntl(fds[0], F_SETLK, &fl), SyscallFailsWithErrno(EBADF));
- EXPECT_THAT(close(fds[0]), SyscallSucceeds());
- EXPECT_THAT(close(fds[1]), SyscallSucceeds());
-}
-
-TEST_F(FcntlLockTest, SetLockDir) {
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_RDONLY, 0666));
-
- struct flock fl;
- fl.l_type = F_RDLCK;
- fl.l_whence = SEEK_SET;
- fl.l_start = 0;
- // Same as SetLockBadFd.
- fl.l_len = 0;
-
- EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds());
-}
-
-TEST_F(FcntlLockTest, SetLockBadOpenFlagsWrite) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY, 0666));
-
- struct flock fl0;
- fl0.l_type = F_WRLCK;
- fl0.l_whence = SEEK_SET;
- fl0.l_start = 0;
- // Same as SetLockBadFd.
- fl0.l_len = 0;
-
- // Expect that setting a write lock using a read only file descriptor
- // won't work.
- EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl0), SyscallFailsWithErrno(EBADF));
-}
-
-TEST_F(FcntlLockTest, SetLockBadOpenFlagsRead) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_WRONLY, 0666));
-
- struct flock fl1;
- fl1.l_type = F_RDLCK;
- fl1.l_whence = SEEK_SET;
- fl1.l_start = 0;
- // Same as SetLockBadFd.
- fl1.l_len = 0;
-
- EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl1), SyscallFailsWithErrno(EBADF));
-}
-
-TEST_F(FcntlLockTest, SetLockUnlockOnNothing) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666));
-
- struct flock fl;
- fl.l_type = F_UNLCK;
- fl.l_whence = SEEK_SET;
- fl.l_start = 0;
- // Same as SetLockBadFd.
- fl.l_len = 0;
-
- EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds());
-}
-
-TEST_F(FcntlLockTest, SetWriteLockSingleProc) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- FileDescriptor fd0 =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666));
-
- struct flock fl;
- fl.l_type = F_WRLCK;
- fl.l_whence = SEEK_SET;
- fl.l_start = 0;
- // Same as SetLockBadFd.
- fl.l_len = 0;
-
- EXPECT_THAT(fcntl(fd0.get(), F_SETLK, &fl), SyscallSucceeds());
- // Expect to be able to take the same lock on the same fd no problem.
- EXPECT_THAT(fcntl(fd0.get(), F_SETLK, &fl), SyscallSucceeds());
-
- FileDescriptor fd1 =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666));
-
- // Expect to be able to take the same lock from a different fd but for
- // the same process.
- EXPECT_THAT(fcntl(fd1.get(), F_SETLK, &fl), SyscallSucceeds());
-}
-
-TEST_F(FcntlLockTest, SetReadLockMultiProc) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666));
-
- struct flock fl;
- fl.l_type = F_RDLCK;
- fl.l_whence = SEEK_SET;
- fl.l_start = 0;
- // Same as SetLockBadFd.
- fl.l_len = 0;
- EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds());
-
- // spawn a child process to take a read lock on the same file.
- pid_t child_pid = 0;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- SubprocessLock(file.path(), false /* write lock */,
- false /* nonblocking */, false /* no eintr retry */,
- -1 /* no socket fd */, fl.l_start, fl.l_len, &child_pid));
-
- int status = 0;
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "Exited with code: " << status;
-}
-
-TEST_F(FcntlLockTest, SetReadThenWriteLockMultiProc) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666));
-
- struct flock fl;
- fl.l_type = F_RDLCK;
- fl.l_whence = SEEK_SET;
- fl.l_start = 0;
- // Same as SetLockBadFd.
- fl.l_len = 0;
- EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds());
-
- // Assert that another process trying to lock on the same file will fail
- // with EAGAIN. It's important that we keep the fd above open so that
- // that the other process will contend with the lock.
- pid_t child_pid = 0;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- SubprocessLock(file.path(), true /* write lock */,
- false /* nonblocking */, false /* no eintr retry */,
- -1 /* no socket fd */, fl.l_start, fl.l_len, &child_pid));
-
- int status = 0;
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == EAGAIN)
- << "Exited with code: " << status;
-
- // Close the fd: we want to test that another process can acquire the
- // lock after this point.
- fd.reset();
- // Assert that another process can now acquire the lock.
-
- child_pid = 0;
- auto cleanup2 = ASSERT_NO_ERRNO_AND_VALUE(
- SubprocessLock(file.path(), true /* write lock */,
- false /* nonblocking */, false /* no eintr retry */,
- -1 /* no socket fd */, fl.l_start, fl.l_len, &child_pid));
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "Exited with code: " << status;
-}
-
-TEST_F(FcntlLockTest, SetWriteThenReadLockMultiProc) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666));
- // Same as SetReadThenWriteLockMultiProc.
-
- struct flock fl;
- fl.l_type = F_WRLCK;
- fl.l_whence = SEEK_SET;
- fl.l_start = 0;
- // Same as SetLockBadFd.
- fl.l_len = 0;
-
- // Same as SetReadThenWriteLockMultiProc.
- EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds());
-
- // Same as SetReadThenWriteLockMultiProc.
- pid_t child_pid = 0;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- SubprocessLock(file.path(), false /* write lock */,
- false /* nonblocking */, false /* no eintr retry */,
- -1 /* no socket fd */, fl.l_start, fl.l_len, &child_pid));
-
- int status = 0;
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == EAGAIN)
- << "Exited with code: " << status;
-
- // Same as SetReadThenWriteLockMultiProc.
- fd.reset(); // Close the fd.
-
- // Same as SetReadThenWriteLockMultiProc.
- child_pid = 0;
- auto cleanup2 = ASSERT_NO_ERRNO_AND_VALUE(
- SubprocessLock(file.path(), false /* write lock */,
- false /* nonblocking */, false /* no eintr retry */,
- -1 /* no socket fd */, fl.l_start, fl.l_len, &child_pid));
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "Exited with code: " << status;
-}
-
-TEST_F(FcntlLockTest, SetWriteLockMultiProc) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666));
- // Same as SetReadThenWriteLockMultiProc.
-
- struct flock fl;
- fl.l_type = F_WRLCK;
- fl.l_whence = SEEK_SET;
- fl.l_start = 0;
- // Same as SetLockBadFd.
- fl.l_len = 0;
-
- // Same as SetReadWriteLockMultiProc.
- EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds());
-
- // Same as SetReadWriteLockMultiProc.
- pid_t child_pid = 0;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- SubprocessLock(file.path(), true /* write lock */,
- false /* nonblocking */, false /* no eintr retry */,
- -1 /* no socket fd */, fl.l_start, fl.l_len, &child_pid));
- int status = 0;
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == EAGAIN)
- << "Exited with code: " << status;
-
- fd.reset(); // Close the FD.
- // Same as SetReadWriteLockMultiProc.
- child_pid = 0;
- auto cleanup2 = ASSERT_NO_ERRNO_AND_VALUE(
- SubprocessLock(file.path(), true /* write lock */,
- false /* nonblocking */, false /* no eintr retry */,
- -1 /* no socket fd */, fl.l_start, fl.l_len, &child_pid));
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "Exited with code: " << status;
-}
-
-TEST_F(FcntlLockTest, SetLockIsRegional) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666));
-
- struct flock fl;
- fl.l_type = F_WRLCK;
- fl.l_whence = SEEK_SET;
- fl.l_start = 0;
- fl.l_len = 4096;
-
- // Same as SetReadWriteLockMultiProc.
- EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds());
-
- // Same as SetReadWriteLockMultiProc.
- pid_t child_pid = 0;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- SubprocessLock(file.path(), true /* write lock */,
- false /* nonblocking */, false /* no eintr retry */,
- -1 /* no socket fd */, fl.l_len, 0, &child_pid));
- int status = 0;
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "Exited with code: " << status;
-}
-
-TEST_F(FcntlLockTest, SetLockUpgradeDowngrade) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666));
-
- struct flock fl;
- fl.l_type = F_RDLCK;
- fl.l_whence = SEEK_SET;
- fl.l_start = 0;
- // Same as SetLockBadFd.
- fl.l_len = 0;
-
- // Same as SetReadWriteLockMultiProc.
- EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds());
-
- // Upgrade to a write lock. This will prevent anyone else from taking
- // the lock.
- fl.l_type = F_WRLCK;
- EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds());
-
- // Same as SetReadWriteLockMultiProc.,
- pid_t child_pid = 0;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- SubprocessLock(file.path(), false /* write lock */,
- false /* nonblocking */, false /* no eintr retry */,
- -1 /* no socket fd */, fl.l_start, fl.l_len, &child_pid));
-
- int status = 0;
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == EAGAIN)
- << "Exited with code: " << status;
-
- // Downgrade back to a read lock.
- fl.l_type = F_RDLCK;
- EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds());
-
- // Do the same stint as before, but this time it should succeed.
- child_pid = 0;
- auto cleanup2 = ASSERT_NO_ERRNO_AND_VALUE(
- SubprocessLock(file.path(), false /* write lock */,
- false /* nonblocking */, false /* no eintr retry */,
- -1 /* no socket fd */, fl.l_start, fl.l_len, &child_pid));
-
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "Exited with code: " << status;
-}
-
-TEST_F(FcntlLockTest, SetLockDroppedOnClose) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666));
-
- // While somewhat surprising, obtaining another fd to the same file and
- // then closing it in this process drops *all* locks.
- FileDescriptor other_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666));
- // Same as SetReadThenWriteLockMultiProc.
-
- struct flock fl;
- fl.l_type = F_WRLCK;
- fl.l_whence = SEEK_SET;
- fl.l_start = 0;
- // Same as SetLockBadFd.
- fl.l_len = 0;
-
- // Same as SetReadWriteLockMultiProc.
- EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds());
-
- other_fd.reset(); // Close.
-
- // Expect to be able to get the lock, given that the close above dropped it.
- pid_t child_pid = 0;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- SubprocessLock(file.path(), true /* write lock */,
- false /* nonblocking */, false /* no eintr retry */,
- -1 /* no socket fd */, fl.l_start, fl.l_len, &child_pid));
-
- int status = 0;
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "Exited with code: " << status;
-}
-
-TEST_F(FcntlLockTest, SetLockUnlock) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666));
-
- // Setup two regional locks with different permissions.
- struct flock fl0;
- fl0.l_type = F_WRLCK;
- fl0.l_whence = SEEK_SET;
- fl0.l_start = 0;
- fl0.l_len = 4096;
-
- struct flock fl1;
- fl1.l_type = F_RDLCK;
- fl1.l_whence = SEEK_SET;
- fl1.l_start = 4096;
- // Same as SetLockBadFd.
- fl1.l_len = 0;
-
- // Set both region locks.
- EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl0), SyscallSucceeds());
- EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl1), SyscallSucceeds());
-
- // Another process should fail to take a read lock on the entire file
- // due to the regional write lock.
- pid_t child_pid = 0;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(SubprocessLock(
- file.path(), false /* write lock */, false /* nonblocking */,
- false /* no eintr retry */, -1 /* no socket fd */, 0, 0, &child_pid));
-
- int status = 0;
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == EAGAIN)
- << "Exited with code: " << status;
-
- // Then only unlock the writable one. This should ensure that other
- // processes can take any read lock that it wants.
- fl0.l_type = F_UNLCK;
- EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl0), SyscallSucceeds());
-
- // Another process should now succeed to get a read lock on the entire file.
- child_pid = 0;
- auto cleanup2 = ASSERT_NO_ERRNO_AND_VALUE(SubprocessLock(
- file.path(), false /* write lock */, false /* nonblocking */,
- false /* no eintr retry */, -1 /* no socket fd */, 0, 0, &child_pid));
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "Exited with code: " << status;
-}
-
-TEST_F(FcntlLockTest, SetLockAcrossRename) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666));
-
- // Setup two regional locks with different permissions.
- struct flock fl;
- fl.l_type = F_WRLCK;
- fl.l_whence = SEEK_SET;
- fl.l_start = 0;
- // Same as SetLockBadFd.
- fl.l_len = 0;
-
- // Set the region lock.
- EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds());
-
- // Rename the file to someplace nearby.
- std::string const newpath = NewTempAbsPath();
- EXPECT_THAT(rename(file.path().c_str(), newpath.c_str()), SyscallSucceeds());
-
- // Another process should fail to take a read lock on the renamed file
- // since we still have an open handle to the inode.
- pid_t child_pid = 0;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- SubprocessLock(newpath, false /* write lock */, false /* nonblocking */,
- false /* no eintr retry */, -1 /* no socket fd */,
- fl.l_start, fl.l_len, &child_pid));
-
- int status = 0;
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == EAGAIN)
- << "Exited with code: " << status;
-}
-
-// NOTE: The blocking tests below aren't perfect. It's hard to assert exactly
-// what the kernel did while handling a syscall. These tests are timing based
-// because there really isn't any other reasonable way to assert that correct
-// blocking behavior happened.
-
-// This test will verify that blocking works as expected when another process
-// holds a write lock when obtaining a write lock. This test will hold the lock
-// for some amount of time and then wait for the second process to send over the
-// socket_fd the amount of time it was blocked for before the lock succeeded.
-TEST_F(FcntlLockTest, SetWriteLockThenBlockingWriteLock) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666));
-
- struct flock fl;
- fl.l_type = F_WRLCK;
- fl.l_whence = SEEK_SET;
- fl.l_start = 0;
- fl.l_len = 0;
-
- // Take the write lock.
- ASSERT_THAT(fcntl(fd.get(), F_SETLKW, &fl), SyscallSucceeds());
-
- // Attempt to take the read lock in a sub process. This will immediately block
- // so we will release our lock after some amount of time and then assert the
- // amount of time the other process was blocked for.
- pid_t child_pid = 0;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(SubprocessLock(
- file.path(), true /* write lock */, true /* Blocking Lock */,
- true /* Retry on EINTR */, fds_[1] /* Socket fd for timing information */,
- fl.l_start, fl.l_len, &child_pid));
-
- // We will wait kHoldLockForSec before we release our lock allowing the
- // subprocess to obtain it.
- constexpr absl::Duration kHoldLockFor = absl::Seconds(5);
- const int64_t kMinBlockTimeUsec = absl::ToInt64Microseconds(absl::Seconds(1));
-
- absl::SleepFor(kHoldLockFor);
-
- // Unlock our write lock.
- fl.l_type = F_UNLCK;
- ASSERT_THAT(fcntl(fd.get(), F_SETLKW, &fl), SyscallSucceeds());
-
- // Read the blocked time from the subprocess socket.
- int64_t subprocess_blocked_time_usec = GetSubprocessFcntlTimeInUsec();
-
- // We must have been waiting at least kMinBlockTime.
- EXPECT_GT(subprocess_blocked_time_usec, kMinBlockTimeUsec);
-
- // The FCNTL write lock must always succeed as it will simply block until it
- // can obtain the lock.
- int status = 0;
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "Exited with code: " << status;
-}
-
-// This test will veirfy that blocking works as expected when another process
-// holds a read lock when obtaining a write lock. This test will hold the lock
-// for some amount of time and then wait for the second process to send over the
-// socket_fd the amount of time it was blocked for before the lock succeeded.
-TEST_F(FcntlLockTest, SetReadLockThenBlockingWriteLock) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666));
-
- struct flock fl;
- fl.l_type = F_RDLCK;
- fl.l_whence = SEEK_SET;
- fl.l_start = 0;
- fl.l_len = 0;
-
- // Take the write lock.
- ASSERT_THAT(fcntl(fd.get(), F_SETLKW, &fl), SyscallSucceeds());
-
- // Attempt to take the read lock in a sub process. This will immediately block
- // so we will release our lock after some amount of time and then assert the
- // amount of time the other process was blocked for.
- pid_t child_pid = 0;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(SubprocessLock(
- file.path(), true /* write lock */, true /* Blocking Lock */,
- true /* Retry on EINTR */, fds_[1] /* Socket fd for timing information */,
- fl.l_start, fl.l_len, &child_pid));
-
- // We will wait kHoldLockForSec before we release our lock allowing the
- // subprocess to obtain it.
- constexpr absl::Duration kHoldLockFor = absl::Seconds(5);
-
- const int64_t kMinBlockTimeUsec = absl::ToInt64Microseconds(absl::Seconds(1));
-
- absl::SleepFor(kHoldLockFor);
-
- // Unlock our READ lock.
- fl.l_type = F_UNLCK;
- ASSERT_THAT(fcntl(fd.get(), F_SETLKW, &fl), SyscallSucceeds());
-
- // Read the blocked time from the subprocess socket.
- int64_t subprocess_blocked_time_usec = GetSubprocessFcntlTimeInUsec();
-
- // We must have been waiting at least kMinBlockTime.
- EXPECT_GT(subprocess_blocked_time_usec, kMinBlockTimeUsec);
-
- // The FCNTL write lock must always succeed as it will simply block until it
- // can obtain the lock.
- int status = 0;
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "Exited with code: " << status;
-}
-
-// This test will veirfy that blocking works as expected when another process
-// holds a write lock when obtaining a read lock. This test will hold the lock
-// for some amount of time and then wait for the second process to send over the
-// socket_fd the amount of time it was blocked for before the lock succeeded.
-TEST_F(FcntlLockTest, SetWriteLockThenBlockingReadLock) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666));
-
- struct flock fl;
- fl.l_type = F_WRLCK;
- fl.l_whence = SEEK_SET;
- fl.l_start = 0;
- fl.l_len = 0;
-
- // Take the write lock.
- ASSERT_THAT(fcntl(fd.get(), F_SETLKW, &fl), SyscallSucceeds());
-
- // Attempt to take the read lock in a sub process. This will immediately block
- // so we will release our lock after some amount of time and then assert the
- // amount of time the other process was blocked for.
- pid_t child_pid = 0;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(SubprocessLock(
- file.path(), false /* read lock */, true /* Blocking Lock */,
- true /* Retry on EINTR */, fds_[1] /* Socket fd for timing information */,
- fl.l_start, fl.l_len, &child_pid));
-
- // We will wait kHoldLockForSec before we release our lock allowing the
- // subprocess to obtain it.
- constexpr absl::Duration kHoldLockFor = absl::Seconds(5);
-
- const int64_t kMinBlockTimeUsec = absl::ToInt64Microseconds(absl::Seconds(1));
-
- absl::SleepFor(kHoldLockFor);
-
- // Unlock our write lock.
- fl.l_type = F_UNLCK;
- ASSERT_THAT(fcntl(fd.get(), F_SETLKW, &fl), SyscallSucceeds());
-
- // Read the blocked time from the subprocess socket.
- int64_t subprocess_blocked_time_usec = GetSubprocessFcntlTimeInUsec();
-
- // We must have been waiting at least kMinBlockTime.
- EXPECT_GT(subprocess_blocked_time_usec, kMinBlockTimeUsec);
-
- // The FCNTL read lock must always succeed as it will simply block until it
- // can obtain the lock.
- int status = 0;
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "Exited with code: " << status;
-}
-
-// This test will verify that when one process only holds a read lock that
-// another will not block while obtaining a read lock when F_SETLKW is used.
-TEST_F(FcntlLockTest, SetReadLockThenBlockingReadLock) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666));
-
- struct flock fl;
- fl.l_type = F_RDLCK;
- fl.l_whence = SEEK_SET;
- fl.l_start = 0;
- fl.l_len = 0;
-
- // Take the READ lock.
- ASSERT_THAT(fcntl(fd.get(), F_SETLKW, &fl), SyscallSucceeds());
-
- // Attempt to take the read lock in a sub process. Since multiple processes
- // can hold a read lock this should immediately return without blocking
- // even though we used F_SETLKW in the subprocess.
- pid_t child_pid = 0;
- auto sp = ASSERT_NO_ERRNO_AND_VALUE(SubprocessLock(
- file.path(), false /* read lock */, true /* Blocking Lock */,
- true /* Retry on EINTR */, -1 /* No fd, should not block */, fl.l_start,
- fl.l_len, &child_pid));
-
- // We never release the lock and the subprocess should still obtain it without
- // blocking for any period of time.
- int status = 0;
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "Exited with code: " << status;
-}
-
-TEST(FcntlTest, GetO_ASYNC) {
- FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
-
- int flag_fl = -1;
- ASSERT_THAT(flag_fl = fcntl(s.get(), F_GETFL), SyscallSucceeds());
- EXPECT_EQ(flag_fl & O_ASYNC, 0);
-
- int flag_fd = -1;
- ASSERT_THAT(flag_fd = fcntl(s.get(), F_GETFD), SyscallSucceeds());
- EXPECT_EQ(flag_fd & O_ASYNC, 0);
-}
-
-TEST(FcntlTest, SetFlO_ASYNC) {
- FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
-
- int before_fl = -1;
- ASSERT_THAT(before_fl = fcntl(s.get(), F_GETFL), SyscallSucceeds());
-
- int before_fd = -1;
- ASSERT_THAT(before_fd = fcntl(s.get(), F_GETFD), SyscallSucceeds());
-
- ASSERT_THAT(fcntl(s.get(), F_SETFL, before_fl | O_ASYNC), SyscallSucceeds());
-
- int after_fl = -1;
- ASSERT_THAT(after_fl = fcntl(s.get(), F_GETFL), SyscallSucceeds());
- EXPECT_EQ(after_fl, before_fl | O_ASYNC);
-
- int after_fd = -1;
- ASSERT_THAT(after_fd = fcntl(s.get(), F_GETFD), SyscallSucceeds());
- EXPECT_EQ(after_fd, before_fd);
-}
-
-TEST(FcntlTest, SetFdO_ASYNC) {
- FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
-
- int before_fl = -1;
- ASSERT_THAT(before_fl = fcntl(s.get(), F_GETFL), SyscallSucceeds());
-
- int before_fd = -1;
- ASSERT_THAT(before_fd = fcntl(s.get(), F_GETFD), SyscallSucceeds());
-
- ASSERT_THAT(fcntl(s.get(), F_SETFD, before_fd | O_ASYNC), SyscallSucceeds());
-
- int after_fl = -1;
- ASSERT_THAT(after_fl = fcntl(s.get(), F_GETFL), SyscallSucceeds());
- EXPECT_EQ(after_fl, before_fl);
-
- int after_fd = -1;
- ASSERT_THAT(after_fd = fcntl(s.get(), F_GETFD), SyscallSucceeds());
- EXPECT_EQ(after_fd, before_fd);
-}
-
-TEST(FcntlTest, DupAfterO_ASYNC) {
- FileDescriptor s1 = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
-
- int before = -1;
- ASSERT_THAT(before = fcntl(s1.get(), F_GETFL), SyscallSucceeds());
-
- ASSERT_THAT(fcntl(s1.get(), F_SETFL, before | O_ASYNC), SyscallSucceeds());
-
- FileDescriptor fd2 = ASSERT_NO_ERRNO_AND_VALUE(s1.Dup());
-
- int after = -1;
- ASSERT_THAT(after = fcntl(fd2.get(), F_GETFL), SyscallSucceeds());
- EXPECT_EQ(after & O_ASYNC, O_ASYNC);
-}
-
-TEST(FcntlTest, GetOwn) {
- FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
-
- ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN),
- SyscallSucceedsWithValue(0));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
-
-int main(int argc, char** argv) {
- gvisor::testing::TestInit(&argc, &argv);
-
- if (!FLAGS_child_setlock_on.empty()) {
- int socket_fd = FLAGS_socket_fd;
- int fd = open(FLAGS_child_setlock_on.c_str(), O_RDWR, 0666);
- if (fd == -1 && errno != 0) {
- int err = errno;
- std::cerr << "CHILD open " << FLAGS_child_setlock_on << " failed " << err
- << std::endl;
- exit(err);
- }
-
- struct flock fl;
- if (FLAGS_child_setlock_write) {
- fl.l_type = F_WRLCK;
- } else {
- fl.l_type = F_RDLCK;
- }
- fl.l_whence = SEEK_SET;
- fl.l_start = FLAGS_child_setlock_start;
- fl.l_len = FLAGS_child_setlock_len;
-
- // Test the fcntl, no need to log, the error is unambiguously
- // from fcntl at this point.
- int err = 0;
- int ret = 0;
-
- gvisor::testing::MonotonicTimer timer;
- timer.Start();
- do {
- ret = fcntl(fd, FLAGS_blocking ? F_SETLKW : F_SETLK, &fl);
- } while (FLAGS_retry_eintr && ret == -1 && errno == EINTR);
- auto usec = absl::ToInt64Microseconds(timer.Duration());
-
- if (ret == -1 && errno != 0) {
- err = errno;
- }
-
- // If there is a socket fd let's send back the time in microseconds it took
- // to execute this syscall.
- if (socket_fd != -1) {
- gvisor::testing::WriteFd(socket_fd, reinterpret_cast<void*>(&usec),
- sizeof(usec));
- close(socket_fd);
- }
-
- close(fd);
- exit(err);
- }
-
- return RUN_ALL_TESTS();
-}
diff --git a/test/syscalls/linux/file_base.h b/test/syscalls/linux/file_base.h
deleted file mode 100644
index 36efabcae..000000000
--- a/test/syscalls/linux/file_base.h
+++ /dev/null
@@ -1,206 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_FILE_BASE_H_
-#define GVISOR_TEST_SYSCALLS_FILE_BASE_H_
-
-#include <arpa/inet.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <netinet/in.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/uio.h>
-#include <unistd.h>
-#include <cstring>
-#include <string>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "absl/strings/string_view.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/posix_error.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-class FileTest : public ::testing::Test {
- public:
- void SetUp() override {
- test_pipe_[0] = -1;
- test_pipe_[1] = -1;
-
- test_file_name_ = NewTempAbsPath();
- test_file_fd_ = ASSERT_NO_ERRNO_AND_VALUE(
- Open(test_file_name_, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR));
-
- // FIXME(edahlgren): enable when mknod syscall is supported.
- // test_fifo_name_ = NewTempAbsPath();
- // ASSERT_THAT(mknod(test_fifo_name_.c_str()), S_IFIFO|0644, 0,
- // SyscallSucceeds());
- // ASSERT_THAT(test_fifo_[1] = open(test_fifo_name_.c_str(),
- // O_WRONLY),
- // SyscallSucceeds());
- // ASSERT_THAT(test_fifo_[0] = open(test_fifo_name_.c_str(),
- // O_RDONLY),
- // SyscallSucceeds());
-
- ASSERT_THAT(pipe(test_pipe_), SyscallSucceeds());
- ASSERT_THAT(fcntl(test_pipe_[0], F_SETFL, O_NONBLOCK), SyscallSucceeds());
- }
-
- // CloseFile will allow the test to manually close the file descriptor.
- void CloseFile() { test_file_fd_.reset(); }
-
- // UnlinkFile will allow the test to manually unlink the file.
- void UnlinkFile() {
- if (!test_file_name_.empty()) {
- EXPECT_THAT(unlink(test_file_name_.c_str()), SyscallSucceeds());
- test_file_name_.clear();
- }
- }
-
- // ClosePipes will allow the test to manually close the pipes.
- void ClosePipes() {
- if (test_pipe_[0] > 0) {
- EXPECT_THAT(close(test_pipe_[0]), SyscallSucceeds());
- }
-
- if (test_pipe_[1] > 0) {
- EXPECT_THAT(close(test_pipe_[1]), SyscallSucceeds());
- }
-
- test_pipe_[0] = -1;
- test_pipe_[1] = -1;
- }
-
- void TearDown() override {
- CloseFile();
- UnlinkFile();
- ClosePipes();
-
- // FIXME(edahlgren): enable when mknod syscall is supported.
- // close(test_fifo_[0]);
- // close(test_fifo_[1]);
- // unlink(test_fifo_name_.c_str());
- }
-
- std::string test_file_name_;
- std::string test_fifo_name_;
- FileDescriptor test_file_fd_;
-
- int test_fifo_[2];
- int test_pipe_[2];
-};
-
-class SocketTest : public ::testing::Test {
- public:
- void SetUp() override {
- test_unix_stream_socket_[0] = -1;
- test_unix_stream_socket_[1] = -1;
- test_unix_dgram_socket_[0] = -1;
- test_unix_dgram_socket_[1] = -1;
- test_unix_seqpacket_socket_[0] = -1;
- test_unix_seqpacket_socket_[1] = -1;
- test_tcp_socket_[0] = -1;
- test_tcp_socket_[1] = -1;
-
- ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, test_unix_stream_socket_),
- SyscallSucceeds());
- ASSERT_THAT(fcntl(test_unix_stream_socket_[0], F_SETFL, O_NONBLOCK),
- SyscallSucceeds());
- ASSERT_THAT(socketpair(AF_UNIX, SOCK_DGRAM, 0, test_unix_dgram_socket_),
- SyscallSucceeds());
- ASSERT_THAT(fcntl(test_unix_dgram_socket_[0], F_SETFL, O_NONBLOCK),
- SyscallSucceeds());
- ASSERT_THAT(
- socketpair(AF_UNIX, SOCK_SEQPACKET, 0, test_unix_seqpacket_socket_),
- SyscallSucceeds());
- ASSERT_THAT(fcntl(test_unix_seqpacket_socket_[0], F_SETFL, O_NONBLOCK),
- SyscallSucceeds());
- }
-
- void TearDown() override {
- close(test_unix_stream_socket_[0]);
- close(test_unix_stream_socket_[1]);
-
- close(test_unix_dgram_socket_[0]);
- close(test_unix_dgram_socket_[1]);
-
- close(test_unix_seqpacket_socket_[0]);
- close(test_unix_seqpacket_socket_[1]);
-
- close(test_tcp_socket_[0]);
- close(test_tcp_socket_[1]);
- }
-
- int test_unix_stream_socket_[2];
- int test_unix_dgram_socket_[2];
- int test_unix_seqpacket_socket_[2];
- int test_tcp_socket_[2];
-};
-
-// MatchesStringLength checks that a tuple argument of (struct iovec *, int)
-// corresponding to an iovec array and its length, contains data that matches
-// the string length strlen.
-MATCHER_P(MatchesStringLength, strlen, "") {
- struct iovec* iovs = arg.first;
- int niov = arg.second;
- int offset = 0;
- for (int i = 0; i < niov; i++) {
- offset += iovs[i].iov_len;
- }
- if (offset != static_cast<int>(strlen)) {
- *result_listener << offset;
- return false;
- }
- return true;
-}
-
-// MatchesStringValue checks that a tuple argument of (struct iovec *, int)
-// corresponding to an iovec array and its length, contains data that matches
-// the string value str.
-MATCHER_P(MatchesStringValue, str, "") {
- struct iovec* iovs = arg.first;
- int len = strlen(str);
- int niov = arg.second;
- int offset = 0;
- for (int i = 0; i < niov; i++) {
- struct iovec iov = iovs[i];
- if (len < offset) {
- *result_listener << "strlen " << len << " < offset " << offset;
- return false;
- }
- if (strncmp(static_cast<char*>(iov.iov_base), &str[offset], iov.iov_len)) {
- absl::string_view iovec_string(static_cast<char*>(iov.iov_base),
- iov.iov_len);
- *result_listener << iovec_string << " @offset " << offset;
- return false;
- }
- offset += iov.iov_len;
- }
- return true;
-}
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_FILE_BASE_H_
diff --git a/test/syscalls/linux/flock.cc b/test/syscalls/linux/flock.cc
deleted file mode 100644
index b4a91455d..000000000
--- a/test/syscalls/linux/flock.cc
+++ /dev/null
@@ -1,588 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <sys/file.h>
-#include <string>
-
-#include "gtest/gtest.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/syscalls/linux/file_base.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-#include "test/util/timer_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-class FlockTest : public FileTest {};
-
-TEST_F(FlockTest, BadFD) {
- // EBADF: fd is not an open file descriptor.
- ASSERT_THAT(flock(-1, 0), SyscallFailsWithErrno(EBADF));
-}
-
-TEST_F(FlockTest, InvalidOpCombinations) {
- // The operation cannot be both exclusive and shared.
- EXPECT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_SH | LOCK_NB),
- SyscallFailsWithErrno(EINVAL));
-
- // Locking and Unlocking doesn't make sense.
- EXPECT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_UN | LOCK_NB),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(flock(test_file_fd_.get(), LOCK_SH | LOCK_UN | LOCK_NB),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_F(FlockTest, NoOperationSpecified) {
- // Not specifying an operation is invalid.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(FlockTestNoFixture, FlockSupportsPipes) {
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
-
- EXPECT_THAT(flock(fds[0], LOCK_EX | LOCK_NB), SyscallSucceeds());
- EXPECT_THAT(close(fds[0]), SyscallSucceeds());
- EXPECT_THAT(close(fds[1]), SyscallSucceeds());
-}
-
-TEST_F(FlockTest, TestSimpleExLock) {
- // Test that we can obtain an exclusive lock (no other holders)
- // and that we can unlock it.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB),
- SyscallSucceedsWithValue(0));
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(FlockTest, TestSimpleShLock) {
- // Test that we can obtain a shared lock (no other holders)
- // and that we can unlock it.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH | LOCK_NB),
- SyscallSucceedsWithValue(0));
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(FlockTest, TestLockableAnyMode) {
- // flock(2): A shared or exclusive lock can be placed on a file
- // regardless of the mode in which the file was opened.
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(
- Open(test_file_name_, O_RDONLY)); // open read only to test
-
- // Mode shouldn't prevent us from taking an exclusive lock.
- ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallSucceedsWithValue(0));
-
- // Unlock
- ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(FlockTest, TestUnlockWithNoHolders) {
- // Test that unlocking when no one holds a lock succeeeds.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(FlockTest, TestRepeatedExLockingBySameHolder) {
- // Test that repeated locking by the same holder for the
- // same type of lock works correctly.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB | LOCK_EX),
- SyscallSucceedsWithValue(0));
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB | LOCK_EX),
- SyscallSucceedsWithValue(0));
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(FlockTest, TestRepeatedExLockingSingleUnlock) {
- // Test that repeated locking by the same holder for the
- // same type of lock works correctly and that a single unlock is required.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB | LOCK_EX),
- SyscallSucceedsWithValue(0));
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB | LOCK_EX),
- SyscallSucceedsWithValue(0));
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDONLY));
-
- // Should be unlocked at this point
- ASSERT_THAT(flock(fd.get(), LOCK_NB | LOCK_EX), SyscallSucceedsWithValue(0));
-
- ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(FlockTest, TestRepeatedShLockingBySameHolder) {
- // Test that repeated locking by the same holder for the
- // same type of lock works correctly.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB | LOCK_SH),
- SyscallSucceedsWithValue(0));
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB | LOCK_SH),
- SyscallSucceedsWithValue(0));
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(FlockTest, TestSingleHolderUpgrade) {
- // Test that a shared lock is upgradable when no one else holds a lock.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB | LOCK_SH),
- SyscallSucceedsWithValue(0));
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB | LOCK_EX),
- SyscallSucceedsWithValue(0));
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(FlockTest, TestSingleHolderDowngrade) {
- // Test single holder lock downgrade case.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB),
- SyscallSucceedsWithValue(0));
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH | LOCK_NB),
- SyscallSucceedsWithValue(0));
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(FlockTest, TestMultipleShared) {
- // This is a simple test to verify that multiple independent shared
- // locks will be granted.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH | LOCK_NB),
- SyscallSucceedsWithValue(0));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR));
-
- // A shared lock should be granted as there only exists other shared locks.
- ASSERT_THAT(flock(fd.get(), LOCK_SH | LOCK_NB), SyscallSucceedsWithValue(0));
-
- // Unlock both.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0));
- ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceedsWithValue(0));
-}
-
-/*
- * flock(2): If a process uses open(2) (or similar) to obtain more than one
- * descriptor for the same file, these descriptors are treated
- * independently by flock(). An attempt to lock the file using one of
- * these file descriptors may be denied by a lock that the calling process
- * has already placed via another descriptor.
- */
-TEST_F(FlockTest, TestMultipleHolderSharedExclusive) {
- // This test will verify that an exclusive lock will not be granted
- // while a shared is held.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH | LOCK_NB),
- SyscallSucceedsWithValue(0));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR));
-
- // Verify We're unable to get an exlcusive lock via the second FD.
- // because someone is holding a shared lock.
- ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB),
- SyscallFailsWithErrno(EWOULDBLOCK));
-
- // Unlock
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(FlockTest, TestSharedLockFailExclusiveHolder) {
- // This test will verify that a shared lock is denied while
- // someone holds an exclusive lock.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB),
- SyscallSucceedsWithValue(0));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR));
-
- // Verify we're unable to get an shared lock via the second FD.
- // because someone is holding an exclusive lock.
- ASSERT_THAT(flock(fd.get(), LOCK_SH | LOCK_NB),
- SyscallFailsWithErrno(EWOULDBLOCK));
-
- // Unlock
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(FlockTest, TestExclusiveLockFailExclusiveHolder) {
- // This test will verify that an exclusive lock is denied while
- // someone already holds an exclsuive lock.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB),
- SyscallSucceedsWithValue(0));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR));
-
- // Verify we're unable to get an exclusive lock via the second FD
- // because someone is already holding an exclusive lock.
- ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB),
- SyscallFailsWithErrno(EWOULDBLOCK));
-
- // Unlock
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(FlockTest, TestMultipleHolderSharedExclusiveUpgrade) {
- // This test will verify that we cannot obtain an exclusive lock while
- // a shared lock is held by another descriptor, then verify that an upgrade
- // is possible on a shared lock once all other shared locks have closed.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH | LOCK_NB),
- SyscallSucceedsWithValue(0));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR));
-
- // Verify we're unable to get an exclusive lock via the second FD because
- // a shared lock is held.
- ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB),
- SyscallFailsWithErrno(EWOULDBLOCK));
-
- // Verify that we can get a shared lock via the second descriptor instead
- ASSERT_THAT(flock(fd.get(), LOCK_SH | LOCK_NB), SyscallSucceedsWithValue(0));
-
- // Unlock the first and there will only be one shared lock remaining.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0));
-
- // Upgrade 2nd fd.
- ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallSucceedsWithValue(0));
-
- // Finally unlock the second
- ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(FlockTest, TestMultipleHolderSharedExclusiveDowngrade) {
- // This test will verify that a shared lock is not obtainable while an
- // exclusive lock is held but that once the first is downgraded that
- // the second independent file descriptor can also get a shared lock.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB),
- SyscallSucceedsWithValue(0));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR));
-
- // Verify We're unable to get a shared lock via the second FD because
- // an exclusive lock is held.
- ASSERT_THAT(flock(fd.get(), LOCK_SH | LOCK_NB),
- SyscallFailsWithErrno(EWOULDBLOCK));
-
- // Verify that we can downgrade the first.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH | LOCK_NB),
- SyscallSucceedsWithValue(0));
-
- // Now verify that we can obtain a shared lock since the first was downgraded.
- ASSERT_THAT(flock(fd.get(), LOCK_SH | LOCK_NB), SyscallSucceedsWithValue(0));
-
- // Finally unlock both.
- ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceedsWithValue(0));
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0));
-}
-
-/*
- * flock(2): Locks created by flock() are associated with an open file table
- * entry. This means that duplicate file descriptors (created by, for example,
- * fork(2) or dup(2)) refer to the same lock, and this lock may be modified or
- * released using any of these descriptors. Furthermore, the lock is released
- * either by an explicit LOCK_UN operation on any of these duplicate descriptors
- * or when all such descriptors have been closed.
- */
-TEST_F(FlockTest, TestDupFdUpgrade) {
- // This test will verify that a shared lock is upgradeable via a dupped
- // file descriptor, if the FD wasn't dupped this would fail.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH | LOCK_NB),
- SyscallSucceedsWithValue(0));
-
- const FileDescriptor dup_fd = ASSERT_NO_ERRNO_AND_VALUE(test_file_fd_.Dup());
-
- // Now we should be able to upgrade via the dupped fd.
- ASSERT_THAT(flock(dup_fd.get(), LOCK_EX | LOCK_NB),
- SyscallSucceedsWithValue(0));
-
- // Validate unlock via dupped fd.
- ASSERT_THAT(flock(dup_fd.get(), LOCK_UN), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(FlockTest, TestDupFdDowngrade) {
- // This test will verify that a exclusive lock is downgradable via a dupped
- // file descriptor, if the FD wasn't dupped this would fail.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB),
- SyscallSucceedsWithValue(0));
-
- const FileDescriptor dup_fd = ASSERT_NO_ERRNO_AND_VALUE(test_file_fd_.Dup());
-
- // Now we should be able to downgrade via the dupped fd.
- ASSERT_THAT(flock(dup_fd.get(), LOCK_SH | LOCK_NB),
- SyscallSucceedsWithValue(0));
-
- // Validate unlock via dupped fd
- ASSERT_THAT(flock(dup_fd.get(), LOCK_UN), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(FlockTest, TestDupFdCloseRelease) {
- // flock(2): Furthermore, the lock is released either by an explicit LOCK_UN
- // operation on any of these duplicate descriptors, or when all such
- // descriptors have been closed.
- //
- // This test will verify that a dupped fd closing will not release the
- // underlying lock until all such dupped fds have closed.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB),
- SyscallSucceedsWithValue(0));
-
- FileDescriptor dup_fd = ASSERT_NO_ERRNO_AND_VALUE(test_file_fd_.Dup());
-
- // At this point we have ONE exclusive locked referenced by two different fds.
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR));
-
- // Validate that we cannot get a lock on a new unrelated FD.
- ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB),
- SyscallFailsWithErrno(EWOULDBLOCK));
-
- // Closing the dupped fd shouldn't affect the lock until all are closed.
- dup_fd.reset(); // Closed the duped fd.
-
- // Validate that we still cannot get a lock on a new unrelated FD.
- ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB),
- SyscallFailsWithErrno(EWOULDBLOCK));
-
- // Closing the first fd
- CloseFile(); // Will validate the syscall succeeds.
-
- // Now we should actually be able to get a lock since all fds related to
- // the first lock are closed.
- ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallSucceedsWithValue(0));
-
- // Unlock.
- ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(FlockTest, TestDupFdUnlockRelease) {
- /* flock(2): Furthermore, the lock is released either by an explicit LOCK_UN
- * operation on any of these duplicate descriptors, or when all such
- * descriptors have been closed.
- */
- // This test will verify that an explict unlock on a dupped FD will release
- // the underlying lock unlike the previous case where close on a dup was
- // not enough to release the lock.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB),
- SyscallSucceedsWithValue(0));
-
- const FileDescriptor dup_fd = ASSERT_NO_ERRNO_AND_VALUE(test_file_fd_.Dup());
-
- // At this point we have ONE exclusive locked referenced by two different fds.
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR));
-
- // Validate that we cannot get a lock on a new unrelated FD.
- ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB),
- SyscallFailsWithErrno(EWOULDBLOCK));
-
- // Explicitly unlock via the dupped descriptor.
- ASSERT_THAT(flock(dup_fd.get(), LOCK_UN), SyscallSucceedsWithValue(0));
-
- // Validate that we can now get the lock since we explicitly unlocked.
- ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallSucceedsWithValue(0));
-
- // Unlock
- ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(FlockTest, TestDupFdFollowedByLock) {
- // This test will verify that taking a lock on a file descriptor that has
- // already been dupped means that the lock is shared between both. This is
- // slightly different than than duping on an already locked FD.
- FileDescriptor dup_fd = ASSERT_NO_ERRNO_AND_VALUE(test_file_fd_.Dup());
-
- // Take a lock.
- ASSERT_THAT(flock(dup_fd.get(), LOCK_EX | LOCK_NB), SyscallSucceeds());
-
- // Now dup_fd and test_file_ should both reference the same lock.
- // We shouldn't be able to obtain a lock until both are closed.
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR));
-
- // Closing the first fd
- dup_fd.reset(); // Close the duped fd.
-
- // Validate that we cannot get a lock yet because the dupped descriptor.
- ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB),
- SyscallFailsWithErrno(EWOULDBLOCK));
-
- // Closing the second fd.
- CloseFile(); // CloseFile() will validate the syscall succeeds.
-
- // Now we should be able to get the lock.
- ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallSucceeds());
-
- // Unlock.
- ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceedsWithValue(0));
-}
-
-// NOTE: These blocking tests are not perfect. Unfortunately it's very hard to
-// determine if a thread was actually blocked in the kernel so we're forced
-// to use timing.
-TEST_F(FlockTest, BlockingLockNoBlockingForSharedLocks_NoRandomSave) {
- // This test will verify that although LOCK_NB isn't specified
- // two different fds can obtain shared locks without blocking.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH), SyscallSucceeds());
-
- // kHoldLockTime is the amount of time we will hold the lock before releasing.
- constexpr absl::Duration kHoldLockTime = absl::Seconds(30);
-
- const DisableSave ds; // Timing-related.
-
- // We do this in another thread so we can determine if it was actually
- // blocked by timing the amount of time it took for the syscall to complete.
- ScopedThread t([&] {
- MonotonicTimer timer;
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR));
-
- // Only a single shared lock is held, the lock will be granted immediately.
- // This should be granted without any blocking. Don't save here to avoid
- // wild discrepencies on timing.
- timer.Start();
- ASSERT_THAT(flock(fd.get(), LOCK_SH), SyscallSucceeds());
-
- // We held the lock for 30 seconds but this thread should not have
- // blocked at all so we expect a very small duration on syscall completion.
- ASSERT_LT(timer.Duration(),
- absl::Seconds(1)); // 1000ms is much less than 30s.
-
- // We can release our second shared lock
- ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceeds());
- });
-
- // Sleep before unlocking.
- absl::SleepFor(kHoldLockTime);
-
- // Release the first shared lock. Don't save in this situation to avoid
- // discrepencies in timing.
- EXPECT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceeds());
-}
-
-TEST_F(FlockTest, BlockingLockFirstSharedSecondExclusive_NoRandomSave) {
- // This test will verify that if someone holds a shared lock any attempt to
- // obtain an exclusive lock will result in blocking.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH), SyscallSucceeds());
-
- // kHoldLockTime is the amount of time we will hold the lock before releasing.
- constexpr absl::Duration kHoldLockTime = absl::Seconds(2);
-
- const DisableSave ds; // Timing-related.
-
- // We do this in another thread so we can determine if it was actually
- // blocked by timing the amount of time it took for the syscall to complete.
- ScopedThread t([&] {
- MonotonicTimer timer;
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR));
-
- // This exclusive lock should block because someone is already holding a
- // shared lock. We don't save here to avoid wild discrepencies on timing.
- timer.Start();
- ASSERT_THAT(RetryEINTR(flock)(fd.get(), LOCK_EX), SyscallSucceeds());
-
- // We should be blocked, we will expect to be blocked for more than 1.0s.
- ASSERT_GT(timer.Duration(), absl::Seconds(1));
-
- // We can release our exclusive lock.
- ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceeds());
- });
-
- // Sleep before unlocking.
- absl::SleepFor(kHoldLockTime);
-
- // Release the shared lock allowing the thread to proceed.
- // We don't save here to avoid wild discrepencies in timing.
- EXPECT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceeds());
-}
-
-TEST_F(FlockTest, BlockingLockFirstExclusiveSecondShared_NoRandomSave) {
- // This test will verify that if someone holds an exclusive lock any attempt
- // to obtain a shared lock will result in blocking.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX), SyscallSucceeds());
-
- // kHoldLockTime is the amount of time we will hold the lock before releasing.
- constexpr absl::Duration kHoldLockTime = absl::Seconds(2);
-
- const DisableSave ds; // Timing-related.
-
- // We do this in another thread so we can determine if it was actually
- // blocked by timing the amount of time it took for the syscall to complete.
- ScopedThread t([&] {
- MonotonicTimer timer;
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR));
-
- // This shared lock should block because someone is already holding an
- // exclusive lock. We don't save here to avoid wild discrepencies on timing.
- timer.Start();
- ASSERT_THAT(RetryEINTR(flock)(fd.get(), LOCK_SH), SyscallSucceeds());
-
- // We should be blocked, we will expect to be blocked for more than 1.0s.
- ASSERT_GT(timer.Duration(), absl::Seconds(1));
-
- // We can release our shared lock.
- ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceeds());
- });
-
- // Sleep before unlocking.
- absl::SleepFor(kHoldLockTime);
-
- // Release the exclusive lock allowing the blocked thread to proceed.
- // We don't save here to avoid wild discrepencies in timing.
- EXPECT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceeds());
-}
-
-TEST_F(FlockTest, BlockingLockFirstExclusiveSecondExclusive_NoRandomSave) {
- // This test will verify that if someone holds an exclusive lock any attempt
- // to obtain another exclusive lock will result in blocking.
- ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX), SyscallSucceeds());
-
- // kHoldLockTime is the amount of time we will hold the lock before releasing.
- constexpr absl::Duration kHoldLockTime = absl::Seconds(2);
-
- const DisableSave ds; // Timing-related.
-
- // We do this in another thread so we can determine if it was actually
- // blocked by timing the amount of time it took for the syscall to complete.
- ScopedThread t([&] {
- MonotonicTimer timer;
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR));
-
- // This exclusive lock should block because someone is already holding an
- // exclusive lock.
- timer.Start();
- ASSERT_THAT(RetryEINTR(flock)(fd.get(), LOCK_EX), SyscallSucceeds());
-
- // We should be blocked, we will expect to be blocked for more than 1.0s.
- ASSERT_GT(timer.Duration(), absl::Seconds(1));
-
- // We can release our exclusive lock.
- ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceeds());
- });
-
- // Sleep before unlocking.
- absl::SleepFor(kHoldLockTime);
-
- // Release the exclusive lock allowing the blocked thread to proceed.
- // We don't save to avoid wild discrepencies in timing.
- EXPECT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceeds());
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/fork.cc b/test/syscalls/linux/fork.cc
deleted file mode 100644
index dd6e1a422..000000000
--- a/test/syscalls/linux/fork.cc
+++ /dev/null
@@ -1,444 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <fcntl.h>
-#include <sched.h>
-#include <stdlib.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <atomic>
-#include <cstdlib>
-
-#include "gtest/gtest.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/capability_util.h"
-#include "test/util/logging.h"
-#include "test/util/memory_util.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-using ::testing::Ge;
-
-class ForkTest : public ::testing::Test {
- protected:
- // SetUp creates a populated, open file.
- void SetUp() override {
- // Make a shared mapping.
- shared_ = reinterpret_cast<char*>(mmap(0, kPageSize, PROT_READ | PROT_WRITE,
- MAP_SHARED | MAP_ANONYMOUS, -1, 0));
- ASSERT_NE(reinterpret_cast<void*>(shared_), MAP_FAILED);
-
- // Make a private mapping.
- private_ =
- reinterpret_cast<char*>(mmap(0, kPageSize, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
- ASSERT_NE(reinterpret_cast<void*>(private_), MAP_FAILED);
-
- // Make a pipe.
- ASSERT_THAT(pipe(pipes_), SyscallSucceeds());
- }
-
- // TearDown frees associated resources.
- void TearDown() override {
- EXPECT_THAT(munmap(shared_, kPageSize), SyscallSucceeds());
- EXPECT_THAT(munmap(private_, kPageSize), SyscallSucceeds());
- EXPECT_THAT(close(pipes_[0]), SyscallSucceeds());
- EXPECT_THAT(close(pipes_[1]), SyscallSucceeds());
- }
-
- // Fork executes a clone system call.
- pid_t Fork() {
- pid_t pid = fork();
- MaybeSave();
- TEST_PCHECK_MSG(pid >= 0, "fork failed");
- return pid;
- }
-
- // Wait waits for the given pid and returns the exit status. If the child was
- // killed by a signal or an error occurs, then 256+signal is returned.
- int Wait(pid_t pid) {
- int status;
- while (true) {
- int rval = wait4(pid, &status, 0, NULL);
- if (rval < 0) {
- return rval;
- }
- if (rval != pid) {
- continue;
- }
- if (WIFEXITED(status)) {
- return WEXITSTATUS(status);
- }
- if (WIFSIGNALED(status)) {
- return 256 + WTERMSIG(status);
- }
- }
- }
-
- // Exit exits the proccess.
- void Exit(int code) {
- _exit(code);
-
- // Should never reach here. Since the exit above failed, we really don't
- // have much in the way of options to indicate failure. So we just try to
- // log an assertion failure to the logs. The parent process will likely
- // fail anyways if exit is not working.
- TEST_CHECK_MSG(false, "_exit returned");
- }
-
- // ReadByte reads a byte from the shared pipe.
- char ReadByte() {
- char val = -1;
- TEST_PCHECK(ReadFd(pipes_[0], &val, 1) == 1);
- MaybeSave();
- return val;
- }
-
- // WriteByte writes a byte from the shared pipe.
- void WriteByte(char val) {
- TEST_PCHECK(WriteFd(pipes_[1], &val, 1) == 1);
- MaybeSave();
- }
-
- // Shared pipe.
- int pipes_[2];
-
- // Shared mapping (one page).
- char* shared_;
-
- // Private mapping (one page).
- char* private_;
-};
-
-TEST_F(ForkTest, Simple) {
- pid_t child = Fork();
- if (child == 0) {
- Exit(0);
- }
- EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(ForkTest, ExitCode) {
- pid_t child = Fork();
- if (child == 0) {
- Exit(123);
- }
- EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(123));
- child = Fork();
- if (child == 0) {
- Exit(1);
- }
- EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(1));
-}
-
-TEST_F(ForkTest, Multi) {
- pid_t child1 = Fork();
- if (child1 == 0) {
- Exit(0);
- }
- pid_t child2 = Fork();
- if (child2 == 0) {
- Exit(1);
- }
- EXPECT_THAT(Wait(child1), SyscallSucceedsWithValue(0));
- EXPECT_THAT(Wait(child2), SyscallSucceedsWithValue(1));
-}
-
-TEST_F(ForkTest, Pipe) {
- pid_t child = Fork();
- if (child == 0) {
- WriteByte(1);
- Exit(0);
- }
- EXPECT_EQ(ReadByte(), 1);
- EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(ForkTest, SharedMapping) {
- pid_t child = Fork();
- if (child == 0) {
- // Wait for the parent.
- ReadByte();
- if (shared_[0] == 1) {
- Exit(0);
- }
- // Failed.
- Exit(1);
- }
- // Change the mapping.
- ASSERT_EQ(shared_[0], 0);
- shared_[0] = 1;
- // Unblock the child.
- WriteByte(0);
- // Did it work?
- EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(ForkTest, PrivateMapping) {
- pid_t child = Fork();
- if (child == 0) {
- // Wait for the parent.
- ReadByte();
- if (private_[0] == 0) {
- Exit(0);
- }
- // Failed.
- Exit(1);
- }
- // Change the mapping.
- ASSERT_EQ(private_[0], 0);
- private_[0] = 1;
- // Unblock the child.
- WriteByte(0);
- // Did it work?
- EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0));
-}
-
-// Test that cpuid works after a fork.
-TEST_F(ForkTest, Cpuid) {
- pid_t child = Fork();
-
- // We should be able to determine the CPU vendor.
- ASSERT_NE(GetCPUVendor(), CPUVendor::kUnknownVendor);
-
- if (child == 0) {
- Exit(0);
- }
- EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(ForkTest, Mmap) {
- pid_t child = Fork();
-
- if (child == 0) {
- void* addr =
- mmap(0, kPageSize, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- MaybeSave();
- Exit(addr == MAP_FAILED);
- }
-
- EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0));
-}
-
-static volatile int alarmed = 0;
-
-void AlarmHandler(int sig, siginfo_t* info, void* context) { alarmed = 1; }
-
-TEST_F(ForkTest, Alarm) {
- // Setup an alarm handler.
- struct sigaction sa;
- sa.sa_sigaction = AlarmHandler;
- sigfillset(&sa.sa_mask);
- sa.sa_flags = SA_SIGINFO;
- EXPECT_THAT(sigaction(SIGALRM, &sa, nullptr), SyscallSucceeds());
-
- pid_t child = Fork();
-
- if (child == 0) {
- alarm(1);
- sleep(3);
- if (!alarmed) {
- Exit(1);
- }
- Exit(0);
- }
-
- EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0));
- EXPECT_EQ(0, alarmed);
-}
-
-// Child cannot affect parent private memory.
-TEST_F(ForkTest, PrivateMemory) {
- std::atomic<uint32_t> local(0);
-
- pid_t child1 = Fork();
- if (child1 == 0) {
- local++;
-
- pid_t child2 = Fork();
- if (child2 == 0) {
- local++;
-
- TEST_CHECK(local.load() == 2);
-
- Exit(0);
- }
-
- TEST_PCHECK(Wait(child2) == 0);
- TEST_CHECK(local.load() == 1);
- Exit(0);
- }
-
- EXPECT_THAT(Wait(child1), SyscallSucceedsWithValue(0));
- EXPECT_EQ(0, local.load());
-}
-
-// Kernel-accessed buffers should remain coherent across COW.
-TEST_F(ForkTest, COWSegment) {
- constexpr int kBufSize = 1024;
- char* read_buf = private_;
- char* touch = private_ + kPageSize / 2;
-
- std::string contents(kBufSize, 'a');
-
- ScopedThread t([&] {
- // Wait to be sure the parent is blocked in read.
- absl::SleepFor(absl::Seconds(3));
-
- // Fork to mark private pages for COW.
- //
- // Use fork directly rather than the Fork wrapper to skip the multi-threaded
- // check, and limit the child to async-signal-safe functions:
- //
- // "After a fork() in a multithreaded program, the child can safely call
- // only async-signal-safe functions (see signal(7)) until such time as it
- // calls execve(2)."
- //
- // Skip ASSERT in the child, as it isn't async-signal-safe.
- pid_t child = fork();
- if (child == 0) {
- // Wait to be sure parent touched memory.
- sleep(3);
- Exit(0);
- }
-
- // Check success only in the parent.
- ASSERT_THAT(child, SyscallSucceedsWithValue(Ge(0)));
-
- // Trigger COW on private page.
- *touch = 42;
-
- // Write to pipe. Parent should still be able to read this.
- EXPECT_THAT(WriteFd(pipes_[1], contents.c_str(), kBufSize),
- SyscallSucceedsWithValue(kBufSize));
-
- EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0));
- });
-
- EXPECT_THAT(ReadFd(pipes_[0], read_buf, kBufSize),
- SyscallSucceedsWithValue(kBufSize));
- EXPECT_STREQ(contents.c_str(), read_buf);
-}
-
-TEST_F(ForkTest, SigAltStack) {
- std::vector<char> stack_mem(SIGSTKSZ);
- stack_t stack = {};
- stack.ss_size = SIGSTKSZ;
- stack.ss_sp = stack_mem.data();
- ASSERT_THAT(sigaltstack(&stack, nullptr), SyscallSucceeds());
-
- pid_t child = Fork();
-
- if (child == 0) {
- stack_t oss = {};
- TEST_PCHECK(sigaltstack(nullptr, &oss) == 0);
- MaybeSave();
-
- TEST_CHECK((oss.ss_flags & SS_DISABLE) == 0);
- TEST_CHECK(oss.ss_size == SIGSTKSZ);
- TEST_CHECK(oss.ss_sp == stack.ss_sp);
-
- Exit(0);
- }
- EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(ForkTest, Affinity) {
- // Make a non-default cpumask.
- cpu_set_t parent_mask;
- EXPECT_THAT(sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &parent_mask),
- SyscallSucceeds());
- // Knock out the lowest bit.
- for (unsigned int n = 0; n < CPU_SETSIZE; n++) {
- if (CPU_ISSET(n, &parent_mask)) {
- CPU_CLR(n, &parent_mask);
- break;
- }
- }
- EXPECT_THAT(sched_setaffinity(/*pid=*/0, sizeof(cpu_set_t), &parent_mask),
- SyscallSucceeds());
-
- pid_t child = Fork();
- if (child == 0) {
- cpu_set_t child_mask;
-
- int ret = sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &child_mask);
- MaybeSave();
- if (ret < 0) {
- Exit(-ret);
- }
-
- TEST_CHECK(CPU_EQUAL(&child_mask, &parent_mask));
-
- Exit(0);
- }
-
- EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0));
-}
-
-TEST(CloneTest, NewUserNamespacePermitsAllOtherNamespaces) {
- // "If CLONE_NEWUSER is specified along with other CLONE_NEW* flags in a
- // single clone(2) or unshare(2) call, the user namespace is guaranteed to be
- // created first, giving the child (clone(2)) or caller (unshare(2))
- // privileges over the remaining namespaces created by the call. Thus, it is
- // possible for an unprivileged caller to specify this combination of flags."
- // - user_namespaces(7)
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace()));
- Mapping child_stack = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
- int child_pid;
- // We only test with CLONE_NEWIPC, CLONE_NEWNET, and CLONE_NEWUTS since these
- // namespaces were implemented in Linux before user namespaces.
- ASSERT_THAT(
- child_pid = clone(
- +[](void*) { return 0; },
- reinterpret_cast<void*>(child_stack.addr() + kPageSize),
- CLONE_NEWUSER | CLONE_NEWIPC | CLONE_NEWNET | CLONE_NEWUTS | SIGCHLD,
- /* arg = */ nullptr),
- SyscallSucceeds());
-
- int status;
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "status = " << status;
-}
-
-#ifdef __x86_64__
-// Clone with CLONE_SETTLS and a non-canonical TLS address is rejected.
-TEST(CloneTest, NonCanonicalTLS) {
- constexpr uintptr_t kNonCanonical = 1ull << 48;
-
- // We need a valid address for the stack pointer. We'll never actually execute
- // on this.
- char stack;
-
- EXPECT_THAT(syscall(__NR_clone, SIGCHLD | CLONE_SETTLS, &stack, nullptr,
- nullptr, kNonCanonical),
- SyscallFailsWithErrno(EPERM));
-}
-#endif
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/fpsig_fork.cc b/test/syscalls/linux/fpsig_fork.cc
deleted file mode 100644
index e7e9f06a1..000000000
--- a/test/syscalls/linux/fpsig_fork.cc
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// This test verifies that fork(2) in a signal handler will correctly
-// restore floating point state after the signal handler returns in both
-// the child and parent.
-#include <sys/time.h>
-
-#include "gtest/gtest.h"
-#include "test/util/logging.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-#define GET_XMM(__var, __xmm) \
- asm volatile("movq %%" #__xmm ", %0" : "=r"(__var))
-#define SET_XMM(__var, __xmm) asm volatile("movq %0, %%" #__xmm : : "r"(__var))
-
-int parent, child;
-
-void sigusr1(int s, siginfo_t* siginfo, void* _uc) {
- // Fork and clobber %xmm0. The fpstate should be restored by sigreturn(2)
- // in both parent and child.
- child = fork();
- TEST_CHECK_MSG(child >= 0, "fork failed");
-
- uint64_t val = SIGUSR1;
- SET_XMM(val, xmm0);
-}
-
-TEST(FPSigTest, Fork) {
- parent = getpid();
- pid_t parent_tid = gettid();
-
- struct sigaction sa = {};
- sigemptyset(&sa.sa_mask);
- sa.sa_flags = SA_SIGINFO;
- sa.sa_sigaction = sigusr1;
- ASSERT_THAT(sigaction(SIGUSR1, &sa, nullptr), SyscallSucceeds());
-
- // The amd64 ABI specifies that the XMM register set is caller-saved. This
- // implies that if there is any function call between SET_XMM and GET_XMM the
- // compiler might save/restore xmm0 implicitly. This defeats the entire
- // purpose of the test which is to verify that fpstate is restored by
- // sigreturn(2).
- //
- // This is the reason why 'tgkill(getpid(), gettid(), SIGUSR1)' is implemented
- // in inline assembly below.
- //
- // If the OS is broken and registers are clobbered by the child, using tgkill
- // to signal the current thread increases the likelihood that this thread will
- // be the one clobbered.
-
- uint64_t expected = 0xdeadbeeffacefeed;
- SET_XMM(expected, xmm0);
-
- asm volatile(
- "movl %[killnr], %%eax;"
- "movl %[parent], %%edi;"
- "movl %[tid], %%esi;"
- "movl %[sig], %%edx;"
- "syscall;"
- :
- : [killnr] "i"(__NR_tgkill), [parent] "rm"(parent),
- [tid] "rm"(parent_tid), [sig] "i"(SIGUSR1)
- : "rax", "rdi", "rsi", "rdx",
- // Clobbered by syscall.
- "rcx", "r11");
-
- uint64_t got;
- GET_XMM(got, xmm0);
-
- if (getpid() == parent) { // Parent.
- int status;
- ASSERT_THAT(waitpid(child, &status, 0), SyscallSucceedsWithValue(child));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
- }
-
- // TEST_CHECK_MSG since this may run in the child.
- TEST_CHECK_MSG(expected == got, "Bad xmm0 value");
-
- if (getpid() != parent) { // Child.
- _exit(0);
- }
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/fpsig_nested.cc b/test/syscalls/linux/fpsig_nested.cc
deleted file mode 100644
index 395463aed..000000000
--- a/test/syscalls/linux/fpsig_nested.cc
+++ /dev/null
@@ -1,134 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// This program verifies that application floating point state is restored
-// correctly after a signal handler returns. It also verifies that this works
-// with nested signals.
-#include <sys/time.h>
-
-#include "gtest/gtest.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-#define GET_XMM(__var, __xmm) \
- asm volatile("movq %%" #__xmm ", %0" : "=r"(__var))
-#define SET_XMM(__var, __xmm) asm volatile("movq %0, %%" #__xmm : : "r"(__var))
-
-int pid;
-int tid;
-
-volatile uint64_t entryxmm[2] = {~0UL, ~0UL};
-volatile uint64_t exitxmm[2];
-
-void sigusr2(int s, siginfo_t* siginfo, void* _uc) {
- uint64_t val = SIGUSR2;
-
- // Record the value of %xmm0 on entry and then clobber it.
- GET_XMM(entryxmm[1], xmm0);
- SET_XMM(val, xmm0);
- GET_XMM(exitxmm[1], xmm0);
-}
-
-void sigusr1(int s, siginfo_t* siginfo, void* _uc) {
- uint64_t val = SIGUSR1;
-
- // Record the value of %xmm0 on entry and then clobber it.
- GET_XMM(entryxmm[0], xmm0);
- SET_XMM(val, xmm0);
-
- // Send a SIGUSR2 to ourself. The signal mask is configured such that
- // the SIGUSR2 handler will run before this handler returns.
- asm volatile(
- "movl %[killnr], %%eax;"
- "movl %[pid], %%edi;"
- "movl %[tid], %%esi;"
- "movl %[sig], %%edx;"
- "syscall;"
- :
- : [killnr] "i"(__NR_tgkill), [pid] "rm"(pid), [tid] "rm"(tid),
- [sig] "i"(SIGUSR2)
- : "rax", "rdi", "rsi", "rdx",
- // Clobbered by syscall.
- "rcx", "r11");
-
- // Record value of %xmm0 again to verify that the nested signal handler
- // does not clobber it.
- GET_XMM(exitxmm[0], xmm0);
-}
-
-TEST(FPSigTest, NestedSignals) {
- pid = getpid();
- tid = gettid();
-
- struct sigaction sa = {};
- sigemptyset(&sa.sa_mask);
- sa.sa_flags = SA_SIGINFO;
- sa.sa_sigaction = sigusr1;
- ASSERT_THAT(sigaction(SIGUSR1, &sa, nullptr), SyscallSucceeds());
-
- sa.sa_sigaction = sigusr2;
- ASSERT_THAT(sigaction(SIGUSR2, &sa, nullptr), SyscallSucceeds());
-
- // The amd64 ABI specifies that the XMM register set is caller-saved. This
- // implies that if there is any function call between SET_XMM and GET_XMM the
- // compiler might save/restore xmm0 implicitly. This defeats the entire
- // purpose of the test which is to verify that fpstate is restored by
- // sigreturn(2).
- //
- // This is the reason why 'tgkill(getpid(), gettid(), SIGUSR1)' is implemented
- // in inline assembly below.
- //
- // If the OS is broken and registers are clobbered by the signal, using tgkill
- // to signal the current thread ensures that this is the clobbered thread.
-
- uint64_t expected = 0xdeadbeeffacefeed;
- SET_XMM(expected, xmm0);
-
- asm volatile(
- "movl %[killnr], %%eax;"
- "movl %[pid], %%edi;"
- "movl %[tid], %%esi;"
- "movl %[sig], %%edx;"
- "syscall;"
- :
- : [killnr] "i"(__NR_tgkill), [pid] "rm"(pid), [tid] "rm"(tid),
- [sig] "i"(SIGUSR1)
- : "rax", "rdi", "rsi", "rdx",
- // Clobbered by syscall.
- "rcx", "r11");
-
- uint64_t got;
- GET_XMM(got, xmm0);
-
- //
- // The checks below verifies the following:
- // - signal handlers must called with a clean fpu state.
- // - sigreturn(2) must restore fpstate of the interrupted context.
- //
- EXPECT_EQ(expected, got);
- EXPECT_EQ(entryxmm[0], 0);
- EXPECT_EQ(entryxmm[1], 0);
- EXPECT_EQ(exitxmm[0], SIGUSR1);
- EXPECT_EQ(exitxmm[1], SIGUSR2);
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/fsync.cc b/test/syscalls/linux/fsync.cc
deleted file mode 100644
index e7e057f06..000000000
--- a/test/syscalls/linux/fsync.cc
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <stdio.h>
-#include <unistd.h>
-
-#include <string>
-
-#include "gtest/gtest.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(FsyncTest, TempFileSucceeds) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666));
- const std::string data = "some data to sync";
- EXPECT_THAT(write(fd.get(), data.c_str(), data.size()),
- SyscallSucceedsWithValue(data.size()));
- EXPECT_THAT(fsync(fd.get()), SyscallSucceeds());
-}
-
-TEST(FsyncTest, TempDirSucceeds) {
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_RDONLY | O_DIRECTORY));
- EXPECT_THAT(fsync(fd.get()), SyscallSucceeds());
-}
-
-TEST(FsyncTest, CannotFsyncOnUnopenedFd) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- int fd;
- ASSERT_THAT(fd = open(file.path().c_str(), O_RDONLY), SyscallSucceeds());
- ASSERT_THAT(close(fd), SyscallSucceeds());
-
- // fd is now invalid.
- EXPECT_THAT(fsync(fd), SyscallFailsWithErrno(EBADF));
-}
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/futex.cc b/test/syscalls/linux/futex.cc
deleted file mode 100644
index d3e3f998c..000000000
--- a/test/syscalls/linux/futex.cc
+++ /dev/null
@@ -1,721 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <linux/futex.h>
-#include <linux/types.h>
-#include <sys/syscall.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <algorithm>
-#include <atomic>
-#include <memory>
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "absl/memory/memory.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/cleanup.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/memory_util.h"
-#include "test/util/save_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-#include "test/util/time_util.h"
-#include "test/util/timer_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// Amount of time we wait for threads doing futex_wait to start running before
-// doing futex_wake.
-constexpr auto kWaiterStartupDelay = absl::Seconds(3);
-
-// Default timeout for waiters in tests where we expect a futex_wake to be
-// ineffective.
-constexpr auto kIneffectiveWakeTimeout = absl::Seconds(6);
-
-static_assert(kWaiterStartupDelay < kIneffectiveWakeTimeout,
- "futex_wait will time out before futex_wake is called");
-
-int futex_wait(bool priv, std::atomic<int>* uaddr, int val,
- absl::Duration timeout = absl::InfiniteDuration()) {
- int op = FUTEX_WAIT;
- if (priv) {
- op |= FUTEX_PRIVATE_FLAG;
- }
-
- if (timeout == absl::InfiniteDuration()) {
- return RetryEINTR(syscall)(SYS_futex, uaddr, op, val, nullptr);
- }
-
- // FUTEX_WAIT doesn't adjust the timeout if it returns EINTR, so we have to do
- // so.
- while (true) {
- auto const timeout_ts = absl::ToTimespec(timeout);
- MonotonicTimer timer;
- timer.Start();
- int const ret = syscall(SYS_futex, uaddr, op, val, &timeout_ts);
- if (ret != -1 || errno != EINTR) {
- return ret;
- }
- timeout = std::max(timeout - timer.Duration(), absl::ZeroDuration());
- }
-}
-
-int futex_wait_bitset(bool priv, std::atomic<int>* uaddr, int val, int bitset,
- absl::Time deadline = absl::InfiniteFuture()) {
- int op = FUTEX_WAIT_BITSET | FUTEX_CLOCK_REALTIME;
- if (priv) {
- op |= FUTEX_PRIVATE_FLAG;
- }
-
- auto const deadline_ts = absl::ToTimespec(deadline);
- return RetryEINTR(syscall)(
- SYS_futex, uaddr, op, val,
- deadline == absl::InfiniteFuture() ? nullptr : &deadline_ts, nullptr,
- bitset);
-}
-
-int futex_wake(bool priv, std::atomic<int>* uaddr, int count) {
- int op = FUTEX_WAKE;
- if (priv) {
- op |= FUTEX_PRIVATE_FLAG;
- }
- return syscall(SYS_futex, uaddr, op, count);
-}
-
-int futex_wake_bitset(bool priv, std::atomic<int>* uaddr, int count,
- int bitset) {
- int op = FUTEX_WAKE_BITSET;
- if (priv) {
- op |= FUTEX_PRIVATE_FLAG;
- }
- return syscall(SYS_futex, uaddr, op, count, nullptr, nullptr, bitset);
-}
-
-int futex_wake_op(bool priv, std::atomic<int>* uaddr1, std::atomic<int>* uaddr2,
- int nwake1, int nwake2, uint32_t sub_op) {
- int op = FUTEX_WAKE_OP;
- if (priv) {
- op |= FUTEX_PRIVATE_FLAG;
- }
- return syscall(SYS_futex, uaddr1, op, nwake1, nwake2, uaddr2, sub_op);
-}
-
-int futex_lock_pi(bool priv, std::atomic<int>* uaddr) {
- int op = FUTEX_LOCK_PI;
- if (priv) {
- op |= FUTEX_PRIVATE_FLAG;
- }
- int zero = 0;
- if (uaddr->compare_exchange_strong(zero, gettid())) {
- return 0;
- }
- return RetryEINTR(syscall)(SYS_futex, uaddr, op, nullptr, nullptr);
-}
-
-int futex_trylock_pi(bool priv, std::atomic<int>* uaddr) {
- int op = FUTEX_TRYLOCK_PI;
- if (priv) {
- op |= FUTEX_PRIVATE_FLAG;
- }
- int zero = 0;
- if (uaddr->compare_exchange_strong(zero, gettid())) {
- return 0;
- }
- return RetryEINTR(syscall)(SYS_futex, uaddr, op, nullptr, nullptr);
-}
-
-int futex_unlock_pi(bool priv, std::atomic<int>* uaddr) {
- int op = FUTEX_UNLOCK_PI;
- if (priv) {
- op |= FUTEX_PRIVATE_FLAG;
- }
- int tid = gettid();
- if (uaddr->compare_exchange_strong(tid, 0)) {
- return 0;
- }
- return RetryEINTR(syscall)(SYS_futex, uaddr, op, nullptr, nullptr);
-}
-
-// Fixture for futex tests parameterized by whether to use private or shared
-// futexes.
-class PrivateAndSharedFutexTest : public ::testing::TestWithParam<bool> {
- protected:
- bool IsPrivate() const { return GetParam(); }
- int PrivateFlag() const { return IsPrivate() ? FUTEX_PRIVATE_FLAG : 0; }
-};
-
-// FUTEX_WAIT with 0 timeout does not block.
-TEST_P(PrivateAndSharedFutexTest, Wait_ZeroTimeout) {
- struct timespec timeout = {};
-
- // Don't use the futex_wait helper because it adjusts timeout.
- int a = 1;
- EXPECT_THAT(syscall(SYS_futex, &a, FUTEX_WAIT | PrivateFlag(), a, &timeout),
- SyscallFailsWithErrno(ETIMEDOUT));
-}
-
-TEST_P(PrivateAndSharedFutexTest, Wait_Timeout) {
- std::atomic<int> a = ATOMIC_VAR_INIT(1);
-
- MonotonicTimer timer;
- timer.Start();
- constexpr absl::Duration kTimeout = absl::Seconds(1);
- EXPECT_THAT(futex_wait(IsPrivate(), &a, a, kTimeout),
- SyscallFailsWithErrno(ETIMEDOUT));
- EXPECT_GE(timer.Duration(), kTimeout);
-}
-
-TEST_P(PrivateAndSharedFutexTest, Wait_BitsetTimeout) {
- std::atomic<int> a = ATOMIC_VAR_INIT(1);
-
- MonotonicTimer timer;
- timer.Start();
- constexpr absl::Duration kTimeout = absl::Seconds(1);
- EXPECT_THAT(
- futex_wait_bitset(IsPrivate(), &a, a, 0xffffffff, absl::Now() + kTimeout),
- SyscallFailsWithErrno(ETIMEDOUT));
- EXPECT_GE(timer.Duration(), kTimeout);
-}
-
-TEST_P(PrivateAndSharedFutexTest, WaitBitset_NegativeTimeout) {
- std::atomic<int> a = ATOMIC_VAR_INIT(1);
-
- MonotonicTimer timer;
- timer.Start();
- EXPECT_THAT(futex_wait_bitset(IsPrivate(), &a, a, 0xffffffff,
- absl::Now() - absl::Seconds(1)),
- SyscallFailsWithErrno(ETIMEDOUT));
-}
-
-TEST_P(PrivateAndSharedFutexTest, Wait_WrongVal) {
- std::atomic<int> a = ATOMIC_VAR_INIT(1);
- EXPECT_THAT(futex_wait(IsPrivate(), &a, a + 1),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST_P(PrivateAndSharedFutexTest, Wait_ZeroBitset) {
- std::atomic<int> a = ATOMIC_VAR_INIT(1);
- EXPECT_THAT(futex_wait_bitset(IsPrivate(), &a, a, 0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_P(PrivateAndSharedFutexTest, Wake1_NoRandomSave) {
- constexpr int kInitialValue = 1;
- std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue);
-
- // Prevent save/restore from interrupting futex_wait, which will cause it to
- // return EAGAIN instead of the expected result if futex_wait is restarted
- // after we change the value of a below.
- DisableSave ds;
- ScopedThread thread([&] {
- EXPECT_THAT(futex_wait(IsPrivate(), &a, kInitialValue),
- SyscallSucceedsWithValue(0));
- });
- absl::SleepFor(kWaiterStartupDelay);
-
- // Change a so that if futex_wake happens before futex_wait, the latter
- // returns EAGAIN instead of hanging the test.
- a.fetch_add(1);
- EXPECT_THAT(futex_wake(IsPrivate(), &a, 1), SyscallSucceedsWithValue(1));
-}
-
-TEST_P(PrivateAndSharedFutexTest, WakeAll_NoRandomSave) {
- constexpr int kInitialValue = 1;
- std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue);
-
- DisableSave ds;
- constexpr int kThreads = 5;
- std::vector<std::unique_ptr<ScopedThread>> threads;
- threads.reserve(kThreads);
- for (int i = 0; i < kThreads; i++) {
- threads.push_back(absl::make_unique<ScopedThread>([&] {
- EXPECT_THAT(futex_wait(IsPrivate(), &a, kInitialValue),
- SyscallSucceeds());
- }));
- }
- absl::SleepFor(kWaiterStartupDelay);
-
- a.fetch_add(1);
- EXPECT_THAT(futex_wake(IsPrivate(), &a, kThreads),
- SyscallSucceedsWithValue(kThreads));
-}
-
-TEST_P(PrivateAndSharedFutexTest, WakeSome_NoRandomSave) {
- constexpr int kInitialValue = 1;
- std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue);
-
- DisableSave ds;
- constexpr int kThreads = 5;
- constexpr int kWokenThreads = 3;
- static_assert(kWokenThreads < kThreads,
- "can't wake more threads than are created");
- std::vector<std::unique_ptr<ScopedThread>> threads;
- threads.reserve(kThreads);
- std::vector<int> rets;
- rets.reserve(kThreads);
- std::vector<int> errs;
- errs.reserve(kThreads);
- for (int i = 0; i < kThreads; i++) {
- rets.push_back(-1);
- errs.push_back(0);
- }
- for (int i = 0; i < kThreads; i++) {
- threads.push_back(absl::make_unique<ScopedThread>([&, i] {
- rets[i] =
- futex_wait(IsPrivate(), &a, kInitialValue, kIneffectiveWakeTimeout);
- errs[i] = errno;
- }));
- }
- absl::SleepFor(kWaiterStartupDelay);
-
- a.fetch_add(1);
- EXPECT_THAT(futex_wake(IsPrivate(), &a, kWokenThreads),
- SyscallSucceedsWithValue(kWokenThreads));
-
- int woken = 0;
- int timedout = 0;
- for (int i = 0; i < kThreads; i++) {
- threads[i]->Join();
- if (rets[i] == 0) {
- woken++;
- } else if (errs[i] == ETIMEDOUT) {
- timedout++;
- } else {
- ADD_FAILURE() << " thread " << i << ": returned " << rets[i] << ", errno "
- << errs[i];
- }
- }
- EXPECT_EQ(woken, kWokenThreads);
- EXPECT_EQ(timedout, kThreads - kWokenThreads);
-}
-
-TEST_P(PrivateAndSharedFutexTest, WaitBitset_Wake_NoRandomSave) {
- constexpr int kInitialValue = 1;
- std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue);
-
- DisableSave ds;
- ScopedThread thread([&] {
- EXPECT_THAT(futex_wait_bitset(IsPrivate(), &a, kInitialValue, 0b01001000),
- SyscallSucceeds());
- });
- absl::SleepFor(kWaiterStartupDelay);
-
- a.fetch_add(1);
- EXPECT_THAT(futex_wake(IsPrivate(), &a, 1), SyscallSucceedsWithValue(1));
-}
-
-TEST_P(PrivateAndSharedFutexTest, Wait_WakeBitset_NoRandomSave) {
- constexpr int kInitialValue = 1;
- std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue);
-
- DisableSave ds;
- ScopedThread thread([&] {
- EXPECT_THAT(futex_wait(IsPrivate(), &a, kInitialValue), SyscallSucceeds());
- });
- absl::SleepFor(kWaiterStartupDelay);
-
- a.fetch_add(1);
- EXPECT_THAT(futex_wake_bitset(IsPrivate(), &a, 1, 0b01001000),
- SyscallSucceedsWithValue(1));
-}
-
-TEST_P(PrivateAndSharedFutexTest, WaitBitset_WakeBitsetMatch_NoRandomSave) {
- constexpr int kInitialValue = 1;
- std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue);
-
- constexpr int kBitset = 0b01001000;
-
- DisableSave ds;
- ScopedThread thread([&] {
- EXPECT_THAT(futex_wait_bitset(IsPrivate(), &a, kInitialValue, kBitset),
- SyscallSucceeds());
- });
- absl::SleepFor(kWaiterStartupDelay);
-
- a.fetch_add(1);
- EXPECT_THAT(futex_wake_bitset(IsPrivate(), &a, 1, kBitset),
- SyscallSucceedsWithValue(1));
-}
-
-TEST_P(PrivateAndSharedFutexTest, WaitBitset_WakeBitsetNoMatch_NoRandomSave) {
- constexpr int kInitialValue = 1;
- std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue);
-
- constexpr int kWaitBitset = 0b01000001;
- constexpr int kWakeBitset = 0b00101000;
- static_assert((kWaitBitset & kWakeBitset) == 0,
- "futex_wake_bitset will wake waiter");
-
- DisableSave ds;
- ScopedThread thread([&] {
- EXPECT_THAT(futex_wait_bitset(IsPrivate(), &a, kInitialValue, kWaitBitset,
- absl::Now() + kIneffectiveWakeTimeout),
- SyscallFailsWithErrno(ETIMEDOUT));
- });
- absl::SleepFor(kWaiterStartupDelay);
-
- a.fetch_add(1);
- EXPECT_THAT(futex_wake_bitset(IsPrivate(), &a, 1, kWakeBitset),
- SyscallSucceedsWithValue(0));
-}
-
-TEST_P(PrivateAndSharedFutexTest, WakeOpCondSuccess_NoRandomSave) {
- constexpr int kInitialValue = 1;
- std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue);
- std::atomic<int> b = ATOMIC_VAR_INIT(kInitialValue);
-
- DisableSave ds;
- ScopedThread thread_a([&] {
- EXPECT_THAT(futex_wait(IsPrivate(), &a, kInitialValue), SyscallSucceeds());
- });
- ScopedThread thread_b([&] {
- EXPECT_THAT(futex_wait(IsPrivate(), &b, kInitialValue), SyscallSucceeds());
- });
- absl::SleepFor(kWaiterStartupDelay);
-
- a.fetch_add(1);
- b.fetch_add(1);
- // This futex_wake_op should:
- // - Wake 1 waiter on a unconditionally.
- // - Wake 1 waiter on b if b == kInitialValue + 1, which it is.
- // - Do "b += 1".
- EXPECT_THAT(futex_wake_op(IsPrivate(), &a, &b, 1, 1,
- FUTEX_OP(FUTEX_OP_ADD, 1, FUTEX_OP_CMP_EQ,
- (kInitialValue + 1))),
- SyscallSucceedsWithValue(2));
- EXPECT_EQ(b, kInitialValue + 2);
-}
-
-TEST_P(PrivateAndSharedFutexTest, WakeOpCondFailure_NoRandomSave) {
- constexpr int kInitialValue = 1;
- std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue);
- std::atomic<int> b = ATOMIC_VAR_INIT(kInitialValue);
-
- DisableSave ds;
- ScopedThread thread_a([&] {
- EXPECT_THAT(futex_wait(IsPrivate(), &a, kInitialValue), SyscallSucceeds());
- });
- ScopedThread thread_b([&] {
- EXPECT_THAT(
- futex_wait(IsPrivate(), &b, kInitialValue, kIneffectiveWakeTimeout),
- SyscallFailsWithErrno(ETIMEDOUT));
- });
- absl::SleepFor(kWaiterStartupDelay);
-
- a.fetch_add(1);
- b.fetch_add(1);
- // This futex_wake_op should:
- // - Wake 1 waiter on a unconditionally.
- // - Wake 1 waiter on b if b == kInitialValue - 1, which it isn't.
- // - Do "b += 1".
- EXPECT_THAT(futex_wake_op(IsPrivate(), &a, &b, 1, 1,
- FUTEX_OP(FUTEX_OP_ADD, 1, FUTEX_OP_CMP_EQ,
- (kInitialValue - 1))),
- SyscallSucceedsWithValue(1));
- EXPECT_EQ(b, kInitialValue + 2);
-}
-
-TEST_P(PrivateAndSharedFutexTest, NoWakeInterprocessPrivateAnon_NoRandomSave) {
- auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
- auto const ptr = static_cast<std::atomic<int>*>(mapping.ptr());
- constexpr int kInitialValue = 1;
- ptr->store(kInitialValue);
-
- DisableSave ds;
- pid_t const child_pid = fork();
- if (child_pid == 0) {
- TEST_PCHECK(futex_wait(IsPrivate(), ptr, kInitialValue,
- kIneffectiveWakeTimeout) == -1 &&
- errno == ETIMEDOUT);
- _exit(0);
- }
- ASSERT_THAT(child_pid, SyscallSucceeds());
- absl::SleepFor(kWaiterStartupDelay);
-
- EXPECT_THAT(futex_wake(IsPrivate(), ptr, 1), SyscallSucceedsWithValue(0));
-
- int status;
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << " status " << status;
-}
-
-TEST_P(PrivateAndSharedFutexTest, WakeAfterCOWBreak_NoRandomSave) {
- // Use a futex on a non-stack mapping so we can be sure that the child process
- // below isn't the one that breaks copy-on-write.
- auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
- auto const ptr = static_cast<std::atomic<int>*>(mapping.ptr());
- constexpr int kInitialValue = 1;
- ptr->store(kInitialValue);
-
- DisableSave ds;
- ScopedThread thread([&] {
- EXPECT_THAT(futex_wait(IsPrivate(), ptr, kInitialValue), SyscallSucceeds());
- });
- absl::SleepFor(kWaiterStartupDelay);
-
- pid_t const child_pid = fork();
- if (child_pid == 0) {
- // Wait to be killed by the parent.
- while (true) pause();
- }
- ASSERT_THAT(child_pid, SyscallSucceeds());
- auto cleanup_child = Cleanup([&] {
- EXPECT_THAT(kill(child_pid, SIGKILL), SyscallSucceeds());
- int status;
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL)
- << " status " << status;
- });
-
- // In addition to preventing a late futex_wait from sleeping, this breaks
- // copy-on-write on the mapped page.
- ptr->fetch_add(1);
- EXPECT_THAT(futex_wake(IsPrivate(), ptr, 1), SyscallSucceedsWithValue(1));
-}
-
-TEST_P(PrivateAndSharedFutexTest, WakeWrongKind_NoRandomSave) {
- constexpr int kInitialValue = 1;
- std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue);
-
- DisableSave ds;
- ScopedThread thread([&] {
- EXPECT_THAT(
- futex_wait(IsPrivate(), &a, kInitialValue, kIneffectiveWakeTimeout),
- SyscallFailsWithErrno(ETIMEDOUT));
- });
- absl::SleepFor(kWaiterStartupDelay);
-
- a.fetch_add(1);
- // The value of priv passed to futex_wake is the opposite of that passed to
- // the futex_waiter; we expect this not to wake the waiter.
- EXPECT_THAT(futex_wake(!IsPrivate(), &a, 1), SyscallSucceedsWithValue(0));
-}
-
-INSTANTIATE_TEST_SUITE_P(SharedPrivate, PrivateAndSharedFutexTest,
- ::testing::Bool());
-
-// Passing null as the address only works for private futexes.
-
-TEST(PrivateFutexTest, WakeOp0Set) {
- std::atomic<int> a = ATOMIC_VAR_INIT(1);
-
- int futex_op = FUTEX_OP(FUTEX_OP_SET, 2, 0, 0);
- EXPECT_THAT(futex_wake_op(true, nullptr, &a, 0, 0, futex_op),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(a, 2);
-}
-
-TEST(PrivateFutexTest, WakeOp0Add) {
- std::atomic<int> a = ATOMIC_VAR_INIT(1);
- int futex_op = FUTEX_OP(FUTEX_OP_ADD, 1, 0, 0);
- EXPECT_THAT(futex_wake_op(true, nullptr, &a, 0, 0, futex_op),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(a, 2);
-}
-
-TEST(PrivateFutexTest, WakeOp0Or) {
- std::atomic<int> a = ATOMIC_VAR_INIT(0b01);
- int futex_op = FUTEX_OP(FUTEX_OP_OR, 0b10, 0, 0);
- EXPECT_THAT(futex_wake_op(true, nullptr, &a, 0, 0, futex_op),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(a, 0b11);
-}
-
-TEST(PrivateFutexTest, WakeOp0Andn) {
- std::atomic<int> a = ATOMIC_VAR_INIT(0b11);
- int futex_op = FUTEX_OP(FUTEX_OP_ANDN, 0b10, 0, 0);
- EXPECT_THAT(futex_wake_op(true, nullptr, &a, 0, 0, futex_op),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(a, 0b01);
-}
-
-TEST(PrivateFutexTest, WakeOp0Xor) {
- std::atomic<int> a = ATOMIC_VAR_INIT(0b1010);
- int futex_op = FUTEX_OP(FUTEX_OP_XOR, 0b1100, 0, 0);
- EXPECT_THAT(futex_wake_op(true, nullptr, &a, 0, 0, futex_op),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(a, 0b0110);
-}
-
-TEST(SharedFutexTest, WakeInterprocessSharedAnon_NoRandomSave) {
- auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED));
- auto const ptr = static_cast<std::atomic<int>*>(mapping.ptr());
- constexpr int kInitialValue = 1;
- ptr->store(kInitialValue);
-
- DisableSave ds;
- pid_t const child_pid = fork();
- if (child_pid == 0) {
- TEST_PCHECK(futex_wait(false, ptr, kInitialValue) == 0);
- _exit(0);
- }
- ASSERT_THAT(child_pid, SyscallSucceeds());
- auto kill_child = Cleanup(
- [&] { EXPECT_THAT(kill(child_pid, SIGKILL), SyscallSucceeds()); });
- absl::SleepFor(kWaiterStartupDelay);
-
- ptr->fetch_add(1);
- // This is an ASSERT so that if it fails, we immediately abort the test (and
- // kill the subprocess).
- ASSERT_THAT(futex_wake(false, ptr, 1), SyscallSucceedsWithValue(1));
-
- kill_child.Release();
- int status;
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << " status " << status;
-}
-
-TEST(SharedFutexTest, WakeInterprocessFile_NoRandomSave) {
- auto const file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- ASSERT_THAT(truncate(file.path().c_str(), kPageSize), SyscallSucceeds());
- auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
- auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(Mmap(
- nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0));
- auto const ptr = static_cast<std::atomic<int>*>(mapping.ptr());
- constexpr int kInitialValue = 1;
- ptr->store(kInitialValue);
-
- DisableSave ds;
- pid_t const child_pid = fork();
- if (child_pid == 0) {
- TEST_PCHECK(futex_wait(false, ptr, kInitialValue) == 0);
- _exit(0);
- }
- ASSERT_THAT(child_pid, SyscallSucceeds());
- auto kill_child = Cleanup(
- [&] { EXPECT_THAT(kill(child_pid, SIGKILL), SyscallSucceeds()); });
- absl::SleepFor(kWaiterStartupDelay);
-
- ptr->fetch_add(1);
- // This is an ASSERT so that if it fails, we immediately abort the test (and
- // kill the subprocess).
- ASSERT_THAT(futex_wake(false, ptr, 1), SyscallSucceedsWithValue(1));
-
- kill_child.Release();
- int status;
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << " status " << status;
-}
-
-TEST_P(PrivateAndSharedFutexTest, PIBasic) {
- std::atomic<int> a = ATOMIC_VAR_INIT(0);
-
- ASSERT_THAT(futex_lock_pi(IsPrivate(), &a), SyscallSucceeds());
- EXPECT_EQ(a.load(), gettid());
- EXPECT_THAT(futex_lock_pi(IsPrivate(), &a), SyscallFailsWithErrno(EDEADLK));
-
- ASSERT_THAT(futex_unlock_pi(IsPrivate(), &a), SyscallSucceeds());
- EXPECT_EQ(a.load(), 0);
- EXPECT_THAT(futex_unlock_pi(IsPrivate(), &a), SyscallFailsWithErrno(EPERM));
-}
-
-TEST_P(PrivateAndSharedFutexTest, PIConcurrency_NoRandomSave) {
- DisableSave ds; // Too many syscalls.
-
- std::atomic<int> a = ATOMIC_VAR_INIT(0);
- const bool is_priv = IsPrivate();
-
- std::unique_ptr<ScopedThread> threads[100];
- for (size_t i = 0; i < ABSL_ARRAYSIZE(threads); ++i) {
- threads[i] = absl::make_unique<ScopedThread>([is_priv, &a] {
- for (size_t j = 0; j < 10; ++j) {
- ASSERT_THAT(futex_lock_pi(is_priv, &a), SyscallSucceeds());
- EXPECT_EQ(a.load() & FUTEX_TID_MASK, gettid());
- SleepSafe(absl::Milliseconds(5));
- ASSERT_THAT(futex_unlock_pi(is_priv, &a), SyscallSucceeds());
- }
- });
- }
-}
-
-TEST_P(PrivateAndSharedFutexTest, PIWaiters) {
- std::atomic<int> a = ATOMIC_VAR_INIT(0);
- const bool is_priv = IsPrivate();
-
- ASSERT_THAT(futex_lock_pi(is_priv, &a), SyscallSucceeds());
- EXPECT_EQ(a.load(), gettid());
-
- ScopedThread th([is_priv, &a] {
- ASSERT_THAT(futex_lock_pi(is_priv, &a), SyscallSucceeds());
- ASSERT_THAT(futex_unlock_pi(is_priv, &a), SyscallSucceeds());
- });
-
- // Wait until the thread blocks on the futex, setting the waiters bit.
- auto start = absl::Now();
- while (a.load() != (FUTEX_WAITERS | gettid())) {
- ASSERT_LT(absl::Now() - start, absl::Seconds(5));
- absl::SleepFor(absl::Milliseconds(100));
- }
- ASSERT_THAT(futex_unlock_pi(is_priv, &a), SyscallSucceeds());
-}
-
-TEST_P(PrivateAndSharedFutexTest, PITryLock) {
- std::atomic<int> a = ATOMIC_VAR_INIT(0);
- const bool is_priv = IsPrivate();
-
- ASSERT_THAT(futex_trylock_pi(IsPrivate(), &a), SyscallSucceeds());
- EXPECT_EQ(a.load(), gettid());
-
- EXPECT_THAT(futex_trylock_pi(is_priv, &a), SyscallFailsWithErrno(EDEADLK));
- ScopedThread th([is_priv, &a] {
- EXPECT_THAT(futex_trylock_pi(is_priv, &a), SyscallFailsWithErrno(EAGAIN));
- });
- th.Join();
-
- ASSERT_THAT(futex_unlock_pi(IsPrivate(), &a), SyscallSucceeds());
-}
-
-TEST_P(PrivateAndSharedFutexTest, PITryLockConcurrency_NoRandomSave) {
- DisableSave ds; // Too many syscalls.
-
- std::atomic<int> a = ATOMIC_VAR_INIT(0);
- const bool is_priv = IsPrivate();
-
- std::unique_ptr<ScopedThread> threads[10];
- for (size_t i = 0; i < ABSL_ARRAYSIZE(threads); ++i) {
- threads[i] = absl::make_unique<ScopedThread>([is_priv, &a] {
- for (size_t j = 0; j < 10;) {
- if (futex_trylock_pi(is_priv, &a) == 0) {
- ++j;
- EXPECT_EQ(a.load() & FUTEX_TID_MASK, gettid());
- SleepSafe(absl::Milliseconds(5));
- ASSERT_THAT(futex_unlock_pi(is_priv, &a), SyscallSucceeds());
- }
- }
- });
- }
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/getcpu.cc b/test/syscalls/linux/getcpu.cc
deleted file mode 100644
index f4d94bd6a..000000000
--- a/test/syscalls/linux/getcpu.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sched.h>
-
-#include "gtest/gtest.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(GetcpuTest, IsValidCpuStress) {
- const int num_cpus = NumCPUs();
- absl::Time deadline = absl::Now() + absl::Seconds(10);
- while (absl::Now() < deadline) {
- int cpu;
- ASSERT_THAT(cpu = sched_getcpu(), SyscallSucceeds());
- ASSERT_LT(cpu, num_cpus);
- }
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/getdents.cc b/test/syscalls/linux/getdents.cc
deleted file mode 100644
index fe9cfafe8..000000000
--- a/test/syscalls/linux/getdents.cc
+++ /dev/null
@@ -1,529 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/mman.h>
-#include <sys/types.h>
-#include <syscall.h>
-#include <unistd.h>
-#include <map>
-#include <string>
-#include <unordered_map>
-#include <unordered_set>
-#include <utility>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/strings/numbers.h"
-#include "absl/strings/str_cat.h"
-#include "test/util/eventfd_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-using ::testing::Contains;
-using ::testing::IsEmpty;
-using ::testing::IsSupersetOf;
-using ::testing::Not;
-using ::testing::NotNull;
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// New Linux dirent format.
-struct linux_dirent64 {
- uint64_t d_ino; // Inode number
- int64_t d_off; // Offset to next linux_dirent64
- unsigned short d_reclen; // NOLINT, Length of this linux_dirent64
- unsigned char d_type; // NOLINT, File type
- char d_name[0]; // Filename (null-terminated)
-};
-
-// Old Linux dirent format.
-struct linux_dirent {
- unsigned long d_ino; // NOLINT
- unsigned long d_off; // NOLINT
- unsigned short d_reclen; // NOLINT
- char d_name[0];
-};
-
-// Wraps a buffer to provide a set of dirents.
-// T is the underlying dirent type.
-template <typename T>
-class DirentBuffer {
- public:
- // DirentBuffer manages the buffer.
- explicit DirentBuffer(size_t size)
- : managed_(true), actual_size_(size), reported_size_(size) {
- data_ = new char[actual_size_];
- }
-
- // The buffer is managed externally.
- DirentBuffer(char* data, size_t actual_size, size_t reported_size)
- : managed_(false),
- data_(data),
- actual_size_(actual_size),
- reported_size_(reported_size) {}
-
- ~DirentBuffer() {
- if (managed_) {
- delete[] data_;
- }
- }
-
- T* Data() { return reinterpret_cast<T*>(data_); }
-
- T* Start(size_t read) {
- read_ = read;
- if (read_) {
- return Data();
- } else {
- return nullptr;
- }
- }
-
- T* Current() { return reinterpret_cast<T*>(&data_[off_]); }
-
- T* Next() {
- size_t new_off = off_ + Current()->d_reclen;
- if (new_off >= read_ || new_off >= actual_size_) {
- return nullptr;
- }
-
- off_ = new_off;
- return Current();
- }
-
- size_t Size() { return reported_size_; }
-
- void Reset() {
- off_ = 0;
- read_ = 0;
- memset(data_, 0, actual_size_);
- }
-
- private:
- bool managed_;
- char* data_;
- size_t actual_size_;
- size_t reported_size_;
-
- size_t off_ = 0;
-
- size_t read_ = 0;
-};
-
-// Test for getdents/getdents64.
-// T is the Linux dirent type.
-template <typename T>
-class GetdentsTest : public ::testing::Test {
- public:
- using LinuxDirentType = T;
- using DirentBufferType = DirentBuffer<T>;
-
- protected:
- void SetUp() override {
- dir_ = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- fd_ = ASSERT_NO_ERRNO_AND_VALUE(Open(dir_.path(), O_RDONLY | O_DIRECTORY));
- }
-
- // Must be overridden with explicit specialization. See below.
- int SyscallNum();
-
- int Getdents(LinuxDirentType* dirp, unsigned int count) {
- return RetryEINTR(syscall)(SyscallNum(), fd_.get(), dirp, count);
- }
-
- // Fill directory with num files, named by number starting at 0.
- void FillDirectory(size_t num) {
- for (size_t i = 0; i < num; i++) {
- auto name = JoinPath(dir_.path(), absl::StrCat(i));
- TEST_CHECK(CreateWithContents(name, "").ok());
- }
- }
-
- // Fill directory with a given list of filenames.
- void FillDirectoryWithFiles(const std::vector<std::string>& filenames) {
- for (const auto& filename : filenames) {
- auto name = JoinPath(dir_.path(), filename);
- TEST_CHECK(CreateWithContents(name, "").ok());
- }
- }
-
- // Seek to the start of the directory.
- PosixError SeekStart() {
- constexpr off_t kStartOfFile = 0;
- off_t offset = lseek(fd_.get(), kStartOfFile, SEEK_SET);
- if (offset < 0) {
- return PosixError(errno, absl::StrCat("error seeking to ", kStartOfFile));
- }
- if (offset != kStartOfFile) {
- return PosixError(EINVAL, absl::StrCat("tried to seek to ", kStartOfFile,
- " but got ", offset));
- }
- return NoError();
- }
-
- // Call getdents multiple times, reading all dirents and calling f on each.
- // f has the type signature PosixError f(T*).
- // If f returns a non-OK error, so does ReadDirents.
- template <typename F>
- PosixError ReadDirents(DirentBufferType* dirents, F const& f) {
- int n;
- do {
- dirents->Reset();
-
- n = Getdents(dirents->Data(), dirents->Size());
- MaybeSave();
- if (n < 0) {
- return PosixError(errno, "getdents");
- }
-
- for (auto d = dirents->Start(n); d; d = dirents->Next()) {
- RETURN_IF_ERRNO(f(d));
- }
- } while (n > 0);
-
- return NoError();
- }
-
- // Call Getdents successively and count all entries.
- int ReadAndCountAllEntries(DirentBufferType* dirents) {
- int found = 0;
-
- EXPECT_NO_ERRNO(ReadDirents(dirents, [&](LinuxDirentType* d) {
- found++;
- return NoError();
- }));
-
- return found;
- }
-
- private:
- TempPath dir_;
- FileDescriptor fd_;
-};
-
-// Multiple template parameters are not allowed, so we must use explicit
-// template specialization to set the syscall number.
-template <>
-int GetdentsTest<struct linux_dirent>::SyscallNum() {
- return SYS_getdents;
-}
-
-template <>
-int GetdentsTest<struct linux_dirent64>::SyscallNum() {
- return SYS_getdents64;
-}
-
-// Test both legacy getdents and getdents64.
-typedef ::testing::Types<struct linux_dirent, struct linux_dirent64>
- GetdentsTypes;
-TYPED_TEST_SUITE(GetdentsTest, GetdentsTypes);
-
-// N.B. TYPED_TESTs require explicitly using this-> to access members of
-// GetdentsTest, since we are inside of a derived class template.
-
-TYPED_TEST(GetdentsTest, VerifyEntries) {
- typename TestFixture::DirentBufferType dirents(1024);
-
- this->FillDirectory(2);
-
- // Map of all the entries we expect to find.
- std::map<std::string, bool> found;
- found["."] = false;
- found[".."] = false;
- found["0"] = false;
- found["1"] = false;
-
- EXPECT_NO_ERRNO(this->ReadDirents(
- &dirents, [&](typename TestFixture::LinuxDirentType* d) {
- auto kv = found.find(d->d_name);
- EXPECT_NE(kv, found.end()) << "Unexpected file: " << d->d_name;
- if (kv != found.end()) {
- EXPECT_FALSE(kv->second);
- }
- found[d->d_name] = true;
- return NoError();
- }));
-
- for (auto& kv : found) {
- EXPECT_TRUE(kv.second) << "File not found: " << kv.first;
- }
-}
-
-TYPED_TEST(GetdentsTest, VerifyPadding) {
- typename TestFixture::DirentBufferType dirents(1024);
-
- // Create files with names of length 1 through 16.
- std::vector<std::string> files;
- std::string filename;
- for (int i = 0; i < 16; ++i) {
- absl::StrAppend(&filename, "a");
- files.push_back(filename);
- }
- this->FillDirectoryWithFiles(files);
-
- // We expect to find all the files, plus '.' and '..'.
- const int expect_found = 2 + files.size();
- int found = 0;
-
- EXPECT_NO_ERRNO(this->ReadDirents(
- &dirents, [&](typename TestFixture::LinuxDirentType* d) {
- EXPECT_EQ(d->d_reclen % 8, 0)
- << "Dirent " << d->d_name
- << " had reclen that was not byte aligned: " << d->d_name;
- found++;
- return NoError();
- }));
-
- // Make sure we found all the files.
- EXPECT_EQ(found, expect_found);
-}
-
-// For a small directory, the provided buffer should be large enough
-// for all entries.
-TYPED_TEST(GetdentsTest, SmallDir) {
- // . and .. should be in an otherwise empty directory.
- int expect = 2;
-
- // Add some actual files.
- this->FillDirectory(2);
- expect += 2;
-
- typename TestFixture::DirentBufferType dirents(256);
-
- EXPECT_EQ(expect, this->ReadAndCountAllEntries(&dirents));
-}
-
-// A directory with lots of files requires calling getdents multiple times.
-TYPED_TEST(GetdentsTest, LargeDir) {
- // . and .. should be in an otherwise empty directory.
- int expect = 2;
-
- // Add some actual files.
- this->FillDirectory(100);
- expect += 100;
-
- typename TestFixture::DirentBufferType dirents(256);
-
- EXPECT_EQ(expect, this->ReadAndCountAllEntries(&dirents));
-}
-
-// If we lie about the size of the buffer, we should still be able to read the
-// entries with the available space.
-TYPED_TEST(GetdentsTest, PartialBuffer) {
- // . and .. should be in an otherwise empty directory.
- int expect = 2;
-
- // Add some actual files.
- this->FillDirectory(100);
- expect += 100;
-
- void* addr = mmap(0, 2 * kPageSize, PROT_READ | PROT_WRITE,
- MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
- ASSERT_NE(addr, MAP_FAILED);
-
- char* buf = reinterpret_cast<char*>(addr);
-
- // Guard page
- EXPECT_THAT(
- mprotect(reinterpret_cast<void*>(buf + kPageSize), kPageSize, PROT_NONE),
- SyscallSucceeds());
-
- // Limit space in buf to 256 bytes.
- buf += kPageSize - 256;
-
- // Lie about the buffer. Even though we claim the buffer is 1 page,
- // we should still get all of the dirents in the first 256 bytes.
- typename TestFixture::DirentBufferType dirents(buf, 256, kPageSize);
-
- EXPECT_EQ(expect, this->ReadAndCountAllEntries(&dirents));
-
- EXPECT_THAT(munmap(addr, 2 * kPageSize), SyscallSucceeds());
-}
-
-// Open many file descriptors, then scan through /proc/self/fd to find and close
-// them all. (The latter is commonly used to handle races between fork/execve
-// and the creation of unwanted non-O_CLOEXEC file descriptors.) This tests that
-// getdents iterates correctly despite mutation of /proc/self/fd.
-TYPED_TEST(GetdentsTest, ProcSelfFd) {
- constexpr size_t kNfds = 10;
- std::unordered_map<int, FileDescriptor> fds;
- fds.reserve(kNfds);
- for (size_t i = 0; i < kNfds; i++) {
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD());
- fds.emplace(fd.get(), std::move(fd));
- }
-
- const FileDescriptor proc_self_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/fd", O_RDONLY | O_DIRECTORY));
-
- // Make the buffer very small since we want to iterate.
- typename TestFixture::DirentBufferType dirents(
- 2 * sizeof(typename TestFixture::LinuxDirentType));
- std::unordered_set<int> prev_fds;
- while (true) {
- dirents.Reset();
- int rv;
- ASSERT_THAT(rv = RetryEINTR(syscall)(this->SyscallNum(), proc_self_fd.get(),
- dirents.Data(), dirents.Size()),
- SyscallSucceeds());
- if (rv == 0) {
- break;
- }
- for (auto* d = dirents.Start(rv); d; d = dirents.Next()) {
- int dfd;
- if (!absl::SimpleAtoi(d->d_name, &dfd)) continue;
- EXPECT_TRUE(prev_fds.insert(dfd).second)
- << "Repeated observation of /proc/self/fd/" << dfd;
- fds.erase(dfd);
- }
- }
-
- // Check that we closed every fd.
- EXPECT_THAT(fds, ::testing::IsEmpty());
-}
-
-// Test that getdents returns ENOTDIR when called on a file.
-TYPED_TEST(GetdentsTest, NotDir) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY));
-
- typename TestFixture::DirentBufferType dirents(256);
- EXPECT_THAT(RetryEINTR(syscall)(this->SyscallNum(), fd.get(), dirents.Data(),
- dirents.Size()),
- SyscallFailsWithErrno(ENOTDIR));
-}
-
-// Test that SEEK_SET to 0 causes getdents to re-read the entries.
-TYPED_TEST(GetdentsTest, SeekResetsCursor) {
- // . and .. should be in an otherwise empty directory.
- int expect = 2;
-
- // Add some files to the directory.
- this->FillDirectory(10);
- expect += 10;
-
- typename TestFixture::DirentBufferType dirents(256);
-
- // We should get all the expected entries.
- EXPECT_EQ(expect, this->ReadAndCountAllEntries(&dirents));
-
- // Seek back to 0.
- ASSERT_NO_ERRNO(this->SeekStart());
-
- // We should get all the expected entries again.
- EXPECT_EQ(expect, this->ReadAndCountAllEntries(&dirents));
-}
-
-// Test that getdents() after SEEK_END succeeds.
-// This is a regression test for #128.
-TYPED_TEST(GetdentsTest, Issue128ProcSeekEnd) {
- auto fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self", O_RDONLY | O_DIRECTORY));
- typename TestFixture::DirentBufferType dirents(256);
-
- ASSERT_THAT(lseek(fd.get(), 0, SEEK_END), SyscallSucceeds());
- ASSERT_THAT(RetryEINTR(syscall)(this->SyscallNum(), fd.get(), dirents.Data(),
- dirents.Size()),
- SyscallSucceeds());
-}
-
-// Some tests using the glibc readdir interface.
-TEST(ReaddirTest, OpenDir) {
- DIR* dev;
- ASSERT_THAT(dev = opendir("/dev"), NotNull());
- EXPECT_THAT(closedir(dev), SyscallSucceeds());
-}
-
-TEST(ReaddirTest, RootContainsBasicDirectories) {
- EXPECT_THAT(ListDir("/", true),
- IsPosixErrorOkAndHolds(IsSupersetOf(
- {"bin", "dev", "etc", "lib", "proc", "sbin", "usr"})));
-}
-
-TEST(ReaddirTest, Bug24096713Dev) {
- auto contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir("/dev", true));
- EXPECT_THAT(contents, Not(IsEmpty()));
-}
-
-TEST(ReaddirTest, Bug24096713ProcTid) {
- auto contents = ASSERT_NO_ERRNO_AND_VALUE(
- ListDir(absl::StrCat("/proc/", syscall(SYS_gettid), "/"), true));
- EXPECT_THAT(contents, Not(IsEmpty()));
-}
-
-TEST(ReaddirTest, Bug33429925Proc) {
- auto contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir("/proc", true));
- EXPECT_THAT(contents, Not(IsEmpty()));
-}
-
-TEST(ReaddirTest, Bug35110122Root) {
- auto contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir("/", true));
- EXPECT_THAT(contents, Not(IsEmpty()));
-}
-
-// Unlink should invalidate getdents cache.
-TEST(ReaddirTest, GoneAfterRemoveCache) {
- TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
- std::string name = std::string(Basename(file.path()));
-
- auto contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir(dir.path(), true));
- EXPECT_THAT(contents, Contains(name));
-
- file.reset();
-
- contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir(dir.path(), true));
- EXPECT_THAT(contents, Not(Contains(name)));
-}
-
-// Regression test for b/137398511. Rename should invalidate getdents cache.
-TEST(ReaddirTest, GoneAfterRenameCache) {
- TempPath src = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- TempPath dst = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(src.path()));
- std::string name = std::string(Basename(file.path()));
-
- auto contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir(src.path(), true));
- EXPECT_THAT(contents, Contains(name));
-
- ASSERT_THAT(rename(file.path().c_str(), JoinPath(dst.path(), name).c_str()),
- SyscallSucceeds());
- // Release file since it was renamed. dst cleanup will ultimately delete it.
- file.release();
-
- contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir(src.path(), true));
- EXPECT_THAT(contents, Not(Contains(name)));
-
- contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir(dst.path(), true));
- EXPECT_THAT(contents, Contains(name));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/getrandom.cc b/test/syscalls/linux/getrandom.cc
deleted file mode 100644
index f97f60029..000000000
--- a/test/syscalls/linux/getrandom.cc
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sys/syscall.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-#ifndef SYS_getrandom
-#if defined(__x86_64__)
-#define SYS_getrandom 318
-#elif defined(__i386__)
-#define SYS_getrandom 355
-#else
-#error "Unknown architecture"
-#endif
-#endif // SYS_getrandom
-
-bool SomeByteIsNonZero(char* random_bytes, int length) {
- for (int i = 0; i < length; i++) {
- if (random_bytes[i] != 0) {
- return true;
- }
- }
- return false;
-}
-
-TEST(GetrandomTest, IsRandom) {
- // This test calls get_random and makes sure that the array is filled in with
- // something that is non-zero. Perhaps we get back \x00\x00\x00\x00\x00.... as
- // a random result, but it's so unlikely that we'll just ignore this.
- char random_bytes[64] = {};
- int n = syscall(SYS_getrandom, random_bytes, 64, 0);
- SKIP_IF(!IsRunningOnGvisor() && n < 0 && errno == ENOSYS);
- EXPECT_THAT(n, SyscallSucceeds());
- EXPECT_GT(n, 0); // Some bytes should be returned.
- EXPECT_TRUE(SomeByteIsNonZero(random_bytes, n));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/getrusage.cc b/test/syscalls/linux/getrusage.cc
deleted file mode 100644
index 9bdb1e4cd..000000000
--- a/test/syscalls/linux/getrusage.cc
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <signal.h>
-#include <sys/mman.h>
-#include <sys/resource.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-
-#include "gtest/gtest.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/logging.h"
-#include "test/util/memory_util.h"
-#include "test/util/signal_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(GetrusageTest, BasicFork) {
- pid_t pid = fork();
- if (pid == 0) {
- struct rusage rusage_self;
- TEST_PCHECK(getrusage(RUSAGE_SELF, &rusage_self) == 0);
- struct rusage rusage_children;
- TEST_PCHECK(getrusage(RUSAGE_CHILDREN, &rusage_children) == 0);
- // The child has consumed some memory.
- TEST_CHECK(rusage_self.ru_maxrss != 0);
- // The child has no children of its own.
- TEST_CHECK(rusage_children.ru_maxrss == 0);
- _exit(0);
- }
- ASSERT_THAT(pid, SyscallSucceeds());
- int status;
- ASSERT_THAT(RetryEINTR(waitpid)(pid, &status, 0), SyscallSucceeds());
- struct rusage rusage_self;
- ASSERT_THAT(getrusage(RUSAGE_SELF, &rusage_self), SyscallSucceeds());
- struct rusage rusage_children;
- ASSERT_THAT(getrusage(RUSAGE_CHILDREN, &rusage_children), SyscallSucceeds());
- // The parent has consumed some memory.
- EXPECT_GT(rusage_self.ru_maxrss, 0);
- // The child has consumed some memory, and because it has exited we can get
- // its max RSS.
- EXPECT_GT(rusage_children.ru_maxrss, 0);
-}
-
-// Verifies that a process can get the max resident set size of its grandchild,
-// i.e. that maxrss propagates correctly from children to waiting parents.
-TEST(GetrusageTest, Grandchild) {
- constexpr int kGrandchildSizeKb = 1024;
- pid_t pid = fork();
- if (pid == 0) {
- pid = fork();
- if (pid == 0) {
- int flags = MAP_ANONYMOUS | MAP_POPULATE | MAP_PRIVATE;
- void *addr =
- mmap(nullptr, kGrandchildSizeKb * 1024, PROT_WRITE, flags, -1, 0);
- TEST_PCHECK(addr != MAP_FAILED);
- } else {
- int status;
- TEST_PCHECK(RetryEINTR(waitpid)(pid, &status, 0) == pid);
- }
- _exit(0);
- }
- ASSERT_THAT(pid, SyscallSucceeds());
- int status;
- ASSERT_THAT(RetryEINTR(waitpid)(pid, &status, 0), SyscallSucceeds());
- struct rusage rusage_self;
- ASSERT_THAT(getrusage(RUSAGE_SELF, &rusage_self), SyscallSucceeds());
- struct rusage rusage_children;
- ASSERT_THAT(getrusage(RUSAGE_CHILDREN, &rusage_children), SyscallSucceeds());
- // The parent has consumed some memory.
- EXPECT_GT(rusage_self.ru_maxrss, 0);
- // The child should consume next to no memory, but the grandchild will
- // consume at least 1MB. Verify that usage bubbles up to the grandparent.
- EXPECT_GT(rusage_children.ru_maxrss, kGrandchildSizeKb);
-}
-
-// Verifies that processes ignoring SIGCHLD do not have updated child maxrss
-// updated.
-TEST(GetrusageTest, IgnoreSIGCHLD) {
- struct sigaction sa;
- sa.sa_handler = SIG_IGN;
- sa.sa_flags = 0;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGCHLD, sa));
- pid_t pid = fork();
- if (pid == 0) {
- struct rusage rusage_self;
- TEST_PCHECK(getrusage(RUSAGE_SELF, &rusage_self) == 0);
- // The child has consumed some memory.
- TEST_CHECK(rusage_self.ru_maxrss != 0);
- _exit(0);
- }
- ASSERT_THAT(pid, SyscallSucceeds());
- int status;
- ASSERT_THAT(RetryEINTR(waitpid)(pid, &status, 0),
- SyscallFailsWithErrno(ECHILD));
- struct rusage rusage_self;
- ASSERT_THAT(getrusage(RUSAGE_SELF, &rusage_self), SyscallSucceeds());
- struct rusage rusage_children;
- ASSERT_THAT(getrusage(RUSAGE_CHILDREN, &rusage_children), SyscallSucceeds());
- // The parent has consumed some memory.
- EXPECT_GT(rusage_self.ru_maxrss, 0);
- // The child's maxrss should not have propagated up.
- EXPECT_EQ(rusage_children.ru_maxrss, 0);
-}
-
-// Verifies that zombie processes do not update their parent's maxrss. Only
-// reaped processes should do this.
-TEST(GetrusageTest, IgnoreZombie) {
- pid_t pid = fork();
- if (pid == 0) {
- struct rusage rusage_self;
- TEST_PCHECK(getrusage(RUSAGE_SELF, &rusage_self) == 0);
- struct rusage rusage_children;
- TEST_PCHECK(getrusage(RUSAGE_CHILDREN, &rusage_children) == 0);
- // The child has consumed some memory.
- TEST_CHECK(rusage_self.ru_maxrss != 0);
- // The child has no children of its own.
- TEST_CHECK(rusage_children.ru_maxrss == 0);
- _exit(0);
- }
- ASSERT_THAT(pid, SyscallSucceeds());
- // Give the child time to exit. Because we don't call wait, the child should
- // remain a zombie.
- absl::SleepFor(absl::Seconds(5));
- struct rusage rusage_self;
- ASSERT_THAT(getrusage(RUSAGE_SELF, &rusage_self), SyscallSucceeds());
- struct rusage rusage_children;
- ASSERT_THAT(getrusage(RUSAGE_CHILDREN, &rusage_children), SyscallSucceeds());
- // The parent has consumed some memory.
- EXPECT_GT(rusage_self.ru_maxrss, 0);
- // The child has consumed some memory, but hasn't been reaped.
- EXPECT_EQ(rusage_children.ru_maxrss, 0);
-}
-
-TEST(GetrusageTest, Wait4) {
- pid_t pid = fork();
- if (pid == 0) {
- struct rusage rusage_self;
- TEST_PCHECK(getrusage(RUSAGE_SELF, &rusage_self) == 0);
- struct rusage rusage_children;
- TEST_PCHECK(getrusage(RUSAGE_CHILDREN, &rusage_children) == 0);
- // The child has consumed some memory.
- TEST_CHECK(rusage_self.ru_maxrss != 0);
- // The child has no children of its own.
- TEST_CHECK(rusage_children.ru_maxrss == 0);
- _exit(0);
- }
- ASSERT_THAT(pid, SyscallSucceeds());
- struct rusage rusage_children;
- int status;
- ASSERT_THAT(RetryEINTR(wait4)(pid, &status, 0, &rusage_children),
- SyscallSucceeds());
- // The child has consumed some memory, and because it has exited we can get
- // its max RSS.
- EXPECT_GT(rusage_children.ru_maxrss, 0);
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/inotify.cc b/test/syscalls/linux/inotify.cc
deleted file mode 100644
index 7384c27dc..000000000
--- a/test/syscalls/linux/inotify.cc
+++ /dev/null
@@ -1,1596 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <libgen.h>
-#include <sched.h>
-#include <sys/epoll.h>
-#include <sys/inotify.h>
-#include <sys/ioctl.h>
-#include <sys/time.h>
-
-#include <atomic>
-#include <list>
-#include <string>
-#include <vector>
-
-#include "absl/strings/str_cat.h"
-#include "absl/strings/str_format.h"
-#include "absl/strings/str_join.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/epoll_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-using ::absl::StreamFormat;
-using ::absl::StrFormat;
-
-constexpr int kBufSize = 1024;
-
-// C++-friendly version of struct inotify_event.
-struct Event {
- int32_t wd;
- uint32_t mask;
- uint32_t cookie;
- uint32_t len;
- std::string name;
-
- Event(uint32_t mask, int32_t wd, absl::string_view name, uint32_t cookie)
- : wd(wd),
- mask(mask),
- cookie(cookie),
- len(name.size()),
- name(std::string(name)) {}
- Event(uint32_t mask, int32_t wd, absl::string_view name)
- : Event(mask, wd, name, 0) {}
- Event(uint32_t mask, int32_t wd) : Event(mask, wd, "", 0) {}
- Event() : Event(0, 0, "", 0) {}
-};
-
-// Prints the symbolic name for a struct inotify_event's 'mask' field.
-std::string FlagString(uint32_t flags) {
- std::vector<std::string> names;
-
-#define EMIT(target) \
- if (flags & target) { \
- names.push_back(#target); \
- flags &= ~target; \
- }
-
- EMIT(IN_ACCESS);
- EMIT(IN_ATTRIB);
- EMIT(IN_CLOSE_WRITE);
- EMIT(IN_CLOSE_NOWRITE);
- EMIT(IN_CREATE);
- EMIT(IN_DELETE);
- EMIT(IN_DELETE_SELF);
- EMIT(IN_MODIFY);
- EMIT(IN_MOVE_SELF);
- EMIT(IN_MOVED_FROM);
- EMIT(IN_MOVED_TO);
- EMIT(IN_OPEN);
-
- EMIT(IN_DONT_FOLLOW);
- EMIT(IN_EXCL_UNLINK);
- EMIT(IN_ONESHOT);
- EMIT(IN_ONLYDIR);
-
- EMIT(IN_IGNORED);
- EMIT(IN_ISDIR);
- EMIT(IN_Q_OVERFLOW);
- EMIT(IN_UNMOUNT);
-
-#undef EMIT
-
- // If we have anything left over at the end, print it as a hex value.
- if (flags) {
- names.push_back(absl::StrCat("0x", absl::Hex(flags)));
- }
-
- return absl::StrJoin(names, "|");
-}
-
-std::string DumpEvent(const Event& event) {
- return StrFormat(
- "%s, wd=%d%s%s", FlagString(event.mask), event.wd,
- (event.len > 0) ? StrFormat(", name=%s", event.name) : "",
- (event.cookie > 0) ? StrFormat(", cookie=%ud", event.cookie) : "");
-}
-
-std::string DumpEvents(const std::vector<Event>& events, int indent_level) {
- std::stringstream ss;
- ss << StreamFormat("%d event%s:\n", events.size(),
- (events.size() > 1) ? "s" : "");
- int i = 0;
- for (const Event& ev : events) {
- ss << StreamFormat("%sevents[%d]: %s\n", std::string(indent_level, '\t'),
- i++, DumpEvent(ev));
- }
- return ss.str();
-}
-
-// A matcher which takes an expected list of events to match against another
-// list of inotify events, in order. This is similar to the ElementsAre matcher,
-// but displays more informative messages on mismatch.
-class EventsAreMatcher
- : public ::testing::MatcherInterface<std::vector<Event>> {
- public:
- explicit EventsAreMatcher(std::vector<Event> references)
- : references_(std::move(references)) {}
-
- bool MatchAndExplain(
- std::vector<Event> events,
- ::testing::MatchResultListener* const listener) const override {
- if (references_.size() != events.size()) {
- *listener << StreamFormat("\n\tCount mismatch, got %s",
- DumpEvents(events, 2));
- return false;
- }
-
- bool success = true;
- for (unsigned int i = 0; i < references_.size(); ++i) {
- const Event& reference = references_[i];
- const Event& target = events[i];
-
- if (target.mask != reference.mask || target.wd != reference.wd ||
- target.name != reference.name || target.cookie != reference.cookie) {
- *listener << StreamFormat("\n\tMismatch at index %d, want %s, got %s,",
- i, DumpEvent(reference), DumpEvent(target));
- success = false;
- }
- }
-
- if (!success) {
- *listener << StreamFormat("\n\tIn total of %s", DumpEvents(events, 2));
- }
- return success;
- }
-
- void DescribeTo(::std::ostream* const os) const override {
- *os << StreamFormat("%s", DumpEvents(references_, 1));
- }
-
- void DescribeNegationTo(::std::ostream* const os) const override {
- *os << StreamFormat("mismatch from %s", DumpEvents(references_, 1));
- }
-
- private:
- std::vector<Event> references_;
-};
-
-::testing::Matcher<std::vector<Event>> Are(std::vector<Event> events) {
- return MakeMatcher(new EventsAreMatcher(std::move(events)));
-}
-
-// Similar to the EventsAre matcher, but the order of events are ignored.
-class UnorderedEventsAreMatcher
- : public ::testing::MatcherInterface<std::vector<Event>> {
- public:
- explicit UnorderedEventsAreMatcher(std::vector<Event> references)
- : references_(std::move(references)) {}
-
- bool MatchAndExplain(
- std::vector<Event> events,
- ::testing::MatchResultListener* const listener) const override {
- if (references_.size() != events.size()) {
- *listener << StreamFormat("\n\tCount mismatch, got %s",
- DumpEvents(events, 2));
- return false;
- }
-
- std::vector<Event> unmatched(references_);
-
- for (const Event& candidate : events) {
- for (auto it = unmatched.begin(); it != unmatched.end();) {
- const Event& reference = *it;
- if (candidate.mask == reference.mask && candidate.wd == reference.wd &&
- candidate.name == reference.name &&
- candidate.cookie == reference.cookie) {
- it = unmatched.erase(it);
- break;
- } else {
- ++it;
- }
- }
- }
-
- // Anything left unmatched? If so, the matcher fails.
- if (!unmatched.empty()) {
- *listener << StreamFormat("\n\tFailed to match %s",
- DumpEvents(unmatched, 2));
- *listener << StreamFormat("\n\tIn total of %s", DumpEvents(events, 2));
- return false;
- }
-
- return true;
- }
-
- void DescribeTo(::std::ostream* const os) const override {
- *os << StreamFormat("unordered %s", DumpEvents(references_, 1));
- }
-
- void DescribeNegationTo(::std::ostream* const os) const override {
- *os << StreamFormat("mismatch from unordered %s",
- DumpEvents(references_, 1));
- }
-
- private:
- std::vector<Event> references_;
-};
-
-::testing::Matcher<std::vector<Event>> AreUnordered(std::vector<Event> events) {
- return MakeMatcher(new UnorderedEventsAreMatcher(std::move(events)));
-}
-
-// Reads events from an inotify fd until either EOF, or read returns EAGAIN.
-PosixErrorOr<std::vector<Event>> DrainEvents(int fd) {
- std::vector<Event> events;
- while (true) {
- int events_size = 0;
- if (ioctl(fd, FIONREAD, &events_size) < 0) {
- return PosixError(errno, "ioctl(FIONREAD) failed on inotify fd");
- }
- // Deliberately use a buffer that is larger than necessary, expecting to
- // only read events_size bytes.
- std::vector<char> buf(events_size + kBufSize, 0);
- const ssize_t readlen = read(fd, buf.data(), buf.size());
- MaybeSave();
- // Read error?
- if (readlen < 0) {
- if (errno == EAGAIN) {
- // If EAGAIN, no more events at the moment. Return what we have so far.
- return events;
- }
- // Some other read error. Return an error. Right now if we encounter this
- // after already reading some events, they get lost. However, we don't
- // expect to see any error, and the calling test will fail immediately if
- // we signal an error anyways, so this is acceptable.
- return PosixError(errno, "read() failed on inotify fd");
- }
- if (readlen < static_cast<int>(sizeof(struct inotify_event))) {
- // Impossibly short read.
- return PosixError(
- EIO,
- "read() didn't return enough data represent even a single event");
- }
- if (readlen != events_size) {
- return PosixError(EINVAL, absl::StrCat("read ", readlen,
- " bytes, expected ", events_size));
- }
- if (readlen == 0) {
- // EOF.
- return events;
- }
-
- // Normal read.
- const char* cursor = buf.data();
- while (cursor < (buf.data() + readlen)) {
- struct inotify_event event = {};
- memcpy(&event, cursor, sizeof(struct inotify_event));
-
- Event ev;
- ev.wd = event.wd;
- ev.mask = event.mask;
- ev.cookie = event.cookie;
- ev.len = event.len;
- if (event.len > 0) {
- TEST_CHECK(static_cast<int>(sizeof(struct inotify_event) + event.len) <=
- readlen);
- ev.name = std::string(cursor +
- offsetof(struct inotify_event, name)); // NOLINT
- // Name field should always be smaller than event.len, otherwise we have
- // a buffer overflow. The two sizes aren't equal because the string
- // constructor will stop at the first null byte, while event.name may be
- // padded up to event.len using multiple null bytes.
- TEST_CHECK(ev.name.size() <= event.len);
- }
-
- events.push_back(ev);
- cursor += sizeof(struct inotify_event) + event.len;
- }
- }
-}
-
-PosixErrorOr<FileDescriptor> InotifyInit1(int flags) {
- int fd;
- EXPECT_THAT(fd = inotify_init1(flags), SyscallSucceeds());
- if (fd < 0) {
- return PosixError(errno, "inotify_init1() failed");
- }
- return FileDescriptor(fd);
-}
-
-PosixErrorOr<int> InotifyAddWatch(int fd, const std::string& path,
- uint32_t mask) {
- int wd;
- EXPECT_THAT(wd = inotify_add_watch(fd, path.c_str(), mask),
- SyscallSucceeds());
- if (wd < 0) {
- return PosixError(errno, "inotify_add_watch() failed");
- }
- return wd;
-}
-
-TEST(Inotify, InotifyFdNotWritable) {
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(0));
- EXPECT_THAT(write(fd.get(), "x", 1), SyscallFailsWithErrno(EBADF));
-}
-
-TEST(Inotify, NonBlockingReadReturnsEagain) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- std::vector<char> buf(kBufSize, 0);
-
- // The read below should return fail with EAGAIN because there is no data to
- // read and we've specified IN_NONBLOCK. We're guaranteed that there is no
- // data to read because we haven't registered any watches yet.
- EXPECT_THAT(read(fd.get(), buf.data(), buf.size()),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST(Inotify, AddWatchOnInvalidFdFails) {
- // Garbage fd.
- EXPECT_THAT(inotify_add_watch(-1, "/tmp", IN_ALL_EVENTS),
- SyscallFailsWithErrno(EBADF));
- EXPECT_THAT(inotify_add_watch(1337, "/tmp", IN_ALL_EVENTS),
- SyscallFailsWithErrno(EBADF));
-
- // Non-inotify fds.
- EXPECT_THAT(inotify_add_watch(0, "/tmp", IN_ALL_EVENTS),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(inotify_add_watch(1, "/tmp", IN_ALL_EVENTS),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(inotify_add_watch(2, "/tmp", IN_ALL_EVENTS),
- SyscallFailsWithErrno(EINVAL));
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open("/tmp", O_RDONLY));
- EXPECT_THAT(inotify_add_watch(fd.get(), "/tmp", IN_ALL_EVENTS),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(Inotify, RemovingWatchGeneratesEvent) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- EXPECT_THAT(inotify_rm_watch(fd.get(), wd), SyscallSucceeds());
-
- // Read events, ensure the first event is IN_IGNORED.
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- EXPECT_THAT(events, Are({Event(IN_IGNORED, wd)}));
-}
-
-TEST(Inotify, CanDeleteFileAfterRemovingWatch) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- EXPECT_THAT(inotify_rm_watch(fd.get(), wd), SyscallSucceeds());
- file1.reset();
-}
-
-TEST(Inotify, CanRemoveWatchAfterDeletingFile) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- file1.reset();
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- EXPECT_THAT(events, Are({Event(IN_ATTRIB, wd), Event(IN_DELETE_SELF, wd),
- Event(IN_IGNORED, wd)}));
-
- EXPECT_THAT(inotify_rm_watch(fd.get(), wd), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(Inotify, DuplicateWatchRemovalFails) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- EXPECT_THAT(inotify_rm_watch(fd.get(), wd), SyscallSucceeds());
- EXPECT_THAT(inotify_rm_watch(fd.get(), wd), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(Inotify, ConcurrentFileDeletionAndWatchRemoval) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const std::string filename = NewTempAbsPathInDir(root.path());
-
- auto file_create_delete = [filename]() {
- const DisableSave ds; // Too expensive.
- for (int i = 0; i < 100; ++i) {
- FileDescriptor file_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_CREAT, S_IRUSR | S_IWUSR));
- file_fd.reset(); // Close before unlinking (although save is disabled).
- EXPECT_THAT(unlink(filename.c_str()), SyscallSucceeds());
- }
- };
-
- const int shared_fd = fd.get(); // We need to pass it to the thread.
- auto add_remove_watch = [shared_fd, filename]() {
- for (int i = 0; i < 100; ++i) {
- int wd = inotify_add_watch(shared_fd, filename.c_str(), IN_ALL_EVENTS);
- MaybeSave();
- if (wd != -1) {
- // Watch added successfully, try removal.
- if (inotify_rm_watch(shared_fd, wd)) {
- // If removal fails, the only acceptable reason is if the wd
- // is invalid, which will be the case if we try to remove
- // the watch after the file has been deleted.
- EXPECT_EQ(errno, EINVAL);
- }
- } else {
- // Add watch failed, this should only fail if the target file doesn't
- // exist.
- EXPECT_EQ(errno, ENOENT);
- }
- }
- };
-
- ScopedThread t1(file_create_delete);
- ScopedThread t2(add_remove_watch);
-}
-
-TEST(Inotify, DeletingChildGeneratesEvents) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const int root_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- const int file1_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- const std::string file1_path = file1.reset();
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(
- events,
- AreUnordered({Event(IN_ATTRIB, file1_wd), Event(IN_DELETE_SELF, file1_wd),
- Event(IN_IGNORED, file1_wd),
- Event(IN_DELETE, root_wd, Basename(file1_path))}));
-}
-
-TEST(Inotify, CreatingFileGeneratesEvents) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- // Create a new file in the directory.
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
-
- // The library function we use to create the new file opens it for writing to
- // create it and sets permissions on it, so we expect the three extra events.
- ASSERT_THAT(events, Are({Event(IN_CREATE, wd, Basename(file1.path())),
- Event(IN_OPEN, wd, Basename(file1.path())),
- Event(IN_CLOSE_WRITE, wd, Basename(file1.path())),
- Event(IN_ATTRIB, wd, Basename(file1.path()))}));
-}
-
-TEST(Inotify, ReadingFileGeneratesAccessEvent) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- root.path(), "some content", TempPath::kDefaultFileMode));
-
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY));
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- char buf;
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_ACCESS, wd, Basename(file1.path()))}));
-}
-
-TEST(Inotify, WritingFileGeneratesModifyEvent) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
-
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_WRONLY));
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- const std::string data = "some content";
- EXPECT_THAT(write(file1_fd.get(), data.c_str(), data.length()),
- SyscallSucceeds());
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_MODIFY, wd, Basename(file1.path()))}));
-}
-
-TEST(Inotify, WatchSetAfterOpenReportsCloseFdEvent) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
-
- FileDescriptor file1_fd_writable =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_WRONLY));
- FileDescriptor file1_fd_not_writable =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY));
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- file1_fd_writable.reset(); // Close file1_fd_writable.
- std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_CLOSE_WRITE, wd, Basename(file1.path()))}));
-
- file1_fd_not_writable.reset(); // Close file1_fd_not_writable.
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events,
- Are({Event(IN_CLOSE_NOWRITE, wd, Basename(file1.path()))}));
-}
-
-TEST(Inotify, ChildrenDeletionInWatchedDirGeneratesEvent) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
- TempPath dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path()));
-
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- const std::string file1_path = file1.reset();
- const std::string dir1_path = dir1.release();
- EXPECT_THAT(rmdir(dir1_path.c_str()), SyscallSucceeds());
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
-
- ASSERT_THAT(events,
- Are({Event(IN_DELETE, wd, Basename(file1_path)),
- Event(IN_DELETE | IN_ISDIR, wd, Basename(dir1_path))}));
-}
-
-TEST(Inotify, WatchTargetDeletionGeneratesEvent) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- EXPECT_THAT(rmdir(root.path().c_str()), SyscallSucceeds());
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_DELETE_SELF, wd), Event(IN_IGNORED, wd)}));
-}
-
-TEST(Inotify, MoveGeneratesEvents) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
-
- const TempPath dir1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path()));
- const TempPath dir2 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path()));
-
- const int root_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- const int dir1_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), dir1.path(), IN_ALL_EVENTS));
- const int dir2_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), dir2.path(), IN_ALL_EVENTS));
- // Test move from root -> root.
- std::string newpath = NewTempAbsPathInDir(root.path());
- std::string oldpath = file1.release();
- EXPECT_THAT(rename(oldpath.c_str(), newpath.c_str()), SyscallSucceeds());
- file1.reset(newpath);
- std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(
- events,
- Are({Event(IN_MOVED_FROM, root_wd, Basename(oldpath), events[0].cookie),
- Event(IN_MOVED_TO, root_wd, Basename(newpath), events[1].cookie)}));
- EXPECT_NE(events[0].cookie, 0);
- EXPECT_EQ(events[0].cookie, events[1].cookie);
- uint32_t last_cookie = events[0].cookie;
-
- // Test move from root -> root/dir1.
- newpath = NewTempAbsPathInDir(dir1.path());
- oldpath = file1.release();
- EXPECT_THAT(rename(oldpath.c_str(), newpath.c_str()), SyscallSucceeds());
- file1.reset(newpath);
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(
- events,
- Are({Event(IN_MOVED_FROM, root_wd, Basename(oldpath), events[0].cookie),
- Event(IN_MOVED_TO, dir1_wd, Basename(newpath), events[1].cookie)}));
- // Cookies should be distinct between distinct rename events.
- EXPECT_NE(events[0].cookie, last_cookie);
- EXPECT_EQ(events[0].cookie, events[1].cookie);
- last_cookie = events[0].cookie;
-
- // Test move from root/dir1 -> root/dir2.
- newpath = NewTempAbsPathInDir(dir2.path());
- oldpath = file1.release();
- EXPECT_THAT(rename(oldpath.c_str(), newpath.c_str()), SyscallSucceeds());
- file1.reset(newpath);
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(
- events,
- Are({Event(IN_MOVED_FROM, dir1_wd, Basename(oldpath), events[0].cookie),
- Event(IN_MOVED_TO, dir2_wd, Basename(newpath), events[1].cookie)}));
- EXPECT_NE(events[0].cookie, last_cookie);
- EXPECT_EQ(events[0].cookie, events[1].cookie);
- last_cookie = events[0].cookie;
-}
-
-TEST(Inotify, MoveWatchedTargetGeneratesEvents) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
-
- const int root_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- const int file1_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- const std::string newpath = NewTempAbsPathInDir(root.path());
- const std::string oldpath = file1.release();
- EXPECT_THAT(rename(oldpath.c_str(), newpath.c_str()), SyscallSucceeds());
- file1.reset(newpath);
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(
- events,
- Are({Event(IN_MOVED_FROM, root_wd, Basename(oldpath), events[0].cookie),
- Event(IN_MOVED_TO, root_wd, Basename(newpath), events[1].cookie),
- // Self move events do not have a cookie.
- Event(IN_MOVE_SELF, file1_wd)}));
- EXPECT_NE(events[0].cookie, 0);
- EXPECT_EQ(events[0].cookie, events[1].cookie);
-}
-
-TEST(Inotify, CoalesceEvents) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- root.path(), "some content", TempPath::kDefaultFileMode));
-
- FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY));
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- // Read the file a few times. This will would generate multiple IN_ACCESS
- // events but they should get coalesced to a single event.
- char buf;
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
-
- // Use the close event verify that we haven't simply left the additional
- // IN_ACCESS events unread.
- file1_fd.reset(); // Close file1_fd.
-
- const std::string file1_name = std::string(Basename(file1.path()));
- std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_ACCESS, wd, file1_name),
- Event(IN_CLOSE_NOWRITE, wd, file1_name)}));
-
- // Now let's try interleaving other events into a stream of repeated events.
- file1_fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDWR));
-
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
- EXPECT_THAT(write(file1_fd.get(), "x", 1), SyscallSucceeds());
- EXPECT_THAT(write(file1_fd.get(), "x", 1), SyscallSucceeds());
- EXPECT_THAT(write(file1_fd.get(), "x", 1), SyscallSucceeds());
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
-
- file1_fd.reset(); // Close the file.
-
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(
- events,
- Are({Event(IN_OPEN, wd, file1_name), Event(IN_ACCESS, wd, file1_name),
- Event(IN_MODIFY, wd, file1_name), Event(IN_ACCESS, wd, file1_name),
- Event(IN_CLOSE_WRITE, wd, file1_name)}));
-
- // Ensure events aren't coalesced if they are from different files.
- const TempPath file2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- root.path(), "some content", TempPath::kDefaultFileMode));
- // Discard events resulting from creation of file2.
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
-
- file1_fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY));
- FileDescriptor file2_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file2.path(), O_RDONLY));
-
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
- EXPECT_THAT(read(file2_fd.get(), &buf, 1), SyscallSucceeds());
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
-
- // Close both files.
- file1_fd.reset();
- file2_fd.reset();
-
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- const std::string file2_name = std::string(Basename(file2.path()));
- ASSERT_THAT(
- events,
- Are({Event(IN_OPEN, wd, file1_name), Event(IN_OPEN, wd, file2_name),
- Event(IN_ACCESS, wd, file1_name), Event(IN_ACCESS, wd, file2_name),
- Event(IN_ACCESS, wd, file1_name),
- Event(IN_CLOSE_NOWRITE, wd, file1_name),
- Event(IN_CLOSE_NOWRITE, wd, file2_name)}));
-}
-
-TEST(Inotify, ClosingInotifyFdWithoutRemovingWatchesWorks) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY));
-
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
- // Note: The check on close will happen in FileDescriptor::~FileDescriptor().
-}
-
-TEST(Inotify, NestedWatches) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- root.path(), "some content", TempPath::kDefaultFileMode));
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY));
-
- const int root_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- const int file1_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- // Read from file1. This should generate an event for both watches.
- char buf;
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_ACCESS, root_wd, Basename(file1.path())),
- Event(IN_ACCESS, file1_wd)}));
-}
-
-TEST(Inotify, ConcurrentThreadsGeneratingEvents) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- std::vector<TempPath> files;
- files.reserve(10);
- for (int i = 0; i < 10; i++) {
- files.emplace_back(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- root.path(), "some content", TempPath::kDefaultFileMode)));
- }
-
- auto test_thread = [&files]() {
- uint32_t seed = time(nullptr);
- for (int i = 0; i < 20; i++) {
- const TempPath& file = files[rand_r(&seed) % files.size()];
- const FileDescriptor file_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_WRONLY));
- TEST_PCHECK(write(file_fd.get(), "x", 1) == 1);
- }
- };
-
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- std::list<ScopedThread> threads;
- for (int i = 0; i < 3; i++) {
- threads.emplace_back(test_thread);
- }
- for (auto& t : threads) {
- t.Join();
- }
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- // 3 threads doing 20 iterations, 3 events per iteration (open, write,
- // close). However, some events may be coalesced, and we can't reliably
- // predict how they'll be coalesced since the test threads aren't
- // synchronized. We can only check that we aren't getting unexpected events.
- for (const Event& ev : events) {
- EXPECT_NE(ev.mask & (IN_OPEN | IN_MODIFY | IN_CLOSE_WRITE), 0);
- }
-}
-
-TEST(Inotify, ReadWithTooSmallBufferFails) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- // Open the file to queue an event. This event will not have a filename, so
- // reading from the inotify fd should return sizeof(struct inotify_event)
- // bytes of data.
- FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY));
- std::vector<char> buf(kBufSize, 0);
- ssize_t readlen;
-
- // Try a buffer too small to hold any potential event. This is rejected
- // outright without the event being dequeued.
- EXPECT_THAT(read(fd.get(), buf.data(), sizeof(struct inotify_event) - 1),
- SyscallFailsWithErrno(EINVAL));
- // Try a buffer just large enough. This should succeeed.
- EXPECT_THAT(
- readlen = read(fd.get(), buf.data(), sizeof(struct inotify_event)),
- SyscallSucceeds());
- EXPECT_EQ(readlen, sizeof(struct inotify_event));
- // Event queue is now empty, the next read should return EAGAIN.
- EXPECT_THAT(read(fd.get(), buf.data(), sizeof(struct inotify_event)),
- SyscallFailsWithErrno(EAGAIN));
-
- // Now put a watch on the directory, so that generated events contain a name.
- EXPECT_THAT(inotify_rm_watch(fd.get(), wd), SyscallSucceeds());
-
- // Drain the event generated from the watch removal.
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
-
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- file1_fd.reset(); // Close file to generate an event.
-
- // Try a buffer too small to hold any event and one too small to hold an event
- // with a name. These should both fail without consuming the event.
- EXPECT_THAT(read(fd.get(), buf.data(), sizeof(struct inotify_event) - 1),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(read(fd.get(), buf.data(), sizeof(struct inotify_event)),
- SyscallFailsWithErrno(EINVAL));
- // Now try with a large enough buffer. This should return the one event.
- EXPECT_THAT(readlen = read(fd.get(), buf.data(), buf.size()),
- SyscallSucceeds());
- EXPECT_GE(readlen,
- sizeof(struct inotify_event) + Basename(file1.path()).size());
- // With the single event read, the queue should once again be empty.
- EXPECT_THAT(read(fd.get(), buf.data(), sizeof(struct inotify_event)),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST(Inotify, BlockingReadOnInotifyFd) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(0));
- const TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- root.path(), "some content", TempPath::kDefaultFileMode));
-
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY));
-
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- // Spawn a thread performing a blocking read for new events on the inotify fd.
- std::vector<char> buf(kBufSize, 0);
- const int shared_fd = fd.get(); // The thread needs it.
- ScopedThread t([shared_fd, &buf]() {
- ssize_t readlen;
- EXPECT_THAT(readlen = read(shared_fd, buf.data(), buf.size()),
- SyscallSucceeds());
- });
-
- // Perform a read on the watched file, which should generate an IN_ACCESS
- // event, unblocking the event_reader thread.
- char c;
- EXPECT_THAT(read(file1_fd.get(), &c, 1), SyscallSucceeds());
-
- // Wait for the thread to read the event and exit.
- t.Join();
-
- // Make sure the event we got back is sane.
- uint32_t event_mask;
- memcpy(&event_mask, buf.data() + offsetof(struct inotify_event, mask),
- sizeof(event_mask));
- EXPECT_EQ(event_mask, IN_ACCESS);
-}
-
-TEST(Inotify, WatchOnRelativePath) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- root.path(), "some content", TempPath::kDefaultFileMode));
-
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY));
-
- // Change working directory to root.
- const char* old_working_dir = get_current_dir_name();
- EXPECT_THAT(chdir(root.path().c_str()), SyscallSucceeds());
-
- // Add a watch on file1 with a relative path.
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch(
- fd.get(), std::string(Basename(file1.path())), IN_ALL_EVENTS));
-
- // Perform a read on file1, this should generate an IN_ACCESS event.
- char c;
- EXPECT_THAT(read(file1_fd.get(), &c, 1), SyscallSucceeds());
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- EXPECT_THAT(events, Are({Event(IN_ACCESS, wd)}));
-
- // Explicitly reset the working directory so that we don't continue to
- // reference "root". Once the test ends, "root" will get unlinked. If we
- // continue to hold a reference, random save/restore tests can fail if a save
- // is triggered after "root" is unlinked; we can't save deleted fs objects
- // with active references.
- EXPECT_THAT(chdir(old_working_dir), SyscallSucceeds());
-}
-
-TEST(Inotify, ZeroLengthReadWriteDoesNotGenerateEvent) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const char kContent[] = "some content";
- TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- root.path(), kContent, TempPath::kDefaultFileMode));
- const int kContentSize = sizeof(kContent) - 1;
-
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDWR));
-
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- std::vector<char> buf(kContentSize, 0);
- // Read all available data.
- ssize_t readlen;
- EXPECT_THAT(readlen = read(file1_fd.get(), buf.data(), kContentSize),
- SyscallSucceeds());
- EXPECT_EQ(readlen, kContentSize);
- // Drain all events and make sure we got the IN_ACCESS for the read.
- std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- EXPECT_THAT(events, Are({Event(IN_ACCESS, wd, Basename(file1.path()))}));
-
- // Now try read again. This should be a 0-length read, since we're at EOF.
- char c;
- EXPECT_THAT(readlen = read(file1_fd.get(), &c, 1), SyscallSucceeds());
- EXPECT_EQ(readlen, 0);
- // We should have no new events.
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- EXPECT_TRUE(events.empty());
-
- // Try issuing a zero-length read.
- EXPECT_THAT(readlen = read(file1_fd.get(), &c, 0), SyscallSucceeds());
- EXPECT_EQ(readlen, 0);
- // We should have no new events.
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- EXPECT_TRUE(events.empty());
-
- // Try issuing a zero-length write.
- ssize_t writelen;
- EXPECT_THAT(writelen = write(file1_fd.get(), &c, 0), SyscallSucceeds());
- EXPECT_EQ(writelen, 0);
- // We should have no new events.
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- EXPECT_TRUE(events.empty());
-}
-
-TEST(Inotify, ChmodGeneratesAttribEvent_NoRandomSave) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
-
- const FileDescriptor root_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(root.path(), O_RDONLY));
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDWR));
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const int root_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- const int file1_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- auto verify_chmod_events = [&]() {
- std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_ATTRIB, root_wd, Basename(file1.path())),
- Event(IN_ATTRIB, file1_wd)}));
- };
-
- // Don't do cooperative S/R tests for any of the {f}chmod* syscalls below, the
- // test will always fail because nodes cannot be saved when they have stricter
- // permissions than the original host node.
- const DisableSave ds;
-
- // Chmod.
- ASSERT_THAT(chmod(file1.path().c_str(), S_IWGRP), SyscallSucceeds());
- verify_chmod_events();
-
- // Fchmod.
- ASSERT_THAT(fchmod(file1_fd.get(), S_IRGRP | S_IWGRP), SyscallSucceeds());
- verify_chmod_events();
-
- // Fchmodat.
- const std::string file1_basename = std::string(Basename(file1.path()));
- ASSERT_THAT(fchmodat(root_fd.get(), file1_basename.c_str(), S_IWGRP, 0),
- SyscallSucceeds());
- verify_chmod_events();
-}
-
-TEST(Inotify, TruncateGeneratesModifyEvent) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDWR));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const int root_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- const int file1_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- auto verify_truncate_events = [&]() {
- std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_MODIFY, root_wd, Basename(file1.path())),
- Event(IN_MODIFY, file1_wd)}));
- };
-
- // Truncate.
- EXPECT_THAT(truncate(file1.path().c_str(), 4096), SyscallSucceeds());
- verify_truncate_events();
-
- // Ftruncate.
- EXPECT_THAT(ftruncate(file1_fd.get(), 8192), SyscallSucceeds());
- verify_truncate_events();
-
- // No events if truncate fails.
- EXPECT_THAT(ftruncate(file1_fd.get(), -1), SyscallFailsWithErrno(EINVAL));
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({}));
-}
-
-TEST(Inotify, GetdentsGeneratesAccessEvent) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- // This internally calls getdents(2). We also expect to see an open/close
- // event for the dirfd.
- ASSERT_NO_ERRNO_AND_VALUE(ListDir(root.path(), false));
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
-
- // Linux only seems to generate access events on getdents() on some
- // calls. Allow the test to pass even if it isn't generated. gVisor will
- // always generate the IN_ACCESS event so the test will at least ensure gVisor
- // behaves reasonably.
- int i = 0;
- EXPECT_EQ(events[i].mask, IN_OPEN | IN_ISDIR);
- ++i;
- if (IsRunningOnGvisor()) {
- EXPECT_EQ(events[i].mask, IN_ACCESS | IN_ISDIR);
- ++i;
- } else {
- if (events[i].mask == (IN_ACCESS | IN_ISDIR)) {
- // Skip over the IN_ACCESS event on Linux, it only shows up some of the
- // time so we can't assert its existence.
- ++i;
- }
- }
- EXPECT_EQ(events[i].mask, IN_CLOSE_NOWRITE | IN_ISDIR);
-}
-
-TEST(Inotify, MknodGeneratesCreateEvent) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- const TempPath file1(root.path() + "/file1");
- const int rc = mknod(file1.path().c_str(), S_IFREG, 0);
- // mknod(2) is only supported on tmpfs in the sandbox.
- SKIP_IF(IsRunningOnGvisor() && rc != 0);
- ASSERT_THAT(rc, SyscallSucceeds());
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_CREATE, wd, Basename(file1.path()))}));
-}
-
-TEST(Inotify, SymlinkGeneratesCreateEvent) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
- const TempPath link1(NewTempAbsPathInDir(root.path()));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const int root_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- ASSERT_THAT(symlink(file1.path().c_str(), link1.path().c_str()),
- SyscallSucceeds());
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
-
- ASSERT_THAT(events, Are({Event(IN_CREATE, root_wd, Basename(link1.path()))}));
-}
-
-TEST(Inotify, LinkGeneratesAttribAndCreateEvents) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
- const TempPath link1(root.path() + "/link1");
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const int root_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- const int file1_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- const int rc = link(file1.path().c_str(), link1.path().c_str());
- // link(2) is only supported on tmpfs in the sandbox.
- SKIP_IF(IsRunningOnGvisor() && rc != 0 &&
- (errno == EPERM || errno == ENOENT));
- ASSERT_THAT(rc, SyscallSucceeds());
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_ATTRIB, file1_wd),
- Event(IN_CREATE, root_wd, Basename(link1.path()))}));
-}
-
-TEST(Inotify, UtimesGeneratesAttribEvent) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
-
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDWR));
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- struct timeval times[2] = {{1, 0}, {2, 0}};
- EXPECT_THAT(futimes(file1_fd.get(), times), SyscallSucceeds());
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_ATTRIB, wd, Basename(file1.path()))}));
-}
-
-TEST(Inotify, HardlinksReuseSameWatch) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
- TempPath link1(root.path() + "/link1");
- const int rc = link(file1.path().c_str(), link1.path().c_str());
- // link(2) is only supported on tmpfs in the sandbox.
- SKIP_IF(IsRunningOnGvisor() && rc != 0 &&
- (errno == EPERM || errno == ENOENT));
- ASSERT_THAT(rc, SyscallSucceeds());
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const int root_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- const int file1_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
- const int link1_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), link1.path(), IN_ALL_EVENTS));
-
- // The watch descriptors for watches on different links to the same file
- // should be identical.
- EXPECT_NE(root_wd, file1_wd);
- EXPECT_EQ(file1_wd, link1_wd);
-
- FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_WRONLY));
-
- std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events,
- AreUnordered({Event(IN_OPEN, root_wd, Basename(file1.path())),
- Event(IN_OPEN, file1_wd)}));
-
- // For the next step, we want to ensure all fds to the file are closed. Do
- // that now and drain the resulting events.
- file1_fd.reset();
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events,
- Are({Event(IN_CLOSE_WRITE, root_wd, Basename(file1.path())),
- Event(IN_CLOSE_WRITE, file1_wd)}));
-
- // Try removing the link and let's see what events show up. Note that after
- // this, we still have a link to the file so the watch shouldn't be
- // automatically removed.
- const std::string link1_path = link1.reset();
-
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_ATTRIB, link1_wd),
- Event(IN_DELETE, root_wd, Basename(link1_path))}));
-
- // Now remove the other link. Since this is the last link to the file, the
- // watch should be automatically removed.
- const std::string file1_path = file1.reset();
-
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(
- events,
- AreUnordered({Event(IN_ATTRIB, file1_wd), Event(IN_DELETE_SELF, file1_wd),
- Event(IN_IGNORED, file1_wd),
- Event(IN_DELETE, root_wd, Basename(file1_path))}));
-}
-
-TEST(Inotify, MkdirGeneratesCreateEventWithDirFlag) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const int root_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- const TempPath dir1(NewTempAbsPathInDir(root.path()));
- ASSERT_THAT(mkdir(dir1.path().c_str(), 0777), SyscallSucceeds());
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(
- events,
- Are({Event(IN_CREATE | IN_ISDIR, root_wd, Basename(dir1.path()))}));
-}
-
-TEST(Inotify, MultipleInotifyInstancesAndWatchesAllGetEvents) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
-
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_WRONLY));
- constexpr int kNumFds = 30;
- std::vector<FileDescriptor> inotify_fds;
-
- for (int i = 0; i < kNumFds; ++i) {
- const DisableSave ds; // Too expensive.
- inotify_fds.emplace_back(
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)));
- const FileDescriptor& fd = inotify_fds[inotify_fds.size() - 1]; // Back.
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
- }
-
- const std::string data = "some content";
- EXPECT_THAT(write(file1_fd.get(), data.c_str(), data.length()),
- SyscallSucceeds());
-
- for (const FileDescriptor& fd : inotify_fds) {
- const DisableSave ds; // Too expensive.
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- if (events.size() >= 2) {
- EXPECT_EQ(events[0].mask, IN_MODIFY);
- EXPECT_EQ(events[0].wd, 1);
- EXPECT_EQ(events[0].name, Basename(file1.path()));
- EXPECT_EQ(events[1].mask, IN_MODIFY);
- EXPECT_EQ(events[1].wd, 2);
- EXPECT_EQ(events[1].name, "");
- }
- }
-}
-
-TEST(Inotify, EventsGoUpAtMostOneLevel) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath dir1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path()));
- TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path()));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- const int dir1_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), dir1.path(), IN_ALL_EVENTS));
-
- const std::string file1_path = file1.reset();
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_DELETE, dir1_wd, Basename(file1_path))}));
-}
-
-TEST(Inotify, DuplicateWatchReturnsSameWatchDescriptor) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const int wd1 = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
- const int wd2 = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- EXPECT_EQ(wd1, wd2);
-
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_WRONLY));
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- // The watch shouldn't be duplicated, we only expect one event.
- ASSERT_THAT(events, Are({Event(IN_OPEN, wd1)}));
-}
-
-TEST(Inotify, UnmatchedEventsAreDiscarded) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch(fd.get(), file1.path(), IN_ACCESS));
-
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_WRONLY));
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- // We only asked for access events, the open event should be discarded.
- ASSERT_THAT(events, Are({}));
-}
-
-TEST(Inotify, AddWatchWithInvalidEventMaskFails) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- EXPECT_THAT(inotify_add_watch(fd.get(), root.path().c_str(), 0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(Inotify, AddWatchOnInvalidPathFails) {
- const TempPath nonexistent(NewTempAbsPath());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- // Non-existent path.
- EXPECT_THAT(
- inotify_add_watch(fd.get(), nonexistent.path().c_str(), IN_CREATE),
- SyscallFailsWithErrno(ENOENT));
-
- // Garbage path pointer.
- EXPECT_THAT(inotify_add_watch(fd.get(), nullptr, IN_CREATE),
- SyscallFailsWithErrno(EFAULT));
-}
-
-TEST(Inotify, InOnlyDirFlagRespected) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- EXPECT_THAT(
- inotify_add_watch(fd.get(), root.path().c_str(), IN_ACCESS | IN_ONLYDIR),
- SyscallSucceeds());
-
- EXPECT_THAT(
- inotify_add_watch(fd.get(), file1.path().c_str(), IN_ACCESS | IN_ONLYDIR),
- SyscallFailsWithErrno(ENOTDIR));
-}
-
-TEST(Inotify, MaskAddMergesWithExistingEventMask) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_WRONLY));
-
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_OPEN | IN_CLOSE_WRITE));
-
- const std::string data = "some content";
- EXPECT_THAT(write(file1_fd.get(), data.c_str(), data.length()),
- SyscallSucceeds());
-
- // We shouldn't get any events, since IN_MODIFY wasn't in the event mask.
- std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({}));
-
- // Add IN_MODIFY to event mask.
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_MODIFY | IN_MASK_ADD));
-
- EXPECT_THAT(write(file1_fd.get(), data.c_str(), data.length()),
- SyscallSucceeds());
-
- // This time we should get the modify event.
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_MODIFY, wd)}));
-
- // Now close the fd. If the modify event was added to the event mask rather
- // than replacing the event mask we won't get the close event.
- file1_fd.reset();
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_CLOSE_WRITE, wd)}));
-}
-
-// Test that control events bits are not considered when checking event mask.
-TEST(Inotify, ControlEvents) {
- const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), dir.path(), IN_ACCESS));
-
- // Check that events in the mask are dispatched and that control bits are
- // part of the event mask.
- std::vector<std::string> files =
- ASSERT_NO_ERRNO_AND_VALUE(ListDir(dir.path(), false));
- ASSERT_EQ(files.size(), 2);
-
- const std::vector<Event> events1 =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events1, Are({Event(IN_ACCESS | IN_ISDIR, wd)}));
-
- // Check that events not in the mask are discarded.
- const FileDescriptor dir_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_RDONLY | O_DIRECTORY));
-
- const std::vector<Event> events2 =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events2, Are({}));
-}
-
-// Regression test to ensure epoll and directory access doesn't deadlock.
-TEST(Inotify, EpollNoDeadlock) {
- const DisableSave ds; // Too many syscalls.
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- // Create lots of directories and watch all of them.
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- std::vector<TempPath> children;
- for (size_t i = 0; i < 1000; ++i) {
- auto child = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path()));
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), child.path(), IN_ACCESS));
- children.emplace_back(std::move(child));
- }
-
- // Run epoll_wait constantly in a separate thread.
- std::atomic<bool> done(false);
- ScopedThread th([&fd, &done] {
- for (auto start = absl::Now(); absl::Now() - start < absl::Seconds(5);) {
- FileDescriptor epoll_fd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
- ASSERT_NO_ERRNO(RegisterEpollFD(epoll_fd.get(), fd.get(),
- EPOLLIN | EPOLLOUT | EPOLLET, 0));
- struct epoll_event result[1];
- EXPECT_THAT(RetryEINTR(epoll_wait)(epoll_fd.get(), result, 1, -1),
- SyscallSucceedsWithValue(1));
-
- sched_yield();
- }
- done = true;
- });
-
- // While epoll thread is running, constantly access all directories to
- // generate inotify events.
- while (!done) {
- std::vector<std::string> files =
- ASSERT_NO_ERRNO_AND_VALUE(ListDir(root.path(), false));
- ASSERT_EQ(files.size(), 1002);
- for (const auto& child : files) {
- if (child == "." || child == "..") {
- continue;
- }
- ASSERT_NO_ERRNO_AND_VALUE(ListDir(JoinPath(root.path(), child), false));
- }
- sched_yield();
- }
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/ioctl.cc b/test/syscalls/linux/ioctl.cc
deleted file mode 100644
index 4948a76f0..000000000
--- a/test/syscalls/linux/ioctl.cc
+++ /dev/null
@@ -1,406 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <arpa/inet.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <net/if.h>
-#include <netdb.h>
-#include <signal.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/ip_socket_test_util.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/signal_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-bool CheckNonBlocking(int fd) {
- int ret = fcntl(fd, F_GETFL, 0);
- TEST_CHECK(ret != -1);
- return (ret & O_NONBLOCK) == O_NONBLOCK;
-}
-
-bool CheckCloExec(int fd) {
- int ret = fcntl(fd, F_GETFD, 0);
- TEST_CHECK(ret != -1);
- return (ret & FD_CLOEXEC) == FD_CLOEXEC;
-}
-
-class IoctlTest : public ::testing::Test {
- protected:
- void SetUp() override {
- ASSERT_THAT(fd_ = open("/dev/null", O_RDONLY), SyscallSucceeds());
- }
-
- void TearDown() override {
- if (fd_ >= 0) {
- ASSERT_THAT(close(fd_), SyscallSucceeds());
- fd_ = -1;
- }
- }
-
- int fd() const { return fd_; }
-
- private:
- int fd_ = -1;
-};
-
-TEST_F(IoctlTest, BadFileDescriptor) {
- EXPECT_THAT(ioctl(-1 /* fd */, 0), SyscallFailsWithErrno(EBADF));
-}
-
-TEST_F(IoctlTest, InvalidControlNumber) {
- EXPECT_THAT(ioctl(STDOUT_FILENO, 0), SyscallFailsWithErrno(ENOTTY));
-}
-
-TEST_F(IoctlTest, FIONBIOSucceeds) {
- EXPECT_FALSE(CheckNonBlocking(fd()));
- int set = 1;
- EXPECT_THAT(ioctl(fd(), FIONBIO, &set), SyscallSucceeds());
- EXPECT_TRUE(CheckNonBlocking(fd()));
- set = 0;
- EXPECT_THAT(ioctl(fd(), FIONBIO, &set), SyscallSucceeds());
- EXPECT_FALSE(CheckNonBlocking(fd()));
-}
-
-TEST_F(IoctlTest, FIONBIOFails) {
- EXPECT_THAT(ioctl(fd(), FIONBIO, nullptr), SyscallFailsWithErrno(EFAULT));
-}
-
-TEST_F(IoctlTest, FIONCLEXSucceeds) {
- EXPECT_THAT(ioctl(fd(), FIONCLEX), SyscallSucceeds());
- EXPECT_FALSE(CheckCloExec(fd()));
-}
-
-TEST_F(IoctlTest, FIOCLEXSucceeds) {
- EXPECT_THAT(ioctl(fd(), FIOCLEX), SyscallSucceeds());
- EXPECT_TRUE(CheckCloExec(fd()));
-}
-
-TEST_F(IoctlTest, FIOASYNCFails) {
- EXPECT_THAT(ioctl(fd(), FIOASYNC, nullptr), SyscallFailsWithErrno(EFAULT));
-}
-
-TEST_F(IoctlTest, FIOASYNCSucceeds) {
- // Not all FDs support FIOASYNC.
- const FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
-
- int before = -1;
- ASSERT_THAT(before = fcntl(s.get(), F_GETFL), SyscallSucceeds());
-
- int set = 1;
- EXPECT_THAT(ioctl(s.get(), FIOASYNC, &set), SyscallSucceeds());
-
- int after_set = -1;
- ASSERT_THAT(after_set = fcntl(s.get(), F_GETFL), SyscallSucceeds());
- EXPECT_EQ(after_set, before | O_ASYNC) << "before was " << before;
-
- set = 0;
- EXPECT_THAT(ioctl(s.get(), FIOASYNC, &set), SyscallSucceeds());
-
- ASSERT_THAT(fcntl(s.get(), F_GETFL), SyscallSucceedsWithValue(before));
-}
-
-/* Count of the number of SIGIOs handled. */
-static volatile int io_received = 0;
-
-void inc_io_handler(int sig, siginfo_t* siginfo, void* arg) { io_received++; }
-
-TEST_F(IoctlTest, FIOASYNCNoTarget) {
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- // Count SIGIOs received.
- io_received = 0;
- struct sigaction sa;
- sa.sa_sigaction = inc_io_handler;
- sigfillset(&sa.sa_mask);
- sa.sa_flags = SA_RESTART;
- auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGIO, sa));
-
- // Actually allow SIGIO delivery.
- auto mask_cleanup =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGIO));
-
- int set = 1;
- EXPECT_THAT(ioctl(pair->second_fd(), FIOASYNC, &set), SyscallSucceeds());
-
- constexpr char kData[] = "abc";
- ASSERT_THAT(WriteFd(pair->first_fd(), kData, sizeof(kData)),
- SyscallSucceedsWithValue(sizeof(kData)));
-
- EXPECT_EQ(io_received, 0);
-}
-
-TEST_F(IoctlTest, FIOASYNCSelfTarget) {
- // FIXME(b/120624367): gVisor erroneously sends SIGIO on close(2), which would
- // kill the test when pair goes out of scope. Temporarily ignore SIGIO so that
- // that the close signal is ignored.
- struct sigaction sa;
- sa.sa_handler = SIG_IGN;
- auto early_sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGIO, sa));
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- // Count SIGIOs received.
- io_received = 0;
- sa.sa_sigaction = inc_io_handler;
- sigfillset(&sa.sa_mask);
- sa.sa_flags = SA_RESTART;
- auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGIO, sa));
-
- // Actually allow SIGIO delivery.
- auto mask_cleanup =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGIO));
-
- int set = 1;
- EXPECT_THAT(ioctl(pair->second_fd(), FIOASYNC, &set), SyscallSucceeds());
-
- pid_t pid = getpid();
- EXPECT_THAT(ioctl(pair->second_fd(), FIOSETOWN, &pid), SyscallSucceeds());
-
- constexpr char kData[] = "abc";
- ASSERT_THAT(WriteFd(pair->first_fd(), kData, sizeof(kData)),
- SyscallSucceedsWithValue(sizeof(kData)));
-
- EXPECT_EQ(io_received, 1);
-}
-
-// Equivalent to FIOASYNCSelfTarget except that FIOSETOWN is called before
-// FIOASYNC.
-TEST_F(IoctlTest, FIOASYNCSelfTarget2) {
- // FIXME(b/120624367): gVisor erroneously sends SIGIO on close(2), which would
- // kill the test when pair goes out of scope. Temporarily ignore SIGIO so that
- // that the close signal is ignored.
- struct sigaction sa;
- sa.sa_handler = SIG_IGN;
- auto early_sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGIO, sa));
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- // Count SIGIOs received.
- io_received = 0;
- sa.sa_sigaction = inc_io_handler;
- sigfillset(&sa.sa_mask);
- sa.sa_flags = SA_RESTART;
- auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGIO, sa));
-
- // Actually allow SIGIO delivery.
- auto mask_cleanup =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGIO));
-
- pid_t pid = getpid();
- EXPECT_THAT(ioctl(pair->second_fd(), FIOSETOWN, &pid), SyscallSucceeds());
-
- int set = 1;
- EXPECT_THAT(ioctl(pair->second_fd(), FIOASYNC, &set), SyscallSucceeds());
-
- constexpr char kData[] = "abc";
- ASSERT_THAT(WriteFd(pair->first_fd(), kData, sizeof(kData)),
- SyscallSucceedsWithValue(sizeof(kData)));
-
- EXPECT_EQ(io_received, 1);
-}
-
-// Check that closing an FD does not result in an event.
-TEST_F(IoctlTest, FIOASYNCSelfTargetClose) {
- // Count SIGIOs received.
- struct sigaction sa;
- io_received = 0;
- sa.sa_sigaction = inc_io_handler;
- sigfillset(&sa.sa_mask);
- sa.sa_flags = SA_RESTART;
- auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGIO, sa));
-
- // Actually allow SIGIO delivery.
- auto mask_cleanup =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGIO));
-
- for (int i = 0; i < 2; i++) {
- auto pair = ASSERT_NO_ERRNO_AND_VALUE(
- UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- pid_t pid = getpid();
- EXPECT_THAT(ioctl(pair->second_fd(), FIOSETOWN, &pid), SyscallSucceeds());
-
- int set = 1;
- EXPECT_THAT(ioctl(pair->second_fd(), FIOASYNC, &set), SyscallSucceeds());
- }
-
- // FIXME(b/120624367): gVisor erroneously sends SIGIO on close.
- SKIP_IF(IsRunningOnGvisor());
-
- EXPECT_EQ(io_received, 0);
-}
-
-TEST_F(IoctlTest, FIOASYNCInvalidPID) {
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
- int set = 1;
- ASSERT_THAT(ioctl(pair->second_fd(), FIOASYNC, &set), SyscallSucceeds());
- pid_t pid = INT_MAX;
- // This succeeds (with behavior equivalent to a pid of 0) in Linux prior to
- // f73127356f34 "fs/fcntl: return -ESRCH in f_setown when pid/pgid can't be
- // found", and fails with EPERM after that commit.
- EXPECT_THAT(ioctl(pair->second_fd(), FIOSETOWN, &pid),
- AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(ESRCH)));
-}
-
-TEST_F(IoctlTest, FIOASYNCUnsetTarget) {
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- // Count SIGIOs received.
- io_received = 0;
- struct sigaction sa;
- sa.sa_sigaction = inc_io_handler;
- sigfillset(&sa.sa_mask);
- sa.sa_flags = SA_RESTART;
- auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGIO, sa));
-
- // Actually allow SIGIO delivery.
- auto mask_cleanup =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGIO));
-
- int set = 1;
- EXPECT_THAT(ioctl(pair->second_fd(), FIOASYNC, &set), SyscallSucceeds());
-
- pid_t pid = getpid();
- EXPECT_THAT(ioctl(pair->second_fd(), FIOSETOWN, &pid), SyscallSucceeds());
-
- // Passing a PID of 0 unsets the target.
- pid = 0;
- EXPECT_THAT(ioctl(pair->second_fd(), FIOSETOWN, &pid), SyscallSucceeds());
-
- constexpr char kData[] = "abc";
- ASSERT_THAT(WriteFd(pair->first_fd(), kData, sizeof(kData)),
- SyscallSucceedsWithValue(sizeof(kData)));
-
- EXPECT_EQ(io_received, 0);
-}
-
-using IoctlTestSIOCGIFCONF = SimpleSocketTest;
-
-TEST_P(IoctlTestSIOCGIFCONF, ValidateNoArrayGetsLength) {
- auto fd = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- // Validate that no array can be used to get the length required.
- struct ifconf ifconf = {};
- ASSERT_THAT(ioctl(fd->get(), SIOCGIFCONF, &ifconf), SyscallSucceeds());
- ASSERT_GT(ifconf.ifc_len, 0);
-}
-
-// This test validates that we will only return a partial array list and not
-// partial ifrreq structs.
-TEST_P(IoctlTestSIOCGIFCONF, ValidateNoPartialIfrsReturned) {
- auto fd = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- struct ifreq ifr = {};
- struct ifconf ifconf = {};
- ifconf.ifc_len = sizeof(ifr) - 1; // One byte too few.
- ifconf.ifc_ifcu.ifcu_req = &ifr;
-
- ASSERT_THAT(ioctl(fd->get(), SIOCGIFCONF, &ifconf), SyscallSucceeds());
- ASSERT_EQ(ifconf.ifc_len, 0);
- ASSERT_EQ(ifr.ifr_name[0], '\0'); // Nothing is returned.
-
- ifconf.ifc_len = sizeof(ifreq);
- ASSERT_THAT(ioctl(fd->get(), SIOCGIFCONF, &ifconf), SyscallSucceeds());
- ASSERT_GT(ifconf.ifc_len, 0);
- ASSERT_NE(ifr.ifr_name[0], '\0'); // An interface can now be returned.
-}
-
-TEST_P(IoctlTestSIOCGIFCONF, ValidateLoopbackIsPresent) {
- auto fd = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- struct ifconf ifconf = {};
- struct ifreq ifr[10] = {}; // Storage for up to 10 interfaces.
-
- ifconf.ifc_req = ifr;
- ifconf.ifc_len = sizeof(ifr);
-
- ASSERT_THAT(ioctl(fd->get(), SIOCGIFCONF, &ifconf), SyscallSucceeds());
- size_t num_if = ifconf.ifc_len / sizeof(struct ifreq);
-
- // We should have at least one interface.
- ASSERT_GE(num_if, 1);
-
- // One of the interfaces should be a loopback.
- bool found_loopback = false;
- for (size_t i = 0; i < num_if; ++i) {
- if (strcmp(ifr[i].ifr_name, "lo") == 0) {
- // SIOCGIFCONF returns the ipv4 address of the interface, let's check it.
- ASSERT_EQ(ifr[i].ifr_addr.sa_family, AF_INET);
-
- // Validate the address is correct for loopback.
- sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(&ifr[i].ifr_addr);
- ASSERT_EQ(htonl(sin->sin_addr.s_addr), INADDR_LOOPBACK);
-
- found_loopback = true;
- break;
- }
- }
- ASSERT_TRUE(found_loopback);
-}
-
-std::vector<SocketKind> IoctlSocketTypes() {
- return {SimpleSocket(AF_UNIX, SOCK_STREAM, 0),
- SimpleSocket(AF_UNIX, SOCK_DGRAM, 0),
- SimpleSocket(AF_INET, SOCK_STREAM, 0),
- SimpleSocket(AF_INET6, SOCK_STREAM, 0),
- SimpleSocket(AF_INET, SOCK_DGRAM, 0),
- SimpleSocket(AF_INET6, SOCK_DGRAM, 0)};
-}
-
-INSTANTIATE_TEST_SUITE_P(IoctlTest, IoctlTestSIOCGIFCONF,
- ::testing::ValuesIn(IoctlSocketTypes()));
-
-} // namespace
-
-TEST_F(IoctlTest, FIOGETOWNSucceeds) {
- const FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
-
- int get = -1;
- ASSERT_THAT(ioctl(s.get(), FIOGETOWN, &get), SyscallSucceeds());
- EXPECT_EQ(get, 0);
-}
-
-TEST_F(IoctlTest, SIOCGPGRPSucceeds) {
- const FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
-
- int get = -1;
- ASSERT_THAT(ioctl(s.get(), SIOCGPGRP, &get), SyscallSucceeds());
- EXPECT_EQ(get, 0);
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/ip_socket_test_util.cc b/test/syscalls/linux/ip_socket_test_util.cc
deleted file mode 100644
index 410b42a47..000000000
--- a/test/syscalls/linux/ip_socket_test_util.cc
+++ /dev/null
@@ -1,197 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <net/if.h>
-#include <netinet/in.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <cstring>
-
-#include "test/syscalls/linux/ip_socket_test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-uint32_t IPFromInetSockaddr(const struct sockaddr* addr) {
- auto* in_addr = reinterpret_cast<const struct sockaddr_in*>(addr);
- return in_addr->sin_addr.s_addr;
-}
-
-uint16_t PortFromInetSockaddr(const struct sockaddr* addr) {
- auto* in_addr = reinterpret_cast<const struct sockaddr_in*>(addr);
- return ntohs(in_addr->sin_port);
-}
-
-PosixErrorOr<int> InterfaceIndex(std::string name) {
- // TODO(igudger): Consider using netlink.
- ifreq req = {};
- memcpy(req.ifr_name, name.c_str(), name.size());
- ASSIGN_OR_RETURN_ERRNO(auto sock, Socket(AF_INET, SOCK_DGRAM, 0));
- RETURN_ERROR_IF_SYSCALL_FAIL(ioctl(sock.get(), SIOCGIFINDEX, &req));
- return req.ifr_ifindex;
-}
-
-namespace {
-
-std::string DescribeSocketType(int type) {
- return absl::StrCat(((type & SOCK_NONBLOCK) != 0) ? "non-blocking " : "",
- ((type & SOCK_CLOEXEC) != 0) ? "close-on-exec " : "");
-}
-
-} // namespace
-
-SocketPairKind IPv6TCPAcceptBindSocketPair(int type) {
- std::string description =
- absl::StrCat(DescribeSocketType(type), "connected IPv6 TCP socket");
- return SocketPairKind{
- description, AF_INET6, type | SOCK_STREAM, IPPROTO_TCP,
- TCPAcceptBindSocketPairCreator(AF_INET6, type | SOCK_STREAM, 0,
- /* dual_stack = */ false)};
-}
-
-SocketPairKind IPv4TCPAcceptBindSocketPair(int type) {
- std::string description =
- absl::StrCat(DescribeSocketType(type), "connected IPv4 TCP socket");
- return SocketPairKind{
- description, AF_INET, type | SOCK_STREAM, IPPROTO_TCP,
- TCPAcceptBindSocketPairCreator(AF_INET, type | SOCK_STREAM, 0,
- /* dual_stack = */ false)};
-}
-
-SocketPairKind DualStackTCPAcceptBindSocketPair(int type) {
- std::string description =
- absl::StrCat(DescribeSocketType(type), "connected dual stack TCP socket");
- return SocketPairKind{
- description, AF_INET6, type | SOCK_STREAM, IPPROTO_TCP,
- TCPAcceptBindSocketPairCreator(AF_INET6, type | SOCK_STREAM, 0,
- /* dual_stack = */ true)};
-}
-
-SocketPairKind IPv6UDPBidirectionalBindSocketPair(int type) {
- std::string description =
- absl::StrCat(DescribeSocketType(type), "connected IPv6 UDP socket");
- return SocketPairKind{
- description, AF_INET6, type | SOCK_DGRAM, IPPROTO_UDP,
- UDPBidirectionalBindSocketPairCreator(AF_INET6, type | SOCK_DGRAM, 0,
- /* dual_stack = */ false)};
-}
-
-SocketPairKind IPv4UDPBidirectionalBindSocketPair(int type) {
- std::string description =
- absl::StrCat(DescribeSocketType(type), "connected IPv4 UDP socket");
- return SocketPairKind{
- description, AF_INET, type | SOCK_DGRAM, IPPROTO_UDP,
- UDPBidirectionalBindSocketPairCreator(AF_INET, type | SOCK_DGRAM, 0,
- /* dual_stack = */ false)};
-}
-
-SocketPairKind DualStackUDPBidirectionalBindSocketPair(int type) {
- std::string description =
- absl::StrCat(DescribeSocketType(type), "connected dual stack UDP socket");
- return SocketPairKind{
- description, AF_INET6, type | SOCK_DGRAM, IPPROTO_UDP,
- UDPBidirectionalBindSocketPairCreator(AF_INET6, type | SOCK_DGRAM, 0,
- /* dual_stack = */ true)};
-}
-
-SocketPairKind IPv4UDPUnboundSocketPair(int type) {
- std::string description =
- absl::StrCat(DescribeSocketType(type), "IPv4 UDP socket");
- return SocketPairKind{
- description, AF_INET, type | SOCK_DGRAM, IPPROTO_UDP,
- UDPUnboundSocketPairCreator(AF_INET, type | SOCK_DGRAM, 0,
- /* dual_stack = */ false)};
-}
-
-SocketKind IPv4UDPUnboundSocket(int type) {
- std::string description =
- absl::StrCat(DescribeSocketType(type), "IPv4 UDP socket");
- return SocketKind{
- description, AF_INET, type | SOCK_DGRAM, IPPROTO_UDP,
- UnboundSocketCreator(AF_INET, type | SOCK_DGRAM, IPPROTO_UDP)};
-}
-
-SocketKind IPv4TCPUnboundSocket(int type) {
- std::string description =
- absl::StrCat(DescribeSocketType(type), "IPv4 TCP socket");
- return SocketKind{
- description, AF_INET, type | SOCK_STREAM, IPPROTO_TCP,
- UnboundSocketCreator(AF_INET, type | SOCK_STREAM, IPPROTO_TCP)};
-}
-
-PosixError IfAddrHelper::Load() {
- Release();
- RETURN_ERROR_IF_SYSCALL_FAIL(getifaddrs(&ifaddr_));
- return PosixError(0);
-}
-
-void IfAddrHelper::Release() {
- if (ifaddr_) {
- freeifaddrs(ifaddr_);
- }
- ifaddr_ = nullptr;
-}
-
-std::vector<std::string> IfAddrHelper::InterfaceList(int family) {
- std::vector<std::string> names;
- for (auto ifa = ifaddr_; ifa != NULL; ifa = ifa->ifa_next) {
- if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != family) {
- continue;
- }
- names.emplace(names.end(), ifa->ifa_name);
- }
- return names;
-}
-
-sockaddr* IfAddrHelper::GetAddr(int family, std::string name) {
- for (auto ifa = ifaddr_; ifa != NULL; ifa = ifa->ifa_next) {
- if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != family) {
- continue;
- }
- if (name == ifa->ifa_name) {
- return ifa->ifa_addr;
- }
- }
- return nullptr;
-}
-
-PosixErrorOr<int> IfAddrHelper::GetIndex(std::string name) {
- return InterfaceIndex(name);
-}
-
-std::string GetAddr4Str(in_addr* a) {
- char str[INET_ADDRSTRLEN];
- inet_ntop(AF_INET, a, str, sizeof(str));
- return std::string(str);
-}
-
-std::string GetAddr6Str(in6_addr* a) {
- char str[INET6_ADDRSTRLEN];
- inet_ntop(AF_INET6, a, str, sizeof(str));
- return std::string(str);
-}
-
-std::string GetAddrStr(sockaddr* a) {
- if (a->sa_family == AF_INET) {
- auto src = &(reinterpret_cast<sockaddr_in*>(a)->sin_addr);
- return GetAddr4Str(src);
- } else if (a->sa_family == AF_INET6) {
- auto src = &(reinterpret_cast<sockaddr_in6*>(a)->sin6_addr);
- return GetAddr6Str(src);
- }
- return std::string("<invalid>");
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/ip_socket_test_util.h b/test/syscalls/linux/ip_socket_test_util.h
deleted file mode 100644
index 3d36b9620..000000000
--- a/test/syscalls/linux/ip_socket_test_util.h
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_IP_SOCKET_TEST_UTIL_H_
-#define GVISOR_TEST_SYSCALLS_IP_SOCKET_TEST_UTIL_H_
-
-#include <arpa/inet.h>
-#include <ifaddrs.h>
-#include <sys/types.h>
-
-#include <string>
-
-#include "test/syscalls/linux/socket_test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Possible values of the "st" field in a /proc/net/{tcp,udp} entry. Source:
-// Linux kernel, include/net/tcp_states.h.
-enum {
- TCP_ESTABLISHED = 1,
- TCP_SYN_SENT,
- TCP_SYN_RECV,
- TCP_FIN_WAIT1,
- TCP_FIN_WAIT2,
- TCP_TIME_WAIT,
- TCP_CLOSE,
- TCP_CLOSE_WAIT,
- TCP_LAST_ACK,
- TCP_LISTEN,
- TCP_CLOSING,
- TCP_NEW_SYN_RECV,
-
- TCP_MAX_STATES
-};
-
-// Extracts the IP address from an inet sockaddr in network byte order.
-uint32_t IPFromInetSockaddr(const struct sockaddr* addr);
-
-// Extracts the port from an inet sockaddr in host byte order.
-uint16_t PortFromInetSockaddr(const struct sockaddr* addr);
-
-// InterfaceIndex returns the index of the named interface.
-PosixErrorOr<int> InterfaceIndex(std::string name);
-
-// IPv6TCPAcceptBindSocketPair returns a SocketPairKind that represents
-// SocketPairs created with bind() and accept() syscalls with AF_INET6 and the
-// given type bound to the IPv6 loopback.
-SocketPairKind IPv6TCPAcceptBindSocketPair(int type);
-
-// IPv4TCPAcceptBindSocketPair returns a SocketPairKind that represents
-// SocketPairs created with bind() and accept() syscalls with AF_INET and the
-// given type bound to the IPv4 loopback.
-SocketPairKind IPv4TCPAcceptBindSocketPair(int type);
-
-// DualStackTCPAcceptBindSocketPair returns a SocketPairKind that represents
-// SocketPairs created with bind() and accept() syscalls with AF_INET6 and the
-// given type bound to the IPv4 loopback.
-SocketPairKind DualStackTCPAcceptBindSocketPair(int type);
-
-// IPv6UDPBidirectionalBindSocketPair returns a SocketPairKind that represents
-// SocketPairs created with bind() and connect() syscalls with AF_INET6 and the
-// given type bound to the IPv6 loopback.
-SocketPairKind IPv6UDPBidirectionalBindSocketPair(int type);
-
-// IPv4UDPBidirectionalBindSocketPair returns a SocketPairKind that represents
-// SocketPairs created with bind() and connect() syscalls with AF_INET and the
-// given type bound to the IPv4 loopback.
-SocketPairKind IPv4UDPBidirectionalBindSocketPair(int type);
-
-// DualStackUDPBidirectionalBindSocketPair returns a SocketPairKind that
-// represents SocketPairs created with bind() and connect() syscalls with
-// AF_INET6 and the given type bound to the IPv4 loopback.
-SocketPairKind DualStackUDPBidirectionalBindSocketPair(int type);
-
-// IPv4UDPUnboundSocketPair returns a SocketPairKind that represents
-// SocketPairs created with AF_INET and the given type.
-SocketPairKind IPv4UDPUnboundSocketPair(int type);
-
-// IPv4UDPUnboundSocketPair returns a SocketKind that represents
-// a SimpleSocket created with AF_INET, SOCK_DGRAM, and the given type.
-SocketKind IPv4UDPUnboundSocket(int type);
-
-// IPv4TCPUnboundSocketPair returns a SocketKind that represents
-// a SimpleSocket created with AF_INET, SOCK_STREAM and the given type.
-SocketKind IPv4TCPUnboundSocket(int type);
-
-// IfAddrHelper is a helper class that determines the local interfaces present
-// and provides functions to obtain their names, index numbers, and IP address.
-class IfAddrHelper {
- public:
- IfAddrHelper() : ifaddr_(nullptr) {}
- ~IfAddrHelper() { Release(); }
-
- PosixError Load();
- void Release();
-
- std::vector<std::string> InterfaceList(int family);
-
- struct sockaddr* GetAddr(int family, std::string name);
- PosixErrorOr<int> GetIndex(std::string name);
-
- private:
- struct ifaddrs* ifaddr_;
-};
-
-// GetAddr4Str returns the given IPv4 network address structure as a string.
-std::string GetAddr4Str(in_addr* a);
-
-// GetAddr6Str returns the given IPv6 network address structure as a string.
-std::string GetAddr6Str(in6_addr* a);
-
-// GetAddrStr returns the given IPv4 or IPv6 network address structure as a
-// string.
-std::string GetAddrStr(sockaddr* a);
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_IP_SOCKET_TEST_UTIL_H_
diff --git a/test/syscalls/linux/iptables.cc b/test/syscalls/linux/iptables.cc
deleted file mode 100644
index b8e4ece64..000000000
--- a/test/syscalls/linux/iptables.cc
+++ /dev/null
@@ -1,204 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/iptables.h"
-
-#include <arpa/inet.h>
-#include <linux/capability.h>
-#include <linux/netfilter/x_tables.h>
-#include <net/if.h>
-#include <netinet/in.h>
-#include <netinet/ip.h>
-#include <netinet/ip_icmp.h>
-#include <stdio.h>
-#include <sys/poll.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <algorithm>
-
-#include "gtest/gtest.h"
-#include "test/util/capability_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-constexpr char kNatTablename[] = "nat";
-constexpr char kErrorTarget[] = "ERROR";
-constexpr size_t kEmptyStandardEntrySize =
- sizeof(struct ipt_entry) + sizeof(struct ipt_standard_target);
-constexpr size_t kEmptyErrorEntrySize =
- sizeof(struct ipt_entry) + sizeof(struct ipt_error_target);
-
-TEST(IPTablesBasic, CreateSocket) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- int sock;
- ASSERT_THAT(sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP),
- SyscallSucceeds());
-
- ASSERT_THAT(close(sock), SyscallSucceeds());
-}
-
-TEST(IPTablesBasic, FailSockoptNonRaw) {
- // Even if the user has CAP_NET_RAW, they shouldn't be able to use the
- // iptables sockopts with a non-raw socket.
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- int sock;
- ASSERT_THAT(sock = socket(AF_INET, SOCK_DGRAM, 0), SyscallSucceeds());
-
- struct ipt_getinfo info = {};
- snprintf(info.name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename);
- socklen_t info_size = sizeof(info);
- EXPECT_THAT(getsockopt(sock, IPPROTO_IP, SO_GET_INFO, &info, &info_size),
- SyscallFailsWithErrno(ENOPROTOOPT));
-
- ASSERT_THAT(close(sock), SyscallSucceeds());
-}
-
-// Fixture for iptables tests.
-class IPTablesTest : public ::testing::Test {
- protected:
- // Creates a socket to be used in tests.
- void SetUp() override;
-
- // Closes the socket created by SetUp().
- void TearDown() override;
-
- // The socket via which to manipulate iptables.
- int s_;
-};
-
-void IPTablesTest::SetUp() {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(s_ = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP), SyscallSucceeds());
-}
-
-void IPTablesTest::TearDown() {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- EXPECT_THAT(close(s_), SyscallSucceeds());
-}
-
-// This tests the initial state of a machine with empty iptables. We don't have
-// a guarantee that the iptables are empty when running in native, but we can
-// test that gVisor has the same initial state that a newly-booted Linux machine
-// would have.
-TEST_F(IPTablesTest, InitialState) {
- SKIP_IF(!IsRunningOnGvisor());
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- //
- // Get info via sockopt.
- //
- struct ipt_getinfo info = {};
- snprintf(info.name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename);
- socklen_t info_size = sizeof(info);
- ASSERT_THAT(getsockopt(s_, IPPROTO_IP, SO_GET_INFO, &info, &info_size),
- SyscallSucceeds());
-
- // The nat table supports PREROUTING, and OUTPUT.
- unsigned int valid_hooks = (1 << NF_IP_PRE_ROUTING) | (1 << NF_IP_LOCAL_OUT) |
- (1 << NF_IP_POST_ROUTING) | (1 << NF_IP_LOCAL_IN);
-
- EXPECT_EQ(info.valid_hooks, valid_hooks);
-
- // Each chain consists of an empty entry with a standard target..
- EXPECT_EQ(info.hook_entry[NF_IP_PRE_ROUTING], 0);
- EXPECT_EQ(info.hook_entry[NF_IP_LOCAL_IN], kEmptyStandardEntrySize);
- EXPECT_EQ(info.hook_entry[NF_IP_LOCAL_OUT], kEmptyStandardEntrySize * 2);
- EXPECT_EQ(info.hook_entry[NF_IP_POST_ROUTING], kEmptyStandardEntrySize * 3);
-
- // The underflow points are the same as the entry points.
- EXPECT_EQ(info.underflow[NF_IP_PRE_ROUTING], 0);
- EXPECT_EQ(info.underflow[NF_IP_LOCAL_IN], kEmptyStandardEntrySize);
- EXPECT_EQ(info.underflow[NF_IP_LOCAL_OUT], kEmptyStandardEntrySize * 2);
- EXPECT_EQ(info.underflow[NF_IP_POST_ROUTING], kEmptyStandardEntrySize * 3);
-
- // One entry for each chain, plus an error entry at the end.
- EXPECT_EQ(info.num_entries, 5);
-
- EXPECT_EQ(info.size, 4 * kEmptyStandardEntrySize + kEmptyErrorEntrySize);
- EXPECT_EQ(strcmp(info.name, kNatTablename), 0);
-
- //
- // Use info to get entries.
- //
- socklen_t entries_size = sizeof(struct ipt_get_entries) + info.size;
- struct ipt_get_entries* entries =
- static_cast<struct ipt_get_entries*>(malloc(entries_size));
- snprintf(entries->name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename);
- entries->size = info.size;
- ASSERT_THAT(
- getsockopt(s_, IPPROTO_IP, SO_GET_ENTRIES, entries, &entries_size),
- SyscallSucceeds());
-
- // Verify the name and size.
- ASSERT_EQ(info.size, entries->size);
- ASSERT_EQ(strcmp(entries->name, kNatTablename), 0);
-
- // Verify that the entrytable is 4 entries with accept targets and no matches
- // followed by a single error target.
- size_t entry_offset = 0;
- while (entry_offset < entries->size) {
- struct ipt_entry* entry = reinterpret_cast<struct ipt_entry*>(
- reinterpret_cast<char*>(entries->entrytable) + entry_offset);
-
- // ip should be zeroes.
- struct ipt_ip zeroed = {};
- EXPECT_EQ(memcmp(static_cast<void*>(&zeroed),
- static_cast<void*>(&entry->ip), sizeof(zeroed)),
- 0);
-
- // target_offset should be zero.
- EXPECT_EQ(entry->target_offset, sizeof(ipt_entry));
-
- if (entry_offset < kEmptyStandardEntrySize * 4) {
- // The first 4 entries are standard targets
- struct ipt_standard_target* target =
- reinterpret_cast<struct ipt_standard_target*>(entry->elems);
- EXPECT_EQ(entry->next_offset, kEmptyStandardEntrySize);
- EXPECT_EQ(target->target.u.user.target_size, sizeof(*target));
- EXPECT_EQ(strcmp(target->target.u.user.name, ""), 0);
- EXPECT_EQ(target->target.u.user.revision, 0);
- // This is what's returned for an accept verdict. I don't know why.
- EXPECT_EQ(target->verdict, -NF_ACCEPT - 1);
- } else {
- // The last entry is an error target
- struct ipt_error_target* target =
- reinterpret_cast<struct ipt_error_target*>(entry->elems);
- EXPECT_EQ(entry->next_offset, kEmptyErrorEntrySize);
- EXPECT_EQ(target->target.u.user.target_size, sizeof(*target));
- EXPECT_EQ(strcmp(target->target.u.user.name, kErrorTarget), 0);
- EXPECT_EQ(target->target.u.user.revision, 0);
- EXPECT_EQ(strcmp(target->errorname, kErrorTarget), 0);
- }
-
- entry_offset += entry->next_offset;
- }
-
- free(entries);
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/iptables.h b/test/syscalls/linux/iptables.h
deleted file mode 100644
index 616bea550..000000000
--- a/test/syscalls/linux/iptables.h
+++ /dev/null
@@ -1,198 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// There are a number of structs and values that we can't #include because of a
-// difference between C and C++ (C++ won't let you implicitly cast from void* to
-// struct something*). We re-define them here.
-
-#ifndef GVISOR_TEST_SYSCALLS_IPTABLES_TYPES_H_
-#define GVISOR_TEST_SYSCALLS_IPTABLES_TYPES_H_
-
-// Netfilter headers require some headers to preceed them.
-// clang-format off
-#include <netinet/in.h>
-#include <stddef.h>
-// clang-format on
-
-#include <linux/netfilter/x_tables.h>
-#include <linux/netfilter_ipv4.h>
-#include <net/if.h>
-#include <netinet/ip.h>
-#include <stdint.h>
-
-#define ipt_standard_target xt_standard_target
-#define ipt_entry_target xt_entry_target
-#define ipt_error_target xt_error_target
-
-enum SockOpts {
- // For setsockopt.
- BASE_CTL = 64,
- SO_SET_REPLACE = BASE_CTL,
- SO_SET_ADD_COUNTERS,
- SO_SET_MAX = SO_SET_ADD_COUNTERS,
-
- // For getsockopt.
- SO_GET_INFO = BASE_CTL,
- SO_GET_ENTRIES,
- SO_GET_REVISION_MATCH,
- SO_GET_REVISION_TARGET,
- SO_GET_MAX = SO_GET_REVISION_TARGET
-};
-
-// ipt_ip specifies basic matching criteria that can be applied by examining
-// only the IP header of a packet.
-struct ipt_ip {
- // Source IP address.
- struct in_addr src;
-
- // Destination IP address.
- struct in_addr dst;
-
- // Source IP address mask.
- struct in_addr smsk;
-
- // Destination IP address mask.
- struct in_addr dmsk;
-
- // Input interface.
- char iniface[IFNAMSIZ];
-
- // Output interface.
- char outiface[IFNAMSIZ];
-
- // Input interface mask.
- unsigned char iniface_mask[IFNAMSIZ];
-
- // Output interface mask.
- unsigned char outiface_mask[IFNAMSIZ];
-
- // Transport protocol.
- uint16_t proto;
-
- // Flags.
- uint8_t flags;
-
- // Inverse flags.
- uint8_t invflags;
-};
-
-// ipt_entry is an iptables rule. It contains information about what packets the
-// rule matches and what action (target) to perform for matching packets.
-struct ipt_entry {
- // Basic matching information used to match a packet's IP header.
- struct ipt_ip ip;
-
- // A caching field that isn't used by userspace.
- unsigned int nfcache;
-
- // The number of bytes between the start of this ipt_entry struct and the
- // rule's target.
- uint16_t target_offset;
-
- // The total size of this rule, from the beginning of the entry to the end of
- // the target.
- uint16_t next_offset;
-
- // A return pointer not used by userspace.
- unsigned int comefrom;
-
- // Counters for packets and bytes, which we don't yet implement.
- struct xt_counters counters;
-
- // The data for all this rules matches followed by the target. This runs
- // beyond the value of sizeof(struct ipt_entry).
- unsigned char elems[0];
-};
-
-// Passed to getsockopt(SO_GET_INFO).
-struct ipt_getinfo {
- // The name of the table. The user only fills this in, the rest is filled in
- // when returning from getsockopt. Currently "nat" and "mangle" are supported.
- char name[XT_TABLE_MAXNAMELEN];
-
- // A bitmap of which hooks apply to the table. For example, a table with hooks
- // PREROUTING and FORWARD has the value
- // (1 << NF_IP_PRE_REOUTING) | (1 << NF_IP_FORWARD).
- unsigned int valid_hooks;
-
- // The offset into the entry table for each valid hook. The entry table is
- // returned by getsockopt(SO_GET_ENTRIES).
- unsigned int hook_entry[NF_IP_NUMHOOKS];
-
- // For each valid hook, the underflow is the offset into the entry table to
- // jump to in case traversing the table yields no verdict (although I have no
- // clue how that could happen - builtin chains always end with a policy, and
- // user-defined chains always end with a RETURN.
- //
- // The entry referred to must be an "unconditional" entry, meaning it has no
- // matches, specifies no IP criteria, and either DROPs or ACCEPTs packets. It
- // basically has to be capable of making a definitive decision no matter what
- // it's passed.
- unsigned int underflow[NF_IP_NUMHOOKS];
-
- // The number of entries in the entry table returned by
- // getsockopt(SO_GET_ENTRIES).
- unsigned int num_entries;
-
- // The size of the entry table returned by getsockopt(SO_GET_ENTRIES).
- unsigned int size;
-};
-
-// Passed to getsockopt(SO_GET_ENTRIES).
-struct ipt_get_entries {
- // The name of the table. The user fills this in. Currently "nat" and "mangle"
- // are supported.
- char name[XT_TABLE_MAXNAMELEN];
-
- // The size of the entry table in bytes. The user fills this in with the value
- // from struct ipt_getinfo.size.
- unsigned int size;
-
- // The entries for the given table. This will run past the size defined by
- // sizeof(struct ipt_get_entries).
- struct ipt_entry entrytable[0];
-};
-
-// Passed to setsockopt(SO_SET_REPLACE).
-struct ipt_replace {
- // The name of the table.
- char name[XT_TABLE_MAXNAMELEN];
-
- // The same as struct ipt_getinfo.valid_hooks. Users don't change this.
- unsigned int valid_hooks;
-
- // The same as struct ipt_getinfo.num_entries.
- unsigned int num_entries;
-
- // The same as struct ipt_getinfo.size.
- unsigned int size;
-
- // The same as struct ipt_getinfo.hook_entry.
- unsigned int hook_entry[NF_IP_NUMHOOKS];
-
- // The same as struct ipt_getinfo.underflow.
- unsigned int underflow[NF_IP_NUMHOOKS];
-
- // The number of counters, which should equal the number of entries.
- unsigned int num_counters;
-
- // The unchanged values from each ipt_entry's counters.
- struct xt_counters *counters;
-
- // The entries to write to the table. This will run past the size defined by
- // sizeof(srtuct ipt_replace);
- struct ipt_entry entries[0];
-};
-
-#endif // GVISOR_TEST_SYSCALLS_IPTABLES_TYPES_H_
diff --git a/test/syscalls/linux/itimer.cc b/test/syscalls/linux/itimer.cc
deleted file mode 100644
index 51ce323b9..000000000
--- a/test/syscalls/linux/itimer.cc
+++ /dev/null
@@ -1,346 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <signal.h>
-#include <sys/socket.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <time.h>
-
-#include <atomic>
-#include <functional>
-#include <iostream>
-#include <vector>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/strings/string_view.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/logging.h"
-#include "test/util/multiprocess_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/signal_util.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-#include "test/util/timer_util.h"
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-constexpr char kSIGALRMToMainThread[] = "--itimer_sigarlm_to_main_thread";
-constexpr char kSIGPROFFairnessActive[] = "--itimer_sigprof_fairness_active";
-constexpr char kSIGPROFFairnessIdle[] = "--itimer_sigprof_fairness_idle";
-
-// Time period to be set for the itimers.
-constexpr absl::Duration kPeriod = absl::Milliseconds(25);
-// Total amount of time to spend per thread.
-constexpr absl::Duration kTestDuration = absl::Seconds(20);
-// Amount of spin iterations to perform as the minimum work item per thread.
-// Chosen to be sub-millisecond range.
-constexpr int kIterations = 10000000;
-// Allow deviation in the number of samples.
-constexpr double kNumSamplesDeviationRatio = 0.2;
-
-TEST(ItimerTest, ItimervalUpdatedBeforeExpiration) {
- constexpr int kSleepSecs = 10;
- constexpr int kAlarmSecs = 15;
- static_assert(
- kSleepSecs < kAlarmSecs,
- "kSleepSecs must be less than kAlarmSecs for the test to be meaningful");
- constexpr int kMaxRemainingSecs = kAlarmSecs - kSleepSecs;
-
- // Install a no-op handler for SIGALRM.
- struct sigaction sa = {};
- sigfillset(&sa.sa_mask);
- sa.sa_handler = +[](int signo) {};
- auto const cleanup_sa =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGALRM, sa));
-
- // Set an itimer-based alarm for kAlarmSecs from now.
- struct itimerval itv = {};
- itv.it_value.tv_sec = kAlarmSecs;
- auto const cleanup_itimer =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedItimer(ITIMER_REAL, itv));
-
- // After sleeping for kSleepSecs, the itimer value should reflect the elapsed
- // time even if it hasn't expired.
- absl::SleepFor(absl::Seconds(kSleepSecs));
- ASSERT_THAT(getitimer(ITIMER_REAL, &itv), SyscallSucceeds());
- EXPECT_TRUE(
- itv.it_value.tv_sec < kMaxRemainingSecs ||
- (itv.it_value.tv_sec == kMaxRemainingSecs && itv.it_value.tv_usec == 0))
- << "Remaining time: " << itv.it_value.tv_sec << " seconds + "
- << itv.it_value.tv_usec << " microseconds";
-}
-
-ABSL_CONST_INIT static thread_local std::atomic_int signal_test_num_samples =
- ATOMIC_VAR_INIT(0);
-
-void SignalTestSignalHandler(int /*signum*/) { signal_test_num_samples++; }
-
-struct SignalTestResult {
- int expected_total;
- int main_thread_samples;
- std::vector<int> worker_samples;
-};
-
-std::ostream& operator<<(std::ostream& os, const SignalTestResult& r) {
- os << "{expected_total: " << r.expected_total
- << ", main_thread_samples: " << r.main_thread_samples
- << ", worker_samples: [";
- bool first = true;
- for (int sample : r.worker_samples) {
- if (!first) {
- os << ", ";
- }
- os << sample;
- first = false;
- }
- os << "]}";
- return os;
-}
-
-// Starts two worker threads and itimer id and measures the number of signal
-// delivered to each thread.
-SignalTestResult ItimerSignalTest(int id, clock_t main_clock,
- clock_t worker_clock, int signal,
- absl::Duration sleep) {
- signal_test_num_samples = 0;
-
- struct sigaction sa = {};
- sa.sa_handler = &SignalTestSignalHandler;
- sa.sa_flags = SA_RESTART;
- sigemptyset(&sa.sa_mask);
- auto sigaction_cleanup = ScopedSigaction(signal, sa).ValueOrDie();
-
- int socketfds[2];
- TEST_PCHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, socketfds) == 0);
-
- // Do the spinning in the workers.
- std::function<void*(int)> work = [&](int socket_fd) {
- FileDescriptor fd(socket_fd);
-
- absl::Time finish = Now(worker_clock) + kTestDuration;
- while (Now(worker_clock) < finish) {
- // Blocked on read.
- char c;
- RetryEINTR(read)(fd.get(), &c, 1);
- for (int i = 0; i < kIterations; i++) {
- // Ensure compiler won't optimize this loop away.
- asm("");
- }
-
- if (sleep != absl::ZeroDuration()) {
- // Sleep so that the entire process is idle for a while.
- absl::SleepFor(sleep);
- }
-
- // Unblock the other thread.
- RetryEINTR(write)(fd.get(), &c, 1);
- }
-
- return reinterpret_cast<void*>(signal_test_num_samples.load());
- };
-
- ScopedThread th1(
- static_cast<std::function<void*()>>(std::bind(work, socketfds[0])));
- ScopedThread th2(
- static_cast<std::function<void*()>>(std::bind(work, socketfds[1])));
-
- absl::Time start = Now(main_clock);
- // Start the timer.
- struct itimerval timer = {};
- timer.it_value = absl::ToTimeval(kPeriod);
- timer.it_interval = absl::ToTimeval(kPeriod);
- auto cleanup_itimer = ScopedItimer(id, timer).ValueOrDie();
-
- // Unblock th1.
- //
- // N.B. th2 owns socketfds[1] but can't close it until it unblocks.
- char c = 0;
- TEST_CHECK(write(socketfds[1], &c, 1) == 1);
-
- SignalTestResult result;
-
- // Wait for the workers to be done and collect their sample counts.
- result.worker_samples.push_back(reinterpret_cast<int64_t>(th1.Join()));
- result.worker_samples.push_back(reinterpret_cast<int64_t>(th2.Join()));
- cleanup_itimer.Release()();
- result.expected_total = (Now(main_clock) - start) / kPeriod;
- result.main_thread_samples = signal_test_num_samples.load();
-
- return result;
-}
-
-int TestSIGALRMToMainThread() {
- SignalTestResult result =
- ItimerSignalTest(ITIMER_REAL, CLOCK_REALTIME, CLOCK_REALTIME, SIGALRM,
- absl::ZeroDuration());
-
- std::cerr << "result: " << result << std::endl;
-
- // ITIMER_REAL-generated SIGALRMs prefer to deliver to the thread group leader
- // (but don't guarantee it), so we expect to see most samples on the main
- // thread.
- //
- // The number of SIGALRMs delivered to a worker should not exceed 20%
- // of the number of total signals expected (this is somewhat arbitrary).
- const int worker_threshold = result.expected_total / 5;
-
- //
- // Linux only guarantees timers will never expire before the requested time.
- // Thus, we only check the upper bound and also it at least have one sample.
- TEST_CHECK(result.main_thread_samples <= result.expected_total);
- TEST_CHECK(result.main_thread_samples > 0);
- for (int num : result.worker_samples) {
- TEST_CHECK_MSG(num <= worker_threshold, "worker received too many samples");
- }
-
- return 0;
-}
-
-// Random save/restore is disabled as it introduces additional latency and
-// unpredictable distribution patterns.
-TEST(ItimerTest, DeliversSIGALRMToMainThread_NoRandomSave) {
- pid_t child;
- int execve_errno;
- auto kill = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec("/proc/self/exe", {"/proc/self/exe", kSIGALRMToMainThread},
- {}, &child, &execve_errno));
- EXPECT_EQ(0, execve_errno);
-
- int status;
- EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0),
- SyscallSucceedsWithValue(child));
-
- // Not required anymore.
- kill.Release();
-
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) << status;
-}
-
-// Signals are delivered to threads fairly.
-//
-// sleep indicates how long to sleep worker threads each iteration to make the
-// entire process idle.
-int TestSIGPROFFairness(absl::Duration sleep) {
- SignalTestResult result =
- ItimerSignalTest(ITIMER_PROF, CLOCK_PROCESS_CPUTIME_ID,
- CLOCK_THREAD_CPUTIME_ID, SIGPROF, sleep);
-
- std::cerr << "result: " << result << std::endl;
-
- // The number of samples on the main thread should be very low as it did
- // nothing.
- TEST_CHECK(result.main_thread_samples < 60);
-
- // Both workers should get roughly equal number of samples.
- TEST_CHECK(result.worker_samples.size() == 2);
-
- TEST_CHECK(result.expected_total > 0);
-
- // In an ideal world each thread would get exactly 50% of the signals,
- // but since that's unlikely to happen we allow for them to get no less than
- // kNumSamplesDeviationRatio of the total observed samples.
- TEST_CHECK_MSG(std::abs(result.worker_samples[0] - result.worker_samples[1]) <
- ((result.worker_samples[0] + result.worker_samples[1]) *
- kNumSamplesDeviationRatio),
- "one worker received disproportionate share of samples");
-
- return 0;
-}
-
-// Random save/restore is disabled as it introduces additional latency and
-// unpredictable distribution patterns.
-TEST(ItimerTest, DeliversSIGPROFToThreadsRoughlyFairlyActive_NoRandomSave) {
- pid_t child;
- int execve_errno;
- auto kill = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec("/proc/self/exe", {"/proc/self/exe", kSIGPROFFairnessActive},
- {}, &child, &execve_errno));
- EXPECT_EQ(0, execve_errno);
-
- int status;
- EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0),
- SyscallSucceedsWithValue(child));
-
- // Not required anymore.
- kill.Release();
-
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "Exited with code: " << status;
-}
-
-// Random save/restore is disabled as it introduces additional latency and
-// unpredictable distribution patterns.
-TEST(ItimerTest, DeliversSIGPROFToThreadsRoughlyFairlyIdle_NoRandomSave) {
- pid_t child;
- int execve_errno;
- auto kill = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec("/proc/self/exe", {"/proc/self/exe", kSIGPROFFairnessIdle},
- {}, &child, &execve_errno));
- EXPECT_EQ(0, execve_errno);
-
- int status;
- EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0),
- SyscallSucceedsWithValue(child));
-
- // Not required anymore.
- kill.Release();
-
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "Exited with code: " << status;
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
-
-namespace {
-void MaskSIGPIPE() {
- // Always mask SIGPIPE as it's common and tests aren't expected to handle it.
- // We don't take the TestInit() path so we must do this manually.
- struct sigaction sa = {};
- sa.sa_handler = SIG_IGN;
- TEST_CHECK(sigaction(SIGPIPE, &sa, nullptr) == 0);
-}
-} // namespace
-
-int main(int argc, char** argv) {
- // These tests require no background threads, so check for them before
- // TestInit.
- for (int i = 0; i < argc; i++) {
- absl::string_view arg(argv[i]);
-
- if (arg == gvisor::testing::kSIGALRMToMainThread) {
- MaskSIGPIPE();
- return gvisor::testing::TestSIGALRMToMainThread();
- }
- if (arg == gvisor::testing::kSIGPROFFairnessActive) {
- MaskSIGPIPE();
- return gvisor::testing::TestSIGPROFFairness(absl::ZeroDuration());
- }
- if (arg == gvisor::testing::kSIGPROFFairnessIdle) {
- MaskSIGPIPE();
- return gvisor::testing::TestSIGPROFFairness(absl::Milliseconds(10));
- }
- }
-
- gvisor::testing::TestInit(&argc, &argv);
-
- return RUN_ALL_TESTS();
-}
diff --git a/test/syscalls/linux/kill.cc b/test/syscalls/linux/kill.cc
deleted file mode 100644
index 18ad923b8..000000000
--- a/test/syscalls/linux/kill.cc
+++ /dev/null
@@ -1,382 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <sys/syscall.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <cerrno>
-#include <csignal>
-
-#include "gtest/gtest.h"
-#include "absl/synchronization/mutex.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/capability_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/logging.h"
-#include "test/util/signal_util.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-DEFINE_int32(scratch_uid, 65534, "scratch UID");
-DEFINE_int32(scratch_gid, 65534, "scratch GID");
-
-using ::testing::Ge;
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(KillTest, CanKillValidPid) {
- // If pid is positive, then signal sig is sent to the process with the ID
- // specified by pid.
- EXPECT_THAT(kill(getpid(), 0), SyscallSucceeds());
- // If pid equals 0, then sig is sent to every process in the process group of
- // the calling process.
- EXPECT_THAT(kill(0, 0), SyscallSucceeds());
-
- ScopedThread([] { EXPECT_THAT(kill(gettid(), 0), SyscallSucceeds()); });
-}
-
-void SigHandler(int sig, siginfo_t* info, void* context) { _exit(0); }
-
-// If pid equals -1, then sig is sent to every process for which the calling
-// process has permission to send signals, except for process 1 (init).
-TEST(KillTest, CanKillAllPIDs) {
- int pipe_fds[2];
- ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds());
- FileDescriptor read_fd(pipe_fds[0]);
- FileDescriptor write_fd(pipe_fds[1]);
-
- pid_t pid = fork();
- if (pid == 0) {
- read_fd.reset();
-
- struct sigaction sa;
- sa.sa_sigaction = SigHandler;
- sigfillset(&sa.sa_mask);
- sa.sa_flags = SA_SIGINFO;
- TEST_PCHECK(sigaction(SIGWINCH, &sa, nullptr) == 0);
- MaybeSave();
-
- // Indicate to the parent that we're ready.
- write_fd.reset();
-
- // Wait until we get the signal from the parent.
- while (true) {
- pause();
- }
- }
-
- ASSERT_THAT(pid, SyscallSucceeds());
-
- write_fd.reset();
-
- // Wait for the child to indicate that it's unmasked the signal by closing
- // the write end.
- char buf;
- ASSERT_THAT(ReadFd(read_fd.get(), &buf, 1), SyscallSucceedsWithValue(0));
-
- // Signal the child and wait for it to die with status 0, indicating that
- // it got the expected signal.
- EXPECT_THAT(kill(-1, SIGWINCH), SyscallSucceeds());
-
- int status;
- ASSERT_THAT(RetryEINTR(waitpid)(pid, &status, 0),
- SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFEXITED(status));
- EXPECT_EQ(0, WEXITSTATUS(status));
-}
-
-TEST(KillTest, CannotKillInvalidPID) {
- // We need an unused pid to verify that kill fails when given one.
- //
- // There is no way to guarantee that a PID is unused, but the PID of a
- // recently exited process likely won't be reused soon.
- pid_t fake_pid = fork();
- if (fake_pid == 0) {
- _exit(0);
- }
-
- ASSERT_THAT(fake_pid, SyscallSucceeds());
-
- int status;
- ASSERT_THAT(RetryEINTR(waitpid)(fake_pid, &status, 0),
- SyscallSucceedsWithValue(fake_pid));
- EXPECT_TRUE(WIFEXITED(status));
- EXPECT_EQ(0, WEXITSTATUS(status));
-
- EXPECT_THAT(kill(fake_pid, 0), SyscallFailsWithErrno(ESRCH));
-}
-
-TEST(KillTest, CannotUseInvalidSignal) {
- EXPECT_THAT(kill(getpid(), 200), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(KillTest, CanKillRemoteProcess) {
- pid_t pid = fork();
- if (pid == 0) {
- while (true) {
- pause();
- }
- }
-
- ASSERT_THAT(pid, SyscallSucceeds());
-
- EXPECT_THAT(kill(pid, SIGKILL), SyscallSucceeds());
-
- int status;
- ASSERT_THAT(RetryEINTR(waitpid)(pid, &status, 0),
- SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFSIGNALED(status));
- EXPECT_EQ(SIGKILL, WTERMSIG(status));
-}
-
-TEST(KillTest, CanKillOwnProcess) {
- EXPECT_THAT(kill(getpid(), 0), SyscallSucceeds());
-}
-
-// Verify that you can kill a process even using a tid from a thread other than
-// the group leader.
-TEST(KillTest, CannotKillTid) {
- pid_t tid;
- bool tid_available = false;
- bool finished = false;
- absl::Mutex mu;
- ScopedThread t([&] {
- mu.Lock();
- tid = gettid();
- tid_available = true;
- mu.Await(absl::Condition(&finished));
- mu.Unlock();
- });
- mu.LockWhen(absl::Condition(&tid_available));
- EXPECT_THAT(kill(tid, 0), SyscallSucceeds());
- finished = true;
- mu.Unlock();
-}
-
-TEST(KillTest, SetPgid) {
- for (int i = 0; i < 10; i++) {
- // The following in the normal pattern for creating a new process group.
- // Both the parent and child process will call setpgid in order to avoid any
- // race conditions. We do this ten times to catch races.
- pid_t pid = fork();
- if (pid == 0) {
- setpgid(0, 0);
- while (true) {
- pause();
- }
- }
-
- ASSERT_THAT(pid, SyscallSucceeds());
-
- // Set the child's group and exit.
- ASSERT_THAT(setpgid(pid, pid), SyscallSucceeds());
- EXPECT_THAT(kill(pid, SIGKILL), SyscallSucceeds());
-
- int status;
- EXPECT_THAT(RetryEINTR(waitpid)(-pid, &status, 0),
- SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFSIGNALED(status));
- EXPECT_EQ(SIGKILL, WTERMSIG(status));
- }
-}
-
-TEST(KillTest, ProcessGroups) {
- // Fork a new child.
- //
- // other_child is used as a placeholder process. We use this PID as our "does
- // not exist" process group to ensure some amount of safety. (It is still
- // possible to violate this assumption, but extremely unlikely.)
- pid_t child = fork();
- if (child == 0) {
- while (true) {
- pause();
- }
- }
- ASSERT_THAT(child, SyscallSucceeds());
-
- pid_t other_child = fork();
- if (other_child == 0) {
- while (true) {
- pause();
- }
- }
- ASSERT_THAT(other_child, SyscallSucceeds());
-
- // Ensure the kill does not succeed without the new group.
- EXPECT_THAT(kill(-child, SIGKILL), SyscallFailsWithErrno(ESRCH));
-
- // Put the child in its own process group.
- ASSERT_THAT(setpgid(child, child), SyscallSucceeds());
-
- // This should be not allowed: you can only create a new group with the same
- // id or join an existing one. The other_child group should not exist.
- ASSERT_THAT(setpgid(child, other_child), SyscallFailsWithErrno(EPERM));
-
- // Done with other_child; kill it.
- EXPECT_THAT(kill(other_child, SIGKILL), SyscallSucceeds());
- int status;
- EXPECT_THAT(RetryEINTR(waitpid)(other_child, &status, 0), SyscallSucceeds());
-
- // Linux returns success for the no-op call.
- ASSERT_THAT(setpgid(child, child), SyscallSucceeds());
-
- // Kill the child's process group.
- ASSERT_THAT(kill(-child, SIGKILL), SyscallSucceeds());
-
- // Wait on the process group; ensure that the signal was as expected.
- EXPECT_THAT(RetryEINTR(waitpid)(-child, &status, 0),
- SyscallSucceedsWithValue(child));
- EXPECT_TRUE(WIFSIGNALED(status));
- EXPECT_EQ(SIGKILL, WTERMSIG(status));
-
- // Try to kill the process group again; ensure that the wait fails.
- EXPECT_THAT(kill(-child, SIGKILL), SyscallFailsWithErrno(ESRCH));
- EXPECT_THAT(RetryEINTR(waitpid)(-child, &status, 0),
- SyscallFailsWithErrno(ECHILD));
-}
-
-TEST(KillTest, ChildDropsPrivsCannotKill) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID)));
-
- int uid = FLAGS_scratch_uid;
- int gid = FLAGS_scratch_gid;
-
- // Create the child that drops privileges and tries to kill the parent.
- pid_t pid = fork();
- if (pid == 0) {
- TEST_PCHECK(setresgid(gid, gid, gid) == 0);
- MaybeSave();
-
- TEST_PCHECK(setresuid(uid, uid, uid) == 0);
- MaybeSave();
-
- // setresuid should have dropped CAP_KILL. Make sure.
- TEST_CHECK(!HaveCapability(CAP_KILL).ValueOrDie());
-
- // Try to kill parent with every signal-sending syscall possible.
- pid_t parent = getppid();
-
- TEST_CHECK(kill(parent, SIGKILL) < 0);
- TEST_PCHECK_MSG(errno == EPERM, "kill failed with wrong errno");
- MaybeSave();
-
- TEST_CHECK(tgkill(parent, parent, SIGKILL) < 0);
- TEST_PCHECK_MSG(errno == EPERM, "tgkill failed with wrong errno");
- MaybeSave();
-
- TEST_CHECK(syscall(SYS_tkill, parent, SIGKILL) < 0);
- TEST_PCHECK_MSG(errno == EPERM, "tkill failed with wrong errno");
- MaybeSave();
-
- siginfo_t uinfo;
- uinfo.si_code = -1; // SI_QUEUE (allowed).
-
- TEST_CHECK(syscall(SYS_rt_sigqueueinfo, parent, SIGKILL, &uinfo) < 0);
- TEST_PCHECK_MSG(errno == EPERM, "rt_sigqueueinfo failed with wrong errno");
- MaybeSave();
-
- TEST_CHECK(syscall(SYS_rt_tgsigqueueinfo, parent, parent, SIGKILL, &uinfo) <
- 0);
- TEST_PCHECK_MSG(errno == EPERM, "rt_sigqueueinfo failed with wrong errno");
- MaybeSave();
-
- _exit(0);
- }
-
- ASSERT_THAT(pid, SyscallSucceeds());
-
- int status;
- EXPECT_THAT(RetryEINTR(waitpid)(pid, &status, 0),
- SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "status = " << status;
-}
-
-TEST(KillTest, CanSIGCONTSameSession) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID)));
-
- pid_t stopped_child = fork();
- if (stopped_child == 0) {
- raise(SIGSTOP);
- _exit(0);
- }
-
- ASSERT_THAT(stopped_child, SyscallSucceeds());
-
- // Put the child in its own process group. The child and parent process
- // groups also share a session.
- ASSERT_THAT(setpgid(stopped_child, stopped_child), SyscallSucceeds());
-
- // Make sure child stopped.
- int status;
- EXPECT_THAT(RetryEINTR(waitpid)(stopped_child, &status, WUNTRACED),
- SyscallSucceedsWithValue(stopped_child));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
- << "status " << status;
-
- int uid = FLAGS_scratch_uid;
- int gid = FLAGS_scratch_gid;
-
- // Drop privileges only in child process, or else this parent process won't be
- // able to open some log files after the test ends.
- pid_t other_child = fork();
- if (other_child == 0) {
- // Drop privileges.
- TEST_PCHECK(setresgid(gid, gid, gid) == 0);
- MaybeSave();
-
- TEST_PCHECK(setresuid(uid, uid, uid) == 0);
- MaybeSave();
-
- // setresuid should have dropped CAP_KILL.
- TEST_CHECK(!HaveCapability(CAP_KILL).ValueOrDie());
-
- // Child 2 and child should now not share a thread group and any UIDs.
- // Child 2 should have no privileges. That means any signal other than
- // SIGCONT should fail.
- TEST_CHECK(kill(stopped_child, SIGKILL) < 0);
- TEST_PCHECK_MSG(errno == EPERM, "kill failed with wrong errno");
- MaybeSave();
-
- TEST_PCHECK(kill(stopped_child, SIGCONT) == 0);
- MaybeSave();
-
- _exit(0);
- }
-
- ASSERT_THAT(stopped_child, SyscallSucceeds());
-
- // Make sure child exited normally.
- EXPECT_THAT(RetryEINTR(waitpid)(stopped_child, &status, 0),
- SyscallSucceedsWithValue(stopped_child));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "status " << status;
-
- // Make sure other_child exited normally.
- EXPECT_THAT(RetryEINTR(waitpid)(other_child, &status, 0),
- SyscallSucceedsWithValue(other_child));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "status " << status;
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/link.cc b/test/syscalls/linux/link.cc
deleted file mode 100644
index a91703070..000000000
--- a/test/syscalls/linux/link.cc
+++ /dev/null
@@ -1,291 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <fcntl.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <string>
-
-#include "gtest/gtest.h"
-#include "absl/strings/str_cat.h"
-#include "test/util/capability_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-DEFINE_int32(scratch_uid, 65534, "scratch UID");
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// IsSameFile returns true if both filenames have the same device and inode.
-bool IsSameFile(const std::string& f1, const std::string& f2) {
- // Use lstat rather than stat, so that symlinks are not followed.
- struct stat stat1 = {};
- EXPECT_THAT(lstat(f1.c_str(), &stat1), SyscallSucceeds());
- struct stat stat2 = {};
- EXPECT_THAT(lstat(f2.c_str(), &stat2), SyscallSucceeds());
-
- return stat1.st_dev == stat2.st_dev && stat1.st_ino == stat2.st_ino;
-}
-
-TEST(LinkTest, CanCreateLinkFile) {
- auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const std::string newname = NewTempAbsPath();
-
- // Get the initial link count.
- uint64_t initial_link_count = ASSERT_NO_ERRNO_AND_VALUE(Links(oldfile.path()));
-
- EXPECT_THAT(link(oldfile.path().c_str(), newname.c_str()), SyscallSucceeds());
-
- EXPECT_TRUE(IsSameFile(oldfile.path(), newname));
-
- // Link count should be incremented.
- EXPECT_THAT(Links(oldfile.path()),
- IsPosixErrorOkAndHolds(initial_link_count + 1));
-
- // Delete the link.
- EXPECT_THAT(unlink(newname.c_str()), SyscallSucceeds());
-
- // Link count should be back to initial.
- EXPECT_THAT(Links(oldfile.path()),
- IsPosixErrorOkAndHolds(initial_link_count));
-}
-
-TEST(LinkTest, PermissionDenied) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_FOWNER)));
-
- // Make the file "unsafe" to link by making it only readable, but not
- // writable.
- const auto oldfile =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0400));
- const std::string newname = NewTempAbsPath();
-
- // Do setuid in a separate thread so that after finishing this test, the
- // process can still open files the test harness created before starting this
- // test. Otherwise, the files are created by root (UID before the test), but
- // cannot be opened by the `uid` set below after the test. After calling
- // setuid(non-zero-UID), there is no way to get root privileges back.
- ScopedThread([&] {
- // Use syscall instead of glibc setuid wrapper because we want this setuid
- // call to only apply to this task. POSIX threads, however, require that all
- // threads have the same UIDs, so using the setuid wrapper sets all threads'
- // real UID.
- // Also drops capabilities.
- EXPECT_THAT(syscall(SYS_setuid, FLAGS_scratch_uid), SyscallSucceeds());
-
- EXPECT_THAT(link(oldfile.path().c_str(), newname.c_str()),
- SyscallFailsWithErrno(EPERM));
- });
-}
-
-TEST(LinkTest, CannotLinkDirectory) {
- auto olddir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const std::string newdir = NewTempAbsPath();
-
- EXPECT_THAT(link(olddir.path().c_str(), newdir.c_str()),
- SyscallFailsWithErrno(EPERM));
-
- EXPECT_THAT(rmdir(olddir.path().c_str()), SyscallSucceeds());
-}
-
-TEST(LinkTest, CannotLinkWithSlash) {
- auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- // Put a final "/" on newname.
- const std::string newname = absl::StrCat(NewTempAbsPath(), "/");
-
- EXPECT_THAT(link(oldfile.path().c_str(), newname.c_str()),
- SyscallFailsWithErrno(ENOENT));
-}
-
-TEST(LinkTest, OldnameIsEmpty) {
- const std::string newname = NewTempAbsPath();
- EXPECT_THAT(link("", newname.c_str()), SyscallFailsWithErrno(ENOENT));
-}
-
-TEST(LinkTest, OldnameDoesNotExist) {
- const std::string oldname = NewTempAbsPath();
- const std::string newname = NewTempAbsPath();
- EXPECT_THAT(link("", newname.c_str()), SyscallFailsWithErrno(ENOENT));
-}
-
-TEST(LinkTest, NewnameCannotExist) {
- const std::string newname =
- JoinPath(GetAbsoluteTestTmpdir(), "thisdoesnotexist", "foo");
- EXPECT_THAT(link("/thisdoesnotmatter", newname.c_str()),
- SyscallFailsWithErrno(ENOENT));
-}
-
-TEST(LinkTest, WithOldDirFD) {
- const std::string oldname_parent = NewTempAbsPath();
- const std::string oldname_base = "child";
- const std::string oldname = JoinPath(oldname_parent, oldname_base);
- const std::string newname = NewTempAbsPath();
-
- // Create oldname_parent directory, and get an FD.
- ASSERT_THAT(mkdir(oldname_parent.c_str(), 0777), SyscallSucceeds());
- const FileDescriptor oldname_parent_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(oldname_parent, O_DIRECTORY | O_RDONLY));
-
- // Create oldname file.
- const FileDescriptor oldname_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(oldname, O_CREAT | O_RDWR, 0666));
-
- // Link oldname to newname, using oldname_parent_fd.
- EXPECT_THAT(linkat(oldname_parent_fd.get(), oldname_base.c_str(), AT_FDCWD,
- newname.c_str(), 0),
- SyscallSucceeds());
-
- EXPECT_TRUE(IsSameFile(oldname, newname));
-
- EXPECT_THAT(unlink(newname.c_str()), SyscallSucceeds());
- EXPECT_THAT(unlink(oldname.c_str()), SyscallSucceeds());
- EXPECT_THAT(rmdir(oldname_parent.c_str()), SyscallSucceeds());
-}
-
-TEST(LinkTest, BogusFlags) {
- ASSERT_THAT(linkat(1, "foo", 2, "bar", 3), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(LinkTest, WithNewDirFD) {
- auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const std::string newname_parent = NewTempAbsPath();
- const std::string newname_base = "child";
- const std::string newname = JoinPath(newname_parent, newname_base);
-
- // Create newname_parent directory, and get an FD.
- EXPECT_THAT(mkdir(newname_parent.c_str(), 0777), SyscallSucceeds());
- const FileDescriptor newname_parent_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(newname_parent, O_DIRECTORY | O_RDONLY));
-
- // Link newname to oldfile, using newname_parent_fd.
- EXPECT_THAT(linkat(AT_FDCWD, oldfile.path().c_str(), newname_parent_fd.get(),
- newname.c_str(), 0),
- SyscallSucceeds());
-
- EXPECT_TRUE(IsSameFile(oldfile.path(), newname));
-
- EXPECT_THAT(unlink(newname.c_str()), SyscallSucceeds());
- EXPECT_THAT(rmdir(newname_parent.c_str()), SyscallSucceeds());
-}
-
-TEST(LinkTest, RelPathsWithNonDirFDs) {
- auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- // Create a file that will be passed as the directory fd for old/new names.
- const std::string filename = NewTempAbsPath();
- const FileDescriptor file_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_CREAT | O_RDWR, 0666));
-
- // Using file_fd as olddirfd will fail.
- EXPECT_THAT(linkat(file_fd.get(), "foo", AT_FDCWD, "bar", 0),
- SyscallFailsWithErrno(ENOTDIR));
-
- // Using file_fd as newdirfd will fail.
- EXPECT_THAT(linkat(AT_FDCWD, oldfile.path().c_str(), file_fd.get(), "bar", 0),
- SyscallFailsWithErrno(ENOTDIR));
-}
-
-TEST(LinkTest, AbsPathsWithNonDirFDs) {
- auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const std::string newname = NewTempAbsPath();
-
- // Create a file that will be passed as the directory fd for old/new names.
- const std::string filename = NewTempAbsPath();
- const FileDescriptor file_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_CREAT | O_RDWR, 0666));
-
- // Using file_fd as the dirfds is OK as long as paths are absolute.
- EXPECT_THAT(linkat(file_fd.get(), oldfile.path().c_str(), file_fd.get(),
- newname.c_str(), 0),
- SyscallSucceeds());
-}
-
-TEST(LinkTest, LinkDoesNotFollowSymlinks) {
- // Create oldfile, and oldsymlink which points to it.
- auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const std::string oldsymlink = NewTempAbsPath();
- EXPECT_THAT(symlink(oldfile.path().c_str(), oldsymlink.c_str()),
- SyscallSucceeds());
-
- // Now hard link newname to oldsymlink.
- const std::string newname = NewTempAbsPath();
- EXPECT_THAT(link(oldsymlink.c_str(), newname.c_str()), SyscallSucceeds());
-
- // The link should not have resolved the symlink, so newname and oldsymlink
- // are the same.
- EXPECT_TRUE(IsSameFile(oldsymlink, newname));
- EXPECT_FALSE(IsSameFile(oldfile.path(), newname));
-
- EXPECT_THAT(unlink(oldsymlink.c_str()), SyscallSucceeds());
- EXPECT_THAT(unlink(newname.c_str()), SyscallSucceeds());
-}
-
-TEST(LinkTest, LinkatDoesNotFollowSymlinkByDefault) {
- // Create oldfile, and oldsymlink which points to it.
- auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const std::string oldsymlink = NewTempAbsPath();
- EXPECT_THAT(symlink(oldfile.path().c_str(), oldsymlink.c_str()),
- SyscallSucceeds());
-
- // Now hard link newname to oldsymlink.
- const std::string newname = NewTempAbsPath();
- EXPECT_THAT(
- linkat(AT_FDCWD, oldsymlink.c_str(), AT_FDCWD, newname.c_str(), 0),
- SyscallSucceeds());
-
- // The link should not have resolved the symlink, so newname and oldsymlink
- // are the same.
- EXPECT_TRUE(IsSameFile(oldsymlink, newname));
- EXPECT_FALSE(IsSameFile(oldfile.path(), newname));
-
- EXPECT_THAT(unlink(oldsymlink.c_str()), SyscallSucceeds());
- EXPECT_THAT(unlink(newname.c_str()), SyscallSucceeds());
-}
-
-TEST(LinkTest, LinkatWithSymlinkFollow) {
- // Create oldfile, and oldsymlink which points to it.
- auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const std::string oldsymlink = NewTempAbsPath();
- ASSERT_THAT(symlink(oldfile.path().c_str(), oldsymlink.c_str()),
- SyscallSucceeds());
-
- // Now hard link newname to oldsymlink, and pass AT_SYMLINK_FOLLOW flag.
- const std::string newname = NewTempAbsPath();
- ASSERT_THAT(linkat(AT_FDCWD, oldsymlink.c_str(), AT_FDCWD, newname.c_str(),
- AT_SYMLINK_FOLLOW),
- SyscallSucceeds());
-
- // The link should have resolved the symlink, so oldfile and newname are the
- // same.
- EXPECT_TRUE(IsSameFile(oldfile.path(), newname));
- EXPECT_FALSE(IsSameFile(oldsymlink, newname));
-
- EXPECT_THAT(unlink(oldsymlink.c_str()), SyscallSucceeds());
- EXPECT_THAT(unlink(newname.c_str()), SyscallSucceeds());
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/lseek.cc b/test/syscalls/linux/lseek.cc
deleted file mode 100644
index a8af8e545..000000000
--- a/test/syscalls/linux/lseek.cc
+++ /dev/null
@@ -1,202 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(LseekTest, InvalidWhence) {
- const std::string kFileData = "hello world\n";
- const TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), kFileData, TempPath::kDefaultFileMode));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_RDWR, 0644));
-
- ASSERT_THAT(lseek(fd.get(), 0, -1), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(LseekTest, NegativeOffset) {
- const std::string kFileData = "hello world\n";
- const TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), kFileData, TempPath::kDefaultFileMode));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_RDWR, 0644));
-
- EXPECT_THAT(lseek(fd.get(), -(kFileData.length() + 1), SEEK_CUR),
- SyscallFailsWithErrno(EINVAL));
-}
-
-// A 32-bit off_t is not large enough to represent an offset larger than
-// maximum file size on standard file systems, so it isn't possible to cause
-// overflow.
-#ifdef __x86_64__
-TEST(LseekTest, Overflow) {
- // HA! Classic Linux. We really should have an EOVERFLOW
- // here, since we're seeking to something that cannot be
- // represented.. but instead we are given an EINVAL.
- const std::string kFileData = "hello world\n";
- const TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), kFileData, TempPath::kDefaultFileMode));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_RDWR, 0644));
- EXPECT_THAT(lseek(fd.get(), 0x7fffffffffffffff, SEEK_END),
- SyscallFailsWithErrno(EINVAL));
-}
-#endif
-
-TEST(LseekTest, Set) {
- const std::string kFileData = "hello world\n";
- const TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), kFileData, TempPath::kDefaultFileMode));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_RDWR, 0644));
-
- char buf = '\0';
- EXPECT_THAT(lseek(fd.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0));
- ASSERT_THAT(read(fd.get(), &buf, 1), SyscallSucceedsWithValue(1));
- EXPECT_EQ(buf, kFileData.c_str()[0]);
- EXPECT_THAT(lseek(fd.get(), 6, SEEK_SET), SyscallSucceedsWithValue(6));
- ASSERT_THAT(read(fd.get(), &buf, 1), SyscallSucceedsWithValue(1));
- EXPECT_EQ(buf, kFileData.c_str()[6]);
-}
-
-TEST(LseekTest, Cur) {
- const std::string kFileData = "hello world\n";
- const TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), kFileData, TempPath::kDefaultFileMode));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_RDWR, 0644));
-
- char buf = '\0';
- EXPECT_THAT(lseek(fd.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0));
- ASSERT_THAT(read(fd.get(), &buf, 1), SyscallSucceedsWithValue(1));
- EXPECT_EQ(buf, kFileData.c_str()[0]);
- EXPECT_THAT(lseek(fd.get(), 3, SEEK_CUR), SyscallSucceedsWithValue(4));
- ASSERT_THAT(read(fd.get(), &buf, 1), SyscallSucceedsWithValue(1));
- EXPECT_EQ(buf, kFileData.c_str()[4]);
-}
-
-TEST(LseekTest, End) {
- const std::string kFileData = "hello world\n";
- const TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), kFileData, TempPath::kDefaultFileMode));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_RDWR, 0644));
-
- char buf = '\0';
- EXPECT_THAT(lseek(fd.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0));
- ASSERT_THAT(read(fd.get(), &buf, 1), SyscallSucceedsWithValue(1));
- EXPECT_EQ(buf, kFileData.c_str()[0]);
- EXPECT_THAT(lseek(fd.get(), -2, SEEK_END), SyscallSucceedsWithValue(10));
- ASSERT_THAT(read(fd.get(), &buf, 1), SyscallSucceedsWithValue(1));
- EXPECT_EQ(buf, kFileData.c_str()[kFileData.length() - 2]);
-}
-
-TEST(LseekTest, InvalidFD) {
- EXPECT_THAT(lseek(-1, 0, SEEK_SET), SyscallFailsWithErrno(EBADF));
-}
-
-TEST(LseekTest, DirCurEnd) {
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open("/tmp", O_RDONLY));
- ASSERT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0));
-}
-
-TEST(LseekTest, ProcDir) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self", O_RDONLY));
- ASSERT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceeds());
- ASSERT_THAT(lseek(fd.get(), 0, SEEK_END), SyscallSucceeds());
-}
-
-TEST(LseekTest, ProcFile) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/meminfo", O_RDONLY));
- ASSERT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceeds());
- ASSERT_THAT(lseek(fd.get(), 0, SEEK_END), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(LseekTest, SysDir) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/sys/devices", O_RDONLY));
- ASSERT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceeds());
- ASSERT_THAT(lseek(fd.get(), 0, SEEK_END), SyscallSucceeds());
-}
-
-TEST(LseekTest, SeekCurrentDir) {
- // From include/linux/fs.h.
- constexpr loff_t MAX_LFS_FILESIZE = 0x7fffffffffffffff;
-
- char* dir = get_current_dir_name();
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(dir, O_RDONLY));
-
- ASSERT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceeds());
- ASSERT_THAT(lseek(fd.get(), 0, SEEK_END),
- // Some filesystems (like ext4) allow lseek(SEEK_END) on a
- // directory and return MAX_LFS_FILESIZE, others return EINVAL.
- AnyOf(SyscallSucceedsWithValue(MAX_LFS_FILESIZE),
- SyscallFailsWithErrno(EINVAL)));
- free(dir);
-}
-
-TEST(LseekTest, ProcStatTwice) {
- const FileDescriptor fd1 =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/stat", O_RDONLY));
- const FileDescriptor fd2 =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/stat", O_RDONLY));
-
- ASSERT_THAT(lseek(fd1.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0));
- ASSERT_THAT(lseek(fd1.get(), 0, SEEK_END), SyscallFailsWithErrno(EINVAL));
- ASSERT_THAT(lseek(fd1.get(), 1000, SEEK_CUR), SyscallSucceeds());
- // Check that just because we moved fd1, fd2 doesn't move.
- ASSERT_THAT(lseek(fd2.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0));
-
- const FileDescriptor fd3 =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/stat", O_RDONLY));
- ASSERT_THAT(lseek(fd3.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0));
-}
-
-TEST(LseekTest, EtcPasswdDup) {
- const FileDescriptor fd1 =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/etc/passwd", O_RDONLY));
- const FileDescriptor fd2 = ASSERT_NO_ERRNO_AND_VALUE(fd1.Dup());
-
- ASSERT_THAT(lseek(fd1.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0));
- ASSERT_THAT(lseek(fd2.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0));
- ASSERT_THAT(lseek(fd1.get(), 1000, SEEK_CUR), SyscallSucceeds());
- // Check that just because we moved fd1, fd2 doesn't move.
- ASSERT_THAT(lseek(fd2.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(1000));
-
- const FileDescriptor fd3 = ASSERT_NO_ERRNO_AND_VALUE(fd1.Dup());
- ASSERT_THAT(lseek(fd3.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(1000));
-}
-
-// TODO(magi): Add tests where we have donated in sockets.
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/madvise.cc b/test/syscalls/linux/madvise.cc
deleted file mode 100644
index 08ff4052c..000000000
--- a/test/syscalls/linux/madvise.cc
+++ /dev/null
@@ -1,252 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include <string>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/logging.h"
-#include "test/util/memory_util.h"
-#include "test/util/multiprocess_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-void ExpectAllMappingBytes(Mapping const& m, char c) {
- auto const v = m.view();
- for (size_t i = 0; i < kPageSize; i++) {
- ASSERT_EQ(v[i], c) << "at offset " << i;
- }
-}
-
-// Equivalent to ExpectAllMappingBytes but async-signal-safe and with less
-// helpful failure messages.
-void CheckAllMappingBytes(Mapping const& m, char c) {
- auto const v = m.view();
- for (size_t i = 0; i < kPageSize; i++) {
- TEST_CHECK_MSG(v[i] == c, "mapping contains wrong value");
- }
-}
-
-TEST(MadviseDontneedTest, ZerosPrivateAnonPage) {
- auto m = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
- ExpectAllMappingBytes(m, 0);
- memset(m.ptr(), 1, m.len());
- ExpectAllMappingBytes(m, 1);
- ASSERT_THAT(madvise(m.ptr(), m.len(), MADV_DONTNEED), SyscallSucceeds());
- ExpectAllMappingBytes(m, 0);
-}
-
-TEST(MadviseDontneedTest, ZerosCOWAnonPageInCallerOnly) {
- auto m = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
- ExpectAllMappingBytes(m, 0);
- memset(m.ptr(), 2, m.len());
- ExpectAllMappingBytes(m, 2);
-
- // Do madvise in a child process.
- pid_t pid = fork();
- CheckAllMappingBytes(m, 2);
- if (pid == 0) {
- TEST_PCHECK(madvise(m.ptr(), m.len(), MADV_DONTNEED) == 0);
- CheckAllMappingBytes(m, 0);
- _exit(0);
- }
-
- ASSERT_THAT(pid, SyscallSucceeds());
-
- int status = 0;
- ASSERT_THAT(waitpid(-1, &status, 0), SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFEXITED(status));
- EXPECT_EQ(WEXITSTATUS(status), 0);
- // The child's madvise should not have affected the parent.
- ExpectAllMappingBytes(m, 2);
-}
-
-TEST(MadviseDontneedTest, DoesNotModifySharedAnonPage) {
- auto m = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED));
- ExpectAllMappingBytes(m, 0);
- memset(m.ptr(), 3, m.len());
- ExpectAllMappingBytes(m, 3);
- ASSERT_THAT(madvise(m.ptr(), m.len(), MADV_DONTNEED), SyscallSucceeds());
- ExpectAllMappingBytes(m, 3);
-}
-
-TEST(MadviseDontneedTest, CleansPrivateFilePage) {
- TempPath f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- /* parent = */ GetAbsoluteTestTmpdir(),
- /* content = */ std::string(kPageSize, 4), TempPath::kDefaultFileMode));
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDWR));
-
- Mapping m = ASSERT_NO_ERRNO_AND_VALUE(Mmap(
- nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd.get(), 0));
- ExpectAllMappingBytes(m, 4);
- memset(m.ptr(), 5, m.len());
- ExpectAllMappingBytes(m, 5);
- ASSERT_THAT(madvise(m.ptr(), m.len(), MADV_DONTNEED), SyscallSucceeds());
- ExpectAllMappingBytes(m, 4);
-}
-
-TEST(MadviseDontneedTest, DoesNotModifySharedFilePage) {
- TempPath f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- /* parent = */ GetAbsoluteTestTmpdir(),
- /* content = */ std::string(kPageSize, 6), TempPath::kDefaultFileMode));
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDWR));
-
- Mapping m = ASSERT_NO_ERRNO_AND_VALUE(Mmap(
- nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0));
- ExpectAllMappingBytes(m, 6);
- memset(m.ptr(), 7, m.len());
- ExpectAllMappingBytes(m, 7);
- ASSERT_THAT(madvise(m.ptr(), m.len(), MADV_DONTNEED), SyscallSucceeds());
- ExpectAllMappingBytes(m, 7);
-}
-
-TEST(MadviseDontneedTest, IgnoresPermissions) {
- auto m =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, MAP_PRIVATE));
- EXPECT_THAT(madvise(m.ptr(), m.len(), MADV_DONTNEED), SyscallSucceeds());
-}
-
-TEST(MadviseDontforkTest, AddressLength) {
- auto m =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, MAP_PRIVATE));
- char *addr = static_cast<char *>(m.ptr());
-
- // Address must be page aligned.
- EXPECT_THAT(madvise(addr + 1, kPageSize, MADV_DONTFORK),
- SyscallFailsWithErrno(EINVAL));
-
- // Zero length madvise always succeeds.
- EXPECT_THAT(madvise(addr, 0, MADV_DONTFORK), SyscallSucceeds());
-
- // Length must not roll over after rounding up.
- size_t badlen = std::numeric_limits<std::size_t>::max() - (kPageSize / 2);
- EXPECT_THAT(madvise(0, badlen, MADV_DONTFORK), SyscallFailsWithErrno(EINVAL));
-
- // Length need not be page aligned - it is implicitly rounded up.
- EXPECT_THAT(madvise(addr, 1, MADV_DONTFORK), SyscallSucceeds());
- EXPECT_THAT(madvise(addr, kPageSize, MADV_DONTFORK), SyscallSucceeds());
-}
-
-TEST(MadviseDontforkTest, DontforkShared) {
- // Mmap two shared file-backed pages and MADV_DONTFORK the second page.
- TempPath f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- /* parent = */ GetAbsoluteTestTmpdir(),
- /* content = */ std::string(kPageSize * 2, 2),
- TempPath::kDefaultFileMode));
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDWR));
-
- Mapping m = ASSERT_NO_ERRNO_AND_VALUE(Mmap(
- nullptr, kPageSize * 2, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0));
-
- const Mapping ms1 = Mapping(reinterpret_cast<void *>(m.addr()), kPageSize);
- const Mapping ms2 =
- Mapping(reinterpret_cast<void *>(m.addr() + kPageSize), kPageSize);
- m.release();
-
- ASSERT_THAT(madvise(ms2.ptr(), kPageSize, MADV_DONTFORK), SyscallSucceeds());
-
- const auto rest = [&] {
- // First page is mapped in child and modifications are visible to parent
- // via the shared mapping.
- TEST_CHECK(IsMapped(ms1.addr()));
- ExpectAllMappingBytes(ms1, 2);
- memset(ms1.ptr(), 1, kPageSize);
- ExpectAllMappingBytes(ms1, 1);
-
- // Second page must not be mapped in child.
- TEST_CHECK(!IsMapped(ms2.addr()));
- };
-
- EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
-
- ExpectAllMappingBytes(ms1, 1); // page contents modified by child.
- ExpectAllMappingBytes(ms2, 2); // page contents unchanged.
-}
-
-TEST(MadviseDontforkTest, DontforkAnonPrivate) {
- // Mmap three anonymous pages and MADV_DONTFORK the middle page.
- Mapping m = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize * 3, PROT_READ | PROT_WRITE, MAP_PRIVATE));
- const Mapping mp1 = Mapping(reinterpret_cast<void *>(m.addr()), kPageSize);
- const Mapping mp2 =
- Mapping(reinterpret_cast<void *>(m.addr() + kPageSize), kPageSize);
- const Mapping mp3 =
- Mapping(reinterpret_cast<void *>(m.addr() + 2 * kPageSize), kPageSize);
- m.release();
-
- ASSERT_THAT(madvise(mp2.ptr(), kPageSize, MADV_DONTFORK), SyscallSucceeds());
-
- // Verify that all pages are zeroed and memset the first, second and third
- // pages to 1, 2, and 3 respectively.
- ExpectAllMappingBytes(mp1, 0);
- memset(mp1.ptr(), 1, kPageSize);
-
- ExpectAllMappingBytes(mp2, 0);
- memset(mp2.ptr(), 2, kPageSize);
-
- ExpectAllMappingBytes(mp3, 0);
- memset(mp3.ptr(), 3, kPageSize);
-
- const auto rest = [&] {
- // Verify first page is mapped, verify its contents and then modify the
- // page. The mapping is private so the modifications are not visible to
- // the parent.
- TEST_CHECK(IsMapped(mp1.addr()));
- ExpectAllMappingBytes(mp1, 1);
- memset(mp1.ptr(), 11, kPageSize);
- ExpectAllMappingBytes(mp1, 11);
-
- // Verify second page is not mapped.
- TEST_CHECK(!IsMapped(mp2.addr()));
-
- // Verify third page is mapped, verify its contents and then modify the
- // page. The mapping is private so the modifications are not visible to
- // the parent.
- TEST_CHECK(IsMapped(mp3.addr()));
- ExpectAllMappingBytes(mp3, 3);
- memset(mp3.ptr(), 13, kPageSize);
- ExpectAllMappingBytes(mp3, 13);
- };
- EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
-
- // The fork and COW by child should not affect the parent mappings.
- ExpectAllMappingBytes(mp1, 1);
- ExpectAllMappingBytes(mp2, 2);
- ExpectAllMappingBytes(mp3, 3);
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/memfd.cc b/test/syscalls/linux/memfd.cc
deleted file mode 100644
index e57b49a4a..000000000
--- a/test/syscalls/linux/memfd.cc
+++ /dev/null
@@ -1,556 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <fcntl.h>
-#include <linux/magic.h>
-#include <linux/memfd.h>
-#include <string.h>
-#include <sys/mman.h>
-#include <sys/statfs.h>
-#include <sys/syscall.h>
-
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/memory_util.h"
-#include "test/util/multiprocess_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-// The header sys/memfd.h isn't available on all systems, so redefining some of
-// the constants here.
-#define F_LINUX_SPECIFIC_BASE 1024
-
-#ifndef F_ADD_SEALS
-#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9)
-#endif /* F_ADD_SEALS */
-
-#ifndef F_GET_SEALS
-#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10)
-#endif /* F_GET_SEALS */
-
-#define F_SEAL_SEAL 0x0001
-#define F_SEAL_SHRINK 0x0002
-#define F_SEAL_GROW 0x0004
-#define F_SEAL_WRITE 0x0008
-
-using ::testing::StartsWith;
-
-const std::string kMemfdName = "some-memfd";
-
-int memfd_create(const std::string& name, unsigned int flags) {
- return syscall(__NR_memfd_create, name.c_str(), flags);
-}
-
-PosixErrorOr<FileDescriptor> MemfdCreate(const std::string& name,
- uint32_t flags) {
- int fd = memfd_create(name, flags);
- if (fd < 0) {
- return PosixError(
- errno, absl::StrFormat("memfd_create(\"%s\", %#x)", name, flags));
- }
- MaybeSave();
- return FileDescriptor(fd);
-}
-
-// Procfs entries for memfds display the appropriate name.
-TEST(MemfdTest, Name) {
- const FileDescriptor memfd =
- ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, 0));
- const std::string proc_name = ASSERT_NO_ERRNO_AND_VALUE(
- ReadLink(absl::StrFormat("/proc/self/fd/%d", memfd.get())));
- EXPECT_THAT(proc_name, StartsWith("/memfd:" + kMemfdName));
-}
-
-// Memfds support read/write syscalls.
-TEST(MemfdTest, WriteRead) {
- const FileDescriptor memfd =
- ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, 0));
-
- // Write a random page of data to the memfd via write(2).
- std::vector<char> buf(kPageSize);
- RandomizeBuffer(buf.data(), buf.size());
- ASSERT_THAT(write(memfd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(kPageSize));
-
- // Read back the same data and verify.
- std::vector<char> buf2(kPageSize);
- ASSERT_THAT(lseek(memfd.get(), 0, SEEK_SET), SyscallSucceeds());
- EXPECT_THAT(read(memfd.get(), buf2.data(), buf2.size()),
- SyscallSucceedsWithValue(kPageSize));
- EXPECT_EQ(buf, buf2);
-}
-
-// Memfds can be mapped and used as usual.
-TEST(MemfdTest, Mmap) {
- const FileDescriptor memfd =
- ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, 0));
- const Mapping m1 = ASSERT_NO_ERRNO_AND_VALUE(Mmap(
- nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, memfd.get(), 0));
-
- // Write a random page of data to the memfd via mmap m1.
- std::vector<char> buf(kPageSize);
- RandomizeBuffer(buf.data(), buf.size());
- ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallSucceeds());
- memcpy(m1.ptr(), buf.data(), buf.size());
-
- // Read the data back via a read syscall on the memfd.
- std::vector<char> buf2(kPageSize);
- EXPECT_THAT(read(memfd.get(), buf2.data(), buf2.size()),
- SyscallSucceedsWithValue(kPageSize));
- EXPECT_EQ(buf, buf2);
-
- // The same data should be accessible via a new mapping m2.
- const Mapping m2 = ASSERT_NO_ERRNO_AND_VALUE(Mmap(
- nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, memfd.get(), 0));
- EXPECT_EQ(0, memcmp(m1.ptr(), m2.ptr(), kPageSize));
-}
-
-TEST(MemfdTest, DuplicateFDsShareContent) {
- const FileDescriptor memfd =
- ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, 0));
- const Mapping m1 = ASSERT_NO_ERRNO_AND_VALUE(Mmap(
- nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, memfd.get(), 0));
- const FileDescriptor memfd2 = ASSERT_NO_ERRNO_AND_VALUE(memfd.Dup());
-
- // Write a random page of data to the memfd via mmap m1.
- std::vector<char> buf(kPageSize);
- RandomizeBuffer(buf.data(), buf.size());
- ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallSucceeds());
- memcpy(m1.ptr(), buf.data(), buf.size());
-
- // Read the data back via a read syscall on a duplicate fd.
- std::vector<char> buf2(kPageSize);
- EXPECT_THAT(read(memfd2.get(), buf2.data(), buf2.size()),
- SyscallSucceedsWithValue(kPageSize));
- EXPECT_EQ(buf, buf2);
-}
-
-// File seals are disabled by default on memfds.
-TEST(MemfdTest, SealingDisabledByDefault) {
- const FileDescriptor memfd =
- ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, 0));
- EXPECT_THAT(fcntl(memfd.get(), F_GET_SEALS),
- SyscallSucceedsWithValue(F_SEAL_SEAL));
- // Attempting to set any seal should fail.
- EXPECT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE),
- SyscallFailsWithErrno(EPERM));
-}
-
-// Seals can be retrieved and updated for memfds.
-TEST(MemfdTest, SealsGetSet) {
- const FileDescriptor memfd =
- ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
- int seals;
- ASSERT_THAT(seals = fcntl(memfd.get(), F_GET_SEALS), SyscallSucceeds());
- // No seals are set yet.
- EXPECT_EQ(0, seals);
-
- // Set a seal and check that we can get it back.
- ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE), SyscallSucceeds());
- EXPECT_THAT(fcntl(memfd.get(), F_GET_SEALS),
- SyscallSucceedsWithValue(F_SEAL_WRITE));
-
- // Set some more seals and verify.
- ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK),
- SyscallSucceeds());
- EXPECT_THAT(
- fcntl(memfd.get(), F_GET_SEALS),
- SyscallSucceedsWithValue(F_SEAL_WRITE | F_SEAL_GROW | F_SEAL_SHRINK));
-
- // Attempting to set a seal that is already set is a no-op.
- ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE), SyscallSucceeds());
- EXPECT_THAT(
- fcntl(memfd.get(), F_GET_SEALS),
- SyscallSucceedsWithValue(F_SEAL_WRITE | F_SEAL_GROW | F_SEAL_SHRINK));
-
- // Add remaining seals and verify.
- ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_SEAL), SyscallSucceeds());
- EXPECT_THAT(fcntl(memfd.get(), F_GET_SEALS),
- SyscallSucceedsWithValue(F_SEAL_WRITE | F_SEAL_GROW |
- F_SEAL_SHRINK | F_SEAL_SEAL));
-}
-
-// F_SEAL_GROW prevents a memfd from being grown using ftruncate.
-TEST(MemfdTest, SealGrowWithTruncate) {
- const FileDescriptor memfd =
- ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
- ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallSucceeds());
- ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_GROW), SyscallSucceeds());
-
- // Try grow the memfd by 1 page.
- ASSERT_THAT(ftruncate(memfd.get(), kPageSize * 2),
- SyscallFailsWithErrno(EPERM));
-
- // Ftruncate calls that don't actually grow the memfd are allowed.
- ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallSucceeds());
- ASSERT_THAT(ftruncate(memfd.get(), kPageSize / 2), SyscallSucceeds());
-
- // After shrinking, growing back is not allowed.
- ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallFailsWithErrno(EPERM));
-}
-
-// F_SEAL_GROW prevents a memfd from being grown using the write syscall.
-TEST(MemfdTest, SealGrowWithWrite) {
- const FileDescriptor memfd =
- ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
-
- // Initially, writing to the memfd succeeds.
- const std::vector<char> buf(kPageSize);
- EXPECT_THAT(write(memfd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(kPageSize));
-
- // Apply F_SEAL_GROW, subsequent writes which extend the memfd should fail.
- ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_GROW), SyscallSucceeds());
- EXPECT_THAT(write(memfd.get(), buf.data(), buf.size()),
- SyscallFailsWithErrno(EPERM));
-
- // However, zero-length writes are ok since they don't grow the memfd.
- EXPECT_THAT(write(memfd.get(), buf.data(), 0), SyscallSucceeds());
-
- // Writing to existing parts of the memfd is also ok.
- ASSERT_THAT(lseek(memfd.get(), 0, SEEK_SET), SyscallSucceeds());
- EXPECT_THAT(write(memfd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(kPageSize));
-
- // Returning the end of the file and writing still not allowed.
- EXPECT_THAT(write(memfd.get(), buf.data(), buf.size()),
- SyscallFailsWithErrno(EPERM));
-}
-
-// F_SEAL_GROW causes writes which partially extend off the current EOF to
-// partially succeed, up to the page containing the EOF.
-TEST(MemfdTest, SealGrowPartialWriteTruncated) {
- const FileDescriptor memfd =
- ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
- ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallSucceeds());
- ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_GROW), SyscallSucceeds());
-
- // FD offset: 1 page, EOF: 1 page.
-
- ASSERT_THAT(lseek(memfd.get(), kPageSize * 3 / 4, SEEK_SET),
- SyscallSucceeds());
-
- // FD offset: 3/4 page. Writing a full page now should only write 1/4 page
- // worth of data. This partially succeeds because the first page is entirely
- // within the file and requires no growth, but attempting to write the final
- // 3/4 page would require growing the file.
- const std::vector<char> buf(kPageSize);
- EXPECT_THAT(write(memfd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(kPageSize / 4));
-}
-
-// F_SEAL_GROW causes writes which partially extend off the current EOF to fail
-// in its entirety if the only data written would be to the page containing the
-// EOF.
-TEST(MemfdTest, SealGrowPartialWriteTruncatedSamePage) {
- const FileDescriptor memfd =
- ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
- ASSERT_THAT(ftruncate(memfd.get(), kPageSize * 3 / 4), SyscallSucceeds());
- ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_GROW), SyscallSucceeds());
-
- // EOF: 3/4 page, writing 1/2 page starting at 1/2 page would cause the file
- // to grow. Since this would require only the page containing the EOF to be
- // modified, the write is rejected entirely.
- const std::vector<char> buf(kPageSize / 2);
- EXPECT_THAT(pwrite(memfd.get(), buf.data(), buf.size(), kPageSize / 2),
- SyscallFailsWithErrno(EPERM));
-
- // However, writing up to EOF is fine.
- EXPECT_THAT(pwrite(memfd.get(), buf.data(), buf.size() / 2, kPageSize / 2),
- SyscallSucceedsWithValue(kPageSize / 4));
-}
-
-// F_SEAL_SHRINK prevents a memfd from being shrunk using ftruncate.
-TEST(MemfdTest, SealShrink) {
- const FileDescriptor memfd =
- ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
- ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallSucceeds());
- ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_SHRINK),
- SyscallSucceeds());
-
- // Shrink by half a page.
- ASSERT_THAT(ftruncate(memfd.get(), kPageSize / 2),
- SyscallFailsWithErrno(EPERM));
-
- // Ftruncate calls that don't actually shrink the file are allowed.
- ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallSucceeds());
- ASSERT_THAT(ftruncate(memfd.get(), kPageSize * 2), SyscallSucceeds());
-
- // After growing, shrinking is still not allowed.
- ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallFailsWithErrno(EPERM));
-}
-
-// F_SEAL_WRITE prevents a memfd from being written to through a write
-// syscall.
-TEST(MemfdTest, SealWriteWithWrite) {
- const FileDescriptor memfd =
- ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
- const std::vector<char> buf(kPageSize);
- ASSERT_THAT(write(memfd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(kPageSize));
- ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE), SyscallSucceeds());
-
- // Attemping to write at the end of the file fails.
- EXPECT_THAT(write(memfd.get(), buf.data(), 1), SyscallFailsWithErrno(EPERM));
-
- // Attemping to overwrite an existing part of the memfd fails.
- EXPECT_THAT(pwrite(memfd.get(), buf.data(), 1, 0),
- SyscallFailsWithErrno(EPERM));
- EXPECT_THAT(pwrite(memfd.get(), buf.data(), buf.size() / 2, kPageSize / 2),
- SyscallFailsWithErrno(EPERM));
- EXPECT_THAT(pwrite(memfd.get(), buf.data(), buf.size(), kPageSize / 2),
- SyscallFailsWithErrno(EPERM));
-
- // Zero-length writes however do not fail.
- EXPECT_THAT(write(memfd.get(), buf.data(), 0), SyscallSucceeds());
-}
-
-// F_SEAL_WRITE prevents a memfd from being written to through an mmap.
-TEST(MemfdTest, SealWriteWithMmap) {
- const FileDescriptor memfd =
- ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
- const std::vector<char> buf(kPageSize);
- ASSERT_THAT(write(memfd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(kPageSize));
- ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE), SyscallSucceeds());
-
- // Can't create a shared mapping with writes sealed.
- void* ret = mmap(nullptr, kPageSize, PROT_WRITE, MAP_SHARED, memfd.get(), 0);
- EXPECT_EQ(ret, MAP_FAILED);
- EXPECT_EQ(errno, EPERM);
- ret = mmap(nullptr, kPageSize, PROT_READ, MAP_SHARED, memfd.get(), 0);
- EXPECT_EQ(ret, MAP_FAILED);
- EXPECT_EQ(errno, EPERM);
-
- // However, private mappings are ok.
- EXPECT_NO_ERRNO(Mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE,
- memfd.get(), 0));
-}
-
-// Adding F_SEAL_WRITE fails when there are outstanding writable mappings to a
-// memfd.
-TEST(MemfdTest, SealWriteWithOutstandingWritbleMapping) {
- const FileDescriptor memfd =
- ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
- const std::vector<char> buf(kPageSize);
- ASSERT_THAT(write(memfd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(kPageSize));
-
- // Attempting to add F_SEAL_WRITE with active shared mapping with any set of
- // permissions fails.
-
- // Read-only shared mapping.
- {
- const Mapping m = ASSERT_NO_ERRNO_AND_VALUE(
- Mmap(nullptr, kPageSize, PROT_READ, MAP_SHARED, memfd.get(), 0));
- EXPECT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE),
- SyscallFailsWithErrno(EBUSY));
- }
-
- // Write-only shared mapping.
- {
- const Mapping m = ASSERT_NO_ERRNO_AND_VALUE(
- Mmap(nullptr, kPageSize, PROT_WRITE, MAP_SHARED, memfd.get(), 0));
- EXPECT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE),
- SyscallFailsWithErrno(EBUSY));
- }
-
- // Read-write shared mapping.
- {
- const Mapping m = ASSERT_NO_ERRNO_AND_VALUE(
- Mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED,
- memfd.get(), 0));
- EXPECT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE),
- SyscallFailsWithErrno(EBUSY));
- }
-
- // F_SEAL_WRITE can be set with private mappings with any permissions.
- {
- const Mapping m = ASSERT_NO_ERRNO_AND_VALUE(
- Mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE,
- memfd.get(), 0));
- EXPECT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE),
- SyscallSucceeds());
- }
-}
-
-// When applying F_SEAL_WRITE fails due to outstanding writable mappings, any
-// additional seals passed to the same add seal call are also rejected.
-TEST(MemfdTest, NoPartialSealApplicationWhenWriteSealRejected) {
- const FileDescriptor memfd =
- ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
- const Mapping m = ASSERT_NO_ERRNO_AND_VALUE(Mmap(
- nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, memfd.get(), 0));
-
- // Try add some seals along with F_SEAL_WRITE. The seal application should
- // fail since there exists an active shared mapping.
- EXPECT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE | F_SEAL_GROW),
- SyscallFailsWithErrno(EBUSY));
-
- // None of the seals should be applied.
- EXPECT_THAT(fcntl(memfd.get(), F_GET_SEALS), SyscallSucceedsWithValue(0));
-}
-
-// Seals are inode level properties, and apply to all file descriptors referring
-// to a memfd.
-TEST(MemfdTest, SealsAreInodeLevelProperties) {
- const FileDescriptor memfd =
- ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
- const FileDescriptor memfd2 = ASSERT_NO_ERRNO_AND_VALUE(memfd.Dup());
-
- // Add seal through the original memfd, and verify that it appears on the
- // dupped fd.
- ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE), SyscallSucceeds());
- EXPECT_THAT(fcntl(memfd2.get(), F_GET_SEALS),
- SyscallSucceedsWithValue(F_SEAL_WRITE));
-
- // Verify the seal actually applies to both fds.
- std::vector<char> buf(kPageSize);
- EXPECT_THAT(write(memfd.get(), buf.data(), buf.size()),
- SyscallFailsWithErrno(EPERM));
- EXPECT_THAT(write(memfd2.get(), buf.data(), buf.size()),
- SyscallFailsWithErrno(EPERM));
-
- // Seals are enforced on new FDs that are dupped after the seal is already
- // applied.
- const FileDescriptor memfd3 = ASSERT_NO_ERRNO_AND_VALUE(memfd2.Dup());
- EXPECT_THAT(write(memfd3.get(), buf.data(), buf.size()),
- SyscallFailsWithErrno(EPERM));
-
- // Try a new seal applied to one of the dupped fds.
- ASSERT_THAT(fcntl(memfd3.get(), F_ADD_SEALS, F_SEAL_GROW), SyscallSucceeds());
- EXPECT_THAT(ftruncate(memfd.get(), kPageSize), SyscallFailsWithErrno(EPERM));
- EXPECT_THAT(ftruncate(memfd2.get(), kPageSize), SyscallFailsWithErrno(EPERM));
- EXPECT_THAT(ftruncate(memfd3.get(), kPageSize), SyscallFailsWithErrno(EPERM));
-}
-
-PosixErrorOr<bool> IsTmpfs(const std::string& path) {
- struct statfs stat;
- if (statfs(path.c_str(), &stat)) {
- if (errno == ENOENT) {
- // Nothing at path, don't raise this as an error. Instead, just report no
- // tmpfs at path.
- return false;
- }
- return PosixError(errno,
- absl::StrFormat("statfs(\"%s\", %#p)", path, &stat));
- }
- return stat.f_type == TMPFS_MAGIC;
-}
-
-// Tmpfs files also support seals, but are created with F_SEAL_SEAL.
-TEST(MemfdTest, TmpfsFilesHaveSealSeal) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs("/tmp")));
- const TempPath tmpfs_file =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn("/tmp"));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfs_file.path(), O_RDWR, 0644));
- EXPECT_THAT(fcntl(fd.get(), F_GET_SEALS),
- SyscallSucceedsWithValue(F_SEAL_SEAL));
-}
-
-// Can open a memfd from procfs and use as normal.
-TEST(MemfdTest, CanOpenFromProcfs) {
- const FileDescriptor memfd =
- ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
-
- // Write a random page of data to the memfd via write(2).
- std::vector<char> buf(kPageSize);
- RandomizeBuffer(buf.data(), buf.size());
- ASSERT_THAT(write(memfd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(kPageSize));
-
- // Read back the same data from the fd obtained from procfs and verify.
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(
- Open(absl::StrFormat("/proc/self/fd/%d", memfd.get()), O_RDWR));
- std::vector<char> buf2(kPageSize);
- EXPECT_THAT(pread(fd.get(), buf2.data(), buf2.size(), 0),
- SyscallSucceedsWithValue(kPageSize));
- EXPECT_EQ(buf, buf2);
-}
-
-// Test that memfd permissions are set up correctly to allow another process to
-// open it from procfs.
-TEST(MemfdTest, OtherProcessCanOpenFromProcfs) {
- const FileDescriptor memfd =
- ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
- const auto memfd_path =
- absl::StrFormat("/proc/%d/fd/%d", getpid(), memfd.get());
- const auto rest = [&] {
- int fd = open(memfd_path.c_str(), O_RDWR);
- TEST_PCHECK(fd >= 0);
- TEST_PCHECK(close(fd) >= 0);
- };
- EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
-}
-
-// Test that only files opened as writable can have seals applied to them.
-// Normally there's no way to specify file permissions on memfds, but we can
-// obtain a read-only memfd by opening the corresponding procfs fd entry as
-// read-only.
-TEST(MemfdTest, MemfdMustBeWritableToModifySeals) {
- const FileDescriptor memfd =
- ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING));
-
- // Initially adding a seal works.
- EXPECT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE), SyscallSucceeds());
-
- // Re-open the memfd as read-only from procfs.
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(
- Open(absl::StrFormat("/proc/self/fd/%d", memfd.get()), O_RDONLY));
-
- // Can't add seals through an unwritable fd.
- EXPECT_THAT(fcntl(fd.get(), F_ADD_SEALS, F_SEAL_GROW),
- SyscallFailsWithErrno(EPERM));
-}
-
-// Test that the memfd implementation internally tracks potentially writable
-// maps correctly.
-TEST(MemfdTest, MultipleWritableAndNonWritableRefsToSameFileRegion) {
- const FileDescriptor memfd =
- ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, 0));
-
- // Populate with a random page of data.
- std::vector<char> buf(kPageSize);
- RandomizeBuffer(buf.data(), buf.size());
- ASSERT_THAT(write(memfd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(kPageSize));
-
- // Read-only map to the page. This should cause an initial mapping to be
- // created.
- Mapping m1 = ASSERT_NO_ERRNO_AND_VALUE(
- Mmap(nullptr, kPageSize, PROT_READ, MAP_PRIVATE, memfd.get(), 0));
-
- // Create a shared writable map to the page. This should cause the internal
- // mapping to become potentially writable.
- Mapping m2 = ASSERT_NO_ERRNO_AND_VALUE(Mmap(
- nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, memfd.get(), 0));
-
- // Drop the read-only mapping first. If writable-ness isn't tracked correctly,
- // this can cause some misaccounting, which can trigger asserts internally.
- m1.reset();
- m2.reset();
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/memory_accounting.cc b/test/syscalls/linux/memory_accounting.cc
deleted file mode 100644
index a6e20f9c3..000000000
--- a/test/syscalls/linux/memory_accounting.cc
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sys/mman.h>
-#include <map>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "absl/strings/match.h"
-#include "absl/strings/numbers.h"
-#include "absl/strings/str_format.h"
-#include "absl/strings/str_split.h"
-#include "test/util/fs_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-using ::absl::StrFormat;
-
-// AnonUsageFromMeminfo scrapes the current anonymous memory usage from
-// /proc/meminfo and returns it in bytes.
-PosixErrorOr<uint64_t> AnonUsageFromMeminfo() {
- ASSIGN_OR_RETURN_ERRNO(auto meminfo, GetContents("/proc/meminfo"));
- std::vector<std::string> lines(absl::StrSplit(meminfo, '\n'));
-
- // Try to find AnonPages line, the format is AnonPages:\\s+(\\d+) kB\n.
- for (const auto& line : lines) {
- if (!absl::StartsWith(line, "AnonPages:")) {
- continue;
- }
-
- std::vector<std::string> parts(
- absl::StrSplit(line, ' ', absl::SkipEmpty()));
- if (parts.size() == 3) {
- // The size is the second field, let's try to parse it as a number.
- ASSIGN_OR_RETURN_ERRNO(auto anon_kb, Atoi<uint64_t>(parts[1]));
- return anon_kb * 1024;
- }
-
- return PosixError(EINVAL, "AnonPages field in /proc/meminfo was malformed");
- }
-
- return PosixError(EINVAL, "AnonPages field not found in /proc/meminfo");
-}
-
-TEST(MemoryAccounting, AnonAccountingPreservedOnSaveRestore) {
- // This test isn't meaningful on Linux. /proc/meminfo reports system-wide
- // memory usage, which can change arbitrarily in Linux from other activity on
- // the machine. In gvisor, this test is the only thing running on the
- // "machine", so values in /proc/meminfo accurately reflect the memory used by
- // the test.
- SKIP_IF(!IsRunningOnGvisor());
-
- uint64_t anon_initial = ASSERT_NO_ERRNO_AND_VALUE(AnonUsageFromMeminfo());
-
- // Cause some anonymous memory usage.
- uint64_t map_bytes = Megabytes(512);
- char* mem =
- static_cast<char*>(mmap(nullptr, map_bytes, PROT_READ | PROT_WRITE,
- MAP_POPULATE | MAP_ANON | MAP_PRIVATE, -1, 0));
- ASSERT_NE(mem, MAP_FAILED)
- << "Map failed, errno: " << errno << " (" << strerror(errno) << ").";
-
- // Write something to each page to prevent them from being decommited on
- // S/R. Zero pages are dropped on save.
- for (uint64_t i = 0; i < map_bytes; i += kPageSize) {
- mem[i] = 'a';
- }
-
- uint64_t anon_after_alloc = ASSERT_NO_ERRNO_AND_VALUE(AnonUsageFromMeminfo());
- EXPECT_THAT(anon_after_alloc,
- EquivalentWithin(anon_initial + map_bytes, 0.03));
-
- // We have many implicit S/R cycles from scraping /proc/meminfo throughout the
- // test, but throw an explicit S/R in here as well.
- MaybeSave();
-
- // Usage should remain the same across S/R.
- uint64_t anon_after_sr = ASSERT_NO_ERRNO_AND_VALUE(AnonUsageFromMeminfo());
- EXPECT_THAT(anon_after_sr, EquivalentWithin(anon_after_alloc, 0.03));
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/mempolicy.cc b/test/syscalls/linux/mempolicy.cc
deleted file mode 100644
index 9d5f47651..000000000
--- a/test/syscalls/linux/mempolicy.cc
+++ /dev/null
@@ -1,289 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <sys/syscall.h>
-
-#include "gtest/gtest.h"
-#include "absl/memory/memory.h"
-#include "test/util/cleanup.h"
-#include "test/util/memory_util.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-#define BITS_PER_BYTE 8
-
-#define MPOL_F_STATIC_NODES (1 << 15)
-#define MPOL_F_RELATIVE_NODES (1 << 14)
-#define MPOL_DEFAULT 0
-#define MPOL_PREFERRED 1
-#define MPOL_BIND 2
-#define MPOL_INTERLEAVE 3
-#define MPOL_LOCAL 4
-#define MPOL_F_NODE (1 << 0)
-#define MPOL_F_ADDR (1 << 1)
-#define MPOL_F_MEMS_ALLOWED (1 << 2)
-#define MPOL_MF_STRICT (1 << 0)
-#define MPOL_MF_MOVE (1 << 1)
-#define MPOL_MF_MOVE_ALL (1 << 2)
-
-int get_mempolicy(int *policy, uint64_t *nmask, uint64_t maxnode, void *addr,
- int flags) {
- return syscall(SYS_get_mempolicy, policy, nmask, maxnode, addr, flags);
-}
-
-int set_mempolicy(int mode, uint64_t *nmask, uint64_t maxnode) {
- return syscall(SYS_set_mempolicy, mode, nmask, maxnode);
-}
-
-int mbind(void *addr, unsigned long len, int mode,
- const unsigned long *nodemask, unsigned long maxnode,
- unsigned flags) {
- return syscall(SYS_mbind, addr, len, mode, nodemask, maxnode, flags);
-}
-
-// Creates a cleanup object that resets the calling thread's mempolicy to the
-// system default when the calling scope ends.
-Cleanup ScopedMempolicy() {
- return Cleanup([] {
- EXPECT_THAT(set_mempolicy(MPOL_DEFAULT, nullptr, 0), SyscallSucceeds());
- });
-}
-
-// Temporarily change the memory policy for the calling thread within the
-// caller's scope.
-PosixErrorOr<Cleanup> ScopedSetMempolicy(int mode, uint64_t *nmask,
- uint64_t maxnode) {
- if (set_mempolicy(mode, nmask, maxnode)) {
- return PosixError(errno, "set_mempolicy");
- }
- return ScopedMempolicy();
-}
-
-TEST(MempolicyTest, CheckDefaultPolicy) {
- int mode = 0;
- uint64_t nodemask = 0;
- ASSERT_THAT(get_mempolicy(&mode, &nodemask, sizeof(nodemask) * BITS_PER_BYTE,
- nullptr, 0),
- SyscallSucceeds());
-
- EXPECT_EQ(MPOL_DEFAULT, mode);
- EXPECT_EQ(0x0, nodemask);
-}
-
-TEST(MempolicyTest, PolicyPreservedAfterSetMempolicy) {
- uint64_t nodemask = 0x1;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSetMempolicy(
- MPOL_BIND, &nodemask, sizeof(nodemask) * BITS_PER_BYTE));
-
- int mode = 0;
- uint64_t nodemask_after = 0x0;
- ASSERT_THAT(get_mempolicy(&mode, &nodemask_after,
- sizeof(nodemask_after) * BITS_PER_BYTE, nullptr, 0),
- SyscallSucceeds());
- EXPECT_EQ(MPOL_BIND, mode);
- EXPECT_EQ(0x1, nodemask_after);
-
- // Try throw in some mode flags.
- for (auto mode_flag : {MPOL_F_STATIC_NODES, MPOL_F_RELATIVE_NODES}) {
- auto cleanup2 = ASSERT_NO_ERRNO_AND_VALUE(
- ScopedSetMempolicy(MPOL_INTERLEAVE | mode_flag, &nodemask,
- sizeof(nodemask) * BITS_PER_BYTE));
- mode = 0;
- nodemask_after = 0x0;
- ASSERT_THAT(
- get_mempolicy(&mode, &nodemask_after,
- sizeof(nodemask_after) * BITS_PER_BYTE, nullptr, 0),
- SyscallSucceeds());
- EXPECT_EQ(MPOL_INTERLEAVE | mode_flag, mode);
- EXPECT_EQ(0x1, nodemask_after);
- }
-}
-
-TEST(MempolicyTest, SetMempolicyRejectsInvalidInputs) {
- auto cleanup = ScopedMempolicy();
- uint64_t nodemask;
-
- if (IsRunningOnGvisor()) {
- // Invalid nodemask, we only support a single node on gvisor.
- nodemask = 0x4;
- ASSERT_THAT(set_mempolicy(MPOL_DEFAULT, &nodemask,
- sizeof(nodemask) * BITS_PER_BYTE),
- SyscallFailsWithErrno(EINVAL));
- }
-
- nodemask = 0x1;
-
- // Invalid mode.
- ASSERT_THAT(set_mempolicy(7439, &nodemask, sizeof(nodemask) * BITS_PER_BYTE),
- SyscallFailsWithErrno(EINVAL));
-
- // Invalid nodemask size.
- ASSERT_THAT(set_mempolicy(MPOL_DEFAULT, &nodemask, 0),
- SyscallFailsWithErrno(EINVAL));
-
- // Invalid mode flag.
- ASSERT_THAT(
- set_mempolicy(MPOL_DEFAULT | MPOL_F_STATIC_NODES | MPOL_F_RELATIVE_NODES,
- &nodemask, sizeof(nodemask) * BITS_PER_BYTE),
- SyscallFailsWithErrno(EINVAL));
-
- // MPOL_INTERLEAVE with empty nodemask.
- nodemask = 0x0;
- ASSERT_THAT(set_mempolicy(MPOL_INTERLEAVE, &nodemask,
- sizeof(nodemask) * BITS_PER_BYTE),
- SyscallFailsWithErrno(EINVAL));
-}
-
-// The manpages specify that the nodemask provided to set_mempolicy are
-// considered empty if the nodemask pointer is null, or if the nodemask size is
-// 0. We use a policy which accepts both empty and non-empty nodemasks
-// (MPOL_PREFERRED), a policy which requires a non-empty nodemask (MPOL_BIND),
-// and a policy which completely ignores the nodemask (MPOL_DEFAULT) to verify
-// argument checking around nodemasks.
-TEST(MempolicyTest, EmptyNodemaskOnSet) {
- auto cleanup = ScopedMempolicy();
-
- EXPECT_THAT(set_mempolicy(MPOL_DEFAULT, nullptr, 1), SyscallSucceeds());
- EXPECT_THAT(set_mempolicy(MPOL_BIND, nullptr, 1),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(set_mempolicy(MPOL_PREFERRED, nullptr, 1), SyscallSucceeds());
-
- uint64_t nodemask = 0x1;
- EXPECT_THAT(set_mempolicy(MPOL_DEFAULT, &nodemask, 0),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(set_mempolicy(MPOL_BIND, &nodemask, 0),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(set_mempolicy(MPOL_PREFERRED, &nodemask, 0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(MempolicyTest, QueryAvailableNodes) {
- uint64_t nodemask = 0;
- ASSERT_THAT(
- get_mempolicy(nullptr, &nodemask, sizeof(nodemask) * BITS_PER_BYTE,
- nullptr, MPOL_F_MEMS_ALLOWED),
- SyscallSucceeds());
- // We can only be sure there is a single node if running on gvisor.
- if (IsRunningOnGvisor()) {
- EXPECT_EQ(0x1, nodemask);
- }
-
- // MPOL_F_ADDR and MPOL_F_NODE flags may not be combined with
- // MPOL_F_MEMS_ALLLOWED.
- for (auto flags :
- {MPOL_F_MEMS_ALLOWED | MPOL_F_ADDR, MPOL_F_MEMS_ALLOWED | MPOL_F_NODE,
- MPOL_F_MEMS_ALLOWED | MPOL_F_ADDR | MPOL_F_NODE}) {
- ASSERT_THAT(get_mempolicy(nullptr, &nodemask,
- sizeof(nodemask) * BITS_PER_BYTE, nullptr, flags),
- SyscallFailsWithErrno(EINVAL));
- }
-}
-
-TEST(MempolicyTest, GetMempolicyQueryNodeForAddress) {
- uint64_t dummy_stack_address;
- auto dummy_heap_address = absl::make_unique<uint64_t>();
- int mode;
-
- for (auto ptr : {&dummy_stack_address, dummy_heap_address.get()}) {
- mode = -1;
- ASSERT_THAT(
- get_mempolicy(&mode, nullptr, 0, ptr, MPOL_F_ADDR | MPOL_F_NODE),
- SyscallSucceeds());
- // If we're not running on gvisor, the address may be allocated on a
- // different numa node.
- if (IsRunningOnGvisor()) {
- EXPECT_EQ(0, mode);
- }
- }
-
- void* invalid_address = reinterpret_cast<void*>(-1);
-
- // Invalid address.
- ASSERT_THAT(get_mempolicy(&mode, nullptr, 0, invalid_address,
- MPOL_F_ADDR | MPOL_F_NODE),
- SyscallFailsWithErrno(EFAULT));
-
- // Invalid mode pointer.
- ASSERT_THAT(get_mempolicy(reinterpret_cast<int*>(invalid_address), nullptr, 0,
- &dummy_stack_address, MPOL_F_ADDR | MPOL_F_NODE),
- SyscallFailsWithErrno(EFAULT));
-}
-
-TEST(MempolicyTest, GetMempolicyCanOmitPointers) {
- int mode;
- uint64_t nodemask;
-
- // Omit nodemask pointer.
- ASSERT_THAT(get_mempolicy(&mode, nullptr, 0, nullptr, 0), SyscallSucceeds());
- // Omit mode pointer.
- ASSERT_THAT(get_mempolicy(nullptr, &nodemask,
- sizeof(nodemask) * BITS_PER_BYTE, nullptr, 0),
- SyscallSucceeds());
- // Omit both pointers.
- ASSERT_THAT(get_mempolicy(nullptr, nullptr, 0, nullptr, 0),
- SyscallSucceeds());
-}
-
-TEST(MempolicyTest, GetMempolicyNextInterleaveNode) {
- int mode;
- // Policy for thread not yet set to MPOL_INTERLEAVE, can't query for
- // the next node which will be used for allocation.
- ASSERT_THAT(get_mempolicy(&mode, nullptr, 0, nullptr, MPOL_F_NODE),
- SyscallFailsWithErrno(EINVAL));
-
- // Set default policy for thread to MPOL_INTERLEAVE.
- uint64_t nodemask = 0x1;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSetMempolicy(
- MPOL_INTERLEAVE, &nodemask, sizeof(nodemask) * BITS_PER_BYTE));
-
- mode = -1;
- ASSERT_THAT(get_mempolicy(&mode, nullptr, 0, nullptr, MPOL_F_NODE),
- SyscallSucceeds());
- EXPECT_EQ(0, mode);
-}
-
-TEST(MempolicyTest, Mbind) {
- // Temporarily set the thread policy to MPOL_PREFERRED.
- const auto cleanup_thread_policy =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSetMempolicy(MPOL_PREFERRED, nullptr, 0));
-
- const auto mapping = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS));
-
- // vmas default to MPOL_DEFAULT irrespective of the thread policy (currently
- // MPOL_PREFERRED).
- int mode;
- ASSERT_THAT(get_mempolicy(&mode, nullptr, 0, mapping.ptr(), MPOL_F_ADDR),
- SyscallSucceeds());
- EXPECT_EQ(mode, MPOL_DEFAULT);
-
- // Set MPOL_PREFERRED for the vma and read it back.
- ASSERT_THAT(
- mbind(mapping.ptr(), mapping.len(), MPOL_PREFERRED, nullptr, 0, 0),
- SyscallSucceeds());
- ASSERT_THAT(get_mempolicy(&mode, nullptr, 0, mapping.ptr(), MPOL_F_ADDR),
- SyscallSucceeds());
- EXPECT_EQ(mode, MPOL_PREFERRED);
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/mincore.cc b/test/syscalls/linux/mincore.cc
deleted file mode 100644
index 5c1240c89..000000000
--- a/test/syscalls/linux/mincore.cc
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <stdint.h>
-#include <string.h>
-#include <sys/mman.h>
-#include <unistd.h>
-
-#include <algorithm>
-#include <vector>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "test/util/memory_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-size_t CountSetLSBs(std::vector<unsigned char> const& vec) {
- return std::count_if(begin(vec), end(vec),
- [](unsigned char c) { return (c & 1) != 0; });
-}
-
-TEST(MincoreTest, DirtyAnonPagesAreResident) {
- constexpr size_t kTestPageCount = 10;
- auto const kTestMappingBytes = kTestPageCount * kPageSize;
- auto m = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kTestMappingBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE));
- memset(m.ptr(), 0, m.len());
-
- std::vector<unsigned char> vec(kTestPageCount, 0);
- ASSERT_THAT(mincore(m.ptr(), kTestMappingBytes, vec.data()),
- SyscallSucceeds());
- EXPECT_EQ(kTestPageCount, CountSetLSBs(vec));
-}
-
-TEST(MincoreTest, UnalignedAddressFails) {
- // Map and touch two pages, then try to mincore the second half of the first
- // page + the first half of the second page. Both pages are mapped, but
- // mincore should return EINVAL due to the misaligned start address.
- constexpr size_t kTestPageCount = 2;
- auto const kTestMappingBytes = kTestPageCount * kPageSize;
- auto m = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kTestMappingBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE));
- memset(m.ptr(), 0, m.len());
-
- std::vector<unsigned char> vec(kTestPageCount, 0);
- EXPECT_THAT(mincore(reinterpret_cast<void*>(m.addr() + kPageSize / 2),
- kPageSize, vec.data()),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(MincoreTest, UnalignedLengthSucceedsAndIsRoundedUp) {
- // Map and touch two pages, then try to mincore the first page + the first
- // half of the second page. mincore should silently round up the length to
- // include both pages.
- constexpr size_t kTestPageCount = 2;
- auto const kTestMappingBytes = kTestPageCount * kPageSize;
- auto m = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kTestMappingBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE));
- memset(m.ptr(), 0, m.len());
-
- std::vector<unsigned char> vec(kTestPageCount, 0);
- ASSERT_THAT(mincore(m.ptr(), kPageSize + kPageSize / 2, vec.data()),
- SyscallSucceeds());
- EXPECT_EQ(kTestPageCount, CountSetLSBs(vec));
-}
-
-TEST(MincoreTest, ZeroLengthSucceedsAndAllowsAnyVecBelowTaskSize) {
- EXPECT_THAT(mincore(nullptr, 0, nullptr), SyscallSucceeds());
-}
-
-TEST(MincoreTest, InvalidLengthFails) {
- EXPECT_THAT(mincore(nullptr, -1, nullptr), SyscallFailsWithErrno(ENOMEM));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/mkdir.cc b/test/syscalls/linux/mkdir.cc
deleted file mode 100644
index cf138d328..000000000
--- a/test/syscalls/linux/mkdir.cc
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/temp_umask.h"
-#include "test/util/capability_util.h"
-#include "test/util/fs_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-class MkdirTest : public ::testing::Test {
- protected:
- // SetUp creates various configurations of files.
- void SetUp() override { dirname_ = NewTempAbsPath(); }
-
- // TearDown unlinks created files.
- void TearDown() override {
- // FIXME(edahlgren): We don't currently implement rmdir.
- // We do this unconditionally because there's no harm in trying.
- rmdir(dirname_.c_str());
- }
-
- std::string dirname_;
-};
-
-TEST_F(MkdirTest, DISABLED_CanCreateReadbleDir) {
- ASSERT_THAT(mkdir(dirname_.c_str(), 0444), SyscallSucceeds());
- ASSERT_THAT(
- open(JoinPath(dirname_, "anything").c_str(), O_RDWR | O_CREAT, 0666),
- SyscallFailsWithErrno(EACCES));
-}
-
-TEST_F(MkdirTest, CanCreateWritableDir) {
- ASSERT_THAT(mkdir(dirname_.c_str(), 0777), SyscallSucceeds());
- std::string filename = JoinPath(dirname_, "anything");
- int fd;
- ASSERT_THAT(fd = open(filename.c_str(), O_RDWR | O_CREAT, 0666),
- SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
- ASSERT_THAT(unlink(filename.c_str()), SyscallSucceeds());
-}
-
-TEST_F(MkdirTest, HonorsUmask) {
- constexpr mode_t kMask = 0111;
- TempUmask mask(kMask);
- ASSERT_THAT(mkdir(dirname_.c_str(), 0777), SyscallSucceeds());
- struct stat statbuf;
- ASSERT_THAT(stat(dirname_.c_str(), &statbuf), SyscallSucceeds());
- EXPECT_EQ(0777 & ~kMask, statbuf.st_mode & 0777);
-}
-
-TEST_F(MkdirTest, HonorsUmask2) {
- constexpr mode_t kMask = 0142;
- TempUmask mask(kMask);
- ASSERT_THAT(mkdir(dirname_.c_str(), 0777), SyscallSucceeds());
- struct stat statbuf;
- ASSERT_THAT(stat(dirname_.c_str(), &statbuf), SyscallSucceeds());
- EXPECT_EQ(0777 & ~kMask, statbuf.st_mode & 0777);
-}
-
-TEST_F(MkdirTest, FailsOnDirWithoutWritePerms) {
- // Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
-
- auto parent = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0555));
- auto dir = JoinPath(parent.path(), "foo");
- ASSERT_THAT(mkdir(dir.c_str(), 0777), SyscallFailsWithErrno(EACCES));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/mknod.cc b/test/syscalls/linux/mknod.cc
deleted file mode 100644
index 4c45766c7..000000000
--- a/test/syscalls/linux/mknod.cc
+++ /dev/null
@@ -1,168 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/un.h>
-#include <unistd.h>
-
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(MknodTest, RegularFile) {
- const std::string node0 = NewTempAbsPath();
- EXPECT_THAT(mknod(node0.c_str(), S_IFREG, 0), SyscallSucceeds());
-
- const std::string node1 = NewTempAbsPath();
- EXPECT_THAT(mknod(node1.c_str(), 0, 0), SyscallSucceeds());
-}
-
-TEST(MknodTest, MknodAtRegularFile) {
- const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const std::string fifo_relpath = NewTempRelPath();
- const std::string fifo = JoinPath(dir.path(), fifo_relpath);
-
- const FileDescriptor dirfd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path().c_str(), O_RDONLY));
- ASSERT_THAT(mknodat(dirfd.get(), fifo_relpath.c_str(), S_IFIFO | S_IRUSR, 0),
- SyscallSucceeds());
-
- struct stat st;
- ASSERT_THAT(stat(fifo.c_str(), &st), SyscallSucceeds());
- EXPECT_TRUE(S_ISFIFO(st.st_mode));
-}
-
-TEST(MknodTest, MknodOnExistingPathFails) {
- const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const TempPath slink = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateSymlinkTo(GetAbsoluteTestTmpdir(), file.path()));
-
- EXPECT_THAT(mknod(file.path().c_str(), S_IFREG, 0),
- SyscallFailsWithErrno(EEXIST));
- EXPECT_THAT(mknod(file.path().c_str(), S_IFIFO, 0),
- SyscallFailsWithErrno(EEXIST));
- EXPECT_THAT(mknod(slink.path().c_str(), S_IFREG, 0),
- SyscallFailsWithErrno(EEXIST));
- EXPECT_THAT(mknod(slink.path().c_str(), S_IFIFO, 0),
- SyscallFailsWithErrno(EEXIST));
-}
-
-TEST(MknodTest, UnimplementedTypesReturnError) {
- const std::string path = NewTempAbsPath();
-
- if (IsRunningOnGvisor()) {
- ASSERT_THAT(mknod(path.c_str(), S_IFSOCK, 0),
- SyscallFailsWithErrno(EOPNOTSUPP));
- }
- // These will fail on linux as well since we don't have CAP_MKNOD.
- ASSERT_THAT(mknod(path.c_str(), S_IFCHR, 0), SyscallFailsWithErrno(EPERM));
- ASSERT_THAT(mknod(path.c_str(), S_IFBLK, 0), SyscallFailsWithErrno(EPERM));
-}
-
-TEST(MknodTest, Fifo) {
- const std::string fifo = NewTempAbsPath();
- ASSERT_THAT(mknod(fifo.c_str(), S_IFIFO | S_IRUSR | S_IWUSR, 0),
- SyscallSucceeds());
-
- struct stat st;
- ASSERT_THAT(stat(fifo.c_str(), &st), SyscallSucceeds());
- EXPECT_TRUE(S_ISFIFO(st.st_mode));
-
- std::string msg = "some std::string";
- std::vector<char> buf(512);
-
- // Read-end of the pipe.
- ScopedThread t([&fifo, &buf, &msg]() {
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(fifo.c_str(), O_RDONLY));
- EXPECT_THAT(ReadFd(fd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(msg.length()));
- EXPECT_EQ(msg, std::string(buf.data()));
- });
-
- // Write-end of the pipe.
- FileDescriptor wfd = ASSERT_NO_ERRNO_AND_VALUE(Open(fifo.c_str(), O_WRONLY));
- EXPECT_THAT(WriteFd(wfd.get(), msg.c_str(), msg.length()),
- SyscallSucceedsWithValue(msg.length()));
-}
-
-TEST(MknodTest, FifoOtrunc) {
- const std::string fifo = NewTempAbsPath();
- ASSERT_THAT(mknod(fifo.c_str(), S_IFIFO | S_IRUSR | S_IWUSR, 0),
- SyscallSucceeds());
-
- struct stat st = {};
- ASSERT_THAT(stat(fifo.c_str(), &st), SyscallSucceeds());
- EXPECT_TRUE(S_ISFIFO(st.st_mode));
-
- std::string msg = "some std::string";
- std::vector<char> buf(512);
- // Read-end of the pipe.
- ScopedThread t([&fifo, &buf, &msg]() {
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(fifo.c_str(), O_RDONLY));
- EXPECT_THAT(ReadFd(fd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(msg.length()));
- EXPECT_EQ(msg, std::string(buf.data()));
- });
-
- // Write-end of the pipe.
- FileDescriptor wfd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(fifo.c_str(), O_WRONLY | O_TRUNC));
- EXPECT_THAT(WriteFd(wfd.get(), msg.c_str(), msg.length()),
- SyscallSucceedsWithValue(msg.length()));
-}
-
-TEST(MknodTest, FifoTruncNoOp) {
- const std::string fifo = NewTempAbsPath();
- ASSERT_THAT(mknod(fifo.c_str(), S_IFIFO | S_IRUSR | S_IWUSR, 0),
- SyscallSucceeds());
-
- EXPECT_THAT(truncate(fifo.c_str(), 0), SyscallFailsWithErrno(EINVAL));
-
- struct stat st = {};
- ASSERT_THAT(stat(fifo.c_str(), &st), SyscallSucceeds());
- EXPECT_TRUE(S_ISFIFO(st.st_mode));
-
- std::string msg = "some std::string";
- std::vector<char> buf(512);
- // Read-end of the pipe.
- ScopedThread t([&fifo, &buf, &msg]() {
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(fifo.c_str(), O_RDONLY));
- EXPECT_THAT(ReadFd(fd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(msg.length()));
- EXPECT_EQ(msg, std::string(buf.data()));
- });
-
- FileDescriptor wfd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(fifo.c_str(), O_WRONLY | O_TRUNC));
- EXPECT_THAT(ftruncate(wfd.get(), 0), SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(WriteFd(wfd.get(), msg.c_str(), msg.length()),
- SyscallSucceedsWithValue(msg.length()));
- EXPECT_THAT(ftruncate(wfd.get(), 0), SyscallFailsWithErrno(EINVAL));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/mlock.cc b/test/syscalls/linux/mlock.cc
deleted file mode 100644
index 283c21ed3..000000000
--- a/test/syscalls/linux/mlock.cc
+++ /dev/null
@@ -1,330 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sys/mman.h>
-#include <sys/resource.h>
-#include <sys/syscall.h>
-#include <unistd.h>
-#include <cerrno>
-#include <cstring>
-
-#include "gmock/gmock.h"
-#include "test/util/capability_util.h"
-#include "test/util/cleanup.h"
-#include "test/util/memory_util.h"
-#include "test/util/multiprocess_util.h"
-#include "test/util/rlimit_util.h"
-#include "test/util/test_util.h"
-
-using ::testing::_;
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-PosixErrorOr<bool> CanMlock() {
- struct rlimit rlim;
- if (getrlimit(RLIMIT_MEMLOCK, &rlim) < 0) {
- return PosixError(errno, "getrlimit(RLIMIT_MEMLOCK)");
- }
- if (rlim.rlim_cur != 0) {
- return true;
- }
- return HaveCapability(CAP_IPC_LOCK);
-}
-
-// Returns true if the page containing addr is mlocked.
-bool IsPageMlocked(uintptr_t addr) {
- // This relies on msync(MS_INVALIDATE) interacting correctly with mlocked
- // pages, which is tested for by the MsyncInvalidate case below.
- int const rv = msync(reinterpret_cast<void*>(addr & ~(kPageSize - 1)),
- kPageSize, MS_ASYNC | MS_INVALIDATE);
- if (rv == 0) {
- return false;
- }
- // This uses TEST_PCHECK_MSG since it's used in subprocesses.
- TEST_PCHECK_MSG(errno == EBUSY, "msync failed with unexpected errno");
- return true;
-}
-
-
-TEST(MlockTest, Basic) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
- auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
- EXPECT_FALSE(IsPageMlocked(mapping.addr()));
- ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), SyscallSucceeds());
- EXPECT_TRUE(IsPageMlocked(mapping.addr()));
-}
-
-TEST(MlockTest, ProtNone) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
- auto const mapping =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, MAP_PRIVATE));
- EXPECT_FALSE(IsPageMlocked(mapping.addr()));
- ASSERT_THAT(mlock(mapping.ptr(), mapping.len()),
- SyscallFailsWithErrno(ENOMEM));
- // ENOMEM is returned because mlock can't populate the page, but it's still
- // considered locked.
- EXPECT_TRUE(IsPageMlocked(mapping.addr()));
-}
-
-TEST(MlockTest, MadviseDontneed) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
- auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
- ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), SyscallSucceeds());
- EXPECT_THAT(madvise(mapping.ptr(), mapping.len(), MADV_DONTNEED),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(MlockTest, MsyncInvalidate) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
- auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
- ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), SyscallSucceeds());
- EXPECT_THAT(msync(mapping.ptr(), mapping.len(), MS_ASYNC | MS_INVALIDATE),
- SyscallFailsWithErrno(EBUSY));
- EXPECT_THAT(msync(mapping.ptr(), mapping.len(), MS_SYNC | MS_INVALIDATE),
- SyscallFailsWithErrno(EBUSY));
-}
-
-TEST(MlockTest, Fork) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
- auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
- EXPECT_FALSE(IsPageMlocked(mapping.addr()));
- ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), SyscallSucceeds());
- EXPECT_TRUE(IsPageMlocked(mapping.addr()));
- EXPECT_THAT(
- InForkedProcess([&] { TEST_CHECK(!IsPageMlocked(mapping.addr())); }),
- IsPosixErrorOkAndHolds(0));
-}
-
-TEST(MlockTest, RlimitMemlockZero) {
- if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) {
- ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false));
- }
- Cleanup reset_rlimit =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, 0));
- auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
- EXPECT_FALSE(IsPageMlocked(mapping.addr()));
- ASSERT_THAT(mlock(mapping.ptr(), mapping.len()),
- SyscallFailsWithErrno(EPERM));
-}
-
-TEST(MlockTest, RlimitMemlockInsufficient) {
- if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) {
- ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false));
- }
- Cleanup reset_rlimit =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, kPageSize));
- auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
- EXPECT_FALSE(IsPageMlocked(mapping.addr()));
- ASSERT_THAT(mlock(mapping.ptr(), mapping.len()),
- SyscallFailsWithErrno(ENOMEM));
-}
-
-TEST(MunlockTest, Basic) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
- auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
- EXPECT_FALSE(IsPageMlocked(mapping.addr()));
- ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), SyscallSucceeds());
- EXPECT_TRUE(IsPageMlocked(mapping.addr()));
- ASSERT_THAT(munlock(mapping.ptr(), mapping.len()), SyscallSucceeds());
- EXPECT_FALSE(IsPageMlocked(mapping.addr()));
-}
-
-TEST(MunlockTest, NotLocked) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
- auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
- EXPECT_FALSE(IsPageMlocked(mapping.addr()));
- EXPECT_THAT(munlock(mapping.ptr(), mapping.len()), SyscallSucceeds());
- EXPECT_FALSE(IsPageMlocked(mapping.addr()));
-}
-
-// There is currently no test for mlockall(MCL_CURRENT) because the default
-// RLIMIT_MEMLOCK of 64 KB is insufficient to actually invoke
-// mlockall(MCL_CURRENT).
-
-TEST(MlockallTest, Future) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
-
- // Run this test in a separate (single-threaded) subprocess to ensure that a
- // background thread doesn't try to mmap a large amount of memory, fail due
- // to hitting RLIMIT_MEMLOCK, and explode the process violently.
- auto const do_test = [] {
- auto const mapping =
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE).ValueOrDie();
- TEST_CHECK(!IsPageMlocked(mapping.addr()));
- TEST_PCHECK(mlockall(MCL_FUTURE) == 0);
- // Ensure that mlockall(MCL_FUTURE) is turned off before the end of the
- // test, as otherwise mmaps may fail unexpectedly.
- Cleanup do_munlockall([] { TEST_PCHECK(munlockall() == 0); });
- auto const mapping2 =
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE).ValueOrDie();
- TEST_CHECK(IsPageMlocked(mapping2.addr()));
- // Fire munlockall() and check that it disables mlockall(MCL_FUTURE).
- do_munlockall.Release()();
- auto const mapping3 =
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE).ValueOrDie();
- TEST_CHECK(!IsPageMlocked(mapping2.addr()));
- };
- EXPECT_THAT(InForkedProcess(do_test), IsPosixErrorOkAndHolds(0));
-}
-
-TEST(MunlockallTest, Basic) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
- auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED));
- EXPECT_TRUE(IsPageMlocked(mapping.addr()));
- ASSERT_THAT(munlockall(), SyscallSucceeds());
- EXPECT_FALSE(IsPageMlocked(mapping.addr()));
-}
-
-#ifndef SYS_mlock2
-#ifdef __x86_64__
-#define SYS_mlock2 325
-#endif
-#endif
-
-#ifndef MLOCK_ONFAULT
-#define MLOCK_ONFAULT 0x01 // Linux: include/uapi/asm-generic/mman-common.h
-#endif
-
-#ifdef SYS_mlock2
-
-int mlock2(void const* addr, size_t len, int flags) {
- return syscall(SYS_mlock2, addr, len, flags);
-}
-
-TEST(Mlock2Test, NoFlags) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
- auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
- EXPECT_FALSE(IsPageMlocked(mapping.addr()));
- ASSERT_THAT(mlock2(mapping.ptr(), mapping.len(), 0), SyscallSucceeds());
- EXPECT_TRUE(IsPageMlocked(mapping.addr()));
-}
-
-TEST(Mlock2Test, MlockOnfault) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
- auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
- EXPECT_FALSE(IsPageMlocked(mapping.addr()));
- ASSERT_THAT(mlock2(mapping.ptr(), mapping.len(), MLOCK_ONFAULT),
- SyscallSucceeds());
- EXPECT_TRUE(IsPageMlocked(mapping.addr()));
-}
-
-TEST(Mlock2Test, UnknownFlags) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
- auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
- EXPECT_THAT(mlock2(mapping.ptr(), mapping.len(), ~0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-#endif // defined(SYS_mlock2)
-
-TEST(MapLockedTest, Basic) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
- auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED));
- EXPECT_TRUE(IsPageMlocked(mapping.addr()));
- EXPECT_THAT(munlock(mapping.ptr(), mapping.len()), SyscallSucceeds());
- EXPECT_FALSE(IsPageMlocked(mapping.addr()));
-}
-
-TEST(MapLockedTest, RlimitMemlockZero) {
- if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) {
- ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false));
- }
- Cleanup reset_rlimit =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, 0));
- EXPECT_THAT(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED),
- PosixErrorIs(EPERM, _));
-}
-
-TEST(MapLockedTest, RlimitMemlockInsufficient) {
- if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) {
- ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false));
- }
- Cleanup reset_rlimit =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, kPageSize));
- EXPECT_THAT(
- MmapAnon(2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED),
- PosixErrorIs(EAGAIN, _));
-}
-
-TEST(MremapLockedTest, Basic) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
- auto mapping = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED));
- EXPECT_TRUE(IsPageMlocked(mapping.addr()));
-
- void* addr = mremap(mapping.ptr(), mapping.len(), 2 * mapping.len(),
- MREMAP_MAYMOVE, nullptr);
- if (addr == MAP_FAILED) {
- FAIL() << "mremap failed: " << errno << " (" << strerror(errno) << ")";
- }
- mapping.release();
- mapping.reset(addr, 2 * mapping.len());
- EXPECT_TRUE(IsPageMlocked(reinterpret_cast<uintptr_t>(addr)));
-}
-
-TEST(MremapLockedTest, RlimitMemlockZero) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
- auto mapping = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED));
- EXPECT_TRUE(IsPageMlocked(mapping.addr()));
-
- if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) {
- ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false));
- }
- Cleanup reset_rlimit =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, 0));
- void* addr = mremap(mapping.ptr(), mapping.len(), 2 * mapping.len(),
- MREMAP_MAYMOVE, nullptr);
- EXPECT_TRUE(addr == MAP_FAILED && errno == EAGAIN)
- << "addr = " << addr << ", errno = " << errno;
-}
-
-TEST(MremapLockedTest, RlimitMemlockInsufficient) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
- auto mapping = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED));
- EXPECT_TRUE(IsPageMlocked(mapping.addr()));
-
- if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) {
- ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false));
- }
- Cleanup reset_rlimit = ASSERT_NO_ERRNO_AND_VALUE(
- ScopedSetSoftRlimit(RLIMIT_MEMLOCK, mapping.len()));
- void* addr = mremap(mapping.ptr(), mapping.len(), 2 * mapping.len(),
- MREMAP_MAYMOVE, nullptr);
- EXPECT_TRUE(addr == MAP_FAILED && errno == EAGAIN)
- << "addr = " << addr << ", errno = " << errno;
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/mmap.cc b/test/syscalls/linux/mmap.cc
deleted file mode 100644
index a112316e9..000000000
--- a/test/syscalls/linux/mmap.cc
+++ /dev/null
@@ -1,1738 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <fcntl.h>
-#include <linux/magic.h>
-#include <linux/unistd.h>
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/mman.h>
-#include <sys/resource.h>
-#include <sys/statfs.h>
-#include <sys/syscall.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
-#include <vector>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/strings/escaping.h"
-#include "absl/strings/str_split.h"
-#include "test/util/cleanup.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/memory_util.h"
-#include "test/util/multiprocess_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-using ::testing::Gt;
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-PosixErrorOr<int64_t> VirtualMemorySize() {
- ASSIGN_OR_RETURN_ERRNO(auto contents, GetContents("/proc/self/statm"));
- std::vector<std::string> parts = absl::StrSplit(contents, ' ');
- if (parts.empty()) {
- return PosixError(EINVAL, "Unable to parse /proc/self/statm");
- }
- ASSIGN_OR_RETURN_ERRNO(auto pages, Atoi<int64_t>(parts[0]));
- return pages * getpagesize();
-}
-
-class MMapTest : public ::testing::Test {
- protected:
- // Unmap mapping, if one was made.
- void TearDown() override {
- if (addr_) {
- EXPECT_THAT(Unmap(), SyscallSucceeds());
- }
- }
-
- // Remembers mapping, so it can be automatically unmapped.
- uintptr_t Map(uintptr_t addr, size_t length, int prot, int flags, int fd,
- off_t offset) {
- void* ret =
- mmap(reinterpret_cast<void*>(addr), length, prot, flags, fd, offset);
-
- if (ret != MAP_FAILED) {
- addr_ = ret;
- length_ = length;
- }
-
- return reinterpret_cast<uintptr_t>(ret);
- }
-
- // Unmap previous mapping
- int Unmap() {
- if (!addr_) {
- return -1;
- }
-
- int ret = munmap(addr_, length_);
-
- addr_ = nullptr;
- length_ = 0;
-
- return ret;
- }
-
- // Msync the mapping.
- int Msync() { return msync(addr_, length_, MS_SYNC); }
-
- // Mlock the mapping.
- int Mlock() { return mlock(addr_, length_); }
-
- // Munlock the mapping.
- int Munlock() { return munlock(addr_, length_); }
-
- int Protect(uintptr_t addr, size_t length, int prot) {
- return mprotect(reinterpret_cast<void*>(addr), length, prot);
- }
-
- void* addr_ = nullptr;
- size_t length_ = 0;
-};
-
-// Matches if arg contains the same contents as string str.
-MATCHER_P(EqualsMemory, str, "") {
- if (0 == memcmp(arg, str.c_str(), str.size())) {
- return true;
- }
-
- *result_listener << "Memory did not match. Got:\n"
- << absl::BytesToHexString(
- std::string(static_cast<char*>(arg), str.size()))
- << "Want:\n"
- << absl::BytesToHexString(str);
- return false;
-}
-
-// We can't map pipes, but for different reasons.
-TEST_F(MMapTest, MapPipe) {
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
- EXPECT_THAT(Map(0, kPageSize, PROT_READ, MAP_PRIVATE, fds[0], 0),
- SyscallFailsWithErrno(ENODEV));
- EXPECT_THAT(Map(0, kPageSize, PROT_READ, MAP_PRIVATE, fds[1], 0),
- SyscallFailsWithErrno(EACCES));
- ASSERT_THAT(close(fds[0]), SyscallSucceeds());
- ASSERT_THAT(close(fds[1]), SyscallSucceeds());
-}
-
-// It's very common to mmap /dev/zero because anonymous mappings aren't part
-// of POSIX although they are widely supported. So a zero initialized memory
-// region would actually come from a "file backed" /dev/zero mapping.
-TEST_F(MMapTest, MapDevZeroShared) {
- // This test will verify that we're able to map a page backed by /dev/zero
- // as MAP_SHARED.
- const FileDescriptor dev_zero =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDWR));
-
- // Test that we can create a RW SHARED mapping of /dev/zero.
- ASSERT_THAT(
- Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, dev_zero.get(), 0),
- SyscallSucceeds());
-}
-
-TEST_F(MMapTest, MapDevZeroPrivate) {
- // This test will verify that we're able to map a page backed by /dev/zero
- // as MAP_PRIVATE.
- const FileDescriptor dev_zero =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDWR));
-
- // Test that we can create a RW SHARED mapping of /dev/zero.
- ASSERT_THAT(
- Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, dev_zero.get(), 0),
- SyscallSucceeds());
-}
-
-TEST_F(MMapTest, MapDevZeroNoPersistence) {
- // This test will verify that two independent mappings of /dev/zero do not
- // appear to reference the same "backed file."
-
- const FileDescriptor dev_zero1 =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDWR));
- const FileDescriptor dev_zero2 =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDWR));
-
- ASSERT_THAT(
- Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, dev_zero1.get(), 0),
- SyscallSucceeds());
-
- // Create a second mapping via the second /dev/zero fd.
- void* psec_map = mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED,
- dev_zero2.get(), 0);
- ASSERT_THAT(reinterpret_cast<intptr_t>(psec_map), SyscallSucceeds());
-
- // Always unmap.
- auto cleanup_psec_map = Cleanup(
- [&] { EXPECT_THAT(munmap(psec_map, kPageSize), SyscallSucceeds()); });
-
- // Verify that we have independently addressed pages.
- ASSERT_NE(psec_map, addr_);
-
- std::string buf_zero(kPageSize, 0x00);
- std::string buf_ones(kPageSize, 0xFF);
-
- // Verify the first is actually all zeros after mmap.
- EXPECT_THAT(addr_, EqualsMemory(buf_zero));
-
- // Let's fill in the first mapping with 0xFF.
- memcpy(addr_, buf_ones.data(), kPageSize);
-
- // Verify that the memcpy actually stuck in the page.
- EXPECT_THAT(addr_, EqualsMemory(buf_ones));
-
- // Verify that it didn't affect the second page which should be all zeros.
- EXPECT_THAT(psec_map, EqualsMemory(buf_zero));
-}
-
-TEST_F(MMapTest, MapDevZeroSharedMultiplePages) {
- // This will test that we're able to map /dev/zero over multiple pages.
- const FileDescriptor dev_zero =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDWR));
-
- // Test that we can create a RW SHARED mapping of /dev/zero.
- ASSERT_THAT(Map(0, kPageSize * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE,
- dev_zero.get(), 0),
- SyscallSucceeds());
-
- std::string buf_zero(kPageSize * 2, 0x00);
- std::string buf_ones(kPageSize * 2, 0xFF);
-
- // Verify the two pages are actually all zeros after mmap.
- EXPECT_THAT(addr_, EqualsMemory(buf_zero));
-
- // Fill out the pages with all ones.
- memcpy(addr_, buf_ones.data(), kPageSize * 2);
-
- // Verify that the memcpy actually stuck in the pages.
- EXPECT_THAT(addr_, EqualsMemory(buf_ones));
-}
-
-TEST_F(MMapTest, MapDevZeroSharedFdNoPersistence) {
- // This test will verify that two independent mappings of /dev/zero do not
- // appear to reference the same "backed file" even when mapped from the
- // same initial fd.
- const FileDescriptor dev_zero =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDWR));
-
- ASSERT_THAT(
- Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, dev_zero.get(), 0),
- SyscallSucceeds());
-
- // Create a second mapping via the same fd.
- void* psec_map = mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED,
- dev_zero.get(), 0);
- ASSERT_THAT(reinterpret_cast<int64_t>(psec_map), SyscallSucceeds());
-
- // Always unmap.
- auto cleanup_psec_map = Cleanup(
- [&] { ASSERT_THAT(munmap(psec_map, kPageSize), SyscallSucceeds()); });
-
- // Verify that we have independently addressed pages.
- ASSERT_NE(psec_map, addr_);
-
- std::string buf_zero(kPageSize, 0x00);
- std::string buf_ones(kPageSize, 0xFF);
-
- // Verify the first is actually all zeros after mmap.
- EXPECT_THAT(addr_, EqualsMemory(buf_zero));
-
- // Let's fill in the first mapping with 0xFF.
- memcpy(addr_, buf_ones.data(), kPageSize);
-
- // Verify that the memcpy actually stuck in the page.
- EXPECT_THAT(addr_, EqualsMemory(buf_ones));
-
- // Verify that it didn't affect the second page which should be all zeros.
- EXPECT_THAT(psec_map, EqualsMemory(buf_zero));
-}
-
-TEST_F(MMapTest, MapDevZeroSegfaultAfterUnmap) {
- SetupGvisorDeathTest();
-
- // This test will verify that we're able to map a page backed by /dev/zero
- // as MAP_SHARED and after it's unmapped any access results in a SIGSEGV.
- // This test is redundant but given the special nature of /dev/zero mappings
- // it doesn't hurt.
- const FileDescriptor dev_zero =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDWR));
-
- const auto rest = [&] {
- // Test that we can create a RW SHARED mapping of /dev/zero.
- TEST_PCHECK(Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED,
- dev_zero.get(),
- 0) != reinterpret_cast<uintptr_t>(MAP_FAILED));
-
- // Confirm that accesses after the unmap result in a SIGSEGV.
- //
- // N.B. We depend on this process being single-threaded to ensure there
- // can't be another mmap to map addr before the dereference below.
- void* addr_saved = addr_; // Unmap resets addr_.
- TEST_PCHECK(Unmap() == 0);
- *reinterpret_cast<volatile int*>(addr_saved) = 0xFF;
- };
-
- EXPECT_THAT(InForkedProcess(rest),
- IsPosixErrorOkAndHolds(W_EXITCODE(0, SIGSEGV)));
-}
-
-TEST_F(MMapTest, MapDevZeroUnaligned) {
- const FileDescriptor dev_zero =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDWR));
- const size_t size = kPageSize + kPageSize / 2;
- const std::string buf_zero(size, 0x00);
-
- ASSERT_THAT(
- Map(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, dev_zero.get(), 0),
- SyscallSucceeds());
- EXPECT_THAT(addr_, EqualsMemory(buf_zero));
- ASSERT_THAT(Unmap(), SyscallSucceeds());
-
- ASSERT_THAT(
- Map(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, dev_zero.get(), 0),
- SyscallSucceeds());
- EXPECT_THAT(addr_, EqualsMemory(buf_zero));
-}
-
-// We can't map _some_ character devices.
-TEST_F(MMapTest, MapCharDevice) {
- const FileDescriptor cdevfd =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/random", 0, 0));
- EXPECT_THAT(Map(0, kPageSize, PROT_READ, MAP_PRIVATE, cdevfd.get(), 0),
- SyscallFailsWithErrno(ENODEV));
-}
-
-// We can't map directories.
-TEST_F(MMapTest, MapDirectory) {
- const FileDescriptor dirfd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(GetAbsoluteTestTmpdir(), 0, 0));
- EXPECT_THAT(Map(0, kPageSize, PROT_READ, MAP_PRIVATE, dirfd.get(), 0),
- SyscallFailsWithErrno(ENODEV));
-}
-
-// We can map *something*
-TEST_F(MMapTest, MapAnything) {
- EXPECT_THAT(Map(0, kPageSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0),
- SyscallSucceedsWithValue(Gt(0)));
-}
-
-// Map length < PageSize allowed
-TEST_F(MMapTest, SmallMap) {
- EXPECT_THAT(Map(0, 128, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0),
- SyscallSucceeds());
-}
-
-// Hint address doesn't break anything.
-// Note: there is no requirement we actually get the hint address
-TEST_F(MMapTest, HintAddress) {
- EXPECT_THAT(
- Map(0x30000000, kPageSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0),
- SyscallSucceeds());
-}
-
-// MAP_FIXED gives us exactly the requested address
-TEST_F(MMapTest, MapFixed) {
- EXPECT_THAT(Map(0x30000000, kPageSize, PROT_NONE,
- MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0),
- SyscallSucceedsWithValue(0x30000000));
-}
-
-// 64-bit addresses work too
-#ifdef __x86_64__
-TEST_F(MMapTest, MapFixed64) {
- EXPECT_THAT(Map(0x300000000000, kPageSize, PROT_NONE,
- MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0),
- SyscallSucceedsWithValue(0x300000000000));
-}
-#endif
-
-// MAP_STACK allowed.
-// There isn't a good way to verify it did anything.
-TEST_F(MMapTest, MapStack) {
- EXPECT_THAT(Map(0, kPageSize, PROT_NONE,
- MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0),
- SyscallSucceeds());
-}
-
-// MAP_LOCKED allowed.
-// There isn't a good way to verify it did anything.
-TEST_F(MMapTest, MapLocked) {
- EXPECT_THAT(Map(0, kPageSize, PROT_NONE,
- MAP_PRIVATE | MAP_ANONYMOUS | MAP_LOCKED, -1, 0),
- SyscallSucceeds());
-}
-
-// MAP_PRIVATE or MAP_SHARED must be passed
-TEST_F(MMapTest, NotPrivateOrShared) {
- EXPECT_THAT(Map(0, kPageSize, PROT_NONE, MAP_ANONYMOUS, -1, 0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-// Only one of MAP_PRIVATE or MAP_SHARED may be passed
-TEST_F(MMapTest, PrivateAndShared) {
- EXPECT_THAT(Map(0, kPageSize, PROT_NONE,
- MAP_PRIVATE | MAP_SHARED | MAP_ANONYMOUS, -1, 0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_F(MMapTest, FixedAlignment) {
- // Addr must be page aligned (MAP_FIXED)
- EXPECT_THAT(Map(0x30000001, kPageSize, PROT_NONE,
- MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-// Non-MAP_FIXED address does not need to be page aligned
-TEST_F(MMapTest, NonFixedAlignment) {
- EXPECT_THAT(
- Map(0x30000001, kPageSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0),
- SyscallSucceeds());
-}
-
-// Length = 0 results in EINVAL.
-TEST_F(MMapTest, InvalidLength) {
- EXPECT_THAT(Map(0, 0, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-// Bad fd not allowed.
-TEST_F(MMapTest, BadFd) {
- EXPECT_THAT(Map(0, kPageSize, PROT_NONE, MAP_PRIVATE, 999, 0),
- SyscallFailsWithErrno(EBADF));
-}
-
-// Mappings are writable.
-TEST_F(MMapTest, ProtWrite) {
- uint64_t addr;
- constexpr uint8_t kFirstWord[] = {42, 42, 42, 42};
-
- EXPECT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0),
- SyscallSucceeds());
-
- // This shouldn't cause a SIGSEGV.
- memset(reinterpret_cast<void*>(addr), 42, kPageSize);
-
- // The written data should actually be there.
- EXPECT_EQ(
- 0, memcmp(reinterpret_cast<void*>(addr), kFirstWord, sizeof(kFirstWord)));
-}
-
-// "Write-only" mappings are writable *and* readable.
-TEST_F(MMapTest, ProtWriteOnly) {
- uint64_t addr;
- constexpr uint8_t kFirstWord[] = {42, 42, 42, 42};
-
- EXPECT_THAT(
- addr = Map(0, kPageSize, PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0),
- SyscallSucceeds());
-
- // This shouldn't cause a SIGSEGV.
- memset(reinterpret_cast<void*>(addr), 42, kPageSize);
-
- // The written data should actually be there.
- EXPECT_EQ(
- 0, memcmp(reinterpret_cast<void*>(addr), kFirstWord, sizeof(kFirstWord)));
-}
-
-// "Write-only" mappings are readable.
-//
-// This is distinct from above to ensure the page is accessible even if the
-// initial fault is a write fault.
-TEST_F(MMapTest, ProtWriteOnlyReadable) {
- uint64_t addr;
- constexpr uint64_t kFirstWord = 0;
-
- EXPECT_THAT(
- addr = Map(0, kPageSize, PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0),
- SyscallSucceeds());
-
- EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr), &kFirstWord,
- sizeof(kFirstWord)));
-}
-
-// Mappings are writable after mprotect from PROT_NONE to PROT_READ|PROT_WRITE.
-TEST_F(MMapTest, ProtectProtWrite) {
- uint64_t addr;
- constexpr uint8_t kFirstWord[] = {42, 42, 42, 42};
-
- EXPECT_THAT(
- addr = Map(0, kPageSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0),
- SyscallSucceeds());
-
- ASSERT_THAT(Protect(addr, kPageSize, PROT_READ | PROT_WRITE),
- SyscallSucceeds());
-
- // This shouldn't cause a SIGSEGV.
- memset(reinterpret_cast<void*>(addr), 42, kPageSize);
-
- // The written data should actually be there.
- EXPECT_EQ(
- 0, memcmp(reinterpret_cast<void*>(addr), kFirstWord, sizeof(kFirstWord)));
-}
-
-// SIGSEGV raised when reading PROT_NONE memory
-TEST_F(MMapTest, ProtNoneDeath) {
- SetupGvisorDeathTest();
-
- uintptr_t addr;
-
- ASSERT_THAT(
- addr = Map(0, kPageSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0),
- SyscallSucceeds());
-
- EXPECT_EXIT(*reinterpret_cast<volatile int*>(addr),
- ::testing::KilledBySignal(SIGSEGV), "");
-}
-
-// SIGSEGV raised when writing PROT_READ only memory
-TEST_F(MMapTest, ReadOnlyDeath) {
- SetupGvisorDeathTest();
-
- uintptr_t addr;
-
- ASSERT_THAT(
- addr = Map(0, kPageSize, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0),
- SyscallSucceeds());
-
- EXPECT_EXIT(*reinterpret_cast<volatile int*>(addr) = 42,
- ::testing::KilledBySignal(SIGSEGV), "");
-}
-
-// Writable mapping mprotect'd to read-only should not be writable.
-TEST_F(MMapTest, MprotectReadOnlyDeath) {
- SetupGvisorDeathTest();
-
- uintptr_t addr;
-
- ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0),
- SyscallSucceeds());
-
- volatile int* val = reinterpret_cast<int*>(addr);
-
- // Copy to ensure page is mapped in.
- *val = 42;
-
- ASSERT_THAT(Protect(addr, kPageSize, PROT_READ), SyscallSucceeds());
-
- // Now it shouldn't be writable.
- EXPECT_EXIT(*val = 0, ::testing::KilledBySignal(SIGSEGV), "");
-}
-
-// Verify that calling mprotect an address that's not page aligned fails.
-TEST_F(MMapTest, MprotectNotPageAligned) {
- uintptr_t addr;
-
- ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0),
- SyscallSucceeds());
- ASSERT_THAT(Protect(addr + 1, kPageSize - 1, PROT_READ),
- SyscallFailsWithErrno(EINVAL));
-}
-
-// Verify that calling mprotect with an absurdly huge length fails.
-TEST_F(MMapTest, MprotectHugeLength) {
- uintptr_t addr;
-
- ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0),
- SyscallSucceeds());
- ASSERT_THAT(Protect(addr, static_cast<size_t>(-1), PROT_READ),
- SyscallFailsWithErrno(ENOMEM));
-}
-
-#if defined(__x86_64__) || defined(__i386__)
-// This code is equivalent in 32 and 64-bit mode
-const uint8_t machine_code[] = {
- 0xb8, 0x2a, 0x00, 0x00, 0x00, // movl $42, %eax
- 0xc3, // retq
-};
-
-// PROT_EXEC allows code execution
-TEST_F(MMapTest, ProtExec) {
- uintptr_t addr;
- uint32_t (*func)(void);
-
- EXPECT_THAT(addr = Map(0, kPageSize, PROT_EXEC | PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0),
- SyscallSucceeds());
-
- memcpy(reinterpret_cast<void*>(addr), machine_code, sizeof(machine_code));
-
- func = reinterpret_cast<uint32_t (*)(void)>(addr);
-
- EXPECT_EQ(42, func());
-}
-
-// No PROT_EXEC disallows code execution
-TEST_F(MMapTest, NoProtExecDeath) {
- SetupGvisorDeathTest();
-
- uintptr_t addr;
- uint32_t (*func)(void);
-
- EXPECT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0),
- SyscallSucceeds());
-
- memcpy(reinterpret_cast<void*>(addr), machine_code, sizeof(machine_code));
-
- func = reinterpret_cast<uint32_t (*)(void)>(addr);
-
- EXPECT_EXIT(func(), ::testing::KilledBySignal(SIGSEGV), "");
-}
-#endif
-
-TEST_F(MMapTest, NoExceedLimitData) {
- void* prevbrk;
- void* target_brk;
- struct rlimit setlim;
-
- prevbrk = sbrk(0);
- ASSERT_NE(-1, reinterpret_cast<intptr_t>(prevbrk));
- target_brk = reinterpret_cast<char*>(prevbrk) + 1;
-
- setlim.rlim_cur = RLIM_INFINITY;
- setlim.rlim_max = RLIM_INFINITY;
- ASSERT_THAT(setrlimit(RLIMIT_DATA, &setlim), SyscallSucceeds());
- EXPECT_THAT(brk(target_brk), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(MMapTest, ExceedLimitData) {
- // To unit test this more precisely, we'd need access to the mm's start_brk
- // and end_brk, which we don't have direct access to :/
- void* prevbrk;
- void* target_brk;
- struct rlimit setlim;
-
- prevbrk = sbrk(0);
- ASSERT_NE(-1, reinterpret_cast<intptr_t>(prevbrk));
- target_brk = reinterpret_cast<char*>(prevbrk) + 8192;
-
- setlim.rlim_cur = 0;
- setlim.rlim_max = RLIM_INFINITY;
- // Set RLIMIT_DATA very low so any subsequent brk() calls fail.
- // Reset RLIMIT_DATA during teardown step.
- ASSERT_THAT(setrlimit(RLIMIT_DATA, &setlim), SyscallSucceeds());
- EXPECT_THAT(brk(target_brk), SyscallFailsWithErrno(ENOMEM));
- // Teardown step...
- setlim.rlim_cur = RLIM_INFINITY;
- ASSERT_THAT(setrlimit(RLIMIT_DATA, &setlim), SyscallSucceeds());
-}
-
-TEST_F(MMapTest, ExceedLimitDataPrlimit) {
- // To unit test this more precisely, we'd need access to the mm's start_brk
- // and end_brk, which we don't have direct access to :/
- void* prevbrk;
- void* target_brk;
- struct rlimit setlim;
-
- prevbrk = sbrk(0);
- ASSERT_NE(-1, reinterpret_cast<intptr_t>(prevbrk));
- target_brk = reinterpret_cast<char*>(prevbrk) + 8192;
-
- setlim.rlim_cur = 0;
- setlim.rlim_max = RLIM_INFINITY;
- // Set RLIMIT_DATA very low so any subsequent brk() calls fail.
- // Reset RLIMIT_DATA during teardown step.
- ASSERT_THAT(prlimit(0, RLIMIT_DATA, &setlim, nullptr), SyscallSucceeds());
- EXPECT_THAT(brk(target_brk), SyscallFailsWithErrno(ENOMEM));
- // Teardown step...
- setlim.rlim_cur = RLIM_INFINITY;
- ASSERT_THAT(setrlimit(RLIMIT_DATA, &setlim), SyscallSucceeds());
-}
-
-TEST_F(MMapTest, ExceedLimitDataPrlimitPID) {
- // To unit test this more precisely, we'd need access to the mm's start_brk
- // and end_brk, which we don't have direct access to :/
- void* prevbrk;
- void* target_brk;
- struct rlimit setlim;
-
- prevbrk = sbrk(0);
- ASSERT_NE(-1, reinterpret_cast<intptr_t>(prevbrk));
- target_brk = reinterpret_cast<char*>(prevbrk) + 8192;
-
- setlim.rlim_cur = 0;
- setlim.rlim_max = RLIM_INFINITY;
- // Set RLIMIT_DATA very low so any subsequent brk() calls fail.
- // Reset RLIMIT_DATA during teardown step.
- ASSERT_THAT(prlimit(syscall(__NR_gettid), RLIMIT_DATA, &setlim, nullptr),
- SyscallSucceeds());
- EXPECT_THAT(brk(target_brk), SyscallFailsWithErrno(ENOMEM));
- // Teardown step...
- setlim.rlim_cur = RLIM_INFINITY;
- ASSERT_THAT(setrlimit(RLIMIT_DATA, &setlim), SyscallSucceeds());
-}
-
-TEST_F(MMapTest, NoExceedLimitAS) {
- constexpr uint64_t kAllocBytes = 200 << 20;
- // Add some headroom to the AS limit in case of e.g. unexpected stack
- // expansion.
- constexpr uint64_t kExtraASBytes = kAllocBytes + (20 << 20);
- static_assert(kAllocBytes < kExtraASBytes,
- "test depends on allocation not exceeding AS limit");
-
- auto vss = ASSERT_NO_ERRNO_AND_VALUE(VirtualMemorySize());
- struct rlimit setlim;
- setlim.rlim_cur = vss + kExtraASBytes;
- setlim.rlim_max = RLIM_INFINITY;
- ASSERT_THAT(setrlimit(RLIMIT_AS, &setlim), SyscallSucceeds());
- EXPECT_THAT(
- Map(0, kAllocBytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0),
- SyscallSucceedsWithValue(Gt(0)));
-}
-
-TEST_F(MMapTest, ExceedLimitAS) {
- constexpr uint64_t kAllocBytes = 200 << 20;
- // Add some headroom to the AS limit in case of e.g. unexpected stack
- // expansion.
- constexpr uint64_t kExtraASBytes = 20 << 20;
- static_assert(kAllocBytes > kExtraASBytes,
- "test depends on allocation exceeding AS limit");
-
- auto vss = ASSERT_NO_ERRNO_AND_VALUE(VirtualMemorySize());
- struct rlimit setlim;
- setlim.rlim_cur = vss + kExtraASBytes;
- setlim.rlim_max = RLIM_INFINITY;
- ASSERT_THAT(setrlimit(RLIMIT_AS, &setlim), SyscallSucceeds());
- EXPECT_THAT(
- Map(0, kAllocBytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0),
- SyscallFailsWithErrno(ENOMEM));
-}
-
-// Tests that setting an anonymous mmap to PROT_NONE doesn't free the memory.
-TEST_F(MMapTest, SettingProtNoneDoesntFreeMemory) {
- uintptr_t addr;
- constexpr uint8_t kFirstWord[] = {42, 42, 42, 42};
-
- EXPECT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0),
- SyscallSucceedsWithValue(Gt(0)));
-
- memset(reinterpret_cast<void*>(addr), 42, kPageSize);
-
- ASSERT_THAT(Protect(addr, kPageSize, PROT_NONE), SyscallSucceeds());
- ASSERT_THAT(Protect(addr, kPageSize, PROT_READ | PROT_WRITE),
- SyscallSucceeds());
-
- // The written data should still be there.
- EXPECT_EQ(
- 0, memcmp(reinterpret_cast<void*>(addr), kFirstWord, sizeof(kFirstWord)));
-}
-
-constexpr char kFileContents[] = "Hello World!";
-
-class MMapFileTest : public MMapTest {
- protected:
- FileDescriptor fd_;
- std::string filename_;
-
- // Open a file for read/write
- void SetUp() override {
- MMapTest::SetUp();
-
- filename_ = NewTempAbsPath();
- fd_ = ASSERT_NO_ERRNO_AND_VALUE(Open(filename_, O_CREAT | O_RDWR, 0644));
-
- // Extend file so it can be written once mapped. Deliberately make the file
- // only half a page in size, so we can test what happens when we access the
- // second half.
- // Use ftruncate(2) once the sentry supports it.
- char zero = 0;
- size_t count = 0;
- do {
- const DisableSave ds; // saving 2048 times is slow and useless.
- Write(&zero, 1), SyscallSucceedsWithValue(1);
- } while (++count < (kPageSize / 2));
- ASSERT_THAT(lseek(fd_.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0));
- }
-
- // Close and delete file
- void TearDown() override {
- MMapTest::TearDown();
- fd_.reset(); // Make sure the files is closed before we unlink it.
- ASSERT_THAT(unlink(filename_.c_str()), SyscallSucceeds());
- }
-
- ssize_t Read(char* buf, size_t count) {
- ssize_t len = 0;
- do {
- ssize_t ret = read(fd_.get(), buf, count);
- if (ret < 0) {
- return ret;
- } else if (ret == 0) {
- return len;
- }
-
- len += ret;
- buf += ret;
- } while (len < static_cast<ssize_t>(count));
-
- return len;
- }
-
- ssize_t Write(const char* buf, size_t count) {
- ssize_t len = 0;
- do {
- ssize_t ret = write(fd_.get(), buf, count);
- if (ret < 0) {
- return ret;
- } else if (ret == 0) {
- return len;
- }
-
- len += ret;
- buf += ret;
- } while (len < static_cast<ssize_t>(count));
-
- return len;
- }
-};
-
-// MAP_POPULATE allowed.
-// There isn't a good way to verify it actually did anything.
-//
-// FIXME(b/37222275): Parameterize.
-TEST_F(MMapFileTest, MapPopulate) {
- ASSERT_THAT(
- Map(0, kPageSize, PROT_READ, MAP_PRIVATE | MAP_POPULATE, fd_.get(), 0),
- SyscallSucceeds());
-}
-
-// MAP_POPULATE on a short file.
-//
-// FIXME(b/37222275): Parameterize.
-TEST_F(MMapFileTest, MapPopulateShort) {
- ASSERT_THAT(Map(0, 2 * kPageSize, PROT_READ, MAP_PRIVATE | MAP_POPULATE,
- fd_.get(), 0),
- SyscallSucceeds());
-}
-
-// Read contents from mapped file.
-TEST_F(MMapFileTest, Read) {
- size_t len = strlen(kFileContents);
- ASSERT_EQ(len, Write(kFileContents, len));
-
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_PRIVATE, fd_.get(), 0),
- SyscallSucceeds());
-
- EXPECT_THAT(reinterpret_cast<char*>(addr),
- EqualsMemory(std::string(kFileContents)));
-}
-
-// Map at an offset.
-TEST_F(MMapFileTest, MapOffset) {
- ASSERT_THAT(lseek(fd_.get(), kPageSize, SEEK_SET), SyscallSucceeds());
-
- size_t len = strlen(kFileContents);
- ASSERT_EQ(len, Write(kFileContents, len));
-
- uintptr_t addr;
- ASSERT_THAT(
- addr = Map(0, kPageSize, PROT_READ, MAP_PRIVATE, fd_.get(), kPageSize),
- SyscallSucceeds());
-
- EXPECT_THAT(reinterpret_cast<char*>(addr),
- EqualsMemory(std::string(kFileContents)));
-}
-
-TEST_F(MMapFileTest, MapOffsetBeyondEnd) {
- SetupGvisorDeathTest();
-
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE,
- fd_.get(), 10 * kPageSize),
- SyscallSucceeds());
-
- // Touching the memory causes SIGBUS.
- size_t len = strlen(kFileContents);
- EXPECT_EXIT(std::copy(kFileContents, kFileContents + len,
- reinterpret_cast<volatile char*>(addr)),
- ::testing::KilledBySignal(SIGBUS), "");
-}
-
-// Verify mmap fails when sum of length and offset overflows.
-TEST_F(MMapFileTest, MapLengthPlusOffsetOverflows) {
- const size_t length = static_cast<size_t>(-kPageSize);
- const off_t offset = kPageSize;
- ASSERT_THAT(Map(0, length, PROT_READ, MAP_PRIVATE, fd_.get(), offset),
- SyscallFailsWithErrno(ENOMEM));
-}
-
-// MAP_PRIVATE PROT_WRITE is allowed on read-only FDs.
-TEST_F(MMapFileTest, WritePrivateOnReadOnlyFd) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(filename_, O_RDONLY));
-
- uintptr_t addr;
- EXPECT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE,
- fd.get(), 0),
- SyscallSucceeds());
-
- // Touch the page to ensure the kernel didn't lie about writability.
- size_t len = strlen(kFileContents);
- std::copy(kFileContents, kFileContents + len,
- reinterpret_cast<volatile char*>(addr));
-}
-
-// MAP_PRIVATE PROT_READ is not allowed on write-only FDs.
-TEST_F(MMapFileTest, ReadPrivateOnWriteOnlyFd) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(filename_, O_WRONLY));
-
- uintptr_t addr;
- EXPECT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_PRIVATE, fd.get(), 0),
- SyscallFailsWithErrno(EACCES));
-}
-
-// MAP_SHARED PROT_WRITE not allowed on read-only FDs.
-TEST_F(MMapFileTest, WriteSharedOnReadOnlyFd) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(filename_, O_RDONLY));
-
- uintptr_t addr;
- EXPECT_THAT(
- addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0),
- SyscallFailsWithErrno(EACCES));
-}
-
-// MAP_SHARED PROT_READ not allowed on write-only FDs.
-//
-// FIXME(b/37222275): Parameterize.
-TEST_F(MMapFileTest, ReadSharedOnWriteOnlyFd) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(filename_, O_WRONLY));
-
- uintptr_t addr;
- EXPECT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_SHARED, fd.get(), 0),
- SyscallFailsWithErrno(EACCES));
-}
-
-// MAP_SHARED PROT_WRITE not allowed on write-only FDs.
-// The FD must always be readable.
-//
-// FIXME(b/37222275): Parameterize.
-TEST_F(MMapFileTest, WriteSharedOnWriteOnlyFd) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(filename_, O_WRONLY));
-
- uintptr_t addr;
- EXPECT_THAT(addr = Map(0, kPageSize, PROT_WRITE, MAP_SHARED, fd.get(), 0),
- SyscallFailsWithErrno(EACCES));
-}
-
-// Overwriting the contents of a file mapped MAP_SHARED PROT_READ
-// should cause the new data to be reflected in the mapping.
-TEST_F(MMapFileTest, ReadSharedConsistentWithOverwrite) {
- // Start from scratch.
- EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds());
-
- // Expand the file to two pages and dirty them.
- std::string bufA(kPageSize, 'a');
- ASSERT_THAT(Write(bufA.c_str(), bufA.size()),
- SyscallSucceedsWithValue(bufA.size()));
- std::string bufB(kPageSize, 'b');
- ASSERT_THAT(Write(bufB.c_str(), bufB.size()),
- SyscallSucceedsWithValue(bufB.size()));
-
- // Map the page.
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0),
- SyscallSucceeds());
-
- // Check that the mapping contains the right file data.
- EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr), bufA.c_str(), kPageSize));
- EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr + kPageSize), bufB.c_str(),
- kPageSize));
-
- // Start at the beginning of the file.
- ASSERT_THAT(lseek(fd_.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0));
-
- // Swap the write pattern.
- ASSERT_THAT(Write(bufB.c_str(), bufB.size()),
- SyscallSucceedsWithValue(bufB.size()));
- ASSERT_THAT(Write(bufA.c_str(), bufA.size()),
- SyscallSucceedsWithValue(bufA.size()));
-
- // Check that the mapping got updated.
- EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr), bufB.c_str(), kPageSize));
- EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr + kPageSize), bufA.c_str(),
- kPageSize));
-}
-
-// Partially overwriting a file mapped MAP_SHARED PROT_READ should be reflected
-// in the mapping.
-TEST_F(MMapFileTest, ReadSharedConsistentWithPartialOverwrite) {
- // Start from scratch.
- EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds());
-
- // Expand the file to two pages and dirty them.
- std::string bufA(kPageSize, 'a');
- ASSERT_THAT(Write(bufA.c_str(), bufA.size()),
- SyscallSucceedsWithValue(bufA.size()));
- std::string bufB(kPageSize, 'b');
- ASSERT_THAT(Write(bufB.c_str(), bufB.size()),
- SyscallSucceedsWithValue(bufB.size()));
-
- // Map the page.
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0),
- SyscallSucceeds());
-
- // Check that the mapping contains the right file data.
- EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr), bufA.c_str(), kPageSize));
- EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr + kPageSize), bufB.c_str(),
- kPageSize));
-
- // Start at the beginning of the file.
- ASSERT_THAT(lseek(fd_.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0));
-
- // Do a partial overwrite, spanning both pages.
- std::string bufC(kPageSize + (kPageSize / 2), 'c');
- ASSERT_THAT(Write(bufC.c_str(), bufC.size()),
- SyscallSucceedsWithValue(bufC.size()));
-
- // Check that the mapping got updated.
- EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr), bufC.c_str(),
- kPageSize + (kPageSize / 2)));
- EXPECT_EQ(0,
- memcmp(reinterpret_cast<void*>(addr + kPageSize + (kPageSize / 2)),
- bufB.c_str(), kPageSize / 2));
-}
-
-// Overwriting a file mapped MAP_SHARED PROT_READ should be reflected in the
-// mapping and the file.
-TEST_F(MMapFileTest, ReadSharedConsistentWithWriteAndFile) {
- // Start from scratch.
- EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds());
-
- // Expand the file to two full pages and dirty it.
- std::string bufA(2 * kPageSize, 'a');
- ASSERT_THAT(Write(bufA.c_str(), bufA.size()),
- SyscallSucceedsWithValue(bufA.size()));
-
- // Map only the first page.
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0),
- SyscallSucceeds());
-
- // Prepare to overwrite the file contents.
- ASSERT_THAT(lseek(fd_.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0));
-
- // Overwrite everything, beyond the mapped portion.
- std::string bufB(2 * kPageSize, 'b');
- ASSERT_THAT(Write(bufB.c_str(), bufB.size()),
- SyscallSucceedsWithValue(bufB.size()));
-
- // What the mapped portion should now look like.
- std::string bufMapped(kPageSize, 'b');
-
- // Expect that the mapped portion is consistent.
- EXPECT_EQ(
- 0, memcmp(reinterpret_cast<void*>(addr), bufMapped.c_str(), kPageSize));
-
- // Prepare to read the entire file contents.
- ASSERT_THAT(lseek(fd_.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0));
-
- // Expect that the file was fully updated.
- std::vector<char> bufFile(2 * kPageSize);
- ASSERT_THAT(Read(bufFile.data(), bufFile.size()),
- SyscallSucceedsWithValue(bufFile.size()));
- // Cast to void* to avoid EXPECT_THAT assuming bufFile.data() is a
- // NUL-terminated C std::string. EXPECT_THAT will try to print a char* as a C
- // std::string, possibly overruning the buffer.
- EXPECT_THAT(reinterpret_cast<void*>(bufFile.data()), EqualsMemory(bufB));
-}
-
-// Write data to mapped file.
-TEST_F(MMapFileTest, WriteShared) {
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED,
- fd_.get(), 0),
- SyscallSucceeds());
-
- size_t len = strlen(kFileContents);
- memcpy(reinterpret_cast<void*>(addr), kFileContents, len);
-
- // The file may not actually be updated until munmap is called.
- ASSERT_THAT(Unmap(), SyscallSucceeds());
-
- std::vector<char> buf(len);
- ASSERT_THAT(Read(buf.data(), buf.size()),
- SyscallSucceedsWithValue(buf.size()));
- // Cast to void* to avoid EXPECT_THAT assuming buf.data() is a
- // NUL-terminated C string. EXPECT_THAT will try to print a char* as a C
- // string, possibly overruning the buffer.
- EXPECT_THAT(reinterpret_cast<void*>(buf.data()),
- EqualsMemory(std::string(kFileContents)));
-}
-
-// Write data to portion of mapped page beyond the end of the file.
-// These writes are not reflected in the file.
-TEST_F(MMapFileTest, WriteSharedBeyondEnd) {
- // The file is only half of a page. We map an entire page. Writes to the
- // end of the mapping must not be reflected in the file.
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED,
- fd_.get(), 0),
- SyscallSucceeds());
-
- // First half; this is reflected in the file.
- std::string first(kPageSize / 2, 'A');
- memcpy(reinterpret_cast<void*>(addr), first.c_str(), first.size());
-
- // Second half; this is not reflected in the file.
- std::string second(kPageSize / 2, 'B');
- memcpy(reinterpret_cast<void*>(addr + kPageSize / 2), second.c_str(),
- second.size());
-
- // The file may not actually be updated until munmap is called.
- ASSERT_THAT(Unmap(), SyscallSucceeds());
-
- // Big enough to fit the entire page, if the writes are mistakenly written to
- // the file.
- std::vector<char> buf(kPageSize);
-
- // Only the first half is in the file.
- ASSERT_THAT(Read(buf.data(), buf.size()),
- SyscallSucceedsWithValue(first.size()));
- // Cast to void* to avoid EXPECT_THAT assuming buf.data() is a
- // NUL-terminated C string. EXPECT_THAT will try to print a char* as a C
- // NUL-terminated C std::string. EXPECT_THAT will try to print a char* as a C
- // std::string, possibly overruning the buffer.
- EXPECT_THAT(reinterpret_cast<void*>(buf.data()), EqualsMemory(first));
-}
-
-// The portion of a mapped page that becomes part of the file after a truncate
-// is reflected in the file.
-TEST_F(MMapFileTest, WriteSharedTruncateUp) {
- // The file is only half of a page. We map an entire page. Writes to the
- // end of the mapping must not be reflected in the file.
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED,
- fd_.get(), 0),
- SyscallSucceeds());
-
- // First half; this is reflected in the file.
- std::string first(kPageSize / 2, 'A');
- memcpy(reinterpret_cast<void*>(addr), first.c_str(), first.size());
-
- // Second half; this is not reflected in the file now (see
- // WriteSharedBeyondEnd), but will be after the truncate.
- std::string second(kPageSize / 2, 'B');
- memcpy(reinterpret_cast<void*>(addr + kPageSize / 2), second.c_str(),
- second.size());
-
- // Extend the file to a full page. The second half of the page will be
- // reflected in the file.
- EXPECT_THAT(ftruncate(fd_.get(), kPageSize), SyscallSucceeds());
-
- // The file may not actually be updated until munmap is called.
- ASSERT_THAT(Unmap(), SyscallSucceeds());
-
- // The whole page is in the file.
- std::vector<char> buf(kPageSize);
- ASSERT_THAT(Read(buf.data(), buf.size()),
- SyscallSucceedsWithValue(buf.size()));
- // Cast to void* to avoid EXPECT_THAT assuming buf.data() is a
- // NUL-terminated C string. EXPECT_THAT will try to print a char* as a C
- // string, possibly overruning the buffer.
- EXPECT_THAT(reinterpret_cast<void*>(buf.data()), EqualsMemory(first));
- EXPECT_THAT(reinterpret_cast<void*>(buf.data() + kPageSize / 2),
- EqualsMemory(second));
-}
-
-TEST_F(MMapFileTest, ReadSharedTruncateDownThenUp) {
- // Start from scratch.
- EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds());
-
- // Expand the file to a full page and dirty it.
- std::string buf(kPageSize, 'a');
- ASSERT_THAT(Write(buf.c_str(), buf.size()),
- SyscallSucceedsWithValue(buf.size()));
-
- // Map the page.
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0),
- SyscallSucceeds());
-
- // Check that the memory contains he file data.
- EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr), buf.c_str(), kPageSize));
-
- // Truncate down, then up.
- EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds());
- EXPECT_THAT(ftruncate(fd_.get(), kPageSize), SyscallSucceeds());
-
- // Check that the memory was zeroed.
- std::string zeroed(kPageSize, '\0');
- EXPECT_EQ(0,
- memcmp(reinterpret_cast<void*>(addr), zeroed.c_str(), kPageSize));
-
- // The file may not actually be updated until msync is called.
- ASSERT_THAT(Msync(), SyscallSucceeds());
-
- // Prepare to read the entire file contents.
- ASSERT_THAT(lseek(fd_.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0));
-
- // Expect that the file is fully updated.
- std::vector<char> bufFile(kPageSize);
- ASSERT_THAT(Read(bufFile.data(), bufFile.size()),
- SyscallSucceedsWithValue(bufFile.size()));
- EXPECT_EQ(0, memcmp(bufFile.data(), zeroed.c_str(), kPageSize));
-}
-
-TEST_F(MMapFileTest, WriteSharedTruncateDownThenUp) {
- // The file is only half of a page. We map an entire page. Writes to the
- // end of the mapping must not be reflected in the file.
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED,
- fd_.get(), 0),
- SyscallSucceeds());
-
- // First half; this will be deleted by truncate(0).
- std::string first(kPageSize / 2, 'A');
- memcpy(reinterpret_cast<void*>(addr), first.c_str(), first.size());
-
- // Truncate down, then up.
- EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds());
- EXPECT_THAT(ftruncate(fd_.get(), kPageSize), SyscallSucceeds());
-
- // The whole page is zeroed in memory.
- std::string zeroed(kPageSize, '\0');
- EXPECT_EQ(0,
- memcmp(reinterpret_cast<void*>(addr), zeroed.c_str(), kPageSize));
-
- // The file may not actually be updated until munmap is called.
- ASSERT_THAT(Unmap(), SyscallSucceeds());
-
- // The whole file is also zeroed.
- std::vector<char> buf(kPageSize);
- ASSERT_THAT(Read(buf.data(), buf.size()),
- SyscallSucceedsWithValue(buf.size()));
- // Cast to void* to avoid EXPECT_THAT assuming buf.data() is a
- // NUL-terminated C string. EXPECT_THAT will try to print a char* as a C
- // string, possibly overruning the buffer.
- EXPECT_THAT(reinterpret_cast<void*>(buf.data()), EqualsMemory(zeroed));
-}
-
-TEST_F(MMapFileTest, ReadSharedTruncateSIGBUS) {
- SetupGvisorDeathTest();
-
- // Start from scratch.
- EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds());
-
- // Expand the file to a full page and dirty it.
- std::string buf(kPageSize, 'a');
- ASSERT_THAT(Write(buf.c_str(), buf.size()),
- SyscallSucceedsWithValue(buf.size()));
-
- // Map the page.
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0),
- SyscallSucceeds());
-
- // Check that the mapping contains the file data.
- EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr), buf.c_str(), kPageSize));
-
- // Truncate down.
- EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds());
-
- // Accessing the truncated region should cause a SIGBUS.
- std::vector<char> in(kPageSize);
- EXPECT_EXIT(
- std::copy(reinterpret_cast<volatile char*>(addr),
- reinterpret_cast<volatile char*>(addr) + kPageSize, in.data()),
- ::testing::KilledBySignal(SIGBUS), "");
-}
-
-TEST_F(MMapFileTest, WriteSharedTruncateSIGBUS) {
- SetupGvisorDeathTest();
-
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED,
- fd_.get(), 0),
- SyscallSucceeds());
-
- // Touch the memory to be sure it really is mapped.
- size_t len = strlen(kFileContents);
- memcpy(reinterpret_cast<void*>(addr), kFileContents, len);
-
- // Truncate down.
- EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds());
-
- // Accessing the truncated file should cause a SIGBUS.
- EXPECT_EXIT(std::copy(kFileContents, kFileContents + len,
- reinterpret_cast<volatile char*>(addr)),
- ::testing::KilledBySignal(SIGBUS), "");
-}
-
-TEST_F(MMapFileTest, ReadSharedTruncatePartialPage) {
- // Start from scratch.
- EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds());
-
- // Dirty the file.
- std::string buf(kPageSize, 'a');
- ASSERT_THAT(Write(buf.c_str(), buf.size()),
- SyscallSucceedsWithValue(buf.size()));
-
- // Map a page.
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0),
- SyscallSucceeds());
-
- // Truncate to half of the page.
- EXPECT_THAT(ftruncate(fd_.get(), kPageSize / 2), SyscallSucceeds());
-
- // First half of the page untouched.
- EXPECT_EQ(0,
- memcmp(reinterpret_cast<void*>(addr), buf.data(), kPageSize / 2));
-
- // Second half is zeroed.
- std::string zeroed(kPageSize / 2, '\0');
- EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr + kPageSize / 2),
- zeroed.c_str(), kPageSize / 2));
-}
-
-// Page can still be accessed and contents are intact after truncating a partial
-// page.
-TEST_F(MMapFileTest, WriteSharedTruncatePartialPage) {
- // Expand the file to a full page.
- EXPECT_THAT(ftruncate(fd_.get(), kPageSize), SyscallSucceeds());
-
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED,
- fd_.get(), 0),
- SyscallSucceeds());
-
- // Fill the entire page.
- std::string contents(kPageSize, 'A');
- memcpy(reinterpret_cast<void*>(addr), contents.c_str(), contents.size());
-
- // Truncate half of the page.
- EXPECT_THAT(ftruncate(fd_.get(), kPageSize / 2), SyscallSucceeds());
-
- // First half of the page untouched.
- EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr), contents.c_str(),
- kPageSize / 2));
-
- // Second half zeroed.
- std::string zeroed(kPageSize / 2, '\0');
- EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr + kPageSize / 2),
- zeroed.c_str(), kPageSize / 2));
-}
-
-// MAP_PRIVATE writes are not carried through to the underlying file.
-TEST_F(MMapFileTest, WritePrivate) {
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE,
- fd_.get(), 0),
- SyscallSucceeds());
-
- size_t len = strlen(kFileContents);
- memcpy(reinterpret_cast<void*>(addr), kFileContents, len);
-
- // The file should not be updated, but if it mistakenly is, it may not be
- // until after munmap is called.
- ASSERT_THAT(Unmap(), SyscallSucceeds());
-
- std::vector<char> buf(len);
- ASSERT_THAT(Read(buf.data(), buf.size()),
- SyscallSucceedsWithValue(buf.size()));
- // Cast to void* to avoid EXPECT_THAT assuming buf.data() is a
- // NUL-terminated C string. EXPECT_THAT will try to print a char* as a C
- // string, possibly overruning the buffer.
- EXPECT_THAT(reinterpret_cast<void*>(buf.data()),
- EqualsMemory(std::string(len, '\0')));
-}
-
-// SIGBUS raised when writing past end of file to a private mapping.
-//
-// FIXME(b/37222275): Parameterize.
-TEST_F(MMapFileTest, SigBusDeathWritePrivate) {
- SetupGvisorDeathTest();
-
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE,
- fd_.get(), 0),
- SyscallSucceeds());
-
- // MMapFileTest makes a file kPageSize/2 long. The entire first page will be
- // accessible. Write just beyond that.
- size_t len = strlen(kFileContents);
- EXPECT_EXIT(std::copy(kFileContents, kFileContents + len,
- reinterpret_cast<volatile char*>(addr + kPageSize)),
- ::testing::KilledBySignal(SIGBUS), "");
-}
-
-// SIGBUS raised when reading past end of file on a shared mapping.
-//
-// FIXME(b/37222275): Parameterize.
-TEST_F(MMapFileTest, SigBusDeathReadShared) {
- SetupGvisorDeathTest();
-
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0),
- SyscallSucceeds());
-
- // MMapFileTest makes a file kPageSize/2 long. The entire first page will be
- // accessible. Read just beyond that.
- std::vector<char> in(kPageSize);
- EXPECT_EXIT(
- std::copy(reinterpret_cast<volatile char*>(addr + kPageSize),
- reinterpret_cast<volatile char*>(addr + kPageSize) + kPageSize,
- in.data()),
- ::testing::KilledBySignal(SIGBUS), "");
-}
-
-// SIGBUS raised when reading past end of file on a shared mapping.
-//
-// FIXME(b/37222275): Parameterize.
-TEST_F(MMapFileTest, SigBusDeathWriteShared) {
- SetupGvisorDeathTest();
-
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED,
- fd_.get(), 0),
- SyscallSucceeds());
-
- // MMapFileTest makes a file kPageSize/2 long. The entire first page will be
- // accessible. Write just beyond that.
- size_t len = strlen(kFileContents);
- EXPECT_EXIT(std::copy(kFileContents, kFileContents + len,
- reinterpret_cast<volatile char*>(addr + kPageSize)),
- ::testing::KilledBySignal(SIGBUS), "");
-}
-
-// Tests that SIGBUS is not raised when writing to a file-mapped page before
-// EOF, even if part of the mapping extends beyond EOF.
-TEST_F(MMapFileTest, NoSigBusOnPagesBeforeEOF) {
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE,
- fd_.get(), 0),
- SyscallSucceeds());
-
- // The test passes if this survives.
- size_t len = strlen(kFileContents);
- std::copy(kFileContents, kFileContents + len,
- reinterpret_cast<volatile char*>(addr));
-}
-
-// Tests that SIGBUS is not raised when writing to a file-mapped page containing
-// EOF, *after* the EOF for a private mapping.
-TEST_F(MMapFileTest, NoSigBusOnPageContainingEOFWritePrivate) {
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE,
- fd_.get(), 0),
- SyscallSucceeds());
-
- // The test passes if this survives. (Technically addr+kPageSize/2 is already
- // beyond EOF, but +1 to check for fencepost errors.)
- size_t len = strlen(kFileContents);
- std::copy(kFileContents, kFileContents + len,
- reinterpret_cast<volatile char*>(addr + (kPageSize / 2) + 1));
-}
-
-// Tests that SIGBUS is not raised when reading from a file-mapped page
-// containing EOF, *after* the EOF for a shared mapping.
-//
-// FIXME(b/37222275): Parameterize.
-TEST_F(MMapFileTest, NoSigBusOnPageContainingEOFReadShared) {
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0),
- SyscallSucceeds());
-
- // The test passes if this survives. (Technically addr+kPageSize/2 is already
- // beyond EOF, but +1 to check for fencepost errors.)
- auto* start = reinterpret_cast<volatile char*>(addr + (kPageSize / 2) + 1);
- size_t len = strlen(kFileContents);
- std::vector<char> in(len);
- std::copy(start, start + len, in.data());
-}
-
-// Tests that SIGBUS is not raised when writing to a file-mapped page containing
-// EOF, *after* the EOF for a shared mapping.
-//
-// FIXME(b/37222275): Parameterize.
-TEST_F(MMapFileTest, NoSigBusOnPageContainingEOFWriteShared) {
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED,
- fd_.get(), 0),
- SyscallSucceeds());
-
- // The test passes if this survives. (Technically addr+kPageSize/2 is already
- // beyond EOF, but +1 to check for fencepost errors.)
- size_t len = strlen(kFileContents);
- std::copy(kFileContents, kFileContents + len,
- reinterpret_cast<volatile char*>(addr + (kPageSize / 2) + 1));
-}
-
-// Tests that reading from writable shared file-mapped pages succeeds.
-//
-// On most platforms this is trivial, but when the file is mapped via the sentry
-// page cache (which does not yet support writing to shared mappings), a bug
-// caused reads to fail unnecessarily on such mappings.
-TEST_F(MMapFileTest, ReadingWritableSharedFilePageSucceeds) {
- uintptr_t addr;
- size_t len = strlen(kFileContents);
-
- ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED,
- fd_.get(), 0),
- SyscallSucceeds());
-
- std::vector<char> buf(kPageSize);
- // The test passes if this survives.
- std::copy(reinterpret_cast<volatile char*>(addr),
- reinterpret_cast<volatile char*>(addr) + len, buf.data());
-}
-
-// Tests that EFAULT is returned when invoking a syscall that requires the OS to
-// read past end of file (resulting in a fault in sentry context in the gVisor
-// case).
-TEST_F(MMapFileTest, InternalSigBus) {
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE,
- fd_.get(), 0),
- SyscallSucceeds());
-
- // This depends on the fact that gVisor implements pipes internally.
- int pipefd[2];
- ASSERT_THAT(pipe(pipefd), SyscallSucceeds());
- EXPECT_THAT(
- write(pipefd[1], reinterpret_cast<void*>(addr + kPageSize), kPageSize),
- SyscallFailsWithErrno(EFAULT));
-
- EXPECT_THAT(close(pipefd[0]), SyscallSucceeds());
- EXPECT_THAT(close(pipefd[1]), SyscallSucceeds());
-}
-
-// Like InternalSigBus, but test the WriteZerosAt path by reading from
-// /dev/zero to a shared mapping (so that the SIGBUS isn't caught during
-// copy-on-write breaking).
-TEST_F(MMapFileTest, InternalSigBusZeroing) {
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED,
- fd_.get(), 0),
- SyscallSucceeds());
-
- const FileDescriptor dev_zero =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDONLY));
- EXPECT_THAT(read(dev_zero.get(), reinterpret_cast<void*>(addr + kPageSize),
- kPageSize),
- SyscallFailsWithErrno(EFAULT));
-}
-
-// Checks that mmaps with a length of uint64_t(-PAGE_SIZE + 1) or greater do not
-// induce a sentry panic (due to "rounding up" to 0).
-TEST_F(MMapTest, HugeLength) {
- EXPECT_THAT(Map(0, static_cast<uint64_t>(-kPageSize + 1), PROT_NONE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0),
- SyscallFailsWithErrno(ENOMEM));
-}
-
-// Tests for a specific gVisor MM caching bug.
-TEST_F(MMapTest, AccessCOWInvalidatesCachedSegments) {
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDWR));
- auto zero_fd = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDONLY));
-
- // Get a two-page private mapping and fill it with 1s.
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0),
- SyscallSucceeds());
- memset(addr_, 1, 2 * kPageSize);
- MaybeSave();
-
- // Fork to make the mapping copy-on-write.
- pid_t const pid = fork();
- if (pid == 0) {
- // The child process waits for the parent to SIGKILL it.
- while (true) {
- pause();
- }
- }
- ASSERT_THAT(pid, SyscallSucceeds());
- auto cleanup_child = Cleanup([&] {
- EXPECT_THAT(kill(pid, SIGKILL), SyscallSucceeds());
- int status;
- EXPECT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid));
- });
-
- // Induce a read-only Access of the first page of the mapping, which will not
- // cause a copy. The usermem.Segment should be cached.
- ASSERT_THAT(PwriteFd(fd.get(), addr_, kPageSize, 0),
- SyscallSucceedsWithValue(kPageSize));
-
- // Induce a writable Access of both pages of the mapping. This should
- // invalidate the cached Segment.
- ASSERT_THAT(PreadFd(zero_fd.get(), addr_, 2 * kPageSize, 0),
- SyscallSucceedsWithValue(2 * kPageSize));
-
- // Induce a read-only Access of the first page of the mapping again. It should
- // read the 0s that were stored in the mapping by the read from /dev/zero. If
- // the read failed to invalidate the cached Segment, it will instead read the
- // 1s in the stale page.
- ASSERT_THAT(PwriteFd(fd.get(), addr_, kPageSize, 0),
- SyscallSucceedsWithValue(kPageSize));
- std::vector<char> buf(kPageSize);
- ASSERT_THAT(PreadFd(fd.get(), buf.data(), kPageSize, 0),
- SyscallSucceedsWithValue(kPageSize));
- for (size_t i = 0; i < kPageSize; i++) {
- ASSERT_EQ(0, buf[i]) << "at offset " << i;
- }
-}
-
-TEST_F(MMapTest, NoReserve) {
- const size_t kSize = 10 * 1 << 20; // 10M
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, kSize, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0),
- SyscallSucceeds());
- EXPECT_GT(addr, 0);
-
- // Check that every page can be read/written. Technically, writing to memory
- // could SIGSEGV in case there is no more memory available. In gVisor it
- // would never happen though because NORESERVE is ignored. In Linux, it's
- // possible to fail, but allocation is small enough that it's highly likely
- // to succeed.
- for (size_t j = 0; j < kSize; j += kPageSize) {
- EXPECT_EQ(0, reinterpret_cast<char*>(addr)[j]);
- reinterpret_cast<char*>(addr)[j] = j;
- }
-}
-
-// Map more than the gVisor page-cache map unit (64k) and ensure that
-// it is consistent with reading from the file.
-TEST_F(MMapFileTest, Bug38498194) {
- // Choose a sufficiently large map unit.
- constexpr int kSize = 4 * 1024 * 1024;
- EXPECT_THAT(ftruncate(fd_.get(), kSize), SyscallSucceeds());
-
- // Map a large enough region so that multiple internal segments
- // are created to back the mapping.
- uintptr_t addr;
- ASSERT_THAT(
- addr = Map(0, kSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd_.get(), 0),
- SyscallSucceeds());
-
- std::vector<char> expect(kSize, 'a');
- std::copy(expect.data(), expect.data() + expect.size(),
- reinterpret_cast<volatile char*>(addr));
-
- // Trigger writeback for gVisor. In Linux pages stay cached until
- // it can't hold onto them anymore.
- ASSERT_THAT(Unmap(), SyscallSucceeds());
-
- std::vector<char> buf(kSize);
- ASSERT_THAT(Read(buf.data(), buf.size()),
- SyscallSucceedsWithValue(buf.size()));
- EXPECT_EQ(buf, expect) << std::string(buf.data(), buf.size());
-}
-
-// Tests that reading from a file to a memory mapping of the same file does not
-// deadlock.
-TEST_F(MMapFileTest, SelfRead) {
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED,
- fd_.get(), 0),
- SyscallSucceeds());
- EXPECT_THAT(Read(reinterpret_cast<char*>(addr), kPageSize / 2),
- SyscallSucceedsWithValue(kPageSize / 2));
- // The resulting file contents are poorly-specified and irrelevant.
-}
-
-// Tests that writing to a file from a memory mapping of the same file does not
-// deadlock.
-TEST_F(MMapFileTest, SelfWrite) {
- uintptr_t addr;
- ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0),
- SyscallSucceeds());
- EXPECT_THAT(Write(reinterpret_cast<char*>(addr), kPageSize / 2),
- SyscallSucceedsWithValue(kPageSize / 2));
- // The resulting file contents are poorly-specified and irrelevant.
-}
-
-TEST(MMapDeathTest, TruncateAfterCOWBreak) {
- SetupGvisorDeathTest();
-
- // Create and map a single-page file.
- auto const temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(temp_file.path(), O_RDWR));
- ASSERT_THAT(ftruncate(fd.get(), kPageSize), SyscallSucceeds());
- auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(Mmap(
- nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd.get(), 0));
-
- // Write to this mapping, causing the page to be copied for write.
- memset(mapping.ptr(), 'a', mapping.len());
- MaybeSave(); // Trigger a co-operative save cycle.
-
- // Truncate the file and expect it to invalidate the copied page.
- ASSERT_THAT(ftruncate(fd.get(), 0), SyscallSucceeds());
- EXPECT_EXIT(*reinterpret_cast<volatile char*>(mapping.ptr()),
- ::testing::KilledBySignal(SIGBUS), "");
-}
-
-// Regression test for #147.
-TEST(MMapNoFixtureTest, MapReadOnlyAfterCreateWriteOnly) {
- std::string filename = NewTempAbsPath();
-
- // We have to create the file O_RDONLY to reproduce the bug because
- // fsgofer.localFile.Create() silently upgrades O_WRONLY to O_RDWR, causing
- // the cached "write-only" FD to be read/write and therefore usable by mmap().
- auto const ro_fd = ASSERT_NO_ERRNO_AND_VALUE(
- Open(filename, O_RDONLY | O_CREAT | O_EXCL, 0666));
-
- // Get a write-only FD for the same file, which should be ignored by mmap()
- // (but isn't in #147).
- auto const wo_fd = ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_WRONLY));
- ASSERT_THAT(ftruncate(wo_fd.get(), kPageSize), SyscallSucceeds());
-
- auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
- Mmap(nullptr, kPageSize, PROT_READ, MAP_SHARED, ro_fd.get(), 0));
- std::vector<char> buf(kPageSize);
- // The test passes if this survives.
- std::copy(static_cast<char*>(mapping.ptr()),
- static_cast<char*>(mapping.endptr()), buf.data());
-}
-
-// Conditional on MAP_32BIT.
-#ifdef __x86_64__
-
-TEST(MMapNoFixtureTest, Map32Bit) {
- auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_NONE, MAP_PRIVATE | MAP_32BIT));
- EXPECT_LT(mapping.addr(), static_cast<uintptr_t>(1) << 32);
- EXPECT_LE(mapping.endaddr(), static_cast<uintptr_t>(1) << 32);
-}
-
-#endif // defined(__x86_64__)
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/mount.cc b/test/syscalls/linux/mount.cc
deleted file mode 100644
index e35be3cab..000000000
--- a/test/syscalls/linux/mount.cc
+++ /dev/null
@@ -1,326 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <sys/mount.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <functional>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/strings/string_view.h"
-#include "absl/time/time.h"
-#include "test/util/capability_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/mount_util.h"
-#include "test/util/multiprocess_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(MountTest, MountBadFilesystem) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- // Linux expects a valid target before it checks the file system name.
- auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- EXPECT_THAT(mount("", dir.path().c_str(), "foobar", 0, ""),
- SyscallFailsWithErrno(ENODEV));
-}
-
-TEST(MountTest, MountInvalidTarget) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- auto const dir = NewTempAbsPath();
- EXPECT_THAT(mount("", dir.c_str(), "tmpfs", 0, ""),
- SyscallFailsWithErrno(ENOENT));
-}
-
-TEST(MountTest, MountPermDenied) {
- // Clear CAP_SYS_ADMIN.
- if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))) {
- EXPECT_NO_ERRNO(SetCapability(CAP_SYS_ADMIN, false));
- }
-
- // Linux expects a valid target before checking capability.
- auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- EXPECT_THAT(mount("", dir.path().c_str(), "", 0, ""),
- SyscallFailsWithErrno(EPERM));
-}
-
-TEST(MountTest, UmountPermDenied) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto const mount =
- ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "tmpfs", 0, "", 0));
-
- // Drop privileges in another thread, so we can still unmount the mounted
- // directory.
- ScopedThread([&]() {
- EXPECT_NO_ERRNO(SetCapability(CAP_SYS_ADMIN, false));
- EXPECT_THAT(umount(dir.path().c_str()), SyscallFailsWithErrno(EPERM));
- });
-}
-
-TEST(MountTest, MountOverBusy) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto const fd = ASSERT_NO_ERRNO_AND_VALUE(
- Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777));
-
- // Should be able to mount over a busy directory.
- ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "tmpfs", 0, "", 0));
-}
-
-TEST(MountTest, OpenFileBusy) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
- Mount("", dir.path(), "tmpfs", 0, "mode=0700", 0));
- auto const fd = ASSERT_NO_ERRNO_AND_VALUE(
- Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777));
-
- // An open file should prevent unmounting.
- EXPECT_THAT(umount(dir.path().c_str()), SyscallFailsWithErrno(EBUSY));
-}
-
-TEST(MountTest, UmountDetach) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- // structure:
- //
- // dir (mount point)
- // subdir
- // file
- //
- // We show that we can walk around in the mount after detach-unmount dir.
- //
- // We show that even though dir is unreachable from outside the mount, we can
- // still reach dir's (former) parent!
- auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- const struct stat before = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
- auto mount =
- ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "tmpfs", 0, "mode=0700",
- /* umountflags= */ MNT_DETACH));
- const struct stat after = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
- EXPECT_NE(before.st_ino, after.st_ino);
-
- // Create files in the new mount.
- constexpr char kContents[] = "no no no";
- auto const subdir =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path()));
- auto const file = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateFileWith(dir.path(), kContents, 0777));
-
- auto const dir_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(subdir.path(), O_RDONLY | O_DIRECTORY));
- auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY));
-
- // Unmount the tmpfs.
- mount.Release()();
-
- const struct stat after2 = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
- EXPECT_EQ(before.st_ino, after2.st_ino);
-
- // Can still read file after unmounting.
- std::vector<char> buf(sizeof(kContents));
- EXPECT_THAT(ReadFd(fd.get(), buf.data(), buf.size()), SyscallSucceeds());
-
- // Walk to dir.
- auto const mounted_dir = ASSERT_NO_ERRNO_AND_VALUE(
- OpenAt(dir_fd.get(), "..", O_DIRECTORY | O_RDONLY));
- // Walk to dir/file.
- auto const fd_again = ASSERT_NO_ERRNO_AND_VALUE(
- OpenAt(mounted_dir.get(), std::string(Basename(file.path())), O_RDONLY));
-
- std::vector<char> buf2(sizeof(kContents));
- EXPECT_THAT(ReadFd(fd_again.get(), buf2.data(), buf2.size()),
- SyscallSucceeds());
- EXPECT_EQ(buf, buf2);
-
- // Walking outside the unmounted realm should still work, too!
- auto const dir_parent = ASSERT_NO_ERRNO_AND_VALUE(
- OpenAt(mounted_dir.get(), "..", O_DIRECTORY | O_RDONLY));
-}
-
-TEST(MountTest, ActiveSubmountBusy) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto const mount1 = ASSERT_NO_ERRNO_AND_VALUE(
- Mount("", dir.path(), "tmpfs", 0, "mode=0700", 0));
-
- auto const dir2 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path()));
- auto const mount2 =
- ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir2.path(), "tmpfs", 0, "", 0));
-
- // Since dir now has an active submount, should not be able to unmount.
- EXPECT_THAT(umount(dir.path().c_str()), SyscallFailsWithErrno(EBUSY));
-}
-
-TEST(MountTest, MountTmpfs) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- // NOTE(b/129868551): Inode IDs are only stable across S/R if we have an open
- // FD for that inode. Since we are going to compare inode IDs below, get a
- // FileDescriptor for this directory here, which will be closed automatically
- // at the end of the test.
- auto const fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY, O_RDONLY));
-
- const struct stat before = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
-
- {
- auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
- Mount("", dir.path(), "tmpfs", 0, "mode=0700", 0));
-
- const struct stat s = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
- EXPECT_EQ(s.st_mode, S_IFDIR | 0700);
- EXPECT_NE(s.st_ino, before.st_ino);
-
- EXPECT_NO_ERRNO(Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777));
- }
-
- // Now that dir is unmounted again, we should have the old inode back.
- const struct stat after = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
- EXPECT_EQ(before.st_ino, after.st_ino);
-}
-
-TEST(MountTest, MountTmpfsMagicValIgnored) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
- Mount("", dir.path(), "tmpfs", MS_MGC_VAL, "mode=0700", 0));
-}
-
-// Passing nullptr to data is equivalent to "".
-TEST(MountTest, NullData) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- EXPECT_THAT(mount("", dir.path().c_str(), "tmpfs", 0, nullptr),
- SyscallSucceeds());
- EXPECT_THAT(umount2(dir.path().c_str(), 0), SyscallSucceeds());
-}
-
-TEST(MountTest, MountReadonly) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
- Mount("", dir.path(), "tmpfs", MS_RDONLY, "mode=0777", 0));
-
- const struct stat s = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
- EXPECT_EQ(s.st_mode, S_IFDIR | 0777);
-
- std::string const filename = JoinPath(dir.path(), "foo");
- EXPECT_THAT(open(filename.c_str(), O_RDWR | O_CREAT, 0777),
- SyscallFailsWithErrno(EROFS));
-}
-
-PosixErrorOr<absl::Time> ATime(absl::string_view file) {
- struct stat s = {};
- if (stat(std::string(file).c_str(), &s) == -1) {
- return PosixError(errno, "stat failed");
- }
- return absl::TimeFromTimespec(s.st_atim);
-}
-
-TEST(MountTest, MountNoAtime) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
- Mount("", dir.path(), "tmpfs", MS_NOATIME, "mode=0777", 0));
-
- std::string const contents = "No no no, don't follow the instructions!";
- auto const file = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateFileWith(dir.path(), contents, 0777));
-
- absl::Time const before = ASSERT_NO_ERRNO_AND_VALUE(ATime(file.path()));
-
- // Reading from the file should change the atime, but the MS_NOATIME flag
- // should prevent that.
- auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
- char buf[100];
- int read_n;
- ASSERT_THAT(read_n = read(fd.get(), buf, sizeof(buf)), SyscallSucceeds());
- EXPECT_EQ(std::string(buf, read_n), contents);
-
- absl::Time const after = ASSERT_NO_ERRNO_AND_VALUE(ATime(file.path()));
-
- // Expect that atime hasn't changed.
- EXPECT_EQ(before, after);
-}
-
-TEST(MountTest, MountNoExec) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
- Mount("", dir.path(), "tmpfs", MS_NOEXEC, "mode=0777", 0));
-
- std::string const contents = "No no no, don't follow the instructions!";
- auto const file = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateFileWith(dir.path(), contents, 0777));
-
- int execve_errno;
- ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(file.path(), {}, {}, nullptr, &execve_errno));
- EXPECT_EQ(execve_errno, EACCES);
-}
-
-TEST(MountTest, RenameRemoveMountPoint) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- auto const dir_parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto const dir =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir_parent.path()));
- auto const new_dir = NewTempAbsPath();
-
- auto const mount =
- ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "tmpfs", 0, "", 0));
-
- ASSERT_THAT(rename(dir.path().c_str(), new_dir.c_str()),
- SyscallFailsWithErrno(EBUSY));
-
- ASSERT_THAT(rmdir(dir.path().c_str()), SyscallFailsWithErrno(EBUSY));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/mremap.cc b/test/syscalls/linux/mremap.cc
deleted file mode 100644
index f0e5f7d82..000000000
--- a/test/syscalls/linux/mremap.cc
+++ /dev/null
@@ -1,492 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <string.h>
-#include <sys/mman.h>
-
-#include <string>
-
-#include "gmock/gmock.h"
-#include "absl/strings/string_view.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/logging.h"
-#include "test/util/memory_util.h"
-#include "test/util/multiprocess_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-using ::testing::_;
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// Fixture for mremap tests parameterized by mmap flags.
-using MremapParamTest = ::testing::TestWithParam<int>;
-
-TEST_P(MremapParamTest, Noop) {
- Mapping const m =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, GetParam()));
-
- ASSERT_THAT(Mremap(m.ptr(), kPageSize, kPageSize, 0, nullptr),
- IsPosixErrorOkAndHolds(m.ptr()));
- EXPECT_TRUE(IsMapped(m.addr()));
-}
-
-TEST_P(MremapParamTest, InPlace_ShrinkingWholeVMA) {
- Mapping const m =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(2 * kPageSize, PROT_NONE, GetParam()));
-
- const auto rest = [&] {
- // N.B. we must be in a single-threaded subprocess to ensure a
- // background thread doesn't concurrently map the second page.
- void* addr = mremap(m.ptr(), 2 * kPageSize, kPageSize, 0, nullptr);
- TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed");
- TEST_CHECK(addr == m.ptr());
- MaybeSave();
-
- TEST_CHECK(IsMapped(m.addr()));
- TEST_CHECK(!IsMapped(m.addr() + kPageSize));
- };
-
- EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
-}
-
-TEST_P(MremapParamTest, InPlace_ShrinkingPartialVMA) {
- Mapping const m =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(3 * kPageSize, PROT_NONE, GetParam()));
-
- const auto rest = [&] {
- void* addr = mremap(m.ptr(), 2 * kPageSize, kPageSize, 0, nullptr);
- TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed");
- TEST_CHECK(addr == m.ptr());
- MaybeSave();
-
- TEST_CHECK(IsMapped(m.addr()));
- TEST_CHECK(!IsMapped(m.addr() + kPageSize));
- TEST_CHECK(IsMapped(m.addr() + 2 * kPageSize));
- };
-
- EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
-}
-
-TEST_P(MremapParamTest, InPlace_ShrinkingAcrossVMAs) {
- Mapping const m =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(3 * kPageSize, PROT_READ, GetParam()));
- // Changing permissions on the first page forces it to become a separate vma.
- ASSERT_THAT(mprotect(m.ptr(), kPageSize, PROT_NONE), SyscallSucceeds());
-
- const auto rest = [&] {
- // Both old_size and new_size now span two vmas; mremap
- // shouldn't care.
- void* addr = mremap(m.ptr(), 3 * kPageSize, 2 * kPageSize, 0, nullptr);
- TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed");
- TEST_CHECK(addr == m.ptr());
- MaybeSave();
-
- TEST_CHECK(IsMapped(m.addr()));
- TEST_CHECK(IsMapped(m.addr() + kPageSize));
- TEST_CHECK(!IsMapped(m.addr() + 2 * kPageSize));
- };
-
- EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
-}
-
-TEST_P(MremapParamTest, InPlace_ExpansionSuccess) {
- Mapping const m =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(2 * kPageSize, PROT_NONE, GetParam()));
-
- const auto rest = [&] {
- // Unmap the second page so that the first can be expanded back into it.
- //
- // N.B. we must be in a single-threaded subprocess to ensure a
- // background thread doesn't concurrently map this page.
- TEST_PCHECK(
- munmap(reinterpret_cast<void*>(m.addr() + kPageSize), kPageSize) == 0);
- MaybeSave();
-
- void* addr = mremap(m.ptr(), kPageSize, 2 * kPageSize, 0, nullptr);
- TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed");
- TEST_CHECK(addr == m.ptr());
- MaybeSave();
-
- TEST_CHECK(IsMapped(m.addr()));
- TEST_CHECK(IsMapped(m.addr() + kPageSize));
- };
-
- EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
-}
-
-TEST_P(MremapParamTest, InPlace_ExpansionFailure) {
- Mapping const m =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(3 * kPageSize, PROT_NONE, GetParam()));
-
- const auto rest = [&] {
- // Unmap the second page, leaving a one-page hole. Trying to expand the
- // first page to three pages should fail since the original third page
- // is still mapped.
- TEST_PCHECK(
- munmap(reinterpret_cast<void*>(m.addr() + kPageSize), kPageSize) == 0);
- MaybeSave();
-
- void* addr = mremap(m.ptr(), kPageSize, 3 * kPageSize, 0, nullptr);
- TEST_CHECK_MSG(addr == MAP_FAILED, "mremap unexpectedly succeeded");
- TEST_PCHECK_MSG(errno == ENOMEM, "mremap failed with wrong errno");
- MaybeSave();
-
- TEST_CHECK(IsMapped(m.addr()));
- TEST_CHECK(!IsMapped(m.addr() + kPageSize));
- TEST_CHECK(IsMapped(m.addr() + 2 * kPageSize));
- };
-
- EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
-}
-
-TEST_P(MremapParamTest, MayMove_Expansion) {
- Mapping const m =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(3 * kPageSize, PROT_NONE, GetParam()));
-
- const auto rest = [&] {
- // Unmap the second page, leaving a one-page hole. Trying to expand the
- // first page to three pages with MREMAP_MAYMOVE should force the
- // mapping to be relocated since the original third page is still
- // mapped.
- TEST_PCHECK(
- munmap(reinterpret_cast<void*>(m.addr() + kPageSize), kPageSize) == 0);
- MaybeSave();
-
- void* addr2 =
- mremap(m.ptr(), kPageSize, 3 * kPageSize, MREMAP_MAYMOVE, nullptr);
- TEST_PCHECK_MSG(addr2 != MAP_FAILED, "mremap failed");
- MaybeSave();
-
- const Mapping m2 = Mapping(addr2, 3 * kPageSize);
- TEST_CHECK(m.addr() != m2.addr());
-
- TEST_CHECK(!IsMapped(m.addr()));
- TEST_CHECK(!IsMapped(m.addr() + kPageSize));
- TEST_CHECK(IsMapped(m.addr() + 2 * kPageSize));
- TEST_CHECK(IsMapped(m2.addr()));
- TEST_CHECK(IsMapped(m2.addr() + kPageSize));
- TEST_CHECK(IsMapped(m2.addr() + 2 * kPageSize));
- };
-
- EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
-}
-
-TEST_P(MremapParamTest, Fixed_SourceAndDestinationCannotOverlap) {
- Mapping const m =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, GetParam()));
-
- ASSERT_THAT(Mremap(m.ptr(), kPageSize, kPageSize,
- MREMAP_MAYMOVE | MREMAP_FIXED, m.ptr()),
- PosixErrorIs(EINVAL, _));
- EXPECT_TRUE(IsMapped(m.addr()));
-}
-
-TEST_P(MremapParamTest, Fixed_SameSize) {
- Mapping const src =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, GetParam()));
- Mapping const dst =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, GetParam()));
-
- const auto rest = [&] {
- // Unmap dst to create a hole.
- TEST_PCHECK(munmap(dst.ptr(), kPageSize) == 0);
- MaybeSave();
-
- void* addr = mremap(src.ptr(), kPageSize, kPageSize,
- MREMAP_MAYMOVE | MREMAP_FIXED, dst.ptr());
- TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed");
- TEST_CHECK(addr == dst.ptr());
- MaybeSave();
-
- TEST_CHECK(!IsMapped(src.addr()));
- TEST_CHECK(IsMapped(dst.addr()));
- };
-
- EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
-}
-
-TEST_P(MremapParamTest, Fixed_SameSize_Unmapping) {
- // Like the Fixed_SameSize case, but expect mremap to unmap the destination
- // automatically.
- Mapping const src =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, GetParam()));
- Mapping const dst =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, GetParam()));
-
- const auto rest = [&] {
- void* addr = mremap(src.ptr(), kPageSize, kPageSize,
- MREMAP_MAYMOVE | MREMAP_FIXED, dst.ptr());
- TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed");
- TEST_CHECK(addr == dst.ptr());
- MaybeSave();
-
- TEST_CHECK(!IsMapped(src.addr()));
- TEST_CHECK(IsMapped(dst.addr()));
- };
-
- EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
-}
-
-TEST_P(MremapParamTest, Fixed_ShrinkingWholeVMA) {
- Mapping const src =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(2 * kPageSize, PROT_NONE, GetParam()));
- Mapping const dst =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(2 * kPageSize, PROT_NONE, GetParam()));
-
- const auto rest = [&] {
- // Unmap dst so we can check that mremap does not keep the
- // second page.
- TEST_PCHECK(munmap(dst.ptr(), 2 * kPageSize) == 0);
- MaybeSave();
-
- void* addr = mremap(src.ptr(), 2 * kPageSize, kPageSize,
- MREMAP_MAYMOVE | MREMAP_FIXED, dst.ptr());
- TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed");
- TEST_CHECK(addr == dst.ptr());
- MaybeSave();
-
- TEST_CHECK(!IsMapped(src.addr()));
- TEST_CHECK(!IsMapped(src.addr() + kPageSize));
- TEST_CHECK(IsMapped(dst.addr()));
- TEST_CHECK(!IsMapped(dst.addr() + kPageSize));
- };
-
- EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
-}
-
-TEST_P(MremapParamTest, Fixed_ShrinkingPartialVMA) {
- Mapping const src =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(3 * kPageSize, PROT_NONE, GetParam()));
- Mapping const dst =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(2 * kPageSize, PROT_NONE, GetParam()));
-
- const auto rest = [&] {
- // Unmap dst so we can check that mremap does not keep the
- // second page.
- TEST_PCHECK(munmap(dst.ptr(), 2 * kPageSize) == 0);
- MaybeSave();
-
- void* addr = mremap(src.ptr(), 2 * kPageSize, kPageSize,
- MREMAP_MAYMOVE | MREMAP_FIXED, dst.ptr());
- TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed");
- TEST_CHECK(addr == dst.ptr());
- MaybeSave();
-
- TEST_CHECK(!IsMapped(src.addr()));
- TEST_CHECK(!IsMapped(src.addr() + kPageSize));
- TEST_CHECK(IsMapped(src.addr() + 2 * kPageSize));
- TEST_CHECK(IsMapped(dst.addr()));
- TEST_CHECK(!IsMapped(dst.addr() + kPageSize));
- };
-
- EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
-}
-
-TEST_P(MremapParamTest, Fixed_ShrinkingAcrossVMAs) {
- Mapping const src =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(3 * kPageSize, PROT_READ, GetParam()));
- // Changing permissions on the first page forces it to become a separate vma.
- ASSERT_THAT(mprotect(src.ptr(), kPageSize, PROT_NONE), SyscallSucceeds());
- Mapping const dst =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(2 * kPageSize, PROT_NONE, GetParam()));
-
- const auto rest = [&] {
- // Unlike flags=0, MREMAP_FIXED requires that [old_address,
- // old_address+new_size) only spans a single vma.
- void* addr = mremap(src.ptr(), 3 * kPageSize, 2 * kPageSize,
- MREMAP_MAYMOVE | MREMAP_FIXED, dst.ptr());
- TEST_CHECK_MSG(addr == MAP_FAILED, "mremap unexpectedly succeeded");
- TEST_PCHECK_MSG(errno == EFAULT, "mremap failed with wrong errno");
- MaybeSave();
-
- TEST_CHECK(IsMapped(src.addr()));
- TEST_CHECK(IsMapped(src.addr() + kPageSize));
- // Despite failing, mremap should have unmapped [old_address+new_size,
- // old_address+old_size) (i.e. the third page).
- TEST_CHECK(!IsMapped(src.addr() + 2 * kPageSize));
- // Despite failing, mremap should have unmapped the destination pages.
- TEST_CHECK(!IsMapped(dst.addr()));
- TEST_CHECK(!IsMapped(dst.addr() + kPageSize));
- };
-
- EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
-}
-
-TEST_P(MremapParamTest, Fixed_Expansion) {
- Mapping const src =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, GetParam()));
- Mapping const dst =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(2 * kPageSize, PROT_NONE, GetParam()));
-
- const auto rest = [&] {
- // Unmap dst so we can check that mremap actually maps all pages
- // at the destination.
- TEST_PCHECK(munmap(dst.ptr(), 2 * kPageSize) == 0);
- MaybeSave();
-
- void* addr = mremap(src.ptr(), kPageSize, 2 * kPageSize,
- MREMAP_MAYMOVE | MREMAP_FIXED, dst.ptr());
- TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed");
- TEST_CHECK(addr == dst.ptr());
- MaybeSave();
-
- TEST_CHECK(!IsMapped(src.addr()));
- TEST_CHECK(IsMapped(dst.addr()));
- TEST_CHECK(IsMapped(dst.addr() + kPageSize));
- };
-
- EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
-}
-
-INSTANTIATE_TEST_SUITE_P(PrivateShared, MremapParamTest,
- ::testing::Values(MAP_PRIVATE, MAP_SHARED));
-
-// mremap with old_size == 0 only works with MAP_SHARED after Linux 4.14
-// (dba58d3b8c50 "mm/mremap: fail map duplication attempts for private
-// mappings").
-
-TEST(MremapTest, InPlace_Copy) {
- Mapping const m =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, MAP_SHARED));
- EXPECT_THAT(Mremap(m.ptr(), 0, kPageSize, 0, nullptr),
- PosixErrorIs(ENOMEM, _));
-}
-
-TEST(MremapTest, MayMove_Copy) {
- Mapping const m =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, MAP_SHARED));
-
- // Remainder of this test executes in a subprocess to ensure that if mremap
- // incorrectly removes m, it is not remapped by another thread.
- const auto rest = [&] {
- void* ptr = mremap(m.ptr(), 0, kPageSize, MREMAP_MAYMOVE, nullptr);
- MaybeSave();
- TEST_PCHECK_MSG(ptr != MAP_FAILED, "mremap failed");
- TEST_CHECK(ptr != m.ptr());
- TEST_CHECK(IsMapped(m.addr()));
- TEST_CHECK(IsMapped(reinterpret_cast<uintptr_t>(ptr)));
- };
- EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
-}
-
-TEST(MremapTest, MustMove_Copy) {
- Mapping const src =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, MAP_SHARED));
- Mapping const dst =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, MAP_PRIVATE));
-
- // Remainder of this test executes in a subprocess to ensure that if mremap
- // incorrectly removes src, it is not remapped by another thread.
- const auto rest = [&] {
- void* ptr = mremap(src.ptr(), 0, kPageSize, MREMAP_MAYMOVE | MREMAP_FIXED,
- dst.ptr());
- MaybeSave();
- TEST_PCHECK_MSG(ptr != MAP_FAILED, "mremap failed");
- TEST_CHECK(ptr == dst.ptr());
- TEST_CHECK(IsMapped(src.addr()));
- TEST_CHECK(IsMapped(dst.addr()));
- };
- EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
-}
-
-void ExpectAllBytesAre(absl::string_view v, char c) {
- for (size_t i = 0; i < v.size(); i++) {
- ASSERT_EQ(v[i], c) << "at offset " << i;
- }
-}
-
-TEST(MremapTest, ExpansionPreservesCOWPagesAndExposesNewFilePages) {
- // Create a file with 3 pages. The first is filled with 'a', the second is
- // filled with 'b', and the third is filled with 'c'.
- TempPath const file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
- ASSERT_THAT(WriteFd(fd.get(), std::string(kPageSize, 'a').c_str(), kPageSize),
- SyscallSucceedsWithValue(kPageSize));
- ASSERT_THAT(WriteFd(fd.get(), std::string(kPageSize, 'b').c_str(), kPageSize),
- SyscallSucceedsWithValue(kPageSize));
- ASSERT_THAT(WriteFd(fd.get(), std::string(kPageSize, 'c').c_str(), kPageSize),
- SyscallSucceedsWithValue(kPageSize));
-
- // Create a private mapping of the first 2 pages, and fill the second page
- // with 'd'.
- Mapping const src = ASSERT_NO_ERRNO_AND_VALUE(Mmap(nullptr, 2 * kPageSize,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE, fd.get(), 0));
- memset(reinterpret_cast<void*>(src.addr() + kPageSize), 'd', kPageSize);
- MaybeSave();
-
- // Move the mapping while expanding it to 3 pages. The resulting mapping
- // should contain the original first page of the file (filled with 'a'),
- // followed by the private copy of the second page (filled with 'd'), followed
- // by the newly-mapped third page of the file (filled with 'c').
- Mapping const dst = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(3 * kPageSize, PROT_NONE, MAP_PRIVATE));
- ASSERT_THAT(Mremap(src.ptr(), 2 * kPageSize, 3 * kPageSize,
- MREMAP_MAYMOVE | MREMAP_FIXED, dst.ptr()),
- IsPosixErrorOkAndHolds(dst.ptr()));
- auto const v = dst.view();
- ExpectAllBytesAre(v.substr(0, kPageSize), 'a');
- ExpectAllBytesAre(v.substr(kPageSize, kPageSize), 'd');
- ExpectAllBytesAre(v.substr(2 * kPageSize, kPageSize), 'c');
-}
-
-TEST(MremapDeathTest, SharedAnon) {
- SetupGvisorDeathTest();
-
- // Reserve 4 pages of address space.
- Mapping const reserved = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(4 * kPageSize, PROT_NONE, MAP_PRIVATE));
-
- // Create a 2-page shared anonymous mapping at the beginning of the
- // reservation. Fill the first page with 'a' and the second with 'b'.
- Mapping const m = ASSERT_NO_ERRNO_AND_VALUE(
- Mmap(reserved.ptr(), 2 * kPageSize, PROT_READ | PROT_WRITE,
- MAP_SHARED | MAP_ANONYMOUS | MAP_FIXED, -1, 0));
- memset(m.ptr(), 'a', kPageSize);
- memset(reinterpret_cast<void*>(m.addr() + kPageSize), 'b', kPageSize);
- MaybeSave();
-
- // Shrink the mapping to 1 page in-place.
- ASSERT_THAT(Mremap(m.ptr(), 2 * kPageSize, kPageSize, 0, m.ptr()),
- IsPosixErrorOkAndHolds(m.ptr()));
-
- // Expand the mapping to 3 pages, moving it forward by 1 page in the process
- // since the old and new mappings can't overlap.
- void* const new_m = reinterpret_cast<void*>(m.addr() + kPageSize);
- ASSERT_THAT(Mremap(m.ptr(), kPageSize, 3 * kPageSize,
- MREMAP_MAYMOVE | MREMAP_FIXED, new_m),
- IsPosixErrorOkAndHolds(new_m));
-
- // The first 2 pages of the mapping should still contain the data we wrote
- // (i.e. shrinking should not have discarded the second page's data), while
- // touching the third page should raise SIGBUS.
- auto const v =
- absl::string_view(static_cast<char const*>(new_m), 3 * kPageSize);
- ExpectAllBytesAre(v.substr(0, kPageSize), 'a');
- ExpectAllBytesAre(v.substr(kPageSize, kPageSize), 'b');
- EXPECT_EXIT(ExpectAllBytesAre(v.substr(2 * kPageSize, kPageSize), '\0'),
- ::testing::KilledBySignal(SIGBUS), "");
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/msync.cc b/test/syscalls/linux/msync.cc
deleted file mode 100644
index ac7146017..000000000
--- a/test/syscalls/linux/msync.cc
+++ /dev/null
@@ -1,153 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sys/mman.h>
-#include <unistd.h>
-
-#include <functional>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "test/util/file_descriptor.h"
-#include "test/util/memory_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// Parameters for msync tests. Use a std::tuple so we can use
-// ::testing::Combine.
-using MsyncTestParam =
- std::tuple<int, // msync flags
- std::function<PosixErrorOr<Mapping>()> // returns mapping to
- // msync
- >;
-
-class MsyncParameterizedTest : public ::testing::TestWithParam<MsyncTestParam> {
- protected:
- int msync_flags() const { return std::get<0>(GetParam()); }
-
- PosixErrorOr<Mapping> GetMapping() const { return std::get<1>(GetParam())(); }
-};
-
-// All valid msync(2) flag combinations, not including MS_INVALIDATE. ("Linux
-// permits a call to msync() that specifies neither [MS_SYNC or MS_ASYNC], with
-// semantics that are (currently) equivalent to specifying MS_ASYNC." -
-// msync(2))
-constexpr std::initializer_list<int> kMsyncFlags = {MS_SYNC, MS_ASYNC, 0};
-
-// Returns functions that return mappings that should be successfully
-// msync()able.
-std::vector<std::function<PosixErrorOr<Mapping>()>> SyncableMappings() {
- std::vector<std::function<PosixErrorOr<Mapping>()>> funcs;
- for (bool const writable : {false, true}) {
- for (int const mflags : {MAP_PRIVATE, MAP_SHARED}) {
- int const prot = PROT_READ | (writable ? PROT_WRITE : 0);
- int const oflags = O_CREAT | (writable ? O_RDWR : O_RDONLY);
- funcs.push_back([=] {
- return MmapAnon(kPageSize, prot, mflags);
- });
- funcs.push_back([=]() -> PosixErrorOr<Mapping> {
- std::string const path = NewTempAbsPath();
- ASSIGN_OR_RETURN_ERRNO(auto fd, Open(path, oflags, 0644));
- // Don't unlink the file since that breaks save/restore. Just let the
- // test infrastructure clean up all of our temporary files when we're
- // done.
- return Mmap(nullptr, kPageSize, prot, mflags, fd.get(), 0);
- });
- }
- }
- return funcs;
-}
-
-PosixErrorOr<Mapping> NoMappings() {
- return PosixError(EINVAL, "unexpected attempt to create a mapping");
-}
-
-// "Fixture" for msync tests that hold for all valid flags, but do not create
-// mappings.
-using MsyncNoMappingTest = MsyncParameterizedTest;
-
-TEST_P(MsyncNoMappingTest, UnmappedAddressWithZeroLengthSucceeds) {
- EXPECT_THAT(msync(nullptr, 0, msync_flags()), SyscallSucceeds());
-}
-
-TEST_P(MsyncNoMappingTest, UnmappedAddressWithNonzeroLengthFails) {
- EXPECT_THAT(msync(nullptr, kPageSize, msync_flags()),
- SyscallFailsWithErrno(ENOMEM));
-}
-
-INSTANTIATE_TEST_SUITE_P(All, MsyncNoMappingTest,
- ::testing::Combine(::testing::ValuesIn(kMsyncFlags),
- ::testing::Values(NoMappings)));
-
-// "Fixture" for msync tests that are not parameterized by msync flags, but do
-// create mappings.
-using MsyncNoFlagsTest = MsyncParameterizedTest;
-
-TEST_P(MsyncNoFlagsTest, BothSyncAndAsyncFails) {
- auto m = ASSERT_NO_ERRNO_AND_VALUE(GetMapping());
- EXPECT_THAT(msync(m.ptr(), m.len(), MS_SYNC | MS_ASYNC),
- SyscallFailsWithErrno(EINVAL));
-}
-
-INSTANTIATE_TEST_SUITE_P(
- All, MsyncNoFlagsTest,
- ::testing::Combine(::testing::Values(0), // ignored
- ::testing::ValuesIn(SyncableMappings())));
-
-// "Fixture" for msync tests parameterized by both msync flags and sources of
-// mappings.
-using MsyncFullParamTest = MsyncParameterizedTest;
-
-TEST_P(MsyncFullParamTest, NormallySucceeds) {
- auto m = ASSERT_NO_ERRNO_AND_VALUE(GetMapping());
- EXPECT_THAT(msync(m.ptr(), m.len(), msync_flags()), SyscallSucceeds());
-}
-
-TEST_P(MsyncFullParamTest, UnalignedLengthSucceeds) {
- auto m = ASSERT_NO_ERRNO_AND_VALUE(GetMapping());
- EXPECT_THAT(msync(m.ptr(), m.len() - 1, msync_flags()), SyscallSucceeds());
-}
-
-TEST_P(MsyncFullParamTest, UnalignedAddressFails) {
- auto m = ASSERT_NO_ERRNO_AND_VALUE(GetMapping());
- EXPECT_THAT(
- msync(reinterpret_cast<void*>(m.addr() + 1), m.len() - 1, msync_flags()),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_P(MsyncFullParamTest, InvalidateUnlockedSucceeds) {
- auto m = ASSERT_NO_ERRNO_AND_VALUE(GetMapping());
- EXPECT_THAT(msync(m.ptr(), m.len(), msync_flags() | MS_INVALIDATE),
- SyscallSucceeds());
-}
-
-// The test for MS_INVALIDATE on mlocked pages is in mlock.cc since it requires
-// probing for mlock support.
-
-INSTANTIATE_TEST_SUITE_P(
- All, MsyncFullParamTest,
- ::testing::Combine(::testing::ValuesIn(kMsyncFlags),
- ::testing::ValuesIn(SyncableMappings())));
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/munmap.cc b/test/syscalls/linux/munmap.cc
deleted file mode 100644
index 067241f4d..000000000
--- a/test/syscalls/linux/munmap.cc
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sys/mman.h>
-
-#include "gtest/gtest.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-class MunmapTest : public ::testing::Test {
- protected:
- void SetUp() override {
- m_ = mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- ASSERT_NE(MAP_FAILED, m_);
- }
-
- void* m_ = nullptr;
-};
-
-TEST_F(MunmapTest, HappyCase) {
- EXPECT_THAT(munmap(m_, kPageSize), SyscallSucceeds());
-}
-
-TEST_F(MunmapTest, ZeroLength) {
- EXPECT_THAT(munmap(m_, 0), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_F(MunmapTest, LastPageRoundUp) {
- // Attempt to unmap up to and including the last page.
- EXPECT_THAT(munmap(m_, static_cast<size_t>(-kPageSize + 1)),
- SyscallFailsWithErrno(EINVAL));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/open.cc b/test/syscalls/linux/open.cc
deleted file mode 100644
index 2b1df52ce..000000000
--- a/test/syscalls/linux/open.cc
+++ /dev/null
@@ -1,378 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <fcntl.h>
-#include <linux/capability.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/memory/memory.h"
-#include "test/syscalls/linux/file_base.h"
-#include "test/util/capability_util.h"
-#include "test/util/cleanup.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// This test is currently very rudimentary.
-//
-// There are plenty of extra cases to cover once the sentry supports them.
-//
-// Different types of opens:
-// * O_CREAT
-// * O_DIRECTORY
-// * O_NOFOLLOW
-// * O_PATH <- Will we ever support this?
-//
-// Special operations on open:
-// * O_EXCL
-//
-// Special files:
-// * Blocking behavior for a named pipe.
-//
-// Different errors:
-// * EACCES
-// * EEXIST
-// * ENAMETOOLONG
-// * ELOOP
-// * ENOTDIR
-// * EPERM
-class OpenTest : public FileTest {
- void SetUp() override {
- FileTest::SetUp();
-
- ASSERT_THAT(
- write(test_file_fd_.get(), test_data_.c_str(), test_data_.length()),
- SyscallSucceedsWithValue(test_data_.length()));
- EXPECT_THAT(lseek(test_file_fd_.get(), 0, SEEK_SET), SyscallSucceeds());
- }
-
- public:
- const std::string test_data_ = "hello world\n";
-};
-
-TEST_F(OpenTest, ReadOnly) {
- char buf;
- const FileDescriptor ro_file =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDONLY));
-
- EXPECT_THAT(read(ro_file.get(), &buf, 1), SyscallSucceedsWithValue(1));
- EXPECT_THAT(lseek(ro_file.get(), 0, SEEK_SET), SyscallSucceeds());
- EXPECT_THAT(write(ro_file.get(), &buf, 1), SyscallFailsWithErrno(EBADF));
-}
-
-TEST_F(OpenTest, WriteOnly) {
- char buf;
- const FileDescriptor wo_file =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_WRONLY));
-
- EXPECT_THAT(read(wo_file.get(), &buf, 1), SyscallFailsWithErrno(EBADF));
- EXPECT_THAT(lseek(wo_file.get(), 0, SEEK_SET), SyscallSucceeds());
- EXPECT_THAT(write(wo_file.get(), &buf, 1), SyscallSucceedsWithValue(1));
-}
-
-TEST_F(OpenTest, ReadWrite) {
- char buf;
- const FileDescriptor rw_file =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR));
-
- EXPECT_THAT(read(rw_file.get(), &buf, 1), SyscallSucceedsWithValue(1));
- EXPECT_THAT(lseek(rw_file.get(), 0, SEEK_SET), SyscallSucceeds());
- EXPECT_THAT(write(rw_file.get(), &buf, 1), SyscallSucceedsWithValue(1));
-}
-
-TEST_F(OpenTest, RelPath) {
- auto name = std::string(Basename(test_file_name_));
-
- ASSERT_THAT(chdir(GetAbsoluteTestTmpdir().c_str()), SyscallSucceeds());
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(name, O_RDONLY));
-}
-
-TEST_F(OpenTest, AbsPath) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDONLY));
-}
-
-TEST_F(OpenTest, AtRelPath) {
- auto name = std::string(Basename(test_file_name_));
- const FileDescriptor dirfd = ASSERT_NO_ERRNO_AND_VALUE(
- Open(GetAbsoluteTestTmpdir(), O_RDONLY | O_DIRECTORY));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(OpenAt(dirfd.get(), name, O_RDONLY));
-}
-
-TEST_F(OpenTest, AtAbsPath) {
- const FileDescriptor dirfd = ASSERT_NO_ERRNO_AND_VALUE(
- Open(GetAbsoluteTestTmpdir(), O_RDONLY | O_DIRECTORY));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(OpenAt(dirfd.get(), test_file_name_, O_RDONLY));
-}
-
-TEST_F(OpenTest, OpenNoFollowSymlink) {
- const std::string link_path = JoinPath(GetAbsoluteTestTmpdir(), "link");
- ASSERT_THAT(symlink(test_file_name_.c_str(), link_path.c_str()),
- SyscallSucceeds());
- auto cleanup = Cleanup([link_path]() {
- EXPECT_THAT(unlink(link_path.c_str()), SyscallSucceeds());
- });
-
- // Open will succeed without O_NOFOLLOW and fails with O_NOFOLLOW.
- const FileDescriptor fd2 =
- ASSERT_NO_ERRNO_AND_VALUE(Open(link_path, O_RDONLY));
- ASSERT_THAT(open(link_path.c_str(), O_RDONLY | O_NOFOLLOW),
- SyscallFailsWithErrno(ELOOP));
-}
-
-TEST_F(OpenTest, OpenNoFollowStillFollowsLinksInPath) {
- // We will create the following structure:
- // tmp_folder/real_folder/file
- // tmp_folder/sym_folder -> tmp_folder/real_folder
- //
- // We will then open tmp_folder/sym_folder/file with O_NOFOLLOW and it
- // should succeed as O_NOFOLLOW only applies to the final path component.
- auto tmp_path =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(GetAbsoluteTestTmpdir()));
- auto sym_path = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateSymlinkTo(GetAbsoluteTestTmpdir(), tmp_path.path()));
- auto file_path =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(tmp_path.path()));
-
- auto path_via_symlink = JoinPath(sym_path.path(), Basename(file_path.path()));
- const FileDescriptor fd2 =
- ASSERT_NO_ERRNO_AND_VALUE(Open(path_via_symlink, O_RDONLY | O_NOFOLLOW));
-}
-
-TEST_F(OpenTest, Fault) {
- char* totally_not_null = nullptr;
- ASSERT_THAT(open(totally_not_null, O_RDONLY), SyscallFailsWithErrno(EFAULT));
-}
-
-TEST_F(OpenTest, AppendOnly) {
- // First write some data to the fresh file.
- const int64_t kBufSize = 1024;
- std::vector<char> buf(kBufSize, 'a');
-
- FileDescriptor fd0 = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR));
-
- std::fill(buf.begin(), buf.end(), 'a');
- EXPECT_THAT(WriteFd(fd0.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(buf.size()));
- fd0.reset(); // Close the file early.
-
- // Next get two handles to the same file. We open two files because we want
- // to make sure that appending is respected between them.
- const FileDescriptor fd1 =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR | O_APPEND));
- EXPECT_THAT(lseek(fd1.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0));
-
- const FileDescriptor fd2 =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR | O_APPEND));
- EXPECT_THAT(lseek(fd2.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0));
-
- // Then try to write to the first file and make sure the bytes are appended.
- EXPECT_THAT(WriteFd(fd1.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(buf.size()));
-
- // Check that the size of the file is correct and that the offset has been
- // incremented to that size.
- struct stat s0;
- EXPECT_THAT(fstat(fd1.get(), &s0), SyscallSucceeds());
- EXPECT_EQ(s0.st_size, kBufSize * 2);
- EXPECT_THAT(lseek(fd1.get(), 0, SEEK_CUR),
- SyscallSucceedsWithValue(kBufSize * 2));
-
- // Then try to write to the second file and make sure the bytes are appended.
- EXPECT_THAT(WriteFd(fd2.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(buf.size()));
-
- // Check that the size of the file is correct and that the offset has been
- // incremented to that size.
- struct stat s1;
- EXPECT_THAT(fstat(fd2.get(), &s1), SyscallSucceeds());
- EXPECT_EQ(s1.st_size, kBufSize * 3);
- EXPECT_THAT(lseek(fd2.get(), 0, SEEK_CUR),
- SyscallSucceedsWithValue(kBufSize * 3));
-}
-
-TEST_F(OpenTest, AppendConcurrentWrite) {
- constexpr int kThreadCount = 5;
- constexpr int kBytesPerThread = 10000;
- std::unique_ptr<ScopedThread> threads[kThreadCount];
-
- // In case of the uncached policy, we expect that a file system can be changed
- // externally, so we create a new inode each time when we open a file and we
- // can't guarantee that writes to files with O_APPEND will work correctly.
- SKIP_IF(getenv("GVISOR_GOFER_UNCACHED"));
-
- EXPECT_THAT(truncate(test_file_name_.c_str(), 0), SyscallSucceeds());
-
- std::string filename = test_file_name_;
- DisableSave ds; // Too many syscalls.
- // Start kThreadCount threads which will write concurrently into the same
- // file.
- for (int i = 0; i < kThreadCount; i++) {
- threads[i] = absl::make_unique<ScopedThread>([filename]() {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_RDWR | O_APPEND));
-
- for (int j = 0; j < kBytesPerThread; j++) {
- EXPECT_THAT(WriteFd(fd.get(), &j, 1), SyscallSucceedsWithValue(1));
- }
- });
- }
- for (int i = 0; i < kThreadCount; i++) {
- threads[i]->Join();
- }
-
- // Check that the size of the file is correct.
- struct stat st;
- EXPECT_THAT(stat(test_file_name_.c_str(), &st), SyscallSucceeds());
- EXPECT_EQ(st.st_size, kThreadCount * kBytesPerThread);
-}
-
-TEST_F(OpenTest, Truncate) {
- {
- // First write some data to the new file and close it.
- FileDescriptor fd0 =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_WRONLY));
- std::vector<char> orig(10, 'a');
- EXPECT_THAT(WriteFd(fd0.get(), orig.data(), orig.size()),
- SyscallSucceedsWithValue(orig.size()));
- }
-
- // Then open with truncate and verify that offset is set to 0.
- const FileDescriptor fd1 =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR | O_TRUNC));
- EXPECT_THAT(lseek(fd1.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0));
-
- // Then write less data to the file and ensure the old content is gone.
- std::vector<char> want(5, 'b');
- EXPECT_THAT(WriteFd(fd1.get(), want.data(), want.size()),
- SyscallSucceedsWithValue(want.size()));
-
- struct stat stat;
- EXPECT_THAT(fstat(fd1.get(), &stat), SyscallSucceeds());
- EXPECT_EQ(stat.st_size, want.size());
- EXPECT_THAT(lseek(fd1.get(), 0, SEEK_CUR),
- SyscallSucceedsWithValue(want.size()));
-
- // Read the data and ensure only the latest write is in the file.
- std::vector<char> got(want.size() + 1, 'c');
- ASSERT_THAT(pread(fd1.get(), got.data(), got.size(), 0),
- SyscallSucceedsWithValue(want.size()));
- EXPECT_EQ(memcmp(want.data(), got.data(), want.size()), 0)
- << "rbuf=" << got.data();
- EXPECT_EQ(got.back(), 'c'); // Last byte should not have been modified.
-}
-
-TEST_F(OpenTest, NameTooLong) {
- char buf[4097] = {};
- memset(buf, 'a', 4097);
- EXPECT_THAT(open(buf, O_RDONLY), SyscallFailsWithErrno(ENAMETOOLONG));
-}
-
-TEST_F(OpenTest, DotsFromRoot) {
- const FileDescriptor rootfd =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/", O_RDONLY | O_DIRECTORY));
- const FileDescriptor other_rootfd =
- ASSERT_NO_ERRNO_AND_VALUE(OpenAt(rootfd.get(), "..", O_RDONLY));
-}
-
-TEST_F(OpenTest, DirectoryWritableFails) {
- ASSERT_THAT(open(GetAbsoluteTestTmpdir().c_str(), O_RDWR),
- SyscallFailsWithErrno(EISDIR));
-}
-
-TEST_F(OpenTest, FileNotDirectory) {
- // Create a file and try to open it with O_DIRECTORY.
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- ASSERT_THAT(open(file.path().c_str(), O_RDONLY | O_DIRECTORY),
- SyscallFailsWithErrno(ENOTDIR));
-}
-
-TEST_F(OpenTest, Null) {
- char c = '\0';
- ASSERT_THAT(open(&c, O_RDONLY), SyscallFailsWithErrno(ENOENT));
-}
-
-// NOTE(b/119785738): While the man pages specify that this behavior should be
-// undefined, Linux truncates the file on opening read only if we have write
-// permission, so we will too.
-TEST_F(OpenTest, CanTruncateReadOnly) {
- const FileDescriptor fd1 =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDONLY | O_TRUNC));
-
- struct stat stat;
- EXPECT_THAT(fstat(fd1.get(), &stat), SyscallSucceeds());
- EXPECT_EQ(stat.st_size, 0);
-}
-
-// If we don't have read permission on the file, opening with
-// O_TRUNC should fail.
-TEST_F(OpenTest, CanTruncateReadOnlyNoWritePermission_NoRandomSave) {
- // Drop capabilities that allow us to override file permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
-
- const DisableSave ds; // Permissions are dropped.
- ASSERT_THAT(chmod(test_file_name_.c_str(), S_IRUSR | S_IRGRP),
- SyscallSucceeds());
-
- ASSERT_THAT(open(test_file_name_.c_str(), O_RDONLY | O_TRUNC),
- SyscallFailsWithErrno(EACCES));
-
- const FileDescriptor fd1 =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDONLY));
-
- struct stat stat;
- EXPECT_THAT(fstat(fd1.get(), &stat), SyscallSucceeds());
- EXPECT_EQ(stat.st_size, test_data_.size());
-}
-
-// If we don't have read permission but have write permission, opening O_WRONLY
-// and O_TRUNC should succeed.
-TEST_F(OpenTest, CanTruncateWriteOnlyNoReadPermission_NoRandomSave) {
- const DisableSave ds; // Permissions are dropped.
-
- EXPECT_THAT(fchmod(test_file_fd_.get(), S_IWUSR | S_IWGRP),
- SyscallSucceeds());
-
- const FileDescriptor fd1 =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_WRONLY | O_TRUNC));
-
- EXPECT_THAT(fchmod(test_file_fd_.get(), S_IRUSR | S_IRGRP),
- SyscallSucceeds());
-
- const FileDescriptor fd2 =
- ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDONLY));
-
- struct stat stat;
- EXPECT_THAT(fstat(fd2.get(), &stat), SyscallSucceeds());
- EXPECT_EQ(stat.st_size, 0);
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/open_create.cc b/test/syscalls/linux/open_create.cc
deleted file mode 100644
index e5a85ef9d..000000000
--- a/test/syscalls/linux/open_create.cc
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/temp_umask.h"
-#include "test/util/capability_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-TEST(CreateTest, TmpFile) {
- int fd;
- EXPECT_THAT(fd = open(JoinPath(GetAbsoluteTestTmpdir(), "a").c_str(),
- O_RDWR | O_CREAT, 0666),
- SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
-}
-
-TEST(CreateTest, ExistingFile) {
- int fd;
- EXPECT_THAT(
- fd = open(JoinPath(GetAbsoluteTestTmpdir(), "ExistingFile").c_str(),
- O_RDWR | O_CREAT, 0666),
- SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
-
- EXPECT_THAT(
- fd = open(JoinPath(GetAbsoluteTestTmpdir(), "ExistingFile").c_str(),
- O_RDWR | O_CREAT, 0666),
- SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
-}
-
-TEST(CreateTest, CreateAtFile) {
- int dirfd;
- EXPECT_THAT(dirfd = open(GetAbsoluteTestTmpdir().c_str(), O_DIRECTORY, 0666),
- SyscallSucceeds());
- EXPECT_THAT(openat(dirfd, "CreateAtFile", O_RDWR | O_CREAT, 0666),
- SyscallSucceeds());
- EXPECT_THAT(close(dirfd), SyscallSucceeds());
-}
-
-TEST(CreateTest, HonorsUmask_NoRandomSave) {
- const DisableSave ds; // file cannot be re-opened as writable.
- TempUmask mask(0222);
- int fd;
- ASSERT_THAT(
- fd = open(JoinPath(GetAbsoluteTestTmpdir(), "UmaskedFile").c_str(),
- O_RDWR | O_CREAT, 0666),
- SyscallSucceeds());
- struct stat statbuf;
- ASSERT_THAT(fstat(fd, &statbuf), SyscallSucceeds());
- EXPECT_EQ(0444, statbuf.st_mode & 0777);
- EXPECT_THAT(close(fd), SyscallSucceeds());
-}
-
-TEST(CreateTest, CreateExclusively) {
- std::string filename = NewTempAbsPath();
-
- int fd;
- ASSERT_THAT(fd = open(filename.c_str(), O_CREAT | O_RDWR, 0644),
- SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
-
- EXPECT_THAT(open(filename.c_str(), O_CREAT | O_EXCL | O_RDWR, 0644),
- SyscallFailsWithErrno(EEXIST));
-}
-
-TEST(CreateTest, CreateFailsOnUnpermittedDir) {
- // Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to
- // always override directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_THAT(open("/foo", O_CREAT | O_RDWR, 0644),
- SyscallFailsWithErrno(EACCES));
-}
-
-TEST(CreateTest, CreateFailsOnDirWithoutWritePerms) {
- // Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to
- // always override directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- auto parent = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0555));
- auto file = JoinPath(parent.path(), "foo");
- ASSERT_THAT(open(file.c_str(), O_CREAT | O_RDWR, 0644),
- SyscallFailsWithErrno(EACCES));
-}
-
-// A file originally created RW, but opened RO can later be opened RW.
-TEST(CreateTest, OpenCreateROThenRW) {
- TempPath file(NewTempAbsPath());
-
- // Create a RW file, but only open it RO.
- FileDescriptor fd1 = ASSERT_NO_ERRNO_AND_VALUE(
- Open(file.path(), O_CREAT | O_EXCL | O_RDONLY, 0644));
-
- // Now get a RW FD.
- FileDescriptor fd2 = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
-
- // fd1 is not writable, but fd2 is.
- char c = 'a';
- EXPECT_THAT(WriteFd(fd1.get(), &c, 1), SyscallFailsWithErrno(EBADF));
- EXPECT_THAT(WriteFd(fd2.get(), &c, 1), SyscallSucceedsWithValue(1));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/packet_socket.cc b/test/syscalls/linux/packet_socket.cc
deleted file mode 100644
index 7a3379b9e..000000000
--- a/test/syscalls/linux/packet_socket.cc
+++ /dev/null
@@ -1,299 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <arpa/inet.h>
-#include <linux/capability.h>
-#include <linux/if_arp.h>
-#include <linux/if_packet.h>
-#include <net/ethernet.h>
-#include <netinet/in.h>
-#include <netinet/ip.h>
-#include <netinet/udp.h>
-#include <poll.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "absl/base/internal/endian.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/capability_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/test_util.h"
-
-// Some of these tests involve sending packets via AF_PACKET sockets and the
-// loopback interface. Because AF_PACKET circumvents so much of the networking
-// stack, Linux sees these packets as "martian", i.e. they claim to be to/from
-// localhost but don't have the usual associated data. Thus Linux drops them by
-// default. You can see where this happens by following the code at:
-//
-// - net/ipv4/ip_input.c:ip_rcv_finish, which calls
-// - net/ipv4/route.c:ip_route_input_noref, which calls
-// - net/ipv4/route.c:ip_route_input_slow, which finds and drops martian
-// packets.
-//
-// To tell Linux not to drop these packets, you need to tell it to accept our
-// funny packets (which are completely valid and correct, but lack associated
-// in-kernel data because we use AF_PACKET):
-//
-// echo 1 >> /proc/sys/net/ipv4/conf/lo/accept_local
-// echo 1 >> /proc/sys/net/ipv4/conf/lo/route_localnet
-//
-// These tests require CAP_NET_RAW to run.
-
-// TODO(gvisor.dev/issue/173): gVisor support.
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-constexpr char kMessage[] = "soweoneul malhaebwa";
-constexpr in_port_t kPort = 0x409c; // htons(40000)
-
-//
-// "Cooked" tests. Cooked AF_PACKET sockets do not contain link layer
-// headers, and provide link layer destination/source information via a
-// returned struct sockaddr_ll.
-//
-
-// Send kMessage via sock to loopback
-void SendUDPMessage(int sock) {
- struct sockaddr_in dest = {};
- dest.sin_port = kPort;
- dest.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- dest.sin_family = AF_INET;
- EXPECT_THAT(sendto(sock, kMessage, sizeof(kMessage), 0,
- reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)),
- SyscallSucceedsWithValue(sizeof(kMessage)));
-}
-
-// Send an IP packet and make sure ETH_P_<something else> doesn't pick it up.
-TEST(BasicCookedPacketTest, WrongType) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
- SKIP_IF(IsRunningOnGvisor());
-
- FileDescriptor sock =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_PACKET, SOCK_DGRAM, ETH_P_PUP));
-
- // Let's use a simple IP payload: a UDP datagram.
- FileDescriptor udp_sock =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0));
- SendUDPMessage(udp_sock.get());
-
- // Wait and make sure the socket never becomes readable.
- struct pollfd pfd = {};
- pfd.fd = sock.get();
- pfd.events = POLLIN;
- EXPECT_THAT(RetryEINTR(poll)(&pfd, 1, 1000), SyscallSucceedsWithValue(0));
-}
-
-// Tests for "cooked" (SOCK_DGRAM) packet(7) sockets.
-class CookedPacketTest : public ::testing::TestWithParam<int> {
- protected:
- // Creates a socket to be used in tests.
- void SetUp() override;
-
- // Closes the socket created by SetUp().
- void TearDown() override;
-
- // Gets the device index of the loopback device.
- int GetLoopbackIndex();
-
- // The socket used for both reading and writing.
- int socket_;
-};
-
-void CookedPacketTest::SetUp() {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
- SKIP_IF(IsRunningOnGvisor());
-
- ASSERT_THAT(socket_ = socket(AF_PACKET, SOCK_DGRAM, htons(GetParam())),
- SyscallSucceeds());
-}
-
-void CookedPacketTest::TearDown() {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
- SKIP_IF(IsRunningOnGvisor());
-
- EXPECT_THAT(close(socket_), SyscallSucceeds());
-}
-
-int CookedPacketTest::GetLoopbackIndex() {
- struct ifreq ifr;
- snprintf(ifr.ifr_name, IFNAMSIZ, "lo");
- EXPECT_THAT(ioctl(socket_, SIOCGIFINDEX, &ifr), SyscallSucceeds());
- EXPECT_NE(ifr.ifr_ifindex, 0);
- return ifr.ifr_ifindex;
-}
-
-// Receive via a packet socket.
-TEST_P(CookedPacketTest, Receive) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
- SKIP_IF(IsRunningOnGvisor());
-
- // Let's use a simple IP payload: a UDP datagram.
- FileDescriptor udp_sock =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0));
- SendUDPMessage(udp_sock.get());
-
- // Wait for the socket to become readable.
- struct pollfd pfd = {};
- pfd.fd = socket_;
- pfd.events = POLLIN;
- EXPECT_THAT(RetryEINTR(poll)(&pfd, 1, 2000), SyscallSucceedsWithValue(1));
-
- // Read and verify the data.
- constexpr size_t packet_size =
- sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(kMessage);
- char buf[64];
- struct sockaddr_ll src = {};
- socklen_t src_len = sizeof(src);
- ASSERT_THAT(recvfrom(socket_, buf, sizeof(buf), 0,
- reinterpret_cast<struct sockaddr*>(&src), &src_len),
- SyscallSucceedsWithValue(packet_size));
- ASSERT_EQ(src_len, sizeof(src));
-
- // Verify the source address.
- EXPECT_EQ(src.sll_family, AF_PACKET);
- EXPECT_EQ(src.sll_protocol, htons(ETH_P_IP));
- EXPECT_EQ(src.sll_ifindex, GetLoopbackIndex());
- EXPECT_EQ(src.sll_hatype, ARPHRD_LOOPBACK);
- EXPECT_EQ(src.sll_halen, ETH_ALEN);
- // This came from the loopback device, so the address is all 0s.
- for (int i = 0; i < src.sll_halen; i++) {
- EXPECT_EQ(src.sll_addr[i], 0);
- }
-
- // Verify the IP header. We memcpy to deal with pointer aligment.
- struct iphdr ip = {};
- memcpy(&ip, buf, sizeof(ip));
- EXPECT_EQ(ip.ihl, 5);
- EXPECT_EQ(ip.version, 4);
- EXPECT_EQ(ip.tot_len, htons(packet_size));
- EXPECT_EQ(ip.protocol, IPPROTO_UDP);
- EXPECT_EQ(ip.daddr, htonl(INADDR_LOOPBACK));
- EXPECT_EQ(ip.saddr, htonl(INADDR_LOOPBACK));
-
- // Verify the UDP header. We memcpy to deal with pointer aligment.
- struct udphdr udp = {};
- memcpy(&udp, buf + sizeof(iphdr), sizeof(udp));
- EXPECT_EQ(udp.dest, kPort);
- EXPECT_EQ(udp.len, htons(sizeof(udphdr) + sizeof(kMessage)));
-
- // Verify the payload.
- char* payload = reinterpret_cast<char*>(buf + sizeof(iphdr) + sizeof(udphdr));
- EXPECT_EQ(strncmp(payload, kMessage, sizeof(kMessage)), 0);
-}
-
-// Send via a packet socket.
-TEST_P(CookedPacketTest, Send) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
- SKIP_IF(IsRunningOnGvisor());
-
- // Let's send a UDP packet and receive it using a regular UDP socket.
- FileDescriptor udp_sock =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0));
- struct sockaddr_in bind_addr = {};
- bind_addr.sin_family = AF_INET;
- bind_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- bind_addr.sin_port = kPort;
- ASSERT_THAT(
- bind(udp_sock.get(), reinterpret_cast<struct sockaddr*>(&bind_addr),
- sizeof(bind_addr)),
- SyscallSucceeds());
-
- // Set up the destination physical address.
- struct sockaddr_ll dest = {};
- dest.sll_family = AF_PACKET;
- dest.sll_halen = ETH_ALEN;
- dest.sll_ifindex = GetLoopbackIndex();
- dest.sll_protocol = htons(ETH_P_IP);
- // We're sending to the loopback device, so the address is all 0s.
- memset(dest.sll_addr, 0x00, ETH_ALEN);
-
- // Set up the IP header.
- struct iphdr iphdr = {0};
- iphdr.ihl = 5;
- iphdr.version = 4;
- iphdr.tos = 0;
- iphdr.tot_len =
- htons(sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(kMessage));
- // Get a pseudo-random ID. If we clash with an in-use ID the test will fail,
- // but we have no way of getting an ID we know to be good.
- srand(*reinterpret_cast<unsigned int*>(&iphdr));
- iphdr.id = rand();
- // Linux sets this bit ("do not fragment") for small packets.
- iphdr.frag_off = 1 << 6;
- iphdr.ttl = 64;
- iphdr.protocol = IPPROTO_UDP;
- iphdr.daddr = htonl(INADDR_LOOPBACK);
- iphdr.saddr = htonl(INADDR_LOOPBACK);
- iphdr.check = IPChecksum(iphdr);
-
- // Set up the UDP header.
- struct udphdr udphdr = {};
- udphdr.source = kPort;
- udphdr.dest = kPort;
- udphdr.len = htons(sizeof(udphdr) + sizeof(kMessage));
- udphdr.check = UDPChecksum(iphdr, udphdr, kMessage, sizeof(kMessage));
-
- // Copy both headers and the payload into our packet buffer.
- char send_buf[sizeof(iphdr) + sizeof(udphdr) + sizeof(kMessage)];
- memcpy(send_buf, &iphdr, sizeof(iphdr));
- memcpy(send_buf + sizeof(iphdr), &udphdr, sizeof(udphdr));
- memcpy(send_buf + sizeof(iphdr) + sizeof(udphdr), kMessage, sizeof(kMessage));
-
- // Send it.
- ASSERT_THAT(sendto(socket_, send_buf, sizeof(send_buf), 0,
- reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Wait for the packet to become available on both sockets.
- struct pollfd pfd = {};
- pfd.fd = udp_sock.get();
- pfd.events = POLLIN;
- ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 5000), SyscallSucceedsWithValue(1));
- pfd.fd = socket_;
- pfd.events = POLLIN;
- ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 5000), SyscallSucceedsWithValue(1));
-
- // Receive on the packet socket.
- char recv_buf[sizeof(send_buf)];
- ASSERT_THAT(recv(socket_, recv_buf, sizeof(recv_buf), 0),
- SyscallSucceedsWithValue(sizeof(recv_buf)));
- ASSERT_EQ(memcmp(recv_buf, send_buf, sizeof(send_buf)), 0);
-
- // Receive on the UDP socket.
- struct sockaddr_in src;
- socklen_t src_len = sizeof(src);
- ASSERT_THAT(recvfrom(udp_sock.get(), recv_buf, sizeof(recv_buf), MSG_DONTWAIT,
- reinterpret_cast<struct sockaddr*>(&src), &src_len),
- SyscallSucceedsWithValue(sizeof(kMessage)));
- // Check src and payload.
- EXPECT_EQ(strncmp(recv_buf, kMessage, sizeof(kMessage)), 0);
- EXPECT_EQ(src.sin_family, AF_INET);
- EXPECT_EQ(src.sin_port, kPort);
- EXPECT_EQ(src.sin_addr.s_addr, htonl(INADDR_LOOPBACK));
-}
-
-INSTANTIATE_TEST_SUITE_P(AllInetTests, CookedPacketTest,
- ::testing::Values(ETH_P_IP, ETH_P_ALL));
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/packet_socket_raw.cc b/test/syscalls/linux/packet_socket_raw.cc
deleted file mode 100644
index 9e96460ee..000000000
--- a/test/syscalls/linux/packet_socket_raw.cc
+++ /dev/null
@@ -1,314 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <arpa/inet.h>
-#include <linux/capability.h>
-#include <linux/if_arp.h>
-#include <linux/if_packet.h>
-#include <net/ethernet.h>
-#include <netinet/in.h>
-#include <netinet/ip.h>
-#include <netinet/udp.h>
-#include <poll.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "absl/base/internal/endian.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/capability_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/test_util.h"
-
-// Some of these tests involve sending packets via AF_PACKET sockets and the
-// loopback interface. Because AF_PACKET circumvents so much of the networking
-// stack, Linux sees these packets as "martian", i.e. they claim to be to/from
-// localhost but don't have the usual associated data. Thus Linux drops them by
-// default. You can see where this happens by following the code at:
-//
-// - net/ipv4/ip_input.c:ip_rcv_finish, which calls
-// - net/ipv4/route.c:ip_route_input_noref, which calls
-// - net/ipv4/route.c:ip_route_input_slow, which finds and drops martian
-// packets.
-//
-// To tell Linux not to drop these packets, you need to tell it to accept our
-// funny packets (which are completely valid and correct, but lack associated
-// in-kernel data because we use AF_PACKET):
-//
-// echo 1 >> /proc/sys/net/ipv4/conf/lo/accept_local
-// echo 1 >> /proc/sys/net/ipv4/conf/lo/route_localnet
-//
-// These tests require CAP_NET_RAW to run.
-
-// TODO(gvisor.dev/issue/173): gVisor support.
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-constexpr char kMessage[] = "soweoneul malhaebwa";
-constexpr in_port_t kPort = 0x409c; // htons(40000)
-
-// Send kMessage via sock to loopback
-void SendUDPMessage(int sock) {
- struct sockaddr_in dest = {};
- dest.sin_port = kPort;
- dest.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- dest.sin_family = AF_INET;
- EXPECT_THAT(sendto(sock, kMessage, sizeof(kMessage), 0,
- reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)),
- SyscallSucceedsWithValue(sizeof(kMessage)));
-}
-
-//
-// Raw tests. Packets sent with raw AF_PACKET sockets always include link layer
-// headers.
-//
-
-// Tests for "raw" (SOCK_RAW) packet(7) sockets.
-class RawPacketTest : public ::testing::TestWithParam<int> {
- protected:
- // Creates a socket to be used in tests.
- void SetUp() override;
-
- // Closes the socket created by SetUp().
- void TearDown() override;
-
- // Gets the device index of the loopback device.
- int GetLoopbackIndex();
-
- // The socket used for both reading and writing.
- int socket_;
-};
-
-void RawPacketTest::SetUp() {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
- SKIP_IF(IsRunningOnGvisor());
-
- if (!IsRunningOnGvisor()) {
- FileDescriptor acceptLocal = ASSERT_NO_ERRNO_AND_VALUE(
- Open("/proc/sys/net/ipv4/conf/lo/accept_local", O_RDONLY));
- FileDescriptor routeLocalnet = ASSERT_NO_ERRNO_AND_VALUE(
- Open("/proc/sys/net/ipv4/conf/lo/route_localnet", O_RDONLY));
- char enabled;
- ASSERT_THAT(read(acceptLocal.get(), &enabled, 1),
- SyscallSucceedsWithValue(1));
- ASSERT_EQ(enabled, '1');
- ASSERT_THAT(read(routeLocalnet.get(), &enabled, 1),
- SyscallSucceedsWithValue(1));
- ASSERT_EQ(enabled, '1');
- }
-
- ASSERT_THAT(socket_ = socket(AF_PACKET, SOCK_RAW, htons(GetParam())),
- SyscallSucceeds());
-}
-
-void RawPacketTest::TearDown() {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
- SKIP_IF(IsRunningOnGvisor());
-
- EXPECT_THAT(close(socket_), SyscallSucceeds());
-}
-
-int RawPacketTest::GetLoopbackIndex() {
- struct ifreq ifr;
- snprintf(ifr.ifr_name, IFNAMSIZ, "lo");
- EXPECT_THAT(ioctl(socket_, SIOCGIFINDEX, &ifr), SyscallSucceeds());
- EXPECT_NE(ifr.ifr_ifindex, 0);
- return ifr.ifr_ifindex;
-}
-
-// Receive via a packet socket.
-TEST_P(RawPacketTest, Receive) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
- SKIP_IF(IsRunningOnGvisor());
-
- // Let's use a simple IP payload: a UDP datagram.
- FileDescriptor udp_sock =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0));
- SendUDPMessage(udp_sock.get());
-
- // Wait for the socket to become readable.
- struct pollfd pfd = {};
- pfd.fd = socket_;
- pfd.events = POLLIN;
- EXPECT_THAT(RetryEINTR(poll)(&pfd, 1, 2000), SyscallSucceedsWithValue(1));
-
- // Read and verify the data.
- constexpr size_t packet_size = sizeof(struct ethhdr) + sizeof(struct iphdr) +
- sizeof(struct udphdr) + sizeof(kMessage);
- char buf[64];
- struct sockaddr_ll src = {};
- socklen_t src_len = sizeof(src);
- ASSERT_THAT(recvfrom(socket_, buf, sizeof(buf), 0,
- reinterpret_cast<struct sockaddr*>(&src), &src_len),
- SyscallSucceedsWithValue(packet_size));
- // sizeof(src) is the size of a struct sockaddr_ll. sockaddr_ll ends with an 8
- // byte physical address field, but ethernet (MAC) addresses only use 6 bytes.
- // Thus src_len should get modified to be 2 less than the size of sockaddr_ll.
- ASSERT_EQ(src_len, sizeof(src) - 2);
-
- // Verify the source address.
- EXPECT_EQ(src.sll_family, AF_PACKET);
- EXPECT_EQ(src.sll_protocol, htons(ETH_P_IP));
- EXPECT_EQ(src.sll_ifindex, GetLoopbackIndex());
- EXPECT_EQ(src.sll_hatype, ARPHRD_LOOPBACK);
- EXPECT_EQ(src.sll_halen, ETH_ALEN);
- // This came from the loopback device, so the address is all 0s.
- for (int i = 0; i < src.sll_halen; i++) {
- EXPECT_EQ(src.sll_addr[i], 0);
- }
-
- // Verify the ethernet header. We memcpy to deal with pointer alignment.
- struct ethhdr eth = {};
- memcpy(&eth, buf, sizeof(eth));
- // The destination and source address should be 0, for loopback.
- for (int i = 0; i < ETH_ALEN; i++) {
- EXPECT_EQ(eth.h_dest[i], 0);
- EXPECT_EQ(eth.h_source[i], 0);
- }
- EXPECT_EQ(eth.h_proto, htons(ETH_P_IP));
-
- // Verify the IP header. We memcpy to deal with pointer aligment.
- struct iphdr ip = {};
- memcpy(&ip, buf + sizeof(ethhdr), sizeof(ip));
- EXPECT_EQ(ip.ihl, 5);
- EXPECT_EQ(ip.version, 4);
- EXPECT_EQ(ip.tot_len, htons(packet_size - sizeof(eth)));
- EXPECT_EQ(ip.protocol, IPPROTO_UDP);
- EXPECT_EQ(ip.daddr, htonl(INADDR_LOOPBACK));
- EXPECT_EQ(ip.saddr, htonl(INADDR_LOOPBACK));
-
- // Verify the UDP header. We memcpy to deal with pointer aligment.
- struct udphdr udp = {};
- memcpy(&udp, buf + sizeof(eth) + sizeof(iphdr), sizeof(udp));
- EXPECT_EQ(udp.dest, kPort);
- EXPECT_EQ(udp.len, htons(sizeof(udphdr) + sizeof(kMessage)));
-
- // Verify the payload.
- char* payload = reinterpret_cast<char*>(buf + sizeof(eth) + sizeof(iphdr) +
- sizeof(udphdr));
- EXPECT_EQ(strncmp(payload, kMessage, sizeof(kMessage)), 0);
-}
-
-// Send via a packet socket.
-TEST_P(RawPacketTest, Send) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
- SKIP_IF(IsRunningOnGvisor());
-
- // Let's send a UDP packet and receive it using a regular UDP socket.
- FileDescriptor udp_sock =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0));
- struct sockaddr_in bind_addr = {};
- bind_addr.sin_family = AF_INET;
- bind_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- bind_addr.sin_port = kPort;
- ASSERT_THAT(
- bind(udp_sock.get(), reinterpret_cast<struct sockaddr*>(&bind_addr),
- sizeof(bind_addr)),
- SyscallSucceeds());
-
- // Set up the destination physical address.
- struct sockaddr_ll dest = {};
- dest.sll_family = AF_PACKET;
- dest.sll_halen = ETH_ALEN;
- dest.sll_ifindex = GetLoopbackIndex();
- dest.sll_protocol = htons(ETH_P_IP);
- // We're sending to the loopback device, so the address is all 0s.
- memset(dest.sll_addr, 0x00, ETH_ALEN);
-
- // Set up the ethernet header. The kernel takes care of the footer.
- // We're sending to and from hardware address 0 (loopback).
- struct ethhdr eth = {};
- eth.h_proto = htons(ETH_P_IP);
-
- // Set up the IP header.
- struct iphdr iphdr = {};
- iphdr.ihl = 5;
- iphdr.version = 4;
- iphdr.tos = 0;
- iphdr.tot_len =
- htons(sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(kMessage));
- // Get a pseudo-random ID. If we clash with an in-use ID the test will fail,
- // but we have no way of getting an ID we know to be good.
- srand(*reinterpret_cast<unsigned int*>(&iphdr));
- iphdr.id = rand();
- // Linux sets this bit ("do not fragment") for small packets.
- iphdr.frag_off = 1 << 6;
- iphdr.ttl = 64;
- iphdr.protocol = IPPROTO_UDP;
- iphdr.daddr = htonl(INADDR_LOOPBACK);
- iphdr.saddr = htonl(INADDR_LOOPBACK);
- iphdr.check = IPChecksum(iphdr);
-
- // Set up the UDP header.
- struct udphdr udphdr = {};
- udphdr.source = kPort;
- udphdr.dest = kPort;
- udphdr.len = htons(sizeof(udphdr) + sizeof(kMessage));
- udphdr.check = UDPChecksum(iphdr, udphdr, kMessage, sizeof(kMessage));
-
- // Copy both headers and the payload into our packet buffer.
- char
- send_buf[sizeof(eth) + sizeof(iphdr) + sizeof(udphdr) + sizeof(kMessage)];
- memcpy(send_buf, &eth, sizeof(eth));
- memcpy(send_buf + sizeof(ethhdr), &iphdr, sizeof(iphdr));
- memcpy(send_buf + sizeof(ethhdr) + sizeof(iphdr), &udphdr, sizeof(udphdr));
- memcpy(send_buf + sizeof(ethhdr) + sizeof(iphdr) + sizeof(udphdr), kMessage,
- sizeof(kMessage));
-
- // Send it.
- ASSERT_THAT(sendto(socket_, send_buf, sizeof(send_buf), 0,
- reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Wait for the packet to become available on both sockets.
- struct pollfd pfd = {};
- pfd.fd = udp_sock.get();
- pfd.events = POLLIN;
- ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 5000), SyscallSucceedsWithValue(1));
- pfd.fd = socket_;
- pfd.events = POLLIN;
- ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 5000), SyscallSucceedsWithValue(1));
-
- // Receive on the packet socket.
- char recv_buf[sizeof(send_buf)];
- ASSERT_THAT(recv(socket_, recv_buf, sizeof(recv_buf), 0),
- SyscallSucceedsWithValue(sizeof(recv_buf)));
- ASSERT_EQ(memcmp(recv_buf, send_buf, sizeof(send_buf)), 0);
-
- // Receive on the UDP socket.
- struct sockaddr_in src;
- socklen_t src_len = sizeof(src);
- ASSERT_THAT(recvfrom(udp_sock.get(), recv_buf, sizeof(recv_buf), MSG_DONTWAIT,
- reinterpret_cast<struct sockaddr*>(&src), &src_len),
- SyscallSucceedsWithValue(sizeof(kMessage)));
- // Check src and payload.
- EXPECT_EQ(strncmp(recv_buf, kMessage, sizeof(kMessage)), 0);
- EXPECT_EQ(src.sin_family, AF_INET);
- EXPECT_EQ(src.sin_port, kPort);
- EXPECT_EQ(src.sin_addr.s_addr, htonl(INADDR_LOOPBACK));
-}
-
-INSTANTIATE_TEST_SUITE_P(AllInetTests, RawPacketTest,
- ::testing::Values(ETH_P_IP /*, ETH_P_ALL*/));
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/partial_bad_buffer.cc b/test/syscalls/linux/partial_bad_buffer.cc
deleted file mode 100644
index 33822ee57..000000000
--- a/test/syscalls/linux/partial_bad_buffer.cc
+++ /dev/null
@@ -1,415 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <fcntl.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <sys/mman.h>
-#include <sys/socket.h>
-#include <sys/syscall.h>
-#include <sys/uio.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "absl/time/clock.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-using ::testing::Gt;
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-constexpr char kMessage[] = "hello world";
-
-// PartialBadBufferTest checks the result of various IO syscalls when passed a
-// buffer that does not have the space specified in the syscall (most of it is
-// PROT_NONE). Linux is annoyingly inconsistent among different syscalls, so we
-// test all of them.
-class PartialBadBufferTest : public ::testing::Test {
- protected:
- void SetUp() override {
- // Create and open a directory for getdents cases.
- directory_ = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- ASSERT_THAT(
- directory_fd_ = open(directory_.path().c_str(), O_RDONLY | O_DIRECTORY),
- SyscallSucceeds());
-
- // Create and open a normal file, placing it in the directory
- // so the getdents cases have some dirents.
- name_ = JoinPath(directory_.path(), "a");
- ASSERT_THAT(fd_ = open(name_.c_str(), O_RDWR | O_CREAT, 0644),
- SyscallSucceeds());
-
- // Write some initial data.
- size_t size = sizeof(kMessage) - 1;
- EXPECT_THAT(WriteFd(fd_, &kMessage, size), SyscallSucceedsWithValue(size));
-
- ASSERT_THAT(lseek(fd_, 0, SEEK_SET), SyscallSucceeds());
-
- addr_ = mmap(0, 2 * kPageSize, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- ASSERT_NE(addr_, MAP_FAILED);
- char* buf = reinterpret_cast<char*>(addr_);
-
- // Guard page for our read to run into.
- ASSERT_THAT(mprotect(reinterpret_cast<void*>(buf + kPageSize), kPageSize,
- PROT_NONE),
- SyscallSucceeds());
-
- // Leave only one free byte in the buffer.
- bad_buffer_ = buf + kPageSize - 1;
- }
-
- void TearDown() override {
- EXPECT_THAT(munmap(addr_, 2 * kPageSize), SyscallSucceeds()) << addr_;
- EXPECT_THAT(close(fd_), SyscallSucceeds());
- EXPECT_THAT(unlink(name_.c_str()), SyscallSucceeds());
- EXPECT_THAT(close(directory_fd_), SyscallSucceeds());
- }
-
- // Return buffer with n bytes of free space.
- // N.B. this is the same buffer used to back bad_buffer_.
- char* FreeBytes(size_t n) {
- TEST_CHECK(n <= static_cast<size_t>(4096));
- return reinterpret_cast<char*>(addr_) + kPageSize - n;
- }
-
- std::string name_;
- int fd_;
- TempPath directory_;
- int directory_fd_;
- void* addr_;
- char* bad_buffer_;
-};
-
-// We do both "big" and "small" tests to try to hit the "zero copy" and
-// non-"zero copy" paths, which have different code paths for handling faults.
-
-TEST_F(PartialBadBufferTest, ReadBig) {
- EXPECT_THAT(RetryEINTR(read)(fd_, bad_buffer_, kPageSize),
- SyscallSucceedsWithValue(1));
- EXPECT_EQ('h', bad_buffer_[0]);
-}
-
-TEST_F(PartialBadBufferTest, ReadSmall) {
- EXPECT_THAT(RetryEINTR(read)(fd_, bad_buffer_, 10),
- SyscallSucceedsWithValue(1));
- EXPECT_EQ('h', bad_buffer_[0]);
-}
-
-TEST_F(PartialBadBufferTest, PreadBig) {
- EXPECT_THAT(RetryEINTR(pread)(fd_, bad_buffer_, kPageSize, 0),
- SyscallSucceedsWithValue(1));
- EXPECT_EQ('h', bad_buffer_[0]);
-}
-
-TEST_F(PartialBadBufferTest, PreadSmall) {
- EXPECT_THAT(RetryEINTR(pread)(fd_, bad_buffer_, 10, 0),
- SyscallSucceedsWithValue(1));
- EXPECT_EQ('h', bad_buffer_[0]);
-}
-
-TEST_F(PartialBadBufferTest, ReadvBig) {
- struct iovec vec;
- vec.iov_base = bad_buffer_;
- vec.iov_len = kPageSize;
-
- EXPECT_THAT(RetryEINTR(readv)(fd_, &vec, 1), SyscallSucceedsWithValue(1));
- EXPECT_EQ('h', bad_buffer_[0]);
-}
-
-TEST_F(PartialBadBufferTest, ReadvSmall) {
- struct iovec vec;
- vec.iov_base = bad_buffer_;
- vec.iov_len = 10;
-
- EXPECT_THAT(RetryEINTR(readv)(fd_, &vec, 1), SyscallSucceedsWithValue(1));
- EXPECT_EQ('h', bad_buffer_[0]);
-}
-
-TEST_F(PartialBadBufferTest, PreadvBig) {
- struct iovec vec;
- vec.iov_base = bad_buffer_;
- vec.iov_len = kPageSize;
-
- EXPECT_THAT(RetryEINTR(preadv)(fd_, &vec, 1, 0), SyscallSucceedsWithValue(1));
- EXPECT_EQ('h', bad_buffer_[0]);
-}
-
-TEST_F(PartialBadBufferTest, PreadvSmall) {
- struct iovec vec;
- vec.iov_base = bad_buffer_;
- vec.iov_len = 10;
-
- EXPECT_THAT(RetryEINTR(preadv)(fd_, &vec, 1, 0), SyscallSucceedsWithValue(1));
- EXPECT_EQ('h', bad_buffer_[0]);
-}
-
-TEST_F(PartialBadBufferTest, WriteBig) {
- // FIXME(b/24788078): The sentry write syscalls will return immediately
- // if Access returns an error, but Access may not return an error
- // and the sentry will instead perform a partial write.
- SKIP_IF(IsRunningOnGvisor());
-
- EXPECT_THAT(RetryEINTR(write)(fd_, bad_buffer_, kPageSize),
- SyscallFailsWithErrno(EFAULT));
-}
-
-TEST_F(PartialBadBufferTest, WriteSmall) {
- // FIXME(b/24788078): The sentry write syscalls will return immediately
- // if Access returns an error, but Access may not return an error
- // and the sentry will instead perform a partial write.
- SKIP_IF(IsRunningOnGvisor());
-
- EXPECT_THAT(RetryEINTR(write)(fd_, bad_buffer_, 10),
- SyscallFailsWithErrno(EFAULT));
-}
-
-TEST_F(PartialBadBufferTest, PwriteBig) {
- // FIXME(b/24788078): The sentry write syscalls will return immediately
- // if Access returns an error, but Access may not return an error
- // and the sentry will instead perform a partial write.
- SKIP_IF(IsRunningOnGvisor());
-
- EXPECT_THAT(RetryEINTR(pwrite)(fd_, bad_buffer_, kPageSize, 0),
- SyscallFailsWithErrno(EFAULT));
-}
-
-TEST_F(PartialBadBufferTest, PwriteSmall) {
- // FIXME(b/24788078): The sentry write syscalls will return immediately
- // if Access returns an error, but Access may not return an error
- // and the sentry will instead perform a partial write.
- SKIP_IF(IsRunningOnGvisor());
-
- EXPECT_THAT(RetryEINTR(pwrite)(fd_, bad_buffer_, 10, 0),
- SyscallFailsWithErrno(EFAULT));
-}
-
-TEST_F(PartialBadBufferTest, WritevBig) {
- // FIXME(b/24788078): The sentry write syscalls will return immediately
- // if Access returns an error, but Access may not return an error
- // and the sentry will instead perform a partial write.
- SKIP_IF(IsRunningOnGvisor());
-
- struct iovec vec;
- vec.iov_base = bad_buffer_;
- vec.iov_len = kPageSize;
-
- EXPECT_THAT(RetryEINTR(writev)(fd_, &vec, 1), SyscallFailsWithErrno(EFAULT));
-}
-
-TEST_F(PartialBadBufferTest, WritevSmall) {
- // FIXME(b/24788078): The sentry write syscalls will return immediately
- // if Access returns an error, but Access may not return an error
- // and the sentry will instead perform a partial write.
- SKIP_IF(IsRunningOnGvisor());
-
- struct iovec vec;
- vec.iov_base = bad_buffer_;
- vec.iov_len = 10;
-
- EXPECT_THAT(RetryEINTR(writev)(fd_, &vec, 1), SyscallFailsWithErrno(EFAULT));
-}
-
-TEST_F(PartialBadBufferTest, PwritevBig) {
- // FIXME(b/24788078): The sentry write syscalls will return immediately
- // if Access returns an error, but Access may not return an error
- // and the sentry will instead perform a partial write.
- SKIP_IF(IsRunningOnGvisor());
-
- struct iovec vec;
- vec.iov_base = bad_buffer_;
- vec.iov_len = kPageSize;
-
- EXPECT_THAT(RetryEINTR(pwritev)(fd_, &vec, 1, 0),
- SyscallFailsWithErrno(EFAULT));
-}
-
-TEST_F(PartialBadBufferTest, PwritevSmall) {
- // FIXME(b/24788078): The sentry write syscalls will return immediately
- // if Access returns an error, but Access may not return an error
- // and the sentry will instead perform a partial write.
- SKIP_IF(IsRunningOnGvisor());
-
- struct iovec vec;
- vec.iov_base = bad_buffer_;
- vec.iov_len = 10;
-
- EXPECT_THAT(RetryEINTR(pwritev)(fd_, &vec, 1, 0),
- SyscallFailsWithErrno(EFAULT));
-}
-
-// getdents returns EFAULT when the you claim the buffer is large enough, but
-// it actually isn't.
-TEST_F(PartialBadBufferTest, GetdentsBig) {
- EXPECT_THAT(RetryEINTR(syscall)(SYS_getdents64, directory_fd_, bad_buffer_,
- kPageSize),
- SyscallFailsWithErrno(EFAULT));
-}
-
-// getdents returns EINVAL when the you claim the buffer is too small.
-TEST_F(PartialBadBufferTest, GetdentsSmall) {
- EXPECT_THAT(
- RetryEINTR(syscall)(SYS_getdents64, directory_fd_, bad_buffer_, 10),
- SyscallFailsWithErrno(EINVAL));
-}
-
-// getdents will write entries into a buffer if there is space before it faults.
-TEST_F(PartialBadBufferTest, GetdentsOneEntry) {
- // 30 bytes is enough for one (small) entry.
- char* buf = FreeBytes(30);
-
- EXPECT_THAT(
- RetryEINTR(syscall)(SYS_getdents64, directory_fd_, buf, kPageSize),
- SyscallSucceedsWithValue(Gt(0)));
-}
-
-// Verify that when write returns EFAULT the kernel hasn't silently written
-// the initial valid bytes.
-TEST_F(PartialBadBufferTest, WriteEfaultIsntPartial) {
- // FIXME(b/24788078): The sentry write syscalls will return immediately
- // if Access returns an error, but Access may not return an error
- // and the sentry will instead perform a partial write.
- SKIP_IF(IsRunningOnGvisor());
-
- bad_buffer_[0] = 'A';
- EXPECT_THAT(RetryEINTR(write)(fd_, bad_buffer_, 10),
- SyscallFailsWithErrno(EFAULT));
-
- size_t size = 255;
- char buf[255];
- memset(buf, 0, size);
-
- EXPECT_THAT(RetryEINTR(pread)(fd_, buf, size, 0),
- SyscallSucceedsWithValue(sizeof(kMessage) - 1));
-
- // 'A' has not been written.
- EXPECT_STREQ(buf, kMessage);
-}
-
-PosixErrorOr<sockaddr_storage> InetLoopbackAddr(int family) {
- struct sockaddr_storage addr;
- memset(&addr, 0, sizeof(addr));
- addr.ss_family = family;
- switch (family) {
- case AF_INET:
- reinterpret_cast<struct sockaddr_in*>(&addr)->sin_addr.s_addr =
- htonl(INADDR_LOOPBACK);
- break;
- case AF_INET6:
- reinterpret_cast<struct sockaddr_in6*>(&addr)->sin6_addr =
- in6addr_loopback;
- break;
- default:
- return PosixError(EINVAL,
- absl::StrCat("unknown socket family: ", family));
- }
- return addr;
-}
-
-// SendMsgTCP verifies that calling sendmsg with a bad address returns an
-// EFAULT. It also verifies that passing a buffer which is made up of 2
-// pages one valid and one guard page succeeds as long as the write is
-// for exactly the size of 1 page.
-TEST_F(PartialBadBufferTest, SendMsgTCP) {
- auto listen_socket =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
-
- // Initialize address to the loopback one.
- sockaddr_storage addr = ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(AF_INET));
- socklen_t addrlen = sizeof(addr);
-
- // Bind to some port then start listening.
- ASSERT_THAT(bind(listen_socket.get(),
- reinterpret_cast<struct sockaddr*>(&addr), addrlen),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(listen_socket.get(), SOMAXCONN), SyscallSucceeds());
-
- // Get the address we're listening on, then connect to it. We need to do this
- // because we're allowing the stack to pick a port for us.
- ASSERT_THAT(getsockname(listen_socket.get(),
- reinterpret_cast<struct sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
-
- auto send_socket =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
-
- ASSERT_THAT(
- RetryEINTR(connect)(send_socket.get(),
- reinterpret_cast<struct sockaddr*>(&addr), addrlen),
- SyscallSucceeds());
-
- // Accept the connection.
- auto recv_socket =
- ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_socket.get(), nullptr, nullptr));
-
- // TODO(gvisor.dev/issue/674): Update this once Netstack matches linux
- // behaviour on a setsockopt of SO_RCVBUF/SO_SNDBUF.
- //
- // Set SO_SNDBUF for socket to exactly kPageSize+1.
- //
- // gVisor does not double the value passed in SO_SNDBUF like linux does so we
- // just increase it by 1 byte here for gVisor so that we can test writing 1
- // byte past the valid page and check that it triggers an EFAULT
- // correctly. Otherwise in gVisor the sendmsg call will just return with no
- // error with kPageSize bytes written successfully.
- const uint32_t buf_size = kPageSize + 1;
- ASSERT_THAT(setsockopt(send_socket.get(), SOL_SOCKET, SO_SNDBUF, &buf_size,
- sizeof(buf_size)),
- SyscallSucceedsWithValue(0));
-
- struct msghdr hdr = {};
- struct iovec iov = {};
- iov.iov_base = bad_buffer_;
- iov.iov_len = kPageSize;
- hdr.msg_iov = &iov;
- hdr.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(sendmsg)(send_socket.get(), &hdr, 0),
- SyscallFailsWithErrno(EFAULT));
-
- // Now assert that writing kPageSize from addr_ succeeds.
- iov.iov_base = addr_;
- ASSERT_THAT(RetryEINTR(sendmsg)(send_socket.get(), &hdr, 0),
- SyscallSucceedsWithValue(kPageSize));
- // Read all the data out so that we drain the socket SND_BUF on the sender.
- std::vector<char> buffer(kPageSize);
- ASSERT_THAT(RetryEINTR(read)(recv_socket.get(), buffer.data(), kPageSize),
- SyscallSucceedsWithValue(kPageSize));
-
- // Sleep for a shortwhile to ensure that we have time to process the
- // ACKs. This is not strictly required unless running under gotsan which is a
- // lot slower and can result in the next write to write only 1 byte instead of
- // our intended kPageSize + 1.
- absl::SleepFor(absl::Milliseconds(50));
-
- // Now assert that writing > kPageSize results in EFAULT.
- iov.iov_len = kPageSize + 1;
- ASSERT_THAT(RetryEINTR(sendmsg)(send_socket.get(), &hdr, 0),
- SyscallFailsWithErrno(EFAULT));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/pause.cc b/test/syscalls/linux/pause.cc
deleted file mode 100644
index 8c05efd6f..000000000
--- a/test/syscalls/linux/pause.cc
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <signal.h>
-#include <sys/syscall.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <atomic>
-
-#include "gtest/gtest.h"
-#include "absl/synchronization/mutex.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/signal_util.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-void NoopSignalHandler(int sig, siginfo_t* info, void* context) {}
-
-} // namespace
-
-TEST(PauseTest, OnlyReturnsWhenSignalHandled) {
- struct sigaction sa;
- sigfillset(&sa.sa_mask);
-
- // Ensure that SIGUSR1 is ignored.
- sa.sa_handler = SIG_IGN;
- ASSERT_THAT(sigaction(SIGUSR1, &sa, nullptr), SyscallSucceeds());
-
- // Register a handler for SIGUSR2.
- sa.sa_sigaction = NoopSignalHandler;
- sa.sa_flags = SA_SIGINFO;
- ASSERT_THAT(sigaction(SIGUSR2, &sa, nullptr), SyscallSucceeds());
-
- // The child sets their own tid.
- absl::Mutex mu;
- pid_t child_tid = 0;
- bool child_tid_available = false;
- std::atomic<int> sent_signal{0};
- std::atomic<int> waking_signal{0};
- ScopedThread t([&] {
- mu.Lock();
- child_tid = gettid();
- child_tid_available = true;
- mu.Unlock();
- EXPECT_THAT(pause(), SyscallFailsWithErrno(EINTR));
- waking_signal.store(sent_signal.load());
- });
- mu.Lock();
- mu.Await(absl::Condition(&child_tid_available));
- mu.Unlock();
-
- // Wait a bit to let the child enter pause().
- absl::SleepFor(absl::Seconds(3));
-
- // The child should not be woken by SIGUSR1.
- sent_signal.store(SIGUSR1);
- ASSERT_THAT(tgkill(getpid(), child_tid, SIGUSR1), SyscallSucceeds());
- absl::SleepFor(absl::Seconds(3));
-
- // The child should be woken by SIGUSR2.
- sent_signal.store(SIGUSR2);
- ASSERT_THAT(tgkill(getpid(), child_tid, SIGUSR2), SyscallSucceeds());
- absl::SleepFor(absl::Seconds(3));
-
- EXPECT_EQ(SIGUSR2, waking_signal.load());
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/pipe.cc b/test/syscalls/linux/pipe.cc
deleted file mode 100644
index 10e2a6dfc..000000000
--- a/test/syscalls/linux/pipe.cc
+++ /dev/null
@@ -1,650 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h> /* Obtain O_* constant definitions */
-#include <sys/ioctl.h>
-#include <sys/uio.h>
-#include <unistd.h>
-
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "absl/strings/str_cat.h"
-#include "absl/synchronization/notification.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/posix_error.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// Used as a non-zero sentinel value, below.
-constexpr int kTestValue = 0x12345678;
-
-// Used for synchronization in race tests.
-const absl::Duration syncDelay = absl::Seconds(2);
-
-struct PipeCreator {
- std::string name_;
-
- // void (fds, is_blocking, is_namedpipe).
- std::function<void(int[2], bool*, bool*)> create_;
-};
-
-class PipeTest : public ::testing::TestWithParam<PipeCreator> {
- public:
- static void SetUpTestSuite() {
- // Tests intentionally generate SIGPIPE.
- TEST_PCHECK(signal(SIGPIPE, SIG_IGN) != SIG_ERR);
- }
-
- // Initializes rfd_ and wfd_ as a blocking pipe.
- //
- // The return value indicates success: the test should be skipped otherwise.
- bool CreateBlocking() { return create(true); }
-
- // Initializes rfd_ and wfd_ as a non-blocking pipe.
- //
- // The return value is per CreateBlocking.
- bool CreateNonBlocking() { return create(false); }
-
- // Returns true iff the pipe represents a named pipe.
- bool IsNamedPipe() const { return named_pipe_; }
-
- int Size() const {
- int s1 = fcntl(rfd_.get(), F_GETPIPE_SZ);
- int s2 = fcntl(wfd_.get(), F_GETPIPE_SZ);
- EXPECT_GT(s1, 0);
- EXPECT_GT(s2, 0);
- EXPECT_EQ(s1, s2);
- return s1;
- }
-
- static void TearDownTestSuite() {
- TEST_PCHECK(signal(SIGPIPE, SIG_DFL) != SIG_ERR);
- }
-
- private:
- bool create(bool wants_blocking) {
- // Generate the pipe.
- int fds[2] = {-1, -1};
- bool is_blocking = false;
- GetParam().create_(fds, &is_blocking, &named_pipe_);
- if (fds[0] < 0 || fds[1] < 0) {
- return false;
- }
-
- // Save descriptors.
- rfd_.reset(fds[0]);
- wfd_.reset(fds[1]);
-
- // Adjust blocking, if needed.
- if (!is_blocking && wants_blocking) {
- // Clear the blocking flag.
- EXPECT_THAT(fcntl(fds[0], F_SETFL, 0), SyscallSucceeds());
- EXPECT_THAT(fcntl(fds[1], F_SETFL, 0), SyscallSucceeds());
- } else if (is_blocking && !wants_blocking) {
- // Set the descriptors to blocking.
- EXPECT_THAT(fcntl(fds[0], F_SETFL, O_NONBLOCK), SyscallSucceeds());
- EXPECT_THAT(fcntl(fds[1], F_SETFL, O_NONBLOCK), SyscallSucceeds());
- }
-
- return true;
- }
-
- protected:
- FileDescriptor rfd_;
- FileDescriptor wfd_;
-
- private:
- bool named_pipe_ = false;
-};
-
-TEST_P(PipeTest, Inode) {
- SKIP_IF(!CreateBlocking());
-
- // Ensure that the inode number is the same for each end.
- struct stat rst;
- ASSERT_THAT(fstat(rfd_.get(), &rst), SyscallSucceeds());
- struct stat wst;
- ASSERT_THAT(fstat(wfd_.get(), &wst), SyscallSucceeds());
- EXPECT_EQ(rst.st_ino, wst.st_ino);
-}
-
-TEST_P(PipeTest, Permissions) {
- SKIP_IF(!CreateBlocking());
-
- // Attempt bad operations.
- int buf = kTestValue;
- ASSERT_THAT(write(rfd_.get(), &buf, sizeof(buf)),
- SyscallFailsWithErrno(EBADF));
- EXPECT_THAT(read(wfd_.get(), &buf, sizeof(buf)),
- SyscallFailsWithErrno(EBADF));
-}
-
-TEST_P(PipeTest, Flags) {
- SKIP_IF(!CreateBlocking());
-
- if (IsNamedPipe()) {
- // May be stubbed to zero; define locally.
- constexpr int kLargefile = 0100000;
- EXPECT_THAT(fcntl(rfd_.get(), F_GETFL),
- SyscallSucceedsWithValue(kLargefile | O_RDONLY));
- EXPECT_THAT(fcntl(wfd_.get(), F_GETFL),
- SyscallSucceedsWithValue(kLargefile | O_WRONLY));
- } else {
- EXPECT_THAT(fcntl(rfd_.get(), F_GETFL), SyscallSucceedsWithValue(O_RDONLY));
- EXPECT_THAT(fcntl(wfd_.get(), F_GETFL), SyscallSucceedsWithValue(O_WRONLY));
- }
-}
-
-TEST_P(PipeTest, Write) {
- SKIP_IF(!CreateBlocking());
-
- int wbuf = kTestValue;
- int rbuf = ~kTestValue;
- ASSERT_THAT(write(wfd_.get(), &wbuf, sizeof(wbuf)),
- SyscallSucceedsWithValue(sizeof(wbuf)));
- ASSERT_THAT(read(rfd_.get(), &rbuf, sizeof(rbuf)),
- SyscallSucceedsWithValue(sizeof(rbuf)));
- EXPECT_EQ(wbuf, rbuf);
-}
-
-TEST_P(PipeTest, WritePage) {
- SKIP_IF(!CreateBlocking());
-
- std::vector<char> wbuf(kPageSize);
- RandomizeBuffer(wbuf.data(), wbuf.size());
- std::vector<char> rbuf(wbuf.size());
-
- ASSERT_THAT(write(wfd_.get(), wbuf.data(), wbuf.size()),
- SyscallSucceedsWithValue(wbuf.size()));
- ASSERT_THAT(read(rfd_.get(), rbuf.data(), rbuf.size()),
- SyscallSucceedsWithValue(rbuf.size()));
- EXPECT_EQ(memcmp(rbuf.data(), wbuf.data(), wbuf.size()), 0);
-}
-
-TEST_P(PipeTest, NonBlocking) {
- SKIP_IF(!CreateNonBlocking());
-
- int wbuf = kTestValue;
- int rbuf = ~kTestValue;
- EXPECT_THAT(read(rfd_.get(), &rbuf, sizeof(rbuf)),
- SyscallFailsWithErrno(EWOULDBLOCK));
- ASSERT_THAT(write(wfd_.get(), &wbuf, sizeof(wbuf)),
- SyscallSucceedsWithValue(sizeof(wbuf)));
-
- ASSERT_THAT(read(rfd_.get(), &rbuf, sizeof(rbuf)),
- SyscallSucceedsWithValue(sizeof(rbuf)));
- EXPECT_EQ(wbuf, rbuf);
- EXPECT_THAT(read(rfd_.get(), &rbuf, sizeof(rbuf)),
- SyscallFailsWithErrno(EWOULDBLOCK));
-}
-
-TEST(Pipe2Test, CloExec) {
- int fds[2];
- ASSERT_THAT(pipe2(fds, O_CLOEXEC), SyscallSucceeds());
- EXPECT_THAT(fcntl(fds[0], F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC));
- EXPECT_THAT(fcntl(fds[1], F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC));
- EXPECT_THAT(close(fds[0]), SyscallSucceeds());
- EXPECT_THAT(close(fds[1]), SyscallSucceeds());
-}
-
-TEST(Pipe2Test, BadOptions) {
- int fds[2];
- EXPECT_THAT(pipe2(fds, 0xDEAD), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_P(PipeTest, Seek) {
- SKIP_IF(!CreateBlocking());
-
- for (int i = 0; i < 4; i++) {
- // Attempt absolute seeks.
- EXPECT_THAT(lseek(rfd_.get(), 0, SEEK_SET), SyscallFailsWithErrno(ESPIPE));
- EXPECT_THAT(lseek(rfd_.get(), 4, SEEK_SET), SyscallFailsWithErrno(ESPIPE));
- EXPECT_THAT(lseek(wfd_.get(), 0, SEEK_SET), SyscallFailsWithErrno(ESPIPE));
- EXPECT_THAT(lseek(wfd_.get(), 4, SEEK_SET), SyscallFailsWithErrno(ESPIPE));
-
- // Attempt relative seeks.
- EXPECT_THAT(lseek(rfd_.get(), 0, SEEK_CUR), SyscallFailsWithErrno(ESPIPE));
- EXPECT_THAT(lseek(rfd_.get(), 4, SEEK_CUR), SyscallFailsWithErrno(ESPIPE));
- EXPECT_THAT(lseek(wfd_.get(), 0, SEEK_CUR), SyscallFailsWithErrno(ESPIPE));
- EXPECT_THAT(lseek(wfd_.get(), 4, SEEK_CUR), SyscallFailsWithErrno(ESPIPE));
-
- // Attempt end-of-file seeks.
- EXPECT_THAT(lseek(rfd_.get(), 0, SEEK_CUR), SyscallFailsWithErrno(ESPIPE));
- EXPECT_THAT(lseek(rfd_.get(), -4, SEEK_END), SyscallFailsWithErrno(ESPIPE));
- EXPECT_THAT(lseek(wfd_.get(), 0, SEEK_CUR), SyscallFailsWithErrno(ESPIPE));
- EXPECT_THAT(lseek(wfd_.get(), -4, SEEK_END), SyscallFailsWithErrno(ESPIPE));
-
- // Add some more data to the pipe.
- int buf = kTestValue;
- ASSERT_THAT(write(wfd_.get(), &buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
- }
-}
-
-TEST_P(PipeTest, OffsetCalls) {
- SKIP_IF(!CreateBlocking());
-
- int buf;
- EXPECT_THAT(pread(wfd_.get(), &buf, sizeof(buf), 0),
- SyscallFailsWithErrno(ESPIPE));
- EXPECT_THAT(pwrite(rfd_.get(), &buf, sizeof(buf), 0),
- SyscallFailsWithErrno(ESPIPE));
-
- struct iovec iov;
- EXPECT_THAT(preadv(wfd_.get(), &iov, 1, 0), SyscallFailsWithErrno(ESPIPE));
- EXPECT_THAT(pwritev(rfd_.get(), &iov, 1, 0), SyscallFailsWithErrno(ESPIPE));
-}
-
-TEST_P(PipeTest, WriterSideCloses) {
- SKIP_IF(!CreateBlocking());
-
- ScopedThread t([this]() {
- int buf = ~kTestValue;
- ASSERT_THAT(read(rfd_.get(), &buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
- EXPECT_EQ(buf, kTestValue);
- // This will return when the close() completes.
- ASSERT_THAT(read(rfd_.get(), &buf, sizeof(buf)), SyscallSucceeds());
- // This will return straight away.
- ASSERT_THAT(read(rfd_.get(), &buf, sizeof(buf)),
- SyscallSucceedsWithValue(0));
- });
-
- // Sleep a bit so the thread can block.
- absl::SleepFor(syncDelay);
-
- // Write to unblock.
- int buf = kTestValue;
- ASSERT_THAT(write(wfd_.get(), &buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- // Sleep a bit so the thread can block again.
- absl::SleepFor(syncDelay);
-
- // Allow the thread to complete.
- ASSERT_THAT(close(wfd_.release()), SyscallSucceeds());
- t.Join();
-}
-
-TEST_P(PipeTest, WriterSideClosesReadDataFirst) {
- SKIP_IF(!CreateBlocking());
-
- int wbuf = kTestValue;
- ASSERT_THAT(write(wfd_.get(), &wbuf, sizeof(wbuf)),
- SyscallSucceedsWithValue(sizeof(wbuf)));
- ASSERT_THAT(close(wfd_.release()), SyscallSucceeds());
-
- int rbuf;
- ASSERT_THAT(read(rfd_.get(), &rbuf, sizeof(rbuf)),
- SyscallSucceedsWithValue(sizeof(rbuf)));
- EXPECT_EQ(wbuf, rbuf);
- EXPECT_THAT(read(rfd_.get(), &rbuf, sizeof(rbuf)),
- SyscallSucceedsWithValue(0));
-}
-
-TEST_P(PipeTest, ReaderSideCloses) {
- SKIP_IF(!CreateBlocking());
-
- ASSERT_THAT(close(rfd_.release()), SyscallSucceeds());
- int buf = kTestValue;
- EXPECT_THAT(write(wfd_.get(), &buf, sizeof(buf)),
- SyscallFailsWithErrno(EPIPE));
-}
-
-TEST_P(PipeTest, CloseTwice) {
- SKIP_IF(!CreateBlocking());
-
- int reader = rfd_.release();
- int writer = wfd_.release();
- ASSERT_THAT(close(reader), SyscallSucceeds());
- ASSERT_THAT(close(writer), SyscallSucceeds());
- EXPECT_THAT(close(reader), SyscallFailsWithErrno(EBADF));
- EXPECT_THAT(close(writer), SyscallFailsWithErrno(EBADF));
-}
-
-// Blocking write returns EPIPE when read end is closed if nothing has been
-// written.
-TEST_P(PipeTest, BlockWriteClosed) {
- SKIP_IF(!CreateBlocking());
-
- absl::Notification notify;
- ScopedThread t([this, &notify]() {
- std::vector<char> buf(Size());
- // Exactly fill the pipe buffer.
- ASSERT_THAT(WriteFd(wfd_.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(buf.size()));
-
- notify.Notify();
-
- // Attempt to write one more byte. Blocks.
- // N.B. Don't use WriteFd, we don't want a retry.
- EXPECT_THAT(write(wfd_.get(), buf.data(), 1), SyscallFailsWithErrno(EPIPE));
- });
-
- notify.WaitForNotification();
- ASSERT_THAT(close(rfd_.release()), SyscallSucceeds());
- t.Join();
-}
-
-// Blocking write returns EPIPE when read end is closed even if something has
-// been written.
-TEST_P(PipeTest, BlockPartialWriteClosed) {
- SKIP_IF(!CreateBlocking());
-
- ScopedThread t([this]() {
- const int pipe_size = Size();
- std::vector<char> buf(2 * pipe_size);
-
- // Write more than fits in the buffer. Blocks then returns partial write
- // when the other end is closed. The next call returns EPIPE.
- ASSERT_THAT(write(wfd_.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(pipe_size));
- EXPECT_THAT(write(wfd_.get(), buf.data(), buf.size()),
- SyscallFailsWithErrno(EPIPE));
- });
-
- // Leave time for write to become blocked.
- absl::SleepFor(syncDelay);
-
- // Unblock the above.
- ASSERT_THAT(close(rfd_.release()), SyscallSucceeds());
- t.Join();
-}
-
-TEST_P(PipeTest, ReadFromClosedFd_NoRandomSave) {
- SKIP_IF(!CreateBlocking());
-
- absl::Notification notify;
- ScopedThread t([this, &notify]() {
- notify.Notify();
- int buf;
- ASSERT_THAT(read(rfd_.get(), &buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
- ASSERT_EQ(kTestValue, buf);
- });
- notify.WaitForNotification();
-
- // Make sure that the thread gets to read().
- absl::SleepFor(syncDelay);
-
- {
- // We cannot save/restore here as the read end of pipe is closed but there
- // is ongoing read() above. We will not be able to restart the read()
- // successfully in restore run since the read fd is closed.
- const DisableSave ds;
- ASSERT_THAT(close(rfd_.release()), SyscallSucceeds());
- int buf = kTestValue;
- ASSERT_THAT(write(wfd_.get(), &buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
- t.Join();
- }
-}
-
-TEST_P(PipeTest, FionRead) {
- SKIP_IF(!CreateBlocking());
-
- int n;
- ASSERT_THAT(ioctl(rfd_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
- EXPECT_EQ(n, 0);
- ASSERT_THAT(ioctl(wfd_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
- EXPECT_EQ(n, 0);
-
- std::vector<char> buf(Size());
- ASSERT_THAT(write(wfd_.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(buf.size()));
-
- EXPECT_THAT(ioctl(rfd_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
- EXPECT_EQ(n, buf.size());
- EXPECT_THAT(ioctl(wfd_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
- EXPECT_EQ(n, buf.size());
-}
-
-// Test that opening an empty anonymous pipe RDONLY via /proc/self/fd/N does not
-// block waiting for a writer.
-TEST_P(PipeTest, OpenViaProcSelfFD) {
- SKIP_IF(!CreateBlocking());
- SKIP_IF(IsNamedPipe());
-
- // Close the write end of the pipe.
- ASSERT_THAT(close(wfd_.release()), SyscallSucceeds());
-
- // Open other side via /proc/self/fd. It should not block.
- FileDescriptor proc_self_fd = ASSERT_NO_ERRNO_AND_VALUE(
- Open(absl::StrCat("/proc/self/fd/", rfd_.get()), O_RDONLY));
-}
-
-// Test that opening and reading from an anonymous pipe (with existing writes)
-// RDONLY via /proc/self/fd/N returns the existing data.
-TEST_P(PipeTest, OpenViaProcSelfFDWithWrites) {
- SKIP_IF(!CreateBlocking());
- SKIP_IF(IsNamedPipe());
-
- // Write to the pipe and then close the write fd.
- int wbuf = kTestValue;
- ASSERT_THAT(write(wfd_.get(), &wbuf, sizeof(wbuf)),
- SyscallSucceedsWithValue(sizeof(wbuf)));
- ASSERT_THAT(close(wfd_.release()), SyscallSucceeds());
-
- // Open read side via /proc/self/fd, and read from it.
- FileDescriptor proc_self_fd = ASSERT_NO_ERRNO_AND_VALUE(
- Open(absl::StrCat("/proc/self/fd/", rfd_.get()), O_RDONLY));
- int rbuf;
- ASSERT_THAT(read(proc_self_fd.get(), &rbuf, sizeof(rbuf)),
- SyscallSucceedsWithValue(sizeof(rbuf)));
- EXPECT_EQ(wbuf, rbuf);
-}
-
-// Test that accesses of /proc/<PID>/fd correctly decrement the refcount.
-TEST_P(PipeTest, ProcFDReleasesFile) {
- SKIP_IF(!CreateBlocking());
-
- // Stat the pipe FD, which shouldn't alter the refcount.
- struct stat wst;
- ASSERT_THAT(lstat(absl::StrCat("/proc/self/fd/", wfd_.get()).c_str(), &wst),
- SyscallSucceeds());
-
- // Close the write end and ensure that read indicates EOF.
- wfd_.reset();
- char buf;
- ASSERT_THAT(read(rfd_.get(), &buf, 1), SyscallSucceedsWithValue(0));
-}
-
-// Same for /proc/<PID>/fdinfo.
-TEST_P(PipeTest, ProcFDInfoReleasesFile) {
- SKIP_IF(!CreateBlocking());
-
- // Stat the pipe FD, which shouldn't alter the refcount.
- struct stat wst;
- ASSERT_THAT(
- lstat(absl::StrCat("/proc/self/fdinfo/", wfd_.get()).c_str(), &wst),
- SyscallSucceeds());
-
- // Close the write end and ensure that read indicates EOF.
- wfd_.reset();
- char buf;
- ASSERT_THAT(read(rfd_.get(), &buf, 1), SyscallSucceedsWithValue(0));
-}
-
-TEST_P(PipeTest, SizeChange) {
- SKIP_IF(!CreateBlocking());
-
- // Set the minimum possible size.
- ASSERT_THAT(fcntl(rfd_.get(), F_SETPIPE_SZ, 0), SyscallSucceeds());
- int min = Size();
- EXPECT_GT(min, 0); // Should be rounded up.
-
- // Set from the read end.
- ASSERT_THAT(fcntl(rfd_.get(), F_SETPIPE_SZ, min + 1), SyscallSucceeds());
- int med = Size();
- EXPECT_GT(med, min); // Should have grown, may be rounded.
-
- // Set from the write end.
- ASSERT_THAT(fcntl(wfd_.get(), F_SETPIPE_SZ, med + 1), SyscallSucceeds());
- int max = Size();
- EXPECT_GT(max, med); // Ditto.
-}
-
-TEST_P(PipeTest, SizeChangeMax) {
- SKIP_IF(!CreateBlocking());
-
- // Assert there's some maximum.
- EXPECT_THAT(fcntl(rfd_.get(), F_SETPIPE_SZ, 0x7fffffffffffffff),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(fcntl(wfd_.get(), F_SETPIPE_SZ, 0x7fffffffffffffff),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_P(PipeTest, SizeChangeFull) {
- SKIP_IF(!CreateBlocking());
-
- // Ensure that we adjust to a large enough size to avoid rounding when we
- // perform the size decrease. If rounding occurs, we may not actually
- // adjust the size and the call below will return success. It was found via
- // experimentation that this granularity avoids the rounding for Linux.
- constexpr int kDelta = 64 * 1024;
- ASSERT_THAT(fcntl(wfd_.get(), F_SETPIPE_SZ, Size() + kDelta),
- SyscallSucceeds());
-
- // Fill the buffer and try to change down.
- std::vector<char> buf(Size());
- ASSERT_THAT(write(wfd_.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(buf.size()));
- EXPECT_THAT(fcntl(wfd_.get(), F_SETPIPE_SZ, Size() - kDelta),
- SyscallFailsWithErrno(EBUSY));
-}
-
-TEST_P(PipeTest, Streaming) {
- SKIP_IF(!CreateBlocking());
-
- // We make too many calls to go through full save cycles.
- DisableSave ds;
-
- // Size() requires 2 syscalls, call it once and remember the value.
- const int pipe_size = Size();
-
- absl::Notification notify;
- ScopedThread t([this, &notify, pipe_size]() {
- // Don't start until it's full.
- notify.WaitForNotification();
- for (int i = 0; i < pipe_size; i++) {
- int rbuf;
- ASSERT_THAT(read(rfd_.get(), &rbuf, sizeof(rbuf)),
- SyscallSucceedsWithValue(sizeof(rbuf)));
- EXPECT_EQ(rbuf, i);
- }
- });
-
- // Write 4 bytes * pipe_size. It will fill up the pipe once, notify the reader
- // to start. Then we write pipe size worth 3 more times to ensure the reader
- // can follow along.
- ssize_t total = 0;
- for (int i = 0; i < pipe_size; i++) {
- ssize_t written = write(wfd_.get(), &i, sizeof(i));
- ASSERT_THAT(written, SyscallSucceedsWithValue(sizeof(i)));
- total += written;
-
- // Is the next write about to fill up the buffer? Wake up the reader once.
- if (total < pipe_size && (total + written) >= pipe_size) {
- notify.Notify();
- }
- }
-}
-
-std::string PipeCreatorName(::testing::TestParamInfo<PipeCreator> info) {
- return info.param.name_; // Use the name specified.
-}
-
-INSTANTIATE_TEST_SUITE_P(
- Pipes, PipeTest,
- ::testing::Values(
- PipeCreator{
- "pipe",
- [](int fds[2], bool* is_blocking, bool* is_namedpipe) {
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
- *is_blocking = true;
- *is_namedpipe = false;
- },
- },
- PipeCreator{
- "pipe2blocking",
- [](int fds[2], bool* is_blocking, bool* is_namedpipe) {
- ASSERT_THAT(pipe2(fds, 0), SyscallSucceeds());
- *is_blocking = true;
- *is_namedpipe = false;
- },
- },
- PipeCreator{
- "pipe2nonblocking",
- [](int fds[2], bool* is_blocking, bool* is_namedpipe) {
- ASSERT_THAT(pipe2(fds, O_NONBLOCK), SyscallSucceeds());
- *is_blocking = false;
- *is_namedpipe = false;
- },
- },
- PipeCreator{
- "smallbuffer",
- [](int fds[2], bool* is_blocking, bool* is_namedpipe) {
- // Set to the minimum available size (will round up).
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
- ASSERT_THAT(fcntl(fds[0], F_SETPIPE_SZ, 0), SyscallSucceeds());
- *is_blocking = true;
- *is_namedpipe = false;
- },
- },
- PipeCreator{
- "namednonblocking",
- [](int fds[2], bool* is_blocking, bool* is_namedpipe) {
- // Create a new file-based pipe (non-blocking).
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- ASSERT_THAT(unlink(file.path().c_str()), SyscallSucceeds());
- SKIP_IF(mkfifo(file.path().c_str(), 0644) != 0);
- fds[0] = open(file.path().c_str(), O_NONBLOCK | O_RDONLY);
- fds[1] = open(file.path().c_str(), O_NONBLOCK | O_WRONLY);
- MaybeSave();
- *is_blocking = false;
- *is_namedpipe = true;
- },
- },
- PipeCreator{
- "namedblocking",
- [](int fds[2], bool* is_blocking, bool* is_namedpipe) {
- // Create a new file-based pipe (blocking).
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- ASSERT_THAT(unlink(file.path().c_str()), SyscallSucceeds());
- SKIP_IF(mkfifo(file.path().c_str(), 0644) != 0);
- ScopedThread t([&file, &fds]() {
- fds[1] = open(file.path().c_str(), O_WRONLY);
- });
- fds[0] = open(file.path().c_str(), O_RDONLY);
- t.Join();
- MaybeSave();
- *is_blocking = true;
- *is_namedpipe = true;
- },
- }),
- PipeCreatorName);
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/poll.cc b/test/syscalls/linux/poll.cc
deleted file mode 100644
index 9e5aa7fd0..000000000
--- a/test/syscalls/linux/poll.cc
+++ /dev/null
@@ -1,293 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <poll.h>
-#include <sys/resource.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-
-#include <algorithm>
-#include <iostream>
-
-#include "gtest/gtest.h"
-#include "absl/synchronization/notification.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/syscalls/linux/base_poll_test.h"
-#include "test/util/eventfd_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/logging.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-class PollTest : public BasePollTest {
- protected:
- void SetUp() override { BasePollTest::SetUp(); }
- void TearDown() override { BasePollTest::TearDown(); }
-};
-
-TEST_F(PollTest, InvalidFds) {
- // fds is invalid because it's null, but we tell ppoll the length is non-zero.
- EXPECT_THAT(poll(nullptr, 1, 1), SyscallFailsWithErrno(EFAULT));
- EXPECT_THAT(poll(nullptr, -1, 1), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_F(PollTest, NullFds) {
- EXPECT_THAT(poll(nullptr, 0, 10), SyscallSucceeds());
-}
-
-TEST_F(PollTest, ZeroTimeout) {
- EXPECT_THAT(poll(nullptr, 0, 0), SyscallSucceeds());
-}
-
-// If random S/R interrupts the poll, SIGALRM may be delivered before poll
-// restarts, causing the poll to hang forever.
-TEST_F(PollTest, NegativeTimeout_NoRandomSave) {
- // Negative timeout mean wait forever so set a timer.
- SetTimer(absl::Milliseconds(100));
- EXPECT_THAT(poll(nullptr, 0, -1), SyscallFailsWithErrno(EINTR));
- EXPECT_TRUE(TimerFired());
-}
-
-TEST_F(PollTest, NonBlockingEventPOLLIN) {
- // Create a pipe.
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
-
- FileDescriptor fd0(fds[0]);
- FileDescriptor fd1(fds[1]);
-
- // Write some data to the pipe.
- char s[] = "foo\n";
- ASSERT_THAT(WriteFd(fd1.get(), s, strlen(s) + 1), SyscallSucceeds());
-
- // Poll on the reader fd with POLLIN event.
- struct pollfd poll_fd = {fd0.get(), POLLIN, 0};
- EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 0), SyscallSucceedsWithValue(1));
-
- // Should trigger POLLIN event.
- EXPECT_EQ(poll_fd.revents & POLLIN, POLLIN);
-}
-
-TEST_F(PollTest, BlockingEventPOLLIN) {
- // Create a pipe.
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
-
- FileDescriptor fd0(fds[0]);
- FileDescriptor fd1(fds[1]);
-
- // Start a blocking poll on the read fd.
- absl::Notification notify;
- ScopedThread t([&fd0, &notify]() {
- notify.Notify();
-
- // Poll on the reader fd with POLLIN event.
- struct pollfd poll_fd = {fd0.get(), POLLIN, 0};
- EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, -1), SyscallSucceedsWithValue(1));
-
- // Should trigger POLLIN event.
- EXPECT_EQ(poll_fd.revents & POLLIN, POLLIN);
- });
-
- notify.WaitForNotification();
- absl::SleepFor(absl::Seconds(1.0));
-
- // Write some data to the pipe.
- char s[] = "foo\n";
- ASSERT_THAT(WriteFd(fd1.get(), s, strlen(s) + 1), SyscallSucceeds());
-}
-
-TEST_F(PollTest, NonBlockingEventPOLLHUP) {
- // Create a pipe.
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
-
- FileDescriptor fd0(fds[0]);
- FileDescriptor fd1(fds[1]);
-
- // Close the writer fd.
- fd1.reset();
-
- // Poll on the reader fd with POLLIN event.
- struct pollfd poll_fd = {fd0.get(), POLLIN, 0};
- EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 0), SyscallSucceedsWithValue(1));
-
- // Should trigger POLLHUP event.
- EXPECT_EQ(poll_fd.revents & POLLHUP, POLLHUP);
-
- // Should not trigger POLLIN event.
- EXPECT_EQ(poll_fd.revents & POLLIN, 0);
-}
-
-TEST_F(PollTest, BlockingEventPOLLHUP) {
- // Create a pipe.
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
-
- FileDescriptor fd0(fds[0]);
- FileDescriptor fd1(fds[1]);
-
- // Start a blocking poll on the read fd.
- absl::Notification notify;
- ScopedThread t([&fd0, &notify]() {
- notify.Notify();
-
- // Poll on the reader fd with POLLIN event.
- struct pollfd poll_fd = {fd0.get(), POLLIN, 0};
- EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, -1), SyscallSucceedsWithValue(1));
-
- // Should trigger POLLHUP event.
- EXPECT_EQ(poll_fd.revents & POLLHUP, POLLHUP);
-
- // Should not trigger POLLIN event.
- EXPECT_EQ(poll_fd.revents & POLLIN, 0);
- });
-
- notify.WaitForNotification();
- absl::SleepFor(absl::Seconds(1.0));
-
- // Write some data and close the writer fd.
- fd1.reset();
-}
-
-TEST_F(PollTest, NonBlockingEventPOLLERR) {
- // Create a pipe.
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
-
- FileDescriptor fd0(fds[0]);
- FileDescriptor fd1(fds[1]);
-
- // Close the reader fd.
- fd0.reset();
-
- // Poll on the writer fd with POLLOUT event.
- struct pollfd poll_fd = {fd1.get(), POLLOUT, 0};
- EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 0), SyscallSucceedsWithValue(1));
-
- // Should trigger POLLERR event.
- EXPECT_EQ(poll_fd.revents & POLLERR, POLLERR);
-
- // Should also trigger POLLOUT event.
- EXPECT_EQ(poll_fd.revents & POLLOUT, POLLOUT);
-}
-
-// This test will validate that if an FD is already ready on some event, whether
-// it's POLLIN or POLLOUT it will not immediately return unless that's actually
-// what the caller was interested in.
-TEST_F(PollTest, ImmediatelyReturnOnlyOnPollEvents) {
- // Create a pipe.
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
-
- FileDescriptor fd0(fds[0]);
- FileDescriptor fd1(fds[1]);
-
- // Wait for read related event on the write side of the pipe, since a write
- // is possible on fds[1] it would mean that POLLOUT would return immediately.
- // We should make sure that we're not woken up with that state that we didn't
- // specificially request.
- constexpr int kTimeoutMs = 100;
- struct pollfd poll_fd = {fd1.get(), POLLIN | POLLPRI | POLLRDHUP, 0};
- EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, kTimeoutMs),
- SyscallSucceedsWithValue(0)); // We should timeout.
- EXPECT_EQ(poll_fd.revents, 0); // Nothing should be in returned events.
-
- // Now let's poll on POLLOUT and we should get back 1 fd as being ready and
- // it should contain POLLOUT in the revents.
- poll_fd.events = POLLOUT;
- EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, kTimeoutMs),
- SyscallSucceedsWithValue(1)); // 1 fd should have an event.
- EXPECT_EQ(poll_fd.revents, POLLOUT); // POLLOUT should be in revents.
-}
-
-// This test validates that poll(2) while data is available immediately returns.
-TEST_F(PollTest, PollLevelTriggered) {
- int fds[2] = {};
- ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, /*protocol=*/0, fds),
- SyscallSucceeds());
-
- FileDescriptor fd0(fds[0]);
- FileDescriptor fd1(fds[1]);
-
- // Write two bytes to the socket.
- const char* kBuf = "aa";
- ASSERT_THAT(RetryEINTR(send)(fd0.get(), kBuf, /*len=*/2, /*flags=*/0),
- SyscallSucceedsWithValue(2)); // 2 bytes should be written.
-
- // Poll(2) should immediately return as there is data available to read.
- constexpr int kInfiniteTimeout = -1;
- struct pollfd poll_fd = {fd1.get(), POLLIN, 0};
- ASSERT_THAT(RetryEINTR(poll)(&poll_fd, /*nfds=*/1, kInfiniteTimeout),
- SyscallSucceedsWithValue(1)); // 1 fd should be ready to read.
- EXPECT_NE(poll_fd.revents & POLLIN, 0);
-
- // Read a single byte.
- char read_byte = 0;
- ASSERT_THAT(RetryEINTR(recv)(fd1.get(), &read_byte, /*len=*/1, /*flags=*/0),
- SyscallSucceedsWithValue(1)); // 1 byte should be read.
- ASSERT_EQ(read_byte, 'a'); // We should have read a single 'a'.
-
- // Create a separate pollfd for our second poll.
- struct pollfd poll_fd_after = {fd1.get(), POLLIN, 0};
-
- // Poll(2) should again immediately return since we only read one byte.
- ASSERT_THAT(RetryEINTR(poll)(&poll_fd_after, /*nfds=*/1, kInfiniteTimeout),
- SyscallSucceedsWithValue(1)); // 1 fd should be ready to read.
- EXPECT_NE(poll_fd_after.revents & POLLIN, 0);
-}
-
-TEST_F(PollTest, Nfds) {
- // Stash value of RLIMIT_NOFILES.
- struct rlimit rlim;
- TEST_PCHECK(getrlimit(RLIMIT_NOFILE, &rlim) == 0);
-
- // gVisor caps the number of FDs that epoll can use beyond RLIMIT_NOFILE.
- constexpr rlim_t gVisorMax = 1048576;
- if (rlim.rlim_cur > gVisorMax) {
- rlim.rlim_cur = gVisorMax;
- TEST_PCHECK(setrlimit(RLIMIT_NOFILE, &rlim) == 0);
- }
-
- rlim_t max_fds = rlim.rlim_cur;
- std::cout << "Using limit: " << max_fds;
-
- // Create an eventfd. Since its value is initially zero, it is writable.
- FileDescriptor efd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD());
-
- // Create the biggest possible pollfd array such that each element is valid.
- // Each entry in the 'fds' array refers to the eventfd and polls for
- // "writable" events (events=POLLOUT). This essentially guarantees that the
- // poll() is a no-op and allows negative testing of the 'nfds' parameter.
- std::vector<struct pollfd> fds(max_fds, {.fd = efd.get(), .events = POLLOUT});
-
- // Verify that 'nfds' up to RLIMIT_NOFILE are allowed.
- EXPECT_THAT(RetryEINTR(poll)(fds.data(), 1, 1), SyscallSucceedsWithValue(1));
- EXPECT_THAT(RetryEINTR(poll)(fds.data(), max_fds / 2, 1),
- SyscallSucceedsWithValue(max_fds / 2));
- EXPECT_THAT(RetryEINTR(poll)(fds.data(), max_fds, 1),
- SyscallSucceedsWithValue(max_fds));
-
- // If 'nfds' exceeds RLIMIT_NOFILE then it must fail with EINVAL.
- EXPECT_THAT(poll(fds.data(), max_fds + 1, 1), SyscallFailsWithErrno(EINVAL));
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/ppoll.cc b/test/syscalls/linux/ppoll.cc
deleted file mode 100644
index 8245a11e8..000000000
--- a/test/syscalls/linux/ppoll.cc
+++ /dev/null
@@ -1,155 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <poll.h>
-#include <signal.h>
-#include <sys/syscall.h>
-#include <sys/time.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "absl/time/time.h"
-#include "test/syscalls/linux/base_poll_test.h"
-#include "test/util/signal_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-// Linux and glibc have a different idea of the sizeof sigset_t. When calling
-// the syscall directly, use what the kernel expects.
-unsigned kSigsetSize = SIGRTMAX / 8;
-
-// Linux ppoll(2) differs from the glibc wrapper function in that Linux updates
-// the timeout with the amount of time remaining. In order to test this behavior
-// we need to use the syscall directly.
-int syscallPpoll(struct pollfd* fds, nfds_t nfds, struct timespec* timeout_ts,
- const sigset_t* sigmask, unsigned mask_size) {
- return syscall(SYS_ppoll, fds, nfds, timeout_ts, sigmask, mask_size);
-}
-
-class PpollTest : public BasePollTest {
- protected:
- void SetUp() override { BasePollTest::SetUp(); }
- void TearDown() override { BasePollTest::TearDown(); }
-};
-
-TEST_F(PpollTest, InvalidFds) {
- // fds is invalid because it's null, but we tell ppoll the length is non-zero.
- struct timespec timeout = {};
- sigset_t sigmask;
- TEST_PCHECK(sigemptyset(&sigmask) == 0);
- EXPECT_THAT(syscallPpoll(nullptr, 1, &timeout, &sigmask, kSigsetSize),
- SyscallFailsWithErrno(EFAULT));
- EXPECT_THAT(syscallPpoll(nullptr, -1, &timeout, &sigmask, kSigsetSize),
- SyscallFailsWithErrno(EINVAL));
-}
-
-// See that when fds is null, ppoll behaves like sleep.
-TEST_F(PpollTest, NullFds) {
- struct timespec timeout = absl::ToTimespec(absl::Milliseconds(10));
- ASSERT_THAT(syscallPpoll(nullptr, 0, &timeout, nullptr, 0),
- SyscallSucceeds());
- EXPECT_EQ(timeout.tv_sec, 0);
- EXPECT_EQ(timeout.tv_nsec, 0);
-}
-
-TEST_F(PpollTest, ZeroTimeout) {
- struct timespec timeout = {};
- ASSERT_THAT(syscallPpoll(nullptr, 0, &timeout, nullptr, 0),
- SyscallSucceeds());
- EXPECT_EQ(timeout.tv_sec, 0);
- EXPECT_EQ(timeout.tv_nsec, 0);
-}
-
-// If random S/R interrupts the ppoll, SIGALRM may be delivered before ppoll
-// restarts, causing the ppoll to hang forever.
-TEST_F(PpollTest, NoTimeout_NoRandomSave) {
- // When there's no timeout, ppoll may never return so set a timer.
- SetTimer(absl::Milliseconds(100));
- // See that we get interrupted by the timer.
- ASSERT_THAT(syscallPpoll(nullptr, 0, nullptr, nullptr, 0),
- SyscallFailsWithErrno(EINTR));
- EXPECT_TRUE(TimerFired());
-}
-
-TEST_F(PpollTest, InvalidTimeoutNegative) {
- struct timespec timeout = absl::ToTimespec(absl::Nanoseconds(-1));
- EXPECT_THAT(syscallPpoll(nullptr, 0, &timeout, nullptr, 0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_F(PpollTest, InvalidTimeoutNotNormalized) {
- struct timespec timeout = {0, 1000000001};
- EXPECT_THAT(syscallPpoll(nullptr, 0, &timeout, nullptr, 0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_F(PpollTest, InvalidMaskSize) {
- struct timespec timeout = {};
- sigset_t sigmask;
- TEST_PCHECK(sigemptyset(&sigmask) == 0);
- EXPECT_THAT(syscallPpoll(nullptr, 0, &timeout, &sigmask, 128),
- SyscallFailsWithErrno(EINVAL));
-}
-
-// Verify that signals blocked by the ppoll mask (that would otherwise be
-// allowed) do not interrupt ppoll.
-TEST_F(PpollTest, SignalMaskBlocksSignal) {
- absl::Duration duration(absl::Seconds(30));
- struct timespec timeout = absl::ToTimespec(duration);
- absl::Duration timer_duration(absl::Seconds(10));
-
- // Call with a mask that blocks SIGALRM. See that ppoll is not interrupted
- // (i.e. returns 0) and that upon completion, the timer has fired.
- sigset_t mask;
- ASSERT_THAT(sigprocmask(0, nullptr, &mask), SyscallSucceeds());
- TEST_PCHECK(sigaddset(&mask, SIGALRM) == 0);
- SetTimer(timer_duration);
- MaybeSave();
- ASSERT_FALSE(TimerFired());
- ASSERT_THAT(syscallPpoll(nullptr, 0, &timeout, &mask, kSigsetSize),
- SyscallSucceeds());
- EXPECT_TRUE(TimerFired());
- EXPECT_EQ(absl::DurationFromTimespec(timeout), absl::Duration());
-}
-
-// Verify that signals allowed by the ppoll mask (that would otherwise be
-// blocked) interrupt ppoll.
-TEST_F(PpollTest, SignalMaskAllowsSignal) {
- absl::Duration duration(absl::Seconds(30));
- struct timespec timeout = absl::ToTimespec(duration);
- absl::Duration timer_duration(absl::Seconds(10));
-
- sigset_t mask;
- ASSERT_THAT(sigprocmask(0, nullptr, &mask), SyscallSucceeds());
-
- // Block SIGALRM.
- auto cleanup =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_BLOCK, SIGALRM));
-
- // Call with a mask that unblocks SIGALRM. See that ppoll is interrupted.
- SetTimer(timer_duration);
- MaybeSave();
- ASSERT_FALSE(TimerFired());
- ASSERT_THAT(syscallPpoll(nullptr, 0, &timeout, &mask, kSigsetSize),
- SyscallFailsWithErrno(EINTR));
- EXPECT_TRUE(TimerFired());
- EXPECT_GT(absl::DurationFromTimespec(timeout), absl::Duration());
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/prctl.cc b/test/syscalls/linux/prctl.cc
deleted file mode 100644
index bd1779557..000000000
--- a/test/syscalls/linux/prctl.cc
+++ /dev/null
@@ -1,229 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sys/prctl.h>
-#include <sys/ptrace.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include <string>
-
-#include "gtest/gtest.h"
-#include "test/util/capability_util.h"
-#include "test/util/cleanup.h"
-#include "test/util/multiprocess_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-DEFINE_bool(prctl_no_new_privs_test_child, false,
- "If true, exit with the return value of prctl(PR_GET_NO_NEW_PRIVS) "
- "plus an offset (see test source).");
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-#ifndef SUID_DUMP_DISABLE
-#define SUID_DUMP_DISABLE 0
-#endif /* SUID_DUMP_DISABLE */
-#ifndef SUID_DUMP_USER
-#define SUID_DUMP_USER 1
-#endif /* SUID_DUMP_USER */
-#ifndef SUID_DUMP_ROOT
-#define SUID_DUMP_ROOT 2
-#endif /* SUID_DUMP_ROOT */
-
-TEST(PrctlTest, NameInitialized) {
- const size_t name_length = 20;
- char name[name_length] = {};
- ASSERT_THAT(prctl(PR_GET_NAME, name), SyscallSucceeds());
- ASSERT_NE(std::string(name), "");
-}
-
-TEST(PrctlTest, SetNameLongName) {
- const size_t name_length = 20;
- const std::string long_name(name_length, 'A');
- ASSERT_THAT(prctl(PR_SET_NAME, long_name.c_str()), SyscallSucceeds());
- char truncated_name[name_length] = {};
- ASSERT_THAT(prctl(PR_GET_NAME, truncated_name), SyscallSucceeds());
- const size_t truncated_length = 15;
- ASSERT_EQ(long_name.substr(0, truncated_length), std::string(truncated_name));
-}
-
-TEST(PrctlTest, ChildProcessName) {
- constexpr size_t kMaxNameLength = 15;
-
- char parent_name[kMaxNameLength + 1] = {};
- memset(parent_name, 'a', kMaxNameLength);
-
- ASSERT_THAT(prctl(PR_SET_NAME, parent_name), SyscallSucceeds());
-
- pid_t child_pid = fork();
- TEST_PCHECK(child_pid >= 0);
- if (child_pid == 0) {
- char child_name[kMaxNameLength + 1] = {};
- TEST_PCHECK(prctl(PR_GET_NAME, child_name) >= 0);
- TEST_CHECK(memcmp(parent_name, child_name, sizeof(parent_name)) == 0);
- _exit(0);
- }
-
- int status;
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "status =" << status;
-}
-
-// Offset added to exit code from test child to distinguish from other abnormal
-// exits.
-constexpr int kPrctlNoNewPrivsTestChildExitBase = 100;
-
-TEST(PrctlTest, NoNewPrivsPreservedAcrossCloneForkAndExecve) {
- // Check if no_new_privs is already set. If it is, we can still test that it's
- // preserved across clone/fork/execve, but we also expect it to still be set
- // at the end of the test. Otherwise, call prctl(PR_SET_NO_NEW_PRIVS) so as
- // not to contaminate the original thread.
- int no_new_privs;
- ASSERT_THAT(no_new_privs = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0),
- SyscallSucceeds());
- ScopedThread([] {
- ASSERT_THAT(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0), SyscallSucceeds());
- EXPECT_THAT(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0),
- SyscallSucceedsWithValue(1));
- ScopedThread([] {
- EXPECT_THAT(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0),
- SyscallSucceedsWithValue(1));
- // Note that these ASSERT_*s failing will only return from this thread,
- // but this is the intended behavior.
- pid_t child_pid = -1;
- int execve_errno = 0;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec("/proc/self/exe",
- {"/proc/self/exe", "--prctl_no_new_privs_test_child"}, {},
- nullptr, &child_pid, &execve_errno));
-
- ASSERT_GT(child_pid, 0);
- ASSERT_EQ(execve_errno, 0);
-
- int status = 0;
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
- SyscallSucceeds());
- ASSERT_TRUE(WIFEXITED(status));
- ASSERT_EQ(WEXITSTATUS(status), kPrctlNoNewPrivsTestChildExitBase + 1);
-
- EXPECT_THAT(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0),
- SyscallSucceedsWithValue(1));
- });
- EXPECT_THAT(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0),
- SyscallSucceedsWithValue(1));
- });
- EXPECT_THAT(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0),
- SyscallSucceedsWithValue(no_new_privs));
-}
-
-TEST(PrctlTest, PDeathSig) {
- pid_t child_pid;
-
- // Make the new process' parent a separate thread since the parent death
- // signal fires when the parent *thread* exits.
- ScopedThread([&] {
- child_pid = fork();
- TEST_CHECK(child_pid >= 0);
- if (child_pid == 0) {
- // In child process.
- TEST_CHECK(prctl(PR_SET_PDEATHSIG, SIGKILL) >= 0);
- int signo;
- TEST_CHECK(prctl(PR_GET_PDEATHSIG, &signo) >= 0);
- TEST_CHECK(signo == SIGKILL);
- // Enable tracing, then raise SIGSTOP and expect our parent to suppress
- // it.
- TEST_CHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) >= 0);
- raise(SIGSTOP);
- // Sleep until killed by our parent death signal. sleep(3) is
- // async-signal-safe, absl::SleepFor isn't.
- while (true) {
- sleep(10);
- }
- }
- // In parent process.
-
- // Wait for the child to send itself SIGSTOP and enter signal-delivery-stop.
- int status;
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
- << "status = " << status;
-
- // Suppress the SIGSTOP and detach from the child.
- ASSERT_THAT(ptrace(PTRACE_DETACH, child_pid, 0, 0), SyscallSucceeds());
- });
-
- // The child should have been killed by its parent death SIGKILL.
- int status;
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL)
- << "status = " << status;
-}
-
-// This test is to validate that calling prctl with PR_SET_MM without the
-// CAP_SYS_RESOURCE returns EPERM.
-TEST(PrctlTest, InvalidPrSetMM) {
- if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_RESOURCE))) {
- ASSERT_NO_ERRNO(SetCapability(CAP_SYS_RESOURCE,
- false)); // Drop capability to test below.
- }
- ASSERT_THAT(prctl(PR_SET_MM, 0, 0, 0, 0), SyscallFailsWithErrno(EPERM));
-}
-
-// Sanity check that dumpability is remembered.
-TEST(PrctlTest, SetGetDumpability) {
- int before;
- ASSERT_THAT(before = prctl(PR_GET_DUMPABLE), SyscallSucceeds());
- auto cleanup = Cleanup([before] {
- ASSERT_THAT(prctl(PR_SET_DUMPABLE, before), SyscallSucceeds());
- });
-
- EXPECT_THAT(prctl(PR_SET_DUMPABLE, SUID_DUMP_DISABLE), SyscallSucceeds());
- EXPECT_THAT(prctl(PR_GET_DUMPABLE),
- SyscallSucceedsWithValue(SUID_DUMP_DISABLE));
-
- EXPECT_THAT(prctl(PR_SET_DUMPABLE, SUID_DUMP_USER), SyscallSucceeds());
- EXPECT_THAT(prctl(PR_GET_DUMPABLE), SyscallSucceedsWithValue(SUID_DUMP_USER));
-}
-
-// SUID_DUMP_ROOT cannot be set via PR_SET_DUMPABLE.
-TEST(PrctlTest, RootDumpability) {
- EXPECT_THAT(prctl(PR_SET_DUMPABLE, SUID_DUMP_ROOT),
- SyscallFailsWithErrno(EINVAL));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
-
-int main(int argc, char** argv) {
- gvisor::testing::TestInit(&argc, &argv);
-
- if (FLAGS_prctl_no_new_privs_test_child) {
- exit(gvisor::testing::kPrctlNoNewPrivsTestChildExitBase +
- prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0));
- }
-
- return RUN_ALL_TESTS();
-}
diff --git a/test/syscalls/linux/prctl_setuid.cc b/test/syscalls/linux/prctl_setuid.cc
deleted file mode 100644
index 00dd6523e..000000000
--- a/test/syscalls/linux/prctl_setuid.cc
+++ /dev/null
@@ -1,262 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sched.h>
-#include <sys/prctl.h>
-#include <string>
-
-#include "gtest/gtest.h"
-#include "test/util/capability_util.h"
-#include "test/util/logging.h"
-#include "test/util/multiprocess_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-DEFINE_int32(scratch_uid, 65534, "scratch UID");
-// This flag is used to verify that after an exec PR_GET_KEEPCAPS
-// returns 0, the return code will be offset by kPrGetKeepCapsExitBase.
-DEFINE_bool(prctl_pr_get_keepcaps, false,
- "If true the test will verify that prctl with pr_get_keepcaps"
- "returns 0. The test will exit with the result of that check.");
-
-// These tests exist seperately from prctl because we need to start
-// them as root. Setuid() has the behavior that permissions are fully
-// removed if one of the UIDs were 0 before a setuid() call. This
-// behavior can be changed by using PR_SET_KEEPCAPS and that is what
-// is tested here.
-//
-// Reference setuid(2):
-// The setuid() function checks the effective user ID of
-// the caller and if it is the superuser, all process-related user ID's
-// are set to uid. After this has occurred, it is impossible for the
-// program to regain root privileges.
-//
-// Thus, a set-user-ID-root program wishing to temporarily drop root
-// privileges, assume the identity of an unprivileged user, and then
-// regain root privileges afterward cannot use setuid(). You can
-// accomplish this with seteuid(2).
-namespace gvisor {
-namespace testing {
-
-// Offset added to exit code from test child to distinguish from other abnormal
-// exits.
-constexpr int kPrGetKeepCapsExitBase = 100;
-
-namespace {
-
-class PrctlKeepCapsSetuidTest : public ::testing::Test {
- protected:
- void SetUp() override {
- // PR_GET_KEEPCAPS will only return 0 or 1 (on success).
- ASSERT_THAT(original_keepcaps_ = prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0),
- SyscallSucceeds());
- ASSERT_TRUE(original_keepcaps_ == 0 || original_keepcaps_ == 1);
- }
-
- void TearDown() override {
- // Restore PR_SET_KEEPCAPS.
- ASSERT_THAT(prctl(PR_SET_KEEPCAPS, original_keepcaps_, 0, 0, 0),
- SyscallSucceeds());
-
- // Verify that it was restored.
- ASSERT_THAT(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0),
- SyscallSucceedsWithValue(original_keepcaps_));
- }
-
- // The original keep caps value exposed so tests can use it if they need.
- int original_keepcaps_ = 0;
-};
-
-// This test will verify that a bad value, eg. not 0 or 1 for
-// PR_SET_KEEPCAPS will return EINVAL as required by prctl(2).
-TEST_F(PrctlKeepCapsSetuidTest, PrctlBadArgsToKeepCaps) {
- ASSERT_THAT(prctl(PR_SET_KEEPCAPS, 2, 0, 0, 0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-// This test will verify that a setuid(2) without PR_SET_KEEPCAPS will cause
-// all capabilities to be dropped.
-TEST_F(PrctlKeepCapsSetuidTest, SetUidNoKeepCaps) {
- // getuid(2) never fails.
- if (getuid() != 0) {
- SKIP_IF(!IsRunningOnGvisor());
- FAIL() << "User is not root on gvisor platform.";
- }
-
- // Do setuid in a separate thread so that after finishing this test, the
- // process can still open files the test harness created before starting
- // this test. Otherwise, the files are created by root (UID before the
- // test), but cannot be opened by the `uid` set below after the test. After
- // calling setuid(non-zero-UID), there is no way to get root privileges
- // back.
- ScopedThread([] {
- // Start by verifying we have a capability.
- TEST_CHECK(HaveCapability(CAP_SYS_ADMIN).ValueOrDie());
-
- // Verify that PR_GET_KEEPCAPS is disabled.
- ASSERT_THAT(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0),
- SyscallSucceedsWithValue(0));
-
- // Use syscall instead of glibc setuid wrapper because we want this setuid
- // call to only apply to this task. POSIX threads, however, require that
- // all threads have the same UIDs, so using the setuid wrapper sets all
- // threads' real UID.
- EXPECT_THAT(syscall(SYS_setuid, FLAGS_scratch_uid), SyscallSucceeds());
-
- // Verify that we changed uid.
- EXPECT_THAT(getuid(), SyscallSucceedsWithValue(FLAGS_scratch_uid));
-
- // Verify we lost the capability in the effective set, this always happens.
- TEST_CHECK(!HaveCapability(CAP_SYS_ADMIN).ValueOrDie());
-
- // We should have also lost it in the permitted set by the setuid() so
- // SetCapability should fail when we try to add it back to the effective set
- ASSERT_FALSE(SetCapability(CAP_SYS_ADMIN, true).ok());
- });
-}
-
-// This test will verify that a setuid with PR_SET_KEEPCAPS will cause
-// capabilities to be retained after we switch away from the root user.
-TEST_F(PrctlKeepCapsSetuidTest, SetUidKeepCaps) {
- // getuid(2) never fails.
- if (getuid() != 0) {
- SKIP_IF(!IsRunningOnGvisor());
- FAIL() << "User is not root on gvisor platform.";
- }
-
- // Do setuid in a separate thread so that after finishing this test, the
- // process can still open files the test harness created before starting
- // this test. Otherwise, the files are created by root (UID before the
- // test), but cannot be opened by the `uid` set below after the test. After
- // calling setuid(non-zero-UID), there is no way to get root privileges
- // back.
- ScopedThread([] {
- // Start by verifying we have a capability.
- TEST_CHECK(HaveCapability(CAP_SYS_ADMIN).ValueOrDie());
-
- // Set PR_SET_KEEPCAPS.
- ASSERT_THAT(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0), SyscallSucceeds());
-
- // Verify PR_SET_KEEPCAPS was set before we proceed.
- ASSERT_THAT(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0),
- SyscallSucceedsWithValue(1));
-
- // Use syscall instead of glibc setuid wrapper because we want this setuid
- // call to only apply to this task. POSIX threads, however, require that
- // all threads have the same UIDs, so using the setuid wrapper sets all
- // threads' real UID.
- EXPECT_THAT(syscall(SYS_setuid, FLAGS_scratch_uid), SyscallSucceeds());
-
- // Verify that we changed uid.
- EXPECT_THAT(getuid(), SyscallSucceedsWithValue(FLAGS_scratch_uid));
-
- // Verify we lost the capability in the effective set, this always happens.
- TEST_CHECK(!HaveCapability(CAP_SYS_ADMIN).ValueOrDie());
-
- // We lost the capability in the effective set, but it will still
- // exist in the permitted set so we can elevate the capability.
- ASSERT_NO_ERRNO(SetCapability(CAP_SYS_ADMIN, true));
-
- // Verify we got back the capability in the effective set.
- TEST_CHECK(HaveCapability(CAP_SYS_ADMIN).ValueOrDie());
- });
-}
-
-// This test will verify that PR_SET_KEEPCAPS is not retained
-// across an execve. According to prctl(2):
-// "The "keep capabilities" value will be reset to 0 on subsequent
-// calls to execve(2)."
-TEST_F(PrctlKeepCapsSetuidTest, NoKeepCapsAfterExec) {
- ASSERT_THAT(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0), SyscallSucceeds());
-
- // Verify PR_SET_KEEPCAPS was set before we proceed.
- ASSERT_THAT(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0), SyscallSucceedsWithValue(1));
-
- pid_t child_pid = -1;
- int execve_errno = 0;
- // Do an exec and then verify that PR_GET_KEEPCAPS returns 0
- // see the body of main below.
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec(
- "/proc/self/exe", {"/proc/self/exe", "--prctl_pr_get_keepcaps"}, {},
- nullptr, &child_pid, &execve_errno));
-
- ASSERT_GT(child_pid, 0);
- ASSERT_EQ(execve_errno, 0);
-
- int status = 0;
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
- ASSERT_TRUE(WIFEXITED(status));
- // PR_SET_KEEPCAPS should have been cleared by the exec.
- // Success should return gvisor::testing::kPrGetKeepCapsExitBase + 0
- ASSERT_EQ(WEXITSTATUS(status), kPrGetKeepCapsExitBase);
-}
-
-TEST_F(PrctlKeepCapsSetuidTest, NoKeepCapsAfterNewUserNamespace) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace()));
-
- // Fork to avoid changing the user namespace of the original test process.
- pid_t const child_pid = fork();
-
- if (child_pid == 0) {
- // Verify that the keepcaps flag is set to 0 when we change user namespaces.
- TEST_PCHECK(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == 0);
- MaybeSave();
-
- TEST_PCHECK(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0) == 1);
- MaybeSave();
-
- TEST_PCHECK(unshare(CLONE_NEWUSER) == 0);
- MaybeSave();
-
- TEST_PCHECK(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0) == 0);
- MaybeSave();
-
- _exit(0);
- }
-
- int status;
- ASSERT_THAT(child_pid, SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "status = " << status;
-}
-
-// This test will verify that PR_SET_KEEPCAPS and PR_GET_KEEPCAPS work correctly
-TEST_F(PrctlKeepCapsSetuidTest, PrGetKeepCaps) {
- // Set PR_SET_KEEPCAPS to the negation of the original.
- ASSERT_THAT(prctl(PR_SET_KEEPCAPS, !original_keepcaps_, 0, 0, 0),
- SyscallSucceeds());
-
- // Verify it was set.
- ASSERT_THAT(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0),
- SyscallSucceedsWithValue(!original_keepcaps_));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
-
-int main(int argc, char** argv) {
- gvisor::testing::TestInit(&argc, &argv);
-
- if (FLAGS_prctl_pr_get_keepcaps) {
- return gvisor::testing::kPrGetKeepCapsExitBase +
- prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0);
- }
-
- return RUN_ALL_TESTS();
-}
diff --git a/test/syscalls/linux/pread64.cc b/test/syscalls/linux/pread64.cc
deleted file mode 100644
index 5e3eb1735..000000000
--- a/test/syscalls/linux/pread64.cc
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/mman.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-class Pread64Test : public ::testing::Test {
- void SetUp() override {
- name_ = NewTempAbsPath();
- ASSERT_NO_ERRNO_AND_VALUE(Open(name_, O_CREAT, 0644));
- }
-
- void TearDown() override { unlink(name_.c_str()); }
-
- public:
- std::string name_;
-};
-
-TEST(Pread64TestNoTempFile, BadFileDescriptor) {
- char buf[1024];
- EXPECT_THAT(pread64(-1, buf, 1024, 0), SyscallFailsWithErrno(EBADF));
-}
-
-TEST_F(Pread64Test, ZeroBuffer) {
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(name_, O_RDWR));
-
- char msg[] = "hello world";
- EXPECT_THAT(pwrite64(fd.get(), msg, strlen(msg), 0),
- SyscallSucceedsWithValue(strlen(msg)));
-
- char buf[10];
- EXPECT_THAT(pread64(fd.get(), buf, 0, 0), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(Pread64Test, BadBuffer) {
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(name_, O_RDWR));
-
- char msg[] = "hello world";
- EXPECT_THAT(pwrite64(fd.get(), msg, strlen(msg), 0),
- SyscallSucceedsWithValue(strlen(msg)));
-
- char* bad_buffer = nullptr;
- EXPECT_THAT(pread64(fd.get(), bad_buffer, 1024, 0),
- SyscallFailsWithErrno(EFAULT));
-}
-
-TEST_F(Pread64Test, WriteOnlyNotReadable) {
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(name_, O_WRONLY));
-
- char buf[1024];
- EXPECT_THAT(pread64(fd.get(), buf, 1024, 0), SyscallFailsWithErrno(EBADF));
-}
-
-TEST_F(Pread64Test, DirNotReadable) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(GetAbsoluteTestTmpdir(), O_RDONLY));
-
- char buf[1024];
- EXPECT_THAT(pread64(fd.get(), buf, 1024, 0), SyscallFailsWithErrno(EISDIR));
-}
-
-TEST_F(Pread64Test, BadOffset) {
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(name_, O_RDONLY));
-
- char buf[1024];
- EXPECT_THAT(pread64(fd.get(), buf, 1024, -1), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_F(Pread64Test, OffsetNotIncremented) {
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(name_, O_RDWR));
-
- char msg[] = "hello world";
- EXPECT_THAT(write(fd.get(), msg, strlen(msg)),
- SyscallSucceedsWithValue(strlen(msg)));
- int offset;
- EXPECT_THAT(offset = lseek(fd.get(), 0, SEEK_CUR), SyscallSucceeds());
-
- char buf1[1024];
- EXPECT_THAT(pread64(fd.get(), buf1, 1024, 0),
- SyscallSucceedsWithValue(strlen(msg)));
- EXPECT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(offset));
-
- char buf2[1024];
- EXPECT_THAT(pread64(fd.get(), buf2, 1024, 3),
- SyscallSucceedsWithValue(strlen(msg) - 3));
- EXPECT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(offset));
-}
-
-TEST_F(Pread64Test, EndOfFile) {
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(name_, O_RDONLY));
-
- char buf[1024];
- EXPECT_THAT(pread64(fd.get(), buf, 1024, 0), SyscallSucceedsWithValue(0));
-}
-
-TEST(Pread64TestNoTempFile, CantReadSocketPair_NoRandomSave) {
- int sock_fds[2];
- EXPECT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fds), SyscallSucceeds());
-
- char buf[1024];
- EXPECT_THAT(pread64(sock_fds[0], buf, 1024, 0),
- SyscallFailsWithErrno(ESPIPE));
- EXPECT_THAT(pread64(sock_fds[1], buf, 1024, 0),
- SyscallFailsWithErrno(ESPIPE));
-
- EXPECT_THAT(close(sock_fds[0]), SyscallSucceeds());
- EXPECT_THAT(close(sock_fds[1]), SyscallSucceeds());
-}
-
-TEST(Pread64TestNoTempFile, CantReadPipe) {
- char buf[1024];
-
- int pipe_fds[2];
- EXPECT_THAT(pipe(pipe_fds), SyscallSucceeds());
-
- EXPECT_THAT(pread64(pipe_fds[0], buf, 1024, 0),
- SyscallFailsWithErrno(ESPIPE));
-
- EXPECT_THAT(close(pipe_fds[0]), SyscallSucceeds());
- EXPECT_THAT(close(pipe_fds[1]), SyscallSucceeds());
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/preadv.cc b/test/syscalls/linux/preadv.cc
deleted file mode 100644
index eebd129f2..000000000
--- a/test/syscalls/linux/preadv.cc
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sys/syscall.h>
-#include <sys/types.h>
-#include <sys/uio.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include <atomic>
-#include <string>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/logging.h"
-#include "test/util/memory_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-#include "test/util/timer_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(PreadvTest, MMConcurrencyStress) {
- // Fill a one-page file with zeroes (the contents don't really matter).
- const auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- /* parent = */ GetAbsoluteTestTmpdir(),
- /* content = */ std::string(kPageSize, 0), TempPath::kDefaultFileMode));
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDONLY));
-
- // Get a one-page private mapping to read to.
- const Mapping m = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
-
- // Repeatedly fork in a separate thread to force the mapping to become
- // copy-on-write.
- std::atomic<bool> done(false);
- const ScopedThread t([&] {
- while (!done.load()) {
- const pid_t pid = fork();
- TEST_CHECK(pid >= 0);
- if (pid == 0) {
- // In child. The parent was obviously multithreaded, so it's neither
- // safe nor necessary to do much more than exit.
- syscall(SYS_exit_group, 0);
- }
- int status;
- ASSERT_THAT(RetryEINTR(waitpid)(pid, &status, 0),
- SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "status = " << status;
- }
- });
-
- // Repeatedly read to the mapping.
- struct iovec iov[2];
- iov[0].iov_base = m.ptr();
- iov[0].iov_len = kPageSize / 2;
- iov[1].iov_base = reinterpret_cast<void*>(m.addr() + kPageSize / 2);
- iov[1].iov_len = kPageSize / 2;
- constexpr absl::Duration kTestDuration = absl::Seconds(5);
- const absl::Time end = absl::Now() + kTestDuration;
- while (absl::Now() < end) {
- // Among other causes, save/restore cycles may cause interruptions resulting
- // in partial reads, so we don't expect any particular return value.
- EXPECT_THAT(RetryEINTR(preadv)(fd.get(), iov, 2, 0), SyscallSucceeds());
- }
-
- // Stop the other thread.
- done.store(true);
-
- // The test passes if it neither deadlocks nor crashes the OS.
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/preadv2.cc b/test/syscalls/linux/preadv2.cc
deleted file mode 100644
index aac960130..000000000
--- a/test/syscalls/linux/preadv2.cc
+++ /dev/null
@@ -1,279 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <sys/syscall.h>
-#include <sys/types.h>
-#include <sys/uio.h>
-
-#include <string>
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "absl/memory/memory.h"
-#include "test/syscalls/linux/file_base.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-#ifndef SYS_preadv2
-#if defined(__x86_64__)
-#define SYS_preadv2 327
-#else
-#error "Unknown architecture"
-#endif
-#endif // SYS_preadv2
-
-#ifndef RWF_HIPRI
-#define RWF_HIPRI 0x1
-#endif // RWF_HIPRI
-
-constexpr int kBufSize = 1024;
-
-std::string SetContent() {
- std::string content;
- for (int i = 0; i < kBufSize; i++) {
- content += static_cast<char>((i % 10) + '0');
- }
- return content;
-}
-
-ssize_t preadv2(unsigned long fd, const struct iovec* iov, unsigned long iovcnt,
- off_t offset, unsigned long flags) {
- // syscall on preadv2 does some weird things (see man syscall and search
- // preadv2), so we insert a 0 to word align the flags argument on native.
- return syscall(SYS_preadv2, fd, iov, iovcnt, offset, 0, flags);
-}
-
-// This test is the base case where we call preadv (no offset, no flags).
-TEST(Preadv2Test, TestBaseCall) {
- SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
-
- std::string content = SetContent();
-
- const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), content, TempPath::kDefaultFileMode));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY));
-
- std::vector<char> buf(kBufSize);
- struct iovec iov[2];
- iov[0].iov_base = buf.data();
- iov[0].iov_len = buf.size() / 2;
- iov[1].iov_base = static_cast<char*>(iov[0].iov_base) + (content.size() / 2);
- iov[1].iov_len = content.size() / 2;
-
- EXPECT_THAT(preadv2(fd.get(), iov, /*iovcnt*/ 2, /*offset=*/0, /*flags=*/0),
- SyscallSucceedsWithValue(kBufSize));
-
- EXPECT_EQ(content, std::string(buf.data(), buf.size()));
-}
-
-// This test is where we call preadv with an offset and no flags.
-TEST(Preadv2Test, TestValidPositiveOffset) {
- SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
-
- std::string content = SetContent();
- const std::string prefix = "0";
-
- const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), prefix + content, TempPath::kDefaultFileMode));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY));
-
- std::vector<char> buf(kBufSize, '0');
- struct iovec iov;
- iov.iov_base = buf.data();
- iov.iov_len = buf.size();
-
- EXPECT_THAT(preadv2(fd.get(), &iov, /*iovcnt=*/1, /*offset=*/prefix.size(),
- /*flags=*/0),
- SyscallSucceedsWithValue(kBufSize));
-
- EXPECT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0));
-
- EXPECT_EQ(content, std::string(buf.data(), buf.size()));
-}
-
-// This test is the base case where we call readv by using -1 as the offset. The
-// read should use the file offset, so the test increments it by one prior to
-// calling preadv2.
-TEST(Preadv2Test, TestNegativeOneOffset) {
- SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
-
- std::string content = SetContent();
- const std::string prefix = "231";
-
- const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), prefix + content, TempPath::kDefaultFileMode));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY));
-
- ASSERT_THAT(lseek(fd.get(), prefix.size(), SEEK_SET),
- SyscallSucceedsWithValue(prefix.size()));
-
- std::vector<char> buf(kBufSize, '0');
- struct iovec iov;
- iov.iov_base = buf.data();
- iov.iov_len = buf.size();
-
- EXPECT_THAT(preadv2(fd.get(), &iov, /*iovcnt=*/1, /*offset=*/-1, /*flags=*/0),
- SyscallSucceedsWithValue(kBufSize));
-
- EXPECT_THAT(lseek(fd.get(), 0, SEEK_CUR),
- SyscallSucceedsWithValue(prefix.size() + buf.size()));
-
- EXPECT_EQ(content, std::string(buf.data(), buf.size()));
-}
-
-// preadv2 requires if the RWF_HIPRI flag is passed, the fd must be opened with
-// O_DIRECT. This test implements a correct call with the RWF_HIPRI flag.
-TEST(Preadv2Test, TestCallWithRWF_HIPRI) {
- SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
-
- std::string content = SetContent();
-
- const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), content, TempPath::kDefaultFileMode));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY));
-
- EXPECT_THAT(fsync(fd.get()), SyscallSucceeds());
-
- std::vector<char> buf(kBufSize, '0');
- struct iovec iov;
- iov.iov_base = buf.data();
- iov.iov_len = buf.size();
-
- EXPECT_THAT(
- preadv2(fd.get(), &iov, /*iovcnt=*/1, /*offset=*/0, /*flags=*/RWF_HIPRI),
- SyscallSucceedsWithValue(kBufSize));
-
- EXPECT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0));
-
- EXPECT_EQ(content, std::string(buf.data(), buf.size()));
-}
-// This test calls preadv2 with an invalid flag.
-TEST(Preadv2Test, TestInvalidFlag) {
- SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
-
- const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY | O_DIRECT));
-
- std::vector<char> buf(kBufSize, '0');
- struct iovec iov;
- iov.iov_base = buf.data();
- iov.iov_len = buf.size();
-
- EXPECT_THAT(preadv2(fd.get(), &iov, /*iovcnt=*/1,
- /*offset=*/0, /*flags=*/0xF0),
- SyscallFailsWithErrno(EOPNOTSUPP));
-}
-
-// This test calls preadv2 with an invalid offset.
-TEST(Preadv2Test, TestInvalidOffset) {
- SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
-
- const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY | O_DIRECT));
-
- auto iov = absl::make_unique<struct iovec[]>(1);
- iov[0].iov_base = nullptr;
- iov[0].iov_len = 0;
-
- EXPECT_THAT(preadv2(fd.get(), iov.get(), /*iovcnt=*/1, /*offset=*/-8,
- /*flags=*/RWF_HIPRI),
- SyscallFailsWithErrno(EINVAL));
-}
-
-// This test calls preadv with a file set O_WRONLY.
-TEST(Preadv2Test, TestUnreadableFile) {
- SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
-
- const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_WRONLY));
-
- auto iov = absl::make_unique<struct iovec[]>(1);
- iov[0].iov_base = nullptr;
- iov[0].iov_len = 0;
-
- EXPECT_THAT(preadv2(fd.get(), iov.get(), /*iovcnt=*/1,
- /*offset=*/0, /*flags=*/0),
- SyscallFailsWithErrno(EBADF));
-}
-
-// Calling preadv2 with a non-negative offset calls preadv. Calling preadv with
-// an unseekable file is not allowed. A pipe is used for an unseekable file.
-TEST(Preadv2Test, TestUnseekableFileInvalid) {
- SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
-
- int pipe_fds[2];
-
- ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds());
-
- auto iov = absl::make_unique<struct iovec[]>(1);
- iov[0].iov_base = nullptr;
- iov[0].iov_len = 0;
-
- EXPECT_THAT(preadv2(pipe_fds[0], iov.get(), /*iovcnt=*/1,
- /*offset=*/2, /*flags=*/0),
- SyscallFailsWithErrno(ESPIPE));
-
- EXPECT_THAT(close(pipe_fds[0]), SyscallSucceeds());
- EXPECT_THAT(close(pipe_fds[1]), SyscallSucceeds());
-}
-
-TEST(Preadv2Test, TestUnseekableFileValid) {
- SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
-
- int pipe_fds[2];
-
- ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds());
-
- std::vector<char> content(32, 'X');
-
- EXPECT_THAT(write(pipe_fds[1], content.data(), content.size()),
- SyscallSucceedsWithValue(content.size()));
-
- std::vector<char> buf(content.size());
- auto iov = absl::make_unique<struct iovec[]>(1);
- iov[0].iov_base = buf.data();
- iov[0].iov_len = buf.size();
-
- EXPECT_THAT(preadv2(pipe_fds[0], iov.get(), /*iovcnt=*/1,
- /*offset=*/static_cast<off_t>(-1), /*flags=*/0),
- SyscallSucceedsWithValue(buf.size()));
-
- EXPECT_EQ(content, buf);
-
- EXPECT_THAT(close(pipe_fds[0]), SyscallSucceeds());
- EXPECT_THAT(close(pipe_fds[1]), SyscallSucceeds());
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/priority.cc b/test/syscalls/linux/priority.cc
deleted file mode 100644
index 1d9bdfa70..000000000
--- a/test/syscalls/linux/priority.cc
+++ /dev/null
@@ -1,216 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sys/resource.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <string>
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "absl/strings/numbers.h"
-#include "absl/strings/str_split.h"
-#include "test/util/capability_util.h"
-#include "test/util/fs_util.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// These tests are for both the getpriority(2) and setpriority(2) syscalls
-// These tests are very rudimentary because getpriority and setpriority
-// have not yet been fully implemented.
-
-// Getpriority does something
-TEST(GetpriorityTest, Implemented) {
- // "getpriority() can legitimately return the value -1, it is necessary to
- // clear the external variable errno prior to the call"
- errno = 0;
- EXPECT_THAT(getpriority(PRIO_PROCESS, /*who=*/0), SyscallSucceeds());
-}
-
-// Invalid which
-TEST(GetpriorityTest, InvalidWhich) {
- errno = 0;
- EXPECT_THAT(getpriority(/*which=*/3, /*who=*/0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-// Process is found when which=PRIO_PROCESS
-TEST(GetpriorityTest, ValidWho) {
- errno = 0;
- EXPECT_THAT(getpriority(PRIO_PROCESS, getpid()), SyscallSucceeds());
-}
-
-// Process is not found when which=PRIO_PROCESS
-TEST(GetpriorityTest, InvalidWho) {
- errno = 0;
- // Flaky, but it's tough to avoid a race condition when finding an unused pid
- EXPECT_THAT(getpriority(PRIO_PROCESS, /*who=*/INT_MAX - 1),
- SyscallFailsWithErrno(ESRCH));
-}
-
-// Setpriority does something
-TEST(SetpriorityTest, Implemented) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE)));
-
- // No need to clear errno for setpriority():
- // "The setpriority() call returns 0 if there is no error, or -1 if there is"
- EXPECT_THAT(setpriority(PRIO_PROCESS, /*who=*/0, /*nice=*/16),
- SyscallSucceeds());
-}
-
-// Invalid which
-TEST(Setpriority, InvalidWhich) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE)));
-
- EXPECT_THAT(setpriority(/*which=*/3, /*who=*/0, /*nice=*/16),
- SyscallFailsWithErrno(EINVAL));
-}
-
-// Process is found when which=PRIO_PROCESS
-TEST(SetpriorityTest, ValidWho) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE)));
-
- EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(), /*nice=*/16),
- SyscallSucceeds());
-}
-
-// niceval is within the range [-20, 19]
-TEST(SetpriorityTest, InsideRange) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE)));
-
- // Set 0 < niceval < 19
- int nice = 12;
- EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(), nice), SyscallSucceeds());
-
- errno = 0;
- EXPECT_THAT(getpriority(PRIO_PROCESS, getpid()),
- SyscallSucceedsWithValue(nice));
-
- // Set -20 < niceval < 0
- nice = -12;
- EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(), nice), SyscallSucceeds());
-
- errno = 0;
- EXPECT_THAT(getpriority(PRIO_PROCESS, getpid()),
- SyscallSucceedsWithValue(nice));
-}
-
-// Verify that priority/niceness are exposed via /proc/PID/stat.
-TEST(SetpriorityTest, NicenessExposedViaProcfs) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE)));
-
- constexpr int kNiceVal = 12;
- ASSERT_THAT(setpriority(PRIO_PROCESS, getpid(), kNiceVal), SyscallSucceeds());
-
- errno = 0;
- ASSERT_THAT(getpriority(PRIO_PROCESS, getpid()),
- SyscallSucceedsWithValue(kNiceVal));
-
- // Now verify we can read that same value via /proc/self/stat.
- std::string proc_stat;
- ASSERT_NO_ERRNO(GetContents("/proc/self/stat", &proc_stat));
- std::vector<std::string> pieces = absl::StrSplit(proc_stat, ' ');
- ASSERT_GT(pieces.size(), 20);
-
- int niceness_procfs = 0;
- ASSERT_TRUE(absl::SimpleAtoi(pieces[18], &niceness_procfs));
- EXPECT_EQ(niceness_procfs, kNiceVal);
-}
-
-// In the kernel's implementation, values outside the range of [-20, 19] are
-// truncated to these minimum and maximum values. See
-// https://elixir.bootlin.com/linux/v4.4/source/kernel/sys.c#L190
-TEST(SetpriorityTest, OutsideRange) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE)));
-
- // Set niceval > 19
- EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(), /*nice=*/100),
- SyscallSucceeds());
-
- errno = 0;
- // Test niceval truncated to 19
- EXPECT_THAT(getpriority(PRIO_PROCESS, getpid()),
- SyscallSucceedsWithValue(/*maxnice=*/19));
-
- // Set niceval < -20
- EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(), /*nice=*/-100),
- SyscallSucceeds());
-
- errno = 0;
- // Test niceval truncated to -20
- EXPECT_THAT(getpriority(PRIO_PROCESS, getpid()),
- SyscallSucceedsWithValue(/*minnice=*/-20));
-}
-
-// Process is not found when which=PRIO_PROCESS
-TEST(SetpriorityTest, InvalidWho) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE)));
-
- // Flaky, but it's tough to avoid a race condition when finding an unused pid
- EXPECT_THAT(setpriority(PRIO_PROCESS,
- /*who=*/INT_MAX - 1,
- /*nice=*/16),
- SyscallFailsWithErrno(ESRCH));
-}
-
-// Nice succeeds, correctly modifies (or in this case does not
-// modify priority of process
-TEST(SetpriorityTest, NiceSucceeds) {
- errno = 0;
- const int priority_before = getpriority(PRIO_PROCESS, /*who=*/0);
- ASSERT_THAT(nice(/*inc=*/0), SyscallSucceeds());
-
- // nice(0) should not change priority
- EXPECT_EQ(priority_before, getpriority(PRIO_PROCESS, /*who=*/0));
-}
-
-// Threads resulting from clone() maintain parent's priority
-// Changes to child priority do not affect parent's priority
-TEST(GetpriorityTest, CloneMaintainsPriority) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE)));
-
- constexpr int kParentPriority = 16;
- constexpr int kChildPriority = 14;
- ASSERT_THAT(setpriority(PRIO_PROCESS, getpid(), kParentPriority),
- SyscallSucceeds());
-
- ScopedThread th([]() {
- // Check that priority equals that of parent thread
- pid_t my_tid;
- EXPECT_THAT(my_tid = syscall(__NR_gettid), SyscallSucceeds());
- EXPECT_THAT(getpriority(PRIO_PROCESS, my_tid),
- SyscallSucceedsWithValue(kParentPriority));
-
- // Change the child thread's priority
- EXPECT_THAT(setpriority(PRIO_PROCESS, my_tid, kChildPriority),
- SyscallSucceeds());
- });
- th.Join();
-
- // Check that parent's priority reemained the same even though
- // the child's priority was altered
- EXPECT_EQ(kParentPriority, getpriority(PRIO_PROCESS, syscall(__NR_gettid)));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/priority_execve.cc b/test/syscalls/linux/priority_execve.cc
deleted file mode 100644
index 5cb343bad..000000000
--- a/test/syscalls/linux/priority_execve.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/resource.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-int main(int argc, char** argv, char** envp) {
- errno = 0;
- int prio = getpriority(PRIO_PROCESS, getpid());
-
- // NOTE: getpriority() can legitimately return negative values
- // in the range [-20, 0). If errno is set, exit with a value that
- // could not be reached by a valid priority. Valid exit values
- // for the test are in the range [1, 40], so we'll use 0.
- if (errno != 0) {
- printf("getpriority() failed with errno = %d\n", errno);
- exit(0);
- }
-
- // Used by test to verify priority is being maintained through
- // calls to execve(). Since prio should always be in the range
- // [-20, 19], we offset by 20 so as not to have negative exit codes.
- exit(20 - prio);
-
- return 0;
-}
diff --git a/test/syscalls/linux/proc.cc b/test/syscalls/linux/proc.cc
deleted file mode 100644
index 6f07803d9..000000000
--- a/test/syscalls/linux/proc.cc
+++ /dev/null
@@ -1,1996 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <elf.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <sched.h>
-#include <signal.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/mman.h>
-#include <sys/prctl.h>
-#include <sys/stat.h>
-#include <sys/utsname.h>
-#include <syscall.h>
-#include <unistd.h>
-
-#include <algorithm>
-#include <atomic>
-#include <functional>
-#include <iostream>
-#include <map>
-#include <memory>
-#include <ostream>
-#include <string>
-#include <unordered_set>
-#include <utility>
-#include <vector>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/strings/ascii.h"
-#include "absl/strings/match.h"
-#include "absl/strings/numbers.h"
-#include "absl/strings/str_cat.h"
-#include "absl/strings/str_split.h"
-#include "absl/strings/string_view.h"
-#include "absl/synchronization/mutex.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/capability_util.h"
-#include "test/util/cleanup.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/memory_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-#include "test/util/time_util.h"
-#include "test/util/timer_util.h"
-
-// NOTE(magi): No, this isn't really a syscall but this is a really simple
-// way to get it tested on both gVisor, PTrace and Linux.
-
-using ::testing::AllOf;
-using ::testing::AnyOf;
-using ::testing::ContainerEq;
-using ::testing::Contains;
-using ::testing::ContainsRegex;
-using ::testing::Eq;
-using ::testing::Gt;
-using ::testing::HasSubstr;
-using ::testing::IsSupersetOf;
-using ::testing::Pair;
-using ::testing::UnorderedElementsAre;
-using ::testing::UnorderedElementsAreArray;
-
-// Exported by glibc.
-extern char** environ;
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-#ifndef SUID_DUMP_DISABLE
-#define SUID_DUMP_DISABLE 0
-#endif /* SUID_DUMP_DISABLE */
-#ifndef SUID_DUMP_USER
-#define SUID_DUMP_USER 1
-#endif /* SUID_DUMP_USER */
-#ifndef SUID_DUMP_ROOT
-#define SUID_DUMP_ROOT 2
-#endif /* SUID_DUMP_ROOT */
-
-// O_LARGEFILE as defined by Linux. glibc tries to be clever by setting it to 0
-// because "it isn't needed", even though Linux can return it via F_GETFL.
-constexpr int kOLargeFile = 00100000;
-
-// Takes the subprocess command line and pid.
-// If it returns !OK, WithSubprocess returns immediately.
-using SubprocessCallback = std::function<PosixError(int)>;
-
-std::vector<std::string> saved_argv; // NOLINT
-
-// Helper function to dump /proc/{pid}/status and check the
-// state data. State should = "Z" for zombied or "RSD" for
-// running, interruptible sleeping (S), or uninterruptible sleep
-// (D).
-void CompareProcessState(absl::string_view state, int pid) {
- auto status_file = ASSERT_NO_ERRNO_AND_VALUE(
- GetContents(absl::StrCat("/proc/", pid, "/status")));
- // N.B. POSIX extended regexes don't support shorthand character classes (\w)
- // inside of brackets.
- EXPECT_THAT(status_file,
- ContainsRegex(absl::StrCat("State:.[", state,
- R"EOL(]\s+\([a-zA-Z ]+\))EOL")));
-}
-
-// Run callbacks while a subprocess is running, zombied, and/or exited.
-PosixError WithSubprocess(SubprocessCallback const& running,
- SubprocessCallback const& zombied,
- SubprocessCallback const& exited) {
- int pipe_fds[2] = {};
- if (pipe(pipe_fds) < 0) {
- return PosixError(errno, "pipe");
- }
-
- int child_pid = fork();
- if (child_pid < 0) {
- return PosixError(errno, "fork");
- }
-
- if (child_pid == 0) {
- close(pipe_fds[0]); // Close the read end.
- const DisableSave ds; // Timing issues.
-
- // Write to the pipe to tell it we're ready.
- char buf = 'a';
- int res = 0;
- res = WriteFd(pipe_fds[1], &buf, sizeof(buf));
- TEST_CHECK_MSG(res == sizeof(buf), "Write failure in subprocess");
-
- while (true) {
- SleepSafe(absl::Milliseconds(100));
- }
- }
-
- close(pipe_fds[1]); // Close the write end.
-
- int status = 0;
- auto wait_cleanup = Cleanup([child_pid, &status] {
- EXPECT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds());
- });
- auto kill_cleanup = Cleanup([child_pid] {
- EXPECT_THAT(kill(child_pid, SIGKILL), SyscallSucceeds());
- });
-
- // Wait for the child.
- char buf = 0;
- int res = ReadFd(pipe_fds[0], &buf, sizeof(buf));
- if (res < 0) {
- return PosixError(errno, "Read from pipe");
- } else if (res == 0) {
- return PosixError(EPIPE, "Unable to read from pipe: EOF");
- }
-
- if (running) {
- // The first arg, RSD, refers to a "running process", or a process with a
- // state of Running (R), Interruptable Sleep (S) or Uninterruptable
- // Sleep (D).
- CompareProcessState("RSD", child_pid);
- RETURN_IF_ERRNO(running(child_pid));
- }
-
- // Kill the process.
- kill_cleanup.Release()();
- siginfo_t info;
- // Wait until the child process has exited (WEXITED flag) but don't
- // reap the child (WNOWAIT flag).
- waitid(P_PID, child_pid, &info, WNOWAIT | WEXITED);
-
- if (zombied) {
- // Arg of "Z" refers to a Zombied Process.
- CompareProcessState("Z", child_pid);
- RETURN_IF_ERRNO(zombied(child_pid));
- }
-
- // Wait on the process.
- wait_cleanup.Release()();
- // If the process is reaped, then then this should return
- // with ECHILD.
- EXPECT_THAT(waitpid(child_pid, &status, WNOHANG),
- SyscallFailsWithErrno(ECHILD));
-
- if (exited) {
- RETURN_IF_ERRNO(exited(child_pid));
- }
-
- return NoError();
-}
-
-// Access the file returned by name when a subprocess is running.
-PosixError AccessWhileRunning(std::function<std::string(int pid)> name,
- int flags, std::function<void(int fd)> access) {
- FileDescriptor fd;
- return WithSubprocess(
- [&](int pid) -> PosixError {
- // Running.
- ASSIGN_OR_RETURN_ERRNO(fd, Open(name(pid), flags));
-
- access(fd.get());
- return NoError();
- },
- nullptr, nullptr);
-}
-
-// Access the file returned by name when the a subprocess is zombied.
-PosixError AccessWhileZombied(std::function<std::string(int pid)> name,
- int flags, std::function<void(int fd)> access) {
- FileDescriptor fd;
- return WithSubprocess(
- [&](int pid) -> PosixError {
- // Running.
- ASSIGN_OR_RETURN_ERRNO(fd, Open(name(pid), flags));
- return NoError();
- },
- [&](int pid) -> PosixError {
- // Zombied.
- access(fd.get());
- return NoError();
- },
- nullptr);
-}
-
-// Access the file returned by name when the a subprocess is exited.
-PosixError AccessWhileExited(std::function<std::string(int pid)> name,
- int flags, std::function<void(int fd)> access) {
- FileDescriptor fd;
- return WithSubprocess(
- [&](int pid) -> PosixError {
- // Running.
- ASSIGN_OR_RETURN_ERRNO(fd, Open(name(pid), flags));
- return NoError();
- },
- nullptr,
- [&](int pid) -> PosixError {
- // Exited.
- access(fd.get());
- return NoError();
- });
-}
-
-// ReadFd(fd=/proc/PID/basename) while PID is running.
-int ReadWhileRunning(std::string const& basename, void* buf, size_t count) {
- int ret = 0;
- int err = 0;
- EXPECT_NO_ERRNO(AccessWhileRunning(
- [&](int pid) -> std::string {
- return absl::StrCat("/proc/", pid, "/", basename);
- },
- O_RDONLY,
- [&](int fd) {
- ret = ReadFd(fd, buf, count);
- err = errno;
- }));
- errno = err;
- return ret;
-}
-
-// ReadFd(fd=/proc/PID/basename) while PID is zombied.
-int ReadWhileZombied(std::string const& basename, void* buf, size_t count) {
- int ret = 0;
- int err = 0;
- EXPECT_NO_ERRNO(AccessWhileZombied(
- [&](int pid) -> std::string {
- return absl::StrCat("/proc/", pid, "/", basename);
- },
- O_RDONLY,
- [&](int fd) {
- ret = ReadFd(fd, buf, count);
- err = errno;
- }));
- errno = err;
- return ret;
-}
-
-// ReadFd(fd=/proc/PID/basename) while PID is exited.
-int ReadWhileExited(std::string const& basename, void* buf, size_t count) {
- int ret = 0;
- int err = 0;
- EXPECT_NO_ERRNO(AccessWhileExited(
- [&](int pid) -> std::string {
- return absl::StrCat("/proc/", pid, "/", basename);
- },
- O_RDONLY,
- [&](int fd) {
- ret = ReadFd(fd, buf, count);
- err = errno;
- }));
- errno = err;
- return ret;
-}
-
-// readlinkat(fd=/proc/PID/, basename) while PID is running.
-int ReadlinkWhileRunning(std::string const& basename, char* buf, size_t count) {
- int ret = 0;
- int err = 0;
- EXPECT_NO_ERRNO(AccessWhileRunning(
- [&](int pid) -> std::string { return absl::StrCat("/proc/", pid, "/"); },
- O_DIRECTORY,
- [&](int fd) {
- ret = readlinkat(fd, basename.c_str(), buf, count);
- err = errno;
- }));
- errno = err;
- return ret;
-}
-
-// readlinkat(fd=/proc/PID/, basename) while PID is zombied.
-int ReadlinkWhileZombied(std::string const& basename, char* buf, size_t count) {
- int ret = 0;
- int err = 0;
- EXPECT_NO_ERRNO(AccessWhileZombied(
- [&](int pid) -> std::string { return absl::StrCat("/proc/", pid, "/"); },
- O_DIRECTORY,
- [&](int fd) {
- ret = readlinkat(fd, basename.c_str(), buf, count);
- err = errno;
- }));
- errno = err;
- return ret;
-}
-
-// readlinkat(fd=/proc/PID/, basename) while PID is exited.
-int ReadlinkWhileExited(std::string const& basename, char* buf, size_t count) {
- int ret = 0;
- int err = 0;
- EXPECT_NO_ERRNO(AccessWhileExited(
- [&](int pid) -> std::string { return absl::StrCat("/proc/", pid, "/"); },
- O_DIRECTORY,
- [&](int fd) {
- ret = readlinkat(fd, basename.c_str(), buf, count);
- err = errno;
- }));
- errno = err;
- return ret;
-}
-
-TEST(ProcTest, NotFoundInRoot) {
- struct stat s;
- EXPECT_THAT(stat("/proc/foobar", &s), SyscallFailsWithErrno(ENOENT));
-}
-
-TEST(ProcSelfTest, IsThreadGroupLeader) {
- ScopedThread([] {
- const pid_t tgid = getpid();
- const pid_t tid = syscall(SYS_gettid);
- EXPECT_NE(tgid, tid);
- auto link = ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/self"));
- EXPECT_EQ(link, absl::StrCat(tgid));
- });
-}
-
-TEST(ProcThreadSelfTest, Basic) {
- const pid_t tgid = getpid();
- const pid_t tid = syscall(SYS_gettid);
- EXPECT_EQ(tgid, tid);
- auto link_threadself =
- ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/thread-self"));
- EXPECT_EQ(link_threadself, absl::StrCat(tgid, "/task/", tid));
- // Just read one file inside thread-self to ensure that the link is valid.
- auto link_threadself_exe =
- ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/thread-self/exe"));
- auto link_procself_exe =
- ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/self/exe"));
- EXPECT_EQ(link_threadself_exe, link_procself_exe);
-}
-
-TEST(ProcThreadSelfTest, Thread) {
- ScopedThread([] {
- const pid_t tgid = getpid();
- const pid_t tid = syscall(SYS_gettid);
- EXPECT_NE(tgid, tid);
- auto link_threadself =
- ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/thread-self"));
-
- EXPECT_EQ(link_threadself, absl::StrCat(tgid, "/task/", tid));
- // Just read one file inside thread-self to ensure that the link is valid.
- auto link_threadself_exe =
- ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/thread-self/exe"));
- auto link_procself_exe =
- ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/self/exe"));
- EXPECT_EQ(link_threadself_exe, link_procself_exe);
- // A thread should not have "/proc/<tid>/task".
- struct stat s;
- EXPECT_THAT(stat("/proc/thread-self/task", &s),
- SyscallFailsWithErrno(ENOENT));
- });
-}
-
-// Returns the /proc/PID/maps entry for the MAP_PRIVATE | MAP_ANONYMOUS mapping
-// m with start address addr and length len.
-std::string AnonymousMapsEntry(uintptr_t addr, size_t len, int prot) {
- return absl::StrCat(absl::Hex(addr, absl::PadSpec::kZeroPad8), "-",
- absl::Hex(addr + len, absl::PadSpec::kZeroPad8), " ",
- prot & PROT_READ ? "r" : "-",
- prot & PROT_WRITE ? "w" : "-",
- prot & PROT_EXEC ? "x" : "-", "p 00000000 00:00 0 ");
-}
-
-std::string AnonymousMapsEntryForMapping(const Mapping& m, int prot) {
- return AnonymousMapsEntry(m.addr(), m.len(), prot);
-}
-
-PosixErrorOr<std::map<uint64_t, uint64_t>> ReadProcSelfAuxv() {
- std::string auxv_file;
- RETURN_IF_ERRNO(GetContents("/proc/self/auxv", &auxv_file));
- const Elf64_auxv_t* auxv_data =
- reinterpret_cast<const Elf64_auxv_t*>(auxv_file.data());
- std::map<uint64_t, uint64_t> auxv_entries;
- for (int i = 0; auxv_data[i].a_type != AT_NULL; i++) {
- auto a_type = auxv_data[i].a_type;
- EXPECT_EQ(0, auxv_entries.count(a_type)) << "a_type: " << a_type;
- auxv_entries.emplace(a_type, auxv_data[i].a_un.a_val);
- }
- return auxv_entries;
-}
-
-TEST(ProcSelfAuxv, EntryPresence) {
- auto auxv_entries = ASSERT_NO_ERRNO_AND_VALUE(ReadProcSelfAuxv());
-
- EXPECT_EQ(auxv_entries.count(AT_ENTRY), 1);
- EXPECT_EQ(auxv_entries.count(AT_PHDR), 1);
- EXPECT_EQ(auxv_entries.count(AT_PHENT), 1);
- EXPECT_EQ(auxv_entries.count(AT_PHNUM), 1);
- EXPECT_EQ(auxv_entries.count(AT_BASE), 1);
- EXPECT_EQ(auxv_entries.count(AT_CLKTCK), 1);
- EXPECT_EQ(auxv_entries.count(AT_RANDOM), 1);
- EXPECT_EQ(auxv_entries.count(AT_EXECFN), 1);
- EXPECT_EQ(auxv_entries.count(AT_PAGESZ), 1);
- EXPECT_EQ(auxv_entries.count(AT_SYSINFO_EHDR), 1);
-}
-
-TEST(ProcSelfAuxv, EntryValues) {
- auto proc_auxv = ASSERT_NO_ERRNO_AND_VALUE(ReadProcSelfAuxv());
-
- // We need to find the ELF auxiliary vector. The section of memory pointed to
- // by envp contains some pointers to non-null pointers, followed by a single
- // pointer to a null pointer, followed by the auxiliary vector.
- char** envpi = environ;
- while (*envpi) {
- ++envpi;
- }
-
- const Elf64_auxv_t* envp_auxv =
- reinterpret_cast<const Elf64_auxv_t*>(envpi + 1);
- int i;
- for (i = 0; envp_auxv[i].a_type != AT_NULL; i++) {
- auto a_type = envp_auxv[i].a_type;
- EXPECT_EQ(proc_auxv.count(a_type), 1);
- EXPECT_EQ(proc_auxv[a_type], envp_auxv[i].a_un.a_val)
- << "a_type: " << a_type;
- }
- EXPECT_EQ(i, proc_auxv.size());
-}
-
-// Just open and read /proc/self/maps, check that we can find [stack]
-TEST(ProcSelfMaps, Basic) {
- auto proc_self_maps =
- ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps"));
-
- std::vector<std::string> strings = absl::StrSplit(proc_self_maps, '\n');
- std::vector<std::string> stacks;
- // Make sure there's a stack in there.
- for (const auto& str : strings) {
- if (str.find("[stack]") != std::string::npos) {
- stacks.push_back(str);
- }
- }
- ASSERT_EQ(1, stacks.size()) << "[stack] not found in: " << proc_self_maps;
- // Linux pads to 73 characters then we add 7.
- EXPECT_EQ(80, stacks[0].length());
-}
-
-TEST(ProcSelfMaps, Map1) {
- Mapping mapping =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_READ, MAP_PRIVATE));
- auto proc_self_maps =
- ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps"));
- std::vector<std::string> strings = absl::StrSplit(proc_self_maps, '\n');
- std::vector<std::string> addrs;
- // Make sure if is listed.
- for (const auto& str : strings) {
- if (str == AnonymousMapsEntryForMapping(mapping, PROT_READ)) {
- addrs.push_back(str);
- }
- }
- ASSERT_EQ(1, addrs.size());
-}
-
-TEST(ProcSelfMaps, Map2) {
- // NOTE(magi): The permissions must be different or the pages will get merged.
- Mapping map1 = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_EXEC, MAP_PRIVATE));
- Mapping map2 =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_WRITE, MAP_PRIVATE));
-
- auto proc_self_maps =
- ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps"));
- std::vector<std::string> strings = absl::StrSplit(proc_self_maps, '\n');
- std::vector<std::string> addrs;
- // Make sure if is listed.
- for (const auto& str : strings) {
- if (str == AnonymousMapsEntryForMapping(map1, PROT_READ | PROT_EXEC)) {
- addrs.push_back(str);
- }
- }
- ASSERT_EQ(1, addrs.size());
- addrs.clear();
- for (const auto& str : strings) {
- if (str == AnonymousMapsEntryForMapping(map2, PROT_WRITE)) {
- addrs.push_back(str);
- }
- }
- ASSERT_EQ(1, addrs.size());
-}
-
-TEST(ProcSelfMaps, MapUnmap) {
- Mapping map1 = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kPageSize, PROT_READ | PROT_EXEC, MAP_PRIVATE));
- Mapping map2 =
- ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_WRITE, MAP_PRIVATE));
-
- auto proc_self_maps =
- ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps"));
- std::vector<std::string> strings = absl::StrSplit(proc_self_maps, '\n');
- std::vector<std::string> addrs;
- // Make sure if is listed.
- for (const auto& str : strings) {
- if (str == AnonymousMapsEntryForMapping(map1, PROT_READ | PROT_EXEC)) {
- addrs.push_back(str);
- }
- }
- ASSERT_EQ(1, addrs.size()) << proc_self_maps;
- addrs.clear();
- for (const auto& str : strings) {
- if (str == AnonymousMapsEntryForMapping(map2, PROT_WRITE)) {
- addrs.push_back(str);
- }
- }
- ASSERT_EQ(1, addrs.size());
-
- map2.reset();
-
- // Read it again.
- proc_self_maps = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps"));
- strings = absl::StrSplit(proc_self_maps, '\n');
- // First entry should be there.
- addrs.clear();
- for (const auto& str : strings) {
- if (str == AnonymousMapsEntryForMapping(map1, PROT_READ | PROT_EXEC)) {
- addrs.push_back(str);
- }
- }
- ASSERT_EQ(1, addrs.size());
- addrs.clear();
- // But not the second.
- for (const auto& str : strings) {
- if (str == AnonymousMapsEntryForMapping(map2, PROT_WRITE)) {
- addrs.push_back(str);
- }
- }
- ASSERT_EQ(0, addrs.size());
-}
-
-TEST(ProcSelfMaps, Mprotect) {
- // FIXME(jamieliu): Linux's mprotect() sometimes fails to merge VMAs in this
- // case.
- SKIP_IF(!IsRunningOnGvisor());
-
- // Reserve 5 pages of address space.
- Mapping m = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(5 * kPageSize, PROT_NONE, MAP_PRIVATE));
-
- // Change the permissions on the middle 3 pages. (The first and last pages may
- // be merged with other vmas on either side, so they aren't tested directly;
- // they just ensure that the middle 3 pages are bracketed by VMAs with
- // incompatible permissions.)
- ASSERT_THAT(mprotect(reinterpret_cast<void*>(m.addr() + kPageSize),
- 3 * kPageSize, PROT_READ),
- SyscallSucceeds());
-
- // Check that the middle 3 pages make up a single VMA.
- auto proc_self_maps =
- ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps"));
- std::vector<std::string> strings = absl::StrSplit(proc_self_maps, '\n');
- EXPECT_THAT(strings, Contains(AnonymousMapsEntry(m.addr() + kPageSize,
- 3 * kPageSize, PROT_READ)));
-
- // Change the permissions on the middle page only.
- ASSERT_THAT(mprotect(reinterpret_cast<void*>(m.addr() + 2 * kPageSize),
- kPageSize, PROT_READ | PROT_WRITE),
- SyscallSucceeds());
-
- // Check that the single VMA has been split into 3 VMAs.
- proc_self_maps = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps"));
- strings = absl::StrSplit(proc_self_maps, '\n');
- EXPECT_THAT(
- strings,
- IsSupersetOf(
- {AnonymousMapsEntry(m.addr() + kPageSize, kPageSize, PROT_READ),
- AnonymousMapsEntry(m.addr() + 2 * kPageSize, kPageSize,
- PROT_READ | PROT_WRITE),
- AnonymousMapsEntry(m.addr() + 3 * kPageSize, kPageSize,
- PROT_READ)}));
-
- // Change the permissions on the middle page back.
- ASSERT_THAT(mprotect(reinterpret_cast<void*>(m.addr() + 2 * kPageSize),
- kPageSize, PROT_READ),
- SyscallSucceeds());
-
- // Check that the 3 VMAs have been merged back into a single VMA.
- proc_self_maps = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps"));
- strings = absl::StrSplit(proc_self_maps, '\n');
- EXPECT_THAT(strings, Contains(AnonymousMapsEntry(m.addr() + kPageSize,
- 3 * kPageSize, PROT_READ)));
-}
-
-TEST(ProcSelfFd, OpenFd) {
- int pipe_fds[2];
- ASSERT_THAT(pipe2(pipe_fds, O_CLOEXEC), SyscallSucceeds());
-
- // Reopen the write end.
- const std::string path = absl::StrCat("/proc/self/fd/", pipe_fds[1]);
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_WRONLY));
-
- // Ensure that a read/write works.
- const std::string data = "hello";
- std::unique_ptr<char[]> buffer(new char[data.size()]);
- EXPECT_THAT(write(fd.get(), data.c_str(), data.size()),
- SyscallSucceedsWithValue(5));
- EXPECT_THAT(read(pipe_fds[0], buffer.get(), data.size()),
- SyscallSucceedsWithValue(5));
- EXPECT_EQ(strncmp(buffer.get(), data.c_str(), data.size()), 0);
-
- // Cleanup.
- ASSERT_THAT(close(pipe_fds[0]), SyscallSucceeds());
- ASSERT_THAT(close(pipe_fds[1]), SyscallSucceeds());
-}
-
-TEST(ProcSelfFdInfo, CorrectFds) {
- // Make sure there is at least one open file.
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDONLY));
-
- // Get files in /proc/self/fd.
- auto fd_files = ASSERT_NO_ERRNO_AND_VALUE(ListDir("/proc/self/fd", false));
-
- // Get files in /proc/self/fdinfo.
- auto fdinfo_files =
- ASSERT_NO_ERRNO_AND_VALUE(ListDir("/proc/self/fdinfo", false));
-
- // They should contain the same fds.
- EXPECT_THAT(fd_files, UnorderedElementsAreArray(fdinfo_files));
-
- // Both should contain fd.
- auto fd_s = absl::StrCat(fd.get());
- EXPECT_THAT(fd_files, Contains(fd_s));
-}
-
-TEST(ProcSelfFdInfo, Flags) {
- std::string path = NewTempAbsPath();
-
- // Create file here with O_CREAT to test that O_CREAT does not appear in
- // fdinfo flags.
- int flags = O_CREAT | O_RDWR | O_APPEND | O_CLOEXEC;
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path, flags, 0644));
-
- // Automatically delete path.
- TempPath temp_path(path);
-
- // O_CREAT does not appear in fdinfo flags.
- flags &= ~O_CREAT;
-
- // O_LARGEFILE always appears (on x86_64).
- flags |= kOLargeFile;
-
- auto fd_info = ASSERT_NO_ERRNO_AND_VALUE(
- GetContents(absl::StrCat("/proc/self/fdinfo/", fd.get())));
- EXPECT_THAT(fd_info, HasSubstr(absl::StrFormat("flags:\t%#o", flags)));
-}
-
-TEST(ProcSelfExe, Absolute) {
- auto exe = ASSERT_NO_ERRNO_AND_VALUE(
- ReadLink(absl::StrCat("/proc/", getpid(), "/exe")));
- EXPECT_EQ(exe[0], '/');
-}
-
-// Sanity check for /proc/cpuinfo fields that must be present.
-TEST(ProcCpuinfo, RequiredFieldsArePresent) {
- std::string proc_cpuinfo =
- ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/cpuinfo"));
- ASSERT_FALSE(proc_cpuinfo.empty());
- std::vector<std::string> cpuinfo_fields = absl::StrSplit(proc_cpuinfo, '\n');
-
- // This list of "required" fields is taken from reading the file
- // arch/x86/kernel/cpu/proc.c and seeing which fields will be unconditionally
- // printed by the kernel.
- static const char* required_fields[] = {
- "processor",
- "vendor_id",
- "cpu family",
- "model\t\t:",
- "model name",
- "stepping",
- "cpu MHz",
- "fpu\t\t:",
- "fpu_exception",
- "cpuid level",
- "wp",
- "bogomips",
- "clflush size",
- "cache_alignment",
- "address sizes",
- "power management",
- };
-
- // Check that the usual fields are there. We don't really care about the
- // contents.
- for (const std::string& field : required_fields) {
- EXPECT_THAT(proc_cpuinfo, HasSubstr(field));
- }
-}
-
-TEST(ProcCpuinfo, DeniesWrite) {
- EXPECT_THAT(open("/proc/cpuinfo", O_WRONLY), SyscallFailsWithErrno(EACCES));
-}
-
-// Sanity checks that uptime is present.
-TEST(ProcUptime, IsPresent) {
- std::string proc_uptime =
- ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/uptime"));
- ASSERT_FALSE(proc_uptime.empty());
- std::vector<std::string> uptime_parts = absl::StrSplit(proc_uptime, ' ');
-
- // Parse once.
- double uptime0, uptime1, idletime0, idletime1;
- ASSERT_TRUE(absl::SimpleAtod(uptime_parts[0], &uptime0));
- ASSERT_TRUE(absl::SimpleAtod(uptime_parts[1], &idletime0));
-
- // Sleep for one second.
- absl::SleepFor(absl::Seconds(1));
-
- // Parse again.
- proc_uptime = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/uptime"));
- ASSERT_FALSE(proc_uptime.empty());
- uptime_parts = absl::StrSplit(proc_uptime, ' ');
- ASSERT_TRUE(absl::SimpleAtod(uptime_parts[0], &uptime1));
- ASSERT_TRUE(absl::SimpleAtod(uptime_parts[1], &idletime1));
-
- // Sanity check.
- //
- // We assert that between 0.99 and 59.99 seconds have passed. If more than a
- // minute has passed, then we must be executing really, really slowly.
- EXPECT_GE(uptime0, 0.0);
- EXPECT_GE(idletime0, 0.0);
- EXPECT_GT(uptime1, uptime0);
- EXPECT_GE(uptime1, uptime0 + 0.99);
- EXPECT_LE(uptime1, uptime0 + 59.99);
- EXPECT_GE(idletime1, idletime0);
-}
-
-TEST(ProcMeminfo, ContainsBasicFields) {
- std::string proc_meminfo =
- ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/meminfo"));
- EXPECT_THAT(proc_meminfo, AllOf(ContainsRegex(R"(MemTotal:\s+[0-9]+ kB)"),
- ContainsRegex(R"(MemFree:\s+[0-9]+ kB)")));
-}
-
-TEST(ProcStat, ContainsBasicFields) {
- std::string proc_stat = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/stat"));
-
- std::vector<std::string> names;
- for (auto const& line : absl::StrSplit(proc_stat, '\n')) {
- std::vector<std::string> fields =
- absl::StrSplit(line, ' ', absl::SkipWhitespace());
- if (fields.empty()) {
- continue;
- }
- names.push_back(fields[0]);
- }
-
- EXPECT_THAT(names,
- IsSupersetOf({"cpu", "intr", "ctxt", "btime", "processes",
- "procs_running", "procs_blocked", "softirq"}));
-}
-
-TEST(ProcStat, EndsWithNewline) {
- std::string proc_stat = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/stat"));
- EXPECT_EQ(proc_stat.back(), '\n');
-}
-
-TEST(ProcStat, Fields) {
- std::string proc_stat = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/stat"));
-
- std::vector<std::string> names;
- for (auto const& line : absl::StrSplit(proc_stat, '\n')) {
- std::vector<std::string> fields =
- absl::StrSplit(line, ' ', absl::SkipWhitespace());
- if (fields.empty()) {
- continue;
- }
-
- if (absl::StartsWith(fields[0], "cpu")) {
- // As of Linux 3.11, each CPU entry has 10 fields, plus the name.
- EXPECT_GE(fields.size(), 11) << proc_stat;
- } else if (fields[0] == "ctxt") {
- // Single field.
- EXPECT_EQ(fields.size(), 2) << proc_stat;
- } else if (fields[0] == "btime") {
- // Single field.
- EXPECT_EQ(fields.size(), 2) << proc_stat;
- } else if (fields[0] == "itime") {
- // Single field.
- ASSERT_EQ(fields.size(), 2) << proc_stat;
- // This is the only floating point field.
- double val;
- EXPECT_TRUE(absl::SimpleAtod(fields[1], &val)) << proc_stat;
- continue;
- } else if (fields[0] == "processes") {
- // Single field.
- EXPECT_EQ(fields.size(), 2) << proc_stat;
- } else if (fields[0] == "procs_running") {
- // Single field.
- EXPECT_EQ(fields.size(), 2) << proc_stat;
- } else if (fields[0] == "procs_blocked") {
- // Single field.
- EXPECT_EQ(fields.size(), 2) << proc_stat;
- } else if (fields[0] == "softirq") {
- // As of Linux 3.11, there are 10 softirqs. 12 fields for name + total.
- EXPECT_GE(fields.size(), 12) << proc_stat;
- }
-
- // All fields besides itime are valid base 10 numbers.
- for (size_t i = 1; i < fields.size(); i++) {
- uint64_t val;
- EXPECT_TRUE(absl::SimpleAtoi(fields[i], &val)) << proc_stat;
- }
- }
-}
-
-TEST(ProcLoadavg, EndsWithNewline) {
- std::string proc_loadvg =
- ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/loadavg"));
- EXPECT_EQ(proc_loadvg.back(), '\n');
-}
-
-TEST(ProcLoadavg, Fields) {
- std::string proc_loadvg =
- ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/loadavg"));
- std::vector<std::string> lines = absl::StrSplit(proc_loadvg, '\n');
-
- // Single line.
- EXPECT_EQ(lines.size(), 2) << proc_loadvg;
-
- std::vector<std::string> fields =
- absl::StrSplit(lines[0], absl::ByAnyChar(" /"), absl::SkipWhitespace());
-
- // Six fields.
- EXPECT_EQ(fields.size(), 6) << proc_loadvg;
-
- double val;
- uint64_t val2;
- // First three fields are floating point numbers.
- EXPECT_TRUE(absl::SimpleAtod(fields[0], &val)) << proc_loadvg;
- EXPECT_TRUE(absl::SimpleAtod(fields[1], &val)) << proc_loadvg;
- EXPECT_TRUE(absl::SimpleAtod(fields[2], &val)) << proc_loadvg;
- // Rest of the fields are valid base 10 numbers.
- EXPECT_TRUE(absl::SimpleAtoi(fields[3], &val2)) << proc_loadvg;
- EXPECT_TRUE(absl::SimpleAtoi(fields[4], &val2)) << proc_loadvg;
- EXPECT_TRUE(absl::SimpleAtoi(fields[5], &val2)) << proc_loadvg;
-}
-
-// NOTE: Tests in priority.cc also check certain priority related fields in
-// /proc/self/stat.
-
-class ProcPidStatTest : public ::testing::TestWithParam<std::string> {};
-
-TEST_P(ProcPidStatTest, HasBasicFields) {
- std::string proc_pid_stat = ASSERT_NO_ERRNO_AND_VALUE(
- GetContents(absl::StrCat("/proc/", GetParam(), "/stat")));
-
- ASSERT_FALSE(proc_pid_stat.empty());
- std::vector<std::string> fields = absl::StrSplit(proc_pid_stat, ' ');
- ASSERT_GE(fields.size(), 24);
- EXPECT_EQ(absl::StrCat(getpid()), fields[0]);
- // fields[1] is the thread name.
- EXPECT_EQ("R", fields[2]); // task state
- EXPECT_EQ(absl::StrCat(getppid()), fields[3]);
-
- // If the test starts up quickly, then the process start time and the kernel
- // boot time will be very close, and the proc starttime field (which is the
- // delta of the two times) will be 0. For that unfortunate reason, we can
- // only check that starttime >= 0, and not that it is strictly > 0.
- uint64_t starttime;
- ASSERT_TRUE(absl::SimpleAtoi(fields[21], &starttime));
- EXPECT_GE(starttime, 0);
-
- uint64_t vss;
- ASSERT_TRUE(absl::SimpleAtoi(fields[22], &vss));
- EXPECT_GT(vss, 0);
-
- uint64_t rss;
- ASSERT_TRUE(absl::SimpleAtoi(fields[23], &rss));
- EXPECT_GT(rss, 0);
-
- uint64_t rsslim;
- ASSERT_TRUE(absl::SimpleAtoi(fields[24], &rsslim));
- EXPECT_GT(rsslim, 0);
-}
-
-INSTANTIATE_TEST_SUITE_P(SelfAndNumericPid, ProcPidStatTest,
- ::testing::Values("self", absl::StrCat(getpid())));
-
-using ProcPidStatmTest = ::testing::TestWithParam<std::string>;
-
-TEST_P(ProcPidStatmTest, HasBasicFields) {
- std::string proc_pid_statm = ASSERT_NO_ERRNO_AND_VALUE(
- GetContents(absl::StrCat("/proc/", GetParam(), "/statm")));
- ASSERT_FALSE(proc_pid_statm.empty());
- std::vector<std::string> fields = absl::StrSplit(proc_pid_statm, ' ');
- ASSERT_GE(fields.size(), 7);
-
- uint64_t vss;
- ASSERT_TRUE(absl::SimpleAtoi(fields[0], &vss));
- EXPECT_GT(vss, 0);
-
- uint64_t rss;
- ASSERT_TRUE(absl::SimpleAtoi(fields[1], &rss));
- EXPECT_GT(rss, 0);
-}
-
-INSTANTIATE_TEST_SUITE_P(SelfAndNumericPid, ProcPidStatmTest,
- ::testing::Values("self", absl::StrCat(getpid())));
-
-PosixErrorOr<uint64_t> CurrentRSS() {
- ASSIGN_OR_RETURN_ERRNO(auto proc_self_stat, GetContents("/proc/self/stat"));
- if (proc_self_stat.empty()) {
- return PosixError(EINVAL, "empty /proc/self/stat");
- }
-
- std::vector<std::string> fields = absl::StrSplit(proc_self_stat, ' ');
- if (fields.size() < 24) {
- return PosixError(
- EINVAL,
- absl::StrCat("/proc/self/stat has too few fields: ", proc_self_stat));
- }
-
- uint64_t rss;
- if (!absl::SimpleAtoi(fields[23], &rss)) {
- return PosixError(
- EINVAL, absl::StrCat("/proc/self/stat RSS field is not a number: ",
- fields[23]));
- }
-
- // RSS is given in number of pages.
- return rss * kPageSize;
-}
-
-// The size of mapping created by MapPopulateRSS.
-constexpr uint64_t kMappingSize = 100 << 20;
-
-// Tolerance on RSS comparisons to account for background thread mappings,
-// reclaimed pages, newly faulted pages, etc.
-constexpr uint64_t kRSSTolerance = 5 << 20;
-
-// Capture RSS before and after an anonymous mapping with passed prot.
-void MapPopulateRSS(int prot, uint64_t* before, uint64_t* after) {
- *before = ASSERT_NO_ERRNO_AND_VALUE(CurrentRSS());
-
- // N.B. The kernel asynchronously accumulates per-task RSS counters into the
- // mm RSS, which is exposed by /proc/PID/stat. Task exit is a synchronization
- // point (kernel/exit.c:do_exit -> sync_mm_rss), so perform the mapping on
- // another thread to ensure it is reflected in RSS after the thread exits.
- Mapping mapping;
- ScopedThread t([&mapping, prot] {
- mapping = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(kMappingSize, prot, MAP_PRIVATE | MAP_POPULATE));
- });
- t.Join();
-
- *after = ASSERT_NO_ERRNO_AND_VALUE(CurrentRSS());
-}
-
-// TODO(b/73896574): Test for PROT_READ + MAP_POPULATE anonymous mappings. Their
-// semantics are more subtle:
-//
-// Small pages -> Zero page mapped, not counted in RSS
-// (mm/memory.c:do_anonymous_page).
-//
-// Huge pages (THP enabled, use_zero_page=0) -> Pages committed
-// (mm/memory.c:__handle_mm_fault -> create_huge_pmd).
-//
-// Huge pages (THP enabled, use_zero_page=1) -> Zero page mapped, not counted in
-// RSS (mm/huge_memory.c:do_huge_pmd_anonymous_page).
-
-// PROT_WRITE + MAP_POPULATE anonymous mappings are always committed.
-TEST(ProcSelfStat, PopulateWriteRSS) {
- uint64_t before, after;
- MapPopulateRSS(PROT_READ | PROT_WRITE, &before, &after);
-
- // Mapping is committed.
- EXPECT_NEAR(before + kMappingSize, after, kRSSTolerance);
-}
-
-// PROT_NONE + MAP_POPULATE anonymous mappings are never committed.
-TEST(ProcSelfStat, PopulateNoneRSS) {
- uint64_t before, after;
- MapPopulateRSS(PROT_NONE, &before, &after);
-
- // Mapping not committed.
- EXPECT_NEAR(before, after, kRSSTolerance);
-}
-
-// Returns the calling thread's name.
-PosixErrorOr<std::string> ThreadName() {
- // "The buffer should allow space for up to 16 bytes; the returned std::string
- // will be null-terminated if it is shorter than that." - prctl(2). But we
- // always want the thread name to be null-terminated.
- char thread_name[17];
- int rc = prctl(PR_GET_NAME, thread_name, 0, 0, 0);
- MaybeSave();
- if (rc < 0) {
- return PosixError(errno, "prctl(PR_GET_NAME)");
- }
- thread_name[16] = '\0';
- return std::string(thread_name);
-}
-
-// Parses the contents of a /proc/[pid]/status file into a collection of
-// key-value pairs.
-PosixErrorOr<std::map<std::string, std::string>> ParseProcStatus(
- absl::string_view status_str) {
- std::map<std::string, std::string> fields;
- for (absl::string_view const line :
- absl::StrSplit(status_str, '\n', absl::SkipWhitespace())) {
- const std::pair<absl::string_view, absl::string_view> kv =
- absl::StrSplit(line, absl::MaxSplits(":\t", 1));
- if (kv.first.empty()) {
- return PosixError(
- EINVAL, absl::StrCat("failed to parse key in line \"", line, "\""));
- }
- std::string key(kv.first);
- if (fields.count(key)) {
- return PosixError(EINVAL,
- absl::StrCat("duplicate key \"", kv.first, "\""));
- }
- std::string value(kv.second);
- absl::StripLeadingAsciiWhitespace(&value);
- fields.emplace(std::move(key), std::move(value));
- }
- return fields;
-}
-
-TEST(ParseProcStatusTest, ParsesSimpleStatusFileWithMixedWhitespaceCorrectly) {
- EXPECT_THAT(
- ParseProcStatus(
- "Name:\tinit\nState:\tS (sleeping)\nCapEff:\t 0000001fffffffff\n"),
- IsPosixErrorOkAndHolds(UnorderedElementsAre(
- Pair("Name", "init"), Pair("State", "S (sleeping)"),
- Pair("CapEff", "0000001fffffffff"))));
-}
-
-TEST(ParseProcStatusTest, DetectsDuplicateKeys) {
- auto proc_status_or = ParseProcStatus("Name:\tfoo\nName:\tfoo\n");
- EXPECT_THAT(proc_status_or,
- PosixErrorIs(EINVAL, ::testing::StrEq("duplicate key \"Name\"")));
-}
-
-TEST(ParseProcStatusTest, DetectsMissingTabs) {
- EXPECT_THAT(ParseProcStatus("Name:foo\nPid: 1\n"),
- IsPosixErrorOkAndHolds(UnorderedElementsAre(Pair("Name:foo", ""),
- Pair("Pid: 1", ""))));
-}
-
-TEST(ProcPidStatusTest, HasBasicFields) {
- // Do this on a separate thread since we want tgid != tid.
- ScopedThread([] {
- const pid_t tgid = getpid();
- const pid_t tid = syscall(SYS_gettid);
- EXPECT_NE(tgid, tid);
- const auto thread_name = ASSERT_NO_ERRNO_AND_VALUE(ThreadName());
-
- std::string status_str = ASSERT_NO_ERRNO_AND_VALUE(
- GetContents(absl::StrCat("/proc/", tid, "/status")));
-
- ASSERT_FALSE(status_str.empty());
- const auto status = ASSERT_NO_ERRNO_AND_VALUE(ParseProcStatus(status_str));
- EXPECT_THAT(status, IsSupersetOf({Pair("Name", thread_name),
- Pair("Tgid", absl::StrCat(tgid)),
- Pair("Pid", absl::StrCat(tid)),
- Pair("PPid", absl::StrCat(getppid()))}));
- });
-}
-
-TEST(ProcPidStatusTest, StateRunning) {
- // Task must be running when reading the file.
- const pid_t tid = syscall(SYS_gettid);
- std::string status_str = ASSERT_NO_ERRNO_AND_VALUE(
- GetContents(absl::StrCat("/proc/", tid, "/status")));
-
- EXPECT_THAT(ParseProcStatus(status_str),
- IsPosixErrorOkAndHolds(Contains(Pair("State", "R (running)"))));
-}
-
-TEST(ProcPidStatusTest, StateSleeping_NoRandomSave) {
- // Starts a child process that blocks and checks that State is sleeping.
- auto res = WithSubprocess(
- [&](int pid) -> PosixError {
- // Because this test is timing based we will disable cooperative saving
- // and the test itself also has random saving disabled.
- const DisableSave ds;
- // Try multiple times in case the child isn't sleeping when status file
- // is read.
- MonotonicTimer timer;
- timer.Start();
- for (;;) {
- ASSIGN_OR_RETURN_ERRNO(
- std::string status_str,
- GetContents(absl::StrCat("/proc/", pid, "/status")));
- ASSIGN_OR_RETURN_ERRNO(auto map, ParseProcStatus(status_str));
- if (map["State"] == std::string("S (sleeping)")) {
- // Test passed!
- return NoError();
- }
- if (timer.Duration() > absl::Seconds(10)) {
- return PosixError(ETIMEDOUT, "Timeout waiting for child to sleep");
- }
- absl::SleepFor(absl::Milliseconds(10));
- }
- },
- nullptr, nullptr);
- ASSERT_NO_ERRNO(res);
-}
-
-TEST(ProcPidStatusTest, ValuesAreTabDelimited) {
- std::string status_str =
- ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/status"));
- ASSERT_FALSE(status_str.empty());
- for (absl::string_view const line :
- absl::StrSplit(status_str, '\n', absl::SkipWhitespace())) {
- EXPECT_NE(std::string::npos, line.find(":\t"));
- }
-}
-
-// Threads properly counts running threads.
-//
-// TODO(mpratt): Test zombied threads while the thread group leader is still
-// running with generalized fork and clone children from the wait test.
-TEST(ProcPidStatusTest, Threads) {
- char buf[4096] = {};
- EXPECT_THAT(ReadWhileRunning("status", buf, sizeof(buf) - 1),
- SyscallSucceedsWithValue(Gt(0)));
-
- auto status = ASSERT_NO_ERRNO_AND_VALUE(ParseProcStatus(buf));
- auto it = status.find("Threads");
- ASSERT_NE(it, status.end());
- int threads = -1;
- EXPECT_TRUE(absl::SimpleAtoi(it->second, &threads))
- << "Threads value " << it->second << " is not a number";
- // Don't make assumptions about the exact number of threads, as it may not be
- // constant.
- EXPECT_GE(threads, 1);
-
- memset(buf, 0, sizeof(buf));
- EXPECT_THAT(ReadWhileZombied("status", buf, sizeof(buf) - 1),
- SyscallSucceedsWithValue(Gt(0)));
-
- status = ASSERT_NO_ERRNO_AND_VALUE(ParseProcStatus(buf));
- it = status.find("Threads");
- ASSERT_NE(it, status.end());
- threads = -1;
- EXPECT_TRUE(absl::SimpleAtoi(it->second, &threads))
- << "Threads value " << it->second << " is not a number";
- // There must be only the thread group leader remaining, zombied.
- EXPECT_EQ(threads, 1);
-}
-
-// Returns true if all characters in s are digits.
-bool IsDigits(absl::string_view s) {
- return std::all_of(s.begin(), s.end(), absl::ascii_isdigit);
-}
-
-TEST(ProcPidStatTest, VmStats) {
- std::string status_str =
- ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/status"));
- ASSERT_FALSE(status_str.empty());
- auto status = ASSERT_NO_ERRNO_AND_VALUE(ParseProcStatus(status_str));
-
- const auto vss_it = status.find("VmSize");
- ASSERT_NE(vss_it, status.end());
-
- absl::string_view vss_str(vss_it->second);
-
- // Room for the " kB" suffix plus at least one digit.
- ASSERT_GT(vss_str.length(), 3);
- EXPECT_TRUE(absl::EndsWith(vss_str, " kB"));
- // Everything else is part of a number.
- EXPECT_TRUE(IsDigits(vss_str.substr(0, vss_str.length() - 3))) << vss_str;
- // ... which is not 0.
- EXPECT_NE('0', vss_str[0]);
-
- const auto rss_it = status.find("VmRSS");
- ASSERT_NE(rss_it, status.end());
-
- absl::string_view rss_str(rss_it->second);
-
- // Room for the " kB" suffix plus at least one digit.
- ASSERT_GT(rss_str.length(), 3);
- EXPECT_TRUE(absl::EndsWith(rss_str, " kB"));
- // Everything else is part of a number.
- EXPECT_TRUE(IsDigits(rss_str.substr(0, rss_str.length() - 3))) << rss_str;
- // ... which is not 0.
- EXPECT_NE('0', rss_str[0]);
-
- const auto data_it = status.find("VmData");
- ASSERT_NE(data_it, status.end());
-
- absl::string_view data_str(data_it->second);
-
- // Room for the " kB" suffix plus at least one digit.
- ASSERT_GT(data_str.length(), 3);
- EXPECT_TRUE(absl::EndsWith(data_str, " kB"));
- // Everything else is part of a number.
- EXPECT_TRUE(IsDigits(data_str.substr(0, data_str.length() - 3))) << data_str;
- // ... which is not 0.
- EXPECT_NE('0', data_str[0]);
-}
-
-// Parse an array of NUL-terminated char* arrays, returning a vector of
-// strings.
-std::vector<std::string> ParseNulTerminatedStrings(std::string contents) {
- EXPECT_EQ('\0', contents.back());
- // The split will leave an empty string if the NUL-byte remains, so pop
- // it.
- contents.pop_back();
-
- return absl::StrSplit(contents, '\0');
-}
-
-TEST(ProcPidCmdline, MatchesArgv) {
- std::vector<std::string> proc_cmdline = ParseNulTerminatedStrings(
- ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/cmdline")));
- EXPECT_THAT(saved_argv, ContainerEq(proc_cmdline));
-}
-
-TEST(ProcPidEnviron, MatchesEnviron) {
- std::vector<std::string> proc_environ = ParseNulTerminatedStrings(
- ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/environ")));
- // Get the environment from the environ variable, which we will compare with
- // /proc/self/environ.
- std::vector<std::string> env;
- for (char** v = environ; *v; v++) {
- env.push_back(*v);
- }
- EXPECT_THAT(env, ContainerEq(proc_environ));
-}
-
-TEST(ProcPidCmdline, SubprocessForkSameCmdline) {
- std::vector<std::string> proc_cmdline_parent;
- std::vector<std::string> proc_cmdline;
- proc_cmdline_parent = ParseNulTerminatedStrings(
- ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/cmdline")));
- auto res = WithSubprocess(
- [&](int pid) -> PosixError {
- ASSIGN_OR_RETURN_ERRNO(
- auto raw_cmdline,
- GetContents(absl::StrCat("/proc/", pid, "/cmdline")));
- proc_cmdline = ParseNulTerminatedStrings(raw_cmdline);
- return NoError();
- },
- nullptr, nullptr);
- ASSERT_NO_ERRNO(res);
-
- for (size_t i = 0; i < proc_cmdline_parent.size(); i++) {
- EXPECT_EQ(proc_cmdline_parent[i], proc_cmdline[i]);
- }
-}
-
-// Test whether /proc/PID/ symlinks can be read for a running process.
-TEST(ProcPidSymlink, SubprocessRunning) {
- char buf[1];
-
- EXPECT_THAT(ReadlinkWhileRunning("exe", buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- EXPECT_THAT(ReadlinkWhileRunning("ns/net", buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- EXPECT_THAT(ReadlinkWhileRunning("ns/pid", buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- EXPECT_THAT(ReadlinkWhileRunning("ns/user", buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-}
-
-// FIXME(gvisor.dev/issue/164): Inconsistent behavior between gVisor and linux
-// on proc files.
-TEST(ProcPidSymlink, SubprocessZombied) {
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
-
- char buf[1];
-
- int want = EACCES;
- if (!IsRunningOnGvisor()) {
- auto version = ASSERT_NO_ERRNO_AND_VALUE(GetKernelVersion());
- if (version.major == 4 && version.minor > 3) {
- want = ENOENT;
- }
- }
-
- EXPECT_THAT(ReadlinkWhileZombied("exe", buf, sizeof(buf)),
- SyscallFailsWithErrno(want));
-
- if (!IsRunningOnGvisor()) {
- EXPECT_THAT(ReadlinkWhileZombied("ns/net", buf, sizeof(buf)),
- SyscallFailsWithErrno(want));
- }
-
- // FIXME(gvisor.dev/issue/164): Inconsistent behavior between gVisor and linux
- // on proc files.
- // 4.17 & gVisor: Syscall succeeds and returns 1
- // EXPECT_THAT(ReadlinkWhileZombied("ns/pid", buf, sizeof(buf)),
- // SyscallFailsWithErrno(EACCES));
-
- // FIXME(gvisor.dev/issue/164): Inconsistent behavior between gVisor and linux
- // on proc files.
- // 4.17 & gVisor: Syscall succeeds and returns 1.
- // EXPECT_THAT(ReadlinkWhileZombied("ns/user", buf, sizeof(buf)),
- // SyscallFailsWithErrno(EACCES));
-}
-
-// Test whether /proc/PID/ symlinks can be read for an exited process.
-TEST(ProcPidSymlink, SubprocessExited) {
- // FIXME(gvisor.dev/issue/164): These all succeed on gVisor.
- SKIP_IF(IsRunningOnGvisor());
-
- char buf[1];
-
- EXPECT_THAT(ReadlinkWhileExited("exe", buf, sizeof(buf)),
- SyscallFailsWithErrno(ESRCH));
-
- EXPECT_THAT(ReadlinkWhileExited("ns/net", buf, sizeof(buf)),
- SyscallFailsWithErrno(ESRCH));
-
- EXPECT_THAT(ReadlinkWhileExited("ns/pid", buf, sizeof(buf)),
- SyscallFailsWithErrno(ESRCH));
-
- EXPECT_THAT(ReadlinkWhileExited("ns/user", buf, sizeof(buf)),
- SyscallFailsWithErrno(ESRCH));
-}
-
-// /proc/PID/exe points to the correct binary.
-TEST(ProcPidExe, Subprocess) {
- auto link = ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/self/exe"));
- auto expected_absolute_path =
- ASSERT_NO_ERRNO_AND_VALUE(MakeAbsolute(link, ""));
-
- char actual[PATH_MAX + 1] = {};
- ASSERT_THAT(ReadlinkWhileRunning("exe", actual, sizeof(actual)),
- SyscallSucceedsWithValue(Gt(0)));
- EXPECT_EQ(actual, expected_absolute_path);
-}
-
-// Test whether /proc/PID/ files can be read for a running process.
-TEST(ProcPidFile, SubprocessRunning) {
- char buf[1];
-
- EXPECT_THAT(ReadWhileRunning("auxv", buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- EXPECT_THAT(ReadWhileRunning("cmdline", buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- EXPECT_THAT(ReadWhileRunning("comm", buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- EXPECT_THAT(ReadWhileRunning("gid_map", buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- EXPECT_THAT(ReadWhileRunning("io", buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- EXPECT_THAT(ReadWhileRunning("maps", buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- EXPECT_THAT(ReadWhileRunning("stat", buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- EXPECT_THAT(ReadWhileRunning("status", buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- EXPECT_THAT(ReadWhileRunning("uid_map", buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-}
-
-// Test whether /proc/PID/ files can be read for a zombie process.
-TEST(ProcPidFile, SubprocessZombie) {
- char buf[1];
-
- // 4.17: Succeeds and returns 1
- // gVisor: Succeeds and returns 0
- EXPECT_THAT(ReadWhileZombied("auxv", buf, sizeof(buf)), SyscallSucceeds());
-
- EXPECT_THAT(ReadWhileZombied("cmdline", buf, sizeof(buf)),
- SyscallSucceedsWithValue(0));
-
- EXPECT_THAT(ReadWhileZombied("comm", buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- EXPECT_THAT(ReadWhileZombied("gid_map", buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- EXPECT_THAT(ReadWhileZombied("maps", buf, sizeof(buf)),
- SyscallSucceedsWithValue(0));
-
- EXPECT_THAT(ReadWhileZombied("stat", buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- EXPECT_THAT(ReadWhileZombied("status", buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- EXPECT_THAT(ReadWhileZombied("uid_map", buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- // FIXME(gvisor.dev/issue/164): Inconsistent behavior between gVisor and linux
- // on proc files.
- // gVisor & 4.17: Succeeds and returns 1.
- // EXPECT_THAT(ReadWhileZombied("io", buf, sizeof(buf)),
- // SyscallFailsWithErrno(EACCES));
-}
-
-// Test whether /proc/PID/ files can be read for an exited process.
-TEST(ProcPidFile, SubprocessExited) {
- char buf[1];
-
- // FIXME(gvisor.dev/issue/164): Inconsistent behavior between kernels
- // gVisor: Fails with ESRCH.
- // 4.17: Succeeds and returns 1.
- // EXPECT_THAT(ReadWhileExited("auxv", buf, sizeof(buf)),
- // SyscallFailsWithErrno(ESRCH));
-
- EXPECT_THAT(ReadWhileExited("cmdline", buf, sizeof(buf)),
- SyscallFailsWithErrno(ESRCH));
-
- if (!IsRunningOnGvisor()) {
- // FIXME(gvisor.dev/issue/164): Succeeds on gVisor.
- EXPECT_THAT(ReadWhileExited("comm", buf, sizeof(buf)),
- SyscallFailsWithErrno(ESRCH));
- }
-
- EXPECT_THAT(ReadWhileExited("gid_map", buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- if (!IsRunningOnGvisor()) {
- // FIXME(gvisor.dev/issue/164): Succeeds on gVisor.
- EXPECT_THAT(ReadWhileExited("io", buf, sizeof(buf)),
- SyscallFailsWithErrno(ESRCH));
- }
-
- if (!IsRunningOnGvisor()) {
- // FIXME(gvisor.dev/issue/164): Returns EOF on gVisor.
- EXPECT_THAT(ReadWhileExited("maps", buf, sizeof(buf)),
- SyscallFailsWithErrno(ESRCH));
- }
-
- if (!IsRunningOnGvisor()) {
- // FIXME(gvisor.dev/issue/164): Succeeds on gVisor.
- EXPECT_THAT(ReadWhileExited("stat", buf, sizeof(buf)),
- SyscallFailsWithErrno(ESRCH));
- }
-
- if (!IsRunningOnGvisor()) {
- // FIXME(gvisor.dev/issue/164): Succeeds on gVisor.
- EXPECT_THAT(ReadWhileExited("status", buf, sizeof(buf)),
- SyscallFailsWithErrno(ESRCH));
- }
-
- EXPECT_THAT(ReadWhileExited("uid_map", buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-}
-
-PosixError DirContainsImpl(absl::string_view path,
- const std::vector<std::string>& targets,
- bool strict) {
- ASSIGN_OR_RETURN_ERRNO(auto listing, ListDir(path, false));
- bool success = true;
-
- for (auto& expected_entry : targets) {
- auto cursor = std::find(listing.begin(), listing.end(), expected_entry);
- if (cursor == listing.end()) {
- success = false;
- }
- }
-
- if (!success) {
- return PosixError(
- ENOENT,
- absl::StrCat("Failed to find one or more paths in '", path, "'"));
- }
-
- if (strict) {
- if (targets.size() != listing.size()) {
- return PosixError(
- EINVAL,
- absl::StrCat("Expected to find ", targets.size(), " elements in '",
- path, "', but found ", listing.size()));
- }
- }
-
- return NoError();
-}
-
-PosixError DirContains(absl::string_view path,
- const std::vector<std::string>& targets) {
- return DirContainsImpl(path, targets, false);
-}
-
-PosixError DirContainsExactly(absl::string_view path,
- const std::vector<std::string>& targets) {
- return DirContainsImpl(path, targets, true);
-}
-
-PosixError EventuallyDirContainsExactly(
- absl::string_view path, const std::vector<std::string>& targets) {
- constexpr int kRetryCount = 100;
- const absl::Duration kRetryDelay = absl::Milliseconds(100);
-
- for (int i = 0; i < kRetryCount; ++i) {
- auto res = DirContainsExactly(path, targets);
- if (res.ok()) {
- return res;
- } else if (i < kRetryCount - 1) {
- // Sleep if this isn't the final iteration.
- absl::SleepFor(kRetryDelay);
- }
- }
- return PosixError(ETIMEDOUT,
- "Timed out while waiting for directory to contain files ");
-}
-
-TEST(ProcTask, Basic) {
- EXPECT_NO_ERRNO(
- DirContains("/proc/self/task", {".", "..", absl::StrCat(getpid())}));
-}
-
-std::vector<std::string> TaskFiles(
- const std::vector<std::string>& initial_contents,
- const std::vector<pid_t>& pids) {
- return VecCat<std::string>(
- initial_contents,
- ApplyVec<std::string>([](const pid_t p) { return absl::StrCat(p); },
- pids));
-}
-
-std::vector<std::string> TaskFiles(const std::vector<pid_t>& pids) {
- return TaskFiles({".", "..", absl::StrCat(getpid())}, pids);
-}
-
-// Helper class for creating a new task in the current thread group.
-class BlockingChild {
- public:
- BlockingChild() : thread_([=] { Start(); }) {}
- ~BlockingChild() { Join(); }
-
- pid_t Tid() const {
- absl::MutexLock ml(&mu_);
- mu_.Await(absl::Condition(&tid_ready_));
- return tid_;
- }
-
- void Join() { Stop(); }
-
- private:
- void Start() {
- absl::MutexLock ml(&mu_);
- tid_ = syscall(__NR_gettid);
- tid_ready_ = true;
- mu_.Await(absl::Condition(&stop_));
- }
-
- void Stop() {
- absl::MutexLock ml(&mu_);
- stop_ = true;
- }
-
- mutable absl::Mutex mu_;
- bool stop_ ABSL_GUARDED_BY(mu_) = false;
- pid_t tid_;
- bool tid_ready_ ABSL_GUARDED_BY(mu_) = false;
-
- // Must be last to ensure that the destructor for the thread is run before
- // any other member of the object is destroyed.
- ScopedThread thread_;
-};
-
-TEST(ProcTask, NewThreadAppears) {
- auto initial = ASSERT_NO_ERRNO_AND_VALUE(ListDir("/proc/self/task", false));
- BlockingChild child1;
- EXPECT_NO_ERRNO(DirContainsExactly("/proc/self/task",
- TaskFiles(initial, {child1.Tid()})));
-}
-
-TEST(ProcTask, KilledThreadsDisappear) {
- auto initial = ASSERT_NO_ERRNO_AND_VALUE(ListDir("/proc/self/task/", false));
-
- BlockingChild child1;
- EXPECT_NO_ERRNO(DirContainsExactly("/proc/self/task",
- TaskFiles(initial, {child1.Tid()})));
-
- // Stat child1's task file.
- struct stat statbuf;
- const std::string child1_task_file =
- absl::StrCat("/proc/self/task/", child1.Tid());
- EXPECT_THAT(stat(child1_task_file.c_str(), &statbuf), SyscallSucceeds());
-
- BlockingChild child2;
- EXPECT_NO_ERRNO(DirContainsExactly(
- "/proc/self/task", TaskFiles(initial, {child1.Tid(), child2.Tid()})));
-
- BlockingChild child3;
- BlockingChild child4;
- BlockingChild child5;
- EXPECT_NO_ERRNO(DirContainsExactly(
- "/proc/self/task",
- TaskFiles(initial, {child1.Tid(), child2.Tid(), child3.Tid(),
- child4.Tid(), child5.Tid()})));
-
- child2.Join();
- EXPECT_NO_ERRNO(EventuallyDirContainsExactly(
- "/proc/self/task", TaskFiles(initial, {child1.Tid(), child3.Tid(),
- child4.Tid(), child5.Tid()})));
-
- child1.Join();
- child4.Join();
- EXPECT_NO_ERRNO(EventuallyDirContainsExactly(
- "/proc/self/task", TaskFiles(initial, {child3.Tid(), child5.Tid()})));
-
- // Stat child1's task file again. This time it should fail.
- EXPECT_THAT(stat(child1_task_file.c_str(), &statbuf),
- SyscallFailsWithErrno(ENOENT));
-
- child3.Join();
- child5.Join();
- EXPECT_NO_ERRNO(EventuallyDirContainsExactly("/proc/self/task", initial));
-}
-
-TEST(ProcTask, ChildTaskDir) {
- BlockingChild child1;
- EXPECT_NO_ERRNO(DirContains("/proc/self/task", TaskFiles({child1.Tid()})));
- EXPECT_NO_ERRNO(DirContains(absl::StrCat("/proc/", child1.Tid(), "/task"),
- TaskFiles({child1.Tid()})));
-}
-
-PosixError VerifyPidDir(std::string path) {
- return DirContains(path, {"exe", "fd", "io", "maps", "ns", "stat", "status"});
-}
-
-TEST(ProcTask, VerifyTaskDir) {
- EXPECT_NO_ERRNO(VerifyPidDir("/proc/self"));
-
- EXPECT_NO_ERRNO(VerifyPidDir(absl::StrCat("/proc/self/task/", getpid())));
- BlockingChild child1;
- EXPECT_NO_ERRNO(VerifyPidDir(absl::StrCat("/proc/self/task/", child1.Tid())));
-
- // Only the first level of task directories should contain the 'task'
- // directory. That is:
- //
- // /proc/1234/task <- should exist
- // /proc/1234/task/1234/task <- should not exist
- // /proc/1234/task/1235/task <- should not exist (where 1235 is in the same
- // thread group as 1234).
- EXPECT_FALSE(
- DirContains(absl::StrCat("/proc/self/task/", getpid()), {"task"}).ok())
- << "Found 'task' directory in an inner directory.";
-}
-
-TEST(ProcTask, TaskDirCannotBeDeleted) {
- // Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
-
- EXPECT_THAT(rmdir("/proc/self/task"), SyscallFails());
- EXPECT_THAT(rmdir(absl::StrCat("/proc/self/task/", getpid()).c_str()),
- SyscallFailsWithErrno(EACCES));
-}
-
-TEST(ProcTask, TaskDirHasCorrectMetadata) {
- struct stat st;
- EXPECT_THAT(stat("/proc/self/task", &st), SyscallSucceeds());
- EXPECT_TRUE(S_ISDIR(st.st_mode));
-
- // Verify file is readable and executable by everyone.
- mode_t expected_permissions =
- S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
- mode_t permissions = st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO);
- EXPECT_EQ(expected_permissions, permissions);
-}
-
-TEST(ProcTask, TaskDirCanSeekToEnd) {
- const FileDescriptor dirfd =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/task", O_RDONLY));
- EXPECT_THAT(lseek(dirfd.get(), 0, SEEK_END), SyscallSucceeds());
-}
-
-TEST(ProcTask, VerifyTaskDirNlinks) {
- // A task directory will have 3 links if the taskgroup has a single
- // thread. For example, the following shows where the links to
- // '/proc/12345/task comes' from for a single threaded process with pid 12345:
- //
- // /proc/12345/task <-- 1 link for the directory itself
- // . <-- link from "."
- // ..
- // 12345
- // .
- // .. <-- link from ".." to parent.
- // <other contents of a task dir>
- //
- // We can't assert an absolute number of links since we don't control how many
- // threads the test framework spawns. Instead, we'll ensure creating a new
- // thread increases the number of links as expected.
-
- // Once we reach the test body, we can count on the thread count being stable
- // unless we spawn a new one.
- uint64_t initial_links = ASSERT_NO_ERRNO_AND_VALUE(Links("/proc/self/task"));
- ASSERT_GE(initial_links, 3);
-
- // For each new subtask, we should gain a new link.
- BlockingChild child1;
- EXPECT_THAT(Links("/proc/self/task"),
- IsPosixErrorOkAndHolds(initial_links + 1));
- BlockingChild child2;
- EXPECT_THAT(Links("/proc/self/task"),
- IsPosixErrorOkAndHolds(initial_links + 2));
-}
-
-TEST(ProcTask, CommContainsThreadNameAndTrailingNewline) {
- constexpr char kThreadName[] = "TestThread12345";
- ASSERT_THAT(prctl(PR_SET_NAME, kThreadName), SyscallSucceeds());
-
- auto thread_name = ASSERT_NO_ERRNO_AND_VALUE(
- GetContents(JoinPath("/proc", absl::StrCat(getpid()), "task",
- absl::StrCat(syscall(SYS_gettid)), "comm")));
- EXPECT_EQ(absl::StrCat(kThreadName, "\n"), thread_name);
-}
-
-TEST(ProcTaskNs, NsDirExistsAndHasCorrectMetadata) {
- EXPECT_NO_ERRNO(DirContains("/proc/self/ns", {"net", "pid", "user"}));
-
- // Let's just test the 'pid' entry, all of them are very similar.
- struct stat st;
- EXPECT_THAT(lstat("/proc/self/ns/pid", &st), SyscallSucceeds());
- EXPECT_TRUE(S_ISLNK(st.st_mode));
-
- auto link = ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/self/ns/pid"));
- EXPECT_THAT(link, ::testing::StartsWith("pid:["));
-}
-
-TEST(ProcTaskNs, AccessOnNsNodeSucceeds) {
- EXPECT_THAT(access("/proc/self/ns/pid", F_OK), SyscallSucceeds());
-}
-
-TEST(ProcSysKernelHostname, Exists) {
- EXPECT_THAT(open("/proc/sys/kernel/hostname", O_RDONLY), SyscallSucceeds());
-}
-
-TEST(ProcSysKernelHostname, MatchesUname) {
- struct utsname buf;
- EXPECT_THAT(uname(&buf), SyscallSucceeds());
- const std::string hostname = absl::StrCat(buf.nodename, "\n");
- auto procfs_hostname =
- ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/kernel/hostname"));
- EXPECT_EQ(procfs_hostname, hostname);
-}
-
-TEST(ProcSysVmMmapMinAddr, HasNumericValue) {
- const std::string mmap_min_addr_str =
- ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/vm/mmap_min_addr"));
- uintptr_t mmap_min_addr;
- EXPECT_TRUE(absl::SimpleAtoi(mmap_min_addr_str, &mmap_min_addr))
- << "/proc/sys/vm/mmap_min_addr does not contain a numeric value: "
- << mmap_min_addr_str;
-}
-
-TEST(ProcSysVmOvercommitMemory, HasNumericValue) {
- const std::string overcommit_memory_str =
- ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/vm/overcommit_memory"));
- uintptr_t overcommit_memory;
- EXPECT_TRUE(absl::SimpleAtoi(overcommit_memory_str, &overcommit_memory))
- << "/proc/sys/vm/overcommit_memory does not contain a numeric value: "
- << overcommit_memory;
-}
-
-// Check that link for proc fd entries point the target node, not the
-// symlink itself.
-TEST(ProcTaskFd, FstatatFollowsSymlink) {
- const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY));
-
- struct stat sproc = {};
- EXPECT_THAT(
- fstatat(-1, absl::StrCat("/proc/self/fd/", fd.get()).c_str(), &sproc, 0),
- SyscallSucceeds());
-
- struct stat sfile = {};
- EXPECT_THAT(fstatat(-1, file.path().c_str(), &sfile, 0), SyscallSucceeds());
-
- // If fstatat follows the fd symlink, the device and inode numbers should
- // match at a minimum.
- EXPECT_EQ(sproc.st_dev, sfile.st_dev);
- EXPECT_EQ(sproc.st_ino, sfile.st_ino);
- EXPECT_EQ(0, memcmp(&sfile, &sproc, sizeof(sfile)));
-}
-
-TEST(ProcFilesystems, Bug65172365) {
- std::string proc_filesystems =
- ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/filesystems"));
- ASSERT_FALSE(proc_filesystems.empty());
-}
-
-TEST(ProcFilesystems, PresenceOfShmMaxMniAll) {
- uint64_t shmmax = 0;
- uint64_t shmall = 0;
- uint64_t shmmni = 0;
- std::string proc_file;
- proc_file = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/kernel/shmmax"));
- ASSERT_FALSE(proc_file.empty());
- ASSERT_TRUE(absl::SimpleAtoi(proc_file, &shmmax));
- proc_file = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/kernel/shmall"));
- ASSERT_FALSE(proc_file.empty());
- ASSERT_TRUE(absl::SimpleAtoi(proc_file, &shmall));
- proc_file = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/kernel/shmmni"));
- ASSERT_FALSE(proc_file.empty());
- ASSERT_TRUE(absl::SimpleAtoi(proc_file, &shmmni));
-
- ASSERT_GT(shmmax, 0);
- ASSERT_GT(shmall, 0);
- ASSERT_GT(shmmni, 0);
- ASSERT_LE(shmall, shmmax);
-
- // These values should never be higher than this by default, for more
- // information see uapi/linux/shm.h
- ASSERT_LE(shmmax, ULONG_MAX - (1UL << 24));
- ASSERT_LE(shmall, ULONG_MAX - (1UL << 24));
-}
-
-// Check that /proc/mounts is a symlink to self/mounts.
-TEST(ProcMounts, IsSymlink) {
- auto link = ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/mounts"));
- EXPECT_EQ(link, "self/mounts");
-}
-
-// Check that /proc/self/mounts looks something like a real mounts file.
-TEST(ProcSelfMounts, RequiredFieldsArePresent) {
- auto mounts = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/mounts"));
- EXPECT_THAT(mounts,
- AllOf(
- // Root mount.
- ContainsRegex(R"(\S+ / \S+ (rw|ro)\S* [0-9]+ [0-9]+\s)"),
- // Root mount.
- ContainsRegex(R"(\S+ /proc \S+ rw\S* [0-9]+ [0-9]+\s)")));
-}
-
-void CheckDuplicatesRecursively(std::string path) {
- errno = 0;
- DIR* dir = opendir(path.c_str());
- if (dir == nullptr) {
- // Ignore any directories we can't read or missing directories as the
- // directory could have been deleted/mutated from the time the parent
- // directory contents were read.
- return;
- }
- auto dir_closer = Cleanup([&dir]() { closedir(dir); });
- std::unordered_set<std::string> children;
- while (true) {
- // Readdir(3): If the end of the directory stream is reached, NULL is
- // returned and errno is not changed. If an error occurs, NULL is returned
- // and errno is set appropriately. To distinguish end of stream and from an
- // error, set errno to zero before calling readdir() and then check the
- // value of errno if NULL is returned.
- errno = 0;
- struct dirent* dp = readdir(dir);
- if (dp == nullptr) {
- ASSERT_EQ(errno, 0) << path;
- break; // We're done.
- }
-
- if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) {
- continue;
- }
-
- ASSERT_EQ(children.find(std::string(dp->d_name)), children.end())
- << dp->d_name;
- children.insert(std::string(dp->d_name));
-
- ASSERT_NE(dp->d_type, DT_UNKNOWN);
-
- if (dp->d_type != DT_DIR) {
- continue;
- }
- CheckDuplicatesRecursively(absl::StrCat(path, "/", dp->d_name));
- }
-}
-
-TEST(Proc, NoDuplicates) { CheckDuplicatesRecursively("/proc"); }
-
-// Most /proc/PID files are owned by the task user with SUID_DUMP_USER.
-TEST(ProcPid, UserDumpableOwner) {
- int before;
- ASSERT_THAT(before = prctl(PR_GET_DUMPABLE), SyscallSucceeds());
- auto cleanup = Cleanup([before] {
- ASSERT_THAT(prctl(PR_SET_DUMPABLE, before), SyscallSucceeds());
- });
-
- EXPECT_THAT(prctl(PR_SET_DUMPABLE, SUID_DUMP_USER), SyscallSucceeds());
-
- // This applies to the task directory itself and files inside.
- struct stat st;
- ASSERT_THAT(stat("/proc/self/", &st), SyscallSucceeds());
- EXPECT_EQ(st.st_uid, geteuid());
- EXPECT_EQ(st.st_gid, getegid());
-
- ASSERT_THAT(stat("/proc/self/stat", &st), SyscallSucceeds());
- EXPECT_EQ(st.st_uid, geteuid());
- EXPECT_EQ(st.st_gid, getegid());
-}
-
-// /proc/PID files are owned by root with SUID_DUMP_DISABLE.
-TEST(ProcPid, RootDumpableOwner) {
- int before;
- ASSERT_THAT(before = prctl(PR_GET_DUMPABLE), SyscallSucceeds());
- auto cleanup = Cleanup([before] {
- ASSERT_THAT(prctl(PR_SET_DUMPABLE, before), SyscallSucceeds());
- });
-
- EXPECT_THAT(prctl(PR_SET_DUMPABLE, SUID_DUMP_DISABLE), SyscallSucceeds());
-
- // This *does not* applies to the task directory itself (or other 0555
- // directories), but does to files inside.
- struct stat st;
- ASSERT_THAT(stat("/proc/self/", &st), SyscallSucceeds());
- EXPECT_EQ(st.st_uid, geteuid());
- EXPECT_EQ(st.st_gid, getegid());
-
- // This file is owned by root. Also allow nobody in case this test is running
- // in a userns without root mapped.
- ASSERT_THAT(stat("/proc/self/stat", &st), SyscallSucceeds());
- EXPECT_THAT(st.st_uid, AnyOf(Eq(0), Eq(65534)));
- EXPECT_THAT(st.st_gid, AnyOf(Eq(0), Eq(65534)));
-}
-
-TEST(Proc, GetdentsEnoent) {
- FileDescriptor fd;
- ASSERT_NO_ERRNO(WithSubprocess(
- [&](int pid) -> PosixError {
- // Running.
- ASSIGN_OR_RETURN_ERRNO(fd, Open(absl::StrCat("/proc/", pid, "/task"),
- O_RDONLY | O_DIRECTORY));
-
- return NoError();
- },
- nullptr, nullptr));
- char buf[1024];
- ASSERT_THAT(syscall(SYS_getdents, fd.get(), buf, sizeof(buf)),
- SyscallFailsWithErrno(ENOENT));
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
-
-int main(int argc, char** argv) {
- for (int i = 0; i < argc; ++i) {
- gvisor::testing::saved_argv.emplace_back(std::string(argv[i]));
- }
-
- gvisor::testing::TestInit(&argc, &argv);
- return RUN_ALL_TESTS();
-}
diff --git a/test/syscalls/linux/proc_net.cc b/test/syscalls/linux/proc_net.cc
deleted file mode 100644
index efdaf202b..000000000
--- a/test/syscalls/linux/proc_net.cc
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/util/capability_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-TEST(ProcNetIfInet6, Format) {
- auto ifinet6 = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/if_inet6"));
- EXPECT_THAT(ifinet6,
- ::testing::MatchesRegex(
- // Ex: "00000000000000000000000000000001 01 80 10 80 lo\n"
- "^([a-f0-9]{32}( [a-f0-9]{2}){4} +[a-z][a-z0-9]*\n)+$"));
-}
-
-TEST(ProcSysNetIpv4Sack, Exists) {
- EXPECT_THAT(open("/proc/sys/net/ipv4/tcp_sack", O_RDONLY), SyscallSucceeds());
-}
-
-TEST(ProcSysNetIpv4Sack, CanReadAndWrite) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_DAC_OVERRIDE))));
-
- auto const fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/sys/net/ipv4/tcp_sack", O_RDWR));
-
- char buf;
- EXPECT_THAT(PreadFd(fd.get(), &buf, sizeof(buf), 0),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- EXPECT_TRUE(buf == '0' || buf == '1') << "unexpected tcp_sack: " << buf;
-
- char to_write = (buf == '1') ? '0' : '1';
- EXPECT_THAT(PwriteFd(fd.get(), &to_write, sizeof(to_write), 0),
- SyscallSucceedsWithValue(sizeof(to_write)));
-
- buf = 0;
- EXPECT_THAT(PreadFd(fd.get(), &buf, sizeof(buf), 0),
- SyscallSucceedsWithValue(sizeof(buf)));
- EXPECT_EQ(buf, to_write);
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/proc_net_tcp.cc b/test/syscalls/linux/proc_net_tcp.cc
deleted file mode 100644
index f6d7ad0bb..000000000
--- a/test/syscalls/linux/proc_net_tcp.cc
+++ /dev/null
@@ -1,255 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "absl/strings/numbers.h"
-#include "absl/strings/str_join.h"
-#include "absl/strings/str_split.h"
-#include "test/syscalls/linux/ip_socket_test_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-using absl::StrCat;
-using absl::StrSplit;
-
-constexpr char kProcNetTCPHeader[] =
- " sl local_address rem_address st tx_queue rx_queue tr tm->when "
- "retrnsmt uid timeout inode "
- " ";
-
-// TCPEntry represents a single entry from /proc/net/tcp.
-struct TCPEntry {
- uint32_t local_addr;
- uint16_t local_port;
-
- uint32_t remote_addr;
- uint16_t remote_port;
-
- uint64_t state;
- uint64_t uid;
- uint64_t inode;
-};
-
-// Finds the first entry in 'entries' for which 'predicate' returns true.
-// Returns true on match, and sets 'match' to a copy of the matching entry. If
-// 'match' is null, it's ignored.
-bool FindBy(const std::vector<TCPEntry>& entries, TCPEntry* match,
- std::function<bool(const TCPEntry&)> predicate) {
- for (const TCPEntry& entry : entries) {
- if (predicate(entry)) {
- if (match != nullptr) {
- *match = entry;
- }
- return true;
- }
- }
- return false;
-}
-
-bool FindByLocalAddr(const std::vector<TCPEntry>& entries, TCPEntry* match,
- const struct sockaddr* addr) {
- uint32_t host = IPFromInetSockaddr(addr);
- uint16_t port = PortFromInetSockaddr(addr);
- return FindBy(entries, match, [host, port](const TCPEntry& e) {
- return (e.local_addr == host && e.local_port == port);
- });
-}
-
-bool FindByRemoteAddr(const std::vector<TCPEntry>& entries, TCPEntry* match,
- const struct sockaddr* addr) {
- uint32_t host = IPFromInetSockaddr(addr);
- uint16_t port = PortFromInetSockaddr(addr);
- return FindBy(entries, match, [host, port](const TCPEntry& e) {
- return (e.remote_addr == host && e.remote_port == port);
- });
-}
-
-// Returns a parsed representation of /proc/net/tcp entries.
-PosixErrorOr<std::vector<TCPEntry>> ProcNetTCPEntries() {
- std::string content;
- RETURN_IF_ERRNO(GetContents("/proc/net/tcp", &content));
-
- bool found_header = false;
- std::vector<TCPEntry> entries;
- std::vector<std::string> lines = StrSplit(content, '\n');
- std::cerr << "<contents of /proc/net/tcp>" << std::endl;
- for (const std::string& line : lines) {
- std::cerr << line << std::endl;
-
- if (!found_header) {
- EXPECT_EQ(line, kProcNetTCPHeader);
- found_header = true;
- continue;
- }
- if (line.empty()) {
- continue;
- }
-
- // Parse a single entry from /proc/net/tcp.
- //
- // Example entries:
- //
- // clang-format off
- //
- // sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
- // 0: 00000000:006F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 1968 1 0000000000000000 100 0 0 10 0
- // 1: 0100007F:7533 00000000:0000 0A 00000000:00000000 00:00000000 00000000 120 0 10684 1 0000000000000000 100 0 0 10 0
- // ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
- // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
- //
- // clang-format on
-
- TCPEntry entry;
- std::vector<std::string> fields =
- StrSplit(line, absl::ByAnyChar(": "), absl::SkipEmpty());
-
- ASSIGN_OR_RETURN_ERRNO(entry.local_addr, AtoiBase(fields[1], 16));
- ASSIGN_OR_RETURN_ERRNO(entry.local_port, AtoiBase(fields[2], 16));
-
- ASSIGN_OR_RETURN_ERRNO(entry.remote_addr, AtoiBase(fields[3], 16));
- ASSIGN_OR_RETURN_ERRNO(entry.remote_port, AtoiBase(fields[4], 16));
-
- ASSIGN_OR_RETURN_ERRNO(entry.state, AtoiBase(fields[5], 16));
- ASSIGN_OR_RETURN_ERRNO(entry.uid, Atoi<uint64_t>(fields[11]));
- ASSIGN_OR_RETURN_ERRNO(entry.inode, Atoi<uint64_t>(fields[13]));
-
- entries.push_back(entry);
- }
- std::cerr << "<end of /proc/net/tcp>" << std::endl;
-
- return entries;
-}
-
-TEST(ProcNetTCP, Exists) {
- const std::string content =
- ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/tcp"));
- const std::string header_line = StrCat(kProcNetTCPHeader, "\n");
- if (IsRunningOnGvisor()) {
- // Should be just the header since we don't have any tcp sockets yet.
- EXPECT_EQ(content, header_line);
- } else {
- // On a general linux machine, we could have abitrary sockets on the system,
- // so just check the header.
- EXPECT_THAT(content, ::testing::StartsWith(header_line));
- }
-}
-
-TEST(ProcNetTCP, EntryUID) {
- auto sockets =
- ASSERT_NO_ERRNO_AND_VALUE(IPv4TCPAcceptBindSocketPair(0).Create());
- std::vector<TCPEntry> entries =
- ASSERT_NO_ERRNO_AND_VALUE(ProcNetTCPEntries());
- TCPEntry e;
- ASSERT_TRUE(FindByLocalAddr(entries, &e, sockets->first_addr()));
- EXPECT_EQ(e.uid, geteuid());
- ASSERT_TRUE(FindByRemoteAddr(entries, &e, sockets->first_addr()));
- EXPECT_EQ(e.uid, geteuid());
-}
-
-TEST(ProcNetTCP, BindAcceptConnect) {
- auto sockets =
- ASSERT_NO_ERRNO_AND_VALUE(IPv4TCPAcceptBindSocketPair(0).Create());
- std::vector<TCPEntry> entries =
- ASSERT_NO_ERRNO_AND_VALUE(ProcNetTCPEntries());
- // We can only make assertions about the total number of entries if we control
- // the entire "machine".
- if (IsRunningOnGvisor()) {
- EXPECT_EQ(entries.size(), 2);
- }
-
- EXPECT_TRUE(FindByLocalAddr(entries, nullptr, sockets->first_addr()));
- EXPECT_TRUE(FindByRemoteAddr(entries, nullptr, sockets->first_addr()));
-}
-
-TEST(ProcNetTCP, InodeReasonable) {
- auto sockets =
- ASSERT_NO_ERRNO_AND_VALUE(IPv4TCPAcceptBindSocketPair(0).Create());
- std::vector<TCPEntry> entries =
- ASSERT_NO_ERRNO_AND_VALUE(ProcNetTCPEntries());
-
- TCPEntry accepted_entry;
- ASSERT_TRUE(FindByLocalAddr(entries, &accepted_entry, sockets->first_addr()));
- EXPECT_NE(accepted_entry.inode, 0);
-
- TCPEntry client_entry;
- ASSERT_TRUE(FindByRemoteAddr(entries, &client_entry, sockets->first_addr()));
- EXPECT_NE(client_entry.inode, 0);
- EXPECT_NE(accepted_entry.inode, client_entry.inode);
-}
-
-TEST(ProcNetTCP, State) {
- std::unique_ptr<FileDescriptor> server =
- ASSERT_NO_ERRNO_AND_VALUE(IPv4TCPUnboundSocket(0).Create());
-
- auto test_addr = V4Loopback();
- ASSERT_THAT(
- bind(server->get(), reinterpret_cast<struct sockaddr*>(&test_addr.addr),
- test_addr.addr_len),
- SyscallSucceeds());
-
- struct sockaddr addr;
- socklen_t addrlen = sizeof(struct sockaddr);
- ASSERT_THAT(getsockname(server->get(), &addr, &addrlen), SyscallSucceeds());
- ASSERT_EQ(addrlen, sizeof(struct sockaddr));
-
- ASSERT_THAT(listen(server->get(), 10), SyscallSucceeds());
- std::vector<TCPEntry> entries =
- ASSERT_NO_ERRNO_AND_VALUE(ProcNetTCPEntries());
- TCPEntry listen_entry;
- ASSERT_TRUE(FindByLocalAddr(entries, &listen_entry, &addr));
- EXPECT_EQ(listen_entry.state, TCP_LISTEN);
-
- std::unique_ptr<FileDescriptor> client =
- ASSERT_NO_ERRNO_AND_VALUE(IPv4TCPUnboundSocket(0).Create());
- ASSERT_THAT(RetryEINTR(connect)(client->get(), &addr, addrlen),
- SyscallSucceeds());
- entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetTCPEntries());
- ASSERT_TRUE(FindByLocalAddr(entries, &listen_entry, &addr));
- EXPECT_EQ(listen_entry.state, TCP_LISTEN);
- TCPEntry client_entry;
- ASSERT_TRUE(FindByRemoteAddr(entries, &client_entry, &addr));
- EXPECT_EQ(client_entry.state, TCP_ESTABLISHED);
-
- FileDescriptor accepted =
- ASSERT_NO_ERRNO_AND_VALUE(Accept(server->get(), nullptr, nullptr));
-
- const uint32_t accepted_local_host = IPFromInetSockaddr(&addr);
- const uint16_t accepted_local_port = PortFromInetSockaddr(&addr);
-
- entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetTCPEntries());
- TCPEntry accepted_entry;
- ASSERT_TRUE(FindBy(entries, &accepted_entry,
- [client_entry, accepted_local_host,
- accepted_local_port](const TCPEntry& e) {
- return e.local_addr == accepted_local_host &&
- e.local_port == accepted_local_port &&
- e.remote_addr == client_entry.local_addr &&
- e.remote_port == client_entry.local_port;
- }));
- EXPECT_EQ(accepted_entry.state, TCP_ESTABLISHED);
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/proc_net_udp.cc b/test/syscalls/linux/proc_net_udp.cc
deleted file mode 100644
index 369df8e0e..000000000
--- a/test/syscalls/linux/proc_net_udp.cc
+++ /dev/null
@@ -1,309 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "absl/strings/numbers.h"
-#include "absl/strings/str_join.h"
-#include "absl/strings/str_split.h"
-#include "test/syscalls/linux/ip_socket_test_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-using absl::StrCat;
-using absl::StrFormat;
-using absl::StrSplit;
-
-constexpr char kProcNetUDPHeader[] =
- " sl local_address rem_address st tx_queue rx_queue tr tm->when "
- "retrnsmt uid timeout inode ref pointer drops ";
-
-// UDPEntry represents a single entry from /proc/net/udp.
-struct UDPEntry {
- uint32_t local_addr;
- uint16_t local_port;
-
- uint32_t remote_addr;
- uint16_t remote_port;
-
- uint64_t state;
- uint64_t uid;
- uint64_t inode;
-};
-
-std::string DescribeFirstInetSocket(const SocketPair& sockets) {
- const struct sockaddr* addr = sockets.first_addr();
- return StrFormat("First test socket: fd:%d %8X:%4X", sockets.first_fd(),
- IPFromInetSockaddr(addr), PortFromInetSockaddr(addr));
-}
-
-std::string DescribeSecondInetSocket(const SocketPair& sockets) {
- const struct sockaddr* addr = sockets.second_addr();
- return StrFormat("Second test socket fd:%d %8X:%4X", sockets.second_fd(),
- IPFromInetSockaddr(addr), PortFromInetSockaddr(addr));
-}
-
-// Finds the first entry in 'entries' for which 'predicate' returns true.
-// Returns true on match, and set 'match' to a copy of the matching entry. If
-// 'match' is null, it's ignored.
-bool FindBy(const std::vector<UDPEntry>& entries, UDPEntry* match,
- std::function<bool(const UDPEntry&)> predicate) {
- for (const UDPEntry& entry : entries) {
- if (predicate(entry)) {
- if (match != nullptr) {
- *match = entry;
- }
- return true;
- }
- }
- return false;
-}
-
-bool FindByLocalAddr(const std::vector<UDPEntry>& entries, UDPEntry* match,
- const struct sockaddr* addr) {
- uint32_t host = IPFromInetSockaddr(addr);
- uint16_t port = PortFromInetSockaddr(addr);
- return FindBy(entries, match, [host, port](const UDPEntry& e) {
- return (e.local_addr == host && e.local_port == port);
- });
-}
-
-bool FindByRemoteAddr(const std::vector<UDPEntry>& entries, UDPEntry* match,
- const struct sockaddr* addr) {
- uint32_t host = IPFromInetSockaddr(addr);
- uint16_t port = PortFromInetSockaddr(addr);
- return FindBy(entries, match, [host, port](const UDPEntry& e) {
- return (e.remote_addr == host && e.remote_port == port);
- });
-}
-
-PosixErrorOr<uint64_t> InodeFromSocketFD(int fd) {
- ASSIGN_OR_RETURN_ERRNO(struct stat s, Fstat(fd));
- if (!S_ISSOCK(s.st_mode)) {
- return PosixError(EINVAL, StrFormat("FD %d is not a socket", fd));
- }
- return s.st_ino;
-}
-
-PosixErrorOr<bool> FindByFD(const std::vector<UDPEntry>& entries,
- UDPEntry* match, int fd) {
- ASSIGN_OR_RETURN_ERRNO(uint64_t inode, InodeFromSocketFD(fd));
- return FindBy(entries, match,
- [inode](const UDPEntry& e) { return (e.inode == inode); });
-}
-
-// Returns a parsed representation of /proc/net/udp entries.
-PosixErrorOr<std::vector<UDPEntry>> ProcNetUDPEntries() {
- std::string content;
- RETURN_IF_ERRNO(GetContents("/proc/net/udp", &content));
-
- bool found_header = false;
- std::vector<UDPEntry> entries;
- std::vector<std::string> lines = StrSplit(content, '\n');
- std::cerr << "<contents of /proc/net/udp>" << std::endl;
- for (const std::string& line : lines) {
- std::cerr << line << std::endl;
-
- if (!found_header) {
- EXPECT_EQ(line, kProcNetUDPHeader);
- found_header = true;
- continue;
- }
- if (line.empty()) {
- continue;
- }
-
- // Parse a single entry from /proc/net/udp.
- //
- // Example entries:
- //
- // clang-format off
- //
- // sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops
- // 3503: 0100007F:0035 00000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 33317 2 0000000000000000 0
- // 3518: 00000000:0044 00000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 40394 2 0000000000000000 0
- // ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
- // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
- //
- // clang-format on
-
- UDPEntry entry;
- std::vector<std::string> fields =
- StrSplit(line, absl::ByAnyChar(": "), absl::SkipEmpty());
-
- ASSIGN_OR_RETURN_ERRNO(entry.local_addr, AtoiBase(fields[1], 16));
- ASSIGN_OR_RETURN_ERRNO(entry.local_port, AtoiBase(fields[2], 16));
-
- ASSIGN_OR_RETURN_ERRNO(entry.remote_addr, AtoiBase(fields[3], 16));
- ASSIGN_OR_RETURN_ERRNO(entry.remote_port, AtoiBase(fields[4], 16));
-
- ASSIGN_OR_RETURN_ERRNO(entry.state, AtoiBase(fields[5], 16));
- ASSIGN_OR_RETURN_ERRNO(entry.uid, Atoi<uint64_t>(fields[11]));
- ASSIGN_OR_RETURN_ERRNO(entry.inode, Atoi<uint64_t>(fields[13]));
-
- // Linux shares internal data structures between TCP and UDP sockets. The
- // proc entries for UDP sockets share some fields with TCP sockets, but
- // these fields should always be zero as they're not meaningful for UDP
- // sockets.
- EXPECT_EQ(fields[8], "00") << StrFormat("sl:%s, tr", fields[0]);
- EXPECT_EQ(fields[9], "00000000") << StrFormat("sl:%s, tm->when", fields[0]);
- EXPECT_EQ(fields[10], "00000000")
- << StrFormat("sl:%s, retrnsmt", fields[0]);
- EXPECT_EQ(fields[12], "0") << StrFormat("sl:%s, timeout", fields[0]);
-
- entries.push_back(entry);
- }
- std::cerr << "<end of /proc/net/udp>" << std::endl;
-
- return entries;
-}
-
-TEST(ProcNetUDP, Exists) {
- const std::string content =
- ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/udp"));
- const std::string header_line = StrCat(kProcNetUDPHeader, "\n");
- EXPECT_THAT(content, ::testing::StartsWith(header_line));
-}
-
-TEST(ProcNetUDP, EntryUID) {
- auto sockets =
- ASSERT_NO_ERRNO_AND_VALUE(IPv4UDPBidirectionalBindSocketPair(0).Create());
- std::vector<UDPEntry> entries =
- ASSERT_NO_ERRNO_AND_VALUE(ProcNetUDPEntries());
- UDPEntry e;
- ASSERT_TRUE(FindByLocalAddr(entries, &e, sockets->first_addr()))
- << DescribeFirstInetSocket(*sockets);
- EXPECT_EQ(e.uid, geteuid());
- ASSERT_TRUE(FindByRemoteAddr(entries, &e, sockets->first_addr()))
- << DescribeSecondInetSocket(*sockets);
- EXPECT_EQ(e.uid, geteuid());
-}
-
-TEST(ProcNetUDP, FindMutualEntries) {
- auto sockets =
- ASSERT_NO_ERRNO_AND_VALUE(IPv4UDPBidirectionalBindSocketPair(0).Create());
- std::vector<UDPEntry> entries =
- ASSERT_NO_ERRNO_AND_VALUE(ProcNetUDPEntries());
-
- EXPECT_TRUE(FindByLocalAddr(entries, nullptr, sockets->first_addr()))
- << DescribeFirstInetSocket(*sockets);
- EXPECT_TRUE(FindByRemoteAddr(entries, nullptr, sockets->first_addr()))
- << DescribeSecondInetSocket(*sockets);
-
- EXPECT_TRUE(FindByLocalAddr(entries, nullptr, sockets->second_addr()))
- << DescribeSecondInetSocket(*sockets);
- EXPECT_TRUE(FindByRemoteAddr(entries, nullptr, sockets->second_addr()))
- << DescribeFirstInetSocket(*sockets);
-}
-
-TEST(ProcNetUDP, EntriesRemovedOnClose) {
- auto sockets =
- ASSERT_NO_ERRNO_AND_VALUE(IPv4UDPBidirectionalBindSocketPair(0).Create());
- std::vector<UDPEntry> entries =
- ASSERT_NO_ERRNO_AND_VALUE(ProcNetUDPEntries());
-
- EXPECT_TRUE(FindByLocalAddr(entries, nullptr, sockets->first_addr()))
- << DescribeFirstInetSocket(*sockets);
- EXPECT_TRUE(FindByLocalAddr(entries, nullptr, sockets->second_addr()))
- << DescribeSecondInetSocket(*sockets);
-
- EXPECT_THAT(close(sockets->release_first_fd()), SyscallSucceeds());
- entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetUDPEntries());
- // First socket's entry should be gone, but the second socket's entry should
- // still exist.
- EXPECT_FALSE(FindByLocalAddr(entries, nullptr, sockets->first_addr()))
- << DescribeFirstInetSocket(*sockets);
- EXPECT_TRUE(FindByLocalAddr(entries, nullptr, sockets->second_addr()))
- << DescribeSecondInetSocket(*sockets);
-
- EXPECT_THAT(close(sockets->release_second_fd()), SyscallSucceeds());
- entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetUDPEntries());
- // Both entries should be gone.
- EXPECT_FALSE(FindByLocalAddr(entries, nullptr, sockets->first_addr()))
- << DescribeFirstInetSocket(*sockets);
- EXPECT_FALSE(FindByLocalAddr(entries, nullptr, sockets->second_addr()))
- << DescribeSecondInetSocket(*sockets);
-}
-
-PosixErrorOr<std::unique_ptr<FileDescriptor>> BoundUDPSocket() {
- ASSIGN_OR_RETURN_ERRNO(std::unique_ptr<FileDescriptor> socket,
- IPv4UDPUnboundSocket(0).Create());
- struct sockaddr_in addr;
- addr.sin_family = AF_INET;
- addr.sin_addr.s_addr = htonl(INADDR_ANY);
- addr.sin_port = 0;
-
- int res = bind(socket->get(), reinterpret_cast<const struct sockaddr*>(&addr),
- sizeof(addr));
- if (res) {
- return PosixError(errno, "bind()");
- }
- return socket;
-}
-
-TEST(ProcNetUDP, BoundEntry) {
- std::unique_ptr<FileDescriptor> socket =
- ASSERT_NO_ERRNO_AND_VALUE(BoundUDPSocket());
- struct sockaddr addr;
- socklen_t len = sizeof(addr);
- ASSERT_THAT(getsockname(socket->get(), &addr, &len), SyscallSucceeds());
- uint16_t port = PortFromInetSockaddr(&addr);
-
- std::vector<UDPEntry> entries =
- ASSERT_NO_ERRNO_AND_VALUE(ProcNetUDPEntries());
- UDPEntry e;
- ASSERT_TRUE(ASSERT_NO_ERRNO_AND_VALUE(FindByFD(entries, &e, socket->get())));
- EXPECT_EQ(e.local_port, port);
- EXPECT_EQ(e.remote_addr, 0);
- EXPECT_EQ(e.remote_port, 0);
-}
-
-TEST(ProcNetUDP, BoundSocketStateClosed) {
- std::unique_ptr<FileDescriptor> socket =
- ASSERT_NO_ERRNO_AND_VALUE(BoundUDPSocket());
- std::vector<UDPEntry> entries =
- ASSERT_NO_ERRNO_AND_VALUE(ProcNetUDPEntries());
- UDPEntry e;
- ASSERT_TRUE(ASSERT_NO_ERRNO_AND_VALUE(FindByFD(entries, &e, socket->get())));
- EXPECT_EQ(e.state, TCP_CLOSE);
-}
-
-TEST(ProcNetUDP, ConnectedSocketStateEstablished) {
- auto sockets =
- ASSERT_NO_ERRNO_AND_VALUE(IPv4UDPBidirectionalBindSocketPair(0).Create());
- std::vector<UDPEntry> entries =
- ASSERT_NO_ERRNO_AND_VALUE(ProcNetUDPEntries());
-
- UDPEntry e;
- ASSERT_TRUE(FindByLocalAddr(entries, &e, sockets->first_addr()))
- << DescribeFirstInetSocket(*sockets);
- EXPECT_EQ(e.state, TCP_ESTABLISHED);
-
- ASSERT_TRUE(FindByLocalAddr(entries, &e, sockets->second_addr()))
- << DescribeSecondInetSocket(*sockets);
- EXPECT_EQ(e.state, TCP_ESTABLISHED);
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/proc_net_unix.cc b/test/syscalls/linux/proc_net_unix.cc
deleted file mode 100644
index 83dbd1364..000000000
--- a/test/syscalls/linux/proc_net_unix.cc
+++ /dev/null
@@ -1,444 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "absl/strings/numbers.h"
-#include "absl/strings/str_format.h"
-#include "absl/strings/str_join.h"
-#include "absl/strings/str_split.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-using absl::StrCat;
-using absl::StreamFormat;
-using absl::StrFormat;
-
-constexpr char kProcNetUnixHeader[] =
- "Num RefCount Protocol Flags Type St Inode Path";
-
-// Possible values of the "st" field in a /proc/net/unix entry. Source: Linux
-// kernel, include/uapi/linux/net.h.
-enum {
- SS_FREE = 0, // Not allocated
- SS_UNCONNECTED, // Unconnected to any socket
- SS_CONNECTING, // In process of connecting
- SS_CONNECTED, // Connected to socket
- SS_DISCONNECTING // In process of disconnecting
-};
-
-// UnixEntry represents a single entry from /proc/net/unix.
-struct UnixEntry {
- uintptr_t addr;
- uint64_t refs;
- uint64_t protocol;
- uint64_t flags;
- uint64_t type;
- uint64_t state;
- uint64_t inode;
- std::string path;
-};
-
-// Abstract socket paths can have either trailing null bytes or '@'s as padding
-// at the end, depending on the linux version. This function strips any such
-// padding.
-void StripAbstractPathPadding(std::string* s) {
- const char pad_char = s->back();
- if (pad_char != '\0' && pad_char != '@') {
- return;
- }
-
- const auto last_pos = s->find_last_not_of(pad_char);
- if (last_pos != std::string::npos) {
- s->resize(last_pos + 1);
- }
-}
-
-// Precondition: addr must be a unix socket address (i.e. sockaddr_un) and
-// addr->sun_path must be null-terminated. This is always the case if addr comes
-// from Linux:
-//
-// Per man unix(7):
-//
-// "When the address of a pathname socket is returned (by [getsockname(2)]), its
-// length is
-//
-// offsetof(struct sockaddr_un, sun_path) + strlen(sun_path) + 1
-//
-// and sun_path contains the null-terminated pathname."
-std::string ExtractPath(const struct sockaddr* addr) {
- const char* path =
- reinterpret_cast<const struct sockaddr_un*>(addr)->sun_path;
- // Note: sockaddr_un.sun_path is an embedded character array of length
- // UNIX_PATH_MAX, so we can always safely dereference the first 2 bytes below.
- //
- // We also rely on the path being null-terminated.
- if (path[0] == 0) {
- std::string abstract_path = StrCat("@", &path[1]);
- StripAbstractPathPadding(&abstract_path);
- return abstract_path;
- }
- return std::string(path);
-}
-
-// Returns a parsed representation of /proc/net/unix entries.
-PosixErrorOr<std::vector<UnixEntry>> ProcNetUnixEntries() {
- std::string content;
- RETURN_IF_ERRNO(GetContents("/proc/net/unix", &content));
-
- bool skipped_header = false;
- std::vector<UnixEntry> entries;
- std::vector<std::string> lines = absl::StrSplit(content, '\n');
- std::cerr << "<contents of /proc/net/unix>" << std::endl;
- for (std::string line : lines) {
- // Emit the proc entry to the test output to provide context for the test
- // results.
- std::cerr << line << std::endl;
-
- if (!skipped_header) {
- EXPECT_EQ(line, kProcNetUnixHeader);
- skipped_header = true;
- continue;
- }
- if (line.empty()) {
- continue;
- }
-
- // Parse a single entry from /proc/net/unix.
- //
- // Sample file:
- //
- // clang-format off
- //
- // Num RefCount Protocol Flags Type St Inode Path"
- // ffffa130e7041c00: 00000002 00000000 00010000 0001 01 1299413685 /tmp/control_server/13293772586877554487
- // ffffa14f547dc400: 00000002 00000000 00010000 0001 01 3793 @remote_coredump
- //
- // clang-format on
- //
- // Note that from the second entry, the inode number can be padded using
- // spaces, so we need to handle it separately during parsing. See
- // net/unix/af_unix.c:unix_seq_show() for how these entries are produced. In
- // particular, only the inode field is padded with spaces.
- UnixEntry entry;
-
- // Process the first 6 fields, up to but not including "Inode".
- std::vector<std::string> fields =
- absl::StrSplit(line, absl::MaxSplits(' ', 6));
-
- if (fields.size() < 7) {
- return PosixError(EINVAL, StrFormat("Invalid entry: '%s'\n", line));
- }
-
- // AtoiBase can't handle the ':' in the "Num" field, so strip it out.
- std::vector<std::string> addr = absl::StrSplit(fields[0], ':');
- ASSIGN_OR_RETURN_ERRNO(entry.addr, AtoiBase(addr[0], 16));
-
- ASSIGN_OR_RETURN_ERRNO(entry.refs, AtoiBase(fields[1], 16));
- ASSIGN_OR_RETURN_ERRNO(entry.protocol, AtoiBase(fields[2], 16));
- ASSIGN_OR_RETURN_ERRNO(entry.flags, AtoiBase(fields[3], 16));
- ASSIGN_OR_RETURN_ERRNO(entry.type, AtoiBase(fields[4], 16));
- ASSIGN_OR_RETURN_ERRNO(entry.state, AtoiBase(fields[5], 16));
-
- absl::string_view rest = absl::StripAsciiWhitespace(fields[6]);
- fields = absl::StrSplit(rest, absl::MaxSplits(' ', 1));
- if (fields.empty()) {
- return PosixError(
- EINVAL, StrFormat("Invalid entry, missing 'Inode': '%s'\n", line));
- }
- ASSIGN_OR_RETURN_ERRNO(entry.inode, AtoiBase(fields[0], 10));
-
- entry.path = "";
- if (fields.size() > 1) {
- entry.path = fields[1];
- StripAbstractPathPadding(&entry.path);
- }
-
- entries.push_back(entry);
- }
- std::cerr << "<end of /proc/net/unix>" << std::endl;
-
- return entries;
-}
-
-// Finds the first entry in 'entries' for which 'predicate' returns true.
-// Returns true on match, and sets 'match' to point to the matching entry.
-bool FindBy(std::vector<UnixEntry> entries, UnixEntry* match,
- std::function<bool(const UnixEntry&)> predicate) {
- for (int i = 0; i < entries.size(); ++i) {
- if (predicate(entries[i])) {
- *match = entries[i];
- return true;
- }
- }
- return false;
-}
-
-bool FindByPath(std::vector<UnixEntry> entries, UnixEntry* match,
- const std::string& path) {
- return FindBy(entries, match,
- [path](const UnixEntry& e) { return e.path == path; });
-}
-
-TEST(ProcNetUnix, Exists) {
- const std::string content =
- ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/unix"));
- const std::string header_line = StrCat(kProcNetUnixHeader, "\n");
- if (IsRunningOnGvisor()) {
- // Should be just the header since we don't have any unix domain sockets
- // yet.
- EXPECT_EQ(content, header_line);
- } else {
- // However, on a general linux machine, we could have abitrary sockets on
- // the system, so just check the header.
- EXPECT_THAT(content, ::testing::StartsWith(header_line));
- }
-}
-
-TEST(ProcNetUnix, FilesystemBindAcceptConnect) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(
- FilesystemBoundUnixDomainSocketPair(SOCK_STREAM).Create());
-
- std::string path1 = ExtractPath(sockets->first_addr());
- std::string path2 = ExtractPath(sockets->second_addr());
- std::cerr << StreamFormat("Server socket address (path1): %s\n", path1);
- std::cerr << StreamFormat("Client socket address (path2): %s\n", path2);
-
- std::vector<UnixEntry> entries =
- ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries());
- if (IsRunningOnGvisor()) {
- EXPECT_EQ(entries.size(), 2);
- }
-
- // The server-side socket's path is listed in the socket entry...
- UnixEntry s1;
- EXPECT_TRUE(FindByPath(entries, &s1, path1));
-
- // ... but the client-side socket's path is not.
- UnixEntry s2;
- EXPECT_FALSE(FindByPath(entries, &s2, path2));
-}
-
-TEST(ProcNetUnix, AbstractBindAcceptConnect) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(
- AbstractBoundUnixDomainSocketPair(SOCK_STREAM).Create());
-
- std::string path1 = ExtractPath(sockets->first_addr());
- std::string path2 = ExtractPath(sockets->second_addr());
- std::cerr << StreamFormat("Server socket address (path1): '%s'\n", path1);
- std::cerr << StreamFormat("Client socket address (path2): '%s'\n", path2);
-
- std::vector<UnixEntry> entries =
- ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries());
- if (IsRunningOnGvisor()) {
- EXPECT_EQ(entries.size(), 2);
- }
-
- // The server-side socket's path is listed in the socket entry...
- UnixEntry s1;
- EXPECT_TRUE(FindByPath(entries, &s1, path1));
-
- // ... but the client-side socket's path is not.
- UnixEntry s2;
- EXPECT_FALSE(FindByPath(entries, &s2, path2));
-}
-
-TEST(ProcNetUnix, SocketPair) {
- // Under gvisor, ensure a socketpair() syscall creates exactly 2 new
- // entries. We have no way to verify this under Linux, as we have no control
- // over socket creation on a general Linux machine.
- SKIP_IF(!IsRunningOnGvisor());
-
- std::vector<UnixEntry> entries =
- ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries());
- ASSERT_EQ(entries.size(), 0);
-
- auto sockets =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_STREAM).Create());
-
- entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries());
- EXPECT_EQ(entries.size(), 2);
-}
-
-TEST(ProcNetUnix, StreamSocketStateUnconnectedOnBind) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(
- AbstractUnboundUnixDomainSocketPair(SOCK_STREAM).Create());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- std::vector<UnixEntry> entries =
- ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries());
-
- const std::string address = ExtractPath(sockets->first_addr());
- UnixEntry bind_entry;
- ASSERT_TRUE(FindByPath(entries, &bind_entry, address));
- EXPECT_EQ(bind_entry.state, SS_UNCONNECTED);
-}
-
-TEST(ProcNetUnix, StreamSocketStateStateUnconnectedOnListen) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(
- AbstractUnboundUnixDomainSocketPair(SOCK_STREAM).Create());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- std::vector<UnixEntry> entries =
- ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries());
-
- const std::string address = ExtractPath(sockets->first_addr());
- UnixEntry bind_entry;
- ASSERT_TRUE(FindByPath(entries, &bind_entry, address));
- EXPECT_EQ(bind_entry.state, SS_UNCONNECTED);
-
- ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
-
- entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries());
- UnixEntry listen_entry;
- ASSERT_TRUE(
- FindByPath(entries, &listen_entry, ExtractPath(sockets->first_addr())));
- EXPECT_EQ(listen_entry.state, SS_UNCONNECTED);
- // The bind and listen entries should refer to the same socket.
- EXPECT_EQ(listen_entry.inode, bind_entry.inode);
-}
-
-TEST(ProcNetUnix, StreamSocketStateStateConnectedOnAccept) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(
- AbstractUnboundUnixDomainSocketPair(SOCK_STREAM).Create());
- const std::string address = ExtractPath(sockets->first_addr());
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
- ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
- std::vector<UnixEntry> entries =
- ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries());
- UnixEntry listen_entry;
- ASSERT_TRUE(
- FindByPath(entries, &listen_entry, ExtractPath(sockets->first_addr())));
-
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- int clientfd;
- ASSERT_THAT(clientfd = accept(sockets->first_fd(), nullptr, nullptr),
- SyscallSucceeds());
-
- // Find the entry for the accepted socket. UDS proc entries don't have a
- // remote address, so we distinguish the accepted socket from the listen
- // socket by checking for a different inode.
- entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries());
- UnixEntry accept_entry;
- ASSERT_TRUE(FindBy(
- entries, &accept_entry, [address, listen_entry](const UnixEntry& e) {
- return e.path == address && e.inode != listen_entry.inode;
- }));
- EXPECT_EQ(accept_entry.state, SS_CONNECTED);
- // Listen entry should still be in SS_UNCONNECTED state.
- ASSERT_TRUE(FindBy(entries, &listen_entry,
- [&sockets, listen_entry](const UnixEntry& e) {
- return e.path == ExtractPath(sockets->first_addr()) &&
- e.inode == listen_entry.inode;
- }));
- EXPECT_EQ(listen_entry.state, SS_UNCONNECTED);
-}
-
-TEST(ProcNetUnix, DgramSocketStateDisconnectingOnBind) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(
- AbstractUnboundUnixDomainSocketPair(SOCK_DGRAM).Create());
-
- std::vector<UnixEntry> entries =
- ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries());
-
- // On gVisor, the only two UDS on the system are the ones we just created and
- // we rely on this to locate the test socket entries in the remainder of the
- // test. On a generic Linux system, we have no easy way to locate the
- // corresponding entries, as they don't have an address yet.
- if (IsRunningOnGvisor()) {
- ASSERT_EQ(entries.size(), 2);
- for (auto e : entries) {
- ASSERT_EQ(e.state, SS_DISCONNECTING);
- }
- }
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries());
- const std::string address = ExtractPath(sockets->first_addr());
- UnixEntry bind_entry;
- ASSERT_TRUE(FindByPath(entries, &bind_entry, address));
- EXPECT_EQ(bind_entry.state, SS_UNCONNECTED);
-}
-
-TEST(ProcNetUnix, DgramSocketStateConnectingOnConnect) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(
- AbstractUnboundUnixDomainSocketPair(SOCK_DGRAM).Create());
-
- std::vector<UnixEntry> entries =
- ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries());
-
- // On gVisor, the only two UDS on the system are the ones we just created and
- // we rely on this to locate the test socket entries in the remainder of the
- // test. On a generic Linux system, we have no easy way to locate the
- // corresponding entries, as they don't have an address yet.
- if (IsRunningOnGvisor()) {
- ASSERT_EQ(entries.size(), 2);
- for (auto e : entries) {
- ASSERT_EQ(e.state, SS_DISCONNECTING);
- }
- }
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries());
- const std::string address = ExtractPath(sockets->first_addr());
- UnixEntry bind_entry;
- ASSERT_TRUE(FindByPath(entries, &bind_entry, address));
-
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries());
-
- // Once again, we have no easy way to identify the connecting socket as it has
- // no listed address. We can only identify the entry as the "non-bind socket
- // entry" on gVisor, where we're guaranteed to have only the two entries we
- // create during this test.
- if (IsRunningOnGvisor()) {
- ASSERT_EQ(entries.size(), 2);
- UnixEntry connect_entry;
- ASSERT_TRUE(
- FindBy(entries, &connect_entry, [bind_entry](const UnixEntry& e) {
- return e.inode != bind_entry.inode;
- }));
- EXPECT_EQ(connect_entry.state, SS_CONNECTING);
- }
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/proc_pid_smaps.cc b/test/syscalls/linux/proc_pid_smaps.cc
deleted file mode 100644
index 7f2e8f203..000000000
--- a/test/syscalls/linux/proc_pid_smaps.cc
+++ /dev/null
@@ -1,468 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <algorithm>
-#include <iostream>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "absl/container/flat_hash_set.h"
-#include "absl/strings/str_cat.h"
-#include "absl/strings/str_format.h"
-#include "absl/strings/str_split.h"
-#include "absl/strings/string_view.h"
-#include "absl/types/optional.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/memory_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/proc_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-using ::testing::Contains;
-using ::testing::ElementsAreArray;
-using ::testing::IsSupersetOf;
-using ::testing::Not;
-using ::testing::Optional;
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-struct ProcPidSmapsEntry {
- ProcMapsEntry maps_entry;
-
- // These fields should always exist, as they were included in e070ad49f311
- // "[PATCH] add /proc/pid/smaps".
- size_t size_kb;
- size_t rss_kb;
- size_t shared_clean_kb;
- size_t shared_dirty_kb;
- size_t private_clean_kb;
- size_t private_dirty_kb;
-
- // These fields were added later and may not be present.
- absl::optional<size_t> pss_kb;
- absl::optional<size_t> referenced_kb;
- absl::optional<size_t> anonymous_kb;
- absl::optional<size_t> anon_huge_pages_kb;
- absl::optional<size_t> shared_hugetlb_kb;
- absl::optional<size_t> private_hugetlb_kb;
- absl::optional<size_t> swap_kb;
- absl::optional<size_t> swap_pss_kb;
- absl::optional<size_t> kernel_page_size_kb;
- absl::optional<size_t> mmu_page_size_kb;
- absl::optional<size_t> locked_kb;
-
- // Caution: "Note that there is no guarantee that every flag and associated
- // mnemonic will be present in all further kernel releases. Things get
- // changed, the flags may be vanished or the reverse -- new added." - Linux
- // Documentation/filesystems/proc.txt, on VmFlags. Avoid checking for any
- // flags that are not extremely well-established.
- absl::optional<std::vector<std::string>> vm_flags;
-};
-
-// Given the value part of a /proc/[pid]/smaps field containing a value in kB
-// (for example, " 4 kB", returns the value in kB (in this example, 4).
-PosixErrorOr<size_t> SmapsValueKb(absl::string_view value) {
- // TODO(jamieliu): let us use RE2 or <regex>
- std::pair<absl::string_view, absl::string_view> parts =
- absl::StrSplit(value, ' ', absl::SkipEmpty());
- if (parts.second != "kB") {
- return PosixError(EINVAL,
- absl::StrCat("invalid smaps field value: ", value));
- }
- ASSIGN_OR_RETURN_ERRNO(auto val_kb, Atoi<size_t>(parts.first));
- return val_kb;
-}
-
-PosixErrorOr<std::vector<ProcPidSmapsEntry>> ParseProcPidSmaps(
- absl::string_view contents) {
- std::vector<ProcPidSmapsEntry> entries;
- absl::optional<ProcPidSmapsEntry> entry;
- bool have_size_kb = false;
- bool have_rss_kb = false;
- bool have_shared_clean_kb = false;
- bool have_shared_dirty_kb = false;
- bool have_private_clean_kb = false;
- bool have_private_dirty_kb = false;
-
- auto const finish_entry = [&] {
- if (entry) {
- if (!have_size_kb) {
- return PosixError(EINVAL, "smaps entry is missing Size");
- }
- if (!have_rss_kb) {
- return PosixError(EINVAL, "smaps entry is missing Rss");
- }
- if (!have_shared_clean_kb) {
- return PosixError(EINVAL, "smaps entry is missing Shared_Clean");
- }
- if (!have_shared_dirty_kb) {
- return PosixError(EINVAL, "smaps entry is missing Shared_Dirty");
- }
- if (!have_private_clean_kb) {
- return PosixError(EINVAL, "smaps entry is missing Private_Clean");
- }
- if (!have_private_dirty_kb) {
- return PosixError(EINVAL, "smaps entry is missing Private_Dirty");
- }
- // std::move(entry.value()) instead of std::move(entry).value(), because
- // otherwise tools may report a "use-after-move" warning, which is
- // spurious because entry.emplace() below resets entry to a new
- // ProcPidSmapsEntry.
- entries.emplace_back(std::move(entry.value()));
- }
- entry.emplace();
- have_size_kb = false;
- have_rss_kb = false;
- have_shared_clean_kb = false;
- have_shared_dirty_kb = false;
- have_private_clean_kb = false;
- have_private_dirty_kb = false;
- return NoError();
- };
-
- // Holds key/value pairs from smaps field lines. Declared here so it can be
- // captured by reference by the following lambdas.
- std::vector<absl::string_view> key_value;
-
- auto const on_required_field_kb = [&](size_t* field, bool* have_field) {
- if (*have_field) {
- return PosixError(
- EINVAL,
- absl::StrFormat("smaps entry has duplicate %s line", key_value[0]));
- }
- ASSIGN_OR_RETURN_ERRNO(*field, SmapsValueKb(key_value[1]));
- *have_field = true;
- return NoError();
- };
-
- auto const on_optional_field_kb = [&](absl::optional<size_t>* field) {
- if (*field) {
- return PosixError(
- EINVAL,
- absl::StrFormat("smaps entry has duplicate %s line", key_value[0]));
- }
- ASSIGN_OR_RETURN_ERRNO(*field, SmapsValueKb(key_value[1]));
- return NoError();
- };
-
- absl::flat_hash_set<std::string> unknown_fields;
- auto const on_unknown_field = [&] {
- absl::string_view key = key_value[0];
- // Don't mention unknown fields more than once.
- if (unknown_fields.count(key)) {
- return;
- }
- unknown_fields.insert(std::string(key));
- std::cerr << "skipping unknown smaps field " << key;
- };
-
- auto lines = absl::StrSplit(contents, '\n', absl::SkipEmpty());
- for (absl::string_view l : lines) {
- // Is this line a valid /proc/[pid]/maps entry?
- auto maybe_maps_entry = ParseProcMapsLine(l);
- if (maybe_maps_entry.ok()) {
- // This marks the beginning of a new /proc/[pid]/smaps entry.
- RETURN_IF_ERRNO(finish_entry());
- entry->maps_entry = std::move(maybe_maps_entry).ValueOrDie();
- continue;
- }
- // Otherwise it's a field in an existing /proc/[pid]/smaps entry of the form
- // "key:value" (where value in practice will be preceded by a variable
- // amount of whitespace).
- if (!entry) {
- std::cerr << "smaps line not considered a maps line: "
- << maybe_maps_entry.error_message();
- return PosixError(
- EINVAL,
- absl::StrCat("smaps field line without preceding maps line: ", l));
- }
- key_value = absl::StrSplit(l, absl::MaxSplits(':', 1));
- if (key_value.size() != 2) {
- return PosixError(EINVAL, absl::StrCat("invalid smaps field line: ", l));
- }
- absl::string_view const key = key_value[0];
- if (key == "Size") {
- RETURN_IF_ERRNO(on_required_field_kb(&entry->size_kb, &have_size_kb));
- } else if (key == "Rss") {
- RETURN_IF_ERRNO(on_required_field_kb(&entry->rss_kb, &have_rss_kb));
- } else if (key == "Shared_Clean") {
- RETURN_IF_ERRNO(
- on_required_field_kb(&entry->shared_clean_kb, &have_shared_clean_kb));
- } else if (key == "Shared_Dirty") {
- RETURN_IF_ERRNO(
- on_required_field_kb(&entry->shared_dirty_kb, &have_shared_dirty_kb));
- } else if (key == "Private_Clean") {
- RETURN_IF_ERRNO(on_required_field_kb(&entry->private_clean_kb,
- &have_private_clean_kb));
- } else if (key == "Private_Dirty") {
- RETURN_IF_ERRNO(on_required_field_kb(&entry->private_dirty_kb,
- &have_private_dirty_kb));
- } else if (key == "Pss") {
- RETURN_IF_ERRNO(on_optional_field_kb(&entry->pss_kb));
- } else if (key == "Referenced") {
- RETURN_IF_ERRNO(on_optional_field_kb(&entry->referenced_kb));
- } else if (key == "Anonymous") {
- RETURN_IF_ERRNO(on_optional_field_kb(&entry->anonymous_kb));
- } else if (key == "AnonHugePages") {
- RETURN_IF_ERRNO(on_optional_field_kb(&entry->anon_huge_pages_kb));
- } else if (key == "Shared_Hugetlb") {
- RETURN_IF_ERRNO(on_optional_field_kb(&entry->shared_hugetlb_kb));
- } else if (key == "Private_Hugetlb") {
- RETURN_IF_ERRNO(on_optional_field_kb(&entry->private_hugetlb_kb));
- } else if (key == "Swap") {
- RETURN_IF_ERRNO(on_optional_field_kb(&entry->swap_kb));
- } else if (key == "SwapPss") {
- RETURN_IF_ERRNO(on_optional_field_kb(&entry->swap_pss_kb));
- } else if (key == "KernelPageSize") {
- RETURN_IF_ERRNO(on_optional_field_kb(&entry->kernel_page_size_kb));
- } else if (key == "MMUPageSize") {
- RETURN_IF_ERRNO(on_optional_field_kb(&entry->mmu_page_size_kb));
- } else if (key == "Locked") {
- RETURN_IF_ERRNO(on_optional_field_kb(&entry->locked_kb));
- } else if (key == "VmFlags") {
- if (entry->vm_flags) {
- return PosixError(EINVAL, "duplicate VmFlags line");
- }
- entry->vm_flags = absl::StrSplit(key_value[1], ' ', absl::SkipEmpty());
- } else {
- on_unknown_field();
- }
- }
- RETURN_IF_ERRNO(finish_entry());
- return entries;
-};
-
-TEST(ParseProcPidSmapsTest, Correctness) {
- auto entries = ASSERT_NO_ERRNO_AND_VALUE(
- ParseProcPidSmaps("0-10000 rw-s 00000000 00:00 0 "
- " /dev/zero (deleted)\n"
- "Size: 0 kB\n"
- "Rss: 1 kB\n"
- "Pss: 2 kB\n"
- "Shared_Clean: 3 kB\n"
- "Shared_Dirty: 4 kB\n"
- "Private_Clean: 5 kB\n"
- "Private_Dirty: 6 kB\n"
- "Referenced: 7 kB\n"
- "Anonymous: 8 kB\n"
- "AnonHugePages: 9 kB\n"
- "Shared_Hugetlb: 10 kB\n"
- "Private_Hugetlb: 11 kB\n"
- "Swap: 12 kB\n"
- "SwapPss: 13 kB\n"
- "KernelPageSize: 14 kB\n"
- "MMUPageSize: 15 kB\n"
- "Locked: 16 kB\n"
- "FutureUnknownKey: 17 kB\n"
- "VmFlags: rd wr sh mr mw me ms lo ?? sd \n"));
- ASSERT_EQ(entries.size(), 1);
- auto& entry = entries[0];
- EXPECT_EQ(entry.maps_entry.filename, "/dev/zero (deleted)");
- EXPECT_EQ(entry.size_kb, 0);
- EXPECT_EQ(entry.rss_kb, 1);
- EXPECT_THAT(entry.pss_kb, Optional(2));
- EXPECT_EQ(entry.shared_clean_kb, 3);
- EXPECT_EQ(entry.shared_dirty_kb, 4);
- EXPECT_EQ(entry.private_clean_kb, 5);
- EXPECT_EQ(entry.private_dirty_kb, 6);
- EXPECT_THAT(entry.referenced_kb, Optional(7));
- EXPECT_THAT(entry.anonymous_kb, Optional(8));
- EXPECT_THAT(entry.anon_huge_pages_kb, Optional(9));
- EXPECT_THAT(entry.shared_hugetlb_kb, Optional(10));
- EXPECT_THAT(entry.private_hugetlb_kb, Optional(11));
- EXPECT_THAT(entry.swap_kb, Optional(12));
- EXPECT_THAT(entry.swap_pss_kb, Optional(13));
- EXPECT_THAT(entry.kernel_page_size_kb, Optional(14));
- EXPECT_THAT(entry.mmu_page_size_kb, Optional(15));
- EXPECT_THAT(entry.locked_kb, Optional(16));
- EXPECT_THAT(entry.vm_flags,
- Optional(ElementsAreArray({"rd", "wr", "sh", "mr", "mw", "me",
- "ms", "lo", "??", "sd"})));
-}
-
-// Returns the unique entry in entries containing the given address.
-PosixErrorOr<ProcPidSmapsEntry> FindUniqueSmapsEntry(
- std::vector<ProcPidSmapsEntry> const& entries, uintptr_t addr) {
- auto const pred = [&](ProcPidSmapsEntry const& entry) {
- return entry.maps_entry.start <= addr && addr < entry.maps_entry.end;
- };
- auto const it = std::find_if(entries.begin(), entries.end(), pred);
- if (it == entries.end()) {
- return PosixError(EINVAL,
- absl::StrFormat("no entry contains address %#x", addr));
- }
- auto const it2 = std::find_if(it + 1, entries.end(), pred);
- if (it2 != entries.end()) {
- return PosixError(
- EINVAL,
- absl::StrFormat("overlapping entries [%#x-%#x) and [%#x-%#x) both "
- "contain address %#x",
- it->maps_entry.start, it->maps_entry.end,
- it2->maps_entry.start, it2->maps_entry.end, addr));
- }
- return *it;
-}
-
-PosixErrorOr<std::vector<ProcPidSmapsEntry>> ReadProcSelfSmaps() {
- ASSIGN_OR_RETURN_ERRNO(std::string contents, GetContents("/proc/self/smaps"));
- return ParseProcPidSmaps(contents);
-}
-
-TEST(ProcPidSmapsTest, SharedAnon) {
- // Map with MAP_POPULATE so we get some RSS.
- Mapping const m = ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(
- 2 * kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE));
- auto const entries = ASSERT_NO_ERRNO_AND_VALUE(ReadProcSelfSmaps());
- auto const entry =
- ASSERT_NO_ERRNO_AND_VALUE(FindUniqueSmapsEntry(entries, m.addr()));
-
- EXPECT_EQ(entry.size_kb, m.len() / 1024);
- // It's possible that populated pages have been swapped out, so RSS might be
- // less than size.
- EXPECT_LE(entry.rss_kb, entry.size_kb);
-
- if (entry.pss_kb) {
- // PSS should be exactly equal to RSS since no other address spaces should
- // be sharing our new mapping.
- EXPECT_EQ(entry.pss_kb.value(), entry.rss_kb);
- }
-
- // "Shared" and "private" in smaps refers to whether or not *physical pages*
- // are shared; thus all pages in our MAP_SHARED mapping should nevertheless
- // be private.
- EXPECT_EQ(entry.shared_clean_kb, 0);
- EXPECT_EQ(entry.shared_dirty_kb, 0);
- EXPECT_EQ(entry.private_clean_kb + entry.private_dirty_kb, entry.rss_kb)
- << "Private_Clean = " << entry.private_clean_kb
- << " kB, Private_Dirty = " << entry.private_dirty_kb << " kB";
-
- // Shared anonymous mappings are implemented as a shmem file, so their pages
- // are not PageAnon.
- if (entry.anonymous_kb) {
- EXPECT_EQ(entry.anonymous_kb.value(), 0);
- }
-
- if (entry.vm_flags) {
- EXPECT_THAT(entry.vm_flags.value(),
- IsSupersetOf({"rd", "wr", "sh", "mr", "mw", "me", "ms"}));
- EXPECT_THAT(entry.vm_flags.value(), Not(Contains("ex")));
- }
-}
-
-TEST(ProcPidSmapsTest, PrivateAnon) {
- // Map with MAP_POPULATE so we get some RSS.
- Mapping const m = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(2 * kPageSize, PROT_WRITE, MAP_PRIVATE | MAP_POPULATE));
- auto const entries = ASSERT_NO_ERRNO_AND_VALUE(ReadProcSelfSmaps());
- auto const entry =
- ASSERT_NO_ERRNO_AND_VALUE(FindUniqueSmapsEntry(entries, m.addr()));
-
- // It's possible that our mapping was merged with another vma, so the smaps
- // entry might be bigger than our original mapping.
- EXPECT_GE(entry.size_kb, m.len() / 1024);
- EXPECT_LE(entry.rss_kb, entry.size_kb);
- if (entry.pss_kb) {
- EXPECT_LE(entry.pss_kb.value(), entry.rss_kb);
- }
-
- if (entry.anonymous_kb) {
- EXPECT_EQ(entry.anonymous_kb.value(), entry.rss_kb);
- }
-
- if (entry.vm_flags) {
- EXPECT_THAT(entry.vm_flags.value(), IsSupersetOf({"wr", "mr", "mw", "me"}));
- // We passed PROT_WRITE to mmap. On at least x86, the mapping is in
- // practice readable because there is no way to configure the MMU to make
- // pages writable but not readable. However, VmFlags should reflect the
- // flags set on the VMA, so "rd" (VM_READ) should not appear in VmFlags.
- EXPECT_THAT(entry.vm_flags.value(), Not(Contains("rd")));
- EXPECT_THAT(entry.vm_flags.value(), Not(Contains("ex")));
- EXPECT_THAT(entry.vm_flags.value(), Not(Contains("sh")));
- EXPECT_THAT(entry.vm_flags.value(), Not(Contains("ms")));
- }
-}
-
-TEST(ProcPidSmapsTest, SharedReadOnlyFile) {
- size_t const kFileSize = kPageSize;
-
- auto const temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- ASSERT_THAT(truncate(temp_file.path().c_str(), kFileSize), SyscallSucceeds());
- auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(temp_file.path(), O_RDONLY));
-
- auto const m = ASSERT_NO_ERRNO_AND_VALUE(Mmap(
- nullptr, kFileSize, PROT_READ, MAP_SHARED | MAP_POPULATE, fd.get(), 0));
- auto const entries = ASSERT_NO_ERRNO_AND_VALUE(ReadProcSelfSmaps());
- auto const entry =
- ASSERT_NO_ERRNO_AND_VALUE(FindUniqueSmapsEntry(entries, m.addr()));
-
- // Most of the same logic as the SharedAnon case applies.
- EXPECT_EQ(entry.size_kb, kFileSize / 1024);
- EXPECT_LE(entry.rss_kb, entry.size_kb);
- if (entry.pss_kb) {
- EXPECT_EQ(entry.pss_kb.value(), entry.rss_kb);
- }
- EXPECT_EQ(entry.shared_clean_kb, 0);
- EXPECT_EQ(entry.shared_dirty_kb, 0);
- EXPECT_EQ(entry.private_clean_kb + entry.private_dirty_kb, entry.rss_kb)
- << "Private_Clean = " << entry.private_clean_kb
- << " kB, Private_Dirty = " << entry.private_dirty_kb << " kB";
- if (entry.anonymous_kb) {
- EXPECT_EQ(entry.anonymous_kb.value(), 0);
- }
-
- if (entry.vm_flags) {
- EXPECT_THAT(entry.vm_flags.value(), IsSupersetOf({"rd", "mr", "me", "ms"}));
- EXPECT_THAT(entry.vm_flags.value(), Not(Contains("wr")));
- EXPECT_THAT(entry.vm_flags.value(), Not(Contains("ex")));
- // Because the mapped file was opened O_RDONLY, the VMA is !VM_MAYWRITE and
- // also !VM_SHARED.
- EXPECT_THAT(entry.vm_flags.value(), Not(Contains("sh")));
- EXPECT_THAT(entry.vm_flags.value(), Not(Contains("mw")));
- }
-}
-
-// Tests that gVisor's /proc/[pid]/smaps provides all of the fields we expect it
-// to, which as of this writing is all fields provided by Linux 4.4.
-TEST(ProcPidSmapsTest, GvisorFields) {
- SKIP_IF(!IsRunningOnGvisor());
- auto const entries = ASSERT_NO_ERRNO_AND_VALUE(ReadProcSelfSmaps());
- for (auto const& entry : entries) {
- EXPECT_TRUE(entry.pss_kb);
- EXPECT_TRUE(entry.referenced_kb);
- EXPECT_TRUE(entry.anonymous_kb);
- EXPECT_TRUE(entry.anon_huge_pages_kb);
- EXPECT_TRUE(entry.shared_hugetlb_kb);
- EXPECT_TRUE(entry.private_hugetlb_kb);
- EXPECT_TRUE(entry.swap_kb);
- EXPECT_TRUE(entry.swap_pss_kb);
- EXPECT_THAT(entry.kernel_page_size_kb, Optional(kPageSize / 1024));
- EXPECT_THAT(entry.mmu_page_size_kb, Optional(kPageSize / 1024));
- EXPECT_TRUE(entry.locked_kb);
- EXPECT_TRUE(entry.vm_flags);
- }
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/proc_pid_uid_gid_map.cc b/test/syscalls/linux/proc_pid_uid_gid_map.cc
deleted file mode 100644
index 748f7be58..000000000
--- a/test/syscalls/linux/proc_pid_uid_gid_map.cc
+++ /dev/null
@@ -1,311 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <sched.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <functional>
-#include <string>
-#include <tuple>
-#include <utility>
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "absl/strings/ascii.h"
-#include "absl/strings/str_cat.h"
-#include "absl/strings/str_split.h"
-#include "test/util/capability_util.h"
-#include "test/util/cleanup.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/logging.h"
-#include "test/util/multiprocess_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/save_util.h"
-#include "test/util/test_util.h"
-#include "test/util/time_util.h"
-
-namespace gvisor {
-namespace testing {
-
-PosixErrorOr<int> InNewUserNamespace(const std::function<void()>& fn) {
- return InForkedProcess([&] {
- TEST_PCHECK(unshare(CLONE_NEWUSER) == 0);
- MaybeSave();
- fn();
- });
-}
-
-PosixErrorOr<std::tuple<pid_t, Cleanup>> CreateProcessInNewUserNamespace() {
- int pipefd[2];
- if (pipe(pipefd) < 0) {
- return PosixError(errno, "pipe failed");
- }
- const auto cleanup_pipe_read =
- Cleanup([&] { EXPECT_THAT(close(pipefd[0]), SyscallSucceeds()); });
- auto cleanup_pipe_write =
- Cleanup([&] { EXPECT_THAT(close(pipefd[1]), SyscallSucceeds()); });
- pid_t child_pid = fork();
- if (child_pid < 0) {
- return PosixError(errno, "fork failed");
- }
- if (child_pid == 0) {
- // Close our copy of the pipe's read end, which doesn't really matter.
- TEST_PCHECK(close(pipefd[0]) >= 0);
- TEST_PCHECK(unshare(CLONE_NEWUSER) == 0);
- MaybeSave();
- // Indicate that we've switched namespaces by unblocking the parent's read.
- TEST_PCHECK(close(pipefd[1]) >= 0);
- while (true) {
- SleepSafe(absl::Minutes(1));
- }
- }
- auto cleanup_child = Cleanup([child_pid] {
- EXPECT_THAT(kill(child_pid, SIGKILL), SyscallSucceeds());
- int status;
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL)
- << "status = " << status;
- });
- // Close our copy of the pipe's write end, then wait for the child to close
- // its copy, indicating that it's switched namespaces.
- cleanup_pipe_write.Release()();
- char buf;
- if (RetryEINTR(read)(pipefd[0], &buf, 1) < 0) {
- return PosixError(errno, "reading from pipe failed");
- }
- MaybeSave();
- return std::make_tuple(child_pid, std::move(cleanup_child));
-}
-
-// TEST_CHECK-fails on error, since this function is used in contexts that
-// require async-signal-safety.
-void DenySetgroupsByPath(const char* path) {
- int fd = open(path, O_WRONLY);
- if (fd < 0 && errno == ENOENT) {
- // On kernels where this file doesn't exist, writing "deny" to it isn't
- // necessary to write to gid_map.
- return;
- }
- TEST_PCHECK(fd >= 0);
- MaybeSave();
- char deny[] = "deny";
- TEST_PCHECK(write(fd, deny, sizeof(deny)) == sizeof(deny));
- MaybeSave();
- TEST_PCHECK(close(fd) == 0);
-}
-
-void DenySelfSetgroups() { DenySetgroupsByPath("/proc/self/setgroups"); }
-
-void DenyPidSetgroups(pid_t pid) {
- DenySetgroupsByPath(absl::StrCat("/proc/", pid, "/setgroups").c_str());
-}
-
-// Returns a valid UID/GID that isn't id.
-uint32_t another_id(uint32_t id) { return (id + 1) % 65535; }
-
-struct TestParam {
- std::string desc;
- int cap;
- std::function<std::string(absl::string_view)> get_map_filename;
- std::function<uint32_t()> get_current_id;
-};
-
-std::string DescribeTestParam(const ::testing::TestParamInfo<TestParam>& info) {
- return info.param.desc;
-}
-
-std::vector<TestParam> UidGidMapTestParams() {
- return {TestParam{"UID", CAP_SETUID,
- [](absl::string_view pid) {
- return absl::StrCat("/proc/", pid, "/uid_map");
- },
- []() -> uint32_t { return getuid(); }},
- TestParam{"GID", CAP_SETGID,
- [](absl::string_view pid) {
- return absl::StrCat("/proc/", pid, "/gid_map");
- },
- []() -> uint32_t { return getgid(); }}};
-}
-
-class ProcUidGidMapTest : public ::testing::TestWithParam<TestParam> {
- protected:
- uint32_t CurrentID() { return GetParam().get_current_id(); }
-};
-
-class ProcSelfUidGidMapTest : public ProcUidGidMapTest {
- protected:
- PosixErrorOr<int> InNewUserNamespaceWithMapFD(
- const std::function<void(int)>& fn) {
- std::string map_filename = GetParam().get_map_filename("self");
- return InNewUserNamespace([&] {
- int fd = open(map_filename.c_str(), O_RDWR);
- TEST_PCHECK(fd >= 0);
- MaybeSave();
- fn(fd);
- TEST_PCHECK(close(fd) == 0);
- });
- }
-};
-
-class ProcPidUidGidMapTest : public ProcUidGidMapTest {
- protected:
- PosixErrorOr<bool> HaveSetIDCapability() {
- return HaveCapability(GetParam().cap);
- }
-
- // Returns true if the caller is running in a user namespace with all IDs
- // mapped. This matters for tests that expect to successfully map arbitrary
- // IDs into a child user namespace, since even with CAP_SET*ID this is only
- // possible if those IDs are mapped into the current one.
- PosixErrorOr<bool> AllIDsMapped() {
- ASSIGN_OR_RETURN_ERRNO(std::string id_map,
- GetContents(GetParam().get_map_filename("self")));
- absl::StripTrailingAsciiWhitespace(&id_map);
- std::vector<std::string> id_map_parts =
- absl::StrSplit(id_map, ' ', absl::SkipEmpty());
- return id_map_parts == std::vector<std::string>({"0", "0", "4294967295"});
- }
-
- PosixErrorOr<FileDescriptor> OpenMapFile(pid_t pid) {
- return Open(GetParam().get_map_filename(absl::StrCat(pid)), O_RDWR);
- }
-};
-
-TEST_P(ProcSelfUidGidMapTest, IsInitiallyEmpty) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace()));
- EXPECT_THAT(InNewUserNamespaceWithMapFD([](int fd) {
- char buf[64];
- TEST_PCHECK(read(fd, buf, sizeof(buf)) == 0);
- }),
- IsPosixErrorOkAndHolds(0));
-}
-
-TEST_P(ProcSelfUidGidMapTest, IdentityMapOwnID) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace()));
- uint32_t id = CurrentID();
- std::string line = absl::StrCat(id, " ", id, " 1");
- EXPECT_THAT(
- InNewUserNamespaceWithMapFD([&](int fd) {
- DenySelfSetgroups();
- TEST_PCHECK(write(fd, line.c_str(), line.size()) == line.size());
- }),
- IsPosixErrorOkAndHolds(0));
-}
-
-TEST_P(ProcSelfUidGidMapTest, TrailingNewlineAndNULIgnored) {
- // This is identical to IdentityMapOwnID, except that a trailing newline, NUL,
- // and an invalid (incomplete) map entry are appended to the valid entry. The
- // newline should be accepted, and everything after the NUL should be ignored.
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace()));
- uint32_t id = CurrentID();
- std::string line = absl::StrCat(id, " ", id, " 1\n\0 4 3");
- EXPECT_THAT(
- InNewUserNamespaceWithMapFD([&](int fd) {
- DenySelfSetgroups();
- // The write should return the full size of the write, even though
- // characters after the NUL were ignored.
- TEST_PCHECK(write(fd, line.c_str(), line.size()) == line.size());
- }),
- IsPosixErrorOkAndHolds(0));
-}
-
-TEST_P(ProcSelfUidGidMapTest, NonIdentityMapOwnID) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace()));
- uint32_t id = CurrentID();
- uint32_t id2 = another_id(id);
- std::string line = absl::StrCat(id2, " ", id, " 1");
- EXPECT_THAT(
- InNewUserNamespaceWithMapFD([&](int fd) {
- DenySelfSetgroups();
- TEST_PCHECK(write(fd, line.c_str(), line.size()) == line.size());
- }),
- IsPosixErrorOkAndHolds(0));
-}
-
-TEST_P(ProcSelfUidGidMapTest, MapOtherID) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace()));
- // Whether or not we have CAP_SET*ID is irrelevant: the process running in the
- // new (child) user namespace won't have any capabilities in the current
- // (parent) user namespace, which is needed.
- uint32_t id = CurrentID();
- uint32_t id2 = another_id(id);
- std::string line = absl::StrCat(id, " ", id2, " 1");
- EXPECT_THAT(InNewUserNamespaceWithMapFD([&](int fd) {
- DenySelfSetgroups();
- TEST_PCHECK(write(fd, line.c_str(), line.size()) < 0);
- TEST_CHECK(errno == EPERM);
- }),
- IsPosixErrorOkAndHolds(0));
-}
-
-INSTANTIATE_TEST_SUITE_P(All, ProcSelfUidGidMapTest,
- ::testing::ValuesIn(UidGidMapTestParams()),
- DescribeTestParam);
-
-TEST_P(ProcPidUidGidMapTest, MapOtherIDPrivileged) {
- // Like ProcSelfUidGidMapTest_MapOtherID, but since we have CAP_SET*ID in the
- // parent user namespace (this one), we can map IDs that aren't ours.
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace()));
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveSetIDCapability()));
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(AllIDsMapped()));
-
- pid_t child_pid;
- Cleanup cleanup_child;
- std::tie(child_pid, cleanup_child) =
- ASSERT_NO_ERRNO_AND_VALUE(CreateProcessInNewUserNamespace());
-
- uint32_t id = CurrentID();
- uint32_t id2 = another_id(id);
- std::string line = absl::StrCat(id, " ", id2, " 1");
- DenyPidSetgroups(child_pid);
- auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenMapFile(child_pid));
- EXPECT_THAT(write(fd.get(), line.c_str(), line.size()),
- SyscallSucceedsWithValue(line.size()));
-}
-
-TEST_P(ProcPidUidGidMapTest, MapAnyIDsPrivileged) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace()));
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveSetIDCapability()));
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(AllIDsMapped()));
-
- pid_t child_pid;
- Cleanup cleanup_child;
- std::tie(child_pid, cleanup_child) =
- ASSERT_NO_ERRNO_AND_VALUE(CreateProcessInNewUserNamespace());
-
- // Test all of:
- //
- // - Mapping ranges of length > 1
- //
- // - Mapping multiple ranges
- //
- // - Non-identity mappings
- char entries[] = "2 0 2\n4 6 2";
- DenyPidSetgroups(child_pid);
- auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenMapFile(child_pid));
- EXPECT_THAT(write(fd.get(), entries, sizeof(entries)),
- SyscallSucceedsWithValue(sizeof(entries)));
-}
-
-INSTANTIATE_TEST_SUITE_P(All, ProcPidUidGidMapTest,
- ::testing::ValuesIn(UidGidMapTestParams()),
- DescribeTestParam);
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/pselect.cc b/test/syscalls/linux/pselect.cc
deleted file mode 100644
index 4e43c4d7f..000000000
--- a/test/syscalls/linux/pselect.cc
+++ /dev/null
@@ -1,190 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <signal.h>
-#include <sys/select.h>
-
-#include "gtest/gtest.h"
-#include "absl/time/time.h"
-#include "test/syscalls/linux/base_poll_test.h"
-#include "test/util/signal_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-struct MaskWithSize {
- sigset_t* mask;
- size_t mask_size;
-};
-
-// Linux and glibc have a different idea of the sizeof sigset_t. When calling
-// the syscall directly, use what the kernel expects.
-unsigned kSigsetSize = SIGRTMAX / 8;
-
-// Linux pselect(2) differs from the glibc wrapper function in that Linux
-// updates the timeout with the amount of time remaining. In order to test this
-// behavior we need to use the syscall directly.
-int syscallPselect6(int nfds, fd_set* readfds, fd_set* writefds,
- fd_set* exceptfds, struct timespec* timeout,
- const MaskWithSize* mask_with_size) {
- return syscall(SYS_pselect6, nfds, readfds, writefds, exceptfds, timeout,
- mask_with_size);
-}
-
-class PselectTest : public BasePollTest {
- protected:
- void SetUp() override { BasePollTest::SetUp(); }
- void TearDown() override { BasePollTest::TearDown(); }
-};
-
-// See that when there are no FD sets, pselect behaves like sleep.
-TEST_F(PselectTest, NullFds) {
- struct timespec timeout = absl::ToTimespec(absl::Milliseconds(10));
- ASSERT_THAT(syscallPselect6(0, nullptr, nullptr, nullptr, &timeout, nullptr),
- SyscallSucceeds());
- EXPECT_EQ(timeout.tv_sec, 0);
- EXPECT_EQ(timeout.tv_nsec, 0);
-
- timeout = absl::ToTimespec(absl::Milliseconds(10));
- ASSERT_THAT(syscallPselect6(1, nullptr, nullptr, nullptr, &timeout, nullptr),
- SyscallSucceeds());
- EXPECT_EQ(timeout.tv_sec, 0);
- EXPECT_EQ(timeout.tv_nsec, 0);
-}
-
-TEST_F(PselectTest, ClosedFds) {
- fd_set read_set;
- FD_ZERO(&read_set);
- int fd;
- ASSERT_THAT(fd = dup(1), SyscallSucceeds());
- ASSERT_THAT(close(fd), SyscallSucceeds());
- FD_SET(fd, &read_set);
- struct timespec timeout = absl::ToTimespec(absl::Milliseconds(10));
- EXPECT_THAT(
- syscallPselect6(fd + 1, &read_set, nullptr, nullptr, &timeout, nullptr),
- SyscallFailsWithErrno(EBADF));
-}
-
-TEST_F(PselectTest, ZeroTimeout) {
- struct timespec timeout = {};
- ASSERT_THAT(syscallPselect6(1, nullptr, nullptr, nullptr, &timeout, nullptr),
- SyscallSucceeds());
- EXPECT_EQ(timeout.tv_sec, 0);
- EXPECT_EQ(timeout.tv_nsec, 0);
-}
-
-// If random S/R interrupts the pselect, SIGALRM may be delivered before pselect
-// restarts, causing the pselect to hang forever.
-TEST_F(PselectTest, NoTimeout_NoRandomSave) {
- // When there's no timeout, pselect may never return so set a timer.
- SetTimer(absl::Milliseconds(100));
- // See that we get interrupted by the timer.
- ASSERT_THAT(syscallPselect6(1, nullptr, nullptr, nullptr, nullptr, nullptr),
- SyscallFailsWithErrno(EINTR));
- EXPECT_TRUE(TimerFired());
-}
-
-TEST_F(PselectTest, InvalidTimeoutNegative) {
- struct timespec timeout = absl::ToTimespec(absl::Seconds(-1));
- ASSERT_THAT(syscallPselect6(1, nullptr, nullptr, nullptr, &timeout, nullptr),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_EQ(timeout.tv_sec, -1);
- EXPECT_EQ(timeout.tv_nsec, 0);
-}
-
-TEST_F(PselectTest, InvalidTimeoutNotNormalized) {
- struct timespec timeout = {0, 1000000001};
- ASSERT_THAT(syscallPselect6(1, nullptr, nullptr, nullptr, &timeout, nullptr),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_EQ(timeout.tv_sec, 0);
- EXPECT_EQ(timeout.tv_nsec, 1000000001);
-}
-
-TEST_F(PselectTest, EmptySigMaskInvalidMaskSize) {
- struct timespec timeout = {};
- MaskWithSize invalid = {nullptr, 7};
- EXPECT_THAT(syscallPselect6(0, nullptr, nullptr, nullptr, &timeout, &invalid),
- SyscallSucceeds());
-}
-
-TEST_F(PselectTest, EmptySigMaskValidMaskSize) {
- struct timespec timeout = {};
- MaskWithSize invalid = {nullptr, 8};
- EXPECT_THAT(syscallPselect6(0, nullptr, nullptr, nullptr, &timeout, &invalid),
- SyscallSucceeds());
-}
-
-TEST_F(PselectTest, InvalidMaskSize) {
- struct timespec timeout = {};
- sigset_t sigmask;
- ASSERT_THAT(sigemptyset(&sigmask), SyscallSucceeds());
- MaskWithSize invalid = {&sigmask, 7};
- EXPECT_THAT(syscallPselect6(1, nullptr, nullptr, nullptr, &timeout, &invalid),
- SyscallFailsWithErrno(EINVAL));
-}
-
-// Verify that signals blocked by the pselect mask (that would otherwise be
-// allowed) do not interrupt pselect.
-TEST_F(PselectTest, SignalMaskBlocksSignal) {
- absl::Duration duration(absl::Seconds(30));
- struct timespec timeout = absl::ToTimespec(duration);
- absl::Duration timer_duration(absl::Seconds(10));
-
- // Call with a mask that blocks SIGALRM. See that pselect is not interrupted
- // (i.e. returns 0) and that upon completion, the timer has fired.
- sigset_t mask;
- ASSERT_THAT(sigprocmask(0, nullptr, &mask), SyscallSucceeds());
- ASSERT_THAT(sigaddset(&mask, SIGALRM), SyscallSucceeds());
- MaskWithSize mask_with_size = {&mask, kSigsetSize};
- SetTimer(timer_duration);
- MaybeSave();
- ASSERT_FALSE(TimerFired());
- ASSERT_THAT(
- syscallPselect6(1, nullptr, nullptr, nullptr, &timeout, &mask_with_size),
- SyscallSucceeds());
- EXPECT_TRUE(TimerFired());
- EXPECT_EQ(absl::DurationFromTimespec(timeout), absl::Duration());
-}
-
-// Verify that signals allowed by the pselect mask (that would otherwise be
-// blocked) interrupt pselect.
-TEST_F(PselectTest, SignalMaskAllowsSignal) {
- absl::Duration duration = absl::Seconds(30);
- struct timespec timeout = absl::ToTimespec(duration);
- absl::Duration timer_duration = absl::Seconds(10);
-
- sigset_t mask;
- ASSERT_THAT(sigprocmask(0, nullptr, &mask), SyscallSucceeds());
-
- // Block SIGALRM.
- auto cleanup =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_BLOCK, SIGALRM));
-
- // Call with a mask that unblocks SIGALRM. See that pselect is interrupted.
- MaskWithSize mask_with_size = {&mask, kSigsetSize};
- SetTimer(timer_duration);
- MaybeSave();
- ASSERT_FALSE(TimerFired());
- ASSERT_THAT(
- syscallPselect6(1, nullptr, nullptr, nullptr, &timeout, &mask_with_size),
- SyscallFailsWithErrno(EINTR));
- EXPECT_TRUE(TimerFired());
- EXPECT_GT(absl::DurationFromTimespec(timeout), absl::Duration());
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/ptrace.cc b/test/syscalls/linux/ptrace.cc
deleted file mode 100644
index abf2b1a04..000000000
--- a/test/syscalls/linux/ptrace.cc
+++ /dev/null
@@ -1,1214 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <elf.h>
-#include <signal.h>
-#include <stddef.h>
-#include <sys/ptrace.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <sys/user.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include <iostream>
-#include <utility>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/logging.h"
-#include "test/util/multiprocess_util.h"
-#include "test/util/signal_util.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-#include "test/util/time_util.h"
-
-DEFINE_bool(ptrace_test_execve_child, false,
- "If true, run the "
- "PtraceExecveTest_Execve_GetRegs_PeekUser_SIGKILL_TraceClone_"
- "TraceExit child workload.");
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// PTRACE_GETSIGMASK and PTRACE_SETSIGMASK are not defined until glibc 2.23
-// (fb53a27c5741 "Add new header definitions from Linux 4.4 (plus older ptrace
-// definitions)").
-constexpr auto kPtraceGetSigMask = static_cast<__ptrace_request>(0x420a);
-constexpr auto kPtraceSetSigMask = static_cast<__ptrace_request>(0x420b);
-
-// PTRACE_SYSEMU is not defined until glibc 2.27 (c48831d0eebf "linux/x86: sync
-// sys/ptrace.h with Linux 4.14 [BZ #22433]").
-constexpr auto kPtraceSysemu = static_cast<__ptrace_request>(31);
-
-// PTRACE_EVENT_STOP is not defined until glibc 2.26 (3f67d1a7021e "Add Linux
-// PTRACE_EVENT_STOP").
-constexpr int kPtraceEventStop = 128;
-
-// Sends sig to the current process with tgkill(2).
-//
-// glibc's raise(2) may change the signal mask before sending the signal. These
-// extra syscalls make tests of syscall, signal interception, etc. difficult to
-// write.
-void RaiseSignal(int sig) {
- pid_t pid = getpid();
- TEST_PCHECK(pid > 0);
- pid_t tid = gettid();
- TEST_PCHECK(tid > 0);
- TEST_PCHECK(tgkill(pid, tid, sig) == 0);
-}
-
-// Returns the Yama ptrace scope.
-PosixErrorOr<int> YamaPtraceScope() {
- constexpr char kYamaPtraceScopePath[] = "/proc/sys/kernel/yama/ptrace_scope";
-
- ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(kYamaPtraceScopePath));
- if (!exists) {
- // File doesn't exist means no Yama, so the scope is disabled -> 0.
- return 0;
- }
-
- std::string contents;
- RETURN_IF_ERRNO(GetContents(kYamaPtraceScopePath, &contents));
-
- int scope;
- if (!absl::SimpleAtoi(contents, &scope)) {
- return PosixError(EINVAL, absl::StrCat(contents, ": not a valid number"));
- }
-
- return scope;
-}
-
-TEST(PtraceTest, AttachSelf) {
- EXPECT_THAT(ptrace(PTRACE_ATTACH, gettid(), 0, 0),
- SyscallFailsWithErrno(EPERM));
-}
-
-TEST(PtraceTest, AttachSameThreadGroup) {
- pid_t const tid = gettid();
- ScopedThread([&] {
- EXPECT_THAT(ptrace(PTRACE_ATTACH, tid, 0, 0), SyscallFailsWithErrno(EPERM));
- });
-}
-
-TEST(PtraceTest, AttachParent_PeekData_PokeData_SignalSuppression) {
- // Yama prevents attaching to a parent. Skip the test if the scope is anything
- // except disabled.
- SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) > 0);
-
- constexpr long kBeforePokeDataValue = 10;
- constexpr long kAfterPokeDataValue = 20;
-
- volatile long word = kBeforePokeDataValue;
-
- pid_t const child_pid = fork();
- if (child_pid == 0) {
- // In child process.
-
- // Attach to the parent.
- pid_t const parent_pid = getppid();
- TEST_PCHECK(ptrace(PTRACE_ATTACH, parent_pid, 0, 0) == 0);
- MaybeSave();
-
- // Block until the parent enters signal-delivery-stop as a result of the
- // SIGSTOP sent by PTRACE_ATTACH.
- int status;
- TEST_PCHECK(waitpid(parent_pid, &status, 0) == parent_pid);
- MaybeSave();
- TEST_CHECK(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP);
-
- // Replace the value of word in the parent process with kAfterPokeDataValue.
- long const parent_word = ptrace(PTRACE_PEEKDATA, parent_pid, &word, 0);
- MaybeSave();
- TEST_CHECK(parent_word == kBeforePokeDataValue);
- TEST_PCHECK(
- ptrace(PTRACE_POKEDATA, parent_pid, &word, kAfterPokeDataValue) == 0);
- MaybeSave();
-
- // Detach from the parent and suppress the SIGSTOP. If the SIGSTOP is not
- // suppressed, the parent will hang in group-stop, causing the test to time
- // out.
- TEST_PCHECK(ptrace(PTRACE_DETACH, parent_pid, 0, 0) == 0);
- MaybeSave();
- _exit(0);
- }
- // In parent process.
- ASSERT_THAT(child_pid, SyscallSucceeds());
-
- // Wait for the child to complete.
- int status;
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << " status " << status;
-
- // Check that the child's PTRACE_POKEDATA was effective.
- EXPECT_EQ(kAfterPokeDataValue, word);
-}
-
-TEST(PtraceTest, GetSigMask) {
- // glibc and the Linux kernel define a sigset_t with different sizes. To avoid
- // creating a kernel_sigset_t and recreating all the modification functions
- // (sigemptyset, etc), we just hardcode the kernel sigset size.
- constexpr int kSizeofKernelSigset = 8;
- constexpr int kBlockSignal = SIGUSR1;
- sigset_t blocked;
- sigemptyset(&blocked);
- sigaddset(&blocked, kBlockSignal);
-
- pid_t const child_pid = fork();
- if (child_pid == 0) {
- // In child process.
-
- // Install a signal handler for kBlockSignal to avoid termination and block
- // it.
- TEST_PCHECK(signal(kBlockSignal, +[](int signo) {}) != SIG_ERR);
- MaybeSave();
- TEST_PCHECK(sigprocmask(SIG_SETMASK, &blocked, nullptr) == 0);
- MaybeSave();
-
- // Enable tracing.
- TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0);
- MaybeSave();
-
- // This should be blocked.
- RaiseSignal(kBlockSignal);
-
- // This should be suppressed by parent, who will change signal mask in the
- // meantime, which means kBlockSignal should be delivered once this resumes.
- RaiseSignal(SIGSTOP);
-
- _exit(0);
- }
- // In parent process.
- ASSERT_THAT(child_pid, SyscallSucceeds());
-
- // Wait for the child to send itself SIGSTOP and enter signal-delivery-stop.
- int status;
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
- << " status " << status;
-
- // Get current signal mask.
- sigset_t set;
- EXPECT_THAT(ptrace(kPtraceGetSigMask, child_pid, kSizeofKernelSigset, &set),
- SyscallSucceeds());
- EXPECT_THAT(blocked, EqualsSigset(set));
-
- // Try to get current signal mask with bad size argument.
- EXPECT_THAT(ptrace(kPtraceGetSigMask, child_pid, 0, nullptr),
- SyscallFailsWithErrno(EINVAL));
-
- // Try to set bad signal mask.
- sigset_t* bad_addr = reinterpret_cast<sigset_t*>(-1);
- EXPECT_THAT(
- ptrace(kPtraceSetSigMask, child_pid, kSizeofKernelSigset, bad_addr),
- SyscallFailsWithErrno(EFAULT));
-
- // Set signal mask to empty set.
- sigset_t set1;
- sigemptyset(&set1);
- EXPECT_THAT(ptrace(kPtraceSetSigMask, child_pid, kSizeofKernelSigset, &set1),
- SyscallSucceeds());
-
- // Suppress SIGSTOP and resume the child. It should re-enter
- // signal-delivery-stop for kBlockSignal.
- ASSERT_THAT(ptrace(PTRACE_CONT, child_pid, 0, 0), SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == kBlockSignal)
- << " status " << status;
-
- ASSERT_THAT(ptrace(PTRACE_CONT, child_pid, 0, 0), SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- // Let's see that process exited normally.
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << " status " << status;
-}
-
-TEST(PtraceTest, GetSiginfo_SetSiginfo_SignalInjection) {
- constexpr int kOriginalSigno = SIGUSR1;
- constexpr int kInjectedSigno = SIGUSR2;
-
- pid_t const child_pid = fork();
- if (child_pid == 0) {
- // In child process.
-
- // Override all signal handlers.
- struct sigaction sa = {};
- sa.sa_handler = +[](int signo) { _exit(signo); };
- TEST_PCHECK(sigfillset(&sa.sa_mask) == 0);
- for (int signo = 1; signo < 32; signo++) {
- if (signo == SIGKILL || signo == SIGSTOP) {
- continue;
- }
- TEST_PCHECK(sigaction(signo, &sa, nullptr) == 0);
- }
- for (int signo = SIGRTMIN; signo <= SIGRTMAX; signo++) {
- TEST_PCHECK(sigaction(signo, &sa, nullptr) == 0);
- }
-
- // Unblock all signals.
- TEST_PCHECK(sigprocmask(SIG_UNBLOCK, &sa.sa_mask, nullptr) == 0);
- MaybeSave();
-
- // Send ourselves kOriginalSignal while ptraced and exit with the signal we
- // actually receive via the signal handler, if any, or 0 if we don't receive
- // a signal.
- TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0);
- MaybeSave();
- RaiseSignal(kOriginalSigno);
- _exit(0);
- }
- // In parent process.
- ASSERT_THAT(child_pid, SyscallSucceeds());
-
- // Wait for the child to send itself kOriginalSigno and enter
- // signal-delivery-stop.
- int status;
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == kOriginalSigno)
- << " status " << status;
-
- siginfo_t siginfo = {};
- ASSERT_THAT(ptrace(PTRACE_GETSIGINFO, child_pid, 0, &siginfo),
- SyscallSucceeds());
- EXPECT_EQ(kOriginalSigno, siginfo.si_signo);
- EXPECT_EQ(SI_TKILL, siginfo.si_code);
-
- // Replace the signal with kInjectedSigno, and check that the child exits
- // with kInjectedSigno, indicating that signal injection was successful.
- siginfo.si_signo = kInjectedSigno;
- ASSERT_THAT(ptrace(PTRACE_SETSIGINFO, child_pid, 0, &siginfo),
- SyscallSucceeds());
- ASSERT_THAT(ptrace(PTRACE_DETACH, child_pid, 0, kInjectedSigno),
- SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == kInjectedSigno)
- << " status " << status;
-}
-
-TEST(PtraceTest, SIGKILLDoesNotCauseSignalDeliveryStop) {
- pid_t const child_pid = fork();
- if (child_pid == 0) {
- // In child process.
- TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0);
- MaybeSave();
- RaiseSignal(SIGKILL);
- TEST_CHECK_MSG(false, "Survived SIGKILL?");
- _exit(1);
- }
- // In parent process.
- ASSERT_THAT(child_pid, SyscallSucceeds());
-
- // Expect the child to die to SIGKILL without entering signal-delivery-stop.
- int status;
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL)
- << " status " << status;
-}
-
-TEST(PtraceTest, PtraceKill) {
- constexpr int kOriginalSigno = SIGUSR1;
-
- pid_t const child_pid = fork();
- if (child_pid == 0) {
- // In child process.
- TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0);
- MaybeSave();
-
- // PTRACE_KILL only works if tracee has entered signal-delivery-stop.
- RaiseSignal(kOriginalSigno);
- TEST_CHECK_MSG(false, "Failed to kill the process?");
- _exit(0);
- }
- // In parent process.
- ASSERT_THAT(child_pid, SyscallSucceeds());
-
- // Wait for the child to send itself kOriginalSigno and enter
- // signal-delivery-stop.
- int status;
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == kOriginalSigno)
- << " status " << status;
-
- ASSERT_THAT(ptrace(PTRACE_KILL, child_pid, 0, 0), SyscallSucceeds());
-
- // Expect the child to die with SIGKILL.
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL)
- << " status " << status;
-}
-
-TEST(PtraceTest, GetRegSet) {
- pid_t const child_pid = fork();
- if (child_pid == 0) {
- // In child process.
-
- // Enable tracing.
- TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0);
- MaybeSave();
-
- // Use kill explicitly because we check the syscall argument register below.
- kill(getpid(), SIGSTOP);
-
- _exit(0);
- }
- // In parent process.
- ASSERT_THAT(child_pid, SyscallSucceeds());
-
- // Wait for the child to send itself SIGSTOP and enter signal-delivery-stop.
- int status;
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
- << " status " << status;
-
- // Get the general registers.
- struct user_regs_struct regs;
- struct iovec iov;
- iov.iov_base = &regs;
- iov.iov_len = sizeof(regs);
- EXPECT_THAT(ptrace(PTRACE_GETREGSET, child_pid, NT_PRSTATUS, &iov),
- SyscallSucceeds());
-
- // Read exactly the full register set.
- EXPECT_EQ(iov.iov_len, sizeof(regs));
-
-#ifdef __x86_64__
- // Child called kill(2), with SIGSTOP as arg 2.
- EXPECT_EQ(regs.rsi, SIGSTOP);
-#endif
-
- // Suppress SIGSTOP and resume the child.
- ASSERT_THAT(ptrace(PTRACE_CONT, child_pid, 0, 0), SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- // Let's see that process exited normally.
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << " status " << status;
-}
-
-TEST(PtraceTest, AttachingConvertsGroupStopToPtraceStop) {
- pid_t const child_pid = fork();
- if (child_pid == 0) {
- // In child process.
- while (true) {
- pause();
- }
- }
- // In parent process.
- ASSERT_THAT(child_pid, SyscallSucceeds());
-
- // SIGSTOP the child and wait for it to stop.
- ASSERT_THAT(kill(child_pid, SIGSTOP), SyscallSucceeds());
- int status;
- ASSERT_THAT(waitpid(child_pid, &status, WUNTRACED),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
- << " status " << status;
-
- // Attach to the child and expect it to re-enter a traced group-stop despite
- // already being stopped.
- ASSERT_THAT(ptrace(PTRACE_ATTACH, child_pid, 0, 0), SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
- << " status " << status;
-
- // Verify that the child is ptrace-stopped by checking that it can receive
- // ptrace commands requiring a ptrace-stop.
- EXPECT_THAT(ptrace(PTRACE_SETOPTIONS, child_pid, 0, 0), SyscallSucceeds());
-
- // Group-stop is distinguished from signal-delivery-stop by PTRACE_GETSIGINFO
- // failing with EINVAL.
- siginfo_t siginfo = {};
- EXPECT_THAT(ptrace(PTRACE_GETSIGINFO, child_pid, 0, &siginfo),
- SyscallFailsWithErrno(EINVAL));
-
- // Detach from the child and expect it to stay stopped without a notification.
- ASSERT_THAT(ptrace(PTRACE_DETACH, child_pid, 0, 0), SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, WUNTRACED | WNOHANG),
- SyscallSucceedsWithValue(0));
-
- // Sending it SIGCONT should cause it to leave its stop.
- ASSERT_THAT(kill(child_pid, SIGCONT), SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, WCONTINUED),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFCONTINUED(status)) << " status " << status;
-
- // Clean up the child.
- ASSERT_THAT(kill(child_pid, SIGKILL), SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL)
- << " status " << status;
-}
-
-// Fixture for tests parameterized by whether or not to use PTRACE_O_TRACEEXEC.
-class PtraceExecveTest : public ::testing::TestWithParam<bool> {
- protected:
- bool TraceExec() const { return GetParam(); }
-};
-
-TEST_P(PtraceExecveTest, Execve_GetRegs_PeekUser_SIGKILL_TraceClone_TraceExit) {
- ExecveArray const owned_child_argv = {"/proc/self/exe",
- "--ptrace_test_execve_child"};
- char* const* const child_argv = owned_child_argv.get();
-
- pid_t const child_pid = fork();
- if (child_pid == 0) {
- // In child process. The test relies on calling execve() in a non-leader
- // thread; pthread_create() isn't async-signal-safe, so the safest way to
- // do this is to execve() first, then enable tracing and run the expected
- // child process behavior in the new subprocess.
- execve(child_argv[0], child_argv, /* envp = */ nullptr);
- TEST_PCHECK_MSG(false, "Survived execve to test child");
- }
- // In parent process.
- ASSERT_THAT(child_pid, SyscallSucceeds());
-
- // Wait for the child to send itself SIGSTOP and enter signal-delivery-stop.
- int status;
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
- << " status " << status;
-
- // Enable PTRACE_O_TRACECLONE so we can get the ID of the child's non-leader
- // thread, PTRACE_O_TRACEEXIT so we can observe the leader's death, and
- // PTRACE_O_TRACEEXEC if required by the test. (The leader doesn't call
- // execve, but options should be inherited across clone.)
- long opts = PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXIT;
- if (TraceExec()) {
- opts |= PTRACE_O_TRACEEXEC;
- }
- ASSERT_THAT(ptrace(PTRACE_SETOPTIONS, child_pid, 0, opts), SyscallSucceeds());
-
- // Suppress the SIGSTOP and wait for the child's leader thread to report
- // PTRACE_EVENT_CLONE. Get the new thread's ID from the event.
- ASSERT_THAT(ptrace(PTRACE_CONT, child_pid, 0, 0), SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_EQ(SIGTRAP | (PTRACE_EVENT_CLONE << 8), status >> 8);
- unsigned long eventmsg;
- ASSERT_THAT(ptrace(PTRACE_GETEVENTMSG, child_pid, 0, &eventmsg),
- SyscallSucceeds());
- pid_t const nonleader_tid = eventmsg;
- pid_t const leader_tid = child_pid;
-
- // The new thread should be ptraced and in signal-delivery-stop by SIGSTOP due
- // to PTRACE_O_TRACECLONE.
- //
- // Before bf959931ddb88c4e4366e96dd22e68fa0db9527c "wait/ptrace: assume __WALL
- // if the child is traced" (4.7) , waiting on it requires __WCLONE since, as a
- // non-leader, its termination signal is 0. After, a standard wait is
- // sufficient.
- ASSERT_THAT(waitpid(nonleader_tid, &status, __WCLONE),
- SyscallSucceedsWithValue(nonleader_tid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
- << " status " << status;
-
- // Resume both child threads.
- for (pid_t const tid : {leader_tid, nonleader_tid}) {
- ASSERT_THAT(ptrace(PTRACE_CONT, tid, 0, 0), SyscallSucceeds());
- }
-
- // The non-leader child thread should call execve, causing the leader thread
- // to enter PTRACE_EVENT_EXIT with an apparent exit code of 0. At this point,
- // the leader has not yet exited, so the non-leader should be blocked in
- // execve.
- ASSERT_THAT(waitpid(leader_tid, &status, 0),
- SyscallSucceedsWithValue(leader_tid));
- EXPECT_EQ(SIGTRAP | (PTRACE_EVENT_EXIT << 8), status >> 8);
- ASSERT_THAT(ptrace(PTRACE_GETEVENTMSG, leader_tid, 0, &eventmsg),
- SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(eventmsg) && WEXITSTATUS(eventmsg) == 0)
- << " eventmsg " << eventmsg;
- EXPECT_THAT(waitpid(nonleader_tid, &status, __WCLONE | WNOHANG),
- SyscallSucceedsWithValue(0));
-
- // Allow the leader to continue exiting. This should allow the non-leader to
- // complete its execve, causing the original leader to be reaped without
- // further notice and the non-leader to steal its ID.
- ASSERT_THAT(ptrace(PTRACE_CONT, leader_tid, 0, 0), SyscallSucceeds());
- ASSERT_THAT(waitpid(leader_tid, &status, 0),
- SyscallSucceedsWithValue(leader_tid));
- if (TraceExec()) {
- // If PTRACE_O_TRACEEXEC was enabled, the execing thread should be in
- // PTRACE_EVENT_EXEC-stop, with the event message set to its old thread ID.
- EXPECT_EQ(SIGTRAP | (PTRACE_EVENT_EXEC << 8), status >> 8);
- ASSERT_THAT(ptrace(PTRACE_GETEVENTMSG, leader_tid, 0, &eventmsg),
- SyscallSucceeds());
- EXPECT_EQ(nonleader_tid, eventmsg);
- } else {
- // Otherwise, the execing thread should have received SIGTRAP and should now
- // be in signal-delivery-stop.
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP)
- << " status " << status;
- }
-
-#ifdef __x86_64__
- {
- // CS should be 0x33, indicating an 64-bit binary.
- constexpr uint64_t kAMD64UserCS = 0x33;
- EXPECT_THAT(ptrace(PTRACE_PEEKUSER, leader_tid,
- offsetof(struct user_regs_struct, cs), 0),
- SyscallSucceedsWithValue(kAMD64UserCS));
- struct user_regs_struct regs = {};
- ASSERT_THAT(ptrace(PTRACE_GETREGS, leader_tid, 0, &regs),
- SyscallSucceeds());
- EXPECT_EQ(kAMD64UserCS, regs.cs);
- }
-#endif // defined(__x86_64__)
-
- // PTRACE_O_TRACEEXIT should have been inherited across execve. Send SIGKILL,
- // which should end the PTRACE_EVENT_EXEC-stop or signal-delivery-stop and
- // leave the child in PTRACE_EVENT_EXIT-stop.
- ASSERT_THAT(kill(leader_tid, SIGKILL), SyscallSucceeds());
- ASSERT_THAT(waitpid(leader_tid, &status, 0),
- SyscallSucceedsWithValue(leader_tid));
- EXPECT_EQ(SIGTRAP | (PTRACE_EVENT_EXIT << 8), status >> 8);
- ASSERT_THAT(ptrace(PTRACE_GETEVENTMSG, leader_tid, 0, &eventmsg),
- SyscallSucceeds());
- EXPECT_TRUE(WIFSIGNALED(eventmsg) && WTERMSIG(eventmsg) == SIGKILL)
- << " eventmsg " << eventmsg;
-
- // End the PTRACE_EVENT_EXIT stop, allowing the child to exit.
- ASSERT_THAT(ptrace(PTRACE_CONT, leader_tid, 0, 0), SyscallSucceeds());
- ASSERT_THAT(waitpid(leader_tid, &status, 0),
- SyscallSucceedsWithValue(leader_tid));
- EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL)
- << " status " << status;
-}
-
-[[noreturn]] void RunExecveChild() {
- // Enable tracing, then raise SIGSTOP and expect our parent to suppress it.
- TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0);
- MaybeSave();
- RaiseSignal(SIGSTOP);
- MaybeSave();
-
- // Call execve() in a non-leader thread. As long as execve() succeeds, what
- // exactly we execve() shouldn't really matter, since the tracer should kill
- // us after execve() completes.
- ScopedThread t([&] {
- ExecveArray const owned_child_argv = {"/proc/self/exe",
- "--this_flag_shouldnt_exist"};
- char* const* const child_argv = owned_child_argv.get();
- execve(child_argv[0], child_argv, /* envp = */ nullptr);
- TEST_PCHECK_MSG(false, "Survived execve? (thread)");
- });
- t.Join();
- TEST_CHECK_MSG(false, "Survived execve? (main)");
- _exit(1);
-}
-
-INSTANTIATE_TEST_SUITE_P(TraceExec, PtraceExecveTest, ::testing::Bool());
-
-// This test has expectations on when syscall-enter/exit-stops occur that are
-// violated if saving occurs, since saving interrupts all syscalls, causing
-// premature syscall-exit.
-TEST(PtraceTest,
- ExitWhenParentIsNotTracer_Syscall_TraceVfork_TraceVforkDone_NoRandomSave) {
- constexpr int kExitTraceeExitCode = 99;
-
- pid_t const child_pid = fork();
- if (child_pid == 0) {
- // In child process.
-
- // Block SIGCHLD so it doesn't interrupt wait4.
- sigset_t mask;
- TEST_PCHECK(sigemptyset(&mask) == 0);
- TEST_PCHECK(sigaddset(&mask, SIGCHLD) == 0);
- TEST_PCHECK(sigprocmask(SIG_SETMASK, &mask, nullptr) == 0);
- MaybeSave();
-
- // Enable tracing, then raise SIGSTOP and expect our parent to suppress it.
- TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0);
- MaybeSave();
- RaiseSignal(SIGSTOP);
- MaybeSave();
-
- // Spawn a vfork child that exits immediately, and reap it. Don't save
- // after vfork since the parent expects to see wait4 as the next syscall.
- pid_t const pid = vfork();
- if (pid == 0) {
- _exit(kExitTraceeExitCode);
- }
- TEST_PCHECK_MSG(pid > 0, "vfork failed");
-
- int status;
- TEST_PCHECK(wait4(pid, &status, 0, nullptr) > 0);
- MaybeSave();
- TEST_CHECK(WIFEXITED(status) && WEXITSTATUS(status) == kExitTraceeExitCode);
- _exit(0);
- }
- // In parent process.
- ASSERT_THAT(child_pid, SyscallSucceeds());
-
- // Wait for the child to send itself SIGSTOP and enter signal-delivery-stop.
- int status;
- ASSERT_THAT(child_pid, SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
- << " status " << status;
-
- // Enable PTRACE_O_TRACEVFORK so we can get the ID of the grandchild,
- // PTRACE_O_TRACEVFORKDONE so we can observe PTRACE_EVENT_VFORK_DONE, and
- // PTRACE_O_TRACESYSGOOD so syscall-enter/exit-stops are unambiguously
- // indicated by a stop signal of SIGTRAP|0x80 rather than just SIGTRAP.
- ASSERT_THAT(ptrace(PTRACE_SETOPTIONS, child_pid, 0,
- PTRACE_O_TRACEVFORK | PTRACE_O_TRACEVFORKDONE |
- PTRACE_O_TRACESYSGOOD),
- SyscallSucceeds());
-
- // Suppress the SIGSTOP and wait for the child to report PTRACE_EVENT_VFORK.
- // Get the new process' ID from the event.
- ASSERT_THAT(ptrace(PTRACE_CONT, child_pid, 0, 0), SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_EQ(SIGTRAP | (PTRACE_EVENT_VFORK << 8), status >> 8);
- unsigned long eventmsg;
- ASSERT_THAT(ptrace(PTRACE_GETEVENTMSG, child_pid, 0, &eventmsg),
- SyscallSucceeds());
- pid_t const grandchild_pid = eventmsg;
-
- // The grandchild should be traced by us and in signal-delivery-stop by
- // SIGSTOP due to PTRACE_O_TRACEVFORK. This allows us to wait on it even
- // though we're not its parent.
- ASSERT_THAT(waitpid(grandchild_pid, &status, 0),
- SyscallSucceedsWithValue(grandchild_pid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
- << " status " << status;
-
- // Resume the child with PTRACE_SYSCALL. Since the grandchild is still in
- // signal-delivery-stop, the child should remain in vfork() waiting for the
- // grandchild to exec or exit.
- ASSERT_THAT(ptrace(PTRACE_SYSCALL, child_pid, 0, 0), SyscallSucceeds());
- absl::SleepFor(absl::Seconds(1));
- ASSERT_THAT(waitpid(child_pid, &status, WNOHANG),
- SyscallSucceedsWithValue(0));
-
- // Suppress the grandchild's SIGSTOP and wait for the grandchild to exit. Pass
- // WNOWAIT to waitid() so that we don't acknowledge the grandchild's exit yet.
- ASSERT_THAT(ptrace(PTRACE_CONT, grandchild_pid, 0, 0), SyscallSucceeds());
- siginfo_t siginfo = {};
- ASSERT_THAT(waitid(P_PID, grandchild_pid, &siginfo, WEXITED | WNOWAIT),
- SyscallSucceeds());
- EXPECT_EQ(SIGCHLD, siginfo.si_signo);
- EXPECT_EQ(CLD_EXITED, siginfo.si_code);
- EXPECT_EQ(kExitTraceeExitCode, siginfo.si_status);
- EXPECT_EQ(grandchild_pid, siginfo.si_pid);
- EXPECT_EQ(getuid(), siginfo.si_uid);
-
- // The child should now be in PTRACE_EVENT_VFORK_DONE stop. The event
- // message should still be the grandchild's PID.
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_EQ(SIGTRAP | (PTRACE_EVENT_VFORK_DONE << 8), status >> 8);
- ASSERT_THAT(ptrace(PTRACE_GETEVENTMSG, child_pid, 0, &eventmsg),
- SyscallSucceeds());
- EXPECT_EQ(grandchild_pid, eventmsg);
-
- // Resume the child with PTRACE_SYSCALL again and expect it to enter
- // syscall-exit-stop for vfork() or clone(), either of which should return the
- // grandchild's PID from the syscall. Aside from PTRACE_O_TRACESYSGOOD,
- // syscall-stops are distinguished from signal-delivery-stop by
- // PTRACE_GETSIGINFO returning a siginfo for which si_code == SIGTRAP or
- // SIGTRAP|0x80.
- ASSERT_THAT(ptrace(PTRACE_SYSCALL, child_pid, 0, 0), SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80))
- << " status " << status;
- ASSERT_THAT(ptrace(PTRACE_GETSIGINFO, child_pid, 0, &siginfo),
- SyscallSucceeds());
- EXPECT_TRUE(siginfo.si_code == SIGTRAP || siginfo.si_code == (SIGTRAP | 0x80))
- << "si_code = " << siginfo.si_code;
-#ifdef __x86_64__
- {
- struct user_regs_struct regs = {};
- ASSERT_THAT(ptrace(PTRACE_GETREGS, child_pid, 0, &regs), SyscallSucceeds());
- EXPECT_TRUE(regs.orig_rax == SYS_vfork || regs.orig_rax == SYS_clone)
- << "orig_rax = " << regs.orig_rax;
- EXPECT_EQ(grandchild_pid, regs.rax);
- }
-#endif // defined(__x86_64__)
-
- // After this point, the child will be making wait4 syscalls that will be
- // interrupted by saving, so saving is not permitted. Note that this is
- // explicitly released below once the grandchild exits.
- DisableSave ds;
-
- // Resume the child with PTRACE_SYSCALL again and expect it to enter
- // syscall-enter-stop for wait4().
- ASSERT_THAT(ptrace(PTRACE_SYSCALL, child_pid, 0, 0), SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80))
- << " status " << status;
- ASSERT_THAT(ptrace(PTRACE_GETSIGINFO, child_pid, 0, &siginfo),
- SyscallSucceeds());
- EXPECT_TRUE(siginfo.si_code == SIGTRAP || siginfo.si_code == (SIGTRAP | 0x80))
- << "si_code = " << siginfo.si_code;
-#ifdef __x86_64__
- {
- EXPECT_THAT(ptrace(PTRACE_PEEKUSER, child_pid,
- offsetof(struct user_regs_struct, orig_rax), 0),
- SyscallSucceedsWithValue(SYS_wait4));
- }
-#endif // defined(__x86_64__)
-
- // Resume the child with PTRACE_SYSCALL again. Since the grandchild is
- // waiting for the tracer (us) to acknowledge its exit first, wait4 should
- // block.
- ASSERT_THAT(ptrace(PTRACE_SYSCALL, child_pid, 0, 0), SyscallSucceeds());
- absl::SleepFor(absl::Seconds(1));
- ASSERT_THAT(waitpid(child_pid, &status, WNOHANG),
- SyscallSucceedsWithValue(0));
-
- // Acknowledge the grandchild's exit.
- ASSERT_THAT(waitpid(grandchild_pid, &status, 0),
- SyscallSucceedsWithValue(grandchild_pid));
- ds.reset();
-
- // Now the child should enter syscall-exit-stop for wait4, returning with the
- // grandchild's PID.
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80))
- << " status " << status;
-#ifdef __x86_64__
- {
- struct user_regs_struct regs = {};
- ASSERT_THAT(ptrace(PTRACE_GETREGS, child_pid, 0, &regs), SyscallSucceeds());
- EXPECT_EQ(SYS_wait4, regs.orig_rax);
- EXPECT_EQ(grandchild_pid, regs.rax);
- }
-#endif // defined(__x86_64__)
-
- // Detach from the child and wait for it to exit.
- ASSERT_THAT(ptrace(PTRACE_DETACH, child_pid, 0, 0), SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << " status " << status;
-}
-
-// These tests requires knowledge of architecture-specific syscall convention.
-#ifdef __x86_64__
-TEST(PtraceTest, Int3) {
- switch (GvisorPlatform()) {
- case Platform::kKVM:
- // TODO(b/124248694): int3 isn't handled properly.
- return;
- default:
- break;
- }
- pid_t const child_pid = fork();
- if (child_pid == 0) {
- // In child process.
-
- // Enable tracing.
- TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0);
-
- // Interrupt 3 - trap to debugger
- asm("int3");
-
- _exit(56);
- }
- // In parent process.
- ASSERT_THAT(child_pid, SyscallSucceeds());
-
- int status;
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP)
- << " status " << status;
-
- ASSERT_THAT(ptrace(PTRACE_CONT, child_pid, 0, 0), SyscallSucceeds());
-
- // The child should validate the injected return value and then exit normally.
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 56)
- << " status " << status;
-}
-
-TEST(PtraceTest, Sysemu_PokeUser) {
- constexpr int kSysemuHelperFirstExitCode = 126;
- constexpr uint64_t kSysemuInjectedExitGroupReturn = 42;
-
- pid_t const child_pid = fork();
- if (child_pid == 0) {
- // In child process.
-
- // Enable tracing, then raise SIGSTOP and expect our parent to suppress it.
- TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0);
- RaiseSignal(SIGSTOP);
-
- // Try to exit_group, expecting the tracer to skip the syscall and set its
- // own return value.
- int const rv = syscall(SYS_exit_group, kSysemuHelperFirstExitCode);
- TEST_PCHECK_MSG(rv == kSysemuInjectedExitGroupReturn,
- "exit_group returned incorrect value");
-
- _exit(0);
- }
- // In parent process.
- ASSERT_THAT(child_pid, SyscallSucceeds());
-
- // Wait for the child to send itself SIGSTOP and enter signal-delivery-stop.
- int status;
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
- << " status " << status;
-
- // Suppress the SIGSTOP and wait for the child to enter syscall-enter-stop
- // for its first exit_group syscall.
- ASSERT_THAT(ptrace(kPtraceSysemu, child_pid, 0, 0), SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP)
- << " status " << status;
-
- struct user_regs_struct regs = {};
- ASSERT_THAT(ptrace(PTRACE_GETREGS, child_pid, 0, &regs), SyscallSucceeds());
- EXPECT_EQ(SYS_exit_group, regs.orig_rax);
- EXPECT_EQ(-ENOSYS, regs.rax);
- EXPECT_EQ(kSysemuHelperFirstExitCode, regs.rdi);
-
- // Replace the exit_group return value, then resume the child, which should
- // automatically skip the syscall.
- ASSERT_THAT(
- ptrace(PTRACE_POKEUSER, child_pid, offsetof(struct user_regs_struct, rax),
- kSysemuInjectedExitGroupReturn),
- SyscallSucceeds());
- ASSERT_THAT(ptrace(PTRACE_DETACH, child_pid, 0, 0), SyscallSucceeds());
-
- // The child should validate the injected return value and then exit normally.
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << " status " << status;
-}
-
-// This test also cares about syscall-exit-stop.
-TEST(PtraceTest, ERESTART_NoRandomSave) {
- constexpr int kSigno = SIGUSR1;
-
- pid_t const child_pid = fork();
- if (child_pid == 0) {
- // In child process.
-
- // Ignore, but unblock, kSigno.
- struct sigaction sa = {};
- sa.sa_handler = SIG_IGN;
- TEST_PCHECK(sigfillset(&sa.sa_mask) == 0);
- TEST_PCHECK(sigaction(kSigno, &sa, nullptr) == 0);
- MaybeSave();
- TEST_PCHECK(sigprocmask(SIG_UNBLOCK, &sa.sa_mask, nullptr) == 0);
- MaybeSave();
-
- // Enable tracing, then raise SIGSTOP and expect our parent to suppress it.
- TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0);
- RaiseSignal(SIGSTOP);
-
- // Invoke the pause syscall, which normally should not return until we
- // receive a signal that "either terminates the process or causes the
- // invocation of a signal-catching function".
- pause();
-
- _exit(0);
- }
- ASSERT_THAT(child_pid, SyscallSucceeds());
-
- // Wait for the child to send itself SIGSTOP and enter signal-delivery-stop.
- int status;
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
- << " status " << status;
-
- // After this point, the child's pause syscall will be interrupted by saving,
- // so saving is not permitted. Note that this is explicitly released below
- // once the child is stopped.
- DisableSave ds;
-
- // Suppress the SIGSTOP and wait for the child to enter syscall-enter-stop for
- // its pause syscall.
- ASSERT_THAT(ptrace(PTRACE_SYSCALL, child_pid, 0, 0), SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP)
- << " status " << status;
-
- struct user_regs_struct regs = {};
- ASSERT_THAT(ptrace(PTRACE_GETREGS, child_pid, 0, &regs), SyscallSucceeds());
- EXPECT_EQ(SYS_pause, regs.orig_rax);
- EXPECT_EQ(-ENOSYS, regs.rax);
-
- // Resume the child with PTRACE_SYSCALL and expect it to block in the pause
- // syscall.
- ASSERT_THAT(ptrace(PTRACE_SYSCALL, child_pid, 0, 0), SyscallSucceeds());
- absl::SleepFor(absl::Seconds(1));
- ASSERT_THAT(waitpid(child_pid, &status, WNOHANG),
- SyscallSucceedsWithValue(0));
-
- // Send the child kSigno, causing it to return ERESTARTNOHAND and enter
- // syscall-exit-stop from the pause syscall.
- constexpr int ERESTARTNOHAND = 514;
- ASSERT_THAT(kill(child_pid, kSigno), SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP)
- << " status " << status;
- ds.reset();
-
- ASSERT_THAT(ptrace(PTRACE_GETREGS, child_pid, 0, &regs), SyscallSucceeds());
- EXPECT_EQ(SYS_pause, regs.orig_rax);
- EXPECT_EQ(-ERESTARTNOHAND, regs.rax);
-
- // Replace the return value from pause with 0, causing pause to not be
- // restarted despite kSigno being ignored.
- ASSERT_THAT(ptrace(PTRACE_POKEUSER, child_pid,
- offsetof(struct user_regs_struct, rax), 0),
- SyscallSucceeds());
-
- // Detach from the child and wait for it to exit.
- ASSERT_THAT(ptrace(PTRACE_DETACH, child_pid, 0, 0), SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << " status " << status;
-}
-#endif // defined(__x86_64__)
-
-TEST(PtraceTest, Seize_Interrupt_Listen) {
- volatile long child_should_spin = 1;
- pid_t const child_pid = fork();
- if (child_pid == 0) {
- // In child process.
- while (child_should_spin) {
- SleepSafe(absl::Seconds(1));
- }
- _exit(1);
- }
-
- // In parent process.
- ASSERT_THAT(child_pid, SyscallSucceeds());
-
- // Attach to the child with PTRACE_SEIZE; doing so should not stop the child.
- ASSERT_THAT(ptrace(PTRACE_SEIZE, child_pid, 0, 0), SyscallSucceeds());
- int status;
- EXPECT_THAT(waitpid(child_pid, &status, WNOHANG),
- SyscallSucceedsWithValue(0));
-
- // Stop the child with PTRACE_INTERRUPT.
- ASSERT_THAT(ptrace(PTRACE_INTERRUPT, child_pid, 0, 0), SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_EQ(SIGTRAP | (kPtraceEventStop << 8), status >> 8);
-
- // Unset child_should_spin to verify that the child never leaves the spin
- // loop.
- ASSERT_THAT(ptrace(PTRACE_POKEDATA, child_pid, &child_should_spin, 0),
- SyscallSucceeds());
-
- // Send SIGSTOP to the child, then resume it, allowing it to proceed to
- // signal-delivery-stop.
- ASSERT_THAT(kill(child_pid, SIGSTOP), SyscallSucceeds());
- ASSERT_THAT(ptrace(PTRACE_CONT, child_pid, 0, 0), SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
- << " status " << status;
-
- // Release the child from signal-delivery-stop without suppressing the
- // SIGSTOP, causing it to enter group-stop.
- ASSERT_THAT(ptrace(PTRACE_CONT, child_pid, 0, SIGSTOP), SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_EQ(SIGSTOP | (kPtraceEventStop << 8), status >> 8);
-
- // "The state of the tracee after PTRACE_LISTEN is somewhat of a gray area: it
- // is not in any ptrace-stop (ptrace commands won't work on it, and it will
- // deliver waitpid(2) notifications), but it also may be considered 'stopped'
- // because it is not executing instructions (is not scheduled), and if it was
- // in group-stop before PTRACE_LISTEN, it will not respond to signals until
- // SIGCONT is received." - ptrace(2).
- ASSERT_THAT(ptrace(PTRACE_LISTEN, child_pid, 0, 0), SyscallSucceeds());
- EXPECT_THAT(ptrace(PTRACE_CONT, child_pid, 0, 0),
- SyscallFailsWithErrno(ESRCH));
- EXPECT_THAT(waitpid(child_pid, &status, WNOHANG),
- SyscallSucceedsWithValue(0));
- EXPECT_THAT(kill(child_pid, SIGTERM), SyscallSucceeds());
- absl::SleepFor(absl::Seconds(1));
- EXPECT_THAT(waitpid(child_pid, &status, WNOHANG),
- SyscallSucceedsWithValue(0));
-
- // Send SIGCONT to the child, causing it to leave group-stop and re-trap due
- // to PTRACE_LISTEN.
- EXPECT_THAT(kill(child_pid, SIGCONT), SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_EQ(SIGTRAP | (kPtraceEventStop << 8), status >> 8);
-
- // Detach the child and expect it to exit due to the SIGTERM we sent while
- // it was stopped by PTRACE_LISTEN.
- ASSERT_THAT(ptrace(PTRACE_DETACH, child_pid, 0, 0), SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM)
- << " status " << status;
-}
-
-TEST(PtraceTest, Interrupt_Listen_RequireSeize) {
- pid_t const child_pid = fork();
- if (child_pid == 0) {
- // In child process.
- TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0);
- MaybeSave();
- raise(SIGSTOP);
- _exit(0);
- }
- // In parent process.
- ASSERT_THAT(child_pid, SyscallSucceeds());
-
- // Wait for the child to send itself SIGSTOP and enter signal-delivery-stop.
- int status;
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
- << " status " << status;
-
- // PTRACE_INTERRUPT and PTRACE_LISTEN should fail since the child wasn't
- // attached with PTRACE_SEIZE, leaving the child in signal-delivery-stop.
- EXPECT_THAT(ptrace(PTRACE_INTERRUPT, child_pid, 0, 0),
- SyscallFailsWithErrno(EIO));
- EXPECT_THAT(ptrace(PTRACE_LISTEN, child_pid, 0, 0),
- SyscallFailsWithErrno(EIO));
-
- // Suppress SIGSTOP and detach from the child, expecting it to exit normally.
- ASSERT_THAT(ptrace(PTRACE_DETACH, child_pid, 0, 0), SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << " status " << status;
-}
-
-TEST(PtraceTest, SeizeSetOptions) {
- pid_t const child_pid = fork();
- if (child_pid == 0) {
- // In child process.
- while (true) {
- SleepSafe(absl::Seconds(1));
- }
- }
-
- // In parent process.
- ASSERT_THAT(child_pid, SyscallSucceeds());
-
- // Attach to the child with PTRACE_SEIZE while setting PTRACE_O_TRACESYSGOOD.
- ASSERT_THAT(ptrace(PTRACE_SEIZE, child_pid, 0, PTRACE_O_TRACESYSGOOD),
- SyscallSucceeds());
-
- // Stop the child with PTRACE_INTERRUPT.
- ASSERT_THAT(ptrace(PTRACE_INTERRUPT, child_pid, 0, 0), SyscallSucceeds());
- int status;
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_EQ(SIGTRAP | (kPtraceEventStop << 8), status >> 8);
-
- // Resume the child with PTRACE_SYSCALL and wait for it to enter
- // syscall-enter-stop. The stop signal status from the syscall stop should be
- // SIGTRAP|0x80, reflecting PTRACE_O_TRACESYSGOOD.
- ASSERT_THAT(ptrace(PTRACE_SYSCALL, child_pid, 0, 0), SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80))
- << " status " << status;
-
- // Clean up the child.
- ASSERT_THAT(kill(child_pid, SIGKILL), SyscallSucceeds());
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- if (WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80)) {
- // "SIGKILL kills even within system calls (syscall-exit-stop is not
- // generated prior to death by SIGKILL). The net effect is that SIGKILL
- // always kills the process (all its threads), even if some threads of the
- // process are ptraced." - ptrace(2). This is technically true, but...
- //
- // When we send SIGKILL to the child, kernel/signal.c:complete_signal() =>
- // signal_wake_up(resume=1) kicks the tracee out of the syscall-enter-stop.
- // The pending SIGKILL causes the syscall to be skipped, but the child
- // thread still reports syscall-exit before checking for pending signals; in
- // current kernels, this is
- // arch/x86/entry/common.c:syscall_return_slowpath() =>
- // syscall_slow_exit_work() =>
- // include/linux/tracehook.h:tracehook_report_syscall_exit() =>
- // ptrace_report_syscall() => kernel/signal.c:ptrace_notify() =>
- // ptrace_do_notify() => ptrace_stop().
- //
- // ptrace_stop() sets the task's state to TASK_TRACED and the task's
- // exit_code to SIGTRAP|0x80 (passed by ptrace_report_syscall()), then calls
- // freezable_schedule(). freezable_schedule() eventually reaches
- // __schedule(), which detects signal_pending_state() due to the pending
- // SIGKILL, sets the task's state back to TASK_RUNNING, and returns without
- // descheduling. Thus, the task never enters syscall-exit-stop. However, if
- // our wait4() => kernel/exit.c:wait_task_stopped() racily observes the
- // TASK_TRACED state and the non-zero exit code set by ptrace_stop() before
- // __schedule() sets the state back to TASK_RUNNING, it will return the
- // task's exit_code as status W_STOPCODE(SIGTRAP|0x80). So we get a spurious
- // syscall-exit-stop notification, and need to wait4() again for task exit.
- //
- // gVisor is not susceptible to this race because
- // kernel.Task.waitCollectTraceeStopLocked() checks specifically for an
- // active ptraceStop, which is not initiated if SIGKILL is pending.
- std::cout << "Observed syscall-exit after SIGKILL";
- ASSERT_THAT(waitpid(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- }
- EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL)
- << " status " << status;
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
-
-int main(int argc, char** argv) {
- gvisor::testing::TestInit(&argc, &argv);
-
- if (FLAGS_ptrace_test_execve_child) {
- gvisor::testing::RunExecveChild();
- }
-
- return RUN_ALL_TESTS();
-}
diff --git a/test/syscalls/linux/pty.cc b/test/syscalls/linux/pty.cc
deleted file mode 100644
index d1ab4703f..000000000
--- a/test/syscalls/linux/pty.cc
+++ /dev/null
@@ -1,1238 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <linux/major.h>
-#include <poll.h>
-#include <sys/ioctl.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <sys/sysmacros.h>
-#include <sys/types.h>
-#include <termios.h>
-#include <unistd.h>
-
-#include <iostream>
-
-#include "gtest/gtest.h"
-#include "absl/base/macros.h"
-#include "absl/strings/str_cat.h"
-#include "absl/synchronization/notification.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/posix_error.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-using ::testing::AnyOf;
-using ::testing::Contains;
-using ::testing::Eq;
-using ::testing::Not;
-
-// Tests Unix98 pseudoterminals.
-//
-// These tests assume that /dev/ptmx exists and is associated with a devpts
-// filesystem mounted at /dev/pts/. While a Linux distribution could
-// theoretically place those anywhere, glibc expects those locations, so they
-// are effectively fixed.
-
-// Minor device number for an unopened ptmx file.
-constexpr int kPtmxMinor = 2;
-
-// The timeout when polling for data from a pty. When data is written to one end
-// of a pty, Linux asynchronously makes it available to the other end, so we
-// have to wait.
-constexpr absl::Duration kTimeout = absl::Seconds(20);
-
-// The maximum line size in bytes returned per read from a pty file.
-constexpr int kMaxLineSize = 4096;
-
-// glibc defines its own, different, version of struct termios. We care about
-// what the kernel does, not glibc.
-#define KERNEL_NCCS 19
-struct kernel_termios {
- tcflag_t c_iflag;
- tcflag_t c_oflag;
- tcflag_t c_cflag;
- tcflag_t c_lflag;
- cc_t c_line;
- cc_t c_cc[KERNEL_NCCS];
-};
-
-bool operator==(struct kernel_termios const& a,
- struct kernel_termios const& b) {
- return memcmp(&a, &b, sizeof(a)) == 0;
-}
-
-// Returns the termios-style control character for the passed character.
-//
-// e.g., for Ctrl-C, i.e., ^C, call ControlCharacter('C').
-//
-// Standard control characters are ASCII bytes 0 through 31.
-constexpr char ControlCharacter(char c) {
- // A is 1, B is 2, etc.
- return c - 'A' + 1;
-}
-
-// Returns the printable character the given control character represents.
-constexpr char FromControlCharacter(char c) { return c + 'A' - 1; }
-
-// Returns true if c is a control character.
-//
-// Standard control characters are ASCII bytes 0 through 31.
-constexpr bool IsControlCharacter(char c) { return c <= 31; }
-
-struct Field {
- const char* name;
- uint64_t mask;
- uint64_t value;
-};
-
-// ParseFields returns a string representation of value, using the names in
-// fields.
-std::string ParseFields(const Field* fields, size_t len, uint64_t value) {
- bool first = true;
- std::string s;
- for (size_t i = 0; i < len; i++) {
- const Field f = fields[i];
- if ((value & f.mask) == f.value) {
- if (!first) {
- s += "|";
- }
- s += f.name;
- first = false;
- value &= ~f.mask;
- }
- }
-
- if (value) {
- if (!first) {
- s += "|";
- }
- absl::StrAppend(&s, value);
- }
-
- return s;
-}
-
-const Field kIflagFields[] = {
- {"IGNBRK", IGNBRK, IGNBRK}, {"BRKINT", BRKINT, BRKINT},
- {"IGNPAR", IGNPAR, IGNPAR}, {"PARMRK", PARMRK, PARMRK},
- {"INPCK", INPCK, INPCK}, {"ISTRIP", ISTRIP, ISTRIP},
- {"INLCR", INLCR, INLCR}, {"IGNCR", IGNCR, IGNCR},
- {"ICRNL", ICRNL, ICRNL}, {"IUCLC", IUCLC, IUCLC},
- {"IXON", IXON, IXON}, {"IXANY", IXANY, IXANY},
- {"IXOFF", IXOFF, IXOFF}, {"IMAXBEL", IMAXBEL, IMAXBEL},
- {"IUTF8", IUTF8, IUTF8},
-};
-
-const Field kOflagFields[] = {
- {"OPOST", OPOST, OPOST}, {"OLCUC", OLCUC, OLCUC},
- {"ONLCR", ONLCR, ONLCR}, {"OCRNL", OCRNL, OCRNL},
- {"ONOCR", ONOCR, ONOCR}, {"ONLRET", ONLRET, ONLRET},
- {"OFILL", OFILL, OFILL}, {"OFDEL", OFDEL, OFDEL},
- {"NL0", NLDLY, NL0}, {"NL1", NLDLY, NL1},
- {"CR0", CRDLY, CR0}, {"CR1", CRDLY, CR1},
- {"CR2", CRDLY, CR2}, {"CR3", CRDLY, CR3},
- {"TAB0", TABDLY, TAB0}, {"TAB1", TABDLY, TAB1},
- {"TAB2", TABDLY, TAB2}, {"TAB3", TABDLY, TAB3},
- {"BS0", BSDLY, BS0}, {"BS1", BSDLY, BS1},
- {"FF0", FFDLY, FF0}, {"FF1", FFDLY, FF1},
- {"VT0", VTDLY, VT0}, {"VT1", VTDLY, VT1},
- {"XTABS", XTABS, XTABS},
-};
-
-#ifndef IBSHIFT
-// Shift from CBAUD to CIBAUD.
-#define IBSHIFT 16
-#endif
-
-const Field kCflagFields[] = {
- {"B0", CBAUD, B0},
- {"B50", CBAUD, B50},
- {"B75", CBAUD, B75},
- {"B110", CBAUD, B110},
- {"B134", CBAUD, B134},
- {"B150", CBAUD, B150},
- {"B200", CBAUD, B200},
- {"B300", CBAUD, B300},
- {"B600", CBAUD, B600},
- {"B1200", CBAUD, B1200},
- {"B1800", CBAUD, B1800},
- {"B2400", CBAUD, B2400},
- {"B4800", CBAUD, B4800},
- {"B9600", CBAUD, B9600},
- {"B19200", CBAUD, B19200},
- {"B38400", CBAUD, B38400},
- {"CS5", CSIZE, CS5},
- {"CS6", CSIZE, CS6},
- {"CS7", CSIZE, CS7},
- {"CS8", CSIZE, CS8},
- {"CSTOPB", CSTOPB, CSTOPB},
- {"CREAD", CREAD, CREAD},
- {"PARENB", PARENB, PARENB},
- {"PARODD", PARODD, PARODD},
- {"HUPCL", HUPCL, HUPCL},
- {"CLOCAL", CLOCAL, CLOCAL},
- {"B57600", CBAUD, B57600},
- {"B115200", CBAUD, B115200},
- {"B230400", CBAUD, B230400},
- {"B460800", CBAUD, B460800},
- {"B500000", CBAUD, B500000},
- {"B576000", CBAUD, B576000},
- {"B921600", CBAUD, B921600},
- {"B1000000", CBAUD, B1000000},
- {"B1152000", CBAUD, B1152000},
- {"B1500000", CBAUD, B1500000},
- {"B2000000", CBAUD, B2000000},
- {"B2500000", CBAUD, B2500000},
- {"B3000000", CBAUD, B3000000},
- {"B3500000", CBAUD, B3500000},
- {"B4000000", CBAUD, B4000000},
- {"CMSPAR", CMSPAR, CMSPAR},
- {"CRTSCTS", CRTSCTS, CRTSCTS},
- {"IB0", CIBAUD, B0 << IBSHIFT},
- {"IB50", CIBAUD, B50 << IBSHIFT},
- {"IB75", CIBAUD, B75 << IBSHIFT},
- {"IB110", CIBAUD, B110 << IBSHIFT},
- {"IB134", CIBAUD, B134 << IBSHIFT},
- {"IB150", CIBAUD, B150 << IBSHIFT},
- {"IB200", CIBAUD, B200 << IBSHIFT},
- {"IB300", CIBAUD, B300 << IBSHIFT},
- {"IB600", CIBAUD, B600 << IBSHIFT},
- {"IB1200", CIBAUD, B1200 << IBSHIFT},
- {"IB1800", CIBAUD, B1800 << IBSHIFT},
- {"IB2400", CIBAUD, B2400 << IBSHIFT},
- {"IB4800", CIBAUD, B4800 << IBSHIFT},
- {"IB9600", CIBAUD, B9600 << IBSHIFT},
- {"IB19200", CIBAUD, B19200 << IBSHIFT},
- {"IB38400", CIBAUD, B38400 << IBSHIFT},
- {"IB57600", CIBAUD, B57600 << IBSHIFT},
- {"IB115200", CIBAUD, B115200 << IBSHIFT},
- {"IB230400", CIBAUD, B230400 << IBSHIFT},
- {"IB460800", CIBAUD, B460800 << IBSHIFT},
- {"IB500000", CIBAUD, B500000 << IBSHIFT},
- {"IB576000", CIBAUD, B576000 << IBSHIFT},
- {"IB921600", CIBAUD, B921600 << IBSHIFT},
- {"IB1000000", CIBAUD, B1000000 << IBSHIFT},
- {"IB1152000", CIBAUD, B1152000 << IBSHIFT},
- {"IB1500000", CIBAUD, B1500000 << IBSHIFT},
- {"IB2000000", CIBAUD, B2000000 << IBSHIFT},
- {"IB2500000", CIBAUD, B2500000 << IBSHIFT},
- {"IB3000000", CIBAUD, B3000000 << IBSHIFT},
- {"IB3500000", CIBAUD, B3500000 << IBSHIFT},
- {"IB4000000", CIBAUD, B4000000 << IBSHIFT},
-};
-
-const Field kLflagFields[] = {
- {"ISIG", ISIG, ISIG}, {"ICANON", ICANON, ICANON},
- {"XCASE", XCASE, XCASE}, {"ECHO", ECHO, ECHO},
- {"ECHOE", ECHOE, ECHOE}, {"ECHOK", ECHOK, ECHOK},
- {"ECHONL", ECHONL, ECHONL}, {"NOFLSH", NOFLSH, NOFLSH},
- {"TOSTOP", TOSTOP, TOSTOP}, {"ECHOCTL", ECHOCTL, ECHOCTL},
- {"ECHOPRT", ECHOPRT, ECHOPRT}, {"ECHOKE", ECHOKE, ECHOKE},
- {"FLUSHO", FLUSHO, FLUSHO}, {"PENDIN", PENDIN, PENDIN},
- {"IEXTEN", IEXTEN, IEXTEN}, {"EXTPROC", EXTPROC, EXTPROC},
-};
-
-std::string FormatCC(char c) {
- if (isgraph(c)) {
- return std::string(1, c);
- } else if (c == ' ') {
- return " ";
- } else if (c == '\t') {
- return "\\t";
- } else if (c == '\r') {
- return "\\r";
- } else if (c == '\n') {
- return "\\n";
- } else if (c == '\0') {
- return "\\0";
- } else if (IsControlCharacter(c)) {
- return absl::StrCat("^", std::string(1, FromControlCharacter(c)));
- }
- return absl::StrCat("\\x", absl::Hex(c));
-}
-
-std::ostream& operator<<(std::ostream& os, struct kernel_termios const& a) {
- os << "{ c_iflag = "
- << ParseFields(kIflagFields, ABSL_ARRAYSIZE(kIflagFields), a.c_iflag);
- os << ", c_oflag = "
- << ParseFields(kOflagFields, ABSL_ARRAYSIZE(kOflagFields), a.c_oflag);
- os << ", c_cflag = "
- << ParseFields(kCflagFields, ABSL_ARRAYSIZE(kCflagFields), a.c_cflag);
- os << ", c_lflag = "
- << ParseFields(kLflagFields, ABSL_ARRAYSIZE(kLflagFields), a.c_lflag);
- os << ", c_line = " << a.c_line;
- os << ", c_cc = { [VINTR] = '" << FormatCC(a.c_cc[VINTR]);
- os << "', [VQUIT] = '" << FormatCC(a.c_cc[VQUIT]);
- os << "', [VERASE] = '" << FormatCC(a.c_cc[VERASE]);
- os << "', [VKILL] = '" << FormatCC(a.c_cc[VKILL]);
- os << "', [VEOF] = '" << FormatCC(a.c_cc[VEOF]);
- os << "', [VTIME] = '" << static_cast<int>(a.c_cc[VTIME]);
- os << "', [VMIN] = " << static_cast<int>(a.c_cc[VMIN]);
- os << ", [VSWTC] = '" << FormatCC(a.c_cc[VSWTC]);
- os << "', [VSTART] = '" << FormatCC(a.c_cc[VSTART]);
- os << "', [VSTOP] = '" << FormatCC(a.c_cc[VSTOP]);
- os << "', [VSUSP] = '" << FormatCC(a.c_cc[VSUSP]);
- os << "', [VEOL] = '" << FormatCC(a.c_cc[VEOL]);
- os << "', [VREPRINT] = '" << FormatCC(a.c_cc[VREPRINT]);
- os << "', [VDISCARD] = '" << FormatCC(a.c_cc[VDISCARD]);
- os << "', [VWERASE] = '" << FormatCC(a.c_cc[VWERASE]);
- os << "', [VLNEXT] = '" << FormatCC(a.c_cc[VLNEXT]);
- os << "', [VEOL2] = '" << FormatCC(a.c_cc[VEOL2]);
- os << "'}";
- return os;
-}
-
-// Return the default termios settings for a new terminal.
-struct kernel_termios DefaultTermios() {
- struct kernel_termios t = {};
- t.c_iflag = IXON | ICRNL;
- t.c_oflag = OPOST | ONLCR;
- t.c_cflag = B38400 | CSIZE | CS8 | CREAD;
- t.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE | IEXTEN;
- t.c_line = 0;
- t.c_cc[VINTR] = ControlCharacter('C');
- t.c_cc[VQUIT] = ControlCharacter('\\');
- t.c_cc[VERASE] = '\x7f';
- t.c_cc[VKILL] = ControlCharacter('U');
- t.c_cc[VEOF] = ControlCharacter('D');
- t.c_cc[VTIME] = '\0';
- t.c_cc[VMIN] = 1;
- t.c_cc[VSWTC] = '\0';
- t.c_cc[VSTART] = ControlCharacter('Q');
- t.c_cc[VSTOP] = ControlCharacter('S');
- t.c_cc[VSUSP] = ControlCharacter('Z');
- t.c_cc[VEOL] = '\0';
- t.c_cc[VREPRINT] = ControlCharacter('R');
- t.c_cc[VDISCARD] = ControlCharacter('O');
- t.c_cc[VWERASE] = ControlCharacter('W');
- t.c_cc[VLNEXT] = ControlCharacter('V');
- t.c_cc[VEOL2] = '\0';
- return t;
-}
-
-// PollAndReadFd tries to read count bytes from buf within timeout.
-//
-// Returns a partial read if some bytes were read.
-//
-// fd must be non-blocking.
-PosixErrorOr<size_t> PollAndReadFd(int fd, void* buf, size_t count,
- absl::Duration timeout) {
- absl::Time end = absl::Now() + timeout;
-
- size_t completed = 0;
- absl::Duration remaining;
- while ((remaining = end - absl::Now()) > absl::ZeroDuration()) {
- struct pollfd pfd = {fd, POLLIN, 0};
- int ret = RetryEINTR(poll)(&pfd, 1, absl::ToInt64Milliseconds(remaining));
- if (ret < 0) {
- return PosixError(errno, "poll failed");
- } else if (ret == 0) {
- // Timed out.
- continue;
- } else if (ret != 1) {
- return PosixError(EINVAL, absl::StrCat("Bad poll ret ", ret));
- }
-
- ssize_t n =
- ReadFd(fd, static_cast<char*>(buf) + completed, count - completed);
- if (n < 0) {
- return PosixError(errno, "read failed");
- }
- completed += n;
- if (completed >= count) {
- return completed;
- }
- }
-
- if (completed) {
- return completed;
- }
- return PosixError(ETIMEDOUT, "Poll timed out");
-}
-
-// Opens the slave end of the passed master as R/W and nonblocking.
-PosixErrorOr<FileDescriptor> OpenSlave(const FileDescriptor& master) {
- // Get pty index.
- int n;
- int ret = ioctl(master.get(), TIOCGPTN, &n);
- if (ret < 0) {
- return PosixError(errno, "ioctl(TIOCGPTN) failed");
- }
-
- // Unlock pts.
- int unlock = 0;
- ret = ioctl(master.get(), TIOCSPTLCK, &unlock);
- if (ret < 0) {
- return PosixError(errno, "ioctl(TIOSPTLCK) failed");
- }
-
- return Open(absl::StrCat("/dev/pts/", n), O_RDWR | O_NONBLOCK);
-}
-
-TEST(BasicPtyTest, StatUnopenedMaster) {
- struct stat s;
- ASSERT_THAT(stat("/dev/ptmx", &s), SyscallSucceeds());
-
- EXPECT_EQ(s.st_rdev, makedev(TTYAUX_MAJOR, kPtmxMinor));
- EXPECT_EQ(s.st_size, 0);
- EXPECT_EQ(s.st_blocks, 0);
-
- // ptmx attached to a specific devpts mount uses block size 1024. See
- // fs/devpts/inode.c:devpts_fill_super.
- //
- // The global ptmx device uses the block size of the filesystem it is created
- // on (which is usually 4096 for disk filesystems).
- EXPECT_THAT(s.st_blksize, AnyOf(Eq(1024), Eq(4096)));
-}
-
-// Waits for count bytes to be readable from fd. Unlike poll, which can return
-// before all data is moved into a pty's read buffer, this function waits for
-// all count bytes to become readable.
-PosixErrorOr<int> WaitUntilReceived(int fd, int count) {
- int buffered = -1;
- absl::Duration remaining;
- absl::Time end = absl::Now() + kTimeout;
- while ((remaining = end - absl::Now()) > absl::ZeroDuration()) {
- if (ioctl(fd, FIONREAD, &buffered) < 0) {
- return PosixError(errno, "failed FIONREAD ioctl");
- }
- if (buffered >= count) {
- return buffered;
- }
- absl::SleepFor(absl::Milliseconds(500));
- }
- return PosixError(
- ETIMEDOUT,
- absl::StrFormat(
- "FIONREAD timed out, receiving only %d of %d expected bytes",
- buffered, count));
-}
-
-// Verifies that there is nothing left to read from fd.
-void ExpectFinished(const FileDescriptor& fd) {
- // Nothing more to read.
- char c;
- EXPECT_THAT(ReadFd(fd.get(), &c, 1), SyscallFailsWithErrno(EAGAIN));
-}
-
-// Verifies that we can read expected bytes from fd into buf.
-void ExpectReadable(const FileDescriptor& fd, int expected, char* buf) {
- size_t n = ASSERT_NO_ERRNO_AND_VALUE(
- PollAndReadFd(fd.get(), buf, expected, kTimeout));
- EXPECT_EQ(expected, n);
-}
-
-TEST(BasicPtyTest, OpenMasterSlave) {
- FileDescriptor master = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR));
- FileDescriptor slave = ASSERT_NO_ERRNO_AND_VALUE(OpenSlave(master));
-}
-
-// The slave entry in /dev/pts/ disappears when the master is closed, even if
-// the slave is still open.
-TEST(BasicPtyTest, SlaveEntryGoneAfterMasterClose) {
- FileDescriptor master = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR));
- FileDescriptor slave = ASSERT_NO_ERRNO_AND_VALUE(OpenSlave(master));
-
- // Get pty index.
- int index = -1;
- ASSERT_THAT(ioctl(master.get(), TIOCGPTN, &index), SyscallSucceeds());
-
- std::string path = absl::StrCat("/dev/pts/", index);
-
- struct stat st;
- EXPECT_THAT(stat(path.c_str(), &st), SyscallSucceeds());
-
- master.reset();
-
- EXPECT_THAT(stat(path.c_str(), &st), SyscallFailsWithErrno(ENOENT));
-}
-
-TEST(BasicPtyTest, Getdents) {
- FileDescriptor master1 = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR));
- int index1 = -1;
- ASSERT_THAT(ioctl(master1.get(), TIOCGPTN, &index1), SyscallSucceeds());
- FileDescriptor slave1 = ASSERT_NO_ERRNO_AND_VALUE(OpenSlave(master1));
-
- FileDescriptor master2 = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR));
- int index2 = -1;
- ASSERT_THAT(ioctl(master2.get(), TIOCGPTN, &index2), SyscallSucceeds());
- FileDescriptor slave2 = ASSERT_NO_ERRNO_AND_VALUE(OpenSlave(master2));
-
- // The directory contains ptmx, index1, and index2. (Plus any additional PTYs
- // unrelated to this test.)
-
- std::vector<std::string> contents =
- ASSERT_NO_ERRNO_AND_VALUE(ListDir("/dev/pts/", true));
- EXPECT_THAT(contents, Contains(absl::StrCat(index1)));
- EXPECT_THAT(contents, Contains(absl::StrCat(index2)));
-
- master2.reset();
-
- // The directory contains ptmx and index1, but not index2 since the master is
- // closed. (Plus any additional PTYs unrelated to this test.)
-
- contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir("/dev/pts/", true));
- EXPECT_THAT(contents, Contains(absl::StrCat(index1)));
- EXPECT_THAT(contents, Not(Contains(absl::StrCat(index2))));
-
- // N.B. devpts supports legacy "single-instance" mode and new "multi-instance"
- // mode. In legacy mode, devpts does not contain a "ptmx" device (the distro
- // must use mknod to create it somewhere, presumably /dev/ptmx).
- // Multi-instance mode does include a "ptmx" device tied to that mount.
- //
- // We don't check for the presence or absence of "ptmx", as distros vary in
- // their usage of the two modes.
-}
-
-class PtyTest : public ::testing::Test {
- protected:
- void SetUp() override {
- master_ = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR | O_NONBLOCK));
- slave_ = ASSERT_NO_ERRNO_AND_VALUE(OpenSlave(master_));
- }
-
- void DisableCanonical() {
- struct kernel_termios t = {};
- EXPECT_THAT(ioctl(slave_.get(), TCGETS, &t), SyscallSucceeds());
- t.c_lflag &= ~ICANON;
- EXPECT_THAT(ioctl(slave_.get(), TCSETS, &t), SyscallSucceeds());
- }
-
- void EnableCanonical() {
- struct kernel_termios t = {};
- EXPECT_THAT(ioctl(slave_.get(), TCGETS, &t), SyscallSucceeds());
- t.c_lflag |= ICANON;
- EXPECT_THAT(ioctl(slave_.get(), TCSETS, &t), SyscallSucceeds());
- }
-
- // Master and slave ends of the PTY. Non-blocking.
- FileDescriptor master_;
- FileDescriptor slave_;
-};
-
-// Master to slave sanity test.
-TEST_F(PtyTest, WriteMasterToSlave) {
- // N.B. by default, the slave reads nothing until the master writes a newline.
- constexpr char kBuf[] = "hello\n";
-
- EXPECT_THAT(WriteFd(master_.get(), kBuf, sizeof(kBuf) - 1),
- SyscallSucceedsWithValue(sizeof(kBuf) - 1));
-
- // Linux moves data from the master to the slave via async work scheduled via
- // tty_flip_buffer_push. Since it is asynchronous, the data may not be
- // available for reading immediately. Instead we must poll and assert that it
- // becomes available "soon".
-
- char buf[sizeof(kBuf)] = {};
- ExpectReadable(slave_, sizeof(buf) - 1, buf);
-
- EXPECT_EQ(memcmp(buf, kBuf, sizeof(kBuf)), 0);
-}
-
-// Slave to master sanity test.
-TEST_F(PtyTest, WriteSlaveToMaster) {
- // N.B. by default, the master reads nothing until the slave writes a newline,
- // and the master gets a carriage return.
- constexpr char kInput[] = "hello\n";
- constexpr char kExpected[] = "hello\r\n";
-
- EXPECT_THAT(WriteFd(slave_.get(), kInput, sizeof(kInput) - 1),
- SyscallSucceedsWithValue(sizeof(kInput) - 1));
-
- // Linux moves data from the master to the slave via async work scheduled via
- // tty_flip_buffer_push. Since it is asynchronous, the data may not be
- // available for reading immediately. Instead we must poll and assert that it
- // becomes available "soon".
-
- char buf[sizeof(kExpected)] = {};
- ExpectReadable(master_, sizeof(buf) - 1, buf);
-
- EXPECT_EQ(memcmp(buf, kExpected, sizeof(kExpected)), 0);
-}
-
-TEST_F(PtyTest, WriteInvalidUTF8) {
- char c = 0xff;
- ASSERT_THAT(syscall(__NR_write, master_.get(), &c, sizeof(c)),
- SyscallSucceedsWithValue(sizeof(c)));
-}
-
-// Both the master and slave report the standard default termios settings.
-//
-// Note that TCGETS on the master actually redirects to the slave (see comment
-// on MasterTermiosUnchangable).
-TEST_F(PtyTest, DefaultTermios) {
- struct kernel_termios t = {};
- EXPECT_THAT(ioctl(slave_.get(), TCGETS, &t), SyscallSucceeds());
- EXPECT_EQ(t, DefaultTermios());
-
- EXPECT_THAT(ioctl(master_.get(), TCGETS, &t), SyscallSucceeds());
- EXPECT_EQ(t, DefaultTermios());
-}
-
-// Changing termios from the master actually affects the slave.
-//
-// TCSETS on the master actually redirects to the slave (see comment on
-// MasterTermiosUnchangable).
-TEST_F(PtyTest, TermiosAffectsSlave) {
- struct kernel_termios master_termios = {};
- EXPECT_THAT(ioctl(master_.get(), TCGETS, &master_termios), SyscallSucceeds());
- master_termios.c_lflag ^= ICANON;
- EXPECT_THAT(ioctl(master_.get(), TCSETS, &master_termios), SyscallSucceeds());
-
- struct kernel_termios slave_termios = {};
- EXPECT_THAT(ioctl(slave_.get(), TCGETS, &slave_termios), SyscallSucceeds());
- EXPECT_EQ(master_termios, slave_termios);
-}
-
-// The master end of the pty has termios:
-//
-// struct kernel_termios t = {
-// .c_iflag = 0;
-// .c_oflag = 0;
-// .c_cflag = B38400 | CS8 | CREAD;
-// .c_lflag = 0;
-// .c_cc = /* same as DefaultTermios */
-// }
-//
-// (From drivers/tty/pty.c:unix98_pty_init)
-//
-// All termios control ioctls on the master actually redirect to the slave
-// (drivers/tty/tty_ioctl.c:tty_mode_ioctl), making it impossible to change the
-// master termios.
-//
-// Verify this by setting ICRNL (which rewrites input \r to \n) and verify that
-// it has no effect on the master.
-TEST_F(PtyTest, MasterTermiosUnchangable) {
- char c = '\r';
- ASSERT_THAT(WriteFd(slave_.get(), &c, 1), SyscallSucceedsWithValue(1));
-
- ExpectReadable(master_, 1, &c);
- EXPECT_EQ(c, '\r'); // ICRNL had no effect!
-
- ExpectFinished(master_);
-}
-
-// ICRNL rewrites input \r to \n.
-TEST_F(PtyTest, TermiosICRNL) {
- struct kernel_termios t = DefaultTermios();
- t.c_iflag |= ICRNL;
- t.c_lflag &= ~ICANON; // for byte-by-byte reading.
- ASSERT_THAT(ioctl(slave_.get(), TCSETS, &t), SyscallSucceeds());
-
- char c = '\r';
- ASSERT_THAT(WriteFd(master_.get(), &c, 1), SyscallSucceedsWithValue(1));
-
- ExpectReadable(slave_, 1, &c);
- EXPECT_EQ(c, '\n');
-
- ExpectFinished(slave_);
-}
-
-// ONLCR rewrites output \n to \r\n.
-TEST_F(PtyTest, TermiosONLCR) {
- struct kernel_termios t = DefaultTermios();
- t.c_oflag |= ONLCR;
- t.c_lflag &= ~ICANON; // for byte-by-byte reading.
- ASSERT_THAT(ioctl(slave_.get(), TCSETS, &t), SyscallSucceeds());
-
- char c = '\n';
- ASSERT_THAT(WriteFd(slave_.get(), &c, 1), SyscallSucceedsWithValue(1));
-
- // Extra byte for NUL for EXPECT_STREQ.
- char buf[3] = {};
- ExpectReadable(master_, 2, buf);
- EXPECT_STREQ(buf, "\r\n");
-
- ExpectFinished(slave_);
-}
-
-TEST_F(PtyTest, TermiosIGNCR) {
- struct kernel_termios t = DefaultTermios();
- t.c_iflag |= IGNCR;
- t.c_lflag &= ~ICANON; // for byte-by-byte reading.
- ASSERT_THAT(ioctl(slave_.get(), TCSETS, &t), SyscallSucceeds());
-
- char c = '\r';
- ASSERT_THAT(WriteFd(master_.get(), &c, 1), SyscallSucceedsWithValue(1));
-
- // Nothing to read.
- ASSERT_THAT(PollAndReadFd(slave_.get(), &c, 1, kTimeout),
- PosixErrorIs(ETIMEDOUT, ::testing::StrEq("Poll timed out")));
-}
-
-// Test that we can successfully poll for readable data from the slave.
-TEST_F(PtyTest, TermiosPollSlave) {
- struct kernel_termios t = DefaultTermios();
- t.c_iflag |= IGNCR;
- t.c_lflag &= ~ICANON; // for byte-by-byte reading.
- ASSERT_THAT(ioctl(slave_.get(), TCSETS, &t), SyscallSucceeds());
-
- absl::Notification notify;
- int sfd = slave_.get();
- ScopedThread th([sfd, &notify]() {
- notify.Notify();
-
- // Poll on the reader fd with POLLIN event.
- struct pollfd poll_fd = {sfd, POLLIN, 0};
- EXPECT_THAT(
- RetryEINTR(poll)(&poll_fd, 1, absl::ToInt64Milliseconds(kTimeout)),
- SyscallSucceedsWithValue(1));
-
- // Should trigger POLLIN event.
- EXPECT_EQ(poll_fd.revents & POLLIN, POLLIN);
- });
-
- notify.WaitForNotification();
- // Sleep ensures that poll begins waiting before we write to the FD.
- absl::SleepFor(absl::Seconds(1));
-
- char s[] = "foo\n";
- ASSERT_THAT(WriteFd(master_.get(), s, strlen(s) + 1), SyscallSucceeds());
-}
-
-// Test that we can successfully poll for readable data from the master.
-TEST_F(PtyTest, TermiosPollMaster) {
- struct kernel_termios t = DefaultTermios();
- t.c_iflag |= IGNCR;
- t.c_lflag &= ~ICANON; // for byte-by-byte reading.
- ASSERT_THAT(ioctl(master_.get(), TCSETS, &t), SyscallSucceeds());
-
- absl::Notification notify;
- int mfd = master_.get();
- ScopedThread th([mfd, &notify]() {
- notify.Notify();
-
- // Poll on the reader fd with POLLIN event.
- struct pollfd poll_fd = {mfd, POLLIN, 0};
- EXPECT_THAT(
- RetryEINTR(poll)(&poll_fd, 1, absl::ToInt64Milliseconds(kTimeout)),
- SyscallSucceedsWithValue(1));
-
- // Should trigger POLLIN event.
- EXPECT_EQ(poll_fd.revents & POLLIN, POLLIN);
- });
-
- notify.WaitForNotification();
- // Sleep ensures that poll begins waiting before we write to the FD.
- absl::SleepFor(absl::Seconds(1));
-
- char s[] = "foo\n";
- ASSERT_THAT(WriteFd(slave_.get(), s, strlen(s) + 1), SyscallSucceeds());
-}
-
-TEST_F(PtyTest, TermiosINLCR) {
- struct kernel_termios t = DefaultTermios();
- t.c_iflag |= INLCR;
- t.c_lflag &= ~ICANON; // for byte-by-byte reading.
- ASSERT_THAT(ioctl(slave_.get(), TCSETS, &t), SyscallSucceeds());
-
- char c = '\n';
- ASSERT_THAT(WriteFd(master_.get(), &c, 1), SyscallSucceedsWithValue(1));
-
- ExpectReadable(slave_, 1, &c);
- EXPECT_EQ(c, '\r');
-
- ExpectFinished(slave_);
-}
-
-TEST_F(PtyTest, TermiosONOCR) {
- struct kernel_termios t = DefaultTermios();
- t.c_oflag |= ONOCR;
- t.c_lflag &= ~ICANON; // for byte-by-byte reading.
- ASSERT_THAT(ioctl(slave_.get(), TCSETS, &t), SyscallSucceeds());
-
- // The terminal is at column 0, so there should be no CR to read.
- char c = '\r';
- ASSERT_THAT(WriteFd(slave_.get(), &c, 1), SyscallSucceedsWithValue(1));
-
- // Nothing to read.
- ASSERT_THAT(PollAndReadFd(master_.get(), &c, 1, kTimeout),
- PosixErrorIs(ETIMEDOUT, ::testing::StrEq("Poll timed out")));
-
- // This time the column is greater than 0, so we should be able to read the CR
- // out of the other end.
- constexpr char kInput[] = "foo\r";
- constexpr int kInputSize = sizeof(kInput) - 1;
- ASSERT_THAT(WriteFd(slave_.get(), kInput, kInputSize),
- SyscallSucceedsWithValue(kInputSize));
-
- char buf[kInputSize] = {};
- ExpectReadable(master_, kInputSize, buf);
-
- EXPECT_EQ(memcmp(buf, kInput, kInputSize), 0);
-
- ExpectFinished(master_);
-
- // Terminal should be at column 0 again, so no CR can be read.
- ASSERT_THAT(WriteFd(slave_.get(), &c, 1), SyscallSucceedsWithValue(1));
-
- // Nothing to read.
- ASSERT_THAT(PollAndReadFd(master_.get(), &c, 1, kTimeout),
- PosixErrorIs(ETIMEDOUT, ::testing::StrEq("Poll timed out")));
-}
-
-TEST_F(PtyTest, TermiosOCRNL) {
- struct kernel_termios t = DefaultTermios();
- t.c_oflag |= OCRNL;
- t.c_lflag &= ~ICANON; // for byte-by-byte reading.
- ASSERT_THAT(ioctl(slave_.get(), TCSETS, &t), SyscallSucceeds());
-
- // The terminal is at column 0, so there should be no CR to read.
- char c = '\r';
- ASSERT_THAT(WriteFd(slave_.get(), &c, 1), SyscallSucceedsWithValue(1));
-
- ExpectReadable(master_, 1, &c);
- EXPECT_EQ(c, '\n');
-
- ExpectFinished(master_);
-}
-
-// Tests that VEOL is disabled when we start, and that we can set it to enable
-// it.
-TEST_F(PtyTest, VEOLTermination) {
- // Write a few bytes ending with '\0', and confirm that we can't read.
- constexpr char kInput[] = "hello";
- ASSERT_THAT(WriteFd(master_.get(), kInput, sizeof(kInput)),
- SyscallSucceedsWithValue(sizeof(kInput)));
- char buf[sizeof(kInput)] = {};
- ASSERT_THAT(PollAndReadFd(slave_.get(), buf, sizeof(kInput), kTimeout),
- PosixErrorIs(ETIMEDOUT, ::testing::StrEq("Poll timed out")));
-
- // Set the EOL character to '=' and write it.
- constexpr char delim = '=';
- struct kernel_termios t = DefaultTermios();
- t.c_cc[VEOL] = delim;
- ASSERT_THAT(ioctl(slave_.get(), TCSETS, &t), SyscallSucceeds());
- ASSERT_THAT(WriteFd(master_.get(), &delim, 1), SyscallSucceedsWithValue(1));
-
- // Now we can read, as sending EOL caused the line to become available.
- ExpectReadable(slave_, sizeof(kInput), buf);
- EXPECT_EQ(memcmp(buf, kInput, sizeof(kInput)), 0);
-
- ExpectReadable(slave_, 1, buf);
- EXPECT_EQ(buf[0], '=');
-
- ExpectFinished(slave_);
-}
-
-// Tests that we can write more than the 4096 character limit, then a
-// terminating character, then read out just the first 4095 bytes plus the
-// terminator.
-TEST_F(PtyTest, CanonBigWrite) {
- constexpr int kWriteLen = kMaxLineSize + 4;
- char input[kWriteLen];
- memset(input, 'M', kWriteLen - 1);
- input[kWriteLen - 1] = '\n';
- ASSERT_THAT(WriteFd(master_.get(), input, kWriteLen),
- SyscallSucceedsWithValue(kWriteLen));
-
- // We can read the line.
- char buf[kMaxLineSize] = {};
- ExpectReadable(slave_, kMaxLineSize, buf);
-
- ExpectFinished(slave_);
-}
-
-// Tests that data written in canonical mode can be read immediately once
-// switched to noncanonical mode.
-TEST_F(PtyTest, SwitchCanonToNoncanon) {
- // Write a few bytes without a terminating character, switch to noncanonical
- // mode, and read them.
- constexpr char kInput[] = "hello";
- ASSERT_THAT(WriteFd(master_.get(), kInput, sizeof(kInput)),
- SyscallSucceedsWithValue(sizeof(kInput)));
-
- // Nothing available yet.
- char buf[sizeof(kInput)] = {};
- ASSERT_THAT(PollAndReadFd(slave_.get(), buf, sizeof(kInput), kTimeout),
- PosixErrorIs(ETIMEDOUT, ::testing::StrEq("Poll timed out")));
-
- DisableCanonical();
-
- ExpectReadable(slave_, sizeof(kInput), buf);
- EXPECT_STREQ(buf, kInput);
-
- ExpectFinished(slave_);
-}
-
-TEST_F(PtyTest, SwitchCanonToNonCanonNewline) {
- // Write a few bytes with a terminating character.
- constexpr char kInput[] = "hello\n";
- ASSERT_THAT(WriteFd(master_.get(), kInput, sizeof(kInput)),
- SyscallSucceedsWithValue(sizeof(kInput)));
-
- DisableCanonical();
-
- // We can read the line.
- char buf[sizeof(kInput)] = {};
- ExpectReadable(slave_, sizeof(kInput), buf);
- EXPECT_STREQ(buf, kInput);
-
- ExpectFinished(slave_);
-}
-
-TEST_F(PtyTest, SwitchNoncanonToCanonNewlineBig) {
- DisableCanonical();
-
- // Write more than the maximum line size, then write a delimiter.
- constexpr int kWriteLen = 4100;
- char input[kWriteLen];
- memset(input, 'M', kWriteLen);
- ASSERT_THAT(WriteFd(master_.get(), input, kWriteLen),
- SyscallSucceedsWithValue(kWriteLen));
- // Wait for the input queue to fill.
- ASSERT_NO_ERRNO(WaitUntilReceived(slave_.get(), kMaxLineSize - 1));
- constexpr char delim = '\n';
- ASSERT_THAT(WriteFd(master_.get(), &delim, 1), SyscallSucceedsWithValue(1));
-
- EnableCanonical();
-
- // We can read the line.
- char buf[kMaxLineSize] = {};
- ExpectReadable(slave_, kMaxLineSize - 1, buf);
-
- // We can also read the remaining characters.
- ExpectReadable(slave_, 6, buf);
-
- ExpectFinished(slave_);
-}
-
-TEST_F(PtyTest, SwitchNoncanonToCanonNoNewline) {
- DisableCanonical();
-
- // Write a few bytes without a terminating character.
- // mode, and read them.
- constexpr char kInput[] = "hello";
- ASSERT_THAT(WriteFd(master_.get(), kInput, sizeof(kInput) - 1),
- SyscallSucceedsWithValue(sizeof(kInput) - 1));
-
- ASSERT_NO_ERRNO(WaitUntilReceived(slave_.get(), sizeof(kInput) - 1));
- EnableCanonical();
-
- // We can read the line.
- char buf[sizeof(kInput)] = {};
- ExpectReadable(slave_, sizeof(kInput) - 1, buf);
- EXPECT_STREQ(buf, kInput);
-
- ExpectFinished(slave_);
-}
-
-TEST_F(PtyTest, SwitchNoncanonToCanonNoNewlineBig) {
- DisableCanonical();
-
- // Write a few bytes without a terminating character.
- // mode, and read them.
- constexpr int kWriteLen = 4100;
- char input[kWriteLen];
- memset(input, 'M', kWriteLen);
- ASSERT_THAT(WriteFd(master_.get(), input, kWriteLen),
- SyscallSucceedsWithValue(kWriteLen));
-
- ASSERT_NO_ERRNO(WaitUntilReceived(slave_.get(), kMaxLineSize - 1));
- EnableCanonical();
-
- // We can read the line.
- char buf[kMaxLineSize] = {};
- ExpectReadable(slave_, kMaxLineSize - 1, buf);
-
- ExpectFinished(slave_);
-}
-
-// Tests that we can write over the 4095 noncanonical limit, then read out
-// everything.
-TEST_F(PtyTest, NoncanonBigWrite) {
- DisableCanonical();
-
- // Write well over the 4095 internal buffer limit.
- constexpr char kInput = 'M';
- constexpr int kInputSize = kMaxLineSize * 2;
- for (int i = 0; i < kInputSize; i++) {
- // This makes too many syscalls for save/restore.
- const DisableSave ds;
- ASSERT_THAT(WriteFd(master_.get(), &kInput, sizeof(kInput)),
- SyscallSucceedsWithValue(sizeof(kInput)));
- }
-
- // We should be able to read out everything. Sleep a bit so that Linux has a
- // chance to move data from the master to the slave.
- ASSERT_NO_ERRNO(WaitUntilReceived(slave_.get(), kMaxLineSize - 1));
- for (int i = 0; i < kInputSize; i++) {
- // This makes too many syscalls for save/restore.
- const DisableSave ds;
- char c;
- ExpectReadable(slave_, 1, &c);
- ASSERT_EQ(c, kInput);
- }
-
- ExpectFinished(slave_);
-}
-
-// ICANON doesn't make input available until a line delimiter is typed.
-//
-// Test newline.
-TEST_F(PtyTest, TermiosICANONNewline) {
- char input[3] = {'a', 'b', 'c'};
- ASSERT_THAT(WriteFd(master_.get(), input, sizeof(input)),
- SyscallSucceedsWithValue(sizeof(input)));
-
- // Extra bytes for newline (written later) and NUL for EXPECT_STREQ.
- char buf[5] = {};
-
- // Nothing available yet.
- ASSERT_THAT(PollAndReadFd(slave_.get(), buf, sizeof(input), kTimeout),
- PosixErrorIs(ETIMEDOUT, ::testing::StrEq("Poll timed out")));
-
- char delim = '\n';
- ASSERT_THAT(WriteFd(master_.get(), &delim, 1), SyscallSucceedsWithValue(1));
-
- // Now it is available.
- ASSERT_NO_ERRNO(WaitUntilReceived(slave_.get(), sizeof(input) + 1));
- ExpectReadable(slave_, sizeof(input) + 1, buf);
- EXPECT_STREQ(buf, "abc\n");
-
- ExpectFinished(slave_);
-}
-
-// ICANON doesn't make input available until a line delimiter is typed.
-//
-// Test EOF (^D).
-TEST_F(PtyTest, TermiosICANONEOF) {
- char input[3] = {'a', 'b', 'c'};
- ASSERT_THAT(WriteFd(master_.get(), input, sizeof(input)),
- SyscallSucceedsWithValue(sizeof(input)));
-
- // Extra byte for NUL for EXPECT_STREQ.
- char buf[4] = {};
-
- // Nothing available yet.
- ASSERT_THAT(PollAndReadFd(slave_.get(), buf, sizeof(input), kTimeout),
- PosixErrorIs(ETIMEDOUT, ::testing::StrEq("Poll timed out")));
- char delim = ControlCharacter('D');
- ASSERT_THAT(WriteFd(master_.get(), &delim, 1), SyscallSucceedsWithValue(1));
-
- // Now it is available. Note that ^D is not included.
- ExpectReadable(slave_, sizeof(input), buf);
- EXPECT_STREQ(buf, "abc");
-
- ExpectFinished(slave_);
-}
-
-// ICANON limits us to 4096 bytes including a terminating character. Anything
-// after and 4095th character is discarded (although still processed for
-// signals and echoing).
-TEST_F(PtyTest, CanonDiscard) {
- constexpr char kInput = 'M';
- constexpr int kInputSize = 4100;
- constexpr int kIter = 3;
-
- // A few times write more than the 4096 character maximum, then a newline.
- constexpr char delim = '\n';
- for (int i = 0; i < kIter; i++) {
- // This makes too many syscalls for save/restore.
- const DisableSave ds;
- for (int i = 0; i < kInputSize; i++) {
- ASSERT_THAT(WriteFd(master_.get(), &kInput, sizeof(kInput)),
- SyscallSucceedsWithValue(sizeof(kInput)));
- }
- ASSERT_THAT(WriteFd(master_.get(), &delim, 1), SyscallSucceedsWithValue(1));
- }
-
- // There should be multiple truncated lines available to read.
- for (int i = 0; i < kIter; i++) {
- char buf[kInputSize] = {};
- ExpectReadable(slave_, kMaxLineSize, buf);
- EXPECT_EQ(buf[kMaxLineSize - 1], delim);
- EXPECT_EQ(buf[kMaxLineSize - 2], kInput);
- }
-
- ExpectFinished(slave_);
-}
-
-TEST_F(PtyTest, CanonMultiline) {
- constexpr char kInput1[] = "GO\n";
- constexpr char kInput2[] = "BLUE\n";
-
- // Write both lines.
- ASSERT_THAT(WriteFd(master_.get(), kInput1, sizeof(kInput1) - 1),
- SyscallSucceedsWithValue(sizeof(kInput1) - 1));
- ASSERT_THAT(WriteFd(master_.get(), kInput2, sizeof(kInput2) - 1),
- SyscallSucceedsWithValue(sizeof(kInput2) - 1));
-
- // Get the first line.
- char line1[8] = {};
- ExpectReadable(slave_, sizeof(kInput1) - 1, line1);
- EXPECT_STREQ(line1, kInput1);
-
- // Get the second line.
- char line2[8] = {};
- ExpectReadable(slave_, sizeof(kInput2) - 1, line2);
- EXPECT_STREQ(line2, kInput2);
-
- ExpectFinished(slave_);
-}
-
-TEST_F(PtyTest, SwitchNoncanonToCanonMultiline) {
- DisableCanonical();
-
- constexpr char kInput1[] = "GO\n";
- constexpr char kInput2[] = "BLUE\n";
- constexpr char kExpected[] = "GO\nBLUE\n";
-
- // Write both lines.
- ASSERT_THAT(WriteFd(master_.get(), kInput1, sizeof(kInput1) - 1),
- SyscallSucceedsWithValue(sizeof(kInput1) - 1));
- ASSERT_THAT(WriteFd(master_.get(), kInput2, sizeof(kInput2) - 1),
- SyscallSucceedsWithValue(sizeof(kInput2) - 1));
-
- ASSERT_NO_ERRNO(
- WaitUntilReceived(slave_.get(), sizeof(kInput1) + sizeof(kInput2) - 2));
- EnableCanonical();
-
- // Get all together as one line.
- char line[9] = {};
- ExpectReadable(slave_, 8, line);
- EXPECT_STREQ(line, kExpected);
-
- ExpectFinished(slave_);
-}
-
-TEST_F(PtyTest, SwitchTwiceMultiline) {
- std::string kInputs[] = {"GO\n", "BLUE\n", "!"};
- std::string kExpected = "GO\nBLUE\n!";
-
- // Write each line.
- for (std::string input : kInputs) {
- ASSERT_THAT(WriteFd(master_.get(), input.c_str(), input.size()),
- SyscallSucceedsWithValue(input.size()));
- }
-
- DisableCanonical();
- // All written characters have to make it into the input queue before
- // canonical mode is re-enabled. If the final '!' character hasn't been
- // enqueued before canonical mode is re-enabled, it won't be readable.
- ASSERT_NO_ERRNO(WaitUntilReceived(slave_.get(), kExpected.size()));
- EnableCanonical();
-
- // Get all together as one line.
- char line[10] = {};
- ExpectReadable(slave_, 9, line);
- EXPECT_STREQ(line, kExpected.c_str());
-
- ExpectFinished(slave_);
-}
-
-TEST_F(PtyTest, QueueSize) {
- // Write the line.
- constexpr char kInput1[] = "GO\n";
- ASSERT_THAT(WriteFd(master_.get(), kInput1, sizeof(kInput1) - 1),
- SyscallSucceedsWithValue(sizeof(kInput1) - 1));
- ASSERT_NO_ERRNO(WaitUntilReceived(slave_.get(), sizeof(kInput1) - 1));
-
- // Ensure that writing more (beyond what is readable) does not impact the
- // readable size.
- char input[kMaxLineSize];
- memset(input, 'M', kMaxLineSize);
- ASSERT_THAT(WriteFd(master_.get(), input, kMaxLineSize),
- SyscallSucceedsWithValue(kMaxLineSize));
- int inputBufSize = ASSERT_NO_ERRNO_AND_VALUE(
- WaitUntilReceived(slave_.get(), sizeof(kInput1) - 1));
- EXPECT_EQ(inputBufSize, sizeof(kInput1) - 1);
-}
-
-TEST_F(PtyTest, PartialBadBuffer) {
- // Allocate 2 pages.
- void* addr = mmap(nullptr, 2 * kPageSize, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- ASSERT_NE(addr, MAP_FAILED);
- char* buf = reinterpret_cast<char*>(addr);
-
- // Guard the 2nd page for our read to run into.
- ASSERT_THAT(
- mprotect(reinterpret_cast<void*>(buf + kPageSize), kPageSize, PROT_NONE),
- SyscallSucceeds());
-
- // Leave only one free byte in the buffer.
- char* bad_buffer = buf + kPageSize - 1;
-
- // Write to the master.
- constexpr char kBuf[] = "hello\n";
- constexpr size_t size = sizeof(kBuf) - 1;
- EXPECT_THAT(WriteFd(master_.get(), kBuf, size),
- SyscallSucceedsWithValue(size));
-
- // Read from the slave into bad_buffer.
- ASSERT_NO_ERRNO(WaitUntilReceived(slave_.get(), size));
- EXPECT_THAT(ReadFd(slave_.get(), bad_buffer, size),
- SyscallFailsWithErrno(EFAULT));
-
- EXPECT_THAT(munmap(addr, 2 * kPageSize), SyscallSucceeds()) << addr;
-}
-
-TEST_F(PtyTest, SimpleEcho) {
- constexpr char kInput[] = "Mr. Eko";
- EXPECT_THAT(WriteFd(master_.get(), kInput, strlen(kInput)),
- SyscallSucceedsWithValue(strlen(kInput)));
-
- char buf[100] = {};
- ExpectReadable(master_, strlen(kInput), buf);
-
- EXPECT_STREQ(buf, kInput);
- ExpectFinished(master_);
-}
-
-TEST_F(PtyTest, GetWindowSize) {
- struct winsize ws;
- ASSERT_THAT(ioctl(slave_.get(), TIOCGWINSZ, &ws), SyscallSucceeds());
- EXPECT_EQ(ws.ws_row, 0);
- EXPECT_EQ(ws.ws_col, 0);
-}
-
-TEST_F(PtyTest, SetSlaveWindowSize) {
- constexpr uint16_t kRows = 343;
- constexpr uint16_t kCols = 2401;
- struct winsize ws = {.ws_row = kRows, .ws_col = kCols};
- ASSERT_THAT(ioctl(slave_.get(), TIOCSWINSZ, &ws), SyscallSucceeds());
-
- struct winsize retrieved_ws = {};
- ASSERT_THAT(ioctl(master_.get(), TIOCGWINSZ, &retrieved_ws),
- SyscallSucceeds());
- EXPECT_EQ(retrieved_ws.ws_row, kRows);
- EXPECT_EQ(retrieved_ws.ws_col, kCols);
-}
-
-TEST_F(PtyTest, SetMasterWindowSize) {
- constexpr uint16_t kRows = 343;
- constexpr uint16_t kCols = 2401;
- struct winsize ws = {.ws_row = kRows, .ws_col = kCols};
- ASSERT_THAT(ioctl(master_.get(), TIOCSWINSZ, &ws), SyscallSucceeds());
-
- struct winsize retrieved_ws = {};
- ASSERT_THAT(ioctl(slave_.get(), TIOCGWINSZ, &retrieved_ws),
- SyscallSucceeds());
- EXPECT_EQ(retrieved_ws.ws_row, kRows);
- EXPECT_EQ(retrieved_ws.ws_col, kCols);
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/pwrite64.cc b/test/syscalls/linux/pwrite64.cc
deleted file mode 100644
index e1603fc2d..000000000
--- a/test/syscalls/linux/pwrite64.cc
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// This test is currently very rudimentary.
-//
-// TODO(edahlgren):
-// * bad buffer states (EFAULT).
-// * bad fds (wrong permission, wrong type of file, EBADF).
-// * check offset is not incremented.
-// * check for EOF.
-// * writing to pipes, symlinks, special files.
-class Pwrite64 : public ::testing::Test {
- void SetUp() override {
- name_ = NewTempAbsPath();
- int fd;
- ASSERT_THAT(fd = open(name_.c_str(), O_CREAT, 0644), SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
- }
-
- void TearDown() override { unlink(name_.c_str()); }
-
- public:
- std::string name_;
-};
-
-TEST_F(Pwrite64, AppendOnly) {
- int fd;
- ASSERT_THAT(fd = open(name_.c_str(), O_APPEND | O_RDWR), SyscallSucceeds());
- constexpr int64_t kBufSize = 1024;
- std::vector<char> buf(kBufSize);
- std::fill(buf.begin(), buf.end(), 'a');
- EXPECT_THAT(PwriteFd(fd, buf.data(), buf.size(), 0),
- SyscallSucceedsWithValue(buf.size()));
- EXPECT_THAT(lseek(fd, 0, SEEK_CUR), SyscallSucceedsWithValue(0));
- EXPECT_THAT(close(fd), SyscallSucceeds());
-}
-
-TEST_F(Pwrite64, InvalidArgs) {
- int fd;
- ASSERT_THAT(fd = open(name_.c_str(), O_APPEND | O_RDWR), SyscallSucceeds());
- constexpr int64_t kBufSize = 1024;
- std::vector<char> buf(kBufSize);
- std::fill(buf.begin(), buf.end(), 'a');
- EXPECT_THAT(PwriteFd(fd, buf.data(), buf.size(), -1),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(close(fd), SyscallSucceeds());
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/pwritev2.cc b/test/syscalls/linux/pwritev2.cc
deleted file mode 100644
index f6a0fc96c..000000000
--- a/test/syscalls/linux/pwritev2.cc
+++ /dev/null
@@ -1,345 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <sys/syscall.h>
-#include <sys/types.h>
-#include <sys/uio.h>
-
-#include <string>
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/file_base.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-#ifndef SYS_pwritev2
-#if defined(__x86_64__)
-#define SYS_pwritev2 328
-#else
-#error "Unknown architecture"
-#endif
-#endif // SYS_pwrite2
-
-#ifndef RWF_HIPRI
-#define RWF_HIPRI 0x1
-#endif // RWF_HIPRI
-
-#ifndef RWF_DSYNC
-#define RWF_DSYNC 0x2
-#endif // RWF_DSYNC
-
-#ifndef RWF_SYNC
-#define RWF_SYNC 0x4
-#endif // RWF_SYNC
-
-constexpr int kBufSize = 1024;
-
-void SetContent(std::vector<char>& content) {
- for (uint i = 0; i < content.size(); i++) {
- content[i] = static_cast<char>((i % 10) + '0');
- }
-}
-
-ssize_t pwritev2(unsigned long fd, const struct iovec* iov,
- unsigned long iovcnt, off_t offset, unsigned long flags) {
- // syscall on pwritev2 does some weird things (see man syscall and search
- // pwritev2), so we insert a 0 to word align the flags argument on native.
- return syscall(SYS_pwritev2, fd, iov, iovcnt, offset, 0, flags);
-}
-
-// This test is the base case where we call pwritev (no offset, no flags).
-TEST(Writev2Test, TestBaseCall) {
- SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
-
- const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
-
- std::vector<char> content(kBufSize);
- SetContent(content);
- struct iovec iov[2];
- iov[0].iov_base = content.data();
- iov[0].iov_len = content.size() / 2;
- iov[1].iov_base = static_cast<char*>(iov[0].iov_base) + (content.size() / 2);
- iov[1].iov_len = content.size() / 2;
-
- ASSERT_THAT(pwritev2(fd.get(), iov, /*iovcnt=*/2,
- /*offset=*/0, /*flags=*/0),
- SyscallSucceedsWithValue(kBufSize));
-
- std::vector<char> buf(kBufSize);
- EXPECT_THAT(read(fd.get(), buf.data(), kBufSize),
- SyscallSucceedsWithValue(kBufSize));
-
- EXPECT_EQ(content, buf);
-}
-
-// This test is where we call pwritev2 with a positive offset and no flags.
-TEST(Pwritev2Test, TestValidPositiveOffset) {
- SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
-
- std::string prefix(kBufSize, '0');
-
- const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), prefix, TempPath::kDefaultFileMode));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
-
- std::vector<char> content(kBufSize);
- SetContent(content);
- struct iovec iov;
- iov.iov_base = content.data();
- iov.iov_len = content.size();
-
- ASSERT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1,
- /*offset=*/prefix.size(), /*flags=*/0),
- SyscallSucceedsWithValue(content.size()));
-
- std::vector<char> buf(prefix.size() + content.size());
- EXPECT_THAT(read(fd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(buf.size()));
-
- std::vector<char> want(prefix.begin(), prefix.end());
- want.insert(want.end(), content.begin(), content.end());
- EXPECT_EQ(want, buf);
-}
-
-// This test is the base case where we call writev by using -1 as the offset.
-// The write should use the file offset, so the test increments the file offset
-// prior to call pwritev2.
-TEST(Pwritev2Test, TestNegativeOneOffset) {
- SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
-
- const std::string prefix = "00";
- const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), prefix.data(), TempPath::kDefaultFileMode));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
- ASSERT_THAT(lseek(fd.get(), prefix.size(), SEEK_SET),
- SyscallSucceedsWithValue(prefix.size()));
-
- std::vector<char> content(kBufSize);
- SetContent(content);
- struct iovec iov;
- iov.iov_base = content.data();
- iov.iov_len = content.size();
-
- ASSERT_THAT(pwritev2(fd.get(), &iov, /*iovcnt*/ 1,
- /*offset=*/static_cast<off_t>(-1), /*flags=*/0),
- SyscallSucceedsWithValue(content.size()));
-
- ASSERT_THAT(lseek(fd.get(), 0, SEEK_CUR),
- SyscallSucceedsWithValue(prefix.size() + content.size()));
-
- std::vector<char> buf(prefix.size() + content.size());
- EXPECT_THAT(pread(fd.get(), buf.data(), buf.size(), /*offset=*/0),
- SyscallSucceedsWithValue(buf.size()));
-
- std::vector<char> want(prefix.begin(), prefix.end());
- want.insert(want.end(), content.begin(), content.end());
- EXPECT_EQ(want, buf);
-}
-
-// pwritev2 requires if the RWF_HIPRI flag is passed, the fd must be opened with
-// O_DIRECT. This test implements a correct call with the RWF_HIPRI flag.
-TEST(Pwritev2Test, TestCallWithRWF_HIPRI) {
- SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
-
- const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
-
- std::vector<char> content(kBufSize);
- SetContent(content);
- struct iovec iov;
- iov.iov_base = content.data();
- iov.iov_len = content.size();
-
- EXPECT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1,
- /*offset=*/0, /*flags=*/RWF_HIPRI),
- SyscallSucceedsWithValue(kBufSize));
-
- std::vector<char> buf(content.size());
- EXPECT_THAT(read(fd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(buf.size()));
-
- EXPECT_EQ(buf, content);
-}
-
-// This test checks that pwritev2 can be called with valid flags
-TEST(Pwritev2Test, TestCallWithValidFlags) {
- SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
-
- const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
-
- std::vector<char> content(kBufSize, '0');
- struct iovec iov;
- iov.iov_base = content.data();
- iov.iov_len = content.size();
-
- EXPECT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1,
- /*offset=*/0, /*flags=*/RWF_DSYNC),
- SyscallSucceedsWithValue(kBufSize));
-
- std::vector<char> buf(content.size());
- EXPECT_THAT(read(fd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(buf.size()));
-
- EXPECT_EQ(buf, content);
-
- SetContent(content);
-
- EXPECT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1,
- /*offset=*/0, /*flags=*/0x4),
- SyscallSucceedsWithValue(kBufSize));
-
- ASSERT_THAT(lseek(fd.get(), 0, SEEK_CUR),
- SyscallSucceedsWithValue(content.size()));
-
- EXPECT_THAT(pread(fd.get(), buf.data(), buf.size(), /*offset=*/0),
- SyscallSucceedsWithValue(buf.size()));
-
- EXPECT_EQ(buf, content);
-}
-
-// This test calls pwritev2 with a bad file descriptor.
-TEST(Writev2Test, TestBadFile) {
- SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
- ASSERT_THAT(pwritev2(/*fd=*/-1, /*iov=*/nullptr, /*iovcnt=*/0,
- /*offset=*/0, /*flags=*/0),
- SyscallFailsWithErrno(EBADF));
-}
-
-// This test calls pwrite2 with an invalid offset.
-TEST(Pwritev2Test, TestInvalidOffset) {
- SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
-
- const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
-
- char buf[16];
- struct iovec iov;
- iov.iov_base = buf;
- iov.iov_len = sizeof(buf);
-
- EXPECT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1,
- /*offset=*/static_cast<off_t>(-8), /*flags=*/0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(Pwritev2Test, TestUnseekableFileValid) {
- SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
-
- int pipe_fds[2];
-
- ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds());
-
- std::vector<char> content(32, '0');
- SetContent(content);
- struct iovec iov;
- iov.iov_base = content.data();
- iov.iov_len = content.size();
-
- EXPECT_THAT(pwritev2(pipe_fds[1], &iov, /*iovcnt=*/1,
- /*offset=*/static_cast<off_t>(-1), /*flags=*/0),
- SyscallSucceedsWithValue(content.size()));
-
- std::vector<char> buf(content.size());
- EXPECT_THAT(read(pipe_fds[0], buf.data(), buf.size()),
- SyscallSucceedsWithValue(buf.size()));
-
- EXPECT_EQ(content, buf);
-
- EXPECT_THAT(close(pipe_fds[0]), SyscallSucceeds());
- EXPECT_THAT(close(pipe_fds[1]), SyscallSucceeds());
-}
-
-// Calling pwritev2 with a non-negative offset calls pwritev. Calling pwritev
-// with an unseekable file is not allowed. A pipe is used for an unseekable
-// file.
-TEST(Pwritev2Test, TestUnseekableFileInValid) {
- SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
-
- int pipe_fds[2];
- char buf[16];
- struct iovec iov;
- iov.iov_base = buf;
- iov.iov_len = sizeof(buf);
-
- ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds());
-
- EXPECT_THAT(pwritev2(pipe_fds[1], &iov, /*iovcnt=*/1,
- /*offset=*/2, /*flags=*/0),
- SyscallFailsWithErrno(ESPIPE));
-
- EXPECT_THAT(close(pipe_fds[0]), SyscallSucceeds());
- EXPECT_THAT(close(pipe_fds[1]), SyscallSucceeds());
-}
-
-TEST(Pwritev2Test, TestReadOnlyFile) {
- SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
-
- const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY));
-
- char buf[16];
- struct iovec iov;
- iov.iov_base = buf;
- iov.iov_len = sizeof(buf);
-
- EXPECT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1,
- /*offset=*/0, /*flags=*/0),
- SyscallFailsWithErrno(EBADF));
-}
-
-// This test calls pwritev2 with an invalid flag.
-TEST(Pwritev2Test, TestInvalidFlag) {
- SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
-
- const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR | O_DIRECT));
-
- char buf[16];
- struct iovec iov;
- iov.iov_base = buf;
- iov.iov_len = sizeof(buf);
-
- EXPECT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1,
- /*offset=*/0, /*flags=*/0xF0),
- SyscallFailsWithErrno(EOPNOTSUPP));
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/raw_socket_hdrincl.cc b/test/syscalls/linux/raw_socket_hdrincl.cc
deleted file mode 100644
index a070817eb..000000000
--- a/test/syscalls/linux/raw_socket_hdrincl.cc
+++ /dev/null
@@ -1,408 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <linux/capability.h>
-#include <netinet/in.h>
-#include <netinet/ip.h>
-#include <netinet/ip_icmp.h>
-#include <netinet/udp.h>
-#include <poll.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <algorithm>
-#include <cstring>
-
-#include "gtest/gtest.h"
-#include "absl/base/internal/endian.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/capability_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// Tests for IPPROTO_RAW raw sockets, which implies IP_HDRINCL.
-class RawHDRINCL : public ::testing::Test {
- protected:
- // Creates a socket to be used in tests.
- void SetUp() override;
-
- // Closes the socket created by SetUp().
- void TearDown() override;
-
- // Returns a valid looback IP header with no payload.
- struct iphdr LoopbackHeader();
-
- // Fills in buf with an IP header, UDP header, and payload. Returns false if
- // buf_size isn't large enough to hold everything.
- bool FillPacket(char* buf, size_t buf_size, int port, const char* payload,
- uint16_t payload_size);
-
- // The socket used for both reading and writing.
- int socket_;
-
- // The loopback address.
- struct sockaddr_in addr_;
-};
-
-void RawHDRINCL::SetUp() {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(socket_ = socket(AF_INET, SOCK_RAW, IPPROTO_RAW),
- SyscallSucceeds());
-
- addr_ = {};
-
- addr_.sin_port = IPPROTO_IP;
- addr_.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- addr_.sin_family = AF_INET;
-}
-
-void RawHDRINCL::TearDown() {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- EXPECT_THAT(close(socket_), SyscallSucceeds());
-}
-
-struct iphdr RawHDRINCL::LoopbackHeader() {
- struct iphdr hdr = {};
- hdr.ihl = 5;
- hdr.version = 4;
- hdr.tos = 0;
- hdr.tot_len = absl::gbswap_16(sizeof(hdr));
- hdr.id = 0;
- hdr.frag_off = 0;
- hdr.ttl = 7;
- hdr.protocol = 1;
- hdr.daddr = htonl(INADDR_LOOPBACK);
- // hdr.check is set by the network stack.
- // hdr.tot_len is set by the network stack.
- // hdr.saddr is set by the network stack.
- return hdr;
-}
-
-bool RawHDRINCL::FillPacket(char* buf, size_t buf_size, int port,
- const char* payload, uint16_t payload_size) {
- if (buf_size < sizeof(struct iphdr) + sizeof(struct udphdr) + payload_size) {
- return false;
- }
-
- struct iphdr ip = LoopbackHeader();
- ip.protocol = IPPROTO_UDP;
-
- struct udphdr udp = {};
- udp.source = absl::gbswap_16(port);
- udp.dest = absl::gbswap_16(port);
- udp.len = absl::gbswap_16(sizeof(udp) + payload_size);
- udp.check = 0;
-
- memcpy(buf, reinterpret_cast<char*>(&ip), sizeof(ip));
- memcpy(buf + sizeof(ip), reinterpret_cast<char*>(&udp), sizeof(udp));
- memcpy(buf + sizeof(ip) + sizeof(udp), payload, payload_size);
-
- return true;
-}
-
-// We should be able to create multiple IPPROTO_RAW sockets. RawHDRINCL::Setup
-// creates the first one, so we only have to create one more here.
-TEST_F(RawHDRINCL, MultipleCreation) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- int s2;
- ASSERT_THAT(s2 = socket(AF_INET, SOCK_RAW, IPPROTO_RAW), SyscallSucceeds());
-
- ASSERT_THAT(close(s2), SyscallSucceeds());
-}
-
-// Test that shutting down an unconnected socket fails.
-TEST_F(RawHDRINCL, FailShutdownWithoutConnect) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(shutdown(socket_, SHUT_WR), SyscallFailsWithErrno(ENOTCONN));
- ASSERT_THAT(shutdown(socket_, SHUT_RD), SyscallFailsWithErrno(ENOTCONN));
-}
-
-// Test that listen() fails.
-TEST_F(RawHDRINCL, FailListen) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(listen(socket_, 1), SyscallFailsWithErrno(ENOTSUP));
-}
-
-// Test that accept() fails.
-TEST_F(RawHDRINCL, FailAccept) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- struct sockaddr saddr;
- socklen_t addrlen;
- ASSERT_THAT(accept(socket_, &saddr, &addrlen),
- SyscallFailsWithErrno(ENOTSUP));
-}
-
-// Test that the socket is writable immediately.
-TEST_F(RawHDRINCL, PollWritableImmediately) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- struct pollfd pfd = {};
- pfd.fd = socket_;
- pfd.events = POLLOUT;
- ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 0), SyscallSucceedsWithValue(1));
-}
-
-// Test that the socket isn't readable.
-TEST_F(RawHDRINCL, NotReadable) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- // Try to receive data with MSG_DONTWAIT, which returns immediately if there's
- // nothing to be read.
- char buf[117];
- ASSERT_THAT(RetryEINTR(recv)(socket_, buf, sizeof(buf), MSG_DONTWAIT),
- SyscallFailsWithErrno(EINVAL));
-}
-
-// Test that we can connect() to a valid IP (loopback).
-TEST_F(RawHDRINCL, ConnectToLoopback) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(connect(socket_, reinterpret_cast<struct sockaddr*>(&addr_),
- sizeof(addr_)),
- SyscallSucceeds());
-}
-
-TEST_F(RawHDRINCL, SendWithoutConnectSucceeds) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- struct iphdr hdr = LoopbackHeader();
- ASSERT_THAT(send(socket_, &hdr, sizeof(hdr), 0),
- SyscallSucceedsWithValue(sizeof(hdr)));
-}
-
-// HDRINCL implies write-only. Verify that we can't read a packet sent to
-// loopback.
-TEST_F(RawHDRINCL, NotReadableAfterWrite) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(connect(socket_, reinterpret_cast<struct sockaddr*>(&addr_),
- sizeof(addr_)),
- SyscallSucceeds());
-
- // Construct a packet with an IP header, UDP header, and payload.
- constexpr char kPayload[] = "odst";
- char packet[sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(kPayload)];
- ASSERT_TRUE(FillPacket(packet, sizeof(packet), 40000 /* port */, kPayload,
- sizeof(kPayload)));
-
- socklen_t addrlen = sizeof(addr_);
- ASSERT_NO_FATAL_FAILURE(
- sendto(socket_, reinterpret_cast<void*>(&packet), sizeof(packet), 0,
- reinterpret_cast<struct sockaddr*>(&addr_), addrlen));
-
- struct pollfd pfd = {};
- pfd.fd = socket_;
- pfd.events = POLLIN;
- ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 1000), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(RawHDRINCL, WriteTooSmall) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(connect(socket_, reinterpret_cast<struct sockaddr*>(&addr_),
- sizeof(addr_)),
- SyscallSucceeds());
-
- // This is smaller than the size of an IP header.
- constexpr char kBuf[] = "JP5";
- ASSERT_THAT(send(socket_, kBuf, sizeof(kBuf), 0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-// Bind to localhost.
-TEST_F(RawHDRINCL, BindToLocalhost) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(
- bind(socket_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)),
- SyscallSucceeds());
-}
-
-// Bind to a different address.
-TEST_F(RawHDRINCL, BindToInvalid) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- struct sockaddr_in bind_addr = {};
- bind_addr.sin_family = AF_INET;
- bind_addr.sin_addr = {1}; // 1.0.0.0 - An address that we can't bind to.
- ASSERT_THAT(bind(socket_, reinterpret_cast<struct sockaddr*>(&bind_addr),
- sizeof(bind_addr)),
- SyscallFailsWithErrno(EADDRNOTAVAIL));
-}
-
-// Send and receive a packet.
-TEST_F(RawHDRINCL, SendAndReceive) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- int port = 40000;
- if (!IsRunningOnGvisor()) {
- port = static_cast<short>(ASSERT_NO_ERRNO_AND_VALUE(
- PortAvailable(0, AddressFamily::kIpv4, SocketType::kUdp, false)));
- }
-
- // IPPROTO_RAW sockets are write-only. We'll have to open another socket to
- // read what we write.
- FileDescriptor udp_sock =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_RAW, IPPROTO_UDP));
-
- // Construct a packet with an IP header, UDP header, and payload.
- constexpr char kPayload[] = "toto";
- char packet[sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(kPayload)];
- ASSERT_TRUE(
- FillPacket(packet, sizeof(packet), port, kPayload, sizeof(kPayload)));
-
- socklen_t addrlen = sizeof(addr_);
- ASSERT_NO_FATAL_FAILURE(sendto(socket_, &packet, sizeof(packet), 0,
- reinterpret_cast<struct sockaddr*>(&addr_),
- addrlen));
-
- // Receive the payload.
- char recv_buf[sizeof(packet)];
- struct sockaddr_in src;
- socklen_t src_size = sizeof(src);
- ASSERT_THAT(recvfrom(udp_sock.get(), recv_buf, sizeof(recv_buf), 0,
- reinterpret_cast<struct sockaddr*>(&src), &src_size),
- SyscallSucceedsWithValue(sizeof(packet)));
- EXPECT_EQ(
- memcmp(kPayload, recv_buf + sizeof(struct iphdr) + sizeof(struct udphdr),
- sizeof(kPayload)),
- 0);
- // The network stack should have set the source address.
- EXPECT_EQ(src.sin_family, AF_INET);
- EXPECT_EQ(absl::gbswap_32(src.sin_addr.s_addr), INADDR_LOOPBACK);
- // The packet ID should be 0, as the packet is less than 68 bytes.
- struct iphdr iphdr = {};
- memcpy(&iphdr, recv_buf, sizeof(iphdr));
- EXPECT_EQ(iphdr.id, 0);
-}
-
-// Send and receive a packet with nonzero IP ID.
-TEST_F(RawHDRINCL, SendAndReceiveNonzeroID) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- int port = 40000;
- if (!IsRunningOnGvisor()) {
- port = static_cast<short>(ASSERT_NO_ERRNO_AND_VALUE(
- PortAvailable(0, AddressFamily::kIpv4, SocketType::kUdp, false)));
- }
-
- // IPPROTO_RAW sockets are write-only. We'll have to open another socket to
- // read what we write.
- FileDescriptor udp_sock =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_RAW, IPPROTO_UDP));
-
- // Construct a packet with an IP header, UDP header, and payload. Make the
- // payload large enough to force an IP ID to be assigned.
- constexpr char kPayload[128] = {};
- char packet[sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(kPayload)];
- ASSERT_TRUE(
- FillPacket(packet, sizeof(packet), port, kPayload, sizeof(kPayload)));
-
- socklen_t addrlen = sizeof(addr_);
- ASSERT_NO_FATAL_FAILURE(sendto(socket_, &packet, sizeof(packet), 0,
- reinterpret_cast<struct sockaddr*>(&addr_),
- addrlen));
-
- // Receive the payload.
- char recv_buf[sizeof(packet)];
- struct sockaddr_in src;
- socklen_t src_size = sizeof(src);
- ASSERT_THAT(recvfrom(udp_sock.get(), recv_buf, sizeof(recv_buf), 0,
- reinterpret_cast<struct sockaddr*>(&src), &src_size),
- SyscallSucceedsWithValue(sizeof(packet)));
- EXPECT_EQ(
- memcmp(kPayload, recv_buf + sizeof(struct iphdr) + sizeof(struct udphdr),
- sizeof(kPayload)),
- 0);
- // The network stack should have set the source address.
- EXPECT_EQ(src.sin_family, AF_INET);
- EXPECT_EQ(absl::gbswap_32(src.sin_addr.s_addr), INADDR_LOOPBACK);
- // The packet ID should not be 0, as the packet was more than 68 bytes.
- struct iphdr* iphdr = reinterpret_cast<struct iphdr*>(recv_buf);
- EXPECT_NE(iphdr->id, 0);
-}
-
-// Send and receive a packet where the sendto address is not the same as the
-// provided destination.
-TEST_F(RawHDRINCL, SendAndReceiveDifferentAddress) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- int port = 40000;
- if (!IsRunningOnGvisor()) {
- port = static_cast<short>(ASSERT_NO_ERRNO_AND_VALUE(
- PortAvailable(0, AddressFamily::kIpv4, SocketType::kUdp, false)));
- }
-
- // IPPROTO_RAW sockets are write-only. We'll have to open another socket to
- // read what we write.
- FileDescriptor udp_sock =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_RAW, IPPROTO_UDP));
-
- // Construct a packet with an IP header, UDP header, and payload.
- constexpr char kPayload[] = "toto";
- char packet[sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(kPayload)];
- ASSERT_TRUE(
- FillPacket(packet, sizeof(packet), port, kPayload, sizeof(kPayload)));
- // Overwrite the IP destination address with an IP we can't get to.
- struct iphdr iphdr = {};
- memcpy(&iphdr, packet, sizeof(iphdr));
- iphdr.daddr = 42;
- memcpy(packet, &iphdr, sizeof(iphdr));
-
- socklen_t addrlen = sizeof(addr_);
- ASSERT_NO_FATAL_FAILURE(sendto(socket_, &packet, sizeof(packet), 0,
- reinterpret_cast<struct sockaddr*>(&addr_),
- addrlen));
-
- // Receive the payload, since sendto should replace the bad destination with
- // localhost.
- char recv_buf[sizeof(packet)];
- struct sockaddr_in src;
- socklen_t src_size = sizeof(src);
- ASSERT_THAT(recvfrom(udp_sock.get(), recv_buf, sizeof(recv_buf), 0,
- reinterpret_cast<struct sockaddr*>(&src), &src_size),
- SyscallSucceedsWithValue(sizeof(packet)));
- EXPECT_EQ(
- memcmp(kPayload, recv_buf + sizeof(struct iphdr) + sizeof(struct udphdr),
- sizeof(kPayload)),
- 0);
- // The network stack should have set the source address.
- EXPECT_EQ(src.sin_family, AF_INET);
- EXPECT_EQ(absl::gbswap_32(src.sin_addr.s_addr), INADDR_LOOPBACK);
- // The packet ID should be 0, as the packet is less than 68 bytes.
- struct iphdr recv_iphdr = {};
- memcpy(&recv_iphdr, recv_buf, sizeof(recv_iphdr));
- EXPECT_EQ(recv_iphdr.id, 0);
- // The destination address should be localhost, not the bad IP we set
- // initially.
- EXPECT_EQ(absl::gbswap_32(recv_iphdr.daddr), INADDR_LOOPBACK);
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/raw_socket_icmp.cc b/test/syscalls/linux/raw_socket_icmp.cc
deleted file mode 100644
index 971592d7d..000000000
--- a/test/syscalls/linux/raw_socket_icmp.cc
+++ /dev/null
@@ -1,509 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <linux/capability.h>
-#include <netinet/in.h>
-#include <netinet/ip.h>
-#include <netinet/ip_icmp.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <algorithm>
-#include <cstdint>
-
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/capability_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// The size of an empty ICMP packet and IP header together.
-constexpr size_t kEmptyICMPSize = 28;
-
-// ICMP raw sockets get their own special tests because Linux automatically
-// responds to ICMP echo requests, and thus a single echo request sent via
-// loopback leads to 2 received ICMP packets.
-
-class RawSocketICMPTest : public ::testing::Test {
- protected:
- // Creates a socket to be used in tests.
- void SetUp() override;
-
- // Closes the socket created by SetUp().
- void TearDown() override;
-
- // Checks that both an ICMP echo request and reply are received. Calls should
- // be wrapped in ASSERT_NO_FATAL_FAILURE.
- void ExpectICMPSuccess(const struct icmphdr& icmp);
-
- // Sends icmp via s_.
- void SendEmptyICMP(const struct icmphdr& icmp);
-
- // Sends icmp via s_ to the given address.
- void SendEmptyICMPTo(int sock, const struct sockaddr_in& addr,
- const struct icmphdr& icmp);
-
- // Reads from s_ into recv_buf.
- void ReceiveICMP(char* recv_buf, size_t recv_buf_len, size_t expected_size,
- struct sockaddr_in* src);
-
- // Reads from sock into recv_buf.
- void ReceiveICMPFrom(char* recv_buf, size_t recv_buf_len,
- size_t expected_size, struct sockaddr_in* src, int sock);
-
- // The socket used for both reading and writing.
- int s_;
-
- // The loopback address.
- struct sockaddr_in addr_;
-};
-
-void RawSocketICMPTest::SetUp() {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(s_ = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP), SyscallSucceeds());
-
- addr_ = {};
-
- // "On raw sockets sin_port is set to the IP protocol." - ip(7).
- addr_.sin_port = IPPROTO_IP;
- addr_.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- addr_.sin_family = AF_INET;
-}
-
-void RawSocketICMPTest::TearDown() {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- EXPECT_THAT(close(s_), SyscallSucceeds());
-}
-
-// We'll only read an echo in this case, as the kernel won't respond to the
-// malformed ICMP checksum.
-TEST_F(RawSocketICMPTest, SendAndReceiveBadChecksum) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- // Prepare and send an ICMP packet. Use arbitrary junk for checksum, sequence,
- // and ID. None of that should matter for raw sockets - the kernel should
- // still give us the packet.
- struct icmphdr icmp;
- icmp.type = ICMP_ECHO;
- icmp.code = 0;
- icmp.checksum = 0;
- icmp.un.echo.sequence = 2012;
- icmp.un.echo.id = 2014;
- ASSERT_NO_FATAL_FAILURE(SendEmptyICMP(icmp));
-
- // Veryify that we get the echo, then that there's nothing else to read.
- char recv_buf[kEmptyICMPSize];
- struct sockaddr_in src;
- ASSERT_NO_FATAL_FAILURE(
- ReceiveICMP(recv_buf, sizeof(recv_buf), sizeof(struct icmphdr), &src));
- EXPECT_EQ(memcmp(&src, &addr_, sizeof(src)), 0);
- // The packet should be identical to what we sent.
- EXPECT_EQ(memcmp(recv_buf + sizeof(struct iphdr), &icmp, sizeof(icmp)), 0);
-
- // And there should be nothing left to read.
- EXPECT_THAT(RetryEINTR(recv)(s_, recv_buf, sizeof(recv_buf), MSG_DONTWAIT),
- SyscallFailsWithErrno(EAGAIN));
-}
-//
-// Send and receive an ICMP packet.
-TEST_F(RawSocketICMPTest, SendAndReceive) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- // Prepare and send an ICMP packet. Use arbitrary junk for sequence and ID.
- // None of that should matter for raw sockets - the kernel should still give
- // us the packet.
- struct icmphdr icmp;
- icmp.type = ICMP_ECHO;
- icmp.code = 0;
- icmp.checksum = 0;
- icmp.un.echo.sequence = 2012;
- icmp.un.echo.id = 2014;
- icmp.checksum = ICMPChecksum(icmp, NULL, 0);
- ASSERT_NO_FATAL_FAILURE(SendEmptyICMP(icmp));
-
- ASSERT_NO_FATAL_FAILURE(ExpectICMPSuccess(icmp));
-}
-
-// We should be able to create multiple raw sockets for the same protocol and
-// receive the same packet on both.
-TEST_F(RawSocketICMPTest, MultipleSocketReceive) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- FileDescriptor s2 =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_RAW, IPPROTO_ICMP));
-
- // Prepare and send an ICMP packet. Use arbitrary junk for sequence and ID.
- // None of that should matter for raw sockets - the kernel should still give
- // us the packet.
- struct icmphdr icmp;
- icmp.type = ICMP_ECHO;
- icmp.code = 0;
- icmp.checksum = 0;
- icmp.un.echo.sequence = 2016;
- icmp.un.echo.id = 2018;
- icmp.checksum = ICMPChecksum(icmp, NULL, 0);
- ASSERT_NO_FATAL_FAILURE(SendEmptyICMP(icmp));
-
- // Both sockets will receive the echo request and reply in indeterminate
- // order, so we'll need to read 2 packets from each.
-
- // Receive on socket 1.
- constexpr int kBufSize = kEmptyICMPSize;
- char recv_buf1[2][kBufSize];
- struct sockaddr_in src;
- for (int i = 0; i < 2; i++) {
- ASSERT_NO_FATAL_FAILURE(ReceiveICMP(recv_buf1[i],
- ABSL_ARRAYSIZE(recv_buf1[i]),
- sizeof(struct icmphdr), &src));
- EXPECT_EQ(memcmp(&src, &addr_, sizeof(src)), 0);
- }
-
- // Receive on socket 2.
- char recv_buf2[2][kBufSize];
- for (int i = 0; i < 2; i++) {
- ASSERT_NO_FATAL_FAILURE(
- ReceiveICMPFrom(recv_buf2[i], ABSL_ARRAYSIZE(recv_buf2[i]),
- sizeof(struct icmphdr), &src, s2.get()));
- EXPECT_EQ(memcmp(&src, &addr_, sizeof(src)), 0);
- }
-
- // Ensure both sockets receive identical packets.
- int types[] = {ICMP_ECHO, ICMP_ECHOREPLY};
- for (int type : types) {
- auto match_type = [=](char buf[kBufSize]) {
- struct icmphdr* icmp =
- reinterpret_cast<struct icmphdr*>(buf + sizeof(struct iphdr));
- return icmp->type == type;
- };
- auto icmp1_it =
- std::find_if(std::begin(recv_buf1), std::end(recv_buf1), match_type);
- auto icmp2_it =
- std::find_if(std::begin(recv_buf2), std::end(recv_buf2), match_type);
- ASSERT_NE(icmp1_it, std::end(recv_buf1));
- ASSERT_NE(icmp2_it, std::end(recv_buf2));
- EXPECT_EQ(memcmp(*icmp1_it + sizeof(struct iphdr),
- *icmp2_it + sizeof(struct iphdr), sizeof(icmp)),
- 0);
- }
-}
-
-// A raw ICMP socket and ping socket should both receive the ICMP packets
-// intended for the ping socket.
-TEST_F(RawSocketICMPTest, RawAndPingSockets) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- FileDescriptor ping_sock =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP));
-
- // Ping sockets take care of the ICMP ID and checksum.
- struct icmphdr icmp;
- icmp.type = ICMP_ECHO;
- icmp.code = 0;
- icmp.un.echo.sequence = *static_cast<unsigned short*>(&icmp.un.echo.sequence);
- ASSERT_THAT(RetryEINTR(sendto)(ping_sock.get(), &icmp, sizeof(icmp), 0,
- reinterpret_cast<struct sockaddr*>(&addr_),
- sizeof(addr_)),
- SyscallSucceedsWithValue(sizeof(icmp)));
-
- // Receive on socket 1, which receives the echo request and reply in
- // indeterminate order.
- constexpr int kBufSize = kEmptyICMPSize;
- char recv_buf1[2][kBufSize];
- struct sockaddr_in src;
- for (int i = 0; i < 2; i++) {
- ASSERT_NO_FATAL_FAILURE(
- ReceiveICMP(recv_buf1[i], kBufSize, sizeof(struct icmphdr), &src));
- EXPECT_EQ(memcmp(&src, &addr_, sizeof(src)), 0);
- }
-
- // Receive on socket 2. Ping sockets only get the echo reply, not the initial
- // echo.
- char ping_recv_buf[kBufSize];
- ASSERT_THAT(RetryEINTR(recv)(ping_sock.get(), ping_recv_buf, kBufSize, 0),
- SyscallSucceedsWithValue(sizeof(struct icmphdr)));
-
- // Ensure both sockets receive identical echo reply packets.
- auto match_type_raw = [=](char buf[kBufSize]) {
- struct icmphdr* icmp =
- reinterpret_cast<struct icmphdr*>(buf + sizeof(struct iphdr));
- return icmp->type == ICMP_ECHOREPLY;
- };
- auto raw_reply_it =
- std::find_if(std::begin(recv_buf1), std::end(recv_buf1), match_type_raw);
- ASSERT_NE(raw_reply_it, std::end(recv_buf1));
- EXPECT_EQ(
- memcmp(*raw_reply_it + sizeof(struct iphdr), ping_recv_buf, sizeof(icmp)),
- 0);
-}
-
-// A raw ICMP socket should be able to send a malformed short ICMP Echo Request,
-// while ping socket should not.
-// Neither should be able to receieve a short malformed packet.
-TEST_F(RawSocketICMPTest, ShortEchoRawAndPingSockets) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- FileDescriptor ping_sock =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP));
-
- struct icmphdr icmp;
- icmp.type = ICMP_ECHO;
- icmp.code = 0;
- icmp.un.echo.sequence = 0;
- icmp.un.echo.id = 6789;
- icmp.checksum = 0;
- icmp.checksum = ICMPChecksum(icmp, NULL, 0);
-
- // Omit 2 bytes from ICMP packet.
- constexpr int kShortICMPSize = sizeof(icmp) - 2;
-
- // Sending a malformed short ICMP message to a ping socket should fail.
- ASSERT_THAT(RetryEINTR(sendto)(ping_sock.get(), &icmp, kShortICMPSize, 0,
- reinterpret_cast<struct sockaddr*>(&addr_),
- sizeof(addr_)),
- SyscallFailsWithErrno(EINVAL));
-
- // Sending a malformed short ICMP message to a raw socket should not fail.
- ASSERT_THAT(RetryEINTR(sendto)(s_, &icmp, kShortICMPSize, 0,
- reinterpret_cast<struct sockaddr*>(&addr_),
- sizeof(addr_)),
- SyscallSucceedsWithValue(kShortICMPSize));
-
- // Neither Ping nor Raw socket should have anything to read.
- char recv_buf[kEmptyICMPSize];
- EXPECT_THAT(RetryEINTR(recv)(ping_sock.get(), recv_buf, sizeof(recv_buf),
- MSG_DONTWAIT),
- SyscallFailsWithErrno(EAGAIN));
- EXPECT_THAT(RetryEINTR(recv)(s_, recv_buf, sizeof(recv_buf), MSG_DONTWAIT),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-// A raw ICMP socket should be able to send a malformed short ICMP Echo Reply,
-// while ping socket should not.
-// Neither should be able to receieve a short malformed packet.
-TEST_F(RawSocketICMPTest, ShortEchoReplyRawAndPingSockets) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- FileDescriptor ping_sock =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP));
-
- struct icmphdr icmp;
- icmp.type = ICMP_ECHOREPLY;
- icmp.code = 0;
- icmp.un.echo.sequence = 0;
- icmp.un.echo.id = 6789;
- icmp.checksum = 0;
- icmp.checksum = ICMPChecksum(icmp, NULL, 0);
-
- // Omit 2 bytes from ICMP packet.
- constexpr int kShortICMPSize = sizeof(icmp) - 2;
-
- // Sending a malformed short ICMP message to a ping socket should fail.
- ASSERT_THAT(RetryEINTR(sendto)(ping_sock.get(), &icmp, kShortICMPSize, 0,
- reinterpret_cast<struct sockaddr*>(&addr_),
- sizeof(addr_)),
- SyscallFailsWithErrno(EINVAL));
-
- // Sending a malformed short ICMP message to a raw socket should not fail.
- ASSERT_THAT(RetryEINTR(sendto)(s_, &icmp, kShortICMPSize, 0,
- reinterpret_cast<struct sockaddr*>(&addr_),
- sizeof(addr_)),
- SyscallSucceedsWithValue(kShortICMPSize));
-
- // Neither Ping nor Raw socket should have anything to read.
- char recv_buf[kEmptyICMPSize];
- EXPECT_THAT(RetryEINTR(recv)(ping_sock.get(), recv_buf, sizeof(recv_buf),
- MSG_DONTWAIT),
- SyscallFailsWithErrno(EAGAIN));
- EXPECT_THAT(RetryEINTR(recv)(s_, recv_buf, sizeof(recv_buf), MSG_DONTWAIT),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-// Test that connect() sends packets to the right place.
-TEST_F(RawSocketICMPTest, SendAndReceiveViaConnect) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(
- connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)),
- SyscallSucceeds());
-
- // Prepare and send an ICMP packet. Use arbitrary junk for sequence and ID.
- // None of that should matter for raw sockets - the kernel should still give
- // us the packet.
- struct icmphdr icmp;
- icmp.type = ICMP_ECHO;
- icmp.code = 0;
- icmp.checksum = 0;
- icmp.un.echo.sequence = 2003;
- icmp.un.echo.id = 2004;
- icmp.checksum = ICMPChecksum(icmp, NULL, 0);
- ASSERT_THAT(send(s_, &icmp, sizeof(icmp), 0),
- SyscallSucceedsWithValue(sizeof(icmp)));
-
- ASSERT_NO_FATAL_FAILURE(ExpectICMPSuccess(icmp));
-}
-
-// Bind to localhost, then send and receive packets.
-TEST_F(RawSocketICMPTest, BindSendAndReceive) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(
- bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)),
- SyscallSucceeds());
-
- // Prepare and send an ICMP packet. Use arbitrary junk for checksum, sequence,
- // and ID. None of that should matter for raw sockets - the kernel should
- // still give us the packet.
- struct icmphdr icmp;
- icmp.type = ICMP_ECHO;
- icmp.code = 0;
- icmp.checksum = 0;
- icmp.un.echo.sequence = 2004;
- icmp.un.echo.id = 2007;
- icmp.checksum = ICMPChecksum(icmp, NULL, 0);
- ASSERT_NO_FATAL_FAILURE(SendEmptyICMP(icmp));
-
- ASSERT_NO_FATAL_FAILURE(ExpectICMPSuccess(icmp));
-}
-
-// Bind and connect to localhost and send/receive packets.
-TEST_F(RawSocketICMPTest, BindConnectSendAndReceive) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(
- bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)),
- SyscallSucceeds());
- ASSERT_THAT(
- connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)),
- SyscallSucceeds());
-
- // Prepare and send an ICMP packet. Use arbitrary junk for checksum, sequence,
- // and ID. None of that should matter for raw sockets - the kernel should
- // still give us the packet.
- struct icmphdr icmp;
- icmp.type = ICMP_ECHO;
- icmp.code = 0;
- icmp.checksum = 0;
- icmp.un.echo.sequence = 2010;
- icmp.un.echo.id = 7;
- icmp.checksum = ICMPChecksum(icmp, NULL, 0);
- ASSERT_NO_FATAL_FAILURE(SendEmptyICMP(icmp));
-
- ASSERT_NO_FATAL_FAILURE(ExpectICMPSuccess(icmp));
-}
-
-void RawSocketICMPTest::ExpectICMPSuccess(const struct icmphdr& icmp) {
- // We're going to receive both the echo request and reply, but the order is
- // indeterminate.
- char recv_buf[kEmptyICMPSize];
- struct sockaddr_in src;
- bool received_request = false;
- bool received_reply = false;
-
- for (int i = 0; i < 2; i++) {
- // Receive the packet.
- ASSERT_NO_FATAL_FAILURE(ReceiveICMP(recv_buf, ABSL_ARRAYSIZE(recv_buf),
- sizeof(struct icmphdr), &src));
- EXPECT_EQ(memcmp(&src, &addr_, sizeof(src)), 0);
- struct icmphdr* recvd_icmp =
- reinterpret_cast<struct icmphdr*>(recv_buf + sizeof(struct iphdr));
- switch (recvd_icmp->type) {
- case ICMP_ECHO:
- EXPECT_FALSE(received_request);
- received_request = true;
- // The packet should be identical to what we sent.
- EXPECT_EQ(memcmp(recv_buf + sizeof(struct iphdr), &icmp, sizeof(icmp)),
- 0);
- break;
-
- case ICMP_ECHOREPLY:
- EXPECT_FALSE(received_reply);
- received_reply = true;
- // Most fields should be the same.
- EXPECT_EQ(recvd_icmp->code, icmp.code);
- EXPECT_EQ(recvd_icmp->un.echo.sequence, icmp.un.echo.sequence);
- EXPECT_EQ(recvd_icmp->un.echo.id, icmp.un.echo.id);
- // A couple are different.
- EXPECT_EQ(recvd_icmp->type, ICMP_ECHOREPLY);
- // The checksum computed over the reply should still be valid.
- EXPECT_EQ(ICMPChecksum(*recvd_icmp, NULL, 0), 0);
- break;
- }
- }
-
- ASSERT_TRUE(received_request);
- ASSERT_TRUE(received_reply);
-}
-
-void RawSocketICMPTest::SendEmptyICMP(const struct icmphdr& icmp) {
- ASSERT_NO_FATAL_FAILURE(SendEmptyICMPTo(s_, addr_, icmp));
-}
-
-void RawSocketICMPTest::SendEmptyICMPTo(int sock,
- const struct sockaddr_in& addr,
- const struct icmphdr& icmp) {
- // It's safe to use const_cast here because sendmsg won't modify the iovec or
- // address.
- struct iovec iov = {};
- iov.iov_base = static_cast<void*>(const_cast<struct icmphdr*>(&icmp));
- iov.iov_len = sizeof(icmp);
- struct msghdr msg = {};
- msg.msg_name = static_cast<void*>(const_cast<struct sockaddr_in*>(&addr));
- msg.msg_namelen = sizeof(addr);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
- msg.msg_control = NULL;
- msg.msg_controllen = 0;
- msg.msg_flags = 0;
- ASSERT_THAT(sendmsg(sock, &msg, 0), SyscallSucceedsWithValue(sizeof(icmp)));
-}
-
-void RawSocketICMPTest::ReceiveICMP(char* recv_buf, size_t recv_buf_len,
- size_t expected_size,
- struct sockaddr_in* src) {
- ASSERT_NO_FATAL_FAILURE(
- ReceiveICMPFrom(recv_buf, recv_buf_len, expected_size, src, s_));
-}
-
-void RawSocketICMPTest::ReceiveICMPFrom(char* recv_buf, size_t recv_buf_len,
- size_t expected_size,
- struct sockaddr_in* src, int sock) {
- struct iovec iov = {};
- iov.iov_base = recv_buf;
- iov.iov_len = recv_buf_len;
- struct msghdr msg = {};
- msg.msg_name = src;
- msg.msg_namelen = sizeof(*src);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
- msg.msg_control = NULL;
- msg.msg_controllen = 0;
- msg.msg_flags = 0;
- // We should receive the ICMP packet plus 20 bytes of IP header.
- ASSERT_THAT(recvmsg(sock, &msg, 0),
- SyscallSucceedsWithValue(expected_size + sizeof(struct iphdr)));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/raw_socket_ipv4.cc b/test/syscalls/linux/raw_socket_ipv4.cc
deleted file mode 100644
index 352037c88..000000000
--- a/test/syscalls/linux/raw_socket_ipv4.cc
+++ /dev/null
@@ -1,387 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <linux/capability.h>
-#include <netinet/in.h>
-#include <netinet/ip.h>
-#include <netinet/ip_icmp.h>
-#include <poll.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <algorithm>
-
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/capability_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/test_util.h"
-
-// Note: in order to run these tests, /proc/sys/net/ipv4/ping_group_range will
-// need to be configured to let the superuser create ping sockets (see icmp(7)).
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// Fixture for tests parameterized by protocol.
-class RawSocketTest : public ::testing::TestWithParam<int> {
- protected:
- // Creates a socket to be used in tests.
- void SetUp() override;
-
- // Closes the socket created by SetUp().
- void TearDown() override;
-
- // Sends buf via s_.
- void SendBuf(const char* buf, int buf_len);
-
- // Sends buf to the provided address via the provided socket.
- void SendBufTo(int sock, const struct sockaddr_in& addr, const char* buf,
- int buf_len);
-
- // Reads from s_ into recv_buf.
- void ReceiveBuf(char* recv_buf, size_t recv_buf_len);
-
- int Protocol() { return GetParam(); }
-
- // The socket used for both reading and writing.
- int s_;
-
- // The loopback address.
- struct sockaddr_in addr_;
-};
-
-void RawSocketTest::SetUp() {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(s_ = socket(AF_INET, SOCK_RAW, Protocol()), SyscallSucceeds());
-
- addr_ = {};
-
- // We don't set ports because raw sockets don't have a notion of ports.
- addr_.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- addr_.sin_family = AF_INET;
-}
-
-void RawSocketTest::TearDown() {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- EXPECT_THAT(close(s_), SyscallSucceeds());
-}
-
-// We should be able to create multiple raw sockets for the same protocol.
-// BasicRawSocket::Setup creates the first one, so we only have to create one
-// more here.
-TEST_P(RawSocketTest, MultipleCreation) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- int s2;
- ASSERT_THAT(s2 = socket(AF_INET, SOCK_RAW, Protocol()), SyscallSucceeds());
-
- ASSERT_THAT(close(s2), SyscallSucceeds());
-}
-
-// Test that shutting down an unconnected socket fails.
-TEST_P(RawSocketTest, FailShutdownWithoutConnect) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(shutdown(s_, SHUT_WR), SyscallFailsWithErrno(ENOTCONN));
- ASSERT_THAT(shutdown(s_, SHUT_RD), SyscallFailsWithErrno(ENOTCONN));
-}
-
-// Shutdown is a no-op for raw sockets (and datagram sockets in general).
-TEST_P(RawSocketTest, ShutdownWriteNoop) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(
- connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)),
- SyscallSucceeds());
- ASSERT_THAT(shutdown(s_, SHUT_WR), SyscallSucceeds());
-
- // Arbitrary.
- constexpr char kBuf[] = "noop";
- ASSERT_THAT(RetryEINTR(write)(s_, kBuf, sizeof(kBuf)),
- SyscallSucceedsWithValue(sizeof(kBuf)));
-}
-
-// Shutdown is a no-op for raw sockets (and datagram sockets in general).
-TEST_P(RawSocketTest, ShutdownReadNoop) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(
- connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)),
- SyscallSucceeds());
- ASSERT_THAT(shutdown(s_, SHUT_RD), SyscallSucceeds());
-
- // Arbitrary.
- constexpr char kBuf[] = "gdg";
- ASSERT_NO_FATAL_FAILURE(SendBuf(kBuf, sizeof(kBuf)));
-
- constexpr size_t kReadSize = sizeof(kBuf) + sizeof(struct iphdr);
- char c[kReadSize];
- ASSERT_THAT(read(s_, &c, sizeof(c)), SyscallSucceedsWithValue(kReadSize));
-}
-
-// Test that listen() fails.
-TEST_P(RawSocketTest, FailListen) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(listen(s_, 1), SyscallFailsWithErrno(ENOTSUP));
-}
-
-// Test that accept() fails.
-TEST_P(RawSocketTest, FailAccept) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- struct sockaddr saddr;
- socklen_t addrlen;
- ASSERT_THAT(accept(s_, &saddr, &addrlen), SyscallFailsWithErrno(ENOTSUP));
-}
-
-// Test that getpeername() returns nothing before connect().
-TEST_P(RawSocketTest, FailGetPeerNameBeforeConnect) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- struct sockaddr saddr;
- socklen_t addrlen = sizeof(saddr);
- ASSERT_THAT(getpeername(s_, &saddr, &addrlen),
- SyscallFailsWithErrno(ENOTCONN));
-}
-
-// Test that getpeername() returns something after connect().
-TEST_P(RawSocketTest, GetPeerName) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(
- connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)),
- SyscallSucceeds());
- struct sockaddr saddr;
- socklen_t addrlen = sizeof(saddr);
- ASSERT_THAT(getpeername(s_, &saddr, &addrlen),
- SyscallFailsWithErrno(ENOTCONN));
- ASSERT_GT(addrlen, 0);
-}
-
-// Test that the socket is writable immediately.
-TEST_P(RawSocketTest, PollWritableImmediately) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- struct pollfd pfd = {};
- pfd.fd = s_;
- pfd.events = POLLOUT;
- ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 10000), SyscallSucceedsWithValue(1));
-}
-
-// Test that the socket isn't readable before receiving anything.
-TEST_P(RawSocketTest, PollNotReadableInitially) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- // Try to receive data with MSG_DONTWAIT, which returns immediately if there's
- // nothing to be read.
- char buf[117];
- ASSERT_THAT(RetryEINTR(recv)(s_, buf, sizeof(buf), MSG_DONTWAIT),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-// Test that the socket becomes readable once something is written to it.
-TEST_P(RawSocketTest, PollTriggeredOnWrite) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- // Write something so that there's data to be read.
- // Arbitrary.
- constexpr char kBuf[] = "JP5";
- ASSERT_NO_FATAL_FAILURE(SendBuf(kBuf, sizeof(kBuf)));
-
- struct pollfd pfd = {};
- pfd.fd = s_;
- pfd.events = POLLIN;
- ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 10000), SyscallSucceedsWithValue(1));
-}
-
-// Test that we can connect() to a valid IP (loopback).
-TEST_P(RawSocketTest, ConnectToLoopback) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(
- connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)),
- SyscallSucceeds());
-}
-
-// Test that calling send() without connect() fails.
-TEST_P(RawSocketTest, SendWithoutConnectFails) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- // Arbitrary.
- constexpr char kBuf[] = "Endgame was good";
- ASSERT_THAT(send(s_, kBuf, sizeof(kBuf), 0),
- SyscallFailsWithErrno(EDESTADDRREQ));
-}
-
-// Bind to localhost.
-TEST_P(RawSocketTest, BindToLocalhost) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(
- bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)),
- SyscallSucceeds());
-}
-
-// Bind to a different address.
-TEST_P(RawSocketTest, BindToInvalid) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- struct sockaddr_in bind_addr = {};
- bind_addr.sin_family = AF_INET;
- bind_addr.sin_addr = {1}; // 1.0.0.0 - An address that we can't bind to.
- ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&bind_addr),
- sizeof(bind_addr)),
- SyscallFailsWithErrno(EADDRNOTAVAIL));
-}
-
-// Send and receive an packet.
-TEST_P(RawSocketTest, SendAndReceive) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- // Arbitrary.
- constexpr char kBuf[] = "TB12";
- ASSERT_NO_FATAL_FAILURE(SendBuf(kBuf, sizeof(kBuf)));
-
- // Receive the packet and make sure it's identical.
- char recv_buf[sizeof(kBuf) + sizeof(struct iphdr)];
- ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf, sizeof(recv_buf)));
- EXPECT_EQ(memcmp(recv_buf + sizeof(struct iphdr), kBuf, sizeof(kBuf)), 0);
-}
-
-// We should be able to create multiple raw sockets for the same protocol and
-// receive the same packet on both.
-TEST_P(RawSocketTest, MultipleSocketReceive) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- int s2;
- ASSERT_THAT(s2 = socket(AF_INET, SOCK_RAW, Protocol()), SyscallSucceeds());
-
- // Arbitrary.
- constexpr char kBuf[] = "TB10";
- ASSERT_NO_FATAL_FAILURE(SendBuf(kBuf, sizeof(kBuf)));
-
- // Receive it on socket 1.
- char recv_buf1[sizeof(kBuf) + sizeof(struct iphdr)];
- ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf1, sizeof(recv_buf1)));
-
- // Receive it on socket 2.
- char recv_buf2[sizeof(kBuf) + sizeof(struct iphdr)];
- ASSERT_NO_FATAL_FAILURE(RecvNoCmsg(s2, recv_buf2, sizeof(recv_buf2)));
-
- EXPECT_EQ(memcmp(recv_buf1 + sizeof(struct iphdr),
- recv_buf2 + sizeof(struct iphdr), sizeof(kBuf)),
- 0);
-
- ASSERT_THAT(close(s2), SyscallSucceeds());
-}
-
-// Test that connect sends packets to the right place.
-TEST_P(RawSocketTest, SendAndReceiveViaConnect) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(
- connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)),
- SyscallSucceeds());
-
- // Arbitrary.
- constexpr char kBuf[] = "JH4";
- ASSERT_THAT(send(s_, kBuf, sizeof(kBuf), 0),
- SyscallSucceedsWithValue(sizeof(kBuf)));
-
- // Receive the packet and make sure it's identical.
- char recv_buf[sizeof(kBuf) + sizeof(struct iphdr)];
- ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf, sizeof(recv_buf)));
- EXPECT_EQ(memcmp(recv_buf + sizeof(struct iphdr), kBuf, sizeof(kBuf)), 0);
-}
-
-// Bind to localhost, then send and receive packets.
-TEST_P(RawSocketTest, BindSendAndReceive) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(
- bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)),
- SyscallSucceeds());
-
- // Arbitrary.
- constexpr char kBuf[] = "DR16";
- ASSERT_NO_FATAL_FAILURE(SendBuf(kBuf, sizeof(kBuf)));
-
- // Receive the packet and make sure it's identical.
- char recv_buf[sizeof(kBuf) + sizeof(struct iphdr)];
- ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf, sizeof(recv_buf)));
- EXPECT_EQ(memcmp(recv_buf + sizeof(struct iphdr), kBuf, sizeof(kBuf)), 0);
-}
-
-// Bind and connect to localhost and send/receive packets.
-TEST_P(RawSocketTest, BindConnectSendAndReceive) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(
- bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)),
- SyscallSucceeds());
- ASSERT_THAT(
- connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)),
- SyscallSucceeds());
-
- // Arbitrary.
- constexpr char kBuf[] = "DG88";
- ASSERT_NO_FATAL_FAILURE(SendBuf(kBuf, sizeof(kBuf)));
-
- // Receive the packet and make sure it's identical.
- char recv_buf[sizeof(kBuf) + sizeof(struct iphdr)];
- ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf, sizeof(recv_buf)));
- EXPECT_EQ(memcmp(recv_buf + sizeof(struct iphdr), kBuf, sizeof(kBuf)), 0);
-}
-
-void RawSocketTest::SendBuf(const char* buf, int buf_len) {
- ASSERT_NO_FATAL_FAILURE(SendBufTo(s_, addr_, buf, buf_len));
-}
-
-void RawSocketTest::SendBufTo(int sock, const struct sockaddr_in& addr,
- const char* buf, int buf_len) {
- // It's safe to use const_cast here because sendmsg won't modify the iovec or
- // address.
- struct iovec iov = {};
- iov.iov_base = static_cast<void*>(const_cast<char*>(buf));
- iov.iov_len = static_cast<size_t>(buf_len);
- struct msghdr msg = {};
- msg.msg_name = static_cast<void*>(const_cast<struct sockaddr_in*>(&addr));
- msg.msg_namelen = sizeof(addr);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
- msg.msg_control = NULL;
- msg.msg_controllen = 0;
- msg.msg_flags = 0;
- ASSERT_THAT(sendmsg(sock, &msg, 0), SyscallSucceedsWithValue(buf_len));
-}
-
-void RawSocketTest::ReceiveBuf(char* recv_buf, size_t recv_buf_len) {
- ASSERT_NO_FATAL_FAILURE(RecvNoCmsg(s_, recv_buf, recv_buf_len));
-}
-
-INSTANTIATE_TEST_SUITE_P(AllInetTests, RawSocketTest,
- ::testing::Values(IPPROTO_TCP, IPPROTO_UDP));
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/read.cc b/test/syscalls/linux/read.cc
deleted file mode 100644
index 4430fa3c2..000000000
--- a/test/syscalls/linux/read.cc
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <unistd.h>
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-class ReadTest : public ::testing::Test {
- void SetUp() override {
- name_ = NewTempAbsPath();
- int fd;
- ASSERT_THAT(fd = open(name_.c_str(), O_CREAT, 0644), SyscallSucceeds());
- ASSERT_THAT(close(fd), SyscallSucceeds());
- }
-
- void TearDown() override { unlink(name_.c_str()); }
-
- public:
- std::string name_;
-};
-
-TEST_F(ReadTest, ZeroBuffer) {
- int fd;
- ASSERT_THAT(fd = open(name_.c_str(), O_RDWR), SyscallSucceeds());
-
- char msg[] = "hello world";
- EXPECT_THAT(PwriteFd(fd, msg, strlen(msg), 0),
- SyscallSucceedsWithValue(strlen(msg)));
-
- char buf[10];
- EXPECT_THAT(ReadFd(fd, buf, 0), SyscallSucceedsWithValue(0));
- EXPECT_THAT(close(fd), SyscallSucceeds());
-}
-
-TEST_F(ReadTest, EmptyFileReturnsZeroAtEOF) {
- int fd;
- ASSERT_THAT(fd = open(name_.c_str(), O_RDWR), SyscallSucceeds());
-
- char eof_buf[10];
- EXPECT_THAT(ReadFd(fd, eof_buf, 10), SyscallSucceedsWithValue(0));
- EXPECT_THAT(close(fd), SyscallSucceeds());
-}
-
-TEST_F(ReadTest, EofAfterRead) {
- int fd;
- ASSERT_THAT(fd = open(name_.c_str(), O_RDWR), SyscallSucceeds());
-
- // Write some bytes to be read.
- constexpr char kMessage[] = "hello world";
- EXPECT_THAT(PwriteFd(fd, kMessage, sizeof(kMessage), 0),
- SyscallSucceedsWithValue(sizeof(kMessage)));
-
- // Read all of the bytes at once.
- char buf[sizeof(kMessage)];
- EXPECT_THAT(ReadFd(fd, buf, sizeof(kMessage)),
- SyscallSucceedsWithValue(sizeof(kMessage)));
-
- // Read again with a non-zero buffer and expect EOF.
- char eof_buf[10];
- EXPECT_THAT(ReadFd(fd, eof_buf, 10), SyscallSucceedsWithValue(0));
- EXPECT_THAT(close(fd), SyscallSucceeds());
-}
-
-TEST_F(ReadTest, DevNullReturnsEof) {
- int fd;
- ASSERT_THAT(fd = open("/dev/null", O_RDONLY), SyscallSucceeds());
- std::vector<char> buf(1);
- EXPECT_THAT(ReadFd(fd, buf.data(), 1), SyscallSucceedsWithValue(0));
- EXPECT_THAT(close(fd), SyscallSucceeds());
-}
-
-const int kReadSize = 128 * 1024;
-
-// Do not allow random save as it could lead to partial reads.
-TEST_F(ReadTest, CanReadFullyFromDevZero_NoRandomSave) {
- int fd;
- ASSERT_THAT(fd = open("/dev/zero", O_RDONLY), SyscallSucceeds());
-
- std::vector<char> buf(kReadSize, 1);
- EXPECT_THAT(ReadFd(fd, buf.data(), kReadSize),
- SyscallSucceedsWithValue(kReadSize));
- EXPECT_THAT(close(fd), SyscallSucceeds());
- EXPECT_EQ(std::vector<char>(kReadSize, 0), buf);
-}
-
-TEST_F(ReadTest, ReadDirectoryFails) {
- const FileDescriptor file =
- ASSERT_NO_ERRNO_AND_VALUE(Open(GetAbsoluteTestTmpdir(), O_RDONLY));
- std::vector<char> buf(1);
- EXPECT_THAT(ReadFd(file.get(), buf.data(), 1), SyscallFailsWithErrno(EISDIR));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/readv.cc b/test/syscalls/linux/readv.cc
deleted file mode 100644
index f327ec3a9..000000000
--- a/test/syscalls/linux/readv.cc
+++ /dev/null
@@ -1,293 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/file_base.h"
-#include "test/syscalls/linux/readv_common.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-#include "test/util/timer_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-class ReadvTest : public FileTest {
- void SetUp() override {
- FileTest::SetUp();
-
- ASSERT_THAT(write(test_file_fd_.get(), kReadvTestData, kReadvTestDataSize),
- SyscallSucceedsWithValue(kReadvTestDataSize));
- ASSERT_THAT(lseek(test_file_fd_.get(), 0, SEEK_SET),
- SyscallSucceedsWithValue(0));
- ASSERT_THAT(write(test_pipe_[1], kReadvTestData, kReadvTestDataSize),
- SyscallSucceedsWithValue(kReadvTestDataSize));
- }
-};
-
-TEST_F(ReadvTest, ReadOneBufferPerByte_File) {
- ReadOneBufferPerByte(test_file_fd_.get());
-}
-
-TEST_F(ReadvTest, ReadOneBufferPerByte_Pipe) {
- ReadOneBufferPerByte(test_pipe_[0]);
-}
-
-TEST_F(ReadvTest, ReadOneHalfAtATime_File) {
- ReadOneHalfAtATime(test_file_fd_.get());
-}
-
-TEST_F(ReadvTest, ReadOneHalfAtATime_Pipe) {
- ReadOneHalfAtATime(test_pipe_[0]);
-}
-
-TEST_F(ReadvTest, ReadAllOneBuffer_File) {
- ReadAllOneBuffer(test_file_fd_.get());
-}
-
-TEST_F(ReadvTest, ReadAllOneBuffer_Pipe) { ReadAllOneBuffer(test_pipe_[0]); }
-
-TEST_F(ReadvTest, ReadAllOneLargeBuffer_File) {
- ReadAllOneLargeBuffer(test_file_fd_.get());
-}
-
-TEST_F(ReadvTest, ReadAllOneLargeBuffer_Pipe) {
- ReadAllOneLargeBuffer(test_pipe_[0]);
-}
-
-TEST_F(ReadvTest, ReadBuffersOverlapping_File) {
- ReadBuffersOverlapping(test_file_fd_.get());
-}
-
-TEST_F(ReadvTest, ReadBuffersOverlapping_Pipe) {
- ReadBuffersOverlapping(test_pipe_[0]);
-}
-
-TEST_F(ReadvTest, ReadBuffersDiscontinuous_File) {
- ReadBuffersDiscontinuous(test_file_fd_.get());
-}
-
-TEST_F(ReadvTest, ReadBuffersDiscontinuous_Pipe) {
- ReadBuffersDiscontinuous(test_pipe_[0]);
-}
-
-TEST_F(ReadvTest, ReadIovecsCompletelyFilled_File) {
- ReadIovecsCompletelyFilled(test_file_fd_.get());
-}
-
-TEST_F(ReadvTest, ReadIovecsCompletelyFilled_Pipe) {
- ReadIovecsCompletelyFilled(test_pipe_[0]);
-}
-
-TEST_F(ReadvTest, BadFileDescriptor) {
- char buffer[1024];
- struct iovec iov[1];
- iov[0].iov_base = buffer;
- iov[0].iov_len = 1024;
-
- ASSERT_THAT(readv(-1, iov, 1024), SyscallFailsWithErrno(EBADF));
-}
-
-TEST_F(ReadvTest, BadIovecsPointer_File) {
- ASSERT_THAT(readv(test_file_fd_.get(), nullptr, 1),
- SyscallFailsWithErrno(EFAULT));
-}
-
-TEST_F(ReadvTest, BadIovecsPointer_Pipe) {
- ASSERT_THAT(readv(test_pipe_[0], nullptr, 1), SyscallFailsWithErrno(EFAULT));
-}
-
-TEST_F(ReadvTest, BadIovecBase_File) {
- struct iovec iov[1];
- iov[0].iov_base = nullptr;
- iov[0].iov_len = 1024;
- ASSERT_THAT(readv(test_file_fd_.get(), iov, 1),
- SyscallFailsWithErrno(EFAULT));
-}
-
-TEST_F(ReadvTest, BadIovecBase_Pipe) {
- struct iovec iov[1];
- iov[0].iov_base = nullptr;
- iov[0].iov_len = 1024;
- ASSERT_THAT(readv(test_pipe_[0], iov, 1), SyscallFailsWithErrno(EFAULT));
-}
-
-TEST_F(ReadvTest, ZeroIovecs_File) {
- struct iovec iov[1];
- iov[0].iov_base = 0;
- iov[0].iov_len = 0;
- ASSERT_THAT(readv(test_file_fd_.get(), iov, 1), SyscallSucceeds());
-}
-
-TEST_F(ReadvTest, ZeroIovecs_Pipe) {
- struct iovec iov[1];
- iov[0].iov_base = 0;
- iov[0].iov_len = 0;
- ASSERT_THAT(readv(test_pipe_[0], iov, 1), SyscallSucceeds());
-}
-
-TEST_F(ReadvTest, NotReadable_File) {
- char buffer[1024];
- struct iovec iov[1];
- iov[0].iov_base = buffer;
- iov[0].iov_len = 1024;
-
- std::string wronly_file = NewTempAbsPath();
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(
- Open(wronly_file, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR));
- ASSERT_THAT(readv(fd.get(), iov, 1), SyscallFailsWithErrno(EBADF));
- fd.reset(); // Close before unlinking.
- ASSERT_THAT(unlink(wronly_file.c_str()), SyscallSucceeds());
-}
-
-TEST_F(ReadvTest, NotReadable_Pipe) {
- char buffer[1024];
- struct iovec iov[1];
- iov[0].iov_base = buffer;
- iov[0].iov_len = 1024;
- ASSERT_THAT(readv(test_pipe_[1], iov, 1), SyscallFailsWithErrno(EBADF));
-}
-
-TEST_F(ReadvTest, DirNotReadable) {
- char buffer[1024];
- struct iovec iov[1];
- iov[0].iov_base = buffer;
- iov[0].iov_len = 1024;
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(GetAbsoluteTestTmpdir(), O_RDONLY));
- ASSERT_THAT(readv(fd.get(), iov, 1), SyscallFailsWithErrno(EISDIR));
-}
-
-TEST_F(ReadvTest, OffsetIncremented) {
- char* buffer = reinterpret_cast<char*>(malloc(kReadvTestDataSize));
- struct iovec iov[1];
- iov[0].iov_base = buffer;
- iov[0].iov_len = kReadvTestDataSize;
-
- ASSERT_THAT(readv(test_file_fd_.get(), iov, 1),
- SyscallSucceedsWithValue(kReadvTestDataSize));
- ASSERT_THAT(lseek(test_file_fd_.get(), 0, SEEK_CUR),
- SyscallSucceedsWithValue(kReadvTestDataSize));
-
- free(buffer);
-}
-
-TEST_F(ReadvTest, EndOfFile) {
- char* buffer = reinterpret_cast<char*>(malloc(kReadvTestDataSize));
- struct iovec iov[1];
- iov[0].iov_base = buffer;
- iov[0].iov_len = kReadvTestDataSize;
- ASSERT_THAT(readv(test_file_fd_.get(), iov, 1),
- SyscallSucceedsWithValue(kReadvTestDataSize));
- free(buffer);
-
- buffer = reinterpret_cast<char*>(malloc(kReadvTestDataSize));
- iov[0].iov_base = buffer;
- iov[0].iov_len = kReadvTestDataSize;
- ASSERT_THAT(readv(test_file_fd_.get(), iov, 1), SyscallSucceedsWithValue(0));
- free(buffer);
-}
-
-TEST_F(ReadvTest, WouldBlock_Pipe) {
- struct iovec iov[1];
- iov[0].iov_base = reinterpret_cast<char*>(malloc(kReadvTestDataSize));
- iov[0].iov_len = kReadvTestDataSize;
- ASSERT_THAT(readv(test_pipe_[0], iov, 1),
- SyscallSucceedsWithValue(kReadvTestDataSize));
- free(iov[0].iov_base);
-
- iov[0].iov_base = reinterpret_cast<char*>(malloc(kReadvTestDataSize));
- ASSERT_THAT(readv(test_pipe_[0], iov, 1), SyscallFailsWithErrno(EAGAIN));
- free(iov[0].iov_base);
-}
-
-TEST_F(ReadvTest, ZeroBuffer) {
- char buf[10];
- struct iovec iov[1];
- iov[0].iov_base = buf;
- iov[0].iov_len = 0;
- ASSERT_THAT(readv(test_pipe_[0], iov, 1), SyscallSucceedsWithValue(0));
-}
-
-TEST_F(ReadvTest, NullIovecInNonemptyArray) {
- std::vector<char> buf(kReadvTestDataSize);
- struct iovec iov[2];
- iov[0].iov_base = nullptr;
- iov[0].iov_len = 0;
- iov[1].iov_base = buf.data();
- iov[1].iov_len = kReadvTestDataSize;
- ASSERT_THAT(readv(test_file_fd_.get(), iov, 2),
- SyscallSucceedsWithValue(kReadvTestDataSize));
-}
-
-TEST_F(ReadvTest, IovecOutsideTaskAddressRangeInNonemptyArray) {
- std::vector<char> buf(kReadvTestDataSize);
- struct iovec iov[2];
- iov[0].iov_base = reinterpret_cast<void*>(~static_cast<uintptr_t>(0));
- iov[0].iov_len = 0;
- iov[1].iov_base = buf.data();
- iov[1].iov_len = kReadvTestDataSize;
- ASSERT_THAT(readv(test_file_fd_.get(), iov, 2),
- SyscallFailsWithErrno(EFAULT));
-}
-
-// This test depends on the maximum extent of a single readv() syscall, so
-// we can't tolerate interruption from saving.
-TEST(ReadvTestNoFixture, TruncatedAtMax_NoRandomSave) {
- // Ensure that we won't be interrupted by ITIMER_PROF.
- struct itimerval itv = {};
- auto const cleanup_itimer =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedItimer(ITIMER_PROF, itv));
-
- // From Linux's include/linux/fs.h.
- size_t const MAX_RW_COUNT = INT_MAX & ~(kPageSize - 1);
-
- // Create an iovec array with 3 segments pointing to consecutive parts of a
- // buffer. The first covers all but the last three pages, and should be
- // written to in its entirety. The second covers the last page before
- // MAX_RW_COUNT and the first page after; only the first page should be
- // written to. The third covers the last page of the buffer, and should be
- // skipped entirely.
- size_t const kBufferSize = MAX_RW_COUNT + 2 * kPageSize;
- size_t const kFirstOffset = MAX_RW_COUNT - kPageSize;
- size_t const kSecondOffset = MAX_RW_COUNT + kPageSize;
- // The buffer is too big to fit on the stack.
- std::vector<char> buf(kBufferSize);
- struct iovec iov[3];
- iov[0].iov_base = buf.data();
- iov[0].iov_len = kFirstOffset;
- iov[1].iov_base = buf.data() + kFirstOffset;
- iov[1].iov_len = kSecondOffset - kFirstOffset;
- iov[2].iov_base = buf.data() + kSecondOffset;
- iov[2].iov_len = kBufferSize - kSecondOffset;
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDONLY));
- EXPECT_THAT(readv(fd.get(), iov, 3), SyscallSucceedsWithValue(MAX_RW_COUNT));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/readv_common.cc b/test/syscalls/linux/readv_common.cc
deleted file mode 100644
index 35d2dd9e3..000000000
--- a/test/syscalls/linux/readv_common.cc
+++ /dev/null
@@ -1,180 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/file_base.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-extern const char kReadvTestData[] =
- "127.0.0.1 localhost"
- ""
- "# The following lines are desirable for IPv6 capable hosts"
- "::1 ip6-localhost ip6-loopback"
- "fe00::0 ip6-localnet"
- "ff00::0 ip6-mcastprefix"
- "ff02::1 ip6-allnodes"
- "ff02::2 ip6-allrouters"
- "ff02::3 ip6-allhosts"
- "192.168.1.100 a"
- "93.184.216.34 foo.bar.example.com xcpu";
-extern const size_t kReadvTestDataSize = sizeof(kReadvTestData);
-
-static void ReadAllOneProvidedBuffer(int fd, std::vector<char>* buffer) {
- struct iovec iovs[1];
- iovs[0].iov_base = buffer->data();
- iovs[0].iov_len = kReadvTestDataSize;
-
- ASSERT_THAT(readv(fd, iovs, 1), SyscallSucceedsWithValue(kReadvTestDataSize));
-
- std::pair<struct iovec*, int> iovec_desc(iovs, 1);
- EXPECT_THAT(iovec_desc, MatchesStringLength(kReadvTestDataSize));
- EXPECT_THAT(iovec_desc, MatchesStringValue(kReadvTestData));
-}
-
-void ReadAllOneBuffer(int fd) {
- std::vector<char> buffer(kReadvTestDataSize);
- ReadAllOneProvidedBuffer(fd, &buffer);
-}
-
-void ReadAllOneLargeBuffer(int fd) {
- std::vector<char> buffer(10 * kReadvTestDataSize);
- ReadAllOneProvidedBuffer(fd, &buffer);
-}
-
-void ReadOneHalfAtATime(int fd) {
- int len0 = kReadvTestDataSize / 2;
- int len1 = kReadvTestDataSize - len0;
- std::vector<char> buffer0(len0);
- std::vector<char> buffer1(len1);
-
- struct iovec iovs[2];
- iovs[0].iov_base = buffer0.data();
- iovs[0].iov_len = len0;
- iovs[1].iov_base = buffer1.data();
- iovs[1].iov_len = len1;
-
- ASSERT_THAT(readv(fd, iovs, 2), SyscallSucceedsWithValue(kReadvTestDataSize));
-
- std::pair<struct iovec*, int> iovec_desc(iovs, 2);
- EXPECT_THAT(iovec_desc, MatchesStringLength(kReadvTestDataSize));
- EXPECT_THAT(iovec_desc, MatchesStringValue(kReadvTestData));
-}
-
-void ReadOneBufferPerByte(int fd) {
- std::vector<char> buffer(kReadvTestDataSize);
- std::vector<struct iovec> iovs(kReadvTestDataSize);
- char* buffer_ptr = buffer.data();
- struct iovec* iovs_ptr = iovs.data();
-
- for (int i = 0; i < static_cast<int>(kReadvTestDataSize); i++) {
- struct iovec iov = {
- .iov_base = &buffer_ptr[i],
- .iov_len = 1,
- };
- iovs_ptr[i] = iov;
- }
-
- ASSERT_THAT(readv(fd, iovs_ptr, kReadvTestDataSize),
- SyscallSucceedsWithValue(kReadvTestDataSize));
-
- std::pair<struct iovec*, int> iovec_desc(iovs.data(), kReadvTestDataSize);
- EXPECT_THAT(iovec_desc, MatchesStringLength(kReadvTestDataSize));
- EXPECT_THAT(iovec_desc, MatchesStringValue(kReadvTestData));
-}
-
-void ReadBuffersOverlapping(int fd) {
- // overlap the first overlap_bytes.
- int overlap_bytes = 8;
- std::vector<char> buffer(kReadvTestDataSize);
-
- // overlapping causes us to get more data.
- int expected_size = kReadvTestDataSize + overlap_bytes;
- std::vector<char> expected(expected_size);
- char* expected_ptr = expected.data();
- memcpy(expected_ptr, &kReadvTestData[overlap_bytes], overlap_bytes);
- memcpy(&expected_ptr[overlap_bytes], &kReadvTestData[overlap_bytes],
- kReadvTestDataSize);
-
- struct iovec iovs[2];
- iovs[0].iov_base = buffer.data();
- iovs[0].iov_len = overlap_bytes;
- iovs[1].iov_base = buffer.data();
- iovs[1].iov_len = kReadvTestDataSize;
-
- ASSERT_THAT(readv(fd, iovs, 2), SyscallSucceedsWithValue(kReadvTestDataSize));
-
- std::pair<struct iovec*, int> iovec_desc(iovs, 2);
- EXPECT_THAT(iovec_desc, MatchesStringLength(expected_size));
- EXPECT_THAT(iovec_desc, MatchesStringValue(expected_ptr));
-}
-
-void ReadBuffersDiscontinuous(int fd) {
- // Each iov is 1 byte separated by 1 byte.
- std::vector<char> buffer(kReadvTestDataSize * 2);
- std::vector<struct iovec> iovs(kReadvTestDataSize);
-
- char* buffer_ptr = buffer.data();
- struct iovec* iovs_ptr = iovs.data();
-
- for (int i = 0; i < static_cast<int>(kReadvTestDataSize); i++) {
- struct iovec iov = {
- .iov_base = &buffer_ptr[i * 2],
- .iov_len = 1,
- };
- iovs_ptr[i] = iov;
- }
-
- ASSERT_THAT(readv(fd, iovs_ptr, kReadvTestDataSize),
- SyscallSucceedsWithValue(kReadvTestDataSize));
-
- std::pair<struct iovec*, int> iovec_desc(iovs.data(), kReadvTestDataSize);
- EXPECT_THAT(iovec_desc, MatchesStringLength(kReadvTestDataSize));
- EXPECT_THAT(iovec_desc, MatchesStringValue(kReadvTestData));
-}
-
-void ReadIovecsCompletelyFilled(int fd) {
- int half = kReadvTestDataSize / 2;
- std::vector<char> buffer(kReadvTestDataSize);
- char* buffer_ptr = buffer.data();
- memset(buffer.data(), '\0', kReadvTestDataSize);
-
- struct iovec iovs[2];
- iovs[0].iov_base = buffer.data();
- iovs[0].iov_len = half;
- iovs[1].iov_base = &buffer_ptr[half];
- iovs[1].iov_len = half;
-
- ASSERT_THAT(readv(fd, iovs, 2), SyscallSucceedsWithValue(half * 2));
-
- std::pair<struct iovec*, int> iovec_desc(iovs, 2);
- EXPECT_THAT(iovec_desc, MatchesStringLength(half * 2));
- EXPECT_THAT(iovec_desc, MatchesStringValue(kReadvTestData));
-
- char* str = static_cast<char*>(iovs[0].iov_base);
- str[iovs[0].iov_len - 1] = '\0';
- ASSERT_EQ(half - 1, strlen(str));
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/readv_common.h b/test/syscalls/linux/readv_common.h
deleted file mode 100644
index 2fa40c35f..000000000
--- a/test/syscalls/linux/readv_common.h
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_READV_COMMON_H_
-#define GVISOR_TEST_SYSCALLS_READV_COMMON_H_
-
-#include <stddef.h>
-
-namespace gvisor {
-namespace testing {
-
-// A NUL-terminated string containing the data used by tests using the following
-// test helpers.
-extern const char kReadvTestData[];
-
-// The size of kReadvTestData, including the terminating NUL.
-extern const size_t kReadvTestDataSize;
-
-// ReadAllOneBuffer asserts that it can read kReadvTestData from an fd using
-// exactly one iovec.
-void ReadAllOneBuffer(int fd);
-
-// ReadAllOneLargeBuffer asserts that it can read kReadvTestData from an fd
-// using exactly one iovec containing an overly large buffer.
-void ReadAllOneLargeBuffer(int fd);
-
-// ReadOneHalfAtATime asserts that it can read test_data_from an fd using
-// exactly two iovecs that are roughly equivalent in size.
-void ReadOneHalfAtATime(int fd);
-
-// ReadOneBufferPerByte asserts that it can read kReadvTestData from an fd
-// using one iovec per byte.
-void ReadOneBufferPerByte(int fd);
-
-// ReadBuffersOverlapping asserts that it can read kReadvTestData from an fd
-// where two iovecs are overlapping.
-void ReadBuffersOverlapping(int fd);
-
-// ReadBuffersDiscontinuous asserts that it can read kReadvTestData from an fd
-// where each iovec is discontinuous from the next by 1 byte.
-void ReadBuffersDiscontinuous(int fd);
-
-// ReadIovecsCompletelyFilled asserts that the previous iovec is completely
-// filled before moving onto the next.
-void ReadIovecsCompletelyFilled(int fd);
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_READV_COMMON_H_
diff --git a/test/syscalls/linux/readv_socket.cc b/test/syscalls/linux/readv_socket.cc
deleted file mode 100644
index 3c315cc02..000000000
--- a/test/syscalls/linux/readv_socket.cc
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/file_base.h"
-#include "test/syscalls/linux/readv_common.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-class ReadvSocketTest : public SocketTest {
- void SetUp() override {
- SocketTest::SetUp();
- ASSERT_THAT(
- write(test_unix_stream_socket_[1], kReadvTestData, kReadvTestDataSize),
- SyscallSucceedsWithValue(kReadvTestDataSize));
- ASSERT_THAT(
- write(test_unix_dgram_socket_[1], kReadvTestData, kReadvTestDataSize),
- SyscallSucceedsWithValue(kReadvTestDataSize));
- ASSERT_THAT(write(test_unix_seqpacket_socket_[1], kReadvTestData,
- kReadvTestDataSize),
- SyscallSucceedsWithValue(kReadvTestDataSize));
- // FIXME(b/69821513): Enable when possible.
- // ASSERT_THAT(write(test_tcp_socket_[1], kReadvTestData,
- // kReadvTestDataSize),
- // SyscallSucceedsWithValue(kReadvTestDataSize));
- }
-};
-
-TEST_F(ReadvSocketTest, ReadOneBufferPerByte_StreamSocket) {
- ReadOneBufferPerByte(test_unix_stream_socket_[0]);
-}
-
-TEST_F(ReadvSocketTest, ReadOneBufferPerByte_DgramSocket) {
- ReadOneBufferPerByte(test_unix_dgram_socket_[0]);
-}
-
-TEST_F(ReadvSocketTest, ReadOneBufferPerByte_SeqPacketSocket) {
- ReadOneBufferPerByte(test_unix_seqpacket_socket_[0]);
-}
-
-TEST_F(ReadvSocketTest, ReadOneHalfAtATime_StreamSocket) {
- ReadOneHalfAtATime(test_unix_stream_socket_[0]);
-}
-
-TEST_F(ReadvSocketTest, ReadOneHalfAtATime_DgramSocket) {
- ReadOneHalfAtATime(test_unix_dgram_socket_[0]);
-}
-
-TEST_F(ReadvSocketTest, ReadAllOneBuffer_StreamSocket) {
- ReadAllOneBuffer(test_unix_stream_socket_[0]);
-}
-
-TEST_F(ReadvSocketTest, ReadAllOneBuffer_DgramSocket) {
- ReadAllOneBuffer(test_unix_dgram_socket_[0]);
-}
-
-TEST_F(ReadvSocketTest, ReadAllOneLargeBuffer_StreamSocket) {
- ReadAllOneLargeBuffer(test_unix_stream_socket_[0]);
-}
-
-TEST_F(ReadvSocketTest, ReadAllOneLargeBuffer_DgramSocket) {
- ReadAllOneLargeBuffer(test_unix_dgram_socket_[0]);
-}
-
-TEST_F(ReadvSocketTest, ReadBuffersOverlapping_StreamSocket) {
- ReadBuffersOverlapping(test_unix_stream_socket_[0]);
-}
-
-TEST_F(ReadvSocketTest, ReadBuffersOverlapping_DgramSocket) {
- ReadBuffersOverlapping(test_unix_dgram_socket_[0]);
-}
-
-TEST_F(ReadvSocketTest, ReadBuffersDiscontinuous_StreamSocket) {
- ReadBuffersDiscontinuous(test_unix_stream_socket_[0]);
-}
-
-TEST_F(ReadvSocketTest, ReadBuffersDiscontinuous_DgramSocket) {
- ReadBuffersDiscontinuous(test_unix_dgram_socket_[0]);
-}
-
-TEST_F(ReadvSocketTest, ReadIovecsCompletelyFilled_StreamSocket) {
- ReadIovecsCompletelyFilled(test_unix_stream_socket_[0]);
-}
-
-TEST_F(ReadvSocketTest, ReadIovecsCompletelyFilled_DgramSocket) {
- ReadIovecsCompletelyFilled(test_unix_dgram_socket_[0]);
-}
-
-TEST_F(ReadvSocketTest, BadIovecsPointer_StreamSocket) {
- ASSERT_THAT(readv(test_unix_stream_socket_[0], nullptr, 1),
- SyscallFailsWithErrno(EFAULT));
-}
-
-TEST_F(ReadvSocketTest, BadIovecsPointer_DgramSocket) {
- ASSERT_THAT(readv(test_unix_dgram_socket_[0], nullptr, 1),
- SyscallFailsWithErrno(EFAULT));
-}
-
-TEST_F(ReadvSocketTest, BadIovecBase_StreamSocket) {
- struct iovec iov[1];
- iov[0].iov_base = nullptr;
- iov[0].iov_len = 1024;
- ASSERT_THAT(readv(test_unix_stream_socket_[0], iov, 1),
- SyscallFailsWithErrno(EFAULT));
-}
-
-TEST_F(ReadvSocketTest, BadIovecBase_DgramSocket) {
- struct iovec iov[1];
- iov[0].iov_base = nullptr;
- iov[0].iov_len = 1024;
- ASSERT_THAT(readv(test_unix_dgram_socket_[0], iov, 1),
- SyscallFailsWithErrno(EFAULT));
-}
-
-TEST_F(ReadvSocketTest, ZeroIovecs_StreamSocket) {
- struct iovec iov[1];
- iov[0].iov_base = 0;
- iov[0].iov_len = 0;
- ASSERT_THAT(readv(test_unix_stream_socket_[0], iov, 1), SyscallSucceeds());
-}
-
-TEST_F(ReadvSocketTest, ZeroIovecs_DgramSocket) {
- struct iovec iov[1];
- iov[0].iov_base = 0;
- iov[0].iov_len = 0;
- ASSERT_THAT(readv(test_unix_dgram_socket_[0], iov, 1), SyscallSucceeds());
-}
-
-TEST_F(ReadvSocketTest, WouldBlock_StreamSocket) {
- struct iovec iov[1];
- iov[0].iov_base = reinterpret_cast<char*>(malloc(kReadvTestDataSize));
- iov[0].iov_len = kReadvTestDataSize;
- ASSERT_THAT(readv(test_unix_stream_socket_[0], iov, 1),
- SyscallSucceedsWithValue(kReadvTestDataSize));
- free(iov[0].iov_base);
-
- iov[0].iov_base = reinterpret_cast<char*>(malloc(kReadvTestDataSize));
- ASSERT_THAT(readv(test_unix_stream_socket_[0], iov, 1),
- SyscallFailsWithErrno(EAGAIN));
- free(iov[0].iov_base);
-}
-
-TEST_F(ReadvSocketTest, WouldBlock_DgramSocket) {
- struct iovec iov[1];
- iov[0].iov_base = reinterpret_cast<char*>(malloc(kReadvTestDataSize));
- iov[0].iov_len = kReadvTestDataSize;
- ASSERT_THAT(readv(test_unix_dgram_socket_[0], iov, 1),
- SyscallSucceedsWithValue(kReadvTestDataSize));
- free(iov[0].iov_base);
-
- iov[0].iov_base = reinterpret_cast<char*>(malloc(kReadvTestDataSize));
- ASSERT_THAT(readv(test_unix_dgram_socket_[0], iov, 1),
- SyscallFailsWithErrno(EAGAIN));
- free(iov[0].iov_base);
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/rename.cc b/test/syscalls/linux/rename.cc
deleted file mode 100644
index c9d76c2e2..000000000
--- a/test/syscalls/linux/rename.cc
+++ /dev/null
@@ -1,394 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <stdio.h>
-#include <string>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "absl/strings/string_view.h"
-#include "test/util/capability_util.h"
-#include "test/util/cleanup.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(RenameTest, RootToAnything) {
- ASSERT_THAT(rename("/", "/bin"), SyscallFailsWithErrno(EBUSY));
-}
-
-TEST(RenameTest, AnythingToRoot) {
- ASSERT_THAT(rename("/bin", "/"), SyscallFailsWithErrno(EBUSY));
-}
-
-TEST(RenameTest, SourceIsAncestorOfTarget) {
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto subdir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path()));
- ASSERT_THAT(rename(dir.path().c_str(), subdir.path().c_str()),
- SyscallFailsWithErrno(EINVAL));
-
- // Try an even deeper directory.
- auto deep_subdir =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(subdir.path()));
- ASSERT_THAT(rename(dir.path().c_str(), deep_subdir.path().c_str()),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(RenameTest, TargetIsAncestorOfSource) {
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto subdir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path()));
- ASSERT_THAT(rename(subdir.path().c_str(), dir.path().c_str()),
- SyscallFailsWithErrno(ENOTEMPTY));
-
- // Try an even deeper directory.
- auto deep_subdir =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(subdir.path()));
- ASSERT_THAT(rename(deep_subdir.path().c_str(), dir.path().c_str()),
- SyscallFailsWithErrno(ENOTEMPTY));
-}
-
-TEST(RenameTest, FileToSelf) {
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- EXPECT_THAT(rename(f.path().c_str(), f.path().c_str()), SyscallSucceeds());
-}
-
-TEST(RenameTest, DirectoryToSelf) {
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- EXPECT_THAT(rename(f.path().c_str(), f.path().c_str()), SyscallSucceeds());
-}
-
-TEST(RenameTest, FileToSameDirectory) {
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- std::string const newpath = NewTempAbsPath();
- ASSERT_THAT(rename(f.path().c_str(), newpath.c_str()), SyscallSucceeds());
- std::string const oldpath = f.release();
- f.reset(newpath);
- EXPECT_THAT(Exists(oldpath), IsPosixErrorOkAndHolds(false));
- EXPECT_THAT(Exists(newpath), IsPosixErrorOkAndHolds(true));
-}
-
-TEST(RenameTest, DirectoryToSameDirectory) {
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- std::string const newpath = NewTempAbsPath();
- ASSERT_THAT(rename(dir.path().c_str(), newpath.c_str()), SyscallSucceeds());
- std::string const oldpath = dir.release();
- dir.reset(newpath);
- EXPECT_THAT(Exists(oldpath), IsPosixErrorOkAndHolds(false));
- EXPECT_THAT(Exists(newpath), IsPosixErrorOkAndHolds(true));
-}
-
-TEST(RenameTest, FileToParentDirectory) {
- auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path()));
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir2.path()));
- std::string const newpath = NewTempAbsPathInDir(dir1.path());
- ASSERT_THAT(rename(f.path().c_str(), newpath.c_str()), SyscallSucceeds());
- std::string const oldpath = f.release();
- f.reset(newpath);
- EXPECT_THAT(Exists(oldpath), IsPosixErrorOkAndHolds(false));
- EXPECT_THAT(Exists(newpath), IsPosixErrorOkAndHolds(true));
-}
-
-TEST(RenameTest, DirectoryToParentDirectory) {
- auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path()));
- auto dir3 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir2.path()));
- EXPECT_THAT(IsDirectory(dir3.path()), IsPosixErrorOkAndHolds(true));
- std::string const newpath = NewTempAbsPathInDir(dir1.path());
- ASSERT_THAT(rename(dir3.path().c_str(), newpath.c_str()), SyscallSucceeds());
- std::string const oldpath = dir3.release();
- dir3.reset(newpath);
- EXPECT_THAT(Exists(oldpath), IsPosixErrorOkAndHolds(false));
- EXPECT_THAT(Exists(newpath), IsPosixErrorOkAndHolds(true));
- EXPECT_THAT(IsDirectory(newpath), IsPosixErrorOkAndHolds(true));
-}
-
-TEST(RenameTest, FileToChildDirectory) {
- auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path()));
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path()));
- std::string const newpath = NewTempAbsPathInDir(dir2.path());
- ASSERT_THAT(rename(f.path().c_str(), newpath.c_str()), SyscallSucceeds());
- std::string const oldpath = f.release();
- f.reset(newpath);
- EXPECT_THAT(Exists(oldpath), IsPosixErrorOkAndHolds(false));
- EXPECT_THAT(Exists(newpath), IsPosixErrorOkAndHolds(true));
-}
-
-TEST(RenameTest, DirectoryToChildDirectory) {
- auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path()));
- auto dir3 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path()));
- std::string const newpath = NewTempAbsPathInDir(dir2.path());
- ASSERT_THAT(rename(dir3.path().c_str(), newpath.c_str()), SyscallSucceeds());
- std::string const oldpath = dir3.release();
- dir3.reset(newpath);
- EXPECT_THAT(Exists(oldpath), IsPosixErrorOkAndHolds(false));
- EXPECT_THAT(Exists(newpath), IsPosixErrorOkAndHolds(true));
- EXPECT_THAT(IsDirectory(newpath), IsPosixErrorOkAndHolds(true));
-}
-
-TEST(RenameTest, DirectoryToOwnChildDirectory) {
- auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path()));
- std::string const newpath = NewTempAbsPathInDir(dir2.path());
- ASSERT_THAT(rename(dir1.path().c_str(), newpath.c_str()),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(RenameTest, FileOverwritesFile) {
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto f1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- dir.path(), "first", TempPath::kDefaultFileMode));
- auto f2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- dir.path(), "second", TempPath::kDefaultFileMode));
- ASSERT_THAT(rename(f1.path().c_str(), f2.path().c_str()), SyscallSucceeds());
- EXPECT_THAT(Exists(f1.path()), IsPosixErrorOkAndHolds(false));
-
- f1.release();
- std::string f2_contents;
- ASSERT_NO_ERRNO(GetContents(f2.path(), &f2_contents));
- EXPECT_EQ("first", f2_contents);
-}
-
-TEST(RenameTest, DirectoryOverwritesDirectoryLinkCount) {
- auto parent1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- EXPECT_THAT(Links(parent1.path()), IsPosixErrorOkAndHolds(2));
-
- auto parent2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- EXPECT_THAT(Links(parent2.path()), IsPosixErrorOkAndHolds(2));
-
- auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent1.path()));
- auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent2.path()));
-
- EXPECT_THAT(Links(parent1.path()), IsPosixErrorOkAndHolds(3));
- EXPECT_THAT(Links(parent2.path()), IsPosixErrorOkAndHolds(3));
-
- ASSERT_THAT(rename(dir1.path().c_str(), dir2.path().c_str()),
- SyscallSucceeds());
-
- EXPECT_THAT(Links(parent1.path()), IsPosixErrorOkAndHolds(2));
- EXPECT_THAT(Links(parent2.path()), IsPosixErrorOkAndHolds(3));
-}
-
-TEST(RenameTest, FileDoesNotExist) {
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const std::string source = JoinPath(dir.path(), "source");
- const std::string dest = JoinPath(dir.path(), "dest");
- ASSERT_THAT(rename(source.c_str(), dest.c_str()),
- SyscallFailsWithErrno(ENOENT));
-}
-
-TEST(RenameTest, FileDoesNotOverwriteDirectory) {
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- ASSERT_THAT(rename(f.path().c_str(), dir.path().c_str()),
- SyscallFailsWithErrno(EISDIR));
-}
-
-TEST(RenameTest, DirectoryDoesNotOverwriteFile) {
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- ASSERT_THAT(rename(dir.path().c_str(), f.path().c_str()),
- SyscallFailsWithErrno(ENOTDIR));
-}
-
-TEST(RenameTest, DirectoryOverwritesEmptyDirectory) {
- auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path()));
- auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- EXPECT_THAT(rename(dir1.path().c_str(), dir2.path().c_str()),
- SyscallSucceeds());
- EXPECT_THAT(Exists(dir1.path()), IsPosixErrorOkAndHolds(false));
- dir1.release();
- EXPECT_THAT(Exists(JoinPath(dir2.path(), Basename(f.path()))),
- IsPosixErrorOkAndHolds(true));
- f.release();
-}
-
-TEST(RenameTest, FailsWithDots) {
- auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto dir1_dot = absl::StrCat(dir1.path(), "/.");
- auto dir2_dot = absl::StrCat(dir2.path(), "/.");
- auto dir1_dot_dot = absl::StrCat(dir1.path(), "/..");
- auto dir2_dot_dot = absl::StrCat(dir2.path(), "/..");
-
- // Try with dot paths in the first argument
- EXPECT_THAT(rename(dir1_dot.c_str(), dir2.path().c_str()),
- SyscallFailsWithErrno(EBUSY));
- EXPECT_THAT(rename(dir1_dot_dot.c_str(), dir2.path().c_str()),
- SyscallFailsWithErrno(EBUSY));
-
- // Try with dot paths in the second argument
- EXPECT_THAT(rename(dir1.path().c_str(), dir2_dot.c_str()),
- SyscallFailsWithErrno(EBUSY));
- EXPECT_THAT(rename(dir1.path().c_str(), dir2_dot_dot.c_str()),
- SyscallFailsWithErrno(EBUSY));
-}
-
-TEST(RenameTest, DirectoryDoesNotOverwriteNonemptyDirectory) {
- auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto f1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path()));
- auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto f2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir2.path()));
- ASSERT_THAT(rename(dir1.path().c_str(), dir2.path().c_str()),
- SyscallFailsWithErrno(ENOTEMPTY));
-}
-
-TEST(RenameTest, FailsWhenOldParentNotWritable) {
- // Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
-
- auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto f1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path()));
- auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- // dir1 is not writable.
- ASSERT_THAT(chmod(dir1.path().c_str(), 0555), SyscallSucceeds());
-
- std::string const newpath = NewTempAbsPathInDir(dir2.path());
- EXPECT_THAT(rename(f1.path().c_str(), newpath.c_str()),
- SyscallFailsWithErrno(EACCES));
-}
-
-TEST(RenameTest, FailsWhenNewParentNotWritable) {
- // Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
-
- auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto f1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path()));
- // dir2 is not writable.
- auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0555));
-
- std::string const newpath = NewTempAbsPathInDir(dir2.path());
- EXPECT_THAT(rename(f1.path().c_str(), newpath.c_str()),
- SyscallFailsWithErrno(EACCES));
-}
-
-// Equivalent to FailsWhenNewParentNotWritable, but with a destination file
-// to overwrite.
-TEST(RenameTest, OverwriteFailsWhenNewParentNotWritable) {
- // Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
-
- auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto f1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path()));
-
- // dir2 is not writable.
- auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto f2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir2.path()));
- ASSERT_THAT(chmod(dir2.path().c_str(), 0555), SyscallSucceeds());
-
- EXPECT_THAT(rename(f1.path().c_str(), f2.path().c_str()),
- SyscallFailsWithErrno(EACCES));
-}
-
-// If the parent directory of source is not accessible, rename returns EACCES
-// because the user cannot determine if source exists.
-TEST(RenameTest, FileDoesNotExistWhenNewParentNotExecutable) {
- // Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
-
- // No execute permission.
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0400));
-
- const std::string source = JoinPath(dir.path(), "source");
- const std::string dest = JoinPath(dir.path(), "dest");
- ASSERT_THAT(rename(source.c_str(), dest.c_str()),
- SyscallFailsWithErrno(EACCES));
-}
-
-TEST(RenameTest, DirectoryWithOpenFdOverwritesEmptyDirectory) {
- auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path()));
- auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- // Get an fd on dir1
- int fd;
- ASSERT_THAT(fd = open(dir1.path().c_str(), O_DIRECTORY), SyscallSucceeds());
- auto close_f = Cleanup([fd] {
- // Close the fd on f.
- EXPECT_THAT(close(fd), SyscallSucceeds());
- });
-
- EXPECT_THAT(rename(dir1.path().c_str(), dir2.path().c_str()),
- SyscallSucceeds());
-
- const std::string new_f_path = JoinPath(dir2.path(), Basename(f.path()));
-
- auto remove_f = Cleanup([&] {
- // Delete f in its new location.
- ASSERT_NO_ERRNO(Delete(new_f_path));
- f.release();
- });
-
- EXPECT_THAT(Exists(dir1.path()), IsPosixErrorOkAndHolds(false));
- dir1.release();
- EXPECT_THAT(Exists(new_f_path), IsPosixErrorOkAndHolds(true));
-}
-
-TEST(RenameTest, FileWithOpenFd) {
- TempPath root_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- TempPath dir1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root_dir.path()));
- TempPath dir2 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root_dir.path()));
- TempPath dir3 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root_dir.path()));
-
- // Create file in dir1.
- constexpr char kContents[] = "foo";
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- dir1.path(), kContents, TempPath::kDefaultFileMode));
-
- // Get fd on file.
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDWR));
-
- // Move f to dir2.
- const std::string path2 = NewTempAbsPathInDir(dir2.path());
- ASSERT_THAT(rename(f.path().c_str(), path2.c_str()), SyscallSucceeds());
-
- // Read f's kContents.
- char buf[sizeof(kContents)];
- EXPECT_THAT(PreadFd(fd.get(), &buf, sizeof(kContents), 0),
- SyscallSucceedsWithValue(sizeof(kContents) - 1));
- EXPECT_EQ(absl::string_view(buf, sizeof(buf) - 1), kContents);
-
- // Move f to dir3.
- const std::string path3 = NewTempAbsPathInDir(dir3.path());
- ASSERT_THAT(rename(path2.c_str(), path3.c_str()), SyscallSucceeds());
-
- // Read f's kContents.
- EXPECT_THAT(PreadFd(fd.get(), &buf, sizeof(kContents), 0),
- SyscallSucceedsWithValue(sizeof(kContents) - 1));
- EXPECT_EQ(absl::string_view(buf, sizeof(buf) - 1), kContents);
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/rlimits.cc b/test/syscalls/linux/rlimits.cc
deleted file mode 100644
index 860f0f688..000000000
--- a/test/syscalls/linux/rlimits.cc
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sys/resource.h>
-#include <sys/time.h>
-
-#include "test/util/capability_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(RlimitTest, SetRlimitHigher) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_RESOURCE)));
-
- struct rlimit rl = {};
- EXPECT_THAT(getrlimit(RLIMIT_NOFILE, &rl), SyscallSucceeds());
-
- // Lower the rlimit first, as it may be equal to /proc/sys/fs/nr_open, in
- // which case even users with CAP_SYS_RESOURCE can't raise it.
- rl.rlim_cur--;
- rl.rlim_max--;
- ASSERT_THAT(setrlimit(RLIMIT_NOFILE, &rl), SyscallSucceeds());
-
- rl.rlim_max++;
- EXPECT_THAT(setrlimit(RLIMIT_NOFILE, &rl), SyscallSucceeds());
-}
-
-TEST(RlimitTest, UnprivilegedSetRlimit) {
- // Drop privileges if necessary.
- if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_RESOURCE))) {
- EXPECT_NO_ERRNO(SetCapability(CAP_SYS_RESOURCE, false));
- }
-
- struct rlimit rl = {};
- rl.rlim_cur = 1000;
- rl.rlim_max = 20000;
- EXPECT_THAT(setrlimit(RLIMIT_NOFILE, &rl), SyscallSucceeds());
-
- struct rlimit rl2 = {};
- EXPECT_THAT(getrlimit(RLIMIT_NOFILE, &rl2), SyscallSucceeds());
- EXPECT_EQ(rl.rlim_cur, rl2.rlim_cur);
- EXPECT_EQ(rl.rlim_max, rl2.rlim_max);
-
- rl.rlim_max = 100000;
- EXPECT_THAT(setrlimit(RLIMIT_NOFILE, &rl), SyscallFailsWithErrno(EPERM));
-}
-
-TEST(RlimitTest, SetSoftRlimitAboveHard) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_RESOURCE)));
-
- struct rlimit rl = {};
- EXPECT_THAT(getrlimit(RLIMIT_NOFILE, &rl), SyscallSucceeds());
-
- rl.rlim_cur = rl.rlim_max + 1;
- EXPECT_THAT(setrlimit(RLIMIT_NOFILE, &rl), SyscallFailsWithErrno(EINVAL));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/rtsignal.cc b/test/syscalls/linux/rtsignal.cc
deleted file mode 100644
index 81d193ffd..000000000
--- a/test/syscalls/linux/rtsignal.cc
+++ /dev/null
@@ -1,172 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sys/syscall.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <cerrno>
-#include <csignal>
-
-#include "gtest/gtest.h"
-#include "test/util/cleanup.h"
-#include "test/util/logging.h"
-#include "test/util/posix_error.h"
-#include "test/util/signal_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// saved_info is set by the handler.
-siginfo_t saved_info;
-
-// has_saved_info is set to true by the handler.
-volatile bool has_saved_info;
-
-void SigHandler(int sig, siginfo_t* info, void* context) {
- // Copy to the given info.
- saved_info = *info;
- has_saved_info = true;
-}
-
-void ClearSavedInfo() {
- // Clear the cached info.
- memset(&saved_info, 0, sizeof(saved_info));
- has_saved_info = false;
-}
-
-PosixErrorOr<Cleanup> SetupSignalHandler(int sig) {
- struct sigaction sa;
- sa.sa_sigaction = SigHandler;
- sigfillset(&sa.sa_mask);
- sa.sa_flags = SA_SIGINFO;
- return ScopedSigaction(sig, sa);
-}
-
-class RtSignalTest : public ::testing::Test {
- protected:
- void SetUp() override {
- action_cleanup_ = ASSERT_NO_ERRNO_AND_VALUE(SetupSignalHandler(SIGUSR1));
- mask_cleanup_ =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGUSR1));
- }
-
- void TearDown() override { ClearSavedInfo(); }
-
- private:
- Cleanup action_cleanup_;
- Cleanup mask_cleanup_;
-};
-
-static int rt_sigqueueinfo(pid_t tgid, int sig, siginfo_t* uinfo) {
- int ret;
- do {
- // NOTE(b/25434735): rt_sigqueueinfo(2) could return EAGAIN for RT signals.
- ret = syscall(SYS_rt_sigqueueinfo, tgid, sig, uinfo);
- } while (ret == -1 && errno == EAGAIN);
- return ret;
-}
-
-TEST_F(RtSignalTest, InvalidTID) {
- siginfo_t uinfo;
- // Depending on the kernel version, these calls may fail with
- // ESRCH (goobunutu machines) or EPERM (production machines). Thus,
- // the test simply ensures that they do fail.
- EXPECT_THAT(rt_sigqueueinfo(-1, SIGUSR1, &uinfo), SyscallFails());
- EXPECT_FALSE(has_saved_info);
- EXPECT_THAT(rt_sigqueueinfo(0, SIGUSR1, &uinfo), SyscallFails());
- EXPECT_FALSE(has_saved_info);
-}
-
-TEST_F(RtSignalTest, InvalidCodes) {
- siginfo_t uinfo;
-
- // We need a child for the code checks to apply. If the process is delivering
- // to itself, then it can use whatever codes it wants and they will go
- // through.
- pid_t child = fork();
- if (child == 0) {
- _exit(1);
- }
- ASSERT_THAT(child, SyscallSucceeds());
-
- // These are not allowed for child processes.
- uinfo.si_code = 0; // SI_USER.
- EXPECT_THAT(rt_sigqueueinfo(child, SIGUSR1, &uinfo),
- SyscallFailsWithErrno(EPERM));
- uinfo.si_code = 0x80; // SI_KERNEL.
- EXPECT_THAT(rt_sigqueueinfo(child, SIGUSR1, &uinfo),
- SyscallFailsWithErrno(EPERM));
- uinfo.si_code = -6; // SI_TKILL.
- EXPECT_THAT(rt_sigqueueinfo(child, SIGUSR1, &uinfo),
- SyscallFailsWithErrno(EPERM));
- uinfo.si_code = -1; // SI_QUEUE (allowed).
- EXPECT_THAT(rt_sigqueueinfo(child, SIGUSR1, &uinfo), SyscallSucceeds());
-
- // Join the child process.
- EXPECT_THAT(waitpid(child, nullptr, 0), SyscallSucceeds());
-}
-
-TEST_F(RtSignalTest, ValueDelivered) {
- siginfo_t uinfo;
- uinfo.si_code = -1; // SI_QUEUE (allowed).
- uinfo.si_errno = 0x1234;
-
- EXPECT_EQ(saved_info.si_errno, 0x0);
- EXPECT_THAT(rt_sigqueueinfo(getpid(), SIGUSR1, &uinfo), SyscallSucceeds());
- EXPECT_TRUE(has_saved_info);
- EXPECT_EQ(saved_info.si_errno, 0x1234);
-}
-
-TEST_F(RtSignalTest, SignoMatch) {
- auto action2_cleanup = ASSERT_NO_ERRNO_AND_VALUE(SetupSignalHandler(SIGUSR2));
- auto mask2_cleanup =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGUSR2));
-
- siginfo_t uinfo;
- uinfo.si_code = -1; // SI_QUEUE (allowed).
-
- EXPECT_THAT(rt_sigqueueinfo(getpid(), SIGUSR1, &uinfo), SyscallSucceeds());
- EXPECT_TRUE(has_saved_info);
- EXPECT_EQ(saved_info.si_signo, SIGUSR1);
-
- ClearSavedInfo();
-
- EXPECT_THAT(rt_sigqueueinfo(getpid(), SIGUSR2, &uinfo), SyscallSucceeds());
- EXPECT_TRUE(has_saved_info);
- EXPECT_EQ(saved_info.si_signo, SIGUSR2);
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
-
-int main(int argc, char** argv) {
- // These tests depend on delivering SIGUSR1/2 to the main thread (so they can
- // synchronously check has_saved_info). Block these so that any other threads
- // created by TestInit will also have them blocked.
- sigset_t set;
- sigemptyset(&set);
- sigaddset(&set, SIGUSR1);
- sigaddset(&set, SIGUSR2);
- TEST_PCHECK(sigprocmask(SIG_BLOCK, &set, nullptr) == 0);
-
- gvisor::testing::TestInit(&argc, &argv);
-
- return RUN_ALL_TESTS();
-}
diff --git a/test/syscalls/linux/sched.cc b/test/syscalls/linux/sched.cc
deleted file mode 100644
index 735e99411..000000000
--- a/test/syscalls/linux/sched.cc
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <sched.h>
-
-#include "gtest/gtest.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// In linux, pid is limited to 29 bits because how futex is implemented.
-constexpr int kImpossiblePID = (1 << 29) + 1;
-
-TEST(SchedGetparamTest, ReturnsZero) {
- struct sched_param param;
- EXPECT_THAT(sched_getparam(getpid(), &param), SyscallSucceeds());
- EXPECT_EQ(param.sched_priority, 0);
- EXPECT_THAT(sched_getparam(/*pid=*/0, &param), SyscallSucceeds());
- EXPECT_EQ(param.sched_priority, 0);
-}
-
-TEST(SchedGetparamTest, InvalidPIDReturnsEINVAL) {
- struct sched_param param;
- EXPECT_THAT(sched_getparam(/*pid=*/-1, &param),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(SchedGetparamTest, ImpossiblePIDReturnsESRCH) {
- struct sched_param param;
- EXPECT_THAT(sched_getparam(kImpossiblePID, &param),
- SyscallFailsWithErrno(ESRCH));
-}
-
-TEST(SchedGetparamTest, NullParamReturnsEINVAL) {
- EXPECT_THAT(sched_getparam(0, nullptr), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(SchedGetschedulerTest, ReturnsSchedOther) {
- EXPECT_THAT(sched_getscheduler(getpid()),
- SyscallSucceedsWithValue(SCHED_OTHER));
- EXPECT_THAT(sched_getscheduler(/*pid=*/0),
- SyscallSucceedsWithValue(SCHED_OTHER));
-}
-
-TEST(SchedGetschedulerTest, ReturnsEINVAL) {
- EXPECT_THAT(sched_getscheduler(/*pid=*/-1), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(SchedGetschedulerTest, ReturnsESRCH) {
- EXPECT_THAT(sched_getscheduler(kImpossiblePID), SyscallFailsWithErrno(ESRCH));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/sched_yield.cc b/test/syscalls/linux/sched_yield.cc
deleted file mode 100644
index 5d24f5b58..000000000
--- a/test/syscalls/linux/sched_yield.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sched.h>
-
-#include "gtest/gtest.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(SchedYieldTest, Success) {
- EXPECT_THAT(sched_yield(), SyscallSucceeds());
- EXPECT_THAT(sched_yield(), SyscallSucceeds());
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/seccomp.cc b/test/syscalls/linux/seccomp.cc
deleted file mode 100644
index e77586852..000000000
--- a/test/syscalls/linux/seccomp.cc
+++ /dev/null
@@ -1,406 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <linux/audit.h>
-#include <linux/filter.h>
-#include <linux/seccomp.h>
-#include <pthread.h>
-#include <sched.h>
-#include <signal.h>
-#include <string.h>
-#include <sys/prctl.h>
-#include <sys/syscall.h>
-#include <time.h>
-#include <ucontext.h>
-#include <unistd.h>
-#include <atomic>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/base/macros.h"
-#include "test/util/logging.h"
-#include "test/util/memory_util.h"
-#include "test/util/multiprocess_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/proc_util.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-#ifndef SYS_SECCOMP
-#define SYS_SECCOMP 1
-#endif
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// A syscall not implemented by Linux that we don't expect to be called.
-constexpr uint32_t kFilteredSyscall = SYS_vserver;
-
-// Applies a seccomp-bpf filter that returns `filtered_result` for
-// `sysno` and allows all other syscalls. Async-signal-safe.
-void ApplySeccompFilter(uint32_t sysno, uint32_t filtered_result,
- uint32_t flags = 0) {
- // "Prior to [PR_SET_SECCOMP], the task must call prctl(PR_SET_NO_NEW_PRIVS,
- // 1) or run with CAP_SYS_ADMIN privileges in its namespace." -
- // Documentation/prctl/seccomp_filter.txt
- //
- // prctl(PR_SET_NO_NEW_PRIVS, 1) may be called repeatedly; calls after the
- // first are no-ops.
- TEST_PCHECK(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == 0);
- MaybeSave();
-
- struct sock_filter filter[] = {
- // A = seccomp_data.arch
- BPF_STMT(BPF_LD | BPF_ABS | BPF_W, 4),
- // if (A != AUDIT_ARCH_X86_64) goto kill
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 0, 4),
- // A = seccomp_data.nr
- BPF_STMT(BPF_LD | BPF_ABS | BPF_W, 0),
- // if (A != sysno) goto allow
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, sysno, 0, 1),
- // return filtered_result
- BPF_STMT(BPF_RET | BPF_K, filtered_result),
- // allow: return SECCOMP_RET_ALLOW
- BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
- // kill: return SECCOMP_RET_KILL
- BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
- };
- struct sock_fprog prog;
- prog.len = ABSL_ARRAYSIZE(filter);
- prog.filter = filter;
- if (flags) {
- TEST_CHECK(syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER, flags, &prog) ==
- 0);
- } else {
- TEST_PCHECK(prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog, 0, 0) == 0);
- }
- MaybeSave();
-}
-
-// Wrapper for sigaction. Async-signal-safe.
-void RegisterSignalHandler(int signum,
- void (*handler)(int, siginfo_t*, void*)) {
- struct sigaction sa = {};
- sa.sa_sigaction = handler;
- sigemptyset(&sa.sa_mask);
- sa.sa_flags = SA_SIGINFO;
- TEST_PCHECK(sigaction(signum, &sa, nullptr) == 0);
- MaybeSave();
-}
-
-// All of the following tests execute in a subprocess to ensure that each test
-// is run in a separate process. This avoids cross-contamination of seccomp
-// state between tests, and is necessary to ensure that test processes killed
-// by SECCOMP_RET_KILL are single-threaded (since SECCOMP_RET_KILL only kills
-// the offending thread, not the whole thread group).
-
-TEST(SeccompTest, RetKillCausesDeathBySIGSYS) {
- pid_t const pid = fork();
- if (pid == 0) {
- // Register a signal handler for SIGSYS that we don't expect to be invoked.
- RegisterSignalHandler(SIGSYS, +[](int, siginfo_t*, void*) { _exit(1); });
- ApplySeccompFilter(kFilteredSyscall, SECCOMP_RET_KILL);
- syscall(kFilteredSyscall);
- TEST_CHECK_MSG(false, "Survived invocation of test syscall");
- }
- ASSERT_THAT(pid, SyscallSucceeds());
- int status;
- ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGSYS)
- << "status " << status;
-}
-
-TEST(SeccompTest, RetKillOnlyKillsOneThread) {
- Mapping stack = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
-
- pid_t const pid = fork();
- if (pid == 0) {
- // Register a signal handler for SIGSYS that we don't expect to be invoked.
- RegisterSignalHandler(SIGSYS, +[](int, siginfo_t*, void*) { _exit(1); });
- ApplySeccompFilter(kFilteredSyscall, SECCOMP_RET_KILL);
- // Pass CLONE_VFORK to block the original thread in the child process until
- // the clone thread exits with SIGSYS.
- //
- // N.B. clone(2) is not officially async-signal-safe, but at minimum glibc's
- // x86_64 implementation is safe. See glibc
- // sysdeps/unix/sysv/linux/x86_64/clone.S.
- clone(
- +[](void* arg) {
- syscall(kFilteredSyscall); // should kill the thread
- _exit(1); // should be unreachable
- return 2; // should be very unreachable, shut up the compiler
- },
- stack.endptr(),
- CLONE_FILES | CLONE_FS | CLONE_SIGHAND | CLONE_THREAD | CLONE_VM |
- CLONE_VFORK,
- nullptr);
- _exit(0);
- }
- ASSERT_THAT(pid, SyscallSucceeds());
- int status;
- ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "status " << status;
-}
-
-TEST(SeccompTest, RetTrapCausesSIGSYS) {
- pid_t const pid = fork();
- if (pid == 0) {
- constexpr uint16_t kTrapValue = 0xdead;
- RegisterSignalHandler(
- SIGSYS, +[](int signo, siginfo_t* info, void* ucv) {
- ucontext_t* uc = static_cast<ucontext_t*>(ucv);
- // This is a signal handler, so we must stay async-signal-safe.
- TEST_CHECK(info->si_signo == SIGSYS);
- TEST_CHECK(info->si_code == SYS_SECCOMP);
- TEST_CHECK(info->si_errno == kTrapValue);
- TEST_CHECK(info->si_call_addr != nullptr);
- TEST_CHECK(info->si_syscall == kFilteredSyscall);
-#ifdef __x86_64__
- TEST_CHECK(info->si_arch == AUDIT_ARCH_X86_64);
- TEST_CHECK(uc->uc_mcontext.gregs[REG_RAX] == kFilteredSyscall);
-#endif // defined(__x86_64__)
- _exit(0);
- });
- ApplySeccompFilter(kFilteredSyscall, SECCOMP_RET_TRAP | kTrapValue);
- syscall(kFilteredSyscall);
- TEST_CHECK_MSG(false, "Survived invocation of test syscall");
- }
- ASSERT_THAT(pid, SyscallSucceeds());
- int status;
- ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "status " << status;
-}
-
-#ifdef __x86_64__
-
-constexpr uint64_t kVsyscallTimeEntry = 0xffffffffff600400;
-
-time_t vsyscall_time(time_t* t) {
- return reinterpret_cast<time_t (*)(time_t*)>(kVsyscallTimeEntry)(t);
-}
-
-TEST(SeccompTest, SeccompAppliesToVsyscall) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsVsyscallEnabled()));
-
- pid_t const pid = fork();
- if (pid == 0) {
- constexpr uint16_t kTrapValue = 0xdead;
- RegisterSignalHandler(
- SIGSYS, +[](int signo, siginfo_t* info, void* ucv) {
- ucontext_t* uc = static_cast<ucontext_t*>(ucv);
- // This is a signal handler, so we must stay async-signal-safe.
- TEST_CHECK(info->si_signo == SIGSYS);
- TEST_CHECK(info->si_code == SYS_SECCOMP);
- TEST_CHECK(info->si_errno == kTrapValue);
- TEST_CHECK(info->si_call_addr != nullptr);
- TEST_CHECK(info->si_syscall == SYS_time);
- TEST_CHECK(info->si_arch == AUDIT_ARCH_X86_64);
- TEST_CHECK(uc->uc_mcontext.gregs[REG_RAX] == SYS_time);
- _exit(0);
- });
- ApplySeccompFilter(SYS_time, SECCOMP_RET_TRAP | kTrapValue);
- vsyscall_time(nullptr); // Should result in death.
- TEST_CHECK_MSG(false, "Survived invocation of test syscall");
- }
- ASSERT_THAT(pid, SyscallSucceeds());
- int status;
- ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "status " << status;
-}
-
-TEST(SeccompTest, RetKillVsyscallCausesDeathBySIGSYS) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsVsyscallEnabled()));
-
- pid_t const pid = fork();
- if (pid == 0) {
- // Register a signal handler for SIGSYS that we don't expect to be invoked.
- RegisterSignalHandler(
- SIGSYS, +[](int, siginfo_t*, void*) { _exit(1); });
- ApplySeccompFilter(SYS_time, SECCOMP_RET_KILL);
- vsyscall_time(nullptr); // Should result in death.
- TEST_CHECK_MSG(false, "Survived invocation of test syscall");
- }
- ASSERT_THAT(pid, SyscallSucceeds());
- int status;
- ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGSYS)
- << "status " << status;
-}
-
-#endif // defined(__x86_64__)
-
-TEST(SeccompTest, RetTraceWithoutPtracerReturnsENOSYS) {
- pid_t const pid = fork();
- if (pid == 0) {
- ApplySeccompFilter(kFilteredSyscall, SECCOMP_RET_TRACE);
- TEST_CHECK(syscall(kFilteredSyscall) == -1 && errno == ENOSYS);
- _exit(0);
- }
- ASSERT_THAT(pid, SyscallSucceeds());
- int status;
- ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "status " << status;
-}
-
-TEST(SeccompTest, RetErrnoReturnsErrno) {
- pid_t const pid = fork();
- if (pid == 0) {
- // ENOTNAM: "Not a XENIX named type file"
- ApplySeccompFilter(kFilteredSyscall, SECCOMP_RET_ERRNO | ENOTNAM);
- TEST_CHECK(syscall(kFilteredSyscall) == -1 && errno == ENOTNAM);
- _exit(0);
- }
- ASSERT_THAT(pid, SyscallSucceeds());
- int status;
- ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "status " << status;
-}
-
-TEST(SeccompTest, RetAllowAllowsSyscall) {
- pid_t const pid = fork();
- if (pid == 0) {
- ApplySeccompFilter(kFilteredSyscall, SECCOMP_RET_ALLOW);
- TEST_CHECK(syscall(kFilteredSyscall) == -1 && errno == ENOSYS);
- _exit(0);
- }
- ASSERT_THAT(pid, SyscallSucceeds());
- int status;
- ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "status " << status;
-}
-
-// This test will validate that TSYNC will apply to all threads.
-TEST(SeccompTest, TsyncAppliesToAllThreads) {
- Mapping stack = ASSERT_NO_ERRNO_AND_VALUE(
- MmapAnon(2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
-
- // We don't want to apply this policy to other test runner threads, so fork.
- const pid_t pid = fork();
-
- if (pid == 0) {
- // First check that we receive a ENOSYS before the policy is applied.
- TEST_CHECK(syscall(kFilteredSyscall) == -1 && errno == ENOSYS);
-
- // N.B. clone(2) is not officially async-signal-safe, but at minimum glibc's
- // x86_64 implementation is safe. See glibc
- // sysdeps/unix/sysv/linux/x86_64/clone.S.
- clone(
- +[](void* arg) {
- ApplySeccompFilter(kFilteredSyscall, SECCOMP_RET_ERRNO | ENOTNAM,
- SECCOMP_FILTER_FLAG_TSYNC);
- return 0;
- },
- stack.endptr(),
- CLONE_FILES | CLONE_FS | CLONE_SIGHAND | CLONE_THREAD | CLONE_VM |
- CLONE_VFORK,
- nullptr);
-
- // Because we're using CLONE_VFORK this thread will be blocked until
- // the second thread has released resources to our virtual memory, since
- // we're not execing that will happen on _exit.
-
- // Now verify that the policy applied to this thread too.
- TEST_CHECK(syscall(kFilteredSyscall) == -1 && errno == ENOTNAM);
- _exit(0);
- }
-
- ASSERT_THAT(pid, SyscallSucceeds());
- int status = 0;
- ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "status " << status;
-}
-
-// This test will validate that seccomp(2) rejects unsupported flags.
-TEST(SeccompTest, SeccompRejectsUnknownFlags) {
- constexpr uint32_t kInvalidFlag = 123;
- ASSERT_THAT(
- syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER, kInvalidFlag, nullptr),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(SeccompTest, LeastPermissiveFilterReturnValueApplies) {
- // This is RetKillCausesDeathBySIGSYS, plus extra filters before and after the
- // one that causes the kill that should be ignored.
- pid_t const pid = fork();
- if (pid == 0) {
- RegisterSignalHandler(SIGSYS, +[](int, siginfo_t*, void*) { _exit(1); });
- ApplySeccompFilter(kFilteredSyscall, SECCOMP_RET_TRACE);
- ApplySeccompFilter(kFilteredSyscall, SECCOMP_RET_KILL);
- ApplySeccompFilter(kFilteredSyscall, SECCOMP_RET_ERRNO | ENOTNAM);
- syscall(kFilteredSyscall);
- TEST_CHECK_MSG(false, "Survived invocation of test syscall");
- }
- ASSERT_THAT(pid, SyscallSucceeds());
- int status;
- ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGSYS)
- << "status " << status;
-}
-
-// Passed as argv[1] to cause the test binary to invoke kFilteredSyscall and
-// exit. Not a real flag since flag parsing happens during initialization,
-// which may create threads.
-constexpr char kInvokeFilteredSyscallFlag[] = "--seccomp_test_child";
-
-TEST(SeccompTest, FiltersPreservedAcrossForkAndExecve) {
- ExecveArray const grandchild_argv(
- {"/proc/self/exe", kInvokeFilteredSyscallFlag});
-
- pid_t const pid = fork();
- if (pid == 0) {
- ApplySeccompFilter(kFilteredSyscall, SECCOMP_RET_KILL);
- pid_t const grandchild_pid = fork();
- if (grandchild_pid == 0) {
- execve(grandchild_argv.get()[0], grandchild_argv.get(),
- /* envp = */ nullptr);
- TEST_PCHECK_MSG(false, "execve failed");
- }
- int status;
- TEST_PCHECK(waitpid(grandchild_pid, &status, 0) == grandchild_pid);
- TEST_CHECK(WIFSIGNALED(status) && WTERMSIG(status) == SIGSYS);
- _exit(0);
- }
- ASSERT_THAT(pid, SyscallSucceeds());
- int status;
- ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "status " << status;
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
-
-int main(int argc, char** argv) {
- if (argc >= 2 &&
- strcmp(argv[1], gvisor::testing::kInvokeFilteredSyscallFlag) == 0) {
- syscall(gvisor::testing::kFilteredSyscall);
- exit(0);
- }
-
- gvisor::testing::TestInit(&argc, &argv);
- return RUN_ALL_TESTS();
-}
diff --git a/test/syscalls/linux/select.cc b/test/syscalls/linux/select.cc
deleted file mode 100644
index 88c010aec..000000000
--- a/test/syscalls/linux/select.cc
+++ /dev/null
@@ -1,168 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <sys/resource.h>
-#include <sys/select.h>
-#include <sys/time.h>
-#include <climits>
-#include <csignal>
-#include <cstdio>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "absl/time/time.h"
-#include "test/syscalls/linux/base_poll_test.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/multiprocess_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/rlimit_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-class SelectTest : public BasePollTest {
- protected:
- void SetUp() override { BasePollTest::SetUp(); }
- void TearDown() override { BasePollTest::TearDown(); }
-};
-
-// See that when there are no FD sets, select behaves like sleep.
-TEST_F(SelectTest, NullFds) {
- struct timeval timeout = absl::ToTimeval(absl::Milliseconds(10));
- ASSERT_THAT(select(0, nullptr, nullptr, nullptr, &timeout),
- SyscallSucceeds());
- EXPECT_EQ(timeout.tv_sec, 0);
- EXPECT_EQ(timeout.tv_usec, 0);
-
- timeout = absl::ToTimeval(absl::Milliseconds(10));
- ASSERT_THAT(select(1, nullptr, nullptr, nullptr, &timeout),
- SyscallSucceeds());
- EXPECT_EQ(timeout.tv_sec, 0);
- EXPECT_EQ(timeout.tv_usec, 0);
-}
-
-TEST_F(SelectTest, NegativeNfds) {
- EXPECT_THAT(select(-1, nullptr, nullptr, nullptr, nullptr),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(select(-100000, nullptr, nullptr, nullptr, nullptr),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(select(INT_MIN, nullptr, nullptr, nullptr, nullptr),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_F(SelectTest, ClosedFds) {
- auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(temp_file.path(), O_RDONLY));
-
- // We can't rely on a file descriptor being closed in a multi threaded
- // application so fork to get a clean process.
- EXPECT_THAT(InForkedProcess([&] {
- int fd_num = fd.get();
- fd.reset();
-
- fd_set read_set;
- FD_ZERO(&read_set);
- FD_SET(fd_num, &read_set);
-
- struct timeval timeout =
- absl::ToTimeval(absl::Milliseconds(10));
- TEST_PCHECK(select(fd_num + 1, &read_set, nullptr, nullptr,
- &timeout) != 0);
- TEST_PCHECK(errno == EBADF);
- }),
- IsPosixErrorOkAndHolds(0));
-}
-
-TEST_F(SelectTest, ZeroTimeout) {
- struct timeval timeout = {};
- EXPECT_THAT(select(1, nullptr, nullptr, nullptr, &timeout),
- SyscallSucceeds());
- // Ignore timeout as its value is now undefined.
-}
-
-// If random S/R interrupts the select, SIGALRM may be delivered before select
-// restarts, causing the select to hang forever.
-TEST_F(SelectTest, NoTimeout_NoRandomSave) {
- // When there's no timeout, select may never return so set a timer.
- SetTimer(absl::Milliseconds(100));
- // See that we get interrupted by the timer.
- ASSERT_THAT(select(1, nullptr, nullptr, nullptr, nullptr),
- SyscallFailsWithErrno(EINTR));
- EXPECT_TRUE(TimerFired());
-}
-
-TEST_F(SelectTest, InvalidTimeoutNegative) {
- struct timeval timeout = absl::ToTimeval(absl::Microseconds(-1));
- EXPECT_THAT(select(1, nullptr, nullptr, nullptr, &timeout),
- SyscallFailsWithErrno(EINVAL));
- // Ignore timeout as its value is now undefined.
-}
-
-// Verify that a signal interrupts select.
-//
-// If random S/R interrupts the select, SIGALRM may be delivered before select
-// restarts, causing the select to hang forever.
-TEST_F(SelectTest, InterruptedBySignal_NoRandomSave) {
- absl::Duration duration(absl::Seconds(5));
- struct timeval timeout = absl::ToTimeval(duration);
- SetTimer(absl::Milliseconds(100));
- ASSERT_FALSE(TimerFired());
- ASSERT_THAT(select(1, nullptr, nullptr, nullptr, &timeout),
- SyscallFailsWithErrno(EINTR));
- EXPECT_TRUE(TimerFired());
- // Ignore timeout as its value is now undefined.
-}
-
-TEST_F(SelectTest, IgnoreBitsAboveNfds) {
- // fd_set is a bit array with at least FD_SETSIZE bits. Test that bits
- // corresponding to file descriptors above nfds are ignored.
- fd_set read_set;
- FD_ZERO(&read_set);
- constexpr int kNfds = 1;
- for (int fd = kNfds; fd < FD_SETSIZE; fd++) {
- FD_SET(fd, &read_set);
- }
- // Pass a zero timeout so that select returns immediately.
- struct timeval timeout = {};
- EXPECT_THAT(select(kNfds, &read_set, nullptr, nullptr, &timeout),
- SyscallSucceedsWithValue(0));
-}
-
-// This test illustrates Linux's behavior of 'select' calls passing after
-// setrlimit RLIMIT_NOFILE is called. In particular, versions of sshd rely on
-// this behavior.
-TEST_F(SelectTest, SetrlimitCallNOFILE) {
- fd_set read_set;
- FD_ZERO(&read_set);
- timeval timeout = {};
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(
- Open(NewTempAbsPath(), O_RDONLY | O_CREAT, S_IRUSR));
-
- Cleanup reset_rlimit =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_NOFILE, 0));
-
- FD_SET(fd.get(), &read_set);
- // this call with zero timeout should return immediately
- EXPECT_THAT(select(fd.get() + 1, &read_set, nullptr, nullptr, &timeout),
- SyscallSucceeds());
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/semaphore.cc b/test/syscalls/linux/semaphore.cc
deleted file mode 100644
index 40c57f543..000000000
--- a/test/syscalls/linux/semaphore.cc
+++ /dev/null
@@ -1,492 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sys/ipc.h>
-#include <sys/sem.h>
-#include <sys/types.h>
-
-#include <atomic>
-#include <cerrno>
-#include <ctime>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/base/macros.h"
-#include "absl/memory/memory.h"
-#include "absl/synchronization/mutex.h"
-#include "absl/time/clock.h"
-#include "test/util/capability_util.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-class AutoSem {
- public:
- explicit AutoSem(int id) : id_(id) {}
- ~AutoSem() {
- if (id_ >= 0) {
- EXPECT_THAT(semctl(id_, 0, IPC_RMID), SyscallSucceeds());
- }
- }
-
- int release() {
- int old = id_;
- id_ = -1;
- return old;
- }
-
- int get() { return id_; }
-
- private:
- int id_ = -1;
-};
-
-TEST(SemaphoreTest, SemGet) {
- // Test creation and lookup.
- AutoSem sem(semget(1, 10, IPC_CREAT));
- ASSERT_THAT(sem.get(), SyscallSucceeds());
- EXPECT_THAT(semget(1, 10, IPC_CREAT), SyscallSucceedsWithValue(sem.get()));
- EXPECT_THAT(semget(1, 9, IPC_CREAT), SyscallSucceedsWithValue(sem.get()));
-
- // Creation and lookup failure cases.
- EXPECT_THAT(semget(1, 11, IPC_CREAT), SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(semget(1, -1, IPC_CREAT), SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(semget(1, 10, IPC_CREAT | IPC_EXCL),
- SyscallFailsWithErrno(EEXIST));
- EXPECT_THAT(semget(2, 1, 0), SyscallFailsWithErrno(ENOENT));
- EXPECT_THAT(semget(2, 0, IPC_CREAT), SyscallFailsWithErrno(EINVAL));
-
- // Private semaphores never conflict.
- AutoSem sem2(semget(IPC_PRIVATE, 1, 0));
- AutoSem sem3(semget(IPC_PRIVATE, 1, 0));
- ASSERT_THAT(sem2.get(), SyscallSucceeds());
- EXPECT_NE(sem.get(), sem2.get());
- ASSERT_THAT(sem3.get(), SyscallSucceeds());
- EXPECT_NE(sem3.get(), sem2.get());
-}
-
-// Tests simple operations that shouldn't block in a single-thread.
-TEST(SemaphoreTest, SemOpSingleNoBlock) {
- AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT));
- ASSERT_THAT(sem.get(), SyscallSucceeds());
-
- struct sembuf buf = {};
- buf.sem_op = 1;
- ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallSucceeds());
-
- buf.sem_op = -1;
- ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallSucceeds());
-
- buf.sem_op = 0;
- ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallSucceeds());
-
- // Error cases with invalid values.
- ASSERT_THAT(semop(sem.get() + 1, &buf, 1), SyscallFailsWithErrno(EINVAL));
-
- buf.sem_num = 1;
- ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallFailsWithErrno(EFBIG));
-
- ASSERT_THAT(semop(sem.get(), nullptr, 0), SyscallFailsWithErrno(EINVAL));
-}
-
-// Tests multiple operations that shouldn't block in a single-thread.
-TEST(SemaphoreTest, SemOpMultiNoBlock) {
- AutoSem sem(semget(IPC_PRIVATE, 4, 0600 | IPC_CREAT));
- ASSERT_THAT(sem.get(), SyscallSucceeds());
-
- struct sembuf bufs[5] = {};
- bufs[0].sem_num = 0;
- bufs[0].sem_op = 10;
- bufs[0].sem_flg = 0;
-
- bufs[1].sem_num = 1;
- bufs[1].sem_op = 2;
- bufs[1].sem_flg = 0;
-
- bufs[2].sem_num = 2;
- bufs[2].sem_op = 3;
- bufs[2].sem_flg = 0;
-
- bufs[3].sem_num = 0;
- bufs[3].sem_op = -5;
- bufs[3].sem_flg = 0;
-
- bufs[4].sem_num = 2;
- bufs[4].sem_op = 2;
- bufs[4].sem_flg = 0;
-
- ASSERT_THAT(semop(sem.get(), bufs, ABSL_ARRAYSIZE(bufs)), SyscallSucceeds());
-
- ASSERT_THAT(semctl(sem.get(), 0, GETVAL), SyscallSucceedsWithValue(5));
- ASSERT_THAT(semctl(sem.get(), 1, GETVAL), SyscallSucceedsWithValue(2));
- ASSERT_THAT(semctl(sem.get(), 2, GETVAL), SyscallSucceedsWithValue(5));
- ASSERT_THAT(semctl(sem.get(), 3, GETVAL), SyscallSucceedsWithValue(0));
-
- for (auto& b : bufs) {
- b.sem_op = -b.sem_op;
- }
- // 0 and 3 order must be reversed, otherwise it will block.
- std::swap(bufs[0].sem_op, bufs[3].sem_op);
- ASSERT_THAT(RetryEINTR(semop)(sem.get(), bufs, ABSL_ARRAYSIZE(bufs)),
- SyscallSucceeds());
-
- // All semaphores should be back to 0 now.
- for (size_t i = 0; i < 4; ++i) {
- ASSERT_THAT(semctl(sem.get(), i, GETVAL), SyscallSucceedsWithValue(0));
- }
-}
-
-// Makes a best effort attempt to ensure that operation would block.
-TEST(SemaphoreTest, SemOpBlock) {
- AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT));
- ASSERT_THAT(sem.get(), SyscallSucceeds());
-
- std::atomic<int> blocked = ATOMIC_VAR_INIT(1);
- ScopedThread th([&sem, &blocked] {
- absl::SleepFor(absl::Milliseconds(100));
- ASSERT_EQ(blocked.load(), 1);
-
- struct sembuf buf = {};
- buf.sem_op = 1;
- ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1), SyscallSucceeds());
- });
-
- struct sembuf buf = {};
- buf.sem_op = -1;
- ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1), SyscallSucceeds());
- blocked.store(0);
-}
-
-// Tests that IPC_NOWAIT returns with no wait.
-TEST(SemaphoreTest, SemOpNoBlock) {
- AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT));
- ASSERT_THAT(sem.get(), SyscallSucceeds());
-
- struct sembuf buf = {};
- buf.sem_flg = IPC_NOWAIT;
-
- buf.sem_op = -1;
- ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallFailsWithErrno(EAGAIN));
-
- buf.sem_op = 1;
- ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallSucceeds());
-
- buf.sem_op = 0;
- ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallFailsWithErrno(EAGAIN));
-}
-
-// Test runs 2 threads, one signals the other waits the same number of times.
-TEST(SemaphoreTest, SemOpSimple) {
- AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT));
- ASSERT_THAT(sem.get(), SyscallSucceeds());
-
- constexpr size_t kLoops = 100;
- ScopedThread th([&sem] {
- struct sembuf buf = {};
- buf.sem_op = 1;
- for (size_t i = 0; i < kLoops; i++) {
- // Sleep to prevent making all increments in one shot without letting
- // the waiter wait.
- absl::SleepFor(absl::Milliseconds(1));
- ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallSucceeds());
- }
- });
-
- struct sembuf buf = {};
- buf.sem_op = -1;
- for (size_t i = 0; i < kLoops; i++) {
- ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1), SyscallSucceeds());
- }
-}
-
-// Tests that semaphore can be removed while there are waiters.
-// NoRandomSave: Test relies on timing that random save throws off.
-TEST(SemaphoreTest, SemOpRemoveWithWaiter_NoRandomSave) {
- AutoSem sem(semget(IPC_PRIVATE, 2, 0600 | IPC_CREAT));
- ASSERT_THAT(sem.get(), SyscallSucceeds());
-
- ScopedThread th([&sem] {
- absl::SleepFor(absl::Milliseconds(250));
- ASSERT_THAT(semctl(sem.release(), 0, IPC_RMID), SyscallSucceeds());
- });
-
- // This must happen before IPC_RMID runs above. Otherwise it fails with EINVAL
- // instead because the semaphore has already been removed.
- struct sembuf buf = {};
- buf.sem_op = -1;
- ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1),
- SyscallFailsWithErrno(EIDRM));
-}
-
-// Semaphore isn't fair. It will execute any waiter that can satisfy the
-// request even if it gets in front of other waiters.
-TEST(SemaphoreTest, SemOpBestFitExecution) {
- AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT));
- ASSERT_THAT(sem.get(), SyscallSucceeds());
-
- ScopedThread th([&sem] {
- struct sembuf buf = {};
- buf.sem_op = -2;
- ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1), SyscallFails());
- // Ensure that wait will only unblock when the semaphore is removed. On
- // EINTR retry it may race with deletion and return EINVAL.
- ASSERT_TRUE(errno == EIDRM || errno == EINVAL) << "errno=" << errno;
- });
-
- // Ensures that '-1' below will unblock even though '-10' above is waiting
- // for the same semaphore.
- for (size_t i = 0; i < 10; ++i) {
- struct sembuf buf = {};
- buf.sem_op = 1;
- ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1), SyscallSucceeds());
-
- absl::SleepFor(absl::Milliseconds(10));
-
- buf.sem_op = -1;
- ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1), SyscallSucceeds());
- }
-
- ASSERT_THAT(semctl(sem.release(), 0, IPC_RMID), SyscallSucceeds());
-}
-
-// Executes random operations in multiple threads and verify correctness.
-TEST(SemaphoreTest, SemOpRandom) {
- // Don't do cooperative S/R tests because there are too many syscalls in
- // this test,
- const DisableSave ds;
-
- AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT));
- ASSERT_THAT(sem.get(), SyscallSucceeds());
-
- // Protects the seed below.
- absl::Mutex mutex;
- uint32_t seed = time(nullptr);
-
- int count = 0; // Tracks semaphore value.
- bool done = false; // Tells waiters to stop after signal threads are done.
-
- // These threads will wait in a loop.
- std::unique_ptr<ScopedThread> decs[5];
- for (auto& dec : decs) {
- dec = absl::make_unique<ScopedThread>([&sem, &mutex, &count, &seed, &done] {
- for (size_t i = 0; i < 500; ++i) {
- int16_t val;
- {
- absl::MutexLock l(&mutex);
- if (done) {
- return;
- }
- val = (rand_r(&seed) % 10 + 1); // Rand between 1 and 10.
- count -= val;
- }
- struct sembuf buf = {};
- buf.sem_op = -val;
- ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1), SyscallSucceeds());
- absl::SleepFor(absl::Milliseconds(val * 2));
- }
- });
- }
-
- // These threads will wait for zero in a loop.
- std::unique_ptr<ScopedThread> zeros[5];
- for (auto& zero : zeros) {
- zero = absl::make_unique<ScopedThread>([&sem, &mutex, &done] {
- for (size_t i = 0; i < 500; ++i) {
- {
- absl::MutexLock l(&mutex);
- if (done) {
- return;
- }
- }
- struct sembuf buf = {};
- buf.sem_op = 0;
- ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1), SyscallSucceeds());
- absl::SleepFor(absl::Milliseconds(10));
- }
- });
- }
-
- // These threads will signal in a loop.
- std::unique_ptr<ScopedThread> incs[5];
- for (auto& inc : incs) {
- inc = absl::make_unique<ScopedThread>([&sem, &mutex, &count, &seed] {
- for (size_t i = 0; i < 500; ++i) {
- int16_t val;
- {
- absl::MutexLock l(&mutex);
- val = (rand_r(&seed) % 10 + 1); // Rand between 1 and 10.
- count += val;
- }
- struct sembuf buf = {};
- buf.sem_op = val;
- ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallSucceeds());
- absl::SleepFor(absl::Milliseconds(val * 2));
- }
- });
- }
-
- // First wait for signal threads to be done.
- for (auto& inc : incs) {
- inc->Join();
- }
-
- // Now there could be waiters blocked (remember operations are random).
- // Notify waiters that we're done and signal semaphore just the right amount.
- {
- absl::MutexLock l(&mutex);
- done = true;
- struct sembuf buf = {};
- buf.sem_op = -count;
- ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallSucceeds());
- }
-
- // Now all waiters should unblock and exit.
- for (auto& dec : decs) {
- dec->Join();
- }
- for (auto& zero : zeros) {
- zero->Join();
- }
-}
-
-TEST(SemaphoreTest, SemOpNamespace) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- AutoSem sem(semget(123, 1, 0600 | IPC_CREAT | IPC_EXCL));
- ASSERT_THAT(sem.get(), SyscallSucceeds());
-
- ScopedThread([]() {
- EXPECT_THAT(unshare(CLONE_NEWIPC), SyscallSucceeds());
- AutoSem sem(semget(123, 1, 0600 | IPC_CREAT | IPC_EXCL));
- ASSERT_THAT(sem.get(), SyscallSucceeds());
- });
-}
-
-TEST(SemaphoreTest, SemCtlVal) {
- AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT));
- ASSERT_THAT(sem.get(), SyscallSucceeds());
-
- // Semaphore must start with 0.
- EXPECT_THAT(semctl(sem.get(), 0, GETVAL), SyscallSucceedsWithValue(0));
-
- // Increase value and ensure waiters are woken up.
- ScopedThread th([&sem] {
- struct sembuf buf = {};
- buf.sem_op = -10;
- ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1), SyscallSucceeds());
- });
-
- ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 9), SyscallSucceeds());
- EXPECT_THAT(semctl(sem.get(), 0, GETVAL), SyscallSucceedsWithValue(9));
-
- ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 20), SyscallSucceeds());
- const int value = semctl(sem.get(), 0, GETVAL);
- // 10 or 20 because it could have raced with waiter above.
- EXPECT_TRUE(value == 10 || value == 20) << "value=" << value;
- th.Join();
-
- // Set it back to 0 and ensure that waiters are woken up.
- ScopedThread thZero([&sem] {
- struct sembuf buf = {};
- buf.sem_op = 0;
- ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1), SyscallSucceeds());
- });
- ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 0), SyscallSucceeds());
- EXPECT_THAT(semctl(sem.get(), 0, GETVAL), SyscallSucceedsWithValue(0));
- thZero.Join();
-}
-
-TEST(SemaphoreTest, SemCtlValAll) {
- AutoSem sem(semget(IPC_PRIVATE, 3, 0600 | IPC_CREAT));
- ASSERT_THAT(sem.get(), SyscallSucceeds());
-
- // Semaphores must start with 0.
- uint16_t get[3] = {10, 10, 10};
- EXPECT_THAT(semctl(sem.get(), 1, GETALL, get), SyscallSucceedsWithValue(0));
- for (auto v : get) {
- EXPECT_EQ(v, 0);
- }
-
- // SetAll and check that they were set.
- uint16_t vals[3] = {0, 10, 20};
- EXPECT_THAT(semctl(sem.get(), 1, SETALL, vals), SyscallSucceedsWithValue(0));
- EXPECT_THAT(semctl(sem.get(), 1, GETALL, get), SyscallSucceedsWithValue(0));
- for (size_t i = 0; i < ABSL_ARRAYSIZE(vals); ++i) {
- EXPECT_EQ(get[i], vals[i]);
- }
-
- EXPECT_THAT(semctl(sem.get(), 1, SETALL, nullptr),
- SyscallFailsWithErrno(EFAULT));
-}
-
-TEST(SemaphoreTest, SemCtlGetPid) {
- AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT));
- ASSERT_THAT(sem.get(), SyscallSucceeds());
-
- ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 1), SyscallSucceeds());
- EXPECT_THAT(semctl(sem.get(), 0, GETPID), SyscallSucceedsWithValue(getpid()));
-}
-
-TEST(SemaphoreTest, SemCtlGetPidFork) {
- AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT));
- ASSERT_THAT(sem.get(), SyscallSucceeds());
-
- const pid_t child_pid = fork();
- if (child_pid == 0) {
- ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 1), SyscallSucceeds());
- ASSERT_THAT(semctl(sem.get(), 0, GETPID),
- SyscallSucceedsWithValue(getpid()));
-
- _exit(0);
- }
- ASSERT_THAT(child_pid, SyscallSucceeds());
-
- int status;
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << " status " << status;
-}
-
-TEST(SemaphoreTest, SemIpcSet) {
- // Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false));
-
- AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT));
- ASSERT_THAT(sem.get(), SyscallSucceeds());
-
- struct semid_ds semid = {};
- semid.sem_perm.uid = getuid();
- semid.sem_perm.gid = getgid();
-
- // Make semaphore readonly and check that signal fails.
- semid.sem_perm.mode = 0400;
- EXPECT_THAT(semctl(sem.get(), 0, IPC_SET, &semid), SyscallSucceeds());
- struct sembuf buf = {};
- buf.sem_op = 1;
- ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallFailsWithErrno(EACCES));
-
- // Make semaphore writeonly and check that wait for zero fails.
- semid.sem_perm.mode = 0200;
- EXPECT_THAT(semctl(sem.get(), 0, IPC_SET, &semid), SyscallSucceeds());
- buf.sem_op = 0;
- ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallFailsWithErrno(EACCES));
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/sendfile.cc b/test/syscalls/linux/sendfile.cc
deleted file mode 100644
index 4502e7fb4..000000000
--- a/test/syscalls/linux/sendfile.cc
+++ /dev/null
@@ -1,517 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <sys/sendfile.h>
-#include <unistd.h>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/strings/string_view.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(SendFileTest, SendZeroBytes) {
- // Create temp files.
- const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- // Open the input file as read only.
- const FileDescriptor inf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY));
-
- // Open the output file as write only.
- const FileDescriptor outf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY));
-
- // Send data and verify that sendfile returns the correct value.
- EXPECT_THAT(sendfile(outf.get(), inf.get(), nullptr, 0),
- SyscallSucceedsWithValue(0));
-}
-
-TEST(SendFileTest, InvalidOffset) {
- // Create temp files.
- const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- // Open the input file as read only.
- const FileDescriptor inf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY));
-
- // Open the output file as write only.
- const FileDescriptor outf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY));
-
- // Send data and verify that sendfile returns the correct value.
- off_t offset = -1;
- EXPECT_THAT(sendfile(outf.get(), inf.get(), &offset, 0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(SendFileTest, SendTrivially) {
- // Create temp files.
- constexpr char kData[] = "To be, or not to be, that is the question:";
- constexpr int kDataSize = sizeof(kData) - 1;
- const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode));
- const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- // Open the input file as read only.
- const FileDescriptor inf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY));
-
- // Open the output file as write only.
- FileDescriptor outf;
- outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY));
-
- // Send data and verify that sendfile returns the correct value.
- int bytes_sent;
- EXPECT_THAT(bytes_sent = sendfile(outf.get(), inf.get(), nullptr, kDataSize),
- SyscallSucceedsWithValue(kDataSize));
-
- // Close outf to avoid leak.
- outf.reset();
-
- // Open the output file as read only.
- outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDONLY));
-
- // Verify that the output file has the correct data.
- char actual[kDataSize];
- ASSERT_THAT(read(outf.get(), &actual, bytes_sent),
- SyscallSucceedsWithValue(kDataSize));
- EXPECT_EQ(kData, absl::string_view(actual, bytes_sent));
-}
-
-TEST(SendFileTest, SendTriviallyWithBothFilesReadWrite) {
- // Create temp files.
- constexpr char kData[] = "Whether 'tis nobler in the mind to suffer";
- constexpr int kDataSize = sizeof(kData) - 1;
- const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode));
- const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- // Open the input file as readwrite.
- const FileDescriptor inf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDWR));
-
- // Open the output file as readwrite.
- FileDescriptor outf;
- outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDWR));
-
- // Send data and verify that sendfile returns the correct value.
- int bytes_sent;
- EXPECT_THAT(bytes_sent = sendfile(outf.get(), inf.get(), nullptr, kDataSize),
- SyscallSucceedsWithValue(kDataSize));
-
- // Close outf to avoid leak.
- outf.reset();
-
- // Open the output file as read only.
- outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDONLY));
-
- // Verify that the output file has the correct data.
- char actual[kDataSize];
- ASSERT_THAT(read(outf.get(), &actual, bytes_sent),
- SyscallSucceedsWithValue(kDataSize));
- EXPECT_EQ(kData, absl::string_view(actual, bytes_sent));
-}
-
-TEST(SendFileTest, SendAndUpdateFileOffset) {
- // Create temp files.
- // Test input string length must be > 2 AND even.
- constexpr char kData[] = "The slings and arrows of outrageous fortune,";
- constexpr int kDataSize = sizeof(kData) - 1;
- constexpr int kHalfDataSize = kDataSize / 2;
- const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode));
- const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- // Open the input file as read only.
- const FileDescriptor inf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY));
-
- // Open the output file as write only.
- FileDescriptor outf;
- outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY));
-
- // Send data and verify that sendfile returns the correct value.
- int bytes_sent;
- EXPECT_THAT(
- bytes_sent = sendfile(outf.get(), inf.get(), nullptr, kHalfDataSize),
- SyscallSucceedsWithValue(kHalfDataSize));
-
- // Close outf to avoid leak.
- outf.reset();
-
- // Open the output file as read only.
- outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDONLY));
-
- // Verify that the output file has the correct data.
- char actual[kHalfDataSize];
- ASSERT_THAT(read(outf.get(), &actual, bytes_sent),
- SyscallSucceedsWithValue(kHalfDataSize));
- EXPECT_EQ(absl::string_view(kData, kHalfDataSize),
- absl::string_view(actual, bytes_sent));
-
- // Verify that the input file offset has been updated
- ASSERT_THAT(read(inf.get(), &actual, kDataSize - bytes_sent),
- SyscallSucceedsWithValue(kHalfDataSize));
- EXPECT_EQ(
- absl::string_view(kData + kDataSize - bytes_sent, kDataSize - bytes_sent),
- absl::string_view(actual, kHalfDataSize));
-}
-
-TEST(SendFileTest, SendAndUpdateFileOffsetFromNonzeroStartingPoint) {
- // Create temp files.
- // Test input string length must be > 2 AND divisible by 4.
- constexpr char kData[] = "The slings and arrows of outrageous fortune,";
- constexpr int kDataSize = sizeof(kData) - 1;
- constexpr int kHalfDataSize = kDataSize / 2;
- constexpr int kQuarterDataSize = kHalfDataSize / 2;
- const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode));
- const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- // Open the input file as read only.
- const FileDescriptor inf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY));
-
- // Open the output file as write only.
- FileDescriptor outf;
- outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY));
-
- // Read a quarter of the data from the infile which should update the file
- // offset, we don't actually care about the data so it goes into the garbage.
- char garbage[kQuarterDataSize];
- ASSERT_THAT(read(inf.get(), &garbage, kQuarterDataSize),
- SyscallSucceedsWithValue(kQuarterDataSize));
-
- // Send data and verify that sendfile returns the correct value.
- int bytes_sent;
- EXPECT_THAT(
- bytes_sent = sendfile(outf.get(), inf.get(), nullptr, kHalfDataSize),
- SyscallSucceedsWithValue(kHalfDataSize));
-
- // Close out_fd to avoid leak.
- outf.reset();
-
- // Open the output file as read only.
- outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDONLY));
-
- // Verify that the output file has the correct data.
- char actual[kHalfDataSize];
- ASSERT_THAT(read(outf.get(), &actual, bytes_sent),
- SyscallSucceedsWithValue(kHalfDataSize));
- EXPECT_EQ(absl::string_view(kData + kQuarterDataSize, kHalfDataSize),
- absl::string_view(actual, bytes_sent));
-
- // Verify that the input file offset has been updated
- ASSERT_THAT(read(inf.get(), &actual, kQuarterDataSize),
- SyscallSucceedsWithValue(kQuarterDataSize));
-
- EXPECT_EQ(
- absl::string_view(kData + kDataSize - kQuarterDataSize, kQuarterDataSize),
- absl::string_view(actual, kQuarterDataSize));
-}
-
-TEST(SendFileTest, SendAndUpdateGivenOffset) {
- // Create temp files.
- // Test input string length must be >= 4 AND divisible by 4.
- constexpr char kData[] = "Or to take Arms against a Sea of troubles,";
- constexpr int kDataSize = sizeof(kData) + 1;
- constexpr int kHalfDataSize = kDataSize / 2;
- constexpr int kQuarterDataSize = kHalfDataSize / 2;
- constexpr int kThreeFourthsDataSize = 3 * kDataSize / 4;
-
- const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode));
- const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- // Open the input file as read only.
- const FileDescriptor inf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY));
-
- // Open the output file as write only.
- FileDescriptor outf;
- outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY));
-
- // Create offset for sending.
- off_t offset = kQuarterDataSize;
-
- // Send data and verify that sendfile returns the correct value.
- int bytes_sent;
- EXPECT_THAT(
- bytes_sent = sendfile(outf.get(), inf.get(), &offset, kHalfDataSize),
- SyscallSucceedsWithValue(kHalfDataSize));
-
- // Close out_fd to avoid leak.
- outf.reset();
-
- // Open the output file as read only.
- outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDONLY));
-
- // Verify that the output file has the correct data.
- char actual[kHalfDataSize];
- ASSERT_THAT(read(outf.get(), &actual, bytes_sent),
- SyscallSucceedsWithValue(kHalfDataSize));
- EXPECT_EQ(absl::string_view(kData + kQuarterDataSize, kHalfDataSize),
- absl::string_view(actual, bytes_sent));
-
- // Verify that the input file offset has NOT been updated.
- ASSERT_THAT(read(inf.get(), &actual, kHalfDataSize),
- SyscallSucceedsWithValue(kHalfDataSize));
- EXPECT_EQ(absl::string_view(kData, kHalfDataSize),
- absl::string_view(actual, kHalfDataSize));
-
- // Verify that the offset pointer has been updated.
- EXPECT_EQ(offset, kThreeFourthsDataSize);
-}
-
-TEST(SendFileTest, DoNotSendfileIfOutfileIsAppendOnly) {
- // Create temp files.
- constexpr char kData[] = "And by opposing end them: to die, to sleep";
- constexpr int kDataSize = sizeof(kData) - 1;
-
- const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode));
- const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- // Open the input file as read only.
- const FileDescriptor inf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY));
-
- // Open the output file as append only.
- const FileDescriptor outf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY | O_APPEND));
-
- // Send data and verify that sendfile returns the correct errno.
- EXPECT_THAT(sendfile(outf.get(), inf.get(), nullptr, kDataSize),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(SendFileTest, AppendCheckOrdering) {
- constexpr char kData[] = "And by opposing end them: to die, to sleep";
- constexpr int kDataSize = sizeof(kData) - 1;
- const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode));
-
- const FileDescriptor read =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY));
- const FileDescriptor write =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_WRONLY));
- const FileDescriptor append =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_APPEND));
-
- // Check that read/write file mode is verified before append.
- EXPECT_THAT(sendfile(append.get(), read.get(), nullptr, kDataSize),
- SyscallFailsWithErrno(EBADF));
- EXPECT_THAT(sendfile(write.get(), write.get(), nullptr, kDataSize),
- SyscallFailsWithErrno(EBADF));
-}
-
-TEST(SendFileTest, DoNotSendfileIfOutfileIsNotWritable) {
- // Create temp files.
- constexpr char kData[] = "No more; and by a sleep, to say we end";
- constexpr int kDataSize = sizeof(kData) - 1;
-
- const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode));
- const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- // Open the input file as read only.
- const FileDescriptor inf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY));
-
- // Open the output file as read only.
- const FileDescriptor outf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDONLY));
-
- // Send data and verify that sendfile returns the correct errno.
- EXPECT_THAT(sendfile(outf.get(), inf.get(), nullptr, kDataSize),
- SyscallFailsWithErrno(EBADF));
-}
-
-TEST(SendFileTest, DoNotSendfileIfInfileIsNotReadable) {
- // Create temp files.
- constexpr char kData[] = "the heart-ache, and the thousand natural shocks";
- constexpr int kDataSize = sizeof(kData) - 1;
-
- const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode));
- const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- // Open the input file as write only.
- const FileDescriptor inf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_WRONLY));
-
- // Open the output file as write only.
- const FileDescriptor outf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY));
-
- // Send data and verify that sendfile returns the correct errno.
- EXPECT_THAT(sendfile(outf.get(), inf.get(), nullptr, kDataSize),
- SyscallFailsWithErrno(EBADF));
-}
-
-TEST(SendFileTest, DoNotSendANegativeNumberOfBytes) {
- // Create temp files.
- constexpr char kData[] = "that Flesh is heir to? 'Tis a consummation";
-
- const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode));
- const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- // Open the input file as read only.
- const FileDescriptor inf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY));
-
- // Open the output file as write only.
- const FileDescriptor outf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY));
-
- // Send data and verify that sendfile returns the correct errno.
- EXPECT_THAT(sendfile(outf.get(), inf.get(), nullptr, -1),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(SendFileTest, SendTheCorrectNumberOfBytesEvenIfWeTryToSendTooManyBytes) {
- // Create temp files.
- constexpr char kData[] = "devoutly to be wished. To die, to sleep,";
- constexpr int kDataSize = sizeof(kData) - 1;
-
- const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode));
- const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- // Open the input file as read only.
- const FileDescriptor inf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY));
-
- // Open the output file as write only.
- FileDescriptor outf;
- outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY));
-
- // Send data and verify that sendfile returns the correct value.
- int bytes_sent;
- EXPECT_THAT(
- bytes_sent = sendfile(outf.get(), inf.get(), nullptr, kDataSize + 100),
- SyscallSucceedsWithValue(kDataSize));
-
- // Close outf to avoid leak.
- outf.reset();
-
- // Open the output file as read only.
- outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDONLY));
-
- // Verify that the output file has the correct data.
- char actual[kDataSize];
- ASSERT_THAT(read(outf.get(), &actual, bytes_sent),
- SyscallSucceedsWithValue(kDataSize));
- EXPECT_EQ(kData, absl::string_view(actual, bytes_sent));
-}
-
-TEST(SendFileTest, SendToNotARegularFile) {
- // Make temp input directory and open as read only.
- const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor inf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_RDONLY));
-
- // Make temp output file and open as write only.
- const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const FileDescriptor outf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY));
-
- // Receive an error since a directory is not a regular file.
- EXPECT_THAT(sendfile(outf.get(), inf.get(), nullptr, 0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(SendFileTest, SendPipeWouldBlock) {
- // Create temp file.
- constexpr char kData[] =
- "The fool doth think he is wise, but the wise man knows himself to be a "
- "fool.";
- constexpr int kDataSize = sizeof(kData) - 1;
- const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode));
-
- // Open the input file as read only.
- const FileDescriptor inf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY));
-
- // Setup the output named pipe.
- int fds[2];
- ASSERT_THAT(pipe2(fds, O_NONBLOCK), SyscallSucceeds());
- const FileDescriptor rfd(fds[0]);
- const FileDescriptor wfd(fds[1]);
-
- // Fill up the pipe's buffer.
- int pipe_size = -1;
- ASSERT_THAT(pipe_size = fcntl(wfd.get(), F_GETPIPE_SZ), SyscallSucceeds());
- std::vector<char> buf(2 * pipe_size);
- ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(pipe_size));
-
- EXPECT_THAT(sendfile(wfd.get(), inf.get(), nullptr, kDataSize),
- SyscallFailsWithErrno(EWOULDBLOCK));
-}
-
-TEST(SendFileTest, SendPipeBlocks) {
- // Create temp file.
- constexpr char kData[] =
- "The fault, dear Brutus, is not in our stars, but in ourselves.";
- constexpr int kDataSize = sizeof(kData) - 1;
- const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode));
-
- // Open the input file as read only.
- const FileDescriptor inf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY));
-
- // Setup the output named pipe.
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
- const FileDescriptor rfd(fds[0]);
- const FileDescriptor wfd(fds[1]);
-
- // Fill up the pipe's buffer.
- int pipe_size = -1;
- ASSERT_THAT(pipe_size = fcntl(wfd.get(), F_GETPIPE_SZ), SyscallSucceeds());
- std::vector<char> buf(pipe_size);
- ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(pipe_size));
-
- ScopedThread t([&]() {
- absl::SleepFor(absl::Milliseconds(100));
- ASSERT_THAT(read(rfd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(pipe_size));
- });
-
- EXPECT_THAT(sendfile(wfd.get(), inf.get(), nullptr, kDataSize),
- SyscallSucceedsWithValue(kDataSize));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/sendfile_socket.cc b/test/syscalls/linux/sendfile_socket.cc
deleted file mode 100644
index 1c56540bc..000000000
--- a/test/syscalls/linux/sendfile_socket.cc
+++ /dev/null
@@ -1,242 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <arpa/inet.h>
-#include <netinet/in.h>
-#include <sys/sendfile.h>
-#include <sys/socket.h>
-#include <unistd.h>
-
-#include <iostream>
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "absl/strings/string_view.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-class SendFileTest : public ::testing::TestWithParam<int> {
- protected:
- PosixErrorOr<std::tuple<int, int>> Sockets() {
- // Bind a server socket.
- int family = GetParam();
- struct sockaddr server_addr = {};
- switch (family) {
- case AF_INET: {
- struct sockaddr_in *server_addr_in =
- reinterpret_cast<struct sockaddr_in *>(&server_addr);
- server_addr_in->sin_family = family;
- server_addr_in->sin_addr.s_addr = INADDR_ANY;
- break;
- }
- case AF_UNIX: {
- struct sockaddr_un *server_addr_un =
- reinterpret_cast<struct sockaddr_un *>(&server_addr);
- server_addr_un->sun_family = family;
- server_addr_un->sun_path[0] = '\0';
- break;
- }
- default:
- return PosixError(EINVAL);
- }
- int server = socket(family, SOCK_STREAM, 0);
- if (bind(server, &server_addr, sizeof(server_addr)) < 0) {
- return PosixError(errno);
- }
- if (listen(server, 1) < 0) {
- close(server);
- return PosixError(errno);
- }
-
- // Fetch the address; both are anonymous.
- socklen_t length = sizeof(server_addr);
- if (getsockname(server, &server_addr, &length) < 0) {
- close(server);
- return PosixError(errno);
- }
-
- // Connect the client.
- int client = socket(family, SOCK_STREAM, 0);
- if (connect(client, &server_addr, length) < 0) {
- close(server);
- close(client);
- return PosixError(errno);
- }
-
- // Accept on the server.
- int server_client = accept(server, nullptr, 0);
- if (server_client < 0) {
- close(server);
- close(client);
- return PosixError(errno);
- }
- close(server);
- return std::make_tuple(client, server_client);
- }
-};
-
-// Sends large file to exercise the path that read and writes data multiple
-// times, esp. when more data is read than can be written.
-TEST_P(SendFileTest, SendMultiple) {
- std::vector<char> data(5 * 1024 * 1024);
- RandomizeBuffer(data.data(), data.size());
-
- // Create temp files.
- const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), absl::string_view(data.data(), data.size()),
- TempPath::kDefaultFileMode));
- const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- // Create sockets.
- std::tuple<int, int> fds = ASSERT_NO_ERRNO_AND_VALUE(Sockets());
- const FileDescriptor server(std::get<0>(fds));
- FileDescriptor client(std::get<1>(fds)); // non-const, reset is used.
-
- // Thread that reads data from socket and dumps to a file.
- ScopedThread th([&] {
- FileDescriptor outf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY));
-
- // Read until socket is closed.
- char buf[10240];
- for (int cnt = 0;; cnt++) {
- int r = RetryEINTR(read)(server.get(), buf, sizeof(buf));
- // We cannot afford to save on every read() call.
- if (cnt % 1000 == 0) {
- ASSERT_THAT(r, SyscallSucceeds());
- } else {
- const DisableSave ds;
- ASSERT_THAT(r, SyscallSucceeds());
- }
- if (r == 0) {
- // EOF
- break;
- }
- int w = RetryEINTR(write)(outf.get(), buf, r);
- // We cannot afford to save on every write() call.
- if (cnt % 1010 == 0) {
- ASSERT_THAT(w, SyscallSucceedsWithValue(r));
- } else {
- const DisableSave ds;
- ASSERT_THAT(w, SyscallSucceedsWithValue(r));
- }
- }
- });
-
- // Open the input file as read only.
- const FileDescriptor inf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY));
-
- int cnt = 0;
- for (size_t sent = 0; sent < data.size(); cnt++) {
- const size_t remain = data.size() - sent;
- std::cout << "sendfile, size=" << data.size() << ", sent=" << sent
- << ", remain=" << remain;
-
- // Send data and verify that sendfile returns the correct value.
- int res = sendfile(client.get(), inf.get(), nullptr, remain);
- // We cannot afford to save on every sendfile() call.
- if (cnt % 120 == 0) {
- MaybeSave();
- }
- if (res == 0) {
- // EOF
- break;
- }
- if (res > 0) {
- sent += res;
- } else {
- ASSERT_TRUE(errno == EINTR || errno == EAGAIN) << "errno=" << errno;
- }
- }
-
- // Close socket to stop thread.
- client.reset();
- th.Join();
-
- // Verify that the output file has the correct data.
- const FileDescriptor outf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDONLY));
- std::vector<char> actual(data.size(), '\0');
- ASSERT_THAT(RetryEINTR(read)(outf.get(), actual.data(), actual.size()),
- SyscallSucceedsWithValue(actual.size()));
- ASSERT_EQ(memcmp(data.data(), actual.data(), data.size()), 0);
-}
-
-TEST_P(SendFileTest, Shutdown) {
- // Create a socket.
- std::tuple<int, int> fds = ASSERT_NO_ERRNO_AND_VALUE(Sockets());
- const FileDescriptor client(std::get<0>(fds));
- FileDescriptor server(std::get<1>(fds)); // non-const, released below.
-
- // If this is a TCP socket, then turn off linger.
- if (GetParam() == AF_INET) {
- struct linger sl;
- sl.l_onoff = 1;
- sl.l_linger = 0;
- ASSERT_THAT(
- setsockopt(server.get(), SOL_SOCKET, SO_LINGER, &sl, sizeof(sl)),
- SyscallSucceeds());
- }
-
- // Create a 1m file with random data.
- std::vector<char> data(1024 * 1024);
- RandomizeBuffer(data.data(), data.size());
- const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), absl::string_view(data.data(), data.size()),
- TempPath::kDefaultFileMode));
- const FileDescriptor inf =
- ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY));
-
- // Read some data, then shutdown the socket. We don't actually care about
- // checking the contents (other tests do that), so we just re-use the same
- // buffer as above.
- ScopedThread t([&]() {
- int done = 0;
- while (done < data.size()) {
- int n = read(server.get(), data.data(), data.size());
- ASSERT_THAT(n, SyscallSucceeds());
- done += n;
- }
- // Close the server side socket.
- ASSERT_THAT(close(server.release()), SyscallSucceeds());
- });
-
- // Continuously stream from the file to the socket. Note we do not assert
- // that a specific amount of data has been written at any time, just that some
- // data is written. Eventually, we should get a connection reset error.
- while (1) {
- off_t offset = 0; // Always read from the start.
- int n = sendfile(client.get(), inf.get(), &offset, data.size());
- EXPECT_THAT(n, AnyOf(SyscallFailsWithErrno(ECONNRESET),
- SyscallFailsWithErrno(EPIPE), SyscallSucceeds()));
- if (n <= 0) {
- break;
- }
- }
-}
-
-INSTANTIATE_TEST_SUITE_P(AddressFamily, SendFileTest,
- ::testing::Values(AF_UNIX, AF_INET));
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/shm.cc b/test/syscalls/linux/shm.cc
deleted file mode 100644
index eb7a3966f..000000000
--- a/test/syscalls/linux/shm.cc
+++ /dev/null
@@ -1,509 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <stdio.h>
-
-#include <sys/ipc.h>
-#include <sys/mman.h>
-#include <sys/shm.h>
-#include <sys/types.h>
-
-#include "absl/time/clock.h"
-#include "test/util/multiprocess_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-using ::testing::_;
-
-const uint64_t kAllocSize = kPageSize * 128ULL;
-
-PosixErrorOr<char*> Shmat(int shmid, const void* shmaddr, int shmflg) {
- const intptr_t addr =
- reinterpret_cast<intptr_t>(shmat(shmid, shmaddr, shmflg));
- if (addr == -1) {
- return PosixError(errno, "shmat() failed");
- }
- return reinterpret_cast<char*>(addr);
-}
-
-PosixError Shmdt(const char* shmaddr) {
- const int ret = shmdt(shmaddr);
- if (ret == -1) {
- return PosixError(errno, "shmdt() failed");
- }
- return NoError();
-}
-
-template <typename T>
-PosixErrorOr<int> Shmctl(int shmid, int cmd, T* buf) {
- int ret = shmctl(shmid, cmd, reinterpret_cast<struct shmid_ds*>(buf));
- if (ret == -1) {
- return PosixError(errno, "shmctl() failed");
- }
- return ret;
-}
-
-// ShmSegment is a RAII object for automatically cleaning up shm segments.
-class ShmSegment {
- public:
- explicit ShmSegment(int id) : id_(id) {}
-
- ~ShmSegment() {
- if (id_ >= 0) {
- EXPECT_NO_ERRNO(Rmid());
- id_ = -1;
- }
- }
-
- ShmSegment(ShmSegment&& other) : id_(other.release()) {}
-
- ShmSegment& operator=(ShmSegment&& other) {
- id_ = other.release();
- return *this;
- }
-
- ShmSegment(ShmSegment const& other) = delete;
- ShmSegment& operator=(ShmSegment const& other) = delete;
-
- int id() const { return id_; }
-
- int release() {
- int id = id_;
- id_ = -1;
- return id;
- }
-
- PosixErrorOr<int> Rmid() {
- RETURN_IF_ERRNO(Shmctl<void>(id_, IPC_RMID, nullptr));
- return release();
- }
-
- private:
- int id_ = -1;
-};
-
-PosixErrorOr<int> ShmgetRaw(key_t key, size_t size, int shmflg) {
- int id = shmget(key, size, shmflg);
- if (id == -1) {
- return PosixError(errno, "shmget() failed");
- }
- return id;
-}
-
-PosixErrorOr<ShmSegment> Shmget(key_t key, size_t size, int shmflg) {
- ASSIGN_OR_RETURN_ERRNO(int id, ShmgetRaw(key, size, shmflg));
- return ShmSegment(id);
-}
-
-TEST(ShmTest, AttachDetach) {
- const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
- Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
- struct shmid_ds attr;
- ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
- EXPECT_EQ(attr.shm_segsz, kAllocSize);
- EXPECT_EQ(attr.shm_nattch, 0);
-
- const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
- ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
- EXPECT_EQ(attr.shm_nattch, 1);
-
- const char* addr2 = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
- ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
- EXPECT_EQ(attr.shm_nattch, 2);
-
- ASSERT_NO_ERRNO(Shmdt(addr));
- ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
- EXPECT_EQ(attr.shm_nattch, 1);
-
- ASSERT_NO_ERRNO(Shmdt(addr2));
- ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
- EXPECT_EQ(attr.shm_nattch, 0);
-}
-
-TEST(ShmTest, LookupByKey) {
- const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const key_t key = ftok(keyfile.path().c_str(), 1);
- const ShmSegment shm =
- ASSERT_NO_ERRNO_AND_VALUE(Shmget(key, kAllocSize, IPC_CREAT | 0777));
- const int id2 = ASSERT_NO_ERRNO_AND_VALUE(ShmgetRaw(key, kAllocSize, 0777));
- EXPECT_EQ(shm.id(), id2);
-}
-
-TEST(ShmTest, DetachedSegmentsPersist) {
- const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
- Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
- char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
- addr[0] = 'x';
- ASSERT_NO_ERRNO(Shmdt(addr));
-
- // We should be able to re-attach to the same segment and get our data back.
- addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
- EXPECT_EQ(addr[0], 'x');
- ASSERT_NO_ERRNO(Shmdt(addr));
-}
-
-TEST(ShmTest, MultipleDetachFails) {
- const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
- Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
- const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
- ASSERT_NO_ERRNO(Shmdt(addr));
- EXPECT_THAT(Shmdt(addr), PosixErrorIs(EINVAL, _));
-}
-
-TEST(ShmTest, IpcStat) {
- const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const key_t key = ftok(keyfile.path().c_str(), 1);
-
- const time_t start = time(nullptr);
-
- const ShmSegment shm =
- ASSERT_NO_ERRNO_AND_VALUE(Shmget(key, kAllocSize, IPC_CREAT | 0777));
-
- const uid_t uid = getuid();
- const gid_t gid = getgid();
- const pid_t pid = getpid();
-
- struct shmid_ds attr;
- ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
-
- EXPECT_EQ(attr.shm_perm.__key, key);
- EXPECT_EQ(attr.shm_perm.uid, uid);
- EXPECT_EQ(attr.shm_perm.gid, gid);
- EXPECT_EQ(attr.shm_perm.cuid, uid);
- EXPECT_EQ(attr.shm_perm.cgid, gid);
- EXPECT_EQ(attr.shm_perm.mode, 0777);
-
- EXPECT_EQ(attr.shm_segsz, kAllocSize);
-
- EXPECT_EQ(attr.shm_atime, 0);
- EXPECT_EQ(attr.shm_dtime, 0);
-
- // Change time is set on creation.
- EXPECT_GE(attr.shm_ctime, start);
-
- EXPECT_EQ(attr.shm_cpid, pid);
- EXPECT_EQ(attr.shm_lpid, 0);
-
- EXPECT_EQ(attr.shm_nattch, 0);
-
- // The timestamps only have a resolution of seconds; slow down so we actually
- // see the timestamps change.
- absl::SleepFor(absl::Seconds(1));
- const time_t pre_attach = time(nullptr);
-
- const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
- ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
-
- EXPECT_GE(attr.shm_atime, pre_attach);
- EXPECT_EQ(attr.shm_dtime, 0);
- EXPECT_LT(attr.shm_ctime, pre_attach);
- EXPECT_EQ(attr.shm_lpid, pid);
- EXPECT_EQ(attr.shm_nattch, 1);
-
- absl::SleepFor(absl::Seconds(1));
- const time_t pre_detach = time(nullptr);
-
- ASSERT_NO_ERRNO(Shmdt(addr));
- ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
-
- EXPECT_LT(attr.shm_atime, pre_detach);
- EXPECT_GE(attr.shm_dtime, pre_detach);
- EXPECT_LT(attr.shm_ctime, pre_detach);
- EXPECT_EQ(attr.shm_lpid, pid);
- EXPECT_EQ(attr.shm_nattch, 0);
-}
-
-TEST(ShmTest, ShmStat) {
- // This test relies on the segment we create to be the first one on the
- // system, causing it to occupy slot 1. We can't reasonably expect this on a
- // general Linux host.
- SKIP_IF(!IsRunningOnGvisor());
-
- const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
- Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
- struct shmid_ds attr;
- ASSERT_NO_ERRNO(Shmctl(1, SHM_STAT, &attr));
- // This does the same thing as IPC_STAT, so only test that the syscall
- // succeeds here.
-}
-
-TEST(ShmTest, IpcInfo) {
- struct shminfo info;
- ASSERT_NO_ERRNO(Shmctl(0, IPC_INFO, &info));
-
- EXPECT_EQ(info.shmmin, 1); // This is always 1, according to the man page.
- EXPECT_GT(info.shmmax, info.shmmin);
- EXPECT_GT(info.shmmni, 0);
- EXPECT_GT(info.shmseg, 0);
- EXPECT_GT(info.shmall, 0);
-}
-
-TEST(ShmTest, ShmInfo) {
- struct shm_info info;
-
- // We generally can't know what other processes on a linux machine
- // does with shared memory segments, so we can't test specific
- // numbers on Linux. When running under gvisor, we're guaranteed to
- // be the only ones using shm, so we can easily verify machine-wide
- // numbers.
- if (IsRunningOnGvisor()) {
- ASSERT_NO_ERRNO(Shmctl(0, SHM_INFO, &info));
- EXPECT_EQ(info.used_ids, 0);
- EXPECT_EQ(info.shm_tot, 0);
- EXPECT_EQ(info.shm_rss, 0);
- EXPECT_EQ(info.shm_swp, 0);
- }
-
- const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
- Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
- const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
-
- ASSERT_NO_ERRNO(Shmctl(1, SHM_INFO, &info));
-
- if (IsRunningOnGvisor()) {
- ASSERT_NO_ERRNO(Shmctl(shm.id(), SHM_INFO, &info));
- EXPECT_EQ(info.used_ids, 1);
- EXPECT_EQ(info.shm_tot, kAllocSize / kPageSize);
- EXPECT_EQ(info.shm_rss, kAllocSize / kPageSize);
- EXPECT_EQ(info.shm_swp, 0); // Gvisor currently never swaps.
- }
-
- ASSERT_NO_ERRNO(Shmdt(addr));
-}
-
-TEST(ShmTest, ShmCtlSet) {
- const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
- Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
- const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
-
- struct shmid_ds attr;
- ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
- ASSERT_EQ(attr.shm_perm.mode, 0777);
-
- attr.shm_perm.mode = 0766;
- ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_SET, &attr));
-
- ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr));
- ASSERT_EQ(attr.shm_perm.mode, 0766);
-
- ASSERT_NO_ERRNO(Shmdt(addr));
-}
-
-TEST(ShmTest, RemovedSegmentsAreMarkedDeleted) {
- ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
- Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
- const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
- const int id = ASSERT_NO_ERRNO_AND_VALUE(shm.Rmid());
- struct shmid_ds attr;
- ASSERT_NO_ERRNO(Shmctl(id, IPC_STAT, &attr));
- EXPECT_NE(attr.shm_perm.mode & SHM_DEST, 0);
- ASSERT_NO_ERRNO(Shmdt(addr));
-}
-
-TEST(ShmTest, RemovedSegmentsAreDestroyed) {
- ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
- Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
- const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
-
- const uint64_t alloc_pages = kAllocSize / kPageSize;
-
- struct shm_info info;
- ASSERT_NO_ERRNO(Shmctl(0 /*ignored*/, SHM_INFO, &info));
- const uint64_t before = info.shm_tot;
-
- ASSERT_NO_ERRNO(shm.Rmid());
- ASSERT_NO_ERRNO(Shmdt(addr));
-
- ASSERT_NO_ERRNO(Shmctl(0 /*ignored*/, SHM_INFO, &info));
- if (IsRunningOnGvisor()) {
- // No guarantees on system-wide shm memory usage on a generic linux host.
- const uint64_t after = info.shm_tot;
- EXPECT_EQ(after, before - alloc_pages);
- }
-}
-
-TEST(ShmTest, AllowsAttachToRemovedSegmentWithRefs) {
- ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
- Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
- const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
- const int id = ASSERT_NO_ERRNO_AND_VALUE(shm.Rmid());
- const char* addr2 = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id, nullptr, 0));
- ASSERT_NO_ERRNO(Shmdt(addr));
- ASSERT_NO_ERRNO(Shmdt(addr2));
-}
-
-TEST(ShmTest, RemovedSegmentsAreNotDiscoverable) {
- const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const key_t key = ftok(keyfile.path().c_str(), 1);
- ShmSegment shm =
- ASSERT_NO_ERRNO_AND_VALUE(Shmget(key, kAllocSize, IPC_CREAT | 0777));
- ASSERT_NO_ERRNO(shm.Rmid());
- EXPECT_THAT(Shmget(key, kAllocSize, 0777), PosixErrorIs(ENOENT, _));
-}
-
-TEST(ShmDeathTest, ReadonlySegment) {
- SetupGvisorDeathTest();
- const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
- Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
- char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, SHM_RDONLY));
- // Reading succeeds.
- static_cast<void>(addr[0]);
- // Writing fails.
- EXPECT_EXIT(addr[0] = 'x', ::testing::KilledBySignal(SIGSEGV), "");
-}
-
-TEST(ShmDeathTest, SegmentNotAccessibleAfterDetach) {
- // This test is susceptible to races with concurrent mmaps running in parallel
- // gtest threads since the test relies on the address freed during a shm
- // segment destruction to remain unused. We run the test body in a forked
- // child to guarantee a single-threaded context to avoid this.
-
- SetupGvisorDeathTest();
-
- const auto rest = [&] {
- ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
- Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
- char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
-
- // Mark the segment as destroyed so it's automatically cleaned up when we
- // crash below. We can't rely on the standard cleanup since the destructor
- // will not run after the SIGSEGV. Note that this doesn't destroy the
- // segment immediately since we're still attached to it.
- ASSERT_NO_ERRNO(shm.Rmid());
-
- addr[0] = 'x';
- ASSERT_NO_ERRNO(Shmdt(addr));
-
- // This access should cause a SIGSEGV.
- addr[0] = 'x';
- };
-
- EXPECT_THAT(InForkedProcess(rest),
- IsPosixErrorOkAndHolds(W_EXITCODE(0, SIGSEGV)));
-}
-
-TEST(ShmTest, RequestingSegmentSmallerThanSHMMINFails) {
- struct shminfo info;
- ASSERT_NO_ERRNO(Shmctl(0, IPC_INFO, &info));
- const uint64_t size = info.shmmin - 1;
- EXPECT_THAT(Shmget(IPC_PRIVATE, size, IPC_CREAT | 0777),
- PosixErrorIs(EINVAL, _));
-}
-
-TEST(ShmTest, RequestingSegmentLargerThanSHMMAXFails) {
- struct shminfo info;
- ASSERT_NO_ERRNO(Shmctl(0, IPC_INFO, &info));
- const uint64_t size = info.shmmax + kPageSize;
- EXPECT_THAT(Shmget(IPC_PRIVATE, size, IPC_CREAT | 0777),
- PosixErrorIs(EINVAL, _));
-}
-
-TEST(ShmTest, RequestingUnalignedSizeSucceeds) {
- EXPECT_NO_ERRNO(Shmget(IPC_PRIVATE, 4097, IPC_CREAT | 0777));
-}
-
-TEST(ShmTest, RequestingDuplicateCreationFails) {
- const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const key_t key = ftok(keyfile.path().c_str(), 1);
- const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
- Shmget(key, kAllocSize, IPC_CREAT | IPC_EXCL | 0777));
- EXPECT_THAT(Shmget(key, kAllocSize, IPC_CREAT | IPC_EXCL | 0777),
- PosixErrorIs(EEXIST, _));
-}
-
-TEST(ShmTest, NonExistentSegmentsAreNotFound) {
- const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const key_t key = ftok(keyfile.path().c_str(), 1);
- // Do not request creation.
- EXPECT_THAT(Shmget(key, kAllocSize, 0777), PosixErrorIs(ENOENT, _));
-}
-
-TEST(ShmTest, SegmentsSizeFixedOnCreation) {
- const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const key_t key = ftok(keyfile.path().c_str(), 1);
-
- // Base segment.
- const ShmSegment shm =
- ASSERT_NO_ERRNO_AND_VALUE(Shmget(key, kAllocSize, IPC_CREAT | 0777));
-
- // Ask for the same segment at half size. This succeeds.
- const int id2 =
- ASSERT_NO_ERRNO_AND_VALUE(ShmgetRaw(key, kAllocSize / 2, 0777));
-
- // Ask for the same segment at double size.
- EXPECT_THAT(Shmget(key, kAllocSize * 2, 0777), PosixErrorIs(EINVAL, _));
-
- char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
- char* addr2 = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id2, nullptr, 0));
-
- // We have 2 different maps...
- EXPECT_NE(addr, addr2);
-
- // ... And both maps are kAllocSize bytes; despite asking for a half-sized
- // segment for the second map.
- addr[kAllocSize - 1] = 'x';
- addr2[kAllocSize - 1] = 'x';
-
- ASSERT_NO_ERRNO(Shmdt(addr));
- ASSERT_NO_ERRNO(Shmdt(addr2));
-}
-
-TEST(ShmTest, PartialUnmap) {
- const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
- Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
- char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
- EXPECT_THAT(munmap(addr + (kAllocSize / 4), kAllocSize / 2),
- SyscallSucceeds());
- ASSERT_NO_ERRNO(Shmdt(addr));
-}
-
-// Check that sentry does not panic when asked for a zero-length private shm
-// segment.
-TEST(ShmTest, GracefullyFailOnZeroLenSegmentCreation) {
- EXPECT_THAT(Shmget(IPC_PRIVATE, 0, 0), PosixErrorIs(EINVAL, _));
-}
-
-TEST(ShmTest, NoDestructionOfAttachedSegmentWithMultipleRmid) {
- ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
- Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
- char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
- char* addr2 = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
-
- // There should be 2 refs to the segment from the 2 attachments, and a single
- // self-reference. Mark the segment as destroyed more than 3 times through
- // shmctl(RMID). If there's a bug with the ref counting, this should cause the
- // count to drop to zero.
- int id = shm.release();
- for (int i = 0; i < 6; ++i) {
- ASSERT_NO_ERRNO(Shmctl<void>(id, IPC_RMID, nullptr));
- }
-
- // Segment should remain accessible.
- addr[0] = 'x';
- ASSERT_NO_ERRNO(Shmdt(addr));
-
- // Segment should remain accessible even after one of the two attachments are
- // detached.
- addr2[0] = 'x';
- ASSERT_NO_ERRNO(Shmdt(addr2));
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/sigaction.cc b/test/syscalls/linux/sigaction.cc
deleted file mode 100644
index 9a53fd3e0..000000000
--- a/test/syscalls/linux/sigaction.cc
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <signal.h>
-
-#include "gtest/gtest.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(SigactionTest, GetLessThanOrEqualToZeroFails) {
- struct sigaction act;
- memset(&act, 0, sizeof(act));
- ASSERT_THAT(sigaction(-1, NULL, &act), SyscallFailsWithErrno(EINVAL));
- ASSERT_THAT(sigaction(0, NULL, &act), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(SigactionTest, SetLessThanOrEqualToZeroFails) {
- struct sigaction act;
- memset(&act, 0, sizeof(act));
- ASSERT_THAT(sigaction(0, &act, NULL), SyscallFailsWithErrno(EINVAL));
- ASSERT_THAT(sigaction(0, &act, NULL), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(SigactionTest, GetGreaterThanMaxFails) {
- struct sigaction act;
- memset(&act, 0, sizeof(act));
- ASSERT_THAT(sigaction(SIGRTMAX + 1, NULL, &act),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(SigactionTest, SetGreaterThanMaxFails) {
- struct sigaction act;
- memset(&act, 0, sizeof(act));
- ASSERT_THAT(sigaction(SIGRTMAX + 1, &act, NULL),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(SigactionTest, SetSigkillFails) {
- struct sigaction act;
- memset(&act, 0, sizeof(act));
- ASSERT_THAT(sigaction(SIGKILL, NULL, &act), SyscallSucceeds());
- ASSERT_THAT(sigaction(SIGKILL, &act, NULL), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(SigactionTest, SetSigstopFails) {
- struct sigaction act;
- memset(&act, 0, sizeof(act));
- ASSERT_THAT(sigaction(SIGSTOP, NULL, &act), SyscallSucceeds());
- ASSERT_THAT(sigaction(SIGSTOP, &act, NULL), SyscallFailsWithErrno(EINVAL));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/sigaltstack.cc b/test/syscalls/linux/sigaltstack.cc
deleted file mode 100644
index 69b6e4f90..000000000
--- a/test/syscalls/linux/sigaltstack.cc
+++ /dev/null
@@ -1,275 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <signal.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <functional>
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/util/cleanup.h"
-#include "test/util/fs_util.h"
-#include "test/util/multiprocess_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/signal_util.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-PosixErrorOr<Cleanup> ScopedSigaltstack(stack_t const& stack) {
- stack_t old_stack;
- int rc = sigaltstack(&stack, &old_stack);
- MaybeSave();
- if (rc < 0) {
- return PosixError(errno, "sigaltstack failed");
- }
- return Cleanup([old_stack] {
- EXPECT_THAT(sigaltstack(&old_stack, nullptr), SyscallSucceeds());
- });
-}
-
-volatile bool got_signal = false;
-volatile int sigaltstack_errno = 0;
-volatile int ss_flags = 0;
-
-void sigaltstack_handler(int sig, siginfo_t* siginfo, void* arg) {
- got_signal = true;
-
- stack_t stack;
- int ret = sigaltstack(nullptr, &stack);
- MaybeSave();
- if (ret < 0) {
- sigaltstack_errno = errno;
- return;
- }
- ss_flags = stack.ss_flags;
-}
-
-TEST(SigaltstackTest, Success) {
- std::vector<char> stack_mem(SIGSTKSZ);
- stack_t stack = {};
- stack.ss_sp = stack_mem.data();
- stack.ss_size = stack_mem.size();
- auto const cleanup_sigstack =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaltstack(stack));
-
- struct sigaction sa = {};
- sa.sa_sigaction = sigaltstack_handler;
- sigfillset(&sa.sa_mask);
- sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
- auto const cleanup_sa =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGUSR1, sa));
-
- // Send signal to this thread, as sigaltstack is per-thread.
- EXPECT_THAT(tgkill(getpid(), gettid(), SIGUSR1), SyscallSucceeds());
-
- EXPECT_TRUE(got_signal);
- EXPECT_EQ(sigaltstack_errno, 0);
- EXPECT_NE(0, ss_flags & SS_ONSTACK);
-}
-
-TEST(SigaltstackTest, ResetByExecve) {
- std::vector<char> stack_mem(SIGSTKSZ);
- stack_t stack = {};
- stack.ss_sp = stack_mem.data();
- stack.ss_size = stack_mem.size();
- auto const cleanup_sigstack =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaltstack(stack));
-
- std::string full_path;
- char* test_src = getenv("TEST_SRCDIR");
- if (test_src) {
- full_path = JoinPath(test_src, "../../linux/sigaltstack_check");
- }
-
- ASSERT_FALSE(full_path.empty());
-
- pid_t child_pid = -1;
- int execve_errno = 0;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(full_path, {"sigaltstack_check"}, {}, nullptr, &child_pid,
- &execve_errno));
-
- ASSERT_GT(child_pid, 0);
- ASSERT_EQ(execve_errno, 0);
-
- int status = 0;
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
- ASSERT_TRUE(WIFEXITED(status));
- ASSERT_EQ(WEXITSTATUS(status), 0);
-}
-
-volatile bool badhandler_on_sigaltstack = true; // Set by the handler.
-char* volatile badhandler_low_water_mark = nullptr; // Set by the handler.
-volatile uint8_t badhandler_recursive_faults = 0; // Consumed by the handler.
-
-void badhandler(int sig, siginfo_t* siginfo, void* arg) {
- char stack_var = 0;
- char* current_ss = &stack_var;
-
- stack_t stack;
- int ret = sigaltstack(nullptr, &stack);
- if (ret < 0 || (stack.ss_flags & SS_ONSTACK) != SS_ONSTACK) {
- // We should always be marked as being on the stack. Don't allow this to hit
- // the bottom if this is ever not true (the main test will fail as a
- // result, but we still need to unwind the recursive faults).
- badhandler_on_sigaltstack = false;
- }
- if (current_ss < badhandler_low_water_mark) {
- // Record the low point for the signal stack. We never expected this to be
- // before stack bottom, but this is asserted in the actual test.
- badhandler_low_water_mark = current_ss;
- }
- if (badhandler_recursive_faults > 0) {
- badhandler_recursive_faults--;
- Fault();
- }
- FixupFault(reinterpret_cast<ucontext_t*>(arg));
-}
-
-TEST(SigaltstackTest, WalksOffBottom) {
- // This test marks the upper half of the stack_mem array as the signal stack.
- // It asserts that when a fault occurs in the handler (already on the signal
- // stack), we eventually continue to fault our way off the stack. We should
- // not revert to the top of the signal stack when we fall off the bottom and
- // the signal stack should remain "in use". When we fall off the signal stack,
- // we should have an unconditional signal delivered and not start using the
- // first part of the stack_mem array.
- std::vector<char> stack_mem(SIGSTKSZ * 2);
- stack_t stack = {};
- stack.ss_sp = stack_mem.data() + SIGSTKSZ; // See above: upper half.
- stack.ss_size = SIGSTKSZ; // Only one half the array.
- auto const cleanup_sigstack =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaltstack(stack));
-
- // Setup the handler: this must be for SIGSEGV, and it must allow proper
- // nesting (no signal mask, no defer) so that we can trigger multiple times.
- //
- // When we walk off the bottom of the signal stack and force signal delivery
- // of a SIGSEGV, the handler will revert to the default behavior (kill).
- struct sigaction sa = {};
- sa.sa_sigaction = badhandler;
- sa.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_NODEFER;
- auto const cleanup_sa =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGSEGV, sa));
-
- // Trigger a single fault.
- badhandler_low_water_mark =
- static_cast<char*>(stack.ss_sp) + SIGSTKSZ; // Expected top.
- badhandler_recursive_faults = 0; // Disable refault.
- Fault();
- EXPECT_TRUE(badhandler_on_sigaltstack);
- EXPECT_THAT(sigaltstack(nullptr, &stack), SyscallSucceeds());
- EXPECT_EQ(stack.ss_flags & SS_ONSTACK, 0);
- EXPECT_LT(badhandler_low_water_mark,
- reinterpret_cast<char*>(stack.ss_sp) + 2 * SIGSTKSZ);
- EXPECT_GT(badhandler_low_water_mark, reinterpret_cast<char*>(stack.ss_sp));
-
- // Trigger two faults.
- char* prev_low_water_mark = badhandler_low_water_mark; // Previous top.
- badhandler_recursive_faults = 1; // One refault.
- Fault();
- ASSERT_TRUE(badhandler_on_sigaltstack);
- EXPECT_THAT(sigaltstack(nullptr, &stack), SyscallSucceeds());
- EXPECT_EQ(stack.ss_flags & SS_ONSTACK, 0);
- EXPECT_LT(badhandler_low_water_mark, prev_low_water_mark);
- EXPECT_GT(badhandler_low_water_mark, reinterpret_cast<char*>(stack.ss_sp));
-
- // Calculate the stack growth for a fault, and set the recursive faults to
- // ensure that the signal handler stack required exceeds our marked stack area
- // by a minimal amount. It should remain in the valid stack_mem area so that
- // we can test the signal is forced merely by going out of the signal stack
- // bounds, not by a genuine fault.
- uintptr_t frame_size =
- static_cast<uintptr_t>(prev_low_water_mark - badhandler_low_water_mark);
- badhandler_recursive_faults = (SIGSTKSZ + frame_size) / frame_size;
- EXPECT_EXIT(Fault(), ::testing::KilledBySignal(SIGSEGV), "");
-}
-
-volatile int setonstack_retval = 0; // Set by the handler.
-volatile int setonstack_errno = 0; // Set by the handler.
-
-void setonstack(int sig, siginfo_t* siginfo, void* arg) {
- char stack_mem[SIGSTKSZ];
- stack_t stack = {};
- stack.ss_sp = &stack_mem[0];
- stack.ss_size = SIGSTKSZ;
- setonstack_retval = sigaltstack(&stack, nullptr);
- setonstack_errno = errno;
- FixupFault(reinterpret_cast<ucontext_t*>(arg));
-}
-
-TEST(SigaltstackTest, SetWhileOnStack) {
- // Reserve twice as much stack here, since the handler will allocate a vector
- // of size SIGTKSZ and attempt to set the sigaltstack to that value.
- std::vector<char> stack_mem(2 * SIGSTKSZ);
- stack_t stack = {};
- stack.ss_sp = stack_mem.data();
- stack.ss_size = stack_mem.size();
- auto const cleanup_sigstack =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaltstack(stack));
-
- // See above.
- struct sigaction sa = {};
- sa.sa_sigaction = setonstack;
- sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
- auto const cleanup_sa =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGSEGV, sa));
-
- // Trigger a fault.
- Fault();
-
- // The set should have failed.
- EXPECT_EQ(setonstack_retval, -1);
- EXPECT_EQ(setonstack_errno, EPERM);
-}
-
-TEST(SigaltstackTest, SetCurrentStack) {
- // This is executed as an exit test because once the signal stack is set to
- // the local stack, there's no good way to unwind. We don't want to taint the
- // test of any other tests that might run within this process.
- EXPECT_EXIT(
- {
- char stack_value = 0;
- stack_t stack = {};
- stack.ss_sp = &stack_value - kPageSize; // Lower than current level.
- stack.ss_size = 2 * kPageSize; // => &stack_value +/- kPageSize.
- TEST_CHECK(sigaltstack(&stack, nullptr) == 0);
- TEST_CHECK(sigaltstack(nullptr, &stack) == 0);
- TEST_CHECK((stack.ss_flags & SS_ONSTACK) != 0);
-
- // Should not be able to change the stack (even no-op).
- TEST_CHECK(sigaltstack(&stack, nullptr) == -1 && errno == EPERM);
-
- // Should not be able to disable the stack.
- stack.ss_flags = SS_DISABLE;
- TEST_CHECK(sigaltstack(&stack, nullptr) == -1 && errno == EPERM);
- exit(0);
- },
- ::testing::ExitedWithCode(0), "");
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/sigaltstack_check.cc b/test/syscalls/linux/sigaltstack_check.cc
deleted file mode 100644
index 5ac1b661d..000000000
--- a/test/syscalls/linux/sigaltstack_check.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Checks that there is no alternate signal stack by default.
-//
-// Used by a test in sigaltstack.cc.
-#include <errno.h>
-#include <signal.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-
-#include "test/util/logging.h"
-
-int main(int /* argc */, char** /* argv */) {
- stack_t stack;
- TEST_CHECK(sigaltstack(nullptr, &stack) >= 0);
- TEST_CHECK(stack.ss_flags == SS_DISABLE);
- TEST_CHECK(stack.ss_sp == 0);
- TEST_CHECK(stack.ss_size == 0);
- return 0;
-}
diff --git a/test/syscalls/linux/sigiret.cc b/test/syscalls/linux/sigiret.cc
deleted file mode 100644
index a47c781ea..000000000
--- a/test/syscalls/linux/sigiret.cc
+++ /dev/null
@@ -1,137 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <signal.h>
-#include <sys/types.h>
-#include <sys/ucontext.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "test/util/logging.h"
-#include "test/util/signal_util.h"
-#include "test/util/test_util.h"
-#include "test/util/timer_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-constexpr uint64_t kOrigRcx = 0xdeadbeeffacefeed;
-constexpr uint64_t kOrigR11 = 0xfacefeedbaad1dea;
-
-volatile int gotvtalrm, ready;
-
-void sigvtalrm(int sig, siginfo_t* siginfo, void* _uc) {
- ucontext_t* uc = reinterpret_cast<ucontext_t*>(_uc);
-
- // Verify that:
- // - test is in the busy-wait loop waiting for signal.
- // - %rcx and %r11 values in mcontext_t match kOrigRcx and kOrigR11.
- if (ready &&
- static_cast<uint64_t>(uc->uc_mcontext.gregs[REG_RCX]) == kOrigRcx &&
- static_cast<uint64_t>(uc->uc_mcontext.gregs[REG_R11]) == kOrigR11) {
- // Modify the values %rcx and %r11 in the ucontext. These are the
- // values seen by the application after the signal handler returns.
- uc->uc_mcontext.gregs[REG_RCX] = ~kOrigRcx;
- uc->uc_mcontext.gregs[REG_R11] = ~kOrigR11;
- gotvtalrm = 1;
- }
-}
-
-TEST(SigIretTest, CheckRcxR11) {
- // Setup signal handler for SIGVTALRM.
- struct sigaction sa = {};
- sigfillset(&sa.sa_mask);
- sa.sa_sigaction = sigvtalrm;
- sa.sa_flags = SA_SIGINFO;
- auto const action_cleanup =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGVTALRM, sa));
-
- auto const mask_cleanup =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGVTALRM));
-
- // Setup itimer to fire after 500 msecs.
- struct itimerval itimer = {};
- itimer.it_value.tv_usec = 500 * 1000; // 500 msecs.
- auto const timer_cleanup =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedItimer(ITIMER_VIRTUAL, itimer));
-
- // Initialize %rcx and %r11 and spin until the signal handler returns.
- uint64_t rcx = kOrigRcx;
- uint64_t r11 = kOrigR11;
- asm volatile(
- "movq %[rcx], %%rcx;" // %rcx = rcx
- "movq %[r11], %%r11;" // %r11 = r11
- "movl $1, %[ready];" // ready = 1
- "1: pause; cmpl $0, %[gotvtalrm]; je 1b;" // while (!gotvtalrm);
- "movq %%rcx, %[rcx];" // rcx = %rcx
- "movq %%r11, %[r11];" // r11 = %r11
- : [ready] "=m"(ready), [rcx] "+m"(rcx), [r11] "+m"(r11)
- : [gotvtalrm] "m"(gotvtalrm)
- : "cc", "memory", "rcx", "r11");
-
- // If sigreturn(2) returns via 'sysret' then %rcx and %r11 will be
- // clobbered and set to 'ptregs->rip' and 'ptregs->rflags' respectively.
- //
- // The following check verifies that %rcx and %r11 were not clobbered
- // when returning from the signal handler (via sigreturn(2)).
- EXPECT_EQ(rcx, ~kOrigRcx);
- EXPECT_EQ(r11, ~kOrigR11);
-}
-
-constexpr uint64_t kNonCanonicalRip = 0xCCCC000000000000;
-
-// Test that a non-canonical signal handler faults as expected.
-TEST(SigIretTest, BadHandler) {
- struct sigaction sa = {};
- sa.sa_sigaction =
- reinterpret_cast<void (*)(int, siginfo_t*, void*)>(kNonCanonicalRip);
- auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGUSR1, sa));
-
- pid_t pid = fork();
- if (pid == 0) {
- // Child, wait for signal.
- while (1) {
- pause();
- }
- }
- ASSERT_THAT(pid, SyscallSucceeds());
-
- EXPECT_THAT(kill(pid, SIGUSR1), SyscallSucceeds());
-
- int status;
- EXPECT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV)
- << "status = " << status;
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
-
-int main(int argc, char** argv) {
- // SigIretTest.CheckRcxR11 depends on delivering SIGVTALRM to the main thread.
- // Block SIGVTALRM so that any other threads created by TestInit will also
- // have SIGVTALRM blocked.
- sigset_t set;
- sigemptyset(&set);
- sigaddset(&set, SIGVTALRM);
- TEST_PCHECK(sigprocmask(SIG_BLOCK, &set, nullptr) == 0);
-
- gvisor::testing::TestInit(&argc, &argv);
-
- return RUN_ALL_TESTS();
-}
diff --git a/test/syscalls/linux/sigprocmask.cc b/test/syscalls/linux/sigprocmask.cc
deleted file mode 100644
index 654c6a47f..000000000
--- a/test/syscalls/linux/sigprocmask.cc
+++ /dev/null
@@ -1,269 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <signal.h>
-#include <stddef.h>
-#include <sys/syscall.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "test/util/signal_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// Signals numbers used for testing.
-static constexpr int kTestSignal1 = SIGUSR1;
-static constexpr int kTestSignal2 = SIGUSR2;
-
-static int raw_sigprocmask(int how, const sigset_t* set, sigset_t* oldset) {
- return syscall(SYS_rt_sigprocmask, how, set, oldset, _NSIG / 8);
-}
-
-// count of the number of signals received
-int signal_count[kMaxSignal + 1];
-
-// signal handler increments the signal counter
-void SigHandler(int sig, siginfo_t* info, void* context) {
- TEST_CHECK(sig > 0 && sig <= kMaxSignal);
- signal_count[sig] += 1;
-}
-
-// The test fixture saves and restores the signal mask and
-// sets up handlers for kTestSignal1 and kTestSignal2.
-class SigProcMaskTest : public ::testing::Test {
- protected:
- void SetUp() override {
- // Save the current signal mask.
- EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, nullptr, &mask_),
- SyscallSucceeds());
-
- // Setup signal handlers for kTestSignal1 and kTestSignal2.
- struct sigaction sa;
- sa.sa_sigaction = SigHandler;
- sigfillset(&sa.sa_mask);
- sa.sa_flags = SA_SIGINFO;
- EXPECT_THAT(sigaction(kTestSignal1, &sa, &sa_test_sig_1_),
- SyscallSucceeds());
- EXPECT_THAT(sigaction(kTestSignal2, &sa, &sa_test_sig_2_),
- SyscallSucceeds());
-
- // Clear the signal counters.
- memset(signal_count, 0, sizeof(signal_count));
- }
-
- void TearDown() override {
- // Restore the signal mask.
- EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, &mask_, nullptr),
- SyscallSucceeds());
-
- // Restore the signal handlers for kTestSignal1 and kTestSignal2.
- EXPECT_THAT(sigaction(kTestSignal1, &sa_test_sig_1_, nullptr),
- SyscallSucceeds());
- EXPECT_THAT(sigaction(kTestSignal2, &sa_test_sig_2_, nullptr),
- SyscallSucceeds());
- }
-
- private:
- sigset_t mask_;
- struct sigaction sa_test_sig_1_;
- struct sigaction sa_test_sig_2_;
-};
-
-// Both sigsets nullptr should succeed and do nothing.
-TEST_F(SigProcMaskTest, NullAddress) {
- EXPECT_THAT(raw_sigprocmask(SIG_BLOCK, nullptr, NULL), SyscallSucceeds());
- EXPECT_THAT(raw_sigprocmask(SIG_UNBLOCK, nullptr, NULL), SyscallSucceeds());
- EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, nullptr, NULL), SyscallSucceeds());
-}
-
-// Bad address for either sigset should fail with EFAULT.
-TEST_F(SigProcMaskTest, BadAddress) {
- sigset_t* bad_addr = reinterpret_cast<sigset_t*>(-1);
-
- EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, bad_addr, nullptr),
- SyscallFailsWithErrno(EFAULT));
-
- EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, nullptr, bad_addr),
- SyscallFailsWithErrno(EFAULT));
-}
-
-// Bad value of the "how" parameter should fail with EINVAL.
-TEST_F(SigProcMaskTest, BadParameter) {
- int bad_param_1 = -1;
- int bad_param_2 = 42;
-
- sigset_t set1;
- sigemptyset(&set1);
-
- EXPECT_THAT(raw_sigprocmask(bad_param_1, &set1, nullptr),
- SyscallFailsWithErrno(EINVAL));
-
- EXPECT_THAT(raw_sigprocmask(bad_param_2, &set1, nullptr),
- SyscallFailsWithErrno(EINVAL));
-}
-
-// Check that we can get the current signal mask.
-TEST_F(SigProcMaskTest, GetMask) {
- sigset_t set1;
- sigset_t set2;
-
- sigemptyset(&set1);
- sigfillset(&set2);
- EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, nullptr, &set1), SyscallSucceeds());
- EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, nullptr, &set2), SyscallSucceeds());
- EXPECT_THAT(set1, EqualsSigset(set2));
-}
-
-// Check that we can set the signal mask.
-TEST_F(SigProcMaskTest, SetMask) {
- sigset_t actual;
- sigset_t expected;
-
- // Try to mask all signals
- sigfillset(&expected);
- EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, &expected, nullptr),
- SyscallSucceeds());
- EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, nullptr, &actual),
- SyscallSucceeds());
- // sigprocmask() should have silently ignored SIGKILL and SIGSTOP.
- sigdelset(&expected, SIGSTOP);
- sigdelset(&expected, SIGKILL);
- EXPECT_THAT(actual, EqualsSigset(expected));
-
- // Try to clear the signal mask
- sigemptyset(&expected);
- EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, &expected, nullptr),
- SyscallSucceeds());
- EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, nullptr, &actual),
- SyscallSucceeds());
- EXPECT_THAT(actual, EqualsSigset(expected));
-
- // Try to set a mask with one signal.
- sigemptyset(&expected);
- sigaddset(&expected, kTestSignal1);
- EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, &expected, nullptr),
- SyscallSucceeds());
- EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, nullptr, &actual),
- SyscallSucceeds());
- EXPECT_THAT(actual, EqualsSigset(expected));
-}
-
-// Check that we can add and remove signals.
-TEST_F(SigProcMaskTest, BlockUnblock) {
- sigset_t actual;
- sigset_t expected;
-
- // Try to set a mask with one signal.
- sigemptyset(&expected);
- sigaddset(&expected, kTestSignal1);
- EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, &expected, nullptr),
- SyscallSucceeds());
- EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, nullptr, &actual),
- SyscallSucceeds());
- EXPECT_THAT(actual, EqualsSigset(expected));
-
- // Try to add another signal.
- sigset_t block;
- sigemptyset(&block);
- sigaddset(&block, kTestSignal2);
- EXPECT_THAT(raw_sigprocmask(SIG_BLOCK, &block, nullptr), SyscallSucceeds());
- EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, nullptr, &actual),
- SyscallSucceeds());
- sigaddset(&expected, kTestSignal2);
- EXPECT_THAT(actual, EqualsSigset(expected));
-
- // Try to remove a signal.
- sigset_t unblock;
- sigemptyset(&unblock);
- sigaddset(&unblock, kTestSignal1);
- EXPECT_THAT(raw_sigprocmask(SIG_UNBLOCK, &unblock, nullptr),
- SyscallSucceeds());
- EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, nullptr, &actual),
- SyscallSucceeds());
- sigdelset(&expected, kTestSignal1);
- EXPECT_THAT(actual, EqualsSigset(expected));
-}
-
-// Test that the signal mask actually blocks signals.
-TEST_F(SigProcMaskTest, SignalHandler) {
- sigset_t mask;
-
- // clear the signal mask
- sigemptyset(&mask);
- EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, &mask, nullptr), SyscallSucceeds());
-
- // Check the initial signal counts.
- EXPECT_EQ(0, signal_count[kTestSignal1]);
- EXPECT_EQ(0, signal_count[kTestSignal2]);
-
- // Check that both kTestSignal1 and kTestSignal2 are not blocked.
- raise(kTestSignal1);
- raise(kTestSignal2);
- EXPECT_EQ(1, signal_count[kTestSignal1]);
- EXPECT_EQ(1, signal_count[kTestSignal2]);
-
- // Block kTestSignal1.
- sigaddset(&mask, kTestSignal1);
- EXPECT_THAT(raw_sigprocmask(SIG_BLOCK, &mask, nullptr), SyscallSucceeds());
-
- // Check that kTestSignal1 is blocked.
- raise(kTestSignal1);
- raise(kTestSignal2);
- EXPECT_EQ(1, signal_count[kTestSignal1]);
- EXPECT_EQ(2, signal_count[kTestSignal2]);
-
- // Unblock kTestSignal1.
- sigaddset(&mask, kTestSignal1);
- EXPECT_THAT(raw_sigprocmask(SIG_UNBLOCK, &mask, nullptr), SyscallSucceeds());
-
- // Check that the unblocked kTestSignal1 has been delivered.
- EXPECT_EQ(2, signal_count[kTestSignal1]);
- EXPECT_EQ(2, signal_count[kTestSignal2]);
-}
-
-// Check that sigprocmask correctly handles aliasing of the set and oldset
-// pointers.
-TEST_F(SigProcMaskTest, AliasedSets) {
- sigset_t mask;
-
- // Set a mask in which only kTestSignal1 is blocked.
- sigset_t mask1;
- sigemptyset(&mask1);
- sigaddset(&mask1, kTestSignal1);
- mask = mask1;
- ASSERT_THAT(raw_sigprocmask(SIG_SETMASK, &mask, nullptr), SyscallSucceeds());
-
- // Exchange it with a mask in which only kTestSignal2 is blocked.
- sigset_t mask2;
- sigemptyset(&mask2);
- sigaddset(&mask2, kTestSignal2);
- mask = mask2;
- ASSERT_THAT(raw_sigprocmask(SIG_SETMASK, &mask, &mask), SyscallSucceeds());
-
- // Check that the exchange succeeeded:
- // mask should now contain the previously-set mask blocking only kTestSignal1.
- EXPECT_THAT(mask, EqualsSigset(mask1));
- // The current mask should block only kTestSignal2.
- ASSERT_THAT(raw_sigprocmask(0, nullptr, &mask), SyscallSucceeds());
- EXPECT_THAT(mask, EqualsSigset(mask2));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/sigstop.cc b/test/syscalls/linux/sigstop.cc
deleted file mode 100644
index 9c7210e17..000000000
--- a/test/syscalls/linux/sigstop.cc
+++ /dev/null
@@ -1,150 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <signal.h>
-#include <stdlib.h>
-#include <sys/select.h>
-
-#include "gtest/gtest.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/multiprocess_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-DEFINE_bool(sigstop_test_child, false,
- "If true, run the SigstopTest child workload.");
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-constexpr absl::Duration kChildStartupDelay = absl::Seconds(5);
-constexpr absl::Duration kChildMainThreadDelay = absl::Seconds(10);
-constexpr absl::Duration kChildExtraThreadDelay = absl::Seconds(15);
-constexpr absl::Duration kPostSIGSTOPDelay = absl::Seconds(20);
-
-// Comparisons on absl::Duration aren't yet constexpr (2017-07-14), so we
-// can't just use static_assert.
-TEST(SigstopTest, TimesAreRelativelyConsistent) {
- EXPECT_LT(kChildStartupDelay, kChildMainThreadDelay)
- << "Child process will exit before the parent process attempts to stop "
- "it";
- EXPECT_LT(kChildMainThreadDelay, kChildExtraThreadDelay)
- << "Secondary thread in child process will exit before main thread, "
- "causing it to exit with the wrong code";
- EXPECT_LT(kChildExtraThreadDelay, kPostSIGSTOPDelay)
- << "Parent process stops waiting before child process may exit if "
- "improperly stopped, rendering the test ineffective";
-}
-
-// Exit codes communicated from the child workload to the parent test process.
-constexpr int kChildMainThreadExitCode = 10;
-constexpr int kChildExtraThreadExitCode = 11;
-
-TEST(SigstopTest, Correctness) {
- pid_t child_pid = -1;
- int execve_errno = 0;
- auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec("/proc/self/exe", {"/proc/self/exe", "--sigstop_test_child"},
- {}, nullptr, &child_pid, &execve_errno));
-
- ASSERT_GT(child_pid, 0);
- ASSERT_EQ(execve_errno, 0);
-
- // Wait for the child subprocess to start the second thread before stopping
- // it.
- absl::SleepFor(kChildStartupDelay);
- ASSERT_THAT(kill(child_pid, SIGSTOP), SyscallSucceeds());
- int status;
- EXPECT_THAT(RetryEINTR(waitpid)(child_pid, &status, WUNTRACED),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFSTOPPED(status));
- EXPECT_EQ(SIGSTOP, WSTOPSIG(status));
-
- // Sleep for longer than either of the sleeps in the child subprocess,
- // expecting the child to stay alive because it's stopped.
- absl::SleepFor(kPostSIGSTOPDelay);
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, WNOHANG),
- SyscallSucceedsWithValue(0));
-
- // Resume the child.
- ASSERT_THAT(kill(child_pid, SIGCONT), SyscallSucceeds());
-
- EXPECT_THAT(RetryEINTR(waitpid)(child_pid, &status, WCONTINUED),
- SyscallSucceedsWithValue(child_pid));
- EXPECT_TRUE(WIFCONTINUED(status));
-
- // Expect it to die.
- ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
- ASSERT_TRUE(WIFEXITED(status));
- ASSERT_EQ(WEXITSTATUS(status), kChildMainThreadExitCode);
-}
-
-// Like base:SleepFor, but tries to avoid counting time spent stopped due to a
-// stop signal toward the sleep.
-//
-// This is required due to an inconsistency in how nanosleep(2) and stop signals
-// interact on Linux. When nanosleep is interrupted, it writes the remaining
-// time back to its second timespec argument, so that if nanosleep is
-// interrupted by a signal handler then userspace can immediately call nanosleep
-// again with that timespec. However, if nanosleep is automatically restarted
-// (because it's interrupted by a signal that is not delivered to a handler,
-// such as a stop signal), it's restarted based on the timer's former *absolute*
-// expiration time (via ERESTART_RESTARTBLOCK => SYS_restart_syscall =>
-// hrtimer_nanosleep_restart). This means that time spent stopped is effectively
-// counted as time spent sleeping, resulting in less time spent sleeping than
-// expected.
-//
-// Dividing the sleep into multiple smaller sleeps limits the impact of this
-// effect to the length of each sleep during which a stop occurs; for example,
-// if a sleeping process is only stopped once, SleepIgnoreStopped can
-// under-sleep by at most 100ms.
-void SleepIgnoreStopped(absl::Duration d) {
- absl::Duration const max_sleep = absl::Milliseconds(100);
- while (d > absl::ZeroDuration()) {
- absl::Duration to_sleep = std::min(d, max_sleep);
- absl::SleepFor(to_sleep);
- d -= to_sleep;
- }
-}
-
-void RunChild() {
- // Start another thread that attempts to call exit_group with a different
- // error code, in order to verify that SIGSTOP stops this thread as well.
- ScopedThread t([] {
- SleepIgnoreStopped(kChildExtraThreadDelay);
- exit(kChildExtraThreadExitCode);
- });
- SleepIgnoreStopped(kChildMainThreadDelay);
- exit(kChildMainThreadExitCode);
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
-
-int main(int argc, char** argv) {
- gvisor::testing::TestInit(&argc, &argv);
-
- if (FLAGS_sigstop_test_child) {
- gvisor::testing::RunChild();
- return 1;
- }
-
- return RUN_ALL_TESTS();
-}
diff --git a/test/syscalls/linux/sigtimedwait.cc b/test/syscalls/linux/sigtimedwait.cc
deleted file mode 100644
index 1e5bf5942..000000000
--- a/test/syscalls/linux/sigtimedwait.cc
+++ /dev/null
@@ -1,324 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/logging.h"
-#include "test/util/signal_util.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-#include "test/util/timer_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// N.B. main() blocks SIGALRM and SIGCHLD on all threads.
-
-constexpr int kAlarmSecs = 12;
-
-void NoopHandler(int sig, siginfo_t* info, void* context) {}
-
-TEST(SigtimedwaitTest, InvalidTimeout) {
- sigset_t mask;
- sigemptyset(&mask);
- struct timespec timeout = {0, 1000000001};
- EXPECT_THAT(sigtimedwait(&mask, nullptr, &timeout),
- SyscallFailsWithErrno(EINVAL));
- timeout = {-1, 0};
- EXPECT_THAT(sigtimedwait(&mask, nullptr, &timeout),
- SyscallFailsWithErrno(EINVAL));
- timeout = {0, -1};
- EXPECT_THAT(sigtimedwait(&mask, nullptr, &timeout),
- SyscallFailsWithErrno(EINVAL));
-}
-
-// No random save as the test relies on alarm timing. Cooperative save tests
-// already cover the save between alarm and wait.
-TEST(SigtimedwaitTest, AlarmReturnsAlarm_NoRandomSave) {
- struct itimerval itv = {};
- itv.it_value.tv_sec = kAlarmSecs;
- const auto itimer_cleanup =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedItimer(ITIMER_REAL, itv));
-
- sigset_t mask;
- sigemptyset(&mask);
- sigaddset(&mask, SIGALRM);
- siginfo_t info = {};
- EXPECT_THAT(RetryEINTR(sigtimedwait)(&mask, &info, nullptr),
- SyscallSucceedsWithValue(SIGALRM));
- EXPECT_EQ(SIGALRM, info.si_signo);
-}
-
-// No random save as the test relies on alarm timing. Cooperative save tests
-// already cover the save between alarm and wait.
-TEST(SigtimedwaitTest, NullTimeoutReturnsEINTR_NoRandomSave) {
- struct sigaction sa;
- sa.sa_sigaction = NoopHandler;
- sigfillset(&sa.sa_mask);
- sa.sa_flags = SA_SIGINFO;
- const auto action_cleanup =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGALRM, sa));
-
- const auto mask_cleanup =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGALRM));
-
- struct itimerval itv = {};
- itv.it_value.tv_sec = kAlarmSecs;
- const auto itimer_cleanup =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedItimer(ITIMER_REAL, itv));
-
- sigset_t mask;
- sigemptyset(&mask);
- EXPECT_THAT(sigtimedwait(&mask, nullptr, nullptr),
- SyscallFailsWithErrno(EINTR));
-}
-
-TEST(SigtimedwaitTest, LegitTimeoutReturnsEAGAIN) {
- sigset_t mask;
- sigemptyset(&mask);
- struct timespec timeout = {1, 0}; // 1 second
- EXPECT_THAT(RetryEINTR(sigtimedwait)(&mask, nullptr, &timeout),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST(SigtimedwaitTest, ZeroTimeoutReturnsEAGAIN) {
- sigset_t mask;
- sigemptyset(&mask);
- struct timespec timeout = {0, 0}; // 0 second
- EXPECT_THAT(sigtimedwait(&mask, nullptr, &timeout),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST(SigtimedwaitTest, KillGeneratedSIGCHLD) {
- EXPECT_THAT(kill(getpid(), SIGCHLD), SyscallSucceeds());
-
- sigset_t mask;
- sigemptyset(&mask);
- sigaddset(&mask, SIGCHLD);
- struct timespec ts = {5, 0};
- EXPECT_THAT(RetryEINTR(sigtimedwait)(&mask, nullptr, &ts),
- SyscallSucceedsWithValue(SIGCHLD));
-}
-
-TEST(SigtimedwaitTest, ChildExitGeneratedSIGCHLD) {
- pid_t pid = fork();
- if (pid == 0) {
- _exit(0);
- }
- ASSERT_THAT(pid, SyscallSucceeds());
-
- int status;
- EXPECT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) << status;
-
- sigset_t mask;
- sigemptyset(&mask);
- sigaddset(&mask, SIGCHLD);
- struct timespec ts = {5, 0};
- EXPECT_THAT(RetryEINTR(sigtimedwait)(&mask, nullptr, &ts),
- SyscallSucceedsWithValue(SIGCHLD));
-}
-
-TEST(SigtimedwaitTest, ChildExitGeneratedSIGCHLDWithHandler) {
- // Setup handler for SIGCHLD, but don't unblock it.
- struct sigaction sa;
- sa.sa_sigaction = NoopHandler;
- sigfillset(&sa.sa_mask);
- sa.sa_flags = SA_SIGINFO;
- const auto action_cleanup =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGCHLD, sa));
-
- pid_t pid = fork();
- if (pid == 0) {
- _exit(0);
- }
- ASSERT_THAT(pid, SyscallSucceeds());
-
- sigset_t mask;
- sigemptyset(&mask);
- sigaddset(&mask, SIGCHLD);
- struct timespec ts = {5, 0};
- EXPECT_THAT(RetryEINTR(sigtimedwait)(&mask, nullptr, &ts),
- SyscallSucceedsWithValue(SIGCHLD));
-
- int status;
- EXPECT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) << status;
-}
-
-// sigtimedwait cannot catch SIGKILL.
-TEST(SigtimedwaitTest, SIGKILLUncaught) {
- // This is a regression test for sigtimedwait dequeuing SIGKILLs, thus
- // preventing the task from exiting.
- //
- // The explanation below is specific to behavior in gVisor. The Linux behavior
- // here is irrelevant because without a bug that prevents delivery of SIGKILL,
- // none of this behavior is visible (in Linux or gVisor).
- //
- // SIGKILL is rather intrusive. Simply sending the SIGKILL marks
- // ThreadGroup.exitStatus as exiting with SIGKILL, before the SIGKILL is even
- // delivered.
- //
- // As a result, we cannot simply exit the child with a different exit code if
- // it survives and expect to see that code in waitpid because:
- // 1. PrepareGroupExit will override Task.exitStatus with
- // ThreadGroup.exitStatus.
- // 2. waitpid(2) will always return ThreadGroup.exitStatus rather than
- // Task.exitStatus.
- //
- // We could use exit(2) to set Task.exitStatus without override, and a SIGCHLD
- // handler to receive Task.exitStatus in the parent, but with that much
- // test complexity, it is cleaner to simply use a pipe to notify the parent
- // that we survived.
- constexpr auto kSigtimedwaitSetupTime = absl::Seconds(2);
-
- int pipe_fds[2];
- ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds());
- FileDescriptor rfd(pipe_fds[0]);
- FileDescriptor wfd(pipe_fds[1]);
-
- pid_t pid = fork();
- if (pid == 0) {
- rfd.reset();
-
- sigset_t mask;
- sigemptyset(&mask);
- sigaddset(&mask, SIGKILL);
- RetryEINTR(sigtimedwait)(&mask, nullptr, nullptr);
-
- // Survived.
- char c = 'a';
- TEST_PCHECK(WriteFd(wfd.get(), &c, 1) == 1);
- _exit(1);
- }
- ASSERT_THAT(pid, SyscallSucceeds());
-
- wfd.reset();
-
- // Wait for child to block in sigtimedwait, then kill it.
- absl::SleepFor(kSigtimedwaitSetupTime);
-
- // Sending SIGKILL will attempt to enqueue the signal twice: once in the
- // normal signal sending path, and once to all Tasks in the ThreadGroup when
- // applying SIGKILL side-effects.
- //
- // If we use kill(2), the former will be on the ThreadGroup signal queue and
- // the latter will be on the Task signal queue. sigtimedwait can only dequeue
- // one signal, so the other would kill the Task, masking bugs.
- //
- // If we use tkill(2), the former will be on the Task signal queue and the
- // latter will be dropped as a duplicate. Then sigtimedwait can theoretically
- // dequeue the single SIGKILL.
- EXPECT_THAT(syscall(SYS_tkill, pid, SIGKILL), SyscallSucceeds());
-
- int status;
- EXPECT_THAT(RetryEINTR(waitpid)(pid, &status, 0),
- SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) << status;
-
- // Child shouldn't have survived.
- char c;
- EXPECT_THAT(ReadFd(rfd.get(), &c, 1), SyscallSucceedsWithValue(0));
-}
-
-TEST(SigtimedwaitTest, IgnoredUnmaskedSignal) {
- constexpr int kSigno = SIGUSR1;
- constexpr auto kSigtimedwaitSetupTime = absl::Seconds(2);
- constexpr auto kSigtimedwaitTimeout = absl::Seconds(5);
- ASSERT_GT(kSigtimedwaitTimeout, kSigtimedwaitSetupTime);
-
- // Ensure that kSigno is ignored, and unmasked on this thread.
- struct sigaction sa = {};
- sa.sa_handler = SIG_IGN;
- const auto scoped_sigaction =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(kSigno, sa));
- sigset_t mask;
- sigemptyset(&mask);
- sigaddset(&mask, kSigno);
- auto scoped_sigmask =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, mask));
-
- // Create a thread which will send us kSigno while we are blocked in
- // sigtimedwait.
- pid_t tid = gettid();
- ScopedThread sigthread([&] {
- absl::SleepFor(kSigtimedwaitSetupTime);
- EXPECT_THAT(tgkill(getpid(), tid, kSigno), SyscallSucceeds());
- });
-
- // sigtimedwait should not observe kSigno since it is ignored and already
- // unmasked, causing it to be dropped before it is enqueued.
- struct timespec timeout_ts = absl::ToTimespec(kSigtimedwaitTimeout);
- EXPECT_THAT(RetryEINTR(sigtimedwait)(&mask, nullptr, &timeout_ts),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST(SigtimedwaitTest, IgnoredMaskedSignal) {
- constexpr int kSigno = SIGUSR1;
- constexpr auto kSigtimedwaitSetupTime = absl::Seconds(2);
- constexpr auto kSigtimedwaitTimeout = absl::Seconds(5);
- ASSERT_GT(kSigtimedwaitTimeout, kSigtimedwaitSetupTime);
-
- // Ensure that kSigno is ignored, and masked on this thread.
- struct sigaction sa = {};
- sa.sa_handler = SIG_IGN;
- const auto scoped_sigaction =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(kSigno, sa));
- sigset_t mask;
- sigemptyset(&mask);
- sigaddset(&mask, kSigno);
- auto scoped_sigmask =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_BLOCK, mask));
-
- // Create a thread which will send us kSigno while we are blocked in
- // sigtimedwait.
- pid_t tid = gettid();
- ScopedThread sigthread([&] {
- absl::SleepFor(kSigtimedwaitSetupTime);
- EXPECT_THAT(tgkill(getpid(), tid, kSigno), SyscallSucceeds());
- });
-
- // sigtimedwait should observe kSigno since it is normally masked, causing it
- // to be enqueued despite being ignored.
- struct timespec timeout_ts = absl::ToTimespec(kSigtimedwaitTimeout);
- EXPECT_THAT(RetryEINTR(sigtimedwait)(&mask, nullptr, &timeout_ts),
- SyscallSucceedsWithValue(kSigno));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
-
-int main(int argc, char** argv) {
- // These tests depend on delivering SIGALRM/SIGCHLD to the main thread or in
- // sigtimedwait. Block them so that any other threads created by TestInit will
- // also have them blocked.
- sigset_t set;
- sigemptyset(&set);
- sigaddset(&set, SIGALRM);
- sigaddset(&set, SIGCHLD);
- TEST_PCHECK(sigprocmask(SIG_BLOCK, &set, nullptr) == 0);
-
- gvisor::testing::TestInit(&argc, &argv);
-
- return RUN_ALL_TESTS();
-}
diff --git a/test/syscalls/linux/socket.cc b/test/syscalls/linux/socket.cc
deleted file mode 100644
index caae215b8..000000000
--- a/test/syscalls/linux/socket.cc
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sys/socket.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-TEST(SocketTest, UnixSocketPairProtocol) {
- int socks[2];
- ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, PF_UNIX, socks),
- SyscallSucceeds());
- close(socks[0]);
- close(socks[1]);
-}
-
-TEST(SocketTest, ProtocolUnix) {
- struct {
- int domain, type, protocol;
- } tests[] = {
- {AF_UNIX, SOCK_STREAM, PF_UNIX},
- {AF_UNIX, SOCK_SEQPACKET, PF_UNIX},
- {AF_UNIX, SOCK_DGRAM, PF_UNIX},
- };
- for (int i = 0; i < ABSL_ARRAYSIZE(tests); i++) {
- ASSERT_NO_ERRNO_AND_VALUE(
- Socket(tests[i].domain, tests[i].type, tests[i].protocol));
- }
-}
-
-TEST(SocketTest, ProtocolInet) {
- struct {
- int domain, type, protocol;
- } tests[] = {
- {AF_INET, SOCK_DGRAM, IPPROTO_UDP},
- {AF_INET, SOCK_STREAM, IPPROTO_TCP},
- };
- for (int i = 0; i < ABSL_ARRAYSIZE(tests); i++) {
- ASSERT_NO_ERRNO_AND_VALUE(
- Socket(tests[i].domain, tests[i].type, tests[i].protocol));
- }
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_abstract.cc b/test/syscalls/linux/socket_abstract.cc
deleted file mode 100644
index 715d87b76..000000000
--- a/test/syscalls/linux/socket_abstract.cc
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <vector>
-
-#include "test/syscalls/linux/socket_generic.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/socket_unix.h"
-#include "test/syscalls/linux/socket_unix_cmsg.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketPairKind> GetSocketPairs() {
- return ApplyVec<SocketPairKind>(
- AbstractBoundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET},
- List<int>{0, SOCK_NONBLOCK}));
-}
-
-INSTANTIATE_TEST_SUITE_P(
- AbstractUnixSockets, AllSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-INSTANTIATE_TEST_SUITE_P(
- AbstractUnixSockets, UnixSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-INSTANTIATE_TEST_SUITE_P(
- AbstractUnixSockets, UnixSocketPairCmsgTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_blocking.cc b/test/syscalls/linux/socket_blocking.cc
deleted file mode 100644
index 00c50d1bf..000000000
--- a/test/syscalls/linux/socket_blocking.cc
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/socket_blocking.h"
-
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <sys/un.h>
-#include <cstdio>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-#include "test/util/timer_util.h"
-
-namespace gvisor {
-namespace testing {
-
-TEST_P(BlockingSocketPairTest, RecvBlocks) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[100];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- constexpr auto kDuration = absl::Milliseconds(200);
- auto before = Now(CLOCK_MONOTONIC);
-
- const ScopedThread t([&]() {
- absl::SleepFor(kDuration);
- ASSERT_THAT(write(sockets->first_fd(), sent_data, sizeof(sent_data)),
- SyscallSucceedsWithValue(sizeof(sent_data)));
- });
-
- char received_data[sizeof(sent_data)] = {};
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(received_data), 0),
- SyscallSucceedsWithValue(sizeof(received_data)));
-
- auto after = Now(CLOCK_MONOTONIC);
- EXPECT_GE(after - before, kDuration);
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_blocking.h b/test/syscalls/linux/socket_blocking.h
deleted file mode 100644
index db26e5ef5..000000000
--- a/test/syscalls/linux/socket_blocking.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_BLOCKING_H_
-#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_BLOCKING_H_
-
-#include "test/syscalls/linux/socket_test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Test fixture for tests that apply to pairs of blocking connected sockets.
-using BlockingSocketPairTest = SocketPairTest;
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_BLOCKING_H_
diff --git a/test/syscalls/linux/socket_filesystem.cc b/test/syscalls/linux/socket_filesystem.cc
deleted file mode 100644
index 74e262959..000000000
--- a/test/syscalls/linux/socket_filesystem.cc
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <vector>
-
-#include "test/syscalls/linux/socket_generic.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/socket_unix.h"
-#include "test/syscalls/linux/socket_unix_cmsg.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketPairKind> GetSocketPairs() {
- return ApplyVec<SocketPairKind>(
- FilesystemBoundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET},
- List<int>{0, SOCK_NONBLOCK}));
-}
-
-INSTANTIATE_TEST_SUITE_P(
- FilesystemUnixSockets, AllSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-INSTANTIATE_TEST_SUITE_P(
- FilesystemUnixSockets, UnixSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-INSTANTIATE_TEST_SUITE_P(
- FilesystemUnixSockets, UnixSocketPairCmsgTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_generic.cc b/test/syscalls/linux/socket_generic.cc
deleted file mode 100644
index 51d614639..000000000
--- a/test/syscalls/linux/socket_generic.cc
+++ /dev/null
@@ -1,741 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/socket_generic.h"
-
-#include <stdio.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "absl/strings/str_format.h"
-#include "absl/strings/string_view.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-// This file is a generic socket test file. It must be built with another file
-// that provides the test types.
-
-namespace gvisor {
-namespace testing {
-
-TEST_P(AllSocketPairTest, BasicReadWrite) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- char buf[20];
- const std::string data = "abc";
- ASSERT_THAT(WriteFd(sockets->first_fd(), data.c_str(), 3),
- SyscallSucceedsWithValue(3));
- ASSERT_THAT(ReadFd(sockets->second_fd(), buf, 3),
- SyscallSucceedsWithValue(3));
- EXPECT_EQ(data, absl::string_view(buf, 3));
-}
-
-TEST_P(AllSocketPairTest, BasicSendRecv) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- char sent_data[512];
- RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
- char received_data[sizeof(sent_data)];
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(received_data), 0),
- SyscallSucceedsWithValue(sizeof(received_data)));
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-}
-
-TEST_P(AllSocketPairTest, BasicSendmmsg) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- char sent_data[200];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- std::vector<struct mmsghdr> msgs(10);
- std::vector<struct iovec> iovs(msgs.size());
- const int chunk_size = sizeof(sent_data) / msgs.size();
- for (size_t i = 0; i < msgs.size(); i++) {
- iovs[i].iov_len = chunk_size;
- iovs[i].iov_base = &sent_data[i * chunk_size];
- msgs[i].msg_hdr.msg_iov = &iovs[i];
- msgs[i].msg_hdr.msg_iovlen = 1;
- }
-
- ASSERT_THAT(
- RetryEINTR(sendmmsg)(sockets->first_fd(), &msgs[0], msgs.size(), 0),
- SyscallSucceedsWithValue(msgs.size()));
-
- for (const struct mmsghdr& msg : msgs) {
- EXPECT_EQ(chunk_size, msg.msg_len);
- }
-
- char received_data[sizeof(sent_data)];
- for (size_t i = 0; i < msgs.size(); i++) {
- ASSERT_THAT(ReadFd(sockets->second_fd(), &received_data[i * chunk_size],
- chunk_size),
- SyscallSucceedsWithValue(chunk_size));
- }
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-}
-
-TEST_P(AllSocketPairTest, BasicRecvmmsg) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- char sent_data[200];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- char received_data[sizeof(sent_data)];
- std::vector<struct mmsghdr> msgs(10);
- std::vector<struct iovec> iovs(msgs.size());
- const int chunk_size = sizeof(sent_data) / msgs.size();
- for (size_t i = 0; i < msgs.size(); i++) {
- iovs[i].iov_len = chunk_size;
- iovs[i].iov_base = &received_data[i * chunk_size];
- msgs[i].msg_hdr.msg_iov = &iovs[i];
- msgs[i].msg_hdr.msg_iovlen = 1;
- }
-
- for (size_t i = 0; i < msgs.size(); i++) {
- ASSERT_THAT(
- WriteFd(sockets->first_fd(), &sent_data[i * chunk_size], chunk_size),
- SyscallSucceedsWithValue(chunk_size));
- }
-
- ASSERT_THAT(RetryEINTR(recvmmsg)(sockets->second_fd(), &msgs[0], msgs.size(),
- 0, nullptr),
- SyscallSucceedsWithValue(msgs.size()));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-
- for (const struct mmsghdr& msg : msgs) {
- EXPECT_EQ(chunk_size, msg.msg_len);
- }
-}
-
-TEST_P(AllSocketPairTest, SendmsgRecvmsg10KB) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- std::vector<char> sent_data(10 * 1024);
- RandomizeBuffer(sent_data.data(), sent_data.size());
- ASSERT_NO_FATAL_FAILURE(
- SendNullCmsg(sockets->first_fd(), sent_data.data(), sent_data.size()));
-
- std::vector<char> received_data(sent_data.size());
- ASSERT_NO_FATAL_FAILURE(RecvNoCmsg(sockets->second_fd(), received_data.data(),
- received_data.size()));
-
- EXPECT_EQ(0,
- memcmp(sent_data.data(), received_data.data(), sent_data.size()));
-}
-
-// This test validates that a sendmsg/recvmsg w/ MSG_CTRUNC is a no-op on
-// input flags.
-TEST_P(AllSocketPairTest, SendmsgRecvmsgMsgCtruncNoop) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- std::vector<char> sent_data(10 * 1024);
- RandomizeBuffer(sent_data.data(), sent_data.size());
- ASSERT_NO_FATAL_FAILURE(
- SendNullCmsg(sockets->first_fd(), sent_data.data(), sent_data.size()));
-
- std::vector<char> received_data(sent_data.size());
- struct msghdr msg = {};
- char control[CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred))];
- msg.msg_control = control;
- msg.msg_controllen = sizeof(control);
-
- struct iovec iov;
- iov.iov_base = &received_data[0];
- iov.iov_len = received_data.size();
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- // MSG_CTRUNC should be a no-op.
- ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, MSG_CTRUNC),
- SyscallSucceedsWithValue(received_data.size()));
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
- EXPECT_EQ(cmsg, nullptr);
- EXPECT_EQ(msg.msg_controllen, 0);
- EXPECT_EQ(0,
- memcmp(sent_data.data(), received_data.data(), sent_data.size()));
-}
-
-TEST_P(AllSocketPairTest, SendmsgRecvmsg16KB) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- std::vector<char> sent_data(16 * 1024);
- RandomizeBuffer(sent_data.data(), sent_data.size());
- ASSERT_NO_FATAL_FAILURE(
- SendNullCmsg(sockets->first_fd(), sent_data.data(), sent_data.size()));
-
- std::vector<char> received_data(sent_data.size());
- ASSERT_NO_FATAL_FAILURE(RecvNoCmsg(sockets->second_fd(), received_data.data(),
- received_data.size()));
-
- EXPECT_EQ(0,
- memcmp(sent_data.data(), received_data.data(), sent_data.size()));
-}
-
-TEST_P(AllSocketPairTest, RecvmsgMsghdrFlagsNotClearedOnFailure) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char received_data[10] = {};
-
- struct iovec iov;
- iov.iov_base = received_data;
- iov.iov_len = sizeof(received_data);
- struct msghdr msg = {};
- msg.msg_flags = -1;
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, MSG_DONTWAIT),
- SyscallFailsWithErrno(EAGAIN));
-
- // Check that msghdr flags were not changed.
- EXPECT_EQ(msg.msg_flags, -1);
-}
-
-TEST_P(AllSocketPairTest, RecvmsgMsghdrFlagsCleared) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[10];
- RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- char received_data[sizeof(sent_data)] = {};
-
- struct iovec iov;
- iov.iov_base = received_data;
- iov.iov_len = sizeof(received_data);
- struct msghdr msg = {};
- msg.msg_flags = -1;
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
- EXPECT_EQ(0, memcmp(received_data, sent_data, sizeof(sent_data)));
-
- // Check that msghdr flags were cleared.
- EXPECT_EQ(msg.msg_flags, 0);
-}
-
-TEST_P(AllSocketPairTest, RecvmsgPeekMsghdrFlagsCleared) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[10];
- RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- char received_data[sizeof(sent_data)] = {};
-
- struct iovec iov;
- iov.iov_base = received_data;
- iov.iov_len = sizeof(received_data);
- struct msghdr msg = {};
- msg.msg_flags = -1;
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, MSG_PEEK),
- SyscallSucceedsWithValue(sizeof(sent_data)));
- EXPECT_EQ(0, memcmp(received_data, sent_data, sizeof(sent_data)));
-
- // Check that msghdr flags were cleared.
- EXPECT_EQ(msg.msg_flags, 0);
-}
-
-TEST_P(AllSocketPairTest, RecvmsgIovNotUpdated) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[10];
- RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- char received_data[sizeof(sent_data) * 2] = {};
-
- struct iovec iov;
- iov.iov_base = received_data;
- iov.iov_len = sizeof(received_data);
- struct msghdr msg = {};
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
- EXPECT_EQ(0, memcmp(received_data, sent_data, sizeof(sent_data)));
-
- // Check that the iovec length was not updated.
- EXPECT_EQ(msg.msg_iov->iov_len, sizeof(received_data));
-}
-
-TEST_P(AllSocketPairTest, RecvmmsgInvalidTimeout) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- char buf[10];
- struct mmsghdr msg = {};
- struct iovec iov = {};
- iov.iov_len = sizeof(buf);
- iov.iov_base = buf;
- msg.msg_hdr.msg_iov = &iov;
- msg.msg_hdr.msg_iovlen = 1;
- struct timespec timeout = {-1, -1};
- ASSERT_THAT(RetryEINTR(recvmmsg)(sockets->first_fd(), &msg, 1, 0, &timeout),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_P(AllSocketPairTest, RecvmmsgTimeoutBeforeRecv) {
- // There is a known bug in the Linux recvmmsg(2) causing it to block forever
- // if the timeout expires while blocking for the first message.
- SKIP_IF(!IsRunningOnGvisor());
-
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- char buf[10];
- struct mmsghdr msg = {};
- struct iovec iov = {};
- iov.iov_len = sizeof(buf);
- iov.iov_base = buf;
- msg.msg_hdr.msg_iov = &iov;
- msg.msg_hdr.msg_iovlen = 1;
- struct timespec timeout = {};
- ASSERT_THAT(RetryEINTR(recvmmsg)(sockets->first_fd(), &msg, 1, 0, &timeout),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST_P(AllSocketPairTest, MsgPeek) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- char sent_data[50];
- memset(&sent_data, 0, sizeof(sent_data));
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data, sizeof(sent_data)),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- char received_data[sizeof(sent_data)];
- for (int i = 0; i < 3; i++) {
- memset(received_data, 0, sizeof(received_data));
- EXPECT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(received_data), MSG_PEEK),
- SyscallSucceedsWithValue(sizeof(received_data)));
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(received_data)));
- }
-
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(received_data), 0),
- SyscallSucceedsWithValue(sizeof(received_data)));
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(received_data)));
-}
-
-TEST_P(AllSocketPairTest, LingerSocketOption) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- struct linger got_linger = {-1, -1};
- socklen_t length = sizeof(struct linger);
- EXPECT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER,
- &got_linger, &length),
- SyscallSucceedsWithValue(0));
- struct linger want_linger = {};
- EXPECT_EQ(0, memcmp(&want_linger, &got_linger, sizeof(struct linger)));
- EXPECT_EQ(sizeof(struct linger), length);
-}
-
-TEST_P(AllSocketPairTest, KeepAliveSocketOption) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- int keepalive = -1;
- socklen_t length = sizeof(int);
- EXPECT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_KEEPALIVE,
- &keepalive, &length),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(0, keepalive);
- EXPECT_EQ(sizeof(int), length);
-}
-
-TEST_P(AllSocketPairTest, RcvBufSucceeds) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- int size = 0;
- socklen_t size_size = sizeof(size);
- EXPECT_THAT(
- getsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVBUF, &size, &size_size),
- SyscallSucceeds());
- EXPECT_GT(size, 0);
-}
-
-TEST_P(AllSocketPairTest, SndBufSucceeds) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- int size = 0;
- socklen_t size_size = sizeof(size);
- EXPECT_THAT(
- getsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDBUF, &size, &size_size),
- SyscallSucceeds());
- EXPECT_GT(size, 0);
-}
-
-TEST_P(AllSocketPairTest, RecvTimeoutReadSucceeds) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct timeval tv {
- .tv_sec = 0, .tv_usec = 10
- };
- EXPECT_THAT(
- setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)),
- SyscallSucceeds());
-
- char buf[20] = {};
- EXPECT_THAT(RetryEINTR(read)(sockets->first_fd(), buf, sizeof(buf)),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST_P(AllSocketPairTest, RecvTimeoutRecvSucceeds) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct timeval tv {
- .tv_sec = 0, .tv_usec = 10
- };
- EXPECT_THAT(
- setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)),
- SyscallSucceeds());
-
- char buf[20] = {};
- EXPECT_THAT(RetryEINTR(recv)(sockets->first_fd(), buf, sizeof(buf), 0),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST_P(AllSocketPairTest, RecvTimeoutRecvOneSecondSucceeds) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct timeval tv {
- .tv_sec = 1, .tv_usec = 0
- };
- EXPECT_THAT(
- setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)),
- SyscallSucceeds());
-
- char buf[20] = {};
- EXPECT_THAT(RetryEINTR(recv)(sockets->first_fd(), buf, sizeof(buf), 0),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST_P(AllSocketPairTest, RecvTimeoutRecvmsgSucceeds) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct timeval tv {
- .tv_sec = 0, .tv_usec = 10
- };
- EXPECT_THAT(
- setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)),
- SyscallSucceeds());
-
- struct msghdr msg = {};
- char buf[20] = {};
- struct iovec iov;
- iov.iov_base = buf;
- iov.iov_len = sizeof(buf);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- EXPECT_THAT(RetryEINTR(recvmsg)(sockets->first_fd(), &msg, 0),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST_P(AllSocketPairTest, SendTimeoutAllowsWrite) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct timeval tv {
- .tv_sec = 0, .tv_usec = 10
- };
- EXPECT_THAT(
- setsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)),
- SyscallSucceeds());
-
- char buf[20] = {};
- ASSERT_THAT(RetryEINTR(write)(sockets->first_fd(), buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-}
-
-TEST_P(AllSocketPairTest, SendTimeoutAllowsSend) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct timeval tv {
- .tv_sec = 0, .tv_usec = 10
- };
- EXPECT_THAT(
- setsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)),
- SyscallSucceeds());
-
- char buf[20] = {};
- ASSERT_THAT(RetryEINTR(send)(sockets->first_fd(), buf, sizeof(buf), 0),
- SyscallSucceedsWithValue(sizeof(buf)));
-}
-
-TEST_P(AllSocketPairTest, SendTimeoutAllowsSendmsg) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct timeval tv {
- .tv_sec = 0, .tv_usec = 10
- };
- EXPECT_THAT(
- setsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)),
- SyscallSucceeds());
-
- char buf[20] = {};
- ASSERT_NO_FATAL_FAILURE(SendNullCmsg(sockets->first_fd(), buf, sizeof(buf)));
-}
-
-TEST_P(AllSocketPairTest, SoRcvTimeoIsSet) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct timeval tv {
- .tv_sec = 0, .tv_usec = 35
- };
- EXPECT_THAT(
- setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)),
- SyscallSucceeds());
-}
-
-TEST_P(AllSocketPairTest, SoRcvTimeoIsSetLargerArg) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct timeval_with_extra {
- struct timeval tv;
- int64_t extra_data;
- } ABSL_ATTRIBUTE_PACKED;
-
- timeval_with_extra tv_extra;
- tv_extra.tv.tv_sec = 0;
- tv_extra.tv.tv_usec = 25;
-
- EXPECT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO,
- &tv_extra, sizeof(tv_extra)),
- SyscallSucceeds());
-}
-
-TEST_P(AllSocketPairTest, RecvTimeoutRecvmsgOneSecondSucceeds) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct timeval tv {
- .tv_sec = 1, .tv_usec = 0
- };
- EXPECT_THAT(
- setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)),
- SyscallSucceeds());
-
- struct msghdr msg = {};
- char buf[20] = {};
- struct iovec iov;
- iov.iov_base = buf;
- iov.iov_len = sizeof(buf);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- EXPECT_THAT(RetryEINTR(recvmsg)(sockets->first_fd(), &msg, 0),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST_P(AllSocketPairTest, RecvTimeoutUsecTooLarge) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct timeval tv {
- .tv_sec = 0, .tv_usec = 2000000 // 2 seconds.
- };
- EXPECT_THAT(
- setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)),
- SyscallFailsWithErrno(EDOM));
-}
-
-TEST_P(AllSocketPairTest, SendTimeoutUsecTooLarge) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct timeval tv {
- .tv_sec = 0, .tv_usec = 2000000 // 2 seconds.
- };
- EXPECT_THAT(
- setsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)),
- SyscallFailsWithErrno(EDOM));
-}
-
-TEST_P(AllSocketPairTest, RecvTimeoutUsecNeg) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct timeval tv {
- .tv_sec = 0, .tv_usec = -1
- };
- EXPECT_THAT(
- setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)),
- SyscallFailsWithErrno(EDOM));
-}
-
-TEST_P(AllSocketPairTest, SendTimeoutUsecNeg) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct timeval tv {
- .tv_sec = 0, .tv_usec = -1
- };
- EXPECT_THAT(
- setsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)),
- SyscallFailsWithErrno(EDOM));
-}
-
-TEST_P(AllSocketPairTest, RecvTimeoutNegSecRead) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct timeval tv {
- .tv_sec = -1, .tv_usec = 0
- };
- EXPECT_THAT(
- setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)),
- SyscallSucceeds());
-
- char buf[20] = {};
- EXPECT_THAT(RetryEINTR(read)(sockets->first_fd(), buf, sizeof(buf)),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST_P(AllSocketPairTest, RecvTimeoutNegSecRecv) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct timeval tv {
- .tv_sec = -1, .tv_usec = 0
- };
- EXPECT_THAT(
- setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)),
- SyscallSucceeds());
-
- char buf[20] = {};
- EXPECT_THAT(RetryEINTR(recv)(sockets->first_fd(), buf, sizeof(buf), 0),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST_P(AllSocketPairTest, RecvTimeoutNegSecRecvmsg) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct timeval tv {
- .tv_sec = -1, .tv_usec = 0
- };
- EXPECT_THAT(
- setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)),
- SyscallSucceeds());
-
- struct msghdr msg = {};
- char buf[20] = {};
- struct iovec iov;
- iov.iov_base = buf;
- iov.iov_len = sizeof(buf);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- EXPECT_THAT(RetryEINTR(recvmsg)(sockets->first_fd(), &msg, 0),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST_P(AllSocketPairTest, RecvWaitAll) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[100];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- ASSERT_THAT(write(sockets->first_fd(), sent_data, sizeof(sent_data)),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- char received_data[sizeof(sent_data)] = {};
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(received_data), MSG_WAITALL),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-}
-
-TEST_P(AllSocketPairTest, RecvWaitAllDontWait) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char data[100] = {};
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), data, sizeof(data),
- MSG_WAITALL | MSG_DONTWAIT),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST_P(AllSocketPairTest, RecvTimeoutWaitAll) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct timeval tv {
- .tv_sec = 0, .tv_usec = 200000 // 200ms
- };
- EXPECT_THAT(setsockopt(sockets->second_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv,
- sizeof(tv)),
- SyscallSucceeds());
-
- char sent_data[100];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- ASSERT_THAT(write(sockets->first_fd(), sent_data, sizeof(sent_data)),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- char received_data[sizeof(sent_data) * 2] = {};
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(received_data), MSG_WAITALL),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-}
-
-TEST_P(AllSocketPairTest, GetSockoptType) {
- int type = GetParam().type;
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- for (const int fd : {sockets->first_fd(), sockets->second_fd()}) {
- int opt;
- socklen_t optlen = sizeof(opt);
- EXPECT_THAT(getsockopt(fd, SOL_SOCKET, SO_TYPE, &opt, &optlen),
- SyscallSucceeds());
-
- // Type may have SOCK_NONBLOCK and SOCK_CLOEXEC ORed into it. Remove these
- // before comparison.
- type &= ~(SOCK_NONBLOCK | SOCK_CLOEXEC);
- EXPECT_EQ(opt, type) << absl::StrFormat(
- "getsockopt(%d, SOL_SOCKET, SO_TYPE, &opt, &optlen) => opt=%d was "
- "unexpected",
- fd, opt);
- }
-}
-
-TEST_P(AllSocketPairTest, GetSockoptDomain) {
- const int domain = GetParam().domain;
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- for (const int fd : {sockets->first_fd(), sockets->second_fd()}) {
- int opt;
- socklen_t optlen = sizeof(opt);
- EXPECT_THAT(getsockopt(fd, SOL_SOCKET, SO_DOMAIN, &opt, &optlen),
- SyscallSucceeds());
- EXPECT_EQ(opt, domain) << absl::StrFormat(
- "getsockopt(%d, SOL_SOCKET, SO_DOMAIN, &opt, &optlen) => opt=%d was "
- "unexpected",
- fd, opt);
- }
-}
-
-TEST_P(AllSocketPairTest, GetSockoptProtocol) {
- const int protocol = GetParam().protocol;
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- for (const int fd : {sockets->first_fd(), sockets->second_fd()}) {
- int opt;
- socklen_t optlen = sizeof(opt);
- EXPECT_THAT(getsockopt(fd, SOL_SOCKET, SO_PROTOCOL, &opt, &optlen),
- SyscallSucceeds());
- EXPECT_EQ(opt, protocol) << absl::StrFormat(
- "getsockopt(%d, SOL_SOCKET, SO_PROTOCOL, &opt, &optlen) => opt=%d was "
- "unexpected",
- fd, opt);
- }
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_generic.h b/test/syscalls/linux/socket_generic.h
deleted file mode 100644
index 00ae7bfc3..000000000
--- a/test/syscalls/linux/socket_generic.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_GENERIC_H_
-#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_GENERIC_H_
-
-#include "test/syscalls/linux/socket_test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Test fixture for tests that apply to pairs of blocking and non-blocking
-// connected stream sockets.
-using AllSocketPairTest = SocketPairTest;
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_GENERIC_H_
diff --git a/test/syscalls/linux/socket_inet_loopback.cc b/test/syscalls/linux/socket_inet_loopback.cc
deleted file mode 100644
index 322ee07ad..000000000
--- a/test/syscalls/linux/socket_inet_loopback.cc
+++ /dev/null
@@ -1,1208 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <arpa/inet.h>
-#include <netinet/in.h>
-#include <poll.h>
-#include <string.h>
-#include <sys/socket.h>
-
-#include <atomic>
-#include <iostream>
-#include <memory>
-#include <string>
-#include <tuple>
-#include <utility>
-#include <vector>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/memory/memory.h"
-#include "absl/strings/str_cat.h"
-#include "absl/time/time.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/posix_error.h"
-#include "test/util/save_util.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-PosixErrorOr<uint16_t> AddrPort(int family, sockaddr_storage const& addr) {
- switch (family) {
- case AF_INET:
- return static_cast<uint16_t>(
- reinterpret_cast<sockaddr_in const*>(&addr)->sin_port);
- case AF_INET6:
- return static_cast<uint16_t>(
- reinterpret_cast<sockaddr_in6 const*>(&addr)->sin6_port);
- default:
- return PosixError(EINVAL,
- absl::StrCat("unknown socket family: ", family));
- }
-}
-
-PosixError SetAddrPort(int family, sockaddr_storage* addr, uint16_t port) {
- switch (family) {
- case AF_INET:
- reinterpret_cast<sockaddr_in*>(addr)->sin_port = port;
- return NoError();
- case AF_INET6:
- reinterpret_cast<sockaddr_in6*>(addr)->sin6_port = port;
- return NoError();
- default:
- return PosixError(EINVAL,
- absl::StrCat("unknown socket family: ", family));
- }
-}
-
-struct TestParam {
- TestAddress listener;
- TestAddress connector;
-};
-
-std::string DescribeTestParam(::testing::TestParamInfo<TestParam> const& info) {
- return absl::StrCat("Listen", info.param.listener.description, "_Connect",
- info.param.connector.description);
-}
-
-using SocketInetLoopbackTest = ::testing::TestWithParam<TestParam>;
-
-TEST(BadSocketPairArgs, ValidateErrForBadCallsToSocketPair) {
- int fd[2] = {};
-
- // Valid AF but invalid for socketpair(2) return ESOCKTNOSUPPORT.
- ASSERT_THAT(socketpair(AF_INET, 0, 0, fd),
- SyscallFailsWithErrno(ESOCKTNOSUPPORT));
- ASSERT_THAT(socketpair(AF_INET6, 0, 0, fd),
- SyscallFailsWithErrno(ESOCKTNOSUPPORT));
-
- // Invalid AF will return ENOAFSUPPORT.
- ASSERT_THAT(socketpair(AF_MAX, 0, 0, fd),
- SyscallFailsWithErrno(EAFNOSUPPORT));
- ASSERT_THAT(socketpair(8675309, 0, 0, fd),
- SyscallFailsWithErrno(EAFNOSUPPORT));
-}
-
-TEST_P(SocketInetLoopbackTest, TCP) {
- auto const& param = GetParam();
-
- TestAddress const& listener = param.listener;
- TestAddress const& connector = param.connector;
-
- // Create the listening socket.
- const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP));
- sockaddr_storage listen_addr = listener.addr;
- ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr),
- listener.addr_len),
- SyscallSucceeds());
- ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds());
-
- // Get the port bound by the listening socket.
- socklen_t addrlen = listener.addr_len;
- ASSERT_THAT(getsockname(listen_fd.get(),
- reinterpret_cast<sockaddr*>(&listen_addr), &addrlen),
- SyscallSucceeds());
- uint16_t const port =
- ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr));
-
- // Connect to the listening socket.
- const FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
- sockaddr_storage conn_addr = connector.addr;
- ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
- ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(),
- reinterpret_cast<sockaddr*>(&conn_addr),
- connector.addr_len),
- SyscallSucceeds());
-
- // Accept the connection.
- //
- // We have to assign a name to the accepted socket, as unamed temporary
- // objects are destructed upon full evaluation of the expression it is in,
- // potentially causing the connecting socket to fail to shutdown properly.
- auto accepted =
- ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr));
-
- ASSERT_THAT(shutdown(listen_fd.get(), SHUT_RDWR), SyscallSucceeds());
-
- ASSERT_THAT(shutdown(conn_fd.get(), SHUT_RDWR), SyscallSucceeds());
-}
-
-TEST_P(SocketInetLoopbackTest, TCPListenClose) {
- auto const& param = GetParam();
-
- TestAddress const& listener = param.listener;
- TestAddress const& connector = param.connector;
-
- // Create the listening socket.
- FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP));
- sockaddr_storage listen_addr = listener.addr;
- ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr),
- listener.addr_len),
- SyscallSucceeds());
- ASSERT_THAT(listen(listen_fd.get(), 1001), SyscallSucceeds());
-
- // Get the port bound by the listening socket.
- socklen_t addrlen = listener.addr_len;
- ASSERT_THAT(getsockname(listen_fd.get(),
- reinterpret_cast<sockaddr*>(&listen_addr), &addrlen),
- SyscallSucceeds());
- uint16_t const port =
- ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr));
-
- DisableSave ds; // Too many system calls.
- sockaddr_storage conn_addr = connector.addr;
- ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
- constexpr int kFDs = 2048;
- constexpr int kThreadCount = 4;
- constexpr int kFDsPerThread = kFDs / kThreadCount;
- FileDescriptor clients[kFDs];
- std::unique_ptr<ScopedThread> threads[kThreadCount];
- for (int i = 0; i < kFDs; i++) {
- clients[i] = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(connector.family(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP));
- }
- for (int i = 0; i < kThreadCount; i++) {
- threads[i] = absl::make_unique<ScopedThread>([&connector, &conn_addr,
- &clients, i]() {
- for (int j = 0; j < kFDsPerThread; j++) {
- int k = i * kFDsPerThread + j;
- int ret =
- connect(clients[k].get(), reinterpret_cast<sockaddr*>(&conn_addr),
- connector.addr_len);
- if (ret != 0) {
- EXPECT_THAT(ret, SyscallFailsWithErrno(EINPROGRESS));
- }
- }
- });
- }
- for (int i = 0; i < kThreadCount; i++) {
- threads[i]->Join();
- }
- for (int i = 0; i < 32; i++) {
- auto accepted =
- ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr));
- }
- // TODO(b/138400178): Fix cooperative S/R failure when ds.reset() is invoked
- // before function end.
- // ds.reset()
-}
-
-TEST_P(SocketInetLoopbackTest, TCPbacklog) {
- auto const& param = GetParam();
-
- TestAddress const& listener = param.listener;
- TestAddress const& connector = param.connector;
-
- // Create the listening socket.
- const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP));
- sockaddr_storage listen_addr = listener.addr;
- ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr),
- listener.addr_len),
- SyscallSucceeds());
- ASSERT_THAT(listen(listen_fd.get(), 2), SyscallSucceeds());
-
- // Get the port bound by the listening socket.
- socklen_t addrlen = listener.addr_len;
- ASSERT_THAT(getsockname(listen_fd.get(),
- reinterpret_cast<sockaddr*>(&listen_addr), &addrlen),
- SyscallSucceeds());
- uint16_t const port =
- ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr));
- int i = 0;
- while (1) {
- int ret;
-
- // Connect to the listening socket.
- const FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(connector.family(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP));
- sockaddr_storage conn_addr = connector.addr;
- ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
- ret = connect(conn_fd.get(), reinterpret_cast<sockaddr*>(&conn_addr),
- connector.addr_len);
- if (ret != 0) {
- EXPECT_THAT(ret, SyscallFailsWithErrno(EINPROGRESS));
- struct pollfd pfd = {
- .fd = conn_fd.get(),
- .events = POLLOUT,
- };
- ret = poll(&pfd, 1, 3000);
- if (ret == 0) break;
- EXPECT_THAT(ret, SyscallSucceedsWithValue(1));
- }
- EXPECT_THAT(RetryEINTR(send)(conn_fd.get(), &i, sizeof(i), 0),
- SyscallSucceedsWithValue(sizeof(i)));
- ASSERT_THAT(shutdown(conn_fd.get(), SHUT_RDWR), SyscallSucceeds());
- i++;
- }
-
- for (; i != 0; i--) {
- // Accept the connection.
- //
- // We have to assign a name to the accepted socket, as unamed temporary
- // objects are destructed upon full evaluation of the expression it is in,
- // potentially causing the connecting socket to fail to shutdown properly.
- auto accepted =
- ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr));
- }
-}
-
-INSTANTIATE_TEST_SUITE_P(
- All, SocketInetLoopbackTest,
- ::testing::Values(
- // Listeners bound to IPv4 addresses refuse connections using IPv6
- // addresses.
- TestParam{V4Any(), V4Any()}, TestParam{V4Any(), V4Loopback()},
- TestParam{V4Any(), V4MappedAny()},
- TestParam{V4Any(), V4MappedLoopback()},
- TestParam{V4Loopback(), V4Any()}, TestParam{V4Loopback(), V4Loopback()},
- TestParam{V4Loopback(), V4MappedLoopback()},
- TestParam{V4MappedAny(), V4Any()},
- TestParam{V4MappedAny(), V4Loopback()},
- TestParam{V4MappedAny(), V4MappedAny()},
- TestParam{V4MappedAny(), V4MappedLoopback()},
- TestParam{V4MappedLoopback(), V4Any()},
- TestParam{V4MappedLoopback(), V4Loopback()},
- TestParam{V4MappedLoopback(), V4MappedLoopback()},
-
- // Listeners bound to IN6ADDR_ANY accept all connections.
- TestParam{V6Any(), V4Any()}, TestParam{V6Any(), V4Loopback()},
- TestParam{V6Any(), V4MappedAny()},
- TestParam{V6Any(), V4MappedLoopback()}, TestParam{V6Any(), V6Any()},
- TestParam{V6Any(), V6Loopback()},
-
- // Listeners bound to IN6ADDR_LOOPBACK refuse connections using IPv4
- // addresses.
- TestParam{V6Loopback(), V6Any()},
- TestParam{V6Loopback(), V6Loopback()}),
- DescribeTestParam);
-
-using SocketInetReusePortTest = ::testing::TestWithParam<TestParam>;
-
-TEST_P(SocketInetReusePortTest, TcpPortReuseMultiThread) {
- auto const& param = GetParam();
-
- TestAddress const& listener = param.listener;
- TestAddress const& connector = param.connector;
- sockaddr_storage listen_addr = listener.addr;
- sockaddr_storage conn_addr = connector.addr;
- constexpr int kThreadCount = 3;
-
- // Create the listening socket.
- FileDescriptor listener_fds[kThreadCount];
- for (int i = 0; i < kThreadCount; i++) {
- listener_fds[i] = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP));
- int fd = listener_fds[i].get();
-
- ASSERT_THAT(setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &kSockOptOn,
- sizeof(kSockOptOn)),
- SyscallSucceeds());
- ASSERT_THAT(
- bind(fd, reinterpret_cast<sockaddr*>(&listen_addr), listener.addr_len),
- SyscallSucceeds());
- ASSERT_THAT(listen(fd, 40), SyscallSucceeds());
-
- // On the first bind we need to determine which port was bound.
- if (i != 0) {
- continue;
- }
-
- // Get the port bound by the listening socket.
- socklen_t addrlen = listener.addr_len;
- ASSERT_THAT(
- getsockname(listener_fds[0].get(),
- reinterpret_cast<sockaddr*>(&listen_addr), &addrlen),
- SyscallSucceeds());
- uint16_t const port =
- ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr));
- ASSERT_NO_ERRNO(SetAddrPort(listener.family(), &listen_addr, port));
- ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
- }
-
- constexpr int kConnectAttempts = 10000;
- std::atomic<int> connects_received = ATOMIC_VAR_INIT(0);
- std::unique_ptr<ScopedThread> listen_thread[kThreadCount];
- int accept_counts[kThreadCount] = {};
- // TODO(avagin): figure how to not disable S/R for the whole test.
- // We need to take into account that this test executes a lot of system
- // calls from many threads.
- DisableSave ds;
-
- for (int i = 0; i < kThreadCount; i++) {
- listen_thread[i] = absl::make_unique<ScopedThread>(
- [&listener_fds, &accept_counts, i, &connects_received]() {
- do {
- auto fd = Accept(listener_fds[i].get(), nullptr, nullptr);
- if (!fd.ok()) {
- if (connects_received >= kConnectAttempts) {
- // Another thread have shutdown our read side causing the
- // accept to fail.
- break;
- }
- ASSERT_NO_ERRNO(fd);
- break;
- }
- // Receive some data from a socket to be sure that the connect()
- // system call has been completed on another side.
- int data;
- EXPECT_THAT(
- RetryEINTR(recv)(fd.ValueOrDie().get(), &data, sizeof(data), 0),
- SyscallSucceedsWithValue(sizeof(data)));
- accept_counts[i]++;
- } while (++connects_received < kConnectAttempts);
-
- // Shutdown all sockets to wake up other threads.
- for (int j = 0; j < kThreadCount; j++) {
- shutdown(listener_fds[j].get(), SHUT_RDWR);
- }
- });
- }
-
- ScopedThread connecting_thread([&connector, &conn_addr]() {
- for (int i = 0; i < kConnectAttempts; i++) {
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
- ASSERT_THAT(
- RetryEINTR(connect)(fd.get(), reinterpret_cast<sockaddr*>(&conn_addr),
- connector.addr_len),
- SyscallSucceeds());
-
- EXPECT_THAT(RetryEINTR(send)(fd.get(), &i, sizeof(i), 0),
- SyscallSucceedsWithValue(sizeof(i)));
- }
- });
-
- // Join threads to be sure that all connections have been counted
- connecting_thread.Join();
- for (int i = 0; i < kThreadCount; i++) {
- listen_thread[i]->Join();
- }
- // Check that connections are distributed fairly between listening sockets
- for (int i = 0; i < kThreadCount; i++)
- EXPECT_THAT(accept_counts[i],
- EquivalentWithin((kConnectAttempts / kThreadCount), 0.10));
-}
-
-TEST_P(SocketInetReusePortTest, UdpPortReuseMultiThread) {
- auto const& param = GetParam();
-
- TestAddress const& listener = param.listener;
- TestAddress const& connector = param.connector;
- sockaddr_storage listen_addr = listener.addr;
- sockaddr_storage conn_addr = connector.addr;
- constexpr int kThreadCount = 3;
-
- // Create the listening socket.
- FileDescriptor listener_fds[kThreadCount];
- for (int i = 0; i < kThreadCount; i++) {
- listener_fds[i] =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(listener.family(), SOCK_DGRAM, 0));
- int fd = listener_fds[i].get();
-
- ASSERT_THAT(setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &kSockOptOn,
- sizeof(kSockOptOn)),
- SyscallSucceeds());
- ASSERT_THAT(
- bind(fd, reinterpret_cast<sockaddr*>(&listen_addr), listener.addr_len),
- SyscallSucceeds());
-
- // On the first bind we need to determine which port was bound.
- if (i != 0) {
- continue;
- }
-
- // Get the port bound by the listening socket.
- socklen_t addrlen = listener.addr_len;
- ASSERT_THAT(
- getsockname(listener_fds[0].get(),
- reinterpret_cast<sockaddr*>(&listen_addr), &addrlen),
- SyscallSucceeds());
- uint16_t const port =
- ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr));
- ASSERT_NO_ERRNO(SetAddrPort(listener.family(), &listen_addr, port));
- ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
- }
-
- constexpr int kConnectAttempts = 10000;
- std::atomic<int> packets_received = ATOMIC_VAR_INIT(0);
- std::unique_ptr<ScopedThread> receiver_thread[kThreadCount];
- int packets_per_socket[kThreadCount] = {};
- // TODO(avagin): figure how to not disable S/R for the whole test.
- DisableSave ds; // Too expensive.
-
- for (int i = 0; i < kThreadCount; i++) {
- receiver_thread[i] = absl::make_unique<ScopedThread>(
- [&listener_fds, &packets_per_socket, i, &packets_received]() {
- do {
- struct sockaddr_storage addr = {};
- socklen_t addrlen = sizeof(addr);
- int data;
-
- auto ret = RetryEINTR(recvfrom)(
- listener_fds[i].get(), &data, sizeof(data), 0,
- reinterpret_cast<struct sockaddr*>(&addr), &addrlen);
-
- if (packets_received < kConnectAttempts) {
- ASSERT_THAT(ret, SyscallSucceedsWithValue(sizeof(data)));
- }
-
- if (ret != sizeof(data)) {
- // Another thread may have shutdown our read side causing the
- // recvfrom to fail.
- break;
- }
-
- packets_received++;
- packets_per_socket[i]++;
-
- // A response is required to synchronize with the main thread,
- // otherwise the main thread can send more than can fit into receive
- // queues.
- EXPECT_THAT(RetryEINTR(sendto)(
- listener_fds[i].get(), &data, sizeof(data), 0,
- reinterpret_cast<sockaddr*>(&addr), addrlen),
- SyscallSucceedsWithValue(sizeof(data)));
- } while (packets_received < kConnectAttempts);
-
- // Shutdown all sockets to wake up other threads.
- for (int j = 0; j < kThreadCount; j++)
- shutdown(listener_fds[j].get(), SHUT_RDWR);
- });
- }
-
- ScopedThread main_thread([&connector, &conn_addr]() {
- for (int i = 0; i < kConnectAttempts; i++) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(connector.family(), SOCK_DGRAM, 0));
- EXPECT_THAT(RetryEINTR(sendto)(fd.get(), &i, sizeof(i), 0,
- reinterpret_cast<sockaddr*>(&conn_addr),
- connector.addr_len),
- SyscallSucceedsWithValue(sizeof(i)));
- int data;
- EXPECT_THAT(RetryEINTR(recv)(fd.get(), &data, sizeof(data), 0),
- SyscallSucceedsWithValue(sizeof(data)));
- }
- });
-
- main_thread.Join();
-
- // Join threads to be sure that all connections have been counted
- for (int i = 0; i < kThreadCount; i++) {
- receiver_thread[i]->Join();
- }
- // Check that packets are distributed fairly between listening sockets.
- for (int i = 0; i < kThreadCount; i++)
- EXPECT_THAT(packets_per_socket[i],
- EquivalentWithin((kConnectAttempts / kThreadCount), 0.10));
-}
-
-INSTANTIATE_TEST_SUITE_P(
- All, SocketInetReusePortTest,
- ::testing::Values(
- // Listeners bound to IPv4 addresses refuse connections using IPv6
- // addresses.
- TestParam{V4Any(), V4Loopback()},
- TestParam{V4Loopback(), V4MappedLoopback()},
-
- // Listeners bound to IN6ADDR_ANY accept all connections.
- TestParam{V6Any(), V4Loopback()}, TestParam{V6Any(), V6Loopback()},
-
- // Listeners bound to IN6ADDR_LOOPBACK refuse connections using IPv4
- // addresses.
- TestParam{V6Loopback(), V6Loopback()}),
- DescribeTestParam);
-
-struct ProtocolTestParam {
- std::string description;
- int type;
-};
-
-std::string DescribeProtocolTestParam(
- ::testing::TestParamInfo<ProtocolTestParam> const& info) {
- return info.param.description;
-}
-
-using SocketMultiProtocolInetLoopbackTest =
- ::testing::TestWithParam<ProtocolTestParam>;
-
-TEST_P(SocketMultiProtocolInetLoopbackTest, V4MappedLoopbackOnlyReservesV4) {
- auto const& param = GetParam();
-
- for (int i = 0; true; i++) {
- // Bind the v4 loopback on a dual stack socket.
- TestAddress const& test_addr_dual = V4MappedLoopback();
- sockaddr_storage addr_dual = test_addr_dual.addr;
- const FileDescriptor fd_dual = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(test_addr_dual.family(), param.type, 0));
- ASSERT_THAT(bind(fd_dual.get(), reinterpret_cast<sockaddr*>(&addr_dual),
- test_addr_dual.addr_len),
- SyscallSucceeds());
-
- // Get the port that we bound.
- socklen_t addrlen = test_addr_dual.addr_len;
- ASSERT_THAT(getsockname(fd_dual.get(),
- reinterpret_cast<sockaddr*>(&addr_dual), &addrlen),
- SyscallSucceeds());
- uint16_t const port =
- ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr_dual.family(), addr_dual));
-
- // Verify that we can still bind the v6 loopback on the same port.
- TestAddress const& test_addr_v6 = V6Loopback();
- sockaddr_storage addr_v6 = test_addr_v6.addr;
- ASSERT_NO_ERRNO(SetAddrPort(test_addr_v6.family(), &addr_v6, port));
- const FileDescriptor fd_v6 =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v6.family(), param.type, 0));
- int ret = bind(fd_v6.get(), reinterpret_cast<sockaddr*>(&addr_v6),
- test_addr_v6.addr_len);
- if (ret == -1 && errno == EADDRINUSE) {
- // Port may have been in use.
- ASSERT_LT(i, 100); // Give up after 100 tries.
- continue;
- }
- ASSERT_THAT(ret, SyscallSucceeds());
-
- // Verify that binding the v4 loopback with the same port on a v4 socket
- // fails.
- TestAddress const& test_addr_v4 = V4Loopback();
- sockaddr_storage addr_v4 = test_addr_v4.addr;
- ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4.family(), &addr_v4, port));
- const FileDescriptor fd_v4 =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v4.family(), param.type, 0));
- ASSERT_THAT(bind(fd_v4.get(), reinterpret_cast<sockaddr*>(&addr_v4),
- test_addr_v4.addr_len),
- SyscallFailsWithErrno(EADDRINUSE));
-
- // No need to try again.
- break;
- }
-}
-
-TEST_P(SocketMultiProtocolInetLoopbackTest, V4MappedAnyOnlyReservesV4) {
- auto const& param = GetParam();
-
- for (int i = 0; true; i++) {
- // Bind the v4 any on a dual stack socket.
- TestAddress const& test_addr_dual = V4MappedAny();
- sockaddr_storage addr_dual = test_addr_dual.addr;
- const FileDescriptor fd_dual = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(test_addr_dual.family(), param.type, 0));
- ASSERT_THAT(bind(fd_dual.get(), reinterpret_cast<sockaddr*>(&addr_dual),
- test_addr_dual.addr_len),
- SyscallSucceeds());
-
- // Get the port that we bound.
- socklen_t addrlen = test_addr_dual.addr_len;
- ASSERT_THAT(getsockname(fd_dual.get(),
- reinterpret_cast<sockaddr*>(&addr_dual), &addrlen),
- SyscallSucceeds());
- uint16_t const port =
- ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr_dual.family(), addr_dual));
-
- // Verify that we can still bind the v6 loopback on the same port.
- TestAddress const& test_addr_v6 = V6Loopback();
- sockaddr_storage addr_v6 = test_addr_v6.addr;
- ASSERT_NO_ERRNO(SetAddrPort(test_addr_v6.family(), &addr_v6, port));
- const FileDescriptor fd_v6 =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v6.family(), param.type, 0));
- int ret = bind(fd_v6.get(), reinterpret_cast<sockaddr*>(&addr_v6),
- test_addr_v6.addr_len);
- if (ret == -1 && errno == EADDRINUSE) {
- // Port may have been in use.
- ASSERT_LT(i, 100); // Give up after 100 tries.
- continue;
- }
- ASSERT_THAT(ret, SyscallSucceeds());
-
- // Verify that binding the v4 loopback with the same port on a v4 socket
- // fails.
- TestAddress const& test_addr_v4 = V4Loopback();
- sockaddr_storage addr_v4 = test_addr_v4.addr;
- ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4.family(), &addr_v4, port));
- const FileDescriptor fd_v4 =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v4.family(), param.type, 0));
- ASSERT_THAT(bind(fd_v4.get(), reinterpret_cast<sockaddr*>(&addr_v4),
- test_addr_v4.addr_len),
- SyscallFailsWithErrno(EADDRINUSE));
-
- // No need to try again.
- break;
- }
-}
-
-TEST_P(SocketMultiProtocolInetLoopbackTest, DualStackV6AnyReservesEverything) {
- auto const& param = GetParam();
-
- // Bind the v6 any on a dual stack socket.
- TestAddress const& test_addr_dual = V6Any();
- sockaddr_storage addr_dual = test_addr_dual.addr;
- const FileDescriptor fd_dual =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_dual.family(), param.type, 0));
- ASSERT_THAT(bind(fd_dual.get(), reinterpret_cast<sockaddr*>(&addr_dual),
- test_addr_dual.addr_len),
- SyscallSucceeds());
-
- // Get the port that we bound.
- socklen_t addrlen = test_addr_dual.addr_len;
- ASSERT_THAT(getsockname(fd_dual.get(),
- reinterpret_cast<sockaddr*>(&addr_dual), &addrlen),
- SyscallSucceeds());
- uint16_t const port =
- ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr_dual.family(), addr_dual));
-
- // Verify that binding the v6 loopback with the same port fails.
- TestAddress const& test_addr_v6 = V6Loopback();
- sockaddr_storage addr_v6 = test_addr_v6.addr;
- ASSERT_NO_ERRNO(SetAddrPort(test_addr_v6.family(), &addr_v6, port));
- const FileDescriptor fd_v6 =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v6.family(), param.type, 0));
- ASSERT_THAT(bind(fd_v6.get(), reinterpret_cast<sockaddr*>(&addr_v6),
- test_addr_v6.addr_len),
- SyscallFailsWithErrno(EADDRINUSE));
-
- // Verify that binding the v4 loopback on the same port with a v6 socket
- // fails.
- TestAddress const& test_addr_v4_mapped = V4MappedLoopback();
- sockaddr_storage addr_v4_mapped = test_addr_v4_mapped.addr;
- ASSERT_NO_ERRNO(
- SetAddrPort(test_addr_v4_mapped.family(), &addr_v4_mapped, port));
- const FileDescriptor fd_v4_mapped = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(test_addr_v4_mapped.family(), param.type, 0));
- ASSERT_THAT(
- bind(fd_v4_mapped.get(), reinterpret_cast<sockaddr*>(&addr_v4_mapped),
- test_addr_v4_mapped.addr_len),
- SyscallFailsWithErrno(EADDRINUSE));
-
- // Verify that binding the v4 loopback on the same port with a v4 socket
- // fails.
- TestAddress const& test_addr_v4 = V4Loopback();
- sockaddr_storage addr_v4 = test_addr_v4.addr;
- ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4.family(), &addr_v4, port));
- const FileDescriptor fd_v4 =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v4.family(), param.type, 0));
- ASSERT_THAT(bind(fd_v4.get(), reinterpret_cast<sockaddr*>(&addr_v4),
- test_addr_v4.addr_len),
- SyscallFailsWithErrno(EADDRINUSE));
-}
-
-TEST_P(SocketMultiProtocolInetLoopbackTest, V6OnlyV6AnyReservesV6) {
- auto const& param = GetParam();
-
- for (int i = 0; true; i++) {
- // Bind the v6 any on a v6-only socket.
- TestAddress const& test_addr_dual = V6Any();
- sockaddr_storage addr_dual = test_addr_dual.addr;
- const FileDescriptor fd_dual = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(test_addr_dual.family(), param.type, 0));
- int one = 1;
- EXPECT_THAT(
- setsockopt(fd_dual.get(), IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)),
- SyscallSucceeds());
- ASSERT_THAT(bind(fd_dual.get(), reinterpret_cast<sockaddr*>(&addr_dual),
- test_addr_dual.addr_len),
- SyscallSucceeds());
-
- // Get the port that we bound.
- socklen_t addrlen = test_addr_dual.addr_len;
- ASSERT_THAT(getsockname(fd_dual.get(),
- reinterpret_cast<sockaddr*>(&addr_dual), &addrlen),
- SyscallSucceeds());
- uint16_t const port =
- ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr_dual.family(), addr_dual));
-
- // Verify that binding the v6 loopback with the same port fails.
- TestAddress const& test_addr_v6 = V6Loopback();
- sockaddr_storage addr_v6 = test_addr_v6.addr;
- ASSERT_NO_ERRNO(SetAddrPort(test_addr_v6.family(), &addr_v6, port));
- const FileDescriptor fd_v6 =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v6.family(), param.type, 0));
- ASSERT_THAT(bind(fd_v6.get(), reinterpret_cast<sockaddr*>(&addr_v6),
- test_addr_v6.addr_len),
- SyscallFailsWithErrno(EADDRINUSE));
-
- // Verify that we can still bind the v4 loopback on the same port.
- TestAddress const& test_addr_v4_mapped = V4MappedLoopback();
- sockaddr_storage addr_v4_mapped = test_addr_v4_mapped.addr;
- ASSERT_NO_ERRNO(
- SetAddrPort(test_addr_v4_mapped.family(), &addr_v4_mapped, port));
- const FileDescriptor fd_v4_mapped = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(test_addr_v4_mapped.family(), param.type, 0));
- int ret =
- bind(fd_v4_mapped.get(), reinterpret_cast<sockaddr*>(&addr_v4_mapped),
- test_addr_v4_mapped.addr_len);
- if (ret == -1 && errno == EADDRINUSE) {
- // Port may have been in use.
- ASSERT_LT(i, 100); // Give up after 100 tries.
- continue;
- }
- ASSERT_THAT(ret, SyscallSucceeds());
-
- // No need to try again.
- break;
- }
-}
-
-TEST_P(SocketMultiProtocolInetLoopbackTest, V6EphemeralPortReserved) {
- auto const& param = GetParam();
-
- // FIXME(b/114268588)
- SKIP_IF(IsRunningOnGvisor() && param.type == SOCK_STREAM);
-
- for (int i = 0; true; i++) {
- // Bind the v6 loopback on a dual stack socket.
- TestAddress const& test_addr = V6Loopback();
- sockaddr_storage bound_addr = test_addr.addr;
- const FileDescriptor bound_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
- ASSERT_THAT(bind(bound_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr),
- test_addr.addr_len),
- SyscallSucceeds());
-
- // Listen iff TCP.
- if (param.type == SOCK_STREAM) {
- ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds());
- }
-
- // Get the port that we bound.
- socklen_t bound_addr_len = test_addr.addr_len;
- ASSERT_THAT(
- getsockname(bound_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr),
- &bound_addr_len),
- SyscallSucceeds());
-
- // Connect to bind an ephemeral port.
- const FileDescriptor connected_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
- ASSERT_THAT(
- connect(connected_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr),
- bound_addr_len),
- SyscallSucceeds());
-
- // Get the ephemeral port.
- sockaddr_storage connected_addr = {};
- socklen_t connected_addr_len = sizeof(connected_addr);
- ASSERT_THAT(getsockname(connected_fd.get(),
- reinterpret_cast<sockaddr*>(&connected_addr),
- &connected_addr_len),
- SyscallSucceeds());
- uint16_t const ephemeral_port =
- ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr));
-
- // Verify that we actually got an ephemeral port.
- ASSERT_NE(ephemeral_port, 0);
-
- // Verify that the ephemeral port is reserved.
- const FileDescriptor checking_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
- EXPECT_THAT(
- bind(checking_fd.get(), reinterpret_cast<sockaddr*>(&connected_addr),
- connected_addr_len),
- SyscallFailsWithErrno(EADDRINUSE));
-
- // Verify that binding the v6 loopback with the same port fails.
- TestAddress const& test_addr_v6 = V6Loopback();
- sockaddr_storage addr_v6 = test_addr_v6.addr;
- ASSERT_NO_ERRNO(
- SetAddrPort(test_addr_v6.family(), &addr_v6, ephemeral_port));
- const FileDescriptor fd_v6 =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v6.family(), param.type, 0));
- ASSERT_THAT(bind(fd_v6.get(), reinterpret_cast<sockaddr*>(&addr_v6),
- test_addr_v6.addr_len),
- SyscallFailsWithErrno(EADDRINUSE));
-
- // Verify that binding the v4 any with the same port fails.
- TestAddress const& test_addr_v4_any = V4Any();
- sockaddr_storage addr_v4_any = test_addr_v4_any.addr;
- ASSERT_NO_ERRNO(
- SetAddrPort(test_addr_v4_any.family(), &addr_v4_any, ephemeral_port));
- const FileDescriptor fd_v4_any = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(test_addr_v4_any.family(), param.type, 0));
- ASSERT_THAT(bind(fd_v4_any.get(), reinterpret_cast<sockaddr*>(&addr_v4_any),
- test_addr_v4_any.addr_len),
- SyscallFailsWithErrno(EADDRINUSE));
-
- // Verify that we can still bind the v4 loopback on the same port.
- TestAddress const& test_addr_v4_mapped = V4MappedLoopback();
- sockaddr_storage addr_v4_mapped = test_addr_v4_mapped.addr;
- ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4_mapped.family(), &addr_v4_mapped,
- ephemeral_port));
- const FileDescriptor fd_v4_mapped = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(test_addr_v4_mapped.family(), param.type, 0));
- int ret =
- bind(fd_v4_mapped.get(), reinterpret_cast<sockaddr*>(&addr_v4_mapped),
- test_addr_v4_mapped.addr_len);
- if (ret == -1 && errno == EADDRINUSE) {
- // Port may have been in use.
- ASSERT_LT(i, 100); // Give up after 100 tries.
- continue;
- }
- EXPECT_THAT(ret, SyscallSucceeds());
-
- // No need to try again.
- break;
- }
-}
-
-TEST_P(SocketMultiProtocolInetLoopbackTest, V4MappedEphemeralPortReserved) {
- auto const& param = GetParam();
-
- // FIXME(b/114268588)
- SKIP_IF(IsRunningOnGvisor() && param.type == SOCK_STREAM);
-
- for (int i = 0; true; i++) {
- // Bind the v4 loopback on a dual stack socket.
- TestAddress const& test_addr = V4MappedLoopback();
- sockaddr_storage bound_addr = test_addr.addr;
- const FileDescriptor bound_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
- ASSERT_THAT(bind(bound_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr),
- test_addr.addr_len),
- SyscallSucceeds());
-
- // Listen iff TCP.
- if (param.type == SOCK_STREAM) {
- ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds());
- }
-
- // Get the port that we bound.
- socklen_t bound_addr_len = test_addr.addr_len;
- ASSERT_THAT(
- getsockname(bound_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr),
- &bound_addr_len),
- SyscallSucceeds());
-
- // Connect to bind an ephemeral port.
- const FileDescriptor connected_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
- ASSERT_THAT(
- connect(connected_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr),
- bound_addr_len),
- SyscallSucceeds());
-
- // Get the ephemeral port.
- sockaddr_storage connected_addr = {};
- socklen_t connected_addr_len = sizeof(connected_addr);
- ASSERT_THAT(getsockname(connected_fd.get(),
- reinterpret_cast<sockaddr*>(&connected_addr),
- &connected_addr_len),
- SyscallSucceeds());
- uint16_t const ephemeral_port =
- ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr));
-
- // Verify that we actually got an ephemeral port.
- ASSERT_NE(ephemeral_port, 0);
-
- // Verify that the ephemeral port is reserved.
- const FileDescriptor checking_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
- EXPECT_THAT(
- bind(checking_fd.get(), reinterpret_cast<sockaddr*>(&connected_addr),
- connected_addr_len),
- SyscallFailsWithErrno(EADDRINUSE));
-
- // Verify that binding the v4 loopback on the same port with a v4 socket
- // fails.
- TestAddress const& test_addr_v4 = V4Loopback();
- sockaddr_storage addr_v4 = test_addr_v4.addr;
- ASSERT_NO_ERRNO(
- SetAddrPort(test_addr_v4.family(), &addr_v4, ephemeral_port));
- const FileDescriptor fd_v4 =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v4.family(), param.type, 0));
- EXPECT_THAT(bind(fd_v4.get(), reinterpret_cast<sockaddr*>(&addr_v4),
- test_addr_v4.addr_len),
- SyscallFailsWithErrno(EADDRINUSE));
-
- // Verify that binding the v6 any on the same port with a dual-stack socket
- // fails.
- TestAddress const& test_addr_v6_any = V6Any();
- sockaddr_storage addr_v6_any = test_addr_v6_any.addr;
- ASSERT_NO_ERRNO(
- SetAddrPort(test_addr_v6_any.family(), &addr_v6_any, ephemeral_port));
- const FileDescriptor fd_v6_any = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(test_addr_v6_any.family(), param.type, 0));
- ASSERT_THAT(bind(fd_v6_any.get(), reinterpret_cast<sockaddr*>(&addr_v6_any),
- test_addr_v6_any.addr_len),
- SyscallFailsWithErrno(EADDRINUSE));
-
- // For some reason, binding the TCP v6-only any is flaky on Linux. Maybe we
- // tend to run out of ephemeral ports? Regardless, binding the v6 loopback
- // seems pretty reliable. Only try to bind the v6-only any on UDP and
- // gVisor.
-
- int ret = -1;
-
- if (!IsRunningOnGvisor() && param.type == SOCK_STREAM) {
- // Verify that we can still bind the v6 loopback on the same port.
- TestAddress const& test_addr_v6 = V6Loopback();
- sockaddr_storage addr_v6 = test_addr_v6.addr;
- ASSERT_NO_ERRNO(
- SetAddrPort(test_addr_v6.family(), &addr_v6, ephemeral_port));
- const FileDescriptor fd_v6 = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(test_addr_v6.family(), param.type, 0));
- ret = bind(fd_v6.get(), reinterpret_cast<sockaddr*>(&addr_v6),
- test_addr_v6.addr_len);
- } else {
- // Verify that we can still bind the v6 any on the same port with a
- // v6-only socket.
- const FileDescriptor fd_v6_only_any = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(test_addr_v6_any.family(), param.type, 0));
- int one = 1;
- EXPECT_THAT(setsockopt(fd_v6_only_any.get(), IPPROTO_IPV6, IPV6_V6ONLY,
- &one, sizeof(one)),
- SyscallSucceeds());
- ret =
- bind(fd_v6_only_any.get(), reinterpret_cast<sockaddr*>(&addr_v6_any),
- test_addr_v6_any.addr_len);
- }
-
- if (ret == -1 && errno == EADDRINUSE) {
- // Port may have been in use.
- ASSERT_LT(i, 100); // Give up after 100 tries.
- continue;
- }
- EXPECT_THAT(ret, SyscallSucceeds());
-
- // No need to try again.
- break;
- }
-}
-
-TEST_P(SocketMultiProtocolInetLoopbackTest, V4EphemeralPortReserved) {
- auto const& param = GetParam();
-
- // FIXME(b/114268588)
- SKIP_IF(IsRunningOnGvisor() && param.type == SOCK_STREAM);
-
- for (int i = 0; true; i++) {
- // Bind the v4 loopback on a v4 socket.
- TestAddress const& test_addr = V4Loopback();
- sockaddr_storage bound_addr = test_addr.addr;
- const FileDescriptor bound_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
- ASSERT_THAT(bind(bound_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr),
- test_addr.addr_len),
- SyscallSucceeds());
-
- // Listen iff TCP.
- if (param.type == SOCK_STREAM) {
- ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds());
- }
-
- // Get the port that we bound.
- socklen_t bound_addr_len = test_addr.addr_len;
- ASSERT_THAT(
- getsockname(bound_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr),
- &bound_addr_len),
- SyscallSucceeds());
-
- // Connect to bind an ephemeral port.
- const FileDescriptor connected_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
- ASSERT_THAT(
- connect(connected_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr),
- bound_addr_len),
- SyscallSucceeds());
-
- // Get the ephemeral port.
- sockaddr_storage connected_addr = {};
- socklen_t connected_addr_len = sizeof(connected_addr);
- ASSERT_THAT(getsockname(connected_fd.get(),
- reinterpret_cast<sockaddr*>(&connected_addr),
- &connected_addr_len),
- SyscallSucceeds());
- uint16_t const ephemeral_port =
- ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr));
-
- // Verify that we actually got an ephemeral port.
- ASSERT_NE(ephemeral_port, 0);
-
- // Verify that the ephemeral port is reserved.
- const FileDescriptor checking_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
- EXPECT_THAT(
- bind(checking_fd.get(), reinterpret_cast<sockaddr*>(&connected_addr),
- connected_addr_len),
- SyscallFailsWithErrno(EADDRINUSE));
-
- // Verify that binding the v4 loopback on the same port with a v6 socket
- // fails.
- TestAddress const& test_addr_v4_mapped = V4MappedLoopback();
- sockaddr_storage addr_v4_mapped = test_addr_v4_mapped.addr;
- ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4_mapped.family(), &addr_v4_mapped,
- ephemeral_port));
- const FileDescriptor fd_v4_mapped = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(test_addr_v4_mapped.family(), param.type, 0));
- EXPECT_THAT(
- bind(fd_v4_mapped.get(), reinterpret_cast<sockaddr*>(&addr_v4_mapped),
- test_addr_v4_mapped.addr_len),
- SyscallFailsWithErrno(EADDRINUSE));
-
- // Verify that binding the v6 any on the same port with a dual-stack socket
- // fails.
- TestAddress const& test_addr_v6_any = V6Any();
- sockaddr_storage addr_v6_any = test_addr_v6_any.addr;
- ASSERT_NO_ERRNO(
- SetAddrPort(test_addr_v6_any.family(), &addr_v6_any, ephemeral_port));
- const FileDescriptor fd_v6_any = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(test_addr_v6_any.family(), param.type, 0));
- ASSERT_THAT(bind(fd_v6_any.get(), reinterpret_cast<sockaddr*>(&addr_v6_any),
- test_addr_v6_any.addr_len),
- SyscallFailsWithErrno(EADDRINUSE));
-
- // For some reason, binding the TCP v6-only any is flaky on Linux. Maybe we
- // tend to run out of ephemeral ports? Regardless, binding the v6 loopback
- // seems pretty reliable. Only try to bind the v6-only any on UDP and
- // gVisor.
-
- int ret = -1;
-
- if (!IsRunningOnGvisor() && param.type == SOCK_STREAM) {
- // Verify that we can still bind the v6 loopback on the same port.
- TestAddress const& test_addr_v6 = V6Loopback();
- sockaddr_storage addr_v6 = test_addr_v6.addr;
- ASSERT_NO_ERRNO(
- SetAddrPort(test_addr_v6.family(), &addr_v6, ephemeral_port));
- const FileDescriptor fd_v6 = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(test_addr_v6.family(), param.type, 0));
- ret = bind(fd_v6.get(), reinterpret_cast<sockaddr*>(&addr_v6),
- test_addr_v6.addr_len);
- } else {
- // Verify that we can still bind the v6 any on the same port with a
- // v6-only socket.
- const FileDescriptor fd_v6_only_any = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(test_addr_v6_any.family(), param.type, 0));
- int one = 1;
- EXPECT_THAT(setsockopt(fd_v6_only_any.get(), IPPROTO_IPV6, IPV6_V6ONLY,
- &one, sizeof(one)),
- SyscallSucceeds());
- ret =
- bind(fd_v6_only_any.get(), reinterpret_cast<sockaddr*>(&addr_v6_any),
- test_addr_v6_any.addr_len);
- }
-
- if (ret == -1 && errno == EADDRINUSE) {
- // Port may have been in use.
- ASSERT_LT(i, 100); // Give up after 100 tries.
- continue;
- }
- EXPECT_THAT(ret, SyscallSucceeds());
-
- // No need to try again.
- break;
- }
-}
-
-TEST_P(SocketMultiProtocolInetLoopbackTest, PortReuseTwoSockets) {
- auto const& param = GetParam();
- TestAddress const& test_addr = V4Loopback();
- sockaddr_storage addr = test_addr.addr;
-
- for (int i = 0; i < 2; i++) {
- const int portreuse1 = i % 2;
- auto s1 =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
- int fd1 = s1.get();
- socklen_t addrlen = test_addr.addr_len;
-
- EXPECT_THAT(
- setsockopt(fd1, SOL_SOCKET, SO_REUSEPORT, &portreuse1, sizeof(int)),
- SyscallSucceeds());
-
- ASSERT_THAT(bind(fd1, reinterpret_cast<sockaddr*>(&addr), addrlen),
- SyscallSucceeds());
-
- ASSERT_THAT(getsockname(fd1, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
- if (param.type == SOCK_STREAM) {
- ASSERT_THAT(listen(fd1, 1), SyscallSucceeds());
- }
-
- // j is less than 4 to check that the port reuse logic works correctly after
- // closing bound sockets.
- for (int j = 0; j < 4; j++) {
- const int portreuse2 = j % 2;
- auto s2 =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
- int fd2 = s2.get();
-
- EXPECT_THAT(
- setsockopt(fd2, SOL_SOCKET, SO_REUSEPORT, &portreuse2, sizeof(int)),
- SyscallSucceeds());
-
- std::cout << portreuse1 << " " << portreuse2;
- int ret = bind(fd2, reinterpret_cast<sockaddr*>(&addr), addrlen);
-
- // Verify that two sockets can be bound to the same port only if
- // SO_REUSEPORT is set for both of them.
- if (!portreuse1 || !portreuse2) {
- ASSERT_THAT(ret, SyscallFailsWithErrno(EADDRINUSE));
- } else {
- ASSERT_THAT(ret, SyscallSucceeds());
- }
- }
- }
-}
-
-// Check that when a socket was bound to an address with REUSEPORT and then
-// closed, we can bind a different socket to the same address without needing
-// REUSEPORT.
-TEST_P(SocketMultiProtocolInetLoopbackTest, NoReusePortFollowingReusePort) {
- auto const& param = GetParam();
- TestAddress const& test_addr = V4Loopback();
- sockaddr_storage addr = test_addr.addr;
-
- auto s = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
- int fd = s.get();
- socklen_t addrlen = test_addr.addr_len;
- int portreuse = 1;
- ASSERT_THAT(
- setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &portreuse, sizeof(portreuse)),
- SyscallSucceeds());
- ASSERT_THAT(bind(fd, reinterpret_cast<sockaddr*>(&addr), addrlen),
- SyscallSucceeds());
- ASSERT_THAT(getsockname(fd, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
- ASSERT_EQ(addrlen, test_addr.addr_len);
-
- s.reset();
-
- // Open a new socket and bind to the same address, but w/o REUSEPORT.
- s = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
- fd = s.get();
- portreuse = 0;
- ASSERT_THAT(
- setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &portreuse, sizeof(portreuse)),
- SyscallSucceeds());
- ASSERT_THAT(bind(fd, reinterpret_cast<sockaddr*>(&addr), addrlen),
- SyscallSucceeds());
-}
-
-INSTANTIATE_TEST_SUITE_P(
- AllFamlies, SocketMultiProtocolInetLoopbackTest,
- ::testing::Values(ProtocolTestParam{"TCP", SOCK_STREAM},
- ProtocolTestParam{"UDP", SOCK_DGRAM}),
- DescribeProtocolTestParam);
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ip_loopback_blocking.cc b/test/syscalls/linux/socket_ip_loopback_blocking.cc
deleted file mode 100644
index d7fc9715b..000000000
--- a/test/syscalls/linux/socket_ip_loopback_blocking.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <netinet/tcp.h>
-#include <vector>
-
-#include "test/syscalls/linux/ip_socket_test_util.h"
-#include "test/syscalls/linux/socket_blocking.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketPairKind> GetSocketPairs() {
- return VecCat<SocketPairKind>(
- std::vector<SocketPairKind>{
- IPv6UDPBidirectionalBindSocketPair(0),
- IPv4UDPBidirectionalBindSocketPair(0),
- },
- ApplyVecToVec<SocketPairKind>(
- std::vector<Middleware>{
- NoOp, SetSockOpt(IPPROTO_TCP, TCP_NODELAY, &kSockOptOn)},
- std::vector<SocketPairKind>{
- IPv6TCPAcceptBindSocketPair(0),
- IPv4TCPAcceptBindSocketPair(0),
- }));
-}
-
-INSTANTIATE_TEST_SUITE_P(
- BlockingIPSockets, BlockingSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ip_tcp_generic.cc b/test/syscalls/linux/socket_ip_tcp_generic.cc
deleted file mode 100644
index a43cf9bce..000000000
--- a/test/syscalls/linux/socket_ip_tcp_generic.cc
+++ /dev/null
@@ -1,701 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/socket_ip_tcp_generic.h"
-
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <poll.h>
-#include <stdio.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <sys/un.h>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-TEST_P(TCPSocketPairTest, TcpInfoSucceedes) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct tcp_info opt = {};
- socklen_t optLen = sizeof(opt);
- EXPECT_THAT(getsockopt(sockets->first_fd(), SOL_TCP, TCP_INFO, &opt, &optLen),
- SyscallSucceeds());
-}
-
-TEST_P(TCPSocketPairTest, ShortTcpInfoSucceedes) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct tcp_info opt = {};
- socklen_t optLen = 1;
- EXPECT_THAT(getsockopt(sockets->first_fd(), SOL_TCP, TCP_INFO, &opt, &optLen),
- SyscallSucceeds());
-}
-
-TEST_P(TCPSocketPairTest, ZeroTcpInfoSucceedes) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct tcp_info opt = {};
- socklen_t optLen = 0;
- EXPECT_THAT(getsockopt(sockets->first_fd(), SOL_TCP, TCP_INFO, &opt, &optLen),
- SyscallSucceeds());
-}
-
-// This test validates that an RST is sent instead of a FIN when data is
-// unread on calls to close(2).
-TEST_P(TCPSocketPairTest, RSTSentOnCloseWithUnreadData) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char buf[10] = {};
- ASSERT_THAT(RetryEINTR(write)(sockets->first_fd(), buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- // Wait until t_ sees the data on its side but don't read it.
- struct pollfd poll_fd = {sockets->second_fd(), POLLIN | POLLHUP, 0};
- constexpr int kPollTimeoutMs = 20000; // Wait up to 20 seconds for the data.
- ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs),
- SyscallSucceedsWithValue(1));
-
- // Now close the connected without reading the data.
- ASSERT_THAT(close(sockets->release_second_fd()), SyscallSucceeds());
-
- // Wait for the other end to receive the RST (up to 20 seconds).
- struct pollfd poll_fd2 = {sockets->first_fd(), POLLIN | POLLHUP, 0};
- ASSERT_THAT(RetryEINTR(poll)(&poll_fd2, 1, kPollTimeoutMs),
- SyscallSucceedsWithValue(1));
-
- // A shutdown with unread data will cause a RST to be sent instead
- // of a FIN, per RFC 2525 section 2.17; this is also what Linux does.
- ASSERT_THAT(RetryEINTR(read)(sockets->first_fd(), buf, sizeof(buf)),
- SyscallFailsWithErrno(ECONNRESET));
-}
-
-// This test will validate that a RST will cause POLLHUP to trigger.
-TEST_P(TCPSocketPairTest, RSTCausesPollHUP) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char buf[10] = {};
- ASSERT_THAT(RetryEINTR(write)(sockets->first_fd(), buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- // Wait until second sees the data on its side but don't read it.
- struct pollfd poll_fd = {sockets->second_fd(), POLLIN, 0};
- constexpr int kPollTimeoutMs = 20000; // Wait up to 20 seconds for the data.
- ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs),
- SyscallSucceedsWithValue(1));
- EXPECT_EQ(poll_fd.revents & POLLIN, POLLIN);
-
- // Confirm we at least have one unread byte.
- int bytes_available = 0;
- ASSERT_THAT(
- RetryEINTR(ioctl)(sockets->second_fd(), FIONREAD, &bytes_available),
- SyscallSucceeds());
- EXPECT_GT(bytes_available, 0);
-
- // Now close the connected socket without reading the data from the second,
- // this will cause a RST and we should see that with POLLHUP.
- ASSERT_THAT(close(sockets->release_second_fd()), SyscallSucceeds());
-
- // Wait for the other end to receive the RST (up to 20 seconds).
- struct pollfd poll_fd3 = {sockets->first_fd(), POLLHUP, 0};
- ASSERT_THAT(RetryEINTR(poll)(&poll_fd3, 1, kPollTimeoutMs),
- SyscallSucceedsWithValue(1));
- ASSERT_NE(poll_fd.revents & (POLLHUP | POLLIN), 0);
-}
-
-// This test validates that even if a RST is sent the other end will not
-// get an ECONNRESET until it's read all data.
-TEST_P(TCPSocketPairTest, RSTSentOnCloseWithUnreadDataAllowsReadBuffered) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char buf[10] = {};
- ASSERT_THAT(RetryEINTR(write)(sockets->first_fd(), buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
- ASSERT_THAT(RetryEINTR(write)(sockets->second_fd(), buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- // Wait until second sees the data on its side but don't read it.
- struct pollfd poll_fd = {sockets->second_fd(), POLLIN, 0};
- constexpr int kPollTimeoutMs = 30000; // Wait up to 30 seconds for the data.
- ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs),
- SyscallSucceedsWithValue(1));
-
- // Wait until first sees the data on its side but don't read it.
- struct pollfd poll_fd2 = {sockets->first_fd(), POLLIN, 0};
- ASSERT_THAT(RetryEINTR(poll)(&poll_fd2, 1, kPollTimeoutMs),
- SyscallSucceedsWithValue(1));
-
- // Now close the connected socket without reading the data from the second.
- ASSERT_THAT(close(sockets->release_second_fd()), SyscallSucceeds());
-
- // Wait for the other end to receive the RST (up to 30 seconds).
- struct pollfd poll_fd3 = {sockets->first_fd(), POLLHUP, 0};
- ASSERT_THAT(RetryEINTR(poll)(&poll_fd3, 1, kPollTimeoutMs),
- SyscallSucceedsWithValue(1));
-
- // Since we also have data buffered we should be able to read it before
- // the syscall will fail with ECONNRESET.
- ASSERT_THAT(RetryEINTR(read)(sockets->first_fd(), buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- // A shutdown with unread data will cause a RST to be sent instead
- // of a FIN, per RFC 2525 section 2.17; this is also what Linux does.
- ASSERT_THAT(RetryEINTR(read)(sockets->first_fd(), buf, sizeof(buf)),
- SyscallFailsWithErrno(ECONNRESET));
-}
-
-// This test will verify that a clean shutdown (FIN) is preformed when there
-// is unread data but only the write side is closed.
-TEST_P(TCPSocketPairTest, FINSentOnShutdownWrWithUnreadData) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char buf[10] = {};
- ASSERT_THAT(RetryEINTR(write)(sockets->first_fd(), buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- // Wait until t_ sees the data on its side but don't read it.
- struct pollfd poll_fd = {sockets->second_fd(), POLLIN | POLLHUP, 0};
- constexpr int kPollTimeoutMs = 20000; // Wait up to 20 seconds for the data.
- ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs),
- SyscallSucceedsWithValue(1));
-
- // Now shutdown the write end leaving the read end open.
- ASSERT_THAT(shutdown(sockets->second_fd(), SHUT_WR), SyscallSucceeds());
-
- // Wait for the other end to receive the FIN (up to 20 seconds).
- struct pollfd poll_fd2 = {sockets->first_fd(), POLLIN | POLLHUP, 0};
- ASSERT_THAT(RetryEINTR(poll)(&poll_fd2, 1, kPollTimeoutMs),
- SyscallSucceedsWithValue(1));
-
- // Since we didn't shutdown the read end this will be a clean close.
- ASSERT_THAT(RetryEINTR(read)(sockets->first_fd(), buf, sizeof(buf)),
- SyscallSucceedsWithValue(0));
-}
-
-// This test will verify that when data is received by a socket, even if it's
-// not read SHUT_RD will not cause any packets to be generated.
-TEST_P(TCPSocketPairTest, ShutdownRdShouldCauseNoPacketsWithUnreadData) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char buf[10] = {};
- ASSERT_THAT(RetryEINTR(write)(sockets->first_fd(), buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- // Wait until t_ sees the data on its side but don't read it.
- struct pollfd poll_fd = {sockets->second_fd(), POLLIN | POLLHUP, 0};
- constexpr int kPollTimeoutMs = 20000; // Wait up to 20 seconds for the data.
- ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs),
- SyscallSucceedsWithValue(1));
-
- // Now shutdown the read end, this will generate no packets to the other end.
- ASSERT_THAT(shutdown(sockets->second_fd(), SHUT_RD), SyscallSucceeds());
-
- // We should not receive any events on the other side of the socket.
- struct pollfd poll_fd2 = {sockets->first_fd(), POLLIN | POLLHUP, 0};
- constexpr int kPollNoResponseTimeoutMs = 3000;
- ASSERT_THAT(RetryEINTR(poll)(&poll_fd2, 1, kPollNoResponseTimeoutMs),
- SyscallSucceedsWithValue(0)); // Timeout.
-}
-
-// This test will verify that a socket which has unread data will still allow
-// the data to be read after shutting down the read side, and once there is no
-// unread data left, then read will return an EOF.
-TEST_P(TCPSocketPairTest, ShutdownRdAllowsReadOfReceivedDataBeforeEOF) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char buf[10] = {};
- ASSERT_THAT(RetryEINTR(write)(sockets->first_fd(), buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- // Wait until t_ sees the data on its side but don't read it.
- struct pollfd poll_fd = {sockets->second_fd(), POLLIN | POLLHUP, 0};
- constexpr int kPollTimeoutMs = 20000; // Wait up to 20 seconds for the data.
- ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs),
- SyscallSucceedsWithValue(1));
-
- // Now shutdown the read end.
- ASSERT_THAT(shutdown(sockets->second_fd(), SHUT_RD), SyscallSucceeds());
-
- // Even though we did a SHUT_RD on the read end we can still read the data.
- ASSERT_THAT(RetryEINTR(read)(sockets->second_fd(), buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- // After reading all of the data, reading the closed read end returns EOF.
- ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs),
- SyscallSucceedsWithValue(1));
- ASSERT_THAT(RetryEINTR(read)(sockets->second_fd(), buf, sizeof(buf)),
- SyscallSucceedsWithValue(0));
-}
-
-TEST_P(TCPSocketPairTest, ClosedReadNonBlockingSocket) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Set the read end to O_NONBLOCK.
- int opts = 0;
- ASSERT_THAT(opts = fcntl(sockets->second_fd(), F_GETFL), SyscallSucceeds());
- ASSERT_THAT(fcntl(sockets->second_fd(), F_SETFL, opts | O_NONBLOCK),
- SyscallSucceeds());
-
- char buf[10] = {};
- ASSERT_THAT(RetryEINTR(send)(sockets->first_fd(), buf, sizeof(buf), 0),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- // Wait until second_fd sees the data and then recv it.
- struct pollfd poll_fd = {sockets->second_fd(), POLLIN, 0};
- constexpr int kPollTimeoutMs = 2000; // Wait up to 2 seconds for the data.
- ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs),
- SyscallSucceedsWithValue(1));
-
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), buf, sizeof(buf), 0),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- // Now shutdown the write end leaving the read end open.
- ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds());
-
- // Wait for close notification and recv again.
- struct pollfd poll_fd2 = {sockets->second_fd(), POLLIN, 0};
- ASSERT_THAT(RetryEINTR(poll)(&poll_fd2, 1, kPollTimeoutMs),
- SyscallSucceedsWithValue(1));
-
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), buf, sizeof(buf), 0),
- SyscallSucceedsWithValue(0));
-}
-
-TEST_P(TCPSocketPairTest,
- ShutdownRdUnreadDataShouldCauseNoPacketsUnlessClosed) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char buf[10] = {};
- ASSERT_THAT(RetryEINTR(write)(sockets->first_fd(), buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- // Wait until t_ sees the data on its side but don't read it.
- struct pollfd poll_fd = {sockets->second_fd(), POLLIN | POLLHUP, 0};
- constexpr int kPollTimeoutMs = 20000; // Wait up to 20 seconds for the data.
- ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs),
- SyscallSucceedsWithValue(1));
-
- // Now shutdown the read end, this will generate no packets to the other end.
- ASSERT_THAT(shutdown(sockets->second_fd(), SHUT_RD), SyscallSucceeds());
-
- // We should not receive any events on the other side of the socket.
- struct pollfd poll_fd2 = {sockets->first_fd(), POLLIN | POLLHUP, 0};
- constexpr int kPollNoResponseTimeoutMs = 3000;
- ASSERT_THAT(RetryEINTR(poll)(&poll_fd2, 1, kPollNoResponseTimeoutMs),
- SyscallSucceedsWithValue(0)); // Timeout.
-
- // Now since we've fully closed the connection it will generate a RST.
- ASSERT_THAT(close(sockets->release_second_fd()), SyscallSucceeds());
- ASSERT_THAT(RetryEINTR(poll)(&poll_fd2, 1, kPollTimeoutMs),
- SyscallSucceedsWithValue(1)); // The other end has closed.
-
- // A shutdown with unread data will cause a RST to be sent instead
- // of a FIN, per RFC 2525 section 2.17; this is also what Linux does.
- ASSERT_THAT(RetryEINTR(read)(sockets->first_fd(), buf, sizeof(buf)),
- SyscallFailsWithErrno(ECONNRESET));
-}
-
-TEST_P(TCPSocketPairTest, TCPCorkDefault) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- int get = -1;
- socklen_t get_len = sizeof(get);
- EXPECT_THAT(
- getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CORK, &get, &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, kSockOptOff);
-}
-
-TEST_P(TCPSocketPairTest, SetTCPCork) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CORK,
- &kSockOptOn, sizeof(kSockOptOn)),
- SyscallSucceeds());
-
- int get = -1;
- socklen_t get_len = sizeof(get);
- EXPECT_THAT(
- getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CORK, &get, &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, kSockOptOn);
-
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CORK,
- &kSockOptOff, sizeof(kSockOptOff)),
- SyscallSucceeds());
-
- EXPECT_THAT(
- getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CORK, &get, &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, kSockOptOff);
-}
-
-TEST_P(TCPSocketPairTest, TCPCork) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CORK,
- &kSockOptOn, sizeof(kSockOptOn)),
- SyscallSucceeds());
-
- constexpr char kData[] = "abc";
- ASSERT_THAT(WriteFd(sockets->first_fd(), kData, sizeof(kData)),
- SyscallSucceedsWithValue(sizeof(kData)));
-
- ASSERT_NO_FATAL_FAILURE(RecvNoData(sockets->second_fd()));
-
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CORK,
- &kSockOptOff, sizeof(kSockOptOff)),
- SyscallSucceeds());
-
- // Create a receive buffer larger than kData.
- char buf[(sizeof(kData) + 1) * 2] = {};
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), buf, sizeof(buf), 0),
- SyscallSucceedsWithValue(sizeof(kData)));
- EXPECT_EQ(absl::string_view(kData, sizeof(kData)),
- absl::string_view(buf, sizeof(kData)));
-}
-
-TEST_P(TCPSocketPairTest, TCPQuickAckDefault) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- int get = -1;
- socklen_t get_len = sizeof(get);
- EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_QUICKACK, &get,
- &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, kSockOptOn);
-}
-
-TEST_P(TCPSocketPairTest, SetTCPQuickAck) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_QUICKACK,
- &kSockOptOff, sizeof(kSockOptOff)),
- SyscallSucceeds());
-
- int get = -1;
- socklen_t get_len = sizeof(get);
- EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_QUICKACK, &get,
- &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, kSockOptOff);
-
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_QUICKACK,
- &kSockOptOn, sizeof(kSockOptOn)),
- SyscallSucceeds());
-
- EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_QUICKACK, &get,
- &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, kSockOptOn);
-}
-
-TEST_P(TCPSocketPairTest, SoKeepaliveDefault) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- int get = -1;
- socklen_t get_len = sizeof(get);
- EXPECT_THAT(
- getsockopt(sockets->first_fd(), SOL_SOCKET, SO_KEEPALIVE, &get, &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, kSockOptOff);
-}
-
-TEST_P(TCPSocketPairTest, SetSoKeepalive) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_KEEPALIVE,
- &kSockOptOn, sizeof(kSockOptOn)),
- SyscallSucceeds());
-
- int get = -1;
- socklen_t get_len = sizeof(get);
- EXPECT_THAT(
- getsockopt(sockets->first_fd(), SOL_SOCKET, SO_KEEPALIVE, &get, &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, kSockOptOn);
-
- ASSERT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_KEEPALIVE,
- &kSockOptOff, sizeof(kSockOptOff)),
- SyscallSucceeds());
-
- EXPECT_THAT(
- getsockopt(sockets->first_fd(), SOL_SOCKET, SO_KEEPALIVE, &get, &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, kSockOptOff);
-}
-
-TEST_P(TCPSocketPairTest, TCPKeepidleDefault) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- int get = -1;
- socklen_t get_len = sizeof(get);
- EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPIDLE, &get,
- &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, 2 * 60 * 60); // 2 hours.
-}
-
-TEST_P(TCPSocketPairTest, TCPKeepintvlDefault) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- int get = -1;
- socklen_t get_len = sizeof(get);
- EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPINTVL, &get,
- &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, 75); // 75 seconds.
-}
-
-TEST_P(TCPSocketPairTest, SetTCPKeepidleZero) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- constexpr int kZero = 0;
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPIDLE, &kZero,
- sizeof(kZero)),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_P(TCPSocketPairTest, SetTCPKeepintvlZero) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- constexpr int kZero = 0;
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPINTVL,
- &kZero, sizeof(kZero)),
- SyscallFailsWithErrno(EINVAL));
-}
-
-// Copied from include/net/tcp.h.
-constexpr int MAX_TCP_KEEPIDLE = 32767;
-constexpr int MAX_TCP_KEEPINTVL = 32767;
-
-TEST_P(TCPSocketPairTest, SetTCPKeepidleAboveMax) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- constexpr int kAboveMax = MAX_TCP_KEEPIDLE + 1;
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPIDLE,
- &kAboveMax, sizeof(kAboveMax)),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_P(TCPSocketPairTest, SetTCPKeepintvlAboveMax) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- constexpr int kAboveMax = MAX_TCP_KEEPINTVL + 1;
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPINTVL,
- &kAboveMax, sizeof(kAboveMax)),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_P(TCPSocketPairTest, SetTCPKeepidleToMax) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPIDLE,
- &MAX_TCP_KEEPIDLE, sizeof(MAX_TCP_KEEPIDLE)),
- SyscallSucceedsWithValue(0));
-
- int get = -1;
- socklen_t get_len = sizeof(get);
- EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPIDLE, &get,
- &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, MAX_TCP_KEEPIDLE);
-}
-
-TEST_P(TCPSocketPairTest, SetTCPKeepintvlToMax) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPINTVL,
- &MAX_TCP_KEEPINTVL, sizeof(MAX_TCP_KEEPINTVL)),
- SyscallSucceedsWithValue(0));
-
- int get = -1;
- socklen_t get_len = sizeof(get);
- EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPINTVL, &get,
- &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, MAX_TCP_KEEPINTVL);
-}
-
-TEST_P(TCPSocketPairTest, SetOOBInline) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- EXPECT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_OOBINLINE,
- &kSockOptOn, sizeof(kSockOptOn)),
- SyscallSucceeds());
-
- int get = -1;
- socklen_t get_len = sizeof(get);
- EXPECT_THAT(
- getsockopt(sockets->first_fd(), SOL_SOCKET, SO_OOBINLINE, &get, &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, kSockOptOn);
-}
-
-TEST_P(TCPSocketPairTest, MsgTruncMsgPeek) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[512];
- RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- // Read half of the data with MSG_TRUNC | MSG_PEEK. This way there will still
- // be some data left to read in the next step even if the data gets consumed.
- char received_data1[sizeof(sent_data) / 2] = {};
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data1,
- sizeof(received_data1), MSG_TRUNC | MSG_PEEK),
- SyscallSucceedsWithValue(sizeof(received_data1)));
-
- // Check that we didn't get anything.
- char zeros[sizeof(received_data1)] = {};
- EXPECT_EQ(0, memcmp(zeros, received_data1, sizeof(received_data1)));
-
- // Check that all of the data is still there.
- char received_data2[sizeof(sent_data)] = {};
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data2,
- sizeof(received_data2), 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- EXPECT_EQ(0, memcmp(received_data2, sent_data, sizeof(sent_data)));
-}
-
-TEST_P(TCPSocketPairTest, SetCongestionControlSucceedsForSupported) {
- // This is Linux's net/tcp.h TCP_CA_NAME_MAX.
- const int kTcpCaNameMax = 16;
-
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- // Netstack only supports reno & cubic so we only test these two values here.
- {
- const char kSetCC[kTcpCaNameMax] = "reno";
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CONGESTION,
- &kSetCC, strlen(kSetCC)),
- SyscallSucceedsWithValue(0));
-
- char got_cc[kTcpCaNameMax];
- memset(got_cc, '1', sizeof(got_cc));
- socklen_t optlen = sizeof(got_cc);
- ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CONGESTION,
- &got_cc, &optlen),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(0, memcmp(got_cc, kSetCC, sizeof(kSetCC)));
- }
- {
- const char kSetCC[kTcpCaNameMax] = "cubic";
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CONGESTION,
- &kSetCC, strlen(kSetCC)),
- SyscallSucceedsWithValue(0));
-
- char got_cc[kTcpCaNameMax];
- memset(got_cc, '1', sizeof(got_cc));
- socklen_t optlen = sizeof(got_cc);
- ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CONGESTION,
- &got_cc, &optlen),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(0, memcmp(got_cc, kSetCC, sizeof(kSetCC)));
- }
-}
-
-TEST_P(TCPSocketPairTest, SetGetTCPCongestionShortReadBuffer) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- {
- // Verify that getsockopt/setsockopt work with buffers smaller than
- // kTcpCaNameMax.
- const char kSetCC[] = "cubic";
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CONGESTION,
- &kSetCC, strlen(kSetCC)),
- SyscallSucceedsWithValue(0));
-
- char got_cc[sizeof(kSetCC)];
- socklen_t optlen = sizeof(got_cc);
- ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CONGESTION,
- &got_cc, &optlen),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(0, memcmp(got_cc, kSetCC, sizeof(got_cc)));
- }
-}
-
-TEST_P(TCPSocketPairTest, SetGetTCPCongestionLargeReadBuffer) {
- // This is Linux's net/tcp.h TCP_CA_NAME_MAX.
- const int kTcpCaNameMax = 16;
-
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- {
- // Verify that getsockopt works with buffers larger than
- // kTcpCaNameMax.
- const char kSetCC[] = "cubic";
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CONGESTION,
- &kSetCC, strlen(kSetCC)),
- SyscallSucceedsWithValue(0));
-
- char got_cc[kTcpCaNameMax + 5];
- socklen_t optlen = sizeof(got_cc);
- ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CONGESTION,
- &got_cc, &optlen),
- SyscallSucceedsWithValue(0));
- // Linux copies the minimum of kTcpCaNameMax or the length of the passed in
- // buffer and sets optlen to the number of bytes actually copied
- // irrespective of the actual length of the congestion control name.
- EXPECT_EQ(kTcpCaNameMax, optlen);
- EXPECT_EQ(0, memcmp(got_cc, kSetCC, sizeof(kSetCC)));
- }
-}
-
-TEST_P(TCPSocketPairTest, SetCongestionControlFailsForUnsupported) {
- // This is Linux's net/tcp.h TCP_CA_NAME_MAX.
- const int kTcpCaNameMax = 16;
-
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- char old_cc[kTcpCaNameMax];
- socklen_t optlen = sizeof(old_cc);
- ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CONGESTION,
- &old_cc, &optlen),
- SyscallSucceedsWithValue(0));
-
- const char kSetCC[] = "invalid_ca_cc";
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CONGESTION,
- &kSetCC, strlen(kSetCC)),
- SyscallFailsWithErrno(ENOENT));
-
- char got_cc[kTcpCaNameMax];
- optlen = sizeof(got_cc);
- ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CONGESTION,
- &got_cc, &optlen),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(0, memcmp(got_cc, old_cc, sizeof(old_cc)));
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ip_tcp_generic.h b/test/syscalls/linux/socket_ip_tcp_generic.h
deleted file mode 100644
index a3eff3c73..000000000
--- a/test/syscalls/linux/socket_ip_tcp_generic.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_TCP_GENERIC_H_
-#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_TCP_GENERIC_H_
-
-#include "test/syscalls/linux/socket_test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Test fixture for tests that apply to pairs of connected TCP sockets.
-using TCPSocketPairTest = SocketPairTest;
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_TCP_GENERIC_H_
diff --git a/test/syscalls/linux/socket_ip_tcp_generic_loopback.cc b/test/syscalls/linux/socket_ip_tcp_generic_loopback.cc
deleted file mode 100644
index 0dc274e2d..000000000
--- a/test/syscalls/linux/socket_ip_tcp_generic_loopback.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <netinet/tcp.h>
-#include <vector>
-
-#include "test/syscalls/linux/ip_socket_test_util.h"
-#include "test/syscalls/linux/socket_ip_tcp_generic.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketPairKind> GetSocketPairs() {
- return ApplyVecToVec<SocketPairKind>(
- std::vector<Middleware>{
- NoOp, SetSockOpt(IPPROTO_TCP, TCP_NODELAY, &kSockOptOn)},
- std::vector<SocketPairKind>{
- IPv6TCPAcceptBindSocketPair(0),
- IPv4TCPAcceptBindSocketPair(0),
- DualStackTCPAcceptBindSocketPair(0),
- });
-}
-
-INSTANTIATE_TEST_SUITE_P(
- AllTCPSockets, TCPSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ip_tcp_loopback.cc b/test/syscalls/linux/socket_ip_tcp_loopback.cc
deleted file mode 100644
index 831de53b8..000000000
--- a/test/syscalls/linux/socket_ip_tcp_loopback.cc
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <vector>
-
-#include "test/syscalls/linux/ip_socket_test_util.h"
-#include "test/syscalls/linux/socket_generic.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketPairKind> GetSocketPairs() {
- return {
- IPv6TCPAcceptBindSocketPair(0),
- IPv4TCPAcceptBindSocketPair(0),
- DualStackTCPAcceptBindSocketPair(0),
- };
-}
-
-INSTANTIATE_TEST_SUITE_P(
- AllUnixDomainSockets, AllSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ip_tcp_loopback_blocking.cc b/test/syscalls/linux/socket_ip_tcp_loopback_blocking.cc
deleted file mode 100644
index cd3ad97d0..000000000
--- a/test/syscalls/linux/socket_ip_tcp_loopback_blocking.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <netinet/tcp.h>
-#include <vector>
-
-#include "test/syscalls/linux/ip_socket_test_util.h"
-#include "test/syscalls/linux/socket_stream_blocking.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketPairKind> GetSocketPairs() {
- return ApplyVecToVec<SocketPairKind>(
- std::vector<Middleware>{
- NoOp, SetSockOpt(IPPROTO_TCP, TCP_NODELAY, &kSockOptOn)},
- std::vector<SocketPairKind>{
- IPv6TCPAcceptBindSocketPair(0),
- IPv4TCPAcceptBindSocketPair(0),
- DualStackTCPAcceptBindSocketPair(0),
- });
-}
-
-INSTANTIATE_TEST_SUITE_P(
- BlockingTCPSockets, BlockingStreamSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ip_tcp_loopback_nonblock.cc b/test/syscalls/linux/socket_ip_tcp_loopback_nonblock.cc
deleted file mode 100644
index 1acdecc17..000000000
--- a/test/syscalls/linux/socket_ip_tcp_loopback_nonblock.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <netinet/tcp.h>
-#include <vector>
-
-#include "test/syscalls/linux/ip_socket_test_util.h"
-#include "test/syscalls/linux/socket_non_blocking.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketPairKind> GetSocketPairs() {
- return ApplyVecToVec<SocketPairKind>(
- std::vector<Middleware>{
- NoOp, SetSockOpt(IPPROTO_TCP, TCP_NODELAY, &kSockOptOn)},
- std::vector<SocketPairKind>{
- IPv6TCPAcceptBindSocketPair(SOCK_NONBLOCK),
- IPv4TCPAcceptBindSocketPair(SOCK_NONBLOCK),
- });
-}
-
-INSTANTIATE_TEST_SUITE_P(
- NonBlockingTCPSockets, NonBlockingSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ip_tcp_udp_generic.cc b/test/syscalls/linux/socket_ip_tcp_udp_generic.cc
deleted file mode 100644
index de63f79d9..000000000
--- a/test/syscalls/linux/socket_ip_tcp_udp_generic.cc
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <poll.h>
-#include <stdio.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <sys/un.h>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/ip_socket_test_util.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// Test fixture for tests that apply to pairs of TCP and UDP sockets.
-using TcpUdpSocketPairTest = SocketPairTest;
-
-TEST_P(TcpUdpSocketPairTest, ShutdownWrFollowedBySendIsError) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Now shutdown the write end of the first.
- ASSERT_THAT(shutdown(sockets->first_fd(), SHUT_WR), SyscallSucceeds());
-
- char buf[10] = {};
- ASSERT_THAT(RetryEINTR(send)(sockets->first_fd(), buf, sizeof(buf), 0),
- SyscallFailsWithErrno(EPIPE));
-}
-
-std::vector<SocketPairKind> GetSocketPairs() {
- return VecCat<SocketPairKind>(
- ApplyVec<SocketPairKind>(
- IPv6UDPBidirectionalBindSocketPair,
- AllBitwiseCombinations(List<int>{0, SOCK_NONBLOCK})),
- ApplyVec<SocketPairKind>(
- IPv4UDPBidirectionalBindSocketPair,
- AllBitwiseCombinations(List<int>{0, SOCK_NONBLOCK})),
- ApplyVec<SocketPairKind>(
- DualStackUDPBidirectionalBindSocketPair,
- AllBitwiseCombinations(List<int>{0, SOCK_NONBLOCK})),
- ApplyVec<SocketPairKind>(
- IPv6TCPAcceptBindSocketPair,
- AllBitwiseCombinations(List<int>{0, SOCK_NONBLOCK})),
- ApplyVec<SocketPairKind>(
- IPv4TCPAcceptBindSocketPair,
- AllBitwiseCombinations(List<int>{0, SOCK_NONBLOCK})),
- ApplyVec<SocketPairKind>(
- DualStackTCPAcceptBindSocketPair,
- AllBitwiseCombinations(List<int>{0, SOCK_NONBLOCK})));
-}
-
-INSTANTIATE_TEST_SUITE_P(
- AllIPSockets, TcpUdpSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ip_udp_generic.cc b/test/syscalls/linux/socket_ip_udp_generic.cc
deleted file mode 100644
index 044394ba7..000000000
--- a/test/syscalls/linux/socket_ip_udp_generic.cc
+++ /dev/null
@@ -1,214 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/socket_ip_udp_generic.h"
-
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <poll.h>
-#include <stdio.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <sys/un.h>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-TEST_P(UDPSocketPairTest, MulticastTTLDefault) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- int get = -1;
- socklen_t get_len = sizeof(get);
- EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL,
- &get, &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, 1);
-}
-
-TEST_P(UDPSocketPairTest, SetUDPMulticastTTLMin) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- constexpr int kMin = 0;
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL,
- &kMin, sizeof(kMin)),
- SyscallSucceeds());
-
- int get = -1;
- socklen_t get_len = sizeof(get);
- EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL,
- &get, &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, kMin);
-}
-
-TEST_P(UDPSocketPairTest, SetUDPMulticastTTLMax) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- constexpr int kMax = 255;
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL,
- &kMax, sizeof(kMax)),
- SyscallSucceeds());
-
- int get = -1;
- socklen_t get_len = sizeof(get);
- EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL,
- &get, &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, kMax);
-}
-
-TEST_P(UDPSocketPairTest, SetUDPMulticastTTLNegativeOne) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- constexpr int kArbitrary = 6;
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL,
- &kArbitrary, sizeof(kArbitrary)),
- SyscallSucceeds());
-
- constexpr int kNegOne = -1;
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL,
- &kNegOne, sizeof(kNegOne)),
- SyscallSucceeds());
-
- int get = -1;
- socklen_t get_len = sizeof(get);
- EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL,
- &get, &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, 1);
-}
-
-TEST_P(UDPSocketPairTest, SetUDPMulticastTTLBelowMin) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- constexpr int kBelowMin = -2;
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL,
- &kBelowMin, sizeof(kBelowMin)),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_P(UDPSocketPairTest, SetUDPMulticastTTLAboveMax) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- constexpr int kAboveMax = 256;
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL,
- &kAboveMax, sizeof(kAboveMax)),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_P(UDPSocketPairTest, SetUDPMulticastTTLChar) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- constexpr char kArbitrary = 6;
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL,
- &kArbitrary, sizeof(kArbitrary)),
- SyscallSucceeds());
-
- int get = -1;
- socklen_t get_len = sizeof(get);
- EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL,
- &get, &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, kArbitrary);
-}
-
-TEST_P(UDPSocketPairTest, SetEmptyIPAddMembership) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct ip_mreqn req = {};
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &req, sizeof(req)),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_P(UDPSocketPairTest, MulticastLoopDefault) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- int get = -1;
- socklen_t get_len = sizeof(get);
- EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_LOOP,
- &get, &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, kSockOptOn);
-}
-
-TEST_P(UDPSocketPairTest, SetMulticastLoop) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_LOOP,
- &kSockOptOff, sizeof(kSockOptOff)),
- SyscallSucceeds());
-
- int get = -1;
- socklen_t get_len = sizeof(get);
- EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_LOOP,
- &get, &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, kSockOptOff);
-
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_LOOP,
- &kSockOptOn, sizeof(kSockOptOn)),
- SyscallSucceeds());
-
- EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_LOOP,
- &get, &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, kSockOptOn);
-}
-
-TEST_P(UDPSocketPairTest, SetMulticastLoopChar) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- constexpr char kSockOptOnChar = kSockOptOn;
- constexpr char kSockOptOffChar = kSockOptOff;
-
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_LOOP,
- &kSockOptOffChar, sizeof(kSockOptOffChar)),
- SyscallSucceeds());
-
- int get = -1;
- socklen_t get_len = sizeof(get);
- EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_LOOP,
- &get, &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, kSockOptOff);
-
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_LOOP,
- &kSockOptOnChar, sizeof(kSockOptOnChar)),
- SyscallSucceeds());
-
- EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_LOOP,
- &get, &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, kSockOptOn);
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ip_udp_generic.h b/test/syscalls/linux/socket_ip_udp_generic.h
deleted file mode 100644
index 106c54e9f..000000000
--- a/test/syscalls/linux/socket_ip_udp_generic.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_UDP_GENERIC_H_
-#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_UDP_GENERIC_H_
-
-#include "test/syscalls/linux/socket_test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Test fixture for tests that apply to pairs of connected UDP sockets.
-using UDPSocketPairTest = SocketPairTest;
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_UDP_GENERIC_H_
diff --git a/test/syscalls/linux/socket_ip_udp_loopback.cc b/test/syscalls/linux/socket_ip_udp_loopback.cc
deleted file mode 100644
index 1df74a348..000000000
--- a/test/syscalls/linux/socket_ip_udp_loopback.cc
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <vector>
-
-#include "test/syscalls/linux/ip_socket_test_util.h"
-#include "test/syscalls/linux/socket_generic.h"
-#include "test/syscalls/linux/socket_ip_udp_generic.h"
-#include "test/syscalls/linux/socket_non_stream.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketPairKind> GetSocketPairs() {
- return {
- IPv6UDPBidirectionalBindSocketPair(0),
- IPv4UDPBidirectionalBindSocketPair(0),
- DualStackUDPBidirectionalBindSocketPair(0),
- };
-}
-
-INSTANTIATE_TEST_SUITE_P(
- AllUDPSockets, AllSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-INSTANTIATE_TEST_SUITE_P(
- AllUDPSockets, NonStreamSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-INSTANTIATE_TEST_SUITE_P(
- AllUDPSockets, UDPSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ip_udp_loopback_blocking.cc b/test/syscalls/linux/socket_ip_udp_loopback_blocking.cc
deleted file mode 100644
index 1e259efa7..000000000
--- a/test/syscalls/linux/socket_ip_udp_loopback_blocking.cc
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <vector>
-
-#include "test/syscalls/linux/ip_socket_test_util.h"
-#include "test/syscalls/linux/socket_non_stream_blocking.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketPairKind> GetSocketPairs() {
- return {
- IPv6UDPBidirectionalBindSocketPair(0),
- IPv4UDPBidirectionalBindSocketPair(0),
- };
-}
-
-INSTANTIATE_TEST_SUITE_P(
- BlockingUDPSockets, BlockingNonStreamSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ip_udp_loopback_nonblock.cc b/test/syscalls/linux/socket_ip_udp_loopback_nonblock.cc
deleted file mode 100644
index 74cbd326d..000000000
--- a/test/syscalls/linux/socket_ip_udp_loopback_nonblock.cc
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <vector>
-
-#include "test/syscalls/linux/ip_socket_test_util.h"
-#include "test/syscalls/linux/socket_non_blocking.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketPairKind> GetSocketPairs() {
- return {
- IPv6UDPBidirectionalBindSocketPair(SOCK_NONBLOCK),
- IPv4UDPBidirectionalBindSocketPair(SOCK_NONBLOCK),
- };
-}
-
-INSTANTIATE_TEST_SUITE_P(
- NonBlockingUDPSockets, NonBlockingSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.cc b/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.cc
deleted file mode 100644
index 3a068aacf..000000000
--- a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.cc
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h"
-
-#include <netinet/in.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <sys/un.h>
-#include <cstdio>
-#include <cstring>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Verifies that a newly instantiated TCP socket does not have the
-// broadcast socket option enabled.
-TEST_P(IPv4TCPUnboundExternalNetworkingSocketTest, TCPBroadcastDefault) {
- auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- int get = -1;
- socklen_t get_sz = sizeof(get);
- EXPECT_THAT(
- getsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &get, &get_sz),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get, kSockOptOff);
- EXPECT_EQ(get_sz, sizeof(get));
-}
-
-// Verifies that a newly instantiated TCP socket returns true after enabling
-// the broadcast socket option.
-TEST_P(IPv4TCPUnboundExternalNetworkingSocketTest, SetTCPBroadcast) {
- auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- EXPECT_THAT(setsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &kSockOptOn,
- sizeof(kSockOptOn)),
- SyscallSucceedsWithValue(0));
-
- int get = -1;
- socklen_t get_sz = sizeof(get);
- EXPECT_THAT(
- getsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &get, &get_sz),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get, kSockOptOn);
- EXPECT_EQ(get_sz, sizeof(get));
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h b/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h
deleted file mode 100644
index fb582b224..000000000
--- a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_TCP_UNBOUND_EXTERNAL_NETWORKING_H_
-#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_TCP_UNBOUND_EXTERNAL_NETWORKING_H_
-
-#include "test/syscalls/linux/socket_test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Test fixture for tests that apply to unbound IPv4 TCP sockets in a sandbox
-// with external networking support.
-using IPv4TCPUnboundExternalNetworkingSocketTest = SimpleSocketTest;
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_TCP_UNBOUND_EXTERNAL_NETWORKING_H_
diff --git a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking_test.cc b/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking_test.cc
deleted file mode 100644
index 92f03e045..000000000
--- a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking_test.cc
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <vector>
-
-#include "test/syscalls/linux/ip_socket_test_util.h"
-#include "test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketKind> GetSockets() {
- return ApplyVec<SocketKind>(
- IPv4TCPUnboundSocket,
- AllBitwiseCombinations(List<int>{0, SOCK_NONBLOCK}));
-}
-
-INSTANTIATE_TEST_SUITE_P(IPv4TCPUnboundSockets,
- IPv4TCPUnboundExternalNetworkingSocketTest,
- ::testing::ValuesIn(GetSockets()));
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound.cc b/test/syscalls/linux/socket_ipv4_udp_unbound.cc
deleted file mode 100644
index 67d29af0a..000000000
--- a/test/syscalls/linux/socket_ipv4_udp_unbound.cc
+++ /dev/null
@@ -1,1684 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/socket_ipv4_udp_unbound.h"
-
-#include <arpa/inet.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <cstdio>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/ip_socket_test_util.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-constexpr char kMulticastAddress[] = "224.0.2.1";
-constexpr char kBroadcastAddress[] = "255.255.255.255";
-
-TestAddress V4Multicast() {
- TestAddress t("V4Multicast");
- t.addr.ss_family = AF_INET;
- t.addr_len = sizeof(sockaddr_in);
- reinterpret_cast<sockaddr_in*>(&t.addr)->sin_addr.s_addr =
- inet_addr(kMulticastAddress);
- return t;
-}
-
-TestAddress V4Broadcast() {
- TestAddress t("V4Broadcast");
- t.addr.ss_family = AF_INET;
- t.addr_len = sizeof(sockaddr_in);
- reinterpret_cast<sockaddr_in*>(&t.addr)->sin_addr.s_addr =
- inet_addr(kBroadcastAddress);
- return t;
-}
-
-// Check that packets are not received without a group membership. Default send
-// interface configured by bind.
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackNoGroup) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Bind the first FD to the loopback. This is an alternative to
- // IP_MULTICAST_IF for setting the default send interface.
- auto sender_addr = V4Loopback();
- EXPECT_THAT(
- bind(sockets->first_fd(), reinterpret_cast<sockaddr*>(&sender_addr.addr),
- sender_addr.addr_len),
- SyscallSucceeds());
-
- // Bind the second FD to the v4 any address. If multicast worked like unicast,
- // this would ensure that we get the packet.
- auto receiver_addr = V4Any();
- EXPECT_THAT(bind(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Send the multicast packet.
- auto send_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- EXPECT_THAT(
- RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&send_addr.addr),
- send_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we did not receive the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- EXPECT_THAT(RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf),
- MSG_DONTWAIT),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-// Check that not setting a default send interface prevents multicast packets
-// from being sent. Group membership interface configured by address.
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackAddrNoDefaultSendIf) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Bind the second FD to the v4 any address to ensure that we can receive any
- // unicast packet.
- auto receiver_addr = V4Any();
- EXPECT_THAT(bind(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Register to receive multicast packets.
- ip_mreq group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- group.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
- EXPECT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
-
- // Send a multicast packet.
- auto send_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- EXPECT_THAT(
- RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&send_addr.addr),
- send_addr.addr_len),
- SyscallFailsWithErrno(ENETUNREACH));
-}
-
-// Check that not setting a default send interface prevents multicast packets
-// from being sent. Group membership interface configured by NIC ID.
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackNicNoDefaultSendIf) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Bind the second FD to the v4 any address to ensure that we can receive any
- // unicast packet.
- auto receiver_addr = V4Any();
- ASSERT_THAT(bind(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Register to receive multicast packets.
- ip_mreqn group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo"));
- EXPECT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
-
- // Send a multicast packet.
- auto send_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- EXPECT_THAT(
- RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&send_addr.addr),
- send_addr.addr_len),
- SyscallFailsWithErrno(ENETUNREACH));
-}
-
-// Check that multicast works when the default send interface is configured by
-// bind and the group membership is configured by address.
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackAddr) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Bind the first FD to the loopback. This is an alternative to
- // IP_MULTICAST_IF for setting the default send interface.
- auto sender_addr = V4Loopback();
- ASSERT_THAT(
- bind(sockets->first_fd(), reinterpret_cast<sockaddr*>(&sender_addr.addr),
- sender_addr.addr_len),
- SyscallSucceeds());
-
- // Bind the second FD to the v4 any address to ensure that we can receive the
- // multicast packet.
- auto receiver_addr = V4Any();
- ASSERT_THAT(bind(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Register to receive multicast packets.
- ip_mreq group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- group.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
- ASSERT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
-
- // Send a multicast packet.
- auto send_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(
- RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&send_addr.addr),
- send_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we received the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(
- RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf), 0),
- SyscallSucceedsWithValue(sizeof(recv_buf)));
-
- EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
-}
-
-// Check that multicast works when the default send interface is configured by
-// bind and the group membership is configured by NIC ID.
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackNic) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Bind the first FD to the loopback. This is an alternative to
- // IP_MULTICAST_IF for setting the default send interface.
- auto sender_addr = V4Loopback();
- ASSERT_THAT(
- bind(sockets->first_fd(), reinterpret_cast<sockaddr*>(&sender_addr.addr),
- sender_addr.addr_len),
- SyscallSucceeds());
-
- // Bind the second FD to the v4 any address to ensure that we can receive the
- // multicast packet.
- auto receiver_addr = V4Any();
- ASSERT_THAT(bind(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Register to receive multicast packets.
- ip_mreqn group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo"));
- ASSERT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
-
- // Send a multicast packet.
- auto send_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(
- RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&send_addr.addr),
- send_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we received the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(
- RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf), 0),
- SyscallSucceedsWithValue(sizeof(recv_buf)));
-
- EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
-}
-
-// Check that multicast works when the default send interface is configured by
-// IP_MULTICAST_IF, the send address is specified in sendto, and the group
-// membership is configured by address.
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackIfAddr) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Set the default send interface.
- ip_mreq iface = {};
- iface.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF,
- &iface, sizeof(iface)),
- SyscallSucceeds());
-
- // Bind the second FD to the v4 any address to ensure that we can receive the
- // multicast packet.
- auto receiver_addr = V4Any();
- ASSERT_THAT(bind(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Register to receive multicast packets.
- ip_mreq group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- group.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
- ASSERT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
-
- // Send a multicast packet.
- auto send_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(
- RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&send_addr.addr),
- send_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we received the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(
- RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf), 0),
- SyscallSucceedsWithValue(sizeof(recv_buf)));
-
- EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
-}
-
-// Check that multicast works when the default send interface is configured by
-// IP_MULTICAST_IF, the send address is specified in sendto, and the group
-// membership is configured by NIC ID.
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackIfNic) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Set the default send interface.
- ip_mreqn iface = {};
- iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo"));
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF,
- &iface, sizeof(iface)),
- SyscallSucceeds());
-
- // Bind the second FD to the v4 any address to ensure that we can receive the
- // multicast packet.
- auto receiver_addr = V4Any();
- ASSERT_THAT(bind(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Register to receive multicast packets.
- ip_mreqn group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo"));
- ASSERT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
-
- // Send a multicast packet.
- auto send_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(
- RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&send_addr.addr),
- send_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we received the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(
- RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf), 0),
- SyscallSucceedsWithValue(sizeof(recv_buf)));
-
- EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
-}
-
-// Check that multicast works when the default send interface is configured by
-// IP_MULTICAST_IF, the send address is specified in connect, and the group
-// membership is configured by address.
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackIfAddrConnect) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Set the default send interface.
- ip_mreq iface = {};
- iface.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF,
- &iface, sizeof(iface)),
- SyscallSucceeds());
-
- // Bind the second FD to the v4 any address to ensure that we can receive the
- // multicast packet.
- auto receiver_addr = V4Any();
- ASSERT_THAT(bind(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Register to receive multicast packets.
- ip_mreq group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- group.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
- ASSERT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
-
- // Send a multicast packet.
- auto connect_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&connect_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- ASSERT_THAT(
- RetryEINTR(connect)(sockets->first_fd(),
- reinterpret_cast<sockaddr*>(&connect_addr.addr),
- connect_addr.addr_len),
- SyscallSucceeds());
-
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), send_buf, sizeof(send_buf), 0),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we received the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(
- RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf), 0),
- SyscallSucceedsWithValue(sizeof(recv_buf)));
-
- EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
-}
-
-// Check that multicast works when the default send interface is configured by
-// IP_MULTICAST_IF, the send address is specified in connect, and the group
-// membership is configured by NIC ID.
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackIfNicConnect) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Set the default send interface.
- ip_mreqn iface = {};
- iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo"));
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF,
- &iface, sizeof(iface)),
- SyscallSucceeds());
-
- // Bind the second FD to the v4 any address to ensure that we can receive the
- // multicast packet.
- auto receiver_addr = V4Any();
- ASSERT_THAT(bind(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Register to receive multicast packets.
- ip_mreqn group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo"));
- ASSERT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
-
- // Send a multicast packet.
- auto connect_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&connect_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- ASSERT_THAT(
- RetryEINTR(connect)(sockets->first_fd(),
- reinterpret_cast<sockaddr*>(&connect_addr.addr),
- connect_addr.addr_len),
- SyscallSucceeds());
-
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), send_buf, sizeof(send_buf), 0),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we received the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(
- RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf), 0),
- SyscallSucceedsWithValue(sizeof(recv_buf)));
-
- EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
-}
-
-// Check that multicast works when the default send interface is configured by
-// IP_MULTICAST_IF, the send address is specified in sendto, and the group
-// membership is configured by address.
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackIfAddrSelf) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Set the default send interface.
- ip_mreq iface = {};
- iface.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF,
- &iface, sizeof(iface)),
- SyscallSucceeds());
-
- // Bind the first FD to the v4 any address to ensure that we can receive the
- // multicast packet.
- auto receiver_addr = V4Any();
- ASSERT_THAT(bind(sockets->first_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->first_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Register to receive multicast packets.
- ip_mreq group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- group.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
-
- // Send a multicast packet.
- auto send_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(
- RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&send_addr.addr),
- send_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we received the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(
- RetryEINTR(recv)(sockets->first_fd(), recv_buf, sizeof(recv_buf), 0),
- SyscallSucceedsWithValue(sizeof(recv_buf)));
-
- EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
-}
-
-// Check that multicast works when the default send interface is configured by
-// IP_MULTICAST_IF, the send address is specified in sendto, and the group
-// membership is configured by NIC ID.
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackIfNicSelf) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Set the default send interface.
- ip_mreqn iface = {};
- iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo"));
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF,
- &iface, sizeof(iface)),
- SyscallSucceeds());
-
- // Bind the first FD to the v4 any address to ensure that we can receive the
- // multicast packet.
- auto receiver_addr = V4Any();
- ASSERT_THAT(bind(sockets->first_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->first_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Register to receive multicast packets.
- ip_mreqn group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo"));
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
-
- // Send a multicast packet.
- auto send_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(
- RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&send_addr.addr),
- send_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we received the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(
- RetryEINTR(recv)(sockets->first_fd(), recv_buf, sizeof(recv_buf), 0),
- SyscallSucceedsWithValue(sizeof(recv_buf)));
-
- EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
-}
-
-// Check that multicast works when the default send interface is configured by
-// IP_MULTICAST_IF, the send address is specified in connect, and the group
-// membership is configured by address.
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackIfAddrSelfConnect) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Set the default send interface.
- ip_mreq iface = {};
- iface.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF,
- &iface, sizeof(iface)),
- SyscallSucceeds());
-
- // Bind the first FD to the v4 any address to ensure that we can receive the
- // multicast packet.
- auto receiver_addr = V4Any();
- ASSERT_THAT(bind(sockets->first_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->first_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Register to receive multicast packets.
- ip_mreq group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- group.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
-
- // Send a multicast packet.
- auto connect_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&connect_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- EXPECT_THAT(
- RetryEINTR(connect)(sockets->first_fd(),
- reinterpret_cast<sockaddr*>(&connect_addr.addr),
- connect_addr.addr_len),
- SyscallSucceeds());
-
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), send_buf, sizeof(send_buf), 0),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we did not receive the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- EXPECT_THAT(RetryEINTR(recv)(sockets->first_fd(), recv_buf, sizeof(recv_buf),
- MSG_DONTWAIT),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-// Check that multicast works when the default send interface is configured by
-// IP_MULTICAST_IF, the send address is specified in connect, and the group
-// membership is configured by NIC ID.
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackIfNicSelfConnect) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Set the default send interface.
- ip_mreqn iface = {};
- iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo"));
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF,
- &iface, sizeof(iface)),
- SyscallSucceeds());
-
- // Bind the first FD to the v4 any address to ensure that we can receive the
- // multicast packet.
- auto receiver_addr = V4Any();
- ASSERT_THAT(bind(sockets->first_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->first_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Register to receive multicast packets.
- ip_mreqn group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo"));
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
-
- // Send a multicast packet.
- auto connect_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&connect_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- ASSERT_THAT(
- RetryEINTR(connect)(sockets->first_fd(),
- reinterpret_cast<sockaddr*>(&connect_addr.addr),
- connect_addr.addr_len),
- SyscallSucceeds());
-
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), send_buf, sizeof(send_buf), 0),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we did not receive the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- EXPECT_THAT(RetryEINTR(recv)(sockets->first_fd(), recv_buf, sizeof(recv_buf),
- MSG_DONTWAIT),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-// Check that multicast works when the default send interface is configured by
-// IP_MULTICAST_IF, the send address is specified in sendto, and the group
-// membership is configured by address.
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackIfAddrSelfNoLoop) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Set the default send interface.
- ip_mreq iface = {};
- iface.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF,
- &iface, sizeof(iface)),
- SyscallSucceeds());
-
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_LOOP,
- &kSockOptOff, sizeof(kSockOptOff)),
- SyscallSucceeds());
-
- // Bind the first FD to the v4 any address to ensure that we can receive the
- // multicast packet.
- auto receiver_addr = V4Any();
- ASSERT_THAT(bind(sockets->first_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->first_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Register to receive multicast packets.
- ip_mreq group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- group.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
-
- // Send a multicast packet.
- auto send_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(
- RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&send_addr.addr),
- send_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we received the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(
- RetryEINTR(recv)(sockets->first_fd(), recv_buf, sizeof(recv_buf), 0),
- SyscallSucceedsWithValue(sizeof(recv_buf)));
-
- EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
-}
-
-// Check that multicast works when the default send interface is configured by
-// IP_MULTICAST_IF, the send address is specified in sendto, and the group
-// membership is configured by NIC ID.
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackIfNicSelfNoLoop) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Set the default send interface.
- ip_mreqn iface = {};
- iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo"));
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF,
- &iface, sizeof(iface)),
- SyscallSucceeds());
-
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_LOOP,
- &kSockOptOff, sizeof(kSockOptOff)),
- SyscallSucceeds());
-
- // Bind the second FD to the v4 any address to ensure that we can receive the
- // multicast packet.
- auto receiver_addr = V4Any();
- ASSERT_THAT(bind(sockets->first_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->first_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Register to receive multicast packets.
- ip_mreqn group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo"));
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
-
- // Send a multicast packet.
- auto send_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(
- RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&send_addr.addr),
- send_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we received the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(
- RetryEINTR(recv)(sockets->first_fd(), recv_buf, sizeof(recv_buf), 0),
- SyscallSucceedsWithValue(sizeof(recv_buf)));
-
- EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
-}
-
-// Check that dropping a group membership that does not exist fails.
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastInvalidDrop) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Unregister from a membership that we didn't have.
- ip_mreq group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- group.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_DROP_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallFailsWithErrno(EADDRNOTAVAIL));
-}
-
-// Check that dropping a group membership prevents multicast packets from being
-// delivered. Default send address configured by bind and group membership
-// interface configured by address.
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastDropAddr) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Bind the first FD to the loopback. This is an alternative to
- // IP_MULTICAST_IF for setting the default send interface.
- auto sender_addr = V4Loopback();
- EXPECT_THAT(
- bind(sockets->first_fd(), reinterpret_cast<sockaddr*>(&sender_addr.addr),
- sender_addr.addr_len),
- SyscallSucceeds());
-
- // Bind the second FD to the v4 any address to ensure that we can receive the
- // multicast packet.
- auto receiver_addr = V4Any();
- EXPECT_THAT(bind(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Register and unregister to receive multicast packets.
- ip_mreq group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- group.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
- EXPECT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
- EXPECT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_DROP_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
-
- // Send a multicast packet.
- auto send_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- EXPECT_THAT(
- RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&send_addr.addr),
- send_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we did not receive the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- EXPECT_THAT(RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf),
- MSG_DONTWAIT),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-// Check that dropping a group membership prevents multicast packets from being
-// delivered. Default send address configured by bind and group membership
-// interface configured by NIC ID.
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastDropNic) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Bind the first FD to the loopback. This is an alternative to
- // IP_MULTICAST_IF for setting the default send interface.
- auto sender_addr = V4Loopback();
- EXPECT_THAT(
- bind(sockets->first_fd(), reinterpret_cast<sockaddr*>(&sender_addr.addr),
- sender_addr.addr_len),
- SyscallSucceeds());
-
- // Bind the second FD to the v4 any address to ensure that we can receive the
- // multicast packet.
- auto receiver_addr = V4Any();
- EXPECT_THAT(bind(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Register and unregister to receive multicast packets.
- ip_mreqn group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo"));
- EXPECT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
- EXPECT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_DROP_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
-
- // Send a multicast packet.
- auto send_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- EXPECT_THAT(
- RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&send_addr.addr),
- send_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we did not receive the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- EXPECT_THAT(RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf),
- MSG_DONTWAIT),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastIfZero) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ip_mreqn iface = {};
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF,
- &iface, sizeof(iface)),
- SyscallSucceeds());
-}
-
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastIfInvalidNic) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ip_mreqn iface = {};
- iface.imr_ifindex = -1;
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF,
- &iface, sizeof(iface)),
- SyscallFailsWithErrno(EADDRNOTAVAIL));
-}
-
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastIfInvalidAddr) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ip_mreq iface = {};
- iface.imr_interface.s_addr = inet_addr("255.255.255");
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF,
- &iface, sizeof(iface)),
- SyscallFailsWithErrno(EADDRNOTAVAIL));
-}
-
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastIfSetShort) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Create a valid full-sized request.
- ip_mreqn iface = {};
- iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo"));
-
- // Send an optlen of 1 to check that optlen is enforced.
- EXPECT_THAT(
- setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF, &iface, 1),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastIfDefault) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- in_addr get = {};
- socklen_t size = sizeof(get);
- ASSERT_THAT(
- getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size),
- SyscallSucceeds());
- EXPECT_EQ(size, sizeof(get));
- EXPECT_EQ(get.s_addr, 0);
-}
-
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastIfDefaultReqn) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ip_mreqn get = {};
- socklen_t size = sizeof(get);
- ASSERT_THAT(
- getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size),
- SyscallSucceeds());
-
- // getsockopt(IP_MULTICAST_IF) can only return an in_addr, so it treats the
- // first sizeof(struct in_addr) bytes of struct ip_mreqn as a struct in_addr.
- // Conveniently, this corresponds to the field ip_mreqn::imr_multiaddr.
- EXPECT_EQ(size, sizeof(in_addr));
-
- // getsockopt(IP_MULTICAST_IF) will only return the interface address which
- // hasn't been set.
- EXPECT_EQ(get.imr_multiaddr.s_addr, 0);
- EXPECT_EQ(get.imr_address.s_addr, 0);
- EXPECT_EQ(get.imr_ifindex, 0);
-}
-
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastIfSetAddrGetReqn) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- in_addr set = {};
- set.s_addr = htonl(INADDR_LOOPBACK);
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF, &set,
- sizeof(set)),
- SyscallSucceeds());
-
- ip_mreqn get = {};
- socklen_t size = sizeof(get);
- ASSERT_THAT(
- getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size),
- SyscallSucceeds());
-
- // getsockopt(IP_MULTICAST_IF) can only return an in_addr, so it treats the
- // first sizeof(struct in_addr) bytes of struct ip_mreqn as a struct in_addr.
- // Conveniently, this corresponds to the field ip_mreqn::imr_multiaddr.
- EXPECT_EQ(size, sizeof(in_addr));
- EXPECT_EQ(get.imr_multiaddr.s_addr, set.s_addr);
- EXPECT_EQ(get.imr_address.s_addr, 0);
- EXPECT_EQ(get.imr_ifindex, 0);
-}
-
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastIfSetReqAddrGetReqn) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ip_mreq set = {};
- set.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF, &set,
- sizeof(set)),
- SyscallSucceeds());
-
- ip_mreqn get = {};
- socklen_t size = sizeof(get);
- ASSERT_THAT(
- getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size),
- SyscallSucceeds());
-
- // getsockopt(IP_MULTICAST_IF) can only return an in_addr, so it treats the
- // first sizeof(struct in_addr) bytes of struct ip_mreqn as a struct in_addr.
- // Conveniently, this corresponds to the field ip_mreqn::imr_multiaddr.
- EXPECT_EQ(size, sizeof(in_addr));
- EXPECT_EQ(get.imr_multiaddr.s_addr, set.imr_interface.s_addr);
- EXPECT_EQ(get.imr_address.s_addr, 0);
- EXPECT_EQ(get.imr_ifindex, 0);
-}
-
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastIfSetNicGetReqn) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ip_mreqn set = {};
- set.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo"));
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF, &set,
- sizeof(set)),
- SyscallSucceeds());
-
- ip_mreqn get = {};
- socklen_t size = sizeof(get);
- ASSERT_THAT(
- getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size),
- SyscallSucceeds());
- EXPECT_EQ(size, sizeof(in_addr));
- EXPECT_EQ(get.imr_multiaddr.s_addr, 0);
- EXPECT_EQ(get.imr_address.s_addr, 0);
- EXPECT_EQ(get.imr_ifindex, 0);
-}
-
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastIfSetAddr) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- in_addr set = {};
- set.s_addr = htonl(INADDR_LOOPBACK);
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF, &set,
- sizeof(set)),
- SyscallSucceeds());
-
- in_addr get = {};
- socklen_t size = sizeof(get);
- ASSERT_THAT(
- getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size),
- SyscallSucceeds());
-
- EXPECT_EQ(size, sizeof(get));
- EXPECT_EQ(get.s_addr, set.s_addr);
-}
-
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastIfSetReqAddr) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ip_mreq set = {};
- set.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF, &set,
- sizeof(set)),
- SyscallSucceeds());
-
- in_addr get = {};
- socklen_t size = sizeof(get);
- ASSERT_THAT(
- getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size),
- SyscallSucceeds());
-
- EXPECT_EQ(size, sizeof(get));
- EXPECT_EQ(get.s_addr, set.imr_interface.s_addr);
-}
-
-TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastIfSetNic) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ip_mreqn set = {};
- set.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo"));
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF, &set,
- sizeof(set)),
- SyscallSucceeds());
-
- in_addr get = {};
- socklen_t size = sizeof(get);
- ASSERT_THAT(
- getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size),
- SyscallSucceeds());
- EXPECT_EQ(size, sizeof(get));
- EXPECT_EQ(get.s_addr, 0);
-}
-
-TEST_P(IPv4UDPUnboundSocketPairTest, TestJoinGroupNoIf) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ip_mreqn group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallFailsWithErrno(ENODEV));
-}
-
-TEST_P(IPv4UDPUnboundSocketPairTest, TestJoinGroupInvalidIf) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ip_mreqn group = {};
- group.imr_address.s_addr = inet_addr("255.255.255");
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallFailsWithErrno(ENODEV));
-}
-
-// Check that multiple memberships are not allowed on the same socket.
-TEST_P(IPv4UDPUnboundSocketPairTest, TestMultipleJoinsOnSingleSocket) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- auto fd = sockets->first_fd();
- ip_mreqn group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo"));
-
- EXPECT_THAT(
- setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)),
- SyscallSucceeds());
-
- EXPECT_THAT(
- setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)),
- SyscallFailsWithErrno(EADDRINUSE));
-}
-
-// Check that two sockets can join the same multicast group at the same time.
-TEST_P(IPv4UDPUnboundSocketPairTest, TestTwoSocketsJoinSameMulticastGroup) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ip_mreqn group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo"));
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
- EXPECT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
-
- // Drop the membership twice on each socket, the second call for each socket
- // should fail.
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_DROP_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_DROP_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallFailsWithErrno(EADDRNOTAVAIL));
- EXPECT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_DROP_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
- EXPECT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_DROP_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallFailsWithErrno(EADDRNOTAVAIL));
-}
-
-// Check that two sockets can join the same multicast group at the same time,
-// and both will receive data on it.
-TEST_P(IPv4UDPUnboundSocketPairTest, TestMcastReceptionOnTwoSockets) {
- std::unique_ptr<SocketPair> socket_pairs[2] = {
- ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()),
- ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair())};
-
- ip_mreq iface = {}, group = {};
- iface.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- group.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
- auto receiver_addr = V4Any();
- int bound_port = 0;
-
- // Create two socketpairs with the exact same configuration.
- for (auto& sockets : socket_pairs) {
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF,
- &iface, sizeof(iface)),
- SyscallSucceeds());
- ASSERT_THAT(setsockopt(sockets->second_fd(), SOL_SOCKET, SO_REUSEPORT,
- &kSockOptOn, sizeof(kSockOptOn)),
- SyscallSucceeds());
- ASSERT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
- ASSERT_THAT(bind(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- // Get the port assigned.
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
- // On the first iteration, save the port we are bound to. On the second
- // iteration, verify the port is the same as the one from the first
- // iteration. In other words, both sockets listen on the same port.
- if (bound_port == 0) {
- bound_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- } else {
- EXPECT_EQ(bound_port,
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port);
- }
- }
-
- // Send a multicast packet to the group from two different sockets and verify
- // it is received by both sockets that joined that group.
- auto send_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = bound_port;
- for (auto& sockets : socket_pairs) {
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(
- RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&send_addr.addr),
- send_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we received the multicast packet on both sockets.
- for (auto& sockets : socket_pairs) {
- char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(
- RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf), 0),
- SyscallSucceedsWithValue(sizeof(recv_buf)));
- EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
- }
- }
-}
-
-// Check that on two sockets that joined a group and listen on ANY, dropping
-// memberships one by one will continue to deliver packets to both sockets until
-// both memberships have been dropped.
-TEST_P(IPv4UDPUnboundSocketPairTest,
- TestMcastReceptionWhenDroppingMemberships) {
- std::unique_ptr<SocketPair> socket_pairs[2] = {
- ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()),
- ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair())};
-
- ip_mreq iface = {}, group = {};
- iface.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- group.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
- auto receiver_addr = V4Any();
- int bound_port = 0;
-
- // Create two socketpairs with the exact same configuration.
- for (auto& sockets : socket_pairs) {
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF,
- &iface, sizeof(iface)),
- SyscallSucceeds());
- ASSERT_THAT(setsockopt(sockets->second_fd(), SOL_SOCKET, SO_REUSEPORT,
- &kSockOptOn, sizeof(kSockOptOn)),
- SyscallSucceeds());
- ASSERT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
- ASSERT_THAT(bind(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- // Get the port assigned.
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
- // On the first iteration, save the port we are bound to. On the second
- // iteration, verify the port is the same as the one from the first
- // iteration. In other words, both sockets listen on the same port.
- if (bound_port == 0) {
- bound_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- } else {
- EXPECT_EQ(bound_port,
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port);
- }
- }
-
- // Drop the membership of the first socket pair and verify data is still
- // received.
- ASSERT_THAT(setsockopt(socket_pairs[0]->second_fd(), IPPROTO_IP,
- IP_DROP_MEMBERSHIP, &group, sizeof(group)),
- SyscallSucceeds());
- // Send a packet from each socket_pair.
- auto send_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = bound_port;
- for (auto& sockets : socket_pairs) {
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(
- RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&send_addr.addr),
- send_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we received the multicast packet on both sockets.
- for (auto& sockets : socket_pairs) {
- char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(
- RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf), 0),
- SyscallSucceedsWithValue(sizeof(recv_buf)));
- EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
- }
- }
-
- // Drop the membership of the second socket pair and verify data stops being
- // received.
- ASSERT_THAT(setsockopt(socket_pairs[1]->second_fd(), IPPROTO_IP,
- IP_DROP_MEMBERSHIP, &group, sizeof(group)),
- SyscallSucceeds());
- // Send a packet from each socket_pair.
- for (auto& sockets : socket_pairs) {
- char send_buf[200];
- ASSERT_THAT(
- RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&send_addr.addr),
- send_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- char recv_buf[sizeof(send_buf)] = {};
- for (auto& sockets : socket_pairs) {
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), recv_buf,
- sizeof(recv_buf), MSG_DONTWAIT),
- SyscallFailsWithErrno(EAGAIN));
- }
- }
-}
-
-// Check that a receiving socket can bind to the multicast address before
-// joining the group and receive data once the group has been joined.
-TEST_P(IPv4UDPUnboundSocketPairTest, TestBindToMcastThenJoinThenReceive) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Bind second socket (receiver) to the multicast address.
- auto receiver_addr = V4Multicast();
- ASSERT_THAT(bind(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- // Update receiver_addr with the correct port number.
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Register to receive multicast packets.
- ip_mreqn group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo"));
- ASSERT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
-
- // Send a multicast packet on the first socket out the loopback interface.
- ip_mreq iface = {};
- iface.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF,
- &iface, sizeof(iface)),
- SyscallSucceeds());
- auto sendto_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&sendto_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(
- RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&sendto_addr.addr),
- sendto_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we received the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf),
- MSG_DONTWAIT),
- SyscallSucceedsWithValue(sizeof(recv_buf)));
- EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
-}
-
-// Check that a receiving socket can bind to the multicast address and won't
-// receive multicast data if it hasn't joined the group.
-TEST_P(IPv4UDPUnboundSocketPairTest, TestBindToMcastThenNoJoinThenNoReceive) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Bind second socket (receiver) to the multicast address.
- auto receiver_addr = V4Multicast();
- ASSERT_THAT(bind(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- // Update receiver_addr with the correct port number.
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Send a multicast packet on the first socket out the loopback interface.
- ip_mreq iface = {};
- iface.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
- ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF,
- &iface, sizeof(iface)),
- SyscallSucceeds());
- auto sendto_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&sendto_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(
- RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&sendto_addr.addr),
- sendto_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we don't receive the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf),
- MSG_DONTWAIT),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-// Check that a socket can bind to a multicast address and still send out
-// packets.
-TEST_P(IPv4UDPUnboundSocketPairTest, TestBindToMcastThenSend) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Bind second socket (receiver) to the ANY address.
- auto receiver_addr = V4Any();
- ASSERT_THAT(bind(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Bind the first socket (sender) to the multicast address.
- auto sender_addr = V4Multicast();
- ASSERT_THAT(
- bind(sockets->first_fd(), reinterpret_cast<sockaddr*>(&sender_addr.addr),
- sender_addr.addr_len),
- SyscallSucceeds());
- socklen_t sender_addr_len = sender_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->first_fd(),
- reinterpret_cast<sockaddr*>(&sender_addr.addr),
- &sender_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(sender_addr_len, sender_addr.addr_len);
-
- // Send a packet on the first socket to the loopback address.
- auto sendto_addr = V4Loopback();
- reinterpret_cast<sockaddr_in*>(&sendto_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(
- RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&sendto_addr.addr),
- sendto_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we received the packet.
- char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf),
- MSG_DONTWAIT),
- SyscallSucceedsWithValue(sizeof(recv_buf)));
- EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
-}
-
-// Check that a receiving socket can bind to the broadcast address and receive
-// broadcast packets.
-TEST_P(IPv4UDPUnboundSocketPairTest, TestBindToBcastThenReceive) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Bind second socket (receiver) to the broadcast address.
- auto receiver_addr = V4Broadcast();
- ASSERT_THAT(bind(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Send a broadcast packet on the first socket out the loopback interface.
- EXPECT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_BROADCAST,
- &kSockOptOn, sizeof(kSockOptOn)),
- SyscallSucceedsWithValue(0));
- // Note: Binding to the loopback interface makes the broadcast go out of it.
- auto sender_bind_addr = V4Loopback();
- ASSERT_THAT(bind(sockets->first_fd(),
- reinterpret_cast<sockaddr*>(&sender_bind_addr.addr),
- sender_bind_addr.addr_len),
- SyscallSucceeds());
- auto sendto_addr = V4Broadcast();
- reinterpret_cast<sockaddr_in*>(&sendto_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(
- RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&sendto_addr.addr),
- sendto_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we received the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf),
- MSG_DONTWAIT),
- SyscallSucceedsWithValue(sizeof(recv_buf)));
- EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
-}
-
-// Check that a socket can bind to the broadcast address and still send out
-// packets.
-TEST_P(IPv4UDPUnboundSocketPairTest, TestBindToBcastThenSend) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Bind second socket (receiver) to the ANY address.
- auto receiver_addr = V4Any();
- ASSERT_THAT(bind(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->second_fd(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Bind the first socket (sender) to the broadcast address.
- auto sender_addr = V4Broadcast();
- ASSERT_THAT(
- bind(sockets->first_fd(), reinterpret_cast<sockaddr*>(&sender_addr.addr),
- sender_addr.addr_len),
- SyscallSucceeds());
- socklen_t sender_addr_len = sender_addr.addr_len;
- ASSERT_THAT(getsockname(sockets->first_fd(),
- reinterpret_cast<sockaddr*>(&sender_addr.addr),
- &sender_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(sender_addr_len, sender_addr.addr_len);
-
- // Send a packet on the first socket to the loopback address.
- auto sendto_addr = V4Loopback();
- reinterpret_cast<sockaddr_in*>(&sendto_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(
- RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&sendto_addr.addr),
- sendto_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we received the packet.
- char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf),
- MSG_DONTWAIT),
- SyscallSucceedsWithValue(sizeof(recv_buf)));
- EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound.h b/test/syscalls/linux/socket_ipv4_udp_unbound.h
deleted file mode 100644
index 8e07bfbbf..000000000
--- a/test/syscalls/linux/socket_ipv4_udp_unbound.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_H_
-#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_H_
-
-#include "test/syscalls/linux/socket_test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Test fixture for tests that apply to pairs of IPv4 UDP sockets.
-using IPv4UDPUnboundSocketPairTest = SocketPairTest;
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_H_
diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc
deleted file mode 100644
index c85ae30dc..000000000
--- a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc
+++ /dev/null
@@ -1,849 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h"
-
-#include <arpa/inet.h>
-#include <ifaddrs.h>
-#include <netinet/in.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <sys/un.h>
-
-#include <cstdint>
-#include <cstdio>
-#include <cstring>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/ip_socket_test_util.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-TestAddress V4EmptyAddress() {
- TestAddress t("V4Empty");
- t.addr.ss_family = AF_INET;
- t.addr_len = sizeof(sockaddr_in);
- return t;
-}
-
-void IPv4UDPUnboundExternalNetworkingSocketTest::SetUp() {
- got_if_infos_ = false;
-
- // Get interface list.
- std::vector<std::string> if_names;
- ASSERT_NO_ERRNO(if_helper_.Load());
- if_names = if_helper_.InterfaceList(AF_INET);
- if (if_names.size() != 2) {
- return;
- }
-
- // Figure out which interface is where.
- int lo = 0, eth = 1;
- if (if_names[lo] != "lo") {
- lo = 1;
- eth = 0;
- }
-
- if (if_names[lo] != "lo") {
- return;
- }
-
- lo_if_idx_ = ASSERT_NO_ERRNO_AND_VALUE(if_helper_.GetIndex(if_names[lo]));
- lo_if_addr_ = if_helper_.GetAddr(AF_INET, if_names[lo]);
- if (lo_if_addr_ == nullptr) {
- return;
- }
- lo_if_sin_addr_ = reinterpret_cast<sockaddr_in*>(lo_if_addr_)->sin_addr;
-
- eth_if_idx_ = ASSERT_NO_ERRNO_AND_VALUE(if_helper_.GetIndex(if_names[eth]));
- eth_if_addr_ = if_helper_.GetAddr(AF_INET, if_names[eth]);
- if (eth_if_addr_ == nullptr) {
- return;
- }
- eth_if_sin_addr_ = reinterpret_cast<sockaddr_in*>(eth_if_addr_)->sin_addr;
-
- got_if_infos_ = true;
-}
-
-// Verifies that a newly instantiated UDP socket does not have the
-// broadcast socket option enabled.
-TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, UDPBroadcastDefault) {
- auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- int get = -1;
- socklen_t get_sz = sizeof(get);
- EXPECT_THAT(
- getsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &get, &get_sz),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get, kSockOptOff);
- EXPECT_EQ(get_sz, sizeof(get));
-}
-
-// Verifies that a newly instantiated UDP socket returns true after enabling
-// the broadcast socket option.
-TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, SetUDPBroadcast) {
- auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- EXPECT_THAT(setsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &kSockOptOn,
- sizeof(kSockOptOn)),
- SyscallSucceedsWithValue(0));
-
- int get = -1;
- socklen_t get_sz = sizeof(get);
- EXPECT_THAT(
- getsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &get, &get_sz),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get, kSockOptOn);
- EXPECT_EQ(get_sz, sizeof(get));
-}
-
-// Verifies that a broadcast UDP packet will arrive at all UDP sockets with
-// the destination port number.
-TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest,
- UDPBroadcastReceivedOnAllExpectedEndpoints) {
- auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
- auto rcvr1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
- auto rcvr2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
- auto norcv = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- // Enable SO_BROADCAST on the sending socket.
- ASSERT_THAT(setsockopt(sender->get(), SOL_SOCKET, SO_BROADCAST, &kSockOptOn,
- sizeof(kSockOptOn)),
- SyscallSucceedsWithValue(0));
-
- // Enable SO_REUSEPORT on the receiving sockets so that they may both be bound
- // to the broadcast messages destination port.
- ASSERT_THAT(setsockopt(rcvr1->get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn,
- sizeof(kSockOptOn)),
- SyscallSucceedsWithValue(0));
- ASSERT_THAT(setsockopt(rcvr2->get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn,
- sizeof(kSockOptOn)),
- SyscallSucceedsWithValue(0));
-
- sockaddr_in rcv_addr = {};
- socklen_t rcv_addr_sz = sizeof(rcv_addr);
- rcv_addr.sin_family = AF_INET;
- rcv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
- ASSERT_THAT(bind(rcvr1->get(), reinterpret_cast<struct sockaddr*>(&rcv_addr),
- rcv_addr_sz),
- SyscallSucceedsWithValue(0));
- // Retrieve port number from first socket so that it can be bound to the
- // second socket.
- rcv_addr = {};
- ASSERT_THAT(
- getsockname(rcvr1->get(), reinterpret_cast<struct sockaddr*>(&rcv_addr),
- &rcv_addr_sz),
- SyscallSucceedsWithValue(0));
- ASSERT_THAT(bind(rcvr2->get(), reinterpret_cast<struct sockaddr*>(&rcv_addr),
- rcv_addr_sz),
- SyscallSucceedsWithValue(0));
-
- // Bind the non-receiving socket to an ephemeral port.
- sockaddr_in norcv_addr = {};
- norcv_addr.sin_family = AF_INET;
- norcv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
- ASSERT_THAT(
- bind(norcv->get(), reinterpret_cast<struct sockaddr*>(&norcv_addr),
- sizeof(norcv_addr)),
- SyscallSucceedsWithValue(0));
-
- // Broadcast a test message.
- sockaddr_in dst_addr = {};
- dst_addr.sin_family = AF_INET;
- dst_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
- dst_addr.sin_port = rcv_addr.sin_port;
- constexpr char kTestMsg[] = "hello, world";
- EXPECT_THAT(
- sendto(sender->get(), kTestMsg, sizeof(kTestMsg), 0,
- reinterpret_cast<struct sockaddr*>(&dst_addr), sizeof(dst_addr)),
- SyscallSucceedsWithValue(sizeof(kTestMsg)));
-
- // Verify that the receiving sockets received the test message.
- char buf[sizeof(kTestMsg)] = {};
- EXPECT_THAT(read(rcvr1->get(), buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(kTestMsg)));
- EXPECT_EQ(0, memcmp(buf, kTestMsg, sizeof(kTestMsg)));
- memset(buf, 0, sizeof(buf));
- EXPECT_THAT(read(rcvr2->get(), buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(kTestMsg)));
- EXPECT_EQ(0, memcmp(buf, kTestMsg, sizeof(kTestMsg)));
-
- // Verify that the non-receiving socket did not receive the test message.
- memset(buf, 0, sizeof(buf));
- EXPECT_THAT(RetryEINTR(recv)(norcv->get(), buf, sizeof(buf), MSG_DONTWAIT),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-// Verifies that a UDP broadcast sent via the loopback interface is not received
-// by the sender.
-TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest,
- UDPBroadcastViaLoopbackFails) {
- auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- // Enable SO_BROADCAST.
- ASSERT_THAT(setsockopt(sender->get(), SOL_SOCKET, SO_BROADCAST, &kSockOptOn,
- sizeof(kSockOptOn)),
- SyscallSucceedsWithValue(0));
-
- // Bind the sender to the loopback interface.
- sockaddr_in src = {};
- socklen_t src_sz = sizeof(src);
- src.sin_family = AF_INET;
- src.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- ASSERT_THAT(
- bind(sender->get(), reinterpret_cast<struct sockaddr*>(&src), src_sz),
- SyscallSucceedsWithValue(0));
- ASSERT_THAT(getsockname(sender->get(),
- reinterpret_cast<struct sockaddr*>(&src), &src_sz),
- SyscallSucceedsWithValue(0));
- ASSERT_EQ(src.sin_addr.s_addr, htonl(INADDR_LOOPBACK));
-
- // Send the message.
- sockaddr_in dst = {};
- dst.sin_family = AF_INET;
- dst.sin_addr.s_addr = htonl(INADDR_BROADCAST);
- dst.sin_port = src.sin_port;
- constexpr char kTestMsg[] = "hello, world";
- EXPECT_THAT(sendto(sender->get(), kTestMsg, sizeof(kTestMsg), 0,
- reinterpret_cast<struct sockaddr*>(&dst), sizeof(dst)),
- SyscallSucceedsWithValue(sizeof(kTestMsg)));
-
- // Verify that the message was not received by the sender (loopback).
- char buf[sizeof(kTestMsg)] = {};
- EXPECT_THAT(RetryEINTR(recv)(sender->get(), buf, sizeof(buf), MSG_DONTWAIT),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-// Verifies that a UDP broadcast fails to send on a socket with SO_BROADCAST
-// disabled.
-TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendBroadcast) {
- auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- // Broadcast a test message without having enabled SO_BROADCAST on the sending
- // socket.
- sockaddr_in addr = {};
- socklen_t addr_sz = sizeof(addr);
- addr.sin_family = AF_INET;
- addr.sin_port = htons(12345);
- addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
- constexpr char kTestMsg[] = "hello, world";
-
- EXPECT_THAT(sendto(sender->get(), kTestMsg, sizeof(kTestMsg), 0,
- reinterpret_cast<struct sockaddr*>(&addr), addr_sz),
- SyscallFailsWithErrno(EACCES));
-}
-
-// Verifies that a UDP unicast on an unbound socket reaches its destination.
-TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendUnicastOnUnbound) {
- auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
- auto rcvr = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- // Bind the receiver and retrieve its address and port number.
- sockaddr_in addr = {};
- addr.sin_family = AF_INET;
- addr.sin_addr.s_addr = htonl(INADDR_ANY);
- addr.sin_port = htons(0);
- ASSERT_THAT(bind(rcvr->get(), reinterpret_cast<struct sockaddr*>(&addr),
- sizeof(addr)),
- SyscallSucceedsWithValue(0));
- memset(&addr, 0, sizeof(addr));
- socklen_t addr_sz = sizeof(addr);
- ASSERT_THAT(getsockname(rcvr->get(),
- reinterpret_cast<struct sockaddr*>(&addr), &addr_sz),
- SyscallSucceedsWithValue(0));
-
- // Send a test message to the receiver.
- constexpr char kTestMsg[] = "hello, world";
- ASSERT_THAT(sendto(sender->get(), kTestMsg, sizeof(kTestMsg), 0,
- reinterpret_cast<struct sockaddr*>(&addr), addr_sz),
- SyscallSucceedsWithValue(sizeof(kTestMsg)));
- char buf[sizeof(kTestMsg)] = {};
- ASSERT_THAT(read(rcvr->get(), buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(kTestMsg)));
-}
-
-constexpr char kMulticastAddress[] = "224.0.2.1";
-
-TestAddress V4Multicast() {
- TestAddress t("V4Multicast");
- t.addr.ss_family = AF_INET;
- t.addr_len = sizeof(sockaddr_in);
- reinterpret_cast<sockaddr_in*>(&t.addr)->sin_addr.s_addr =
- inet_addr(kMulticastAddress);
- return t;
-}
-
-// Check that multicast packets won't be delivered to the sending socket with no
-// set interface or group membership.
-TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest,
- TestSendMulticastSelfNoGroup) {
- // FIXME(b/125485338): A group membership is not required for external
- // multicast on gVisor.
- SKIP_IF(IsRunningOnGvisor());
-
- auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- auto bind_addr = V4Any();
- ASSERT_THAT(bind(socket->get(), reinterpret_cast<sockaddr*>(&bind_addr.addr),
- bind_addr.addr_len),
- SyscallSucceeds());
- socklen_t bind_addr_len = bind_addr.addr_len;
- ASSERT_THAT(
- getsockname(socket->get(), reinterpret_cast<sockaddr*>(&bind_addr.addr),
- &bind_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(bind_addr_len, bind_addr.addr_len);
-
- // Send a multicast packet.
- auto send_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&bind_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(RetryEINTR(sendto)(socket->get(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&send_addr.addr),
- send_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we did not receive the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(
- RetryEINTR(recv)(socket->get(), recv_buf, sizeof(recv_buf), MSG_DONTWAIT),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-// Check that multicast packets will be delivered to the sending socket without
-// setting an interface.
-TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendMulticastSelf) {
- auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- auto bind_addr = V4Any();
- ASSERT_THAT(bind(socket->get(), reinterpret_cast<sockaddr*>(&bind_addr.addr),
- bind_addr.addr_len),
- SyscallSucceeds());
- socklen_t bind_addr_len = bind_addr.addr_len;
- ASSERT_THAT(
- getsockname(socket->get(), reinterpret_cast<sockaddr*>(&bind_addr.addr),
- &bind_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(bind_addr_len, bind_addr.addr_len);
-
- // Register to receive multicast packets.
- ip_mreq group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- ASSERT_THAT(setsockopt(socket->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group,
- sizeof(group)),
- SyscallSucceeds());
-
- // Send a multicast packet.
- auto send_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&bind_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(RetryEINTR(sendto)(socket->get(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&send_addr.addr),
- send_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we received the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(RetryEINTR(recv)(socket->get(), recv_buf, sizeof(recv_buf), 0),
- SyscallSucceedsWithValue(sizeof(recv_buf)));
-
- EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
-}
-
-// Check that multicast packets won't be delivered to the sending socket with no
-// set interface and IP_MULTICAST_LOOP disabled.
-TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest,
- TestSendMulticastSelfLoopOff) {
- auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- auto bind_addr = V4Any();
- ASSERT_THAT(bind(socket->get(), reinterpret_cast<sockaddr*>(&bind_addr.addr),
- bind_addr.addr_len),
- SyscallSucceeds());
- socklen_t bind_addr_len = bind_addr.addr_len;
- ASSERT_THAT(
- getsockname(socket->get(), reinterpret_cast<sockaddr*>(&bind_addr.addr),
- &bind_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(bind_addr_len, bind_addr.addr_len);
-
- // Disable multicast looping.
- EXPECT_THAT(setsockopt(socket->get(), IPPROTO_IP, IP_MULTICAST_LOOP,
- &kSockOptOff, sizeof(kSockOptOff)),
- SyscallSucceeds());
-
- // Register to receive multicast packets.
- ip_mreq group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- EXPECT_THAT(setsockopt(socket->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group,
- sizeof(group)),
- SyscallSucceeds());
-
- // Send a multicast packet.
- auto send_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&bind_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(RetryEINTR(sendto)(socket->get(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&send_addr.addr),
- send_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we did not receive the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- EXPECT_THAT(
- RetryEINTR(recv)(socket->get(), recv_buf, sizeof(recv_buf), MSG_DONTWAIT),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-// Check that multicast packets won't be delivered to another socket with no
-// set interface or group membership.
-TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendMulticastNoGroup) {
- // FIXME(b/125485338): A group membership is not required for external
- // multicast on gVisor.
- SKIP_IF(IsRunningOnGvisor());
-
- auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
- auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- // Bind the second FD to the v4 any address to ensure that we can receive the
- // multicast packet.
- auto receiver_addr = V4Any();
- ASSERT_THAT(
- bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(receiver->get(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Send a multicast packet.
- auto send_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&send_addr.addr),
- send_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we did not receive the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(RetryEINTR(recv)(receiver->get(), recv_buf, sizeof(recv_buf),
- MSG_DONTWAIT),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-// Check that multicast packets will be delivered to another socket without
-// setting an interface.
-TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendMulticast) {
- auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
- auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- // Bind the second FD to the v4 any address to ensure that we can receive the
- // multicast packet.
- auto receiver_addr = V4Any();
- ASSERT_THAT(
- bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(receiver->get(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Register to receive multicast packets.
- ip_mreqn group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group,
- sizeof(group)),
- SyscallSucceeds());
-
- // Send a multicast packet.
- auto send_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&send_addr.addr),
- send_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we received the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(RetryEINTR(recv)(receiver->get(), recv_buf, sizeof(recv_buf), 0),
- SyscallSucceedsWithValue(sizeof(recv_buf)));
-
- EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
-}
-
-// Check that multicast packets won't be delivered to another socket with no
-// set interface and IP_MULTICAST_LOOP disabled on the sending socket.
-TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest,
- TestSendMulticastSenderNoLoop) {
- auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
- auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- // Bind the second FD to the v4 any address to ensure that we can receive the
- // multicast packet.
- auto receiver_addr = V4Any();
- ASSERT_THAT(
- bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(receiver->get(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Disable multicast looping on the sender.
- EXPECT_THAT(setsockopt(sender->get(), IPPROTO_IP, IP_MULTICAST_LOOP,
- &kSockOptOff, sizeof(kSockOptOff)),
- SyscallSucceeds());
-
- // Register to receive multicast packets.
- ip_mreqn group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- EXPECT_THAT(setsockopt(receiver->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group,
- sizeof(group)),
- SyscallSucceeds());
-
- // Send a multicast packet.
- auto send_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&send_addr.addr),
- send_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we did not receive the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(RetryEINTR(recv)(receiver->get(), recv_buf, sizeof(recv_buf),
- MSG_DONTWAIT),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-// Check that multicast packets will be delivered to the sending socket without
-// setting an interface and IP_MULTICAST_LOOP disabled on the receiving socket.
-TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest,
- TestSendMulticastReceiverNoLoop) {
- auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
- auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- // Bind the second FD to the v4 any address to ensure that we can receive the
- // multicast packet.
- auto receiver_addr = V4Any();
- ASSERT_THAT(
- bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(receiver->get(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
-
- // Disable multicast looping on the receiver.
- ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IP, IP_MULTICAST_LOOP,
- &kSockOptOff, sizeof(kSockOptOff)),
- SyscallSucceeds());
-
- // Register to receive multicast packets.
- ip_mreqn group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group,
- sizeof(group)),
- SyscallSucceeds());
-
- // Send a multicast packet.
- auto send_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&send_addr.addr),
- send_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Check that we received the multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(RetryEINTR(recv)(receiver->get(), recv_buf, sizeof(recv_buf), 0),
- SyscallSucceedsWithValue(sizeof(recv_buf)));
-
- EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
-}
-
-// Check that two sockets can join the same multicast group at the same time,
-// and both will receive data on it.
-TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendMulticastToTwo) {
- auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
- std::unique_ptr<FileDescriptor> receivers[2] = {
- ASSERT_NO_ERRNO_AND_VALUE(NewSocket()),
- ASSERT_NO_ERRNO_AND_VALUE(NewSocket())};
-
- ip_mreq group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- auto receiver_addr = V4Any();
- int bound_port = 0;
- for (auto& receiver : receivers) {
- ASSERT_THAT(setsockopt(receiver->get(), SOL_SOCKET, SO_REUSEPORT,
- &kSockOptOn, sizeof(kSockOptOn)),
- SyscallSucceeds());
- // Bind the receiver to the v4 any address to ensure that we can receive the
- // multicast packet.
- ASSERT_THAT(
- bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(receiver->get(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
- // On the first iteration, save the port we are bound to. On the second
- // iteration, verify the port is the same as the one from the first
- // iteration. In other words, both sockets listen on the same port.
- if (bound_port == 0) {
- bound_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- } else {
- EXPECT_EQ(bound_port,
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port);
- }
-
- // Register to receive multicast packets.
- ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- &group, sizeof(group)),
- SyscallSucceeds());
- }
-
- // Send a multicast packet to the group and verify both receivers get it.
- auto send_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = bound_port;
- char send_buf[200];
- RandomizeBuffer(send_buf, sizeof(send_buf));
- ASSERT_THAT(RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&send_addr.addr),
- send_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
- for (auto& receiver : receivers) {
- char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(
- RetryEINTR(recv)(receiver->get(), recv_buf, sizeof(recv_buf), 0),
- SyscallSucceedsWithValue(sizeof(recv_buf)));
- EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
- }
-}
-
-// Check that when receiving a looped-back multicast packet, its source address
-// is not a multicast address.
-TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest,
- IpMulticastLoopbackFromAddr) {
- auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
- auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- auto receiver_addr = V4Any();
- ASSERT_THAT(
- bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(receiver->get(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
- int receiver_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
-
- ip_mreq group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group,
- sizeof(group)),
- SyscallSucceeds());
-
- // Connect to the multicast address. This binds us to the outgoing interface
- // and allows us to get its IP (to be compared against the src-IP on the
- // receiver side).
- auto sendto_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&sendto_addr.addr)->sin_port = receiver_port;
- ASSERT_THAT(RetryEINTR(connect)(
- sender->get(), reinterpret_cast<sockaddr*>(&sendto_addr.addr),
- sendto_addr.addr_len),
- SyscallSucceeds());
- auto sender_addr = V4EmptyAddress();
- ASSERT_THAT(
- getsockname(sender->get(), reinterpret_cast<sockaddr*>(&sender_addr.addr),
- &sender_addr.addr_len),
- SyscallSucceeds());
- ASSERT_EQ(sizeof(struct sockaddr_in), sender_addr.addr_len);
- sockaddr_in* sender_addr_in =
- reinterpret_cast<sockaddr_in*>(&sender_addr.addr);
-
- // Send a multicast packet.
- char send_buf[4] = {};
- ASSERT_THAT(RetryEINTR(send)(sender->get(), send_buf, sizeof(send_buf), 0),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Receive a multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- auto src_addr = V4EmptyAddress();
- ASSERT_THAT(
- RetryEINTR(recvfrom)(receiver->get(), recv_buf, sizeof(recv_buf), 0,
- reinterpret_cast<sockaddr*>(&src_addr.addr),
- &src_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(recv_buf)));
- ASSERT_EQ(sizeof(struct sockaddr_in), src_addr.addr_len);
- sockaddr_in* src_addr_in = reinterpret_cast<sockaddr_in*>(&src_addr.addr);
-
- // Verify that the received source IP:port matches the sender one.
- EXPECT_EQ(sender_addr_in->sin_port, src_addr_in->sin_port);
- EXPECT_EQ(sender_addr_in->sin_addr.s_addr, src_addr_in->sin_addr.s_addr);
-}
-
-// Check that when setting the IP_MULTICAST_IF option to both an index pointing
-// to the loopback interface and an address pointing to the non-loopback
-// interface, a multicast packet sent out uses the latter as its source address.
-TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest,
- IpMulticastLoopbackIfNicAndAddr) {
- // FIXME(b/137899561): Linux instance for syscall tests sometimes misses its
- // IPv4 address on eth0.
- SKIP_IF(!got_if_infos_);
-
- // Create receiver, bind to ANY and join the multicast group.
- auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
- auto receiver_addr = V4Any();
- ASSERT_THAT(
- bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- receiver_addr.addr_len),
- SyscallSucceeds());
- socklen_t receiver_addr_len = receiver_addr.addr_len;
- ASSERT_THAT(getsockname(receiver->get(),
- reinterpret_cast<sockaddr*>(&receiver_addr.addr),
- &receiver_addr_len),
- SyscallSucceeds());
- EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
- int receiver_port =
- reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
- ip_mreqn group = {};
- group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
- group.imr_ifindex = lo_if_idx_;
- ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group,
- sizeof(group)),
- SyscallSucceeds());
-
- // Set outgoing multicast interface config, with NIC and addr pointing to
- // different interfaces.
- auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
- ip_mreqn iface = {};
- iface.imr_ifindex = lo_if_idx_;
- iface.imr_address = eth_if_sin_addr_;
- ASSERT_THAT(setsockopt(sender->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface,
- sizeof(iface)),
- SyscallSucceeds());
-
- // Send a multicast packet.
- auto sendto_addr = V4Multicast();
- reinterpret_cast<sockaddr_in*>(&sendto_addr.addr)->sin_port = receiver_port;
- char send_buf[4] = {};
- ASSERT_THAT(RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0,
- reinterpret_cast<sockaddr*>(&sendto_addr.addr),
- sendto_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(send_buf)));
-
- // Receive a multicast packet.
- char recv_buf[sizeof(send_buf)] = {};
- auto src_addr = V4EmptyAddress();
- ASSERT_THAT(
- RetryEINTR(recvfrom)(receiver->get(), recv_buf, sizeof(recv_buf), 0,
- reinterpret_cast<sockaddr*>(&src_addr.addr),
- &src_addr.addr_len),
- SyscallSucceedsWithValue(sizeof(recv_buf)));
- ASSERT_EQ(sizeof(struct sockaddr_in), src_addr.addr_len);
- sockaddr_in* src_addr_in = reinterpret_cast<sockaddr_in*>(&src_addr.addr);
-
- // FIXME (b/137781162): When sending a multicast packet use the proper logic
- // to determine the packet's src-IP.
- SKIP_IF(IsRunningOnGvisor());
-
- // Verify the received source address.
- EXPECT_EQ(eth_if_sin_addr_.s_addr, src_addr_in->sin_addr.s_addr);
-}
-
-// Check that when we are bound to one interface we can set IP_MULTICAST_IF to
-// another interface.
-TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest,
- IpMulticastLoopbackBindToOneIfSetMcastIfToAnother) {
- // FIXME(b/137899561): Linux instance for syscall tests sometimes misses its
- // IPv4 address on eth0.
- SKIP_IF(!got_if_infos_);
-
- // FIXME (b/137790511): When bound to one interface it is not possible to set
- // IP_MULTICAST_IF to a different interface.
- SKIP_IF(IsRunningOnGvisor());
-
- // Create sender and bind to eth interface.
- auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
- ASSERT_THAT(bind(sender->get(), eth_if_addr_, sizeof(sockaddr_in)),
- SyscallSucceeds());
-
- // Run through all possible combinations of index and address for
- // IP_MULTICAST_IF that selects the loopback interface.
- struct {
- int imr_ifindex;
- struct in_addr imr_address;
- } test_data[] = {
- {lo_if_idx_, {}},
- {0, lo_if_sin_addr_},
- {lo_if_idx_, lo_if_sin_addr_},
- {lo_if_idx_, eth_if_sin_addr_},
- };
- for (auto t : test_data) {
- ip_mreqn iface = {};
- iface.imr_ifindex = t.imr_ifindex;
- iface.imr_address = t.imr_address;
- EXPECT_THAT(setsockopt(sender->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface,
- sizeof(iface)),
- SyscallSucceeds())
- << "imr_index=" << iface.imr_ifindex
- << " imr_address=" << GetAddr4Str(&iface.imr_address);
- }
-}
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h
deleted file mode 100644
index bec2e96ee..000000000
--- a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_EXTERNAL_NETWORKING_H_
-#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_EXTERNAL_NETWORKING_H_
-
-#include "test/syscalls/linux/ip_socket_test_util.h"
-#include "test/syscalls/linux/socket_test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Test fixture for tests that apply to unbound IPv4 UDP sockets in a sandbox
-// with external networking support.
-class IPv4UDPUnboundExternalNetworkingSocketTest : public SimpleSocketTest {
- protected:
- void SetUp();
-
- IfAddrHelper if_helper_;
-
- // got_if_infos_ is set to false if SetUp() could not obtain all interface
- // infos that we need.
- bool got_if_infos_;
-
- // Interface infos.
- int lo_if_idx_;
- int eth_if_idx_;
- sockaddr* lo_if_addr_;
- sockaddr* eth_if_addr_;
- in_addr lo_if_sin_addr_;
- in_addr eth_if_sin_addr_;
-};
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_EXTERNAL_NETWORKING_H_
diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking_test.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking_test.cc
deleted file mode 100644
index 9d4e1ab97..000000000
--- a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking_test.cc
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <vector>
-
-#include "test/syscalls/linux/ip_socket_test_util.h"
-#include "test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketKind> GetSockets() {
- return ApplyVec<SocketKind>(
- IPv4UDPUnboundSocket,
- AllBitwiseCombinations(List<int>{0, SOCK_NONBLOCK}));
-}
-
-INSTANTIATE_TEST_SUITE_P(IPv4UDPUnboundSockets,
- IPv4UDPUnboundExternalNetworkingSocketTest,
- ::testing::ValuesIn(GetSockets()));
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_loopback.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_loopback.cc
deleted file mode 100644
index cb0105471..000000000
--- a/test/syscalls/linux/socket_ipv4_udp_unbound_loopback.cc
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <vector>
-
-#include "test/syscalls/linux/ip_socket_test_util.h"
-#include "test/syscalls/linux/socket_ipv4_udp_unbound.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketPairKind> GetSocketPairs() {
- return ApplyVec<SocketPairKind>(
- IPv4UDPUnboundSocketPair,
- AllBitwiseCombinations(List<int>{0, SOCK_NONBLOCK}));
-}
-
-INSTANTIATE_TEST_SUITE_P(IPv4UDPSockets, IPv4UDPUnboundSocketPairTest,
- ::testing::ValuesIn(GetSocketPairs()));
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_netdevice.cc b/test/syscalls/linux/socket_netdevice.cc
deleted file mode 100644
index 765f8e0e4..000000000
--- a/test/syscalls/linux/socket_netdevice.cc
+++ /dev/null
@@ -1,183 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <linux/netlink.h>
-#include <linux/rtnetlink.h>
-#include <linux/sockios.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-
-#include "gtest/gtest.h"
-#include "absl/base/internal/endian.h"
-#include "test/syscalls/linux/socket_netlink_util.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/test_util.h"
-
-// Tests for netdevice queries.
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-using ::testing::AnyOf;
-using ::testing::Eq;
-
-TEST(NetdeviceTest, Loopback) {
- FileDescriptor sock =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0));
-
- // Prepare the request.
- struct ifreq ifr;
- snprintf(ifr.ifr_name, IFNAMSIZ, "lo");
-
- // Check for a non-zero interface index.
- ASSERT_THAT(ioctl(sock.get(), SIOCGIFINDEX, &ifr), SyscallSucceeds());
- EXPECT_NE(ifr.ifr_ifindex, 0);
-
- // Check that the loopback is zero hardware address.
- ASSERT_THAT(ioctl(sock.get(), SIOCGIFHWADDR, &ifr), SyscallSucceeds());
- EXPECT_EQ(ifr.ifr_hwaddr.sa_data[0], 0);
- EXPECT_EQ(ifr.ifr_hwaddr.sa_data[1], 0);
- EXPECT_EQ(ifr.ifr_hwaddr.sa_data[2], 0);
- EXPECT_EQ(ifr.ifr_hwaddr.sa_data[3], 0);
- EXPECT_EQ(ifr.ifr_hwaddr.sa_data[4], 0);
- EXPECT_EQ(ifr.ifr_hwaddr.sa_data[5], 0);
-}
-
-TEST(NetdeviceTest, Netmask) {
- // We need an interface index to identify the loopback device.
- FileDescriptor sock =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0));
- struct ifreq ifr;
- snprintf(ifr.ifr_name, IFNAMSIZ, "lo");
- ASSERT_THAT(ioctl(sock.get(), SIOCGIFINDEX, &ifr), SyscallSucceeds());
- EXPECT_NE(ifr.ifr_ifindex, 0);
-
- // Use a netlink socket to get the netmask, which we'll then compare to the
- // netmask obtained via ioctl.
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket());
- uint32_t port = ASSERT_NO_ERRNO_AND_VALUE(NetlinkPortID(fd.get()));
-
- struct request {
- struct nlmsghdr hdr;
- struct rtgenmsg rgm;
- };
-
- constexpr uint32_t kSeq = 12345;
-
- struct request req;
- req.hdr.nlmsg_len = sizeof(req);
- req.hdr.nlmsg_type = RTM_GETADDR;
- req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
- req.hdr.nlmsg_seq = kSeq;
- req.rgm.rtgen_family = AF_UNSPEC;
-
- // Iterate through messages until we find the one containing the prefix length
- // (i.e. netmask) for the loopback device.
- int prefixlen = -1;
- ASSERT_NO_ERRNO(NetlinkRequestResponse(
- fd, &req, sizeof(req),
- [&](const struct nlmsghdr *hdr) {
- EXPECT_THAT(hdr->nlmsg_type, AnyOf(Eq(RTM_NEWADDR), Eq(NLMSG_DONE)));
-
- EXPECT_TRUE((hdr->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI)
- << std::hex << hdr->nlmsg_flags;
-
- EXPECT_EQ(hdr->nlmsg_seq, kSeq);
- EXPECT_EQ(hdr->nlmsg_pid, port);
-
- if (hdr->nlmsg_type != RTM_NEWADDR) {
- return;
- }
-
- // RTM_NEWADDR contains at least the header and ifaddrmsg.
- EXPECT_GE(hdr->nlmsg_len, sizeof(*hdr) + sizeof(struct ifaddrmsg));
-
- struct ifaddrmsg *ifaddrmsg =
- reinterpret_cast<struct ifaddrmsg *>(NLMSG_DATA(hdr));
- if (ifaddrmsg->ifa_index == static_cast<uint32_t>(ifr.ifr_ifindex) &&
- ifaddrmsg->ifa_family == AF_INET) {
- prefixlen = ifaddrmsg->ifa_prefixlen;
- }
- },
- false));
-
- ASSERT_GE(prefixlen, 0);
-
- // Netmask is stored big endian in struct sockaddr_in, so we do the same for
- // comparison.
- uint32_t mask = 0xffffffff << (32 - prefixlen);
- mask = absl::gbswap_32(mask);
-
- // Check that the loopback interface has the correct subnet mask.
- snprintf(ifr.ifr_name, IFNAMSIZ, "lo");
- ASSERT_THAT(ioctl(sock.get(), SIOCGIFNETMASK, &ifr), SyscallSucceeds());
- EXPECT_EQ(ifr.ifr_netmask.sa_family, AF_INET);
- struct sockaddr_in *sin =
- reinterpret_cast<struct sockaddr_in *>(&ifr.ifr_netmask);
- EXPECT_EQ(sin->sin_addr.s_addr, mask);
-}
-
-TEST(NetdeviceTest, InterfaceName) {
- FileDescriptor sock =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0));
-
- // Prepare the request.
- struct ifreq ifr;
- snprintf(ifr.ifr_name, IFNAMSIZ, "lo");
-
- // Check for a non-zero interface index.
- ASSERT_THAT(ioctl(sock.get(), SIOCGIFINDEX, &ifr), SyscallSucceeds());
- EXPECT_NE(ifr.ifr_ifindex, 0);
-
- // Check that SIOCGIFNAME finds the loopback interface.
- snprintf(ifr.ifr_name, IFNAMSIZ, "foo");
- ASSERT_THAT(ioctl(sock.get(), SIOCGIFNAME, &ifr), SyscallSucceeds());
- EXPECT_STREQ(ifr.ifr_name, "lo");
-}
-
-TEST(NetdeviceTest, InterfaceFlags) {
- FileDescriptor sock =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0));
-
- // Prepare the request.
- struct ifreq ifr;
- snprintf(ifr.ifr_name, IFNAMSIZ, "lo");
-
- // Check that SIOCGIFFLAGS marks the interface with IFF_LOOPBACK, IFF_UP, and
- // IFF_RUNNING.
- ASSERT_THAT(ioctl(sock.get(), SIOCGIFFLAGS, &ifr), SyscallSucceeds());
- EXPECT_EQ(ifr.ifr_flags & IFF_UP, IFF_UP);
- EXPECT_EQ(ifr.ifr_flags & IFF_RUNNING, IFF_RUNNING);
-}
-
-TEST(NetdeviceTest, InterfaceMTU) {
- FileDescriptor sock =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0));
-
- // Prepare the request.
- struct ifreq ifr = {};
- snprintf(ifr.ifr_name, IFNAMSIZ, "lo");
-
- // Check that SIOCGIFMTU returns a nonzero MTU.
- ASSERT_THAT(ioctl(sock.get(), SIOCGIFMTU, &ifr), SyscallSucceeds());
- EXPECT_GT(ifr.ifr_mtu, 0);
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_netlink_route.cc b/test/syscalls/linux/socket_netlink_route.cc
deleted file mode 100644
index 32fe0d6d1..000000000
--- a/test/syscalls/linux/socket_netlink_route.cc
+++ /dev/null
@@ -1,545 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <arpa/inet.h>
-#include <ifaddrs.h>
-#include <linux/netlink.h>
-#include <linux/rtnetlink.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <iostream>
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "absl/strings/str_format.h"
-#include "test/syscalls/linux/socket_netlink_util.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/cleanup.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/test_util.h"
-
-// Tests for NETLINK_ROUTE sockets.
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-using ::testing::AnyOf;
-using ::testing::Eq;
-
-// Netlink sockets must be SOCK_DGRAM or SOCK_RAW.
-TEST(NetlinkRouteTest, Types) {
- EXPECT_THAT(socket(AF_NETLINK, SOCK_STREAM, NETLINK_ROUTE),
- SyscallFailsWithErrno(ESOCKTNOSUPPORT));
- EXPECT_THAT(socket(AF_NETLINK, SOCK_SEQPACKET, NETLINK_ROUTE),
- SyscallFailsWithErrno(ESOCKTNOSUPPORT));
- EXPECT_THAT(socket(AF_NETLINK, SOCK_RDM, NETLINK_ROUTE),
- SyscallFailsWithErrno(ESOCKTNOSUPPORT));
- EXPECT_THAT(socket(AF_NETLINK, SOCK_DCCP, NETLINK_ROUTE),
- SyscallFailsWithErrno(ESOCKTNOSUPPORT));
- EXPECT_THAT(socket(AF_NETLINK, SOCK_PACKET, NETLINK_ROUTE),
- SyscallFailsWithErrno(ESOCKTNOSUPPORT));
-
- int fd;
- EXPECT_THAT(fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE),
- SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
-
- EXPECT_THAT(fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE),
- SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
-}
-
-TEST(NetlinkRouteTest, AutomaticPort) {
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE));
-
- struct sockaddr_nl addr = {};
- addr.nl_family = AF_NETLINK;
-
- EXPECT_THAT(
- bind(fd.get(), reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)),
- SyscallSucceeds());
-
- socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(getsockname(fd.get(), reinterpret_cast<struct sockaddr*>(&addr),
- &addrlen),
- SyscallSucceeds());
- EXPECT_EQ(addrlen, sizeof(addr));
- // This is the only netlink socket in the process, so it should get the PID as
- // the port id.
- //
- // N.B. Another process could theoretically have explicitly reserved our pid
- // as a port ID, but that is very unlikely.
- EXPECT_EQ(addr.nl_pid, getpid());
-}
-
-// Calling connect automatically binds to an automatic port.
-TEST(NetlinkRouteTest, ConnectBinds) {
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE));
-
- struct sockaddr_nl addr = {};
- addr.nl_family = AF_NETLINK;
-
- EXPECT_THAT(connect(fd.get(), reinterpret_cast<struct sockaddr*>(&addr),
- sizeof(addr)),
- SyscallSucceeds());
-
- socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(getsockname(fd.get(), reinterpret_cast<struct sockaddr*>(&addr),
- &addrlen),
- SyscallSucceeds());
- EXPECT_EQ(addrlen, sizeof(addr));
-
- // Each test is running in a pid namespace, so another process can explicitly
- // reserve our pid as a port ID. In this case, a negative portid value will be
- // set.
- if (static_cast<pid_t>(addr.nl_pid) > 0) {
- EXPECT_EQ(addr.nl_pid, getpid());
- }
-
- memset(&addr, 0, sizeof(addr));
- addr.nl_family = AF_NETLINK;
-
- // Connecting again is allowed, but keeps the same port.
- EXPECT_THAT(connect(fd.get(), reinterpret_cast<struct sockaddr*>(&addr),
- sizeof(addr)),
- SyscallSucceeds());
-
- addrlen = sizeof(addr);
- EXPECT_THAT(getsockname(fd.get(), reinterpret_cast<struct sockaddr*>(&addr),
- &addrlen),
- SyscallSucceeds());
- EXPECT_EQ(addrlen, sizeof(addr));
- EXPECT_EQ(addr.nl_pid, getpid());
-}
-
-TEST(NetlinkRouteTest, GetPeerName) {
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE));
-
- struct sockaddr_nl addr = {};
- socklen_t addrlen = sizeof(addr);
-
- EXPECT_THAT(getpeername(fd.get(), reinterpret_cast<struct sockaddr*>(&addr),
- &addrlen),
- SyscallSucceeds());
-
- EXPECT_EQ(addrlen, sizeof(addr));
- EXPECT_EQ(addr.nl_family, AF_NETLINK);
- // Peer is the kernel if we didn't connect elsewhere.
- EXPECT_EQ(addr.nl_pid, 0);
-}
-
-// Parameters for GetSockOpt test. They are:
-// 0: Socket option to query.
-// 1: A predicate to run on the returned sockopt value. Should return true if
-// the value is considered ok.
-// 2: A description of what the sockopt value is expected to be. Should complete
-// the sentence "<value> was unexpected, expected <description>"
-using SockOptTest = ::testing::TestWithParam<
- std::tuple<int, std::function<bool(int)>, std::string>>;
-
-TEST_P(SockOptTest, GetSockOpt) {
- int sockopt = std::get<0>(GetParam());
- auto verifier = std::get<1>(GetParam());
- std::string verifier_description = std::get<2>(GetParam());
-
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE));
-
- int res;
- socklen_t len = sizeof(res);
-
- EXPECT_THAT(getsockopt(fd.get(), SOL_SOCKET, sockopt, &res, &len),
- SyscallSucceeds());
-
- EXPECT_EQ(len, sizeof(res));
- EXPECT_TRUE(verifier(res)) << absl::StrFormat(
- "getsockopt(%d, SOL_SOCKET, %d, &res, &len) => res=%d was unexpected, "
- "expected %s",
- fd.get(), sockopt, res, verifier_description);
-}
-
-std::function<bool(int)> IsPositive() {
- return [](int val) { return val > 0; };
-}
-
-std::function<bool(int)> IsEqual(int target) {
- return [target](int val) { return val == target; };
-}
-
-INSTANTIATE_TEST_SUITE_P(
- NetlinkRouteTest, SockOptTest,
- ::testing::Values(
- std::make_tuple(SO_SNDBUF, IsPositive(), "positive send buffer size"),
- std::make_tuple(SO_RCVBUF, IsPositive(),
- "positive receive buffer size"),
- std::make_tuple(SO_TYPE, IsEqual(SOCK_RAW),
- absl::StrFormat("SOCK_RAW (%d)", SOCK_RAW)),
- std::make_tuple(SO_DOMAIN, IsEqual(AF_NETLINK),
- absl::StrFormat("AF_NETLINK (%d)", AF_NETLINK)),
- std::make_tuple(SO_PROTOCOL, IsEqual(NETLINK_ROUTE),
- absl::StrFormat("NETLINK_ROUTE (%d)", NETLINK_ROUTE))));
-
-// Validates the reponses to RTM_GETLINK + NLM_F_DUMP.
-void CheckGetLinkResponse(const struct nlmsghdr* hdr, int seq, int port) {
- EXPECT_THAT(hdr->nlmsg_type, AnyOf(Eq(RTM_NEWLINK), Eq(NLMSG_DONE)));
-
- EXPECT_TRUE((hdr->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI)
- << std::hex << hdr->nlmsg_flags;
-
- EXPECT_EQ(hdr->nlmsg_seq, seq);
- EXPECT_EQ(hdr->nlmsg_pid, port);
-
- if (hdr->nlmsg_type != RTM_NEWLINK) {
- return;
- }
-
- // RTM_NEWLINK contains at least the header and ifinfomsg.
- EXPECT_GE(hdr->nlmsg_len, NLMSG_SPACE(sizeof(struct ifinfomsg)));
-
- // TODO(mpratt): Check ifinfomsg contents and following attrs.
-}
-
-TEST(NetlinkRouteTest, GetLinkDump) {
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket());
- uint32_t port = ASSERT_NO_ERRNO_AND_VALUE(NetlinkPortID(fd.get()));
-
- struct request {
- struct nlmsghdr hdr;
- struct ifinfomsg ifm;
- };
-
- constexpr uint32_t kSeq = 12345;
-
- struct request req = {};
- req.hdr.nlmsg_len = sizeof(req);
- req.hdr.nlmsg_type = RTM_GETLINK;
- req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
- req.hdr.nlmsg_seq = kSeq;
- req.ifm.ifi_family = AF_UNSPEC;
-
- // Loopback is common among all tests, check that it's found.
- bool loopbackFound = false;
- ASSERT_NO_ERRNO(NetlinkRequestResponse(
- fd, &req, sizeof(req),
- [&](const struct nlmsghdr* hdr) {
- CheckGetLinkResponse(hdr, kSeq, port);
- if (hdr->nlmsg_type != RTM_NEWLINK) {
- return;
- }
- ASSERT_GE(hdr->nlmsg_len, NLMSG_SPACE(sizeof(struct ifinfomsg)));
- const struct ifinfomsg* msg =
- reinterpret_cast<const struct ifinfomsg*>(NLMSG_DATA(hdr));
- std::cout << "Found interface idx=" << msg->ifi_index
- << ", type=" << std::hex << msg->ifi_type;
- if (msg->ifi_type == ARPHRD_LOOPBACK) {
- loopbackFound = true;
- EXPECT_NE(msg->ifi_flags & IFF_LOOPBACK, 0);
- }
- },
- false));
- EXPECT_TRUE(loopbackFound);
-}
-
-TEST(NetlinkRouteTest, MsgHdrMsgUnsuppType) {
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket());
-
- struct request {
- struct nlmsghdr hdr;
- struct ifinfomsg ifm;
- };
-
- constexpr uint32_t kSeq = 12345;
-
- struct request req = {};
- req.hdr.nlmsg_len = sizeof(req);
- // If type & 0x3 is equal to 0x2, this means a get request
- // which doesn't require CAP_SYS_ADMIN.
- req.hdr.nlmsg_type = ((__RTM_MAX + 1024) & (~0x3)) | 0x2;
- req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
- req.hdr.nlmsg_seq = kSeq;
- req.ifm.ifi_family = AF_UNSPEC;
-
- ASSERT_NO_ERRNO(NetlinkRequestResponse(
- fd, &req, sizeof(req),
- [&](const struct nlmsghdr* hdr) {
- EXPECT_THAT(hdr->nlmsg_type, Eq(NLMSG_ERROR));
- EXPECT_EQ(hdr->nlmsg_seq, kSeq);
- EXPECT_GE(hdr->nlmsg_len, sizeof(*hdr) + sizeof(struct nlmsgerr));
-
- const struct nlmsgerr* msg =
- reinterpret_cast<const struct nlmsgerr*>(NLMSG_DATA(hdr));
- EXPECT_EQ(msg->error, -EOPNOTSUPP);
- },
- true));
-}
-
-TEST(NetlinkRouteTest, MsgHdrMsgTrunc) {
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket());
-
- struct request {
- struct nlmsghdr hdr;
- struct ifinfomsg ifm;
- };
-
- constexpr uint32_t kSeq = 12345;
-
- struct request req = {};
- req.hdr.nlmsg_len = sizeof(req);
- req.hdr.nlmsg_type = RTM_GETLINK;
- req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
- req.hdr.nlmsg_seq = kSeq;
- req.ifm.ifi_family = AF_UNSPEC;
-
- struct iovec iov = {};
- iov.iov_base = &req;
- iov.iov_len = sizeof(req);
-
- struct msghdr msg = {};
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
- // No destination required; it defaults to pid 0, the kernel.
-
- ASSERT_THAT(RetryEINTR(sendmsg)(fd.get(), &msg, 0), SyscallSucceeds());
-
- // Small enough to ensure that the response doesn't fit.
- constexpr size_t kBufferSize = 10;
- std::vector<char> buf(kBufferSize);
- iov.iov_base = buf.data();
- iov.iov_len = buf.size();
-
- ASSERT_THAT(RetryEINTR(recvmsg)(fd.get(), &msg, 0),
- SyscallSucceedsWithValue(kBufferSize));
- EXPECT_EQ((msg.msg_flags & MSG_TRUNC), MSG_TRUNC);
-}
-
-TEST(NetlinkRouteTest, MsgTruncMsgHdrMsgTrunc) {
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket());
-
- struct request {
- struct nlmsghdr hdr;
- struct ifinfomsg ifm;
- };
-
- constexpr uint32_t kSeq = 12345;
-
- struct request req = {};
- req.hdr.nlmsg_len = sizeof(req);
- req.hdr.nlmsg_type = RTM_GETLINK;
- req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
- req.hdr.nlmsg_seq = kSeq;
- req.ifm.ifi_family = AF_UNSPEC;
-
- struct iovec iov = {};
- iov.iov_base = &req;
- iov.iov_len = sizeof(req);
-
- struct msghdr msg = {};
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
- // No destination required; it defaults to pid 0, the kernel.
-
- ASSERT_THAT(RetryEINTR(sendmsg)(fd.get(), &msg, 0), SyscallSucceeds());
-
- // Small enough to ensure that the response doesn't fit.
- constexpr size_t kBufferSize = 10;
- std::vector<char> buf(kBufferSize);
- iov.iov_base = buf.data();
- iov.iov_len = buf.size();
-
- int res = 0;
- ASSERT_THAT(res = RetryEINTR(recvmsg)(fd.get(), &msg, MSG_TRUNC),
- SyscallSucceeds());
- EXPECT_GT(res, kBufferSize);
- EXPECT_EQ((msg.msg_flags & MSG_TRUNC), MSG_TRUNC);
-}
-
-TEST(NetlinkRouteTest, ControlMessageIgnored) {
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket());
- uint32_t port = ASSERT_NO_ERRNO_AND_VALUE(NetlinkPortID(fd.get()));
-
- struct request {
- struct nlmsghdr control_hdr;
- struct nlmsghdr message_hdr;
- struct ifinfomsg ifm;
- };
-
- constexpr uint32_t kSeq = 12345;
-
- struct request req = {};
-
- // This control message is ignored. We still receive a response for the
- // following RTM_GETLINK.
- req.control_hdr.nlmsg_len = sizeof(req.control_hdr);
- req.control_hdr.nlmsg_type = NLMSG_DONE;
- req.control_hdr.nlmsg_seq = kSeq;
-
- req.message_hdr.nlmsg_len = sizeof(req.message_hdr) + sizeof(req.ifm);
- req.message_hdr.nlmsg_type = RTM_GETLINK;
- req.message_hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
- req.message_hdr.nlmsg_seq = kSeq;
-
- req.ifm.ifi_family = AF_UNSPEC;
-
- ASSERT_NO_ERRNO(NetlinkRequestResponse(
- fd, &req, sizeof(req),
- [&](const struct nlmsghdr* hdr) {
- CheckGetLinkResponse(hdr, kSeq, port);
- },
- false));
-}
-
-TEST(NetlinkRouteTest, GetAddrDump) {
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket());
- uint32_t port = ASSERT_NO_ERRNO_AND_VALUE(NetlinkPortID(fd.get()));
-
- struct request {
- struct nlmsghdr hdr;
- struct rtgenmsg rgm;
- };
-
- constexpr uint32_t kSeq = 12345;
-
- struct request req;
- req.hdr.nlmsg_len = sizeof(req);
- req.hdr.nlmsg_type = RTM_GETADDR;
- req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
- req.hdr.nlmsg_seq = kSeq;
- req.rgm.rtgen_family = AF_UNSPEC;
-
- ASSERT_NO_ERRNO(NetlinkRequestResponse(
- fd, &req, sizeof(req),
- [&](const struct nlmsghdr* hdr) {
- EXPECT_THAT(hdr->nlmsg_type, AnyOf(Eq(RTM_NEWADDR), Eq(NLMSG_DONE)));
-
- EXPECT_TRUE((hdr->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI)
- << std::hex << hdr->nlmsg_flags;
-
- EXPECT_EQ(hdr->nlmsg_seq, kSeq);
- EXPECT_EQ(hdr->nlmsg_pid, port);
-
- if (hdr->nlmsg_type != RTM_NEWADDR) {
- return;
- }
-
- // RTM_NEWADDR contains at least the header and ifaddrmsg.
- EXPECT_GE(hdr->nlmsg_len, sizeof(*hdr) + sizeof(struct ifaddrmsg));
-
- // TODO(mpratt): Check ifaddrmsg contents and following attrs.
- },
- false));
-}
-
-TEST(NetlinkRouteTest, LookupAll) {
- struct ifaddrs* if_addr_list = nullptr;
- auto cleanup = Cleanup([&if_addr_list]() { freeifaddrs(if_addr_list); });
-
- // Not a syscall but we can use the syscall matcher as glibc sets errno.
- ASSERT_THAT(getifaddrs(&if_addr_list), SyscallSucceeds());
-
- int count = 0;
- for (struct ifaddrs* i = if_addr_list; i; i = i->ifa_next) {
- if (!i->ifa_addr || (i->ifa_addr->sa_family != AF_INET &&
- i->ifa_addr->sa_family != AF_INET6)) {
- continue;
- }
- count++;
- }
- ASSERT_GT(count, 0);
-}
-
-// GetRouteDump tests a RTM_GETROUTE + NLM_F_DUMP request.
-TEST(NetlinkRouteTest, GetRouteDump) {
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket());
- uint32_t port = ASSERT_NO_ERRNO_AND_VALUE(NetlinkPortID(fd.get()));
-
- struct request {
- struct nlmsghdr hdr;
- struct rtmsg rtm;
- };
-
- constexpr uint32_t kSeq = 12345;
-
- struct request req = {};
- req.hdr.nlmsg_len = sizeof(req);
- req.hdr.nlmsg_type = RTM_GETROUTE;
- req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
- req.hdr.nlmsg_seq = kSeq;
- req.rtm.rtm_family = AF_UNSPEC;
-
- bool routeFound = false;
- bool dstFound = true;
- ASSERT_NO_ERRNO(NetlinkRequestResponse(
- fd, &req, sizeof(req),
- [&](const struct nlmsghdr* hdr) {
- // Validate the reponse to RTM_GETROUTE + NLM_F_DUMP.
- EXPECT_THAT(hdr->nlmsg_type, AnyOf(Eq(RTM_NEWROUTE), Eq(NLMSG_DONE)));
-
- EXPECT_TRUE((hdr->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI)
- << std::hex << hdr->nlmsg_flags;
-
- EXPECT_EQ(hdr->nlmsg_seq, kSeq);
- EXPECT_EQ(hdr->nlmsg_pid, port);
-
- // The test should not proceed if it's not a RTM_NEWROUTE message.
- if (hdr->nlmsg_type != RTM_NEWROUTE) {
- return;
- }
-
- // RTM_NEWROUTE contains at least the header and rtmsg.
- ASSERT_GE(hdr->nlmsg_len, NLMSG_SPACE(sizeof(struct rtmsg)));
- const struct rtmsg* msg =
- reinterpret_cast<const struct rtmsg*>(NLMSG_DATA(hdr));
- // NOTE: rtmsg fields are char fields.
- std::cout << "Found route table=" << static_cast<int>(msg->rtm_table)
- << ", protocol=" << static_cast<int>(msg->rtm_protocol)
- << ", scope=" << static_cast<int>(msg->rtm_scope)
- << ", type=" << static_cast<int>(msg->rtm_type);
-
- int len = RTM_PAYLOAD(hdr);
- bool rtDstFound = false;
- for (struct rtattr* attr = RTM_RTA(msg); RTA_OK(attr, len);
- attr = RTA_NEXT(attr, len)) {
- if (attr->rta_type == RTA_DST) {
- char address[INET_ADDRSTRLEN] = {};
- inet_ntop(AF_INET, RTA_DATA(attr), address, sizeof(address));
- std::cout << ", dst=" << address;
- rtDstFound = true;
- }
- }
-
- std::cout << std::endl;
-
- if (msg->rtm_table == RT_TABLE_MAIN) {
- routeFound = true;
- dstFound = rtDstFound && dstFound;
- }
- },
- false));
- // At least one route found in main route table.
- EXPECT_TRUE(routeFound);
- // Found RTA_DST for each route in main table.
- EXPECT_TRUE(dstFound);
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_netlink_util.cc b/test/syscalls/linux/socket_netlink_util.cc
deleted file mode 100644
index 36b6560c2..000000000
--- a/test/syscalls/linux/socket_netlink_util.cc
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sys/socket.h>
-
-#include <linux/if_arp.h>
-#include <linux/netlink.h>
-#include <linux/rtnetlink.h>
-
-#include <vector>
-
-#include "absl/strings/str_cat.h"
-#include "test/syscalls/linux/socket_netlink_util.h"
-#include "test/syscalls/linux/socket_test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-PosixErrorOr<FileDescriptor> NetlinkBoundSocket() {
- FileDescriptor fd;
- ASSIGN_OR_RETURN_ERRNO(fd, Socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE));
-
- struct sockaddr_nl addr = {};
- addr.nl_family = AF_NETLINK;
-
- RETURN_ERROR_IF_SYSCALL_FAIL(
- bind(fd.get(), reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)));
- MaybeSave();
-
- return std::move(fd);
-}
-
-PosixErrorOr<uint32_t> NetlinkPortID(int fd) {
- struct sockaddr_nl addr;
- socklen_t addrlen = sizeof(addr);
-
- RETURN_ERROR_IF_SYSCALL_FAIL(
- getsockname(fd, reinterpret_cast<struct sockaddr*>(&addr), &addrlen));
- MaybeSave();
-
- return static_cast<uint32_t>(addr.nl_pid);
-}
-
-PosixError NetlinkRequestResponse(
- const FileDescriptor& fd, void* request, size_t len,
- const std::function<void(const struct nlmsghdr* hdr)>& fn,
- bool expect_nlmsgerr) {
- struct iovec iov = {};
- iov.iov_base = request;
- iov.iov_len = len;
-
- struct msghdr msg = {};
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
- // No destination required; it defaults to pid 0, the kernel.
-
- RETURN_ERROR_IF_SYSCALL_FAIL(RetryEINTR(sendmsg)(fd.get(), &msg, 0));
-
- constexpr size_t kBufferSize = 4096;
- std::vector<char> buf(kBufferSize);
- iov.iov_base = buf.data();
- iov.iov_len = buf.size();
-
- // Response is a series of NLM_F_MULTI messages, ending with a NLMSG_DONE
- // message.
- int type = -1;
- do {
- int len;
- RETURN_ERROR_IF_SYSCALL_FAIL(len = RetryEINTR(recvmsg)(fd.get(), &msg, 0));
-
- // We don't bother with the complexity of dealing with truncated messages.
- // We must allocate a large enough buffer up front.
- if ((msg.msg_flags & MSG_TRUNC) == MSG_TRUNC) {
- return PosixError(EIO,
- absl::StrCat("Received truncated message with flags: ",
- msg.msg_flags));
- }
-
- for (struct nlmsghdr* hdr = reinterpret_cast<struct nlmsghdr*>(buf.data());
- NLMSG_OK(hdr, len); hdr = NLMSG_NEXT(hdr, len)) {
- fn(hdr);
- type = hdr->nlmsg_type;
- }
- } while (type != NLMSG_DONE && type != NLMSG_ERROR);
-
- if (expect_nlmsgerr) {
- EXPECT_EQ(type, NLMSG_ERROR);
- } else {
- EXPECT_EQ(type, NLMSG_DONE);
- }
- return NoError();
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_netlink_util.h b/test/syscalls/linux/socket_netlink_util.h
deleted file mode 100644
index db8639a2f..000000000
--- a/test/syscalls/linux/socket_netlink_util.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_SOCKET_NETLINK_UTIL_H_
-#define GVISOR_TEST_SYSCALLS_SOCKET_NETLINK_UTIL_H_
-
-#include <linux/if_arp.h>
-#include <linux/netlink.h>
-#include <linux/rtnetlink.h>
-
-#include "test/util/file_descriptor.h"
-#include "test/util/posix_error.h"
-
-namespace gvisor {
-namespace testing {
-
-// Returns a bound NETLINK_ROUTE socket.
-PosixErrorOr<FileDescriptor> NetlinkBoundSocket();
-
-// Returns the port ID of the passed socket.
-PosixErrorOr<uint32_t> NetlinkPortID(int fd);
-
-// Send the passed request and call fn will all response netlink messages.
-PosixError NetlinkRequestResponse(
- const FileDescriptor& fd, void* request, size_t len,
- const std::function<void(const struct nlmsghdr* hdr)>& fn,
- bool expect_nlmsgerr);
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_SOCKET_NETLINK_UTIL_H_
diff --git a/test/syscalls/linux/socket_non_blocking.cc b/test/syscalls/linux/socket_non_blocking.cc
deleted file mode 100644
index 73e6dc618..000000000
--- a/test/syscalls/linux/socket_non_blocking.cc
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/socket_non_blocking.h"
-
-#include <stdio.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <sys/un.h>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-TEST_P(NonBlockingSocketPairTest, ReadNothingAvailable) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- char buf[20] = {};
- ASSERT_THAT(ReadFd(sockets->first_fd(), buf, sizeof(buf)),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST_P(NonBlockingSocketPairTest, RecvNothingAvailable) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- char buf[20] = {};
- ASSERT_THAT(RetryEINTR(recv)(sockets->first_fd(), buf, sizeof(buf), 0),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST_P(NonBlockingSocketPairTest, RecvMsgNothingAvailable) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct iovec iov;
- char buf[20] = {};
- iov.iov_base = buf;
- iov.iov_len = sizeof(buf);
- struct msghdr msg = {};
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sockets->first_fd(), &msg, 0),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_non_blocking.h b/test/syscalls/linux/socket_non_blocking.h
deleted file mode 100644
index bd3e02fd2..000000000
--- a/test/syscalls/linux/socket_non_blocking.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_NON_BLOCKING_H_
-#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_NON_BLOCKING_H_
-
-#include "test/syscalls/linux/socket_test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Test fixture for tests that apply to pairs of connected non-blocking sockets.
-using NonBlockingSocketPairTest = SocketPairTest;
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_NON_BLOCKING_H_
diff --git a/test/syscalls/linux/socket_non_stream.cc b/test/syscalls/linux/socket_non_stream.cc
deleted file mode 100644
index 3c599b6e8..000000000
--- a/test/syscalls/linux/socket_non_stream.cc
+++ /dev/null
@@ -1,229 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/socket_non_stream.h"
-
-#include <stdio.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/ip_socket_test_util.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-TEST_P(NonStreamSocketPairTest, SendMsgTooLarge) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- int sndbuf;
- socklen_t length = sizeof(sndbuf);
- ASSERT_THAT(
- getsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDBUF, &sndbuf, &length),
- SyscallSucceeds());
-
- // Make the call too large to fit in the send buffer.
- const int buffer_size = 3 * sndbuf;
-
- EXPECT_THAT(SendLargeSendMsg(sockets, buffer_size, false /* reader */),
- SyscallFailsWithErrno(EMSGSIZE));
-}
-
-// Stream sockets allow data sent with a single (e.g. write, sendmsg) syscall
-// to be read in pieces with multiple (e.g. read, recvmsg) syscalls.
-//
-// SplitRecv checks that control messages can only be read on the first (e.g.
-// read, recvmsg) syscall, even if it doesn't provide space for the control
-// message.
-TEST_P(NonStreamSocketPairTest, SplitRecv) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- char sent_data[512];
- RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
- char received_data[sizeof(sent_data) / 2];
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(received_data), 0),
- SyscallSucceedsWithValue(sizeof(received_data)));
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(received_data)));
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(received_data), MSG_DONTWAIT),
- SyscallFailsWithErrno(EWOULDBLOCK));
-}
-
-// Stream sockets allow data sent with multiple sends to be read in a single
-// recv. Datagram sockets do not.
-//
-// SingleRecv checks that only a single message is readable in a single recv.
-TEST_P(NonStreamSocketPairTest, SingleRecv) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- char sent_data1[20];
- RandomizeBuffer(sent_data1, sizeof(sent_data1));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), sent_data1, sizeof(sent_data1), 0),
- SyscallSucceedsWithValue(sizeof(sent_data1)));
- char sent_data2[20];
- RandomizeBuffer(sent_data2, sizeof(sent_data2));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), sent_data2, sizeof(sent_data2), 0),
- SyscallSucceedsWithValue(sizeof(sent_data2)));
- char received_data[sizeof(sent_data1) + sizeof(sent_data2)];
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(received_data), 0),
- SyscallSucceedsWithValue(sizeof(sent_data1)));
- EXPECT_EQ(0, memcmp(sent_data1, received_data, sizeof(sent_data1)));
-}
-
-TEST_P(NonStreamSocketPairTest, RecvmsgMsghdrFlagMsgTrunc) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[10];
- RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- char received_data[sizeof(sent_data) / 2] = {};
-
- struct iovec iov;
- iov.iov_base = received_data;
- iov.iov_len = sizeof(received_data);
- struct msghdr msg = {};
- msg.msg_flags = -1;
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0),
- SyscallSucceedsWithValue(sizeof(received_data)));
- EXPECT_EQ(0, memcmp(received_data, sent_data, sizeof(received_data)));
-
- // Check that msghdr flags were updated.
- EXPECT_EQ(msg.msg_flags, MSG_TRUNC);
-}
-
-// Stream sockets allow data sent with multiple sends to be peeked at in a
-// single recv. Datagram sockets (except for unix sockets) do not.
-//
-// SinglePeek checks that only a single message is peekable in a single recv.
-TEST_P(NonStreamSocketPairTest, SinglePeek) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- char sent_data1[20];
- RandomizeBuffer(sent_data1, sizeof(sent_data1));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), sent_data1, sizeof(sent_data1), 0),
- SyscallSucceedsWithValue(sizeof(sent_data1)));
- char sent_data2[20];
- RandomizeBuffer(sent_data2, sizeof(sent_data2));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), sent_data2, sizeof(sent_data2), 0),
- SyscallSucceedsWithValue(sizeof(sent_data2)));
- char received_data[sizeof(sent_data1) + sizeof(sent_data2)];
- for (int i = 0; i < 3; i++) {
- memset(received_data, 0, sizeof(received_data));
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(received_data), MSG_PEEK),
- SyscallSucceedsWithValue(sizeof(sent_data1)));
- EXPECT_EQ(0, memcmp(sent_data1, received_data, sizeof(sent_data1)));
- }
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(sent_data1), 0),
- SyscallSucceedsWithValue(sizeof(sent_data1)));
- EXPECT_EQ(0, memcmp(sent_data1, received_data, sizeof(sent_data1)));
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(sent_data2), 0),
- SyscallSucceedsWithValue(sizeof(sent_data2)));
- EXPECT_EQ(0, memcmp(sent_data2, received_data, sizeof(sent_data2)));
-}
-
-TEST_P(NonStreamSocketPairTest, MsgTruncTruncation) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- char sent_data[512];
- RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
- char received_data[sizeof(sent_data)] = {};
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(received_data) / 2, MSG_TRUNC),
- SyscallSucceedsWithValue(sizeof(sent_data)));
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data) / 2));
-
- // Check that we didn't get any extra data.
- EXPECT_NE(0, memcmp(sent_data + sizeof(sent_data) / 2,
- received_data + sizeof(received_data) / 2,
- sizeof(sent_data) / 2));
-}
-
-TEST_P(NonStreamSocketPairTest, MsgTruncTruncationRecvmsgMsghdrFlagMsgTrunc) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[10];
- RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- char received_data[sizeof(sent_data) / 2] = {};
-
- struct iovec iov;
- iov.iov_base = received_data;
- iov.iov_len = sizeof(received_data);
- struct msghdr msg = {};
- msg.msg_flags = -1;
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, MSG_TRUNC),
- SyscallSucceedsWithValue(sizeof(sent_data)));
- EXPECT_EQ(0, memcmp(received_data, sent_data, sizeof(received_data)));
-
- // Check that msghdr flags were updated.
- EXPECT_EQ(msg.msg_flags, MSG_TRUNC);
-}
-
-TEST_P(NonStreamSocketPairTest, MsgTruncSameSize) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- char sent_data[512];
- RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
- char received_data[sizeof(sent_data)];
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(received_data), MSG_TRUNC),
- SyscallSucceedsWithValue(sizeof(received_data)));
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-}
-
-TEST_P(NonStreamSocketPairTest, MsgTruncNotFull) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- char sent_data[512];
- RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
- char received_data[2 * sizeof(sent_data)];
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(received_data), MSG_TRUNC),
- SyscallSucceedsWithValue(sizeof(sent_data)));
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_non_stream.h b/test/syscalls/linux/socket_non_stream.h
deleted file mode 100644
index 469fbe6a2..000000000
--- a/test/syscalls/linux/socket_non_stream.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_NON_STREAM_H_
-#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_NON_STREAM_H_
-
-#include "test/syscalls/linux/socket_test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Test fixture for tests that apply to pairs of connected non-stream sockets.
-using NonStreamSocketPairTest = SocketPairTest;
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_NON_STREAM_H_
diff --git a/test/syscalls/linux/socket_non_stream_blocking.cc b/test/syscalls/linux/socket_non_stream_blocking.cc
deleted file mode 100644
index 76127d181..000000000
--- a/test/syscalls/linux/socket_non_stream_blocking.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/socket_non_stream_blocking.h"
-
-#include <stdio.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <sys/un.h>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-TEST_P(BlockingNonStreamSocketPairTest, RecvLessThanBufferWaitAll) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[100];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- ASSERT_THAT(write(sockets->first_fd(), sent_data, sizeof(sent_data)),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- char received_data[sizeof(sent_data) * 2] = {};
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(received_data), MSG_WAITALL),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_non_stream_blocking.h b/test/syscalls/linux/socket_non_stream_blocking.h
deleted file mode 100644
index 6e205a039..000000000
--- a/test/syscalls/linux/socket_non_stream_blocking.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_NON_STREAM_BLOCKING_H_
-#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_NON_STREAM_BLOCKING_H_
-
-#include "test/syscalls/linux/socket_test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Test fixture for tests that apply to pairs of blocking connected non-stream
-// sockets.
-using BlockingNonStreamSocketPairTest = SocketPairTest;
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_NON_STREAM_BLOCKING_H_
diff --git a/test/syscalls/linux/socket_stream.cc b/test/syscalls/linux/socket_stream.cc
deleted file mode 100644
index 0417dd347..000000000
--- a/test/syscalls/linux/socket_stream.cc
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/socket_stream.h"
-
-#include <stdio.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <sys/un.h>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "absl/time/clock.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-TEST_P(StreamSocketPairTest, SplitRecv) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- char sent_data[512];
- RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
- char received_data[sizeof(sent_data) / 2];
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(received_data), 0),
- SyscallSucceedsWithValue(sizeof(received_data)));
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(received_data)));
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(received_data), 0),
- SyscallSucceedsWithValue(sizeof(received_data)));
- EXPECT_EQ(0, memcmp(sent_data + sizeof(received_data), received_data,
- sizeof(received_data)));
-}
-
-// Stream sockets allow data sent with multiple sends to be read in a single
-// recv.
-//
-// CoalescedRecv checks that multiple messages are readable in a single recv.
-TEST_P(StreamSocketPairTest, CoalescedRecv) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- char sent_data1[20];
- RandomizeBuffer(sent_data1, sizeof(sent_data1));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), sent_data1, sizeof(sent_data1), 0),
- SyscallSucceedsWithValue(sizeof(sent_data1)));
- char sent_data2[20];
- RandomizeBuffer(sent_data2, sizeof(sent_data2));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), sent_data2, sizeof(sent_data2), 0),
- SyscallSucceedsWithValue(sizeof(sent_data2)));
- char received_data[sizeof(sent_data1) + sizeof(sent_data2)];
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(received_data), 0),
- SyscallSucceedsWithValue(sizeof(received_data)));
- EXPECT_EQ(0, memcmp(sent_data1, received_data, sizeof(sent_data1)));
- EXPECT_EQ(0, memcmp(sent_data2, received_data + sizeof(sent_data1),
- sizeof(sent_data2)));
-}
-
-TEST_P(StreamSocketPairTest, WriteOneSideClosed) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds());
- const char str[] = "abc";
- ASSERT_THAT(write(sockets->second_fd(), str, 3),
- SyscallFailsWithErrno(EPIPE));
-}
-
-TEST_P(StreamSocketPairTest, RecvmsgMsghdrFlagsNoMsgTrunc) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[10];
- RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- char received_data[sizeof(sent_data) / 2] = {};
-
- struct iovec iov;
- iov.iov_base = received_data;
- iov.iov_len = sizeof(received_data);
- struct msghdr msg = {};
- msg.msg_flags = -1;
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0),
- SyscallSucceedsWithValue(sizeof(received_data)));
- EXPECT_EQ(0, memcmp(received_data, sent_data, sizeof(received_data)));
-
- // Check that msghdr flags were cleared (MSG_TRUNC was not set).
- EXPECT_EQ(msg.msg_flags, 0);
-}
-
-TEST_P(StreamSocketPairTest, MsgTrunc) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- char sent_data[512];
- RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
- char received_data[sizeof(sent_data)];
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(received_data) / 2, MSG_TRUNC),
- SyscallSucceedsWithValue(sizeof(sent_data) / 2));
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data) / 2));
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_stream.h b/test/syscalls/linux/socket_stream.h
deleted file mode 100644
index b837b8f8c..000000000
--- a/test/syscalls/linux/socket_stream.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_STREAM_H_
-#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_STREAM_H_
-
-#include "test/syscalls/linux/socket_test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Test fixture for tests that apply to pairs of blocking and non-blocking
-// connected stream sockets.
-using StreamSocketPairTest = SocketPairTest;
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_STREAM_H_
diff --git a/test/syscalls/linux/socket_stream_blocking.cc b/test/syscalls/linux/socket_stream_blocking.cc
deleted file mode 100644
index 8367460d2..000000000
--- a/test/syscalls/linux/socket_stream_blocking.cc
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/socket_stream_blocking.h"
-
-#include <stdio.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <sys/un.h>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-#include "test/util/timer_util.h"
-
-namespace gvisor {
-namespace testing {
-
-TEST_P(BlockingStreamSocketPairTest, BlockPartialWriteClosed) {
- // FIXME(b/35921550): gVisor doesn't support SO_SNDBUF on UDS, nor does it
- // enforce any limit; it will write arbitrary amounts of data without
- // blocking.
- SKIP_IF(IsRunningOnGvisor());
-
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- int buffer_size;
- socklen_t length = sizeof(buffer_size);
- ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDBUF,
- &buffer_size, &length),
- SyscallSucceeds());
-
- int wfd = sockets->first_fd();
- ScopedThread t([wfd, buffer_size]() {
- std::vector<char> buf(2 * buffer_size);
- // Write more than fits in the buffer. Blocks then returns partial write
- // when the other end is closed. The next call returns EPIPE.
- //
- // N.B. writes occur in chunks, so we may see less than buffer_size from
- // the first call.
- ASSERT_THAT(write(wfd, buf.data(), buf.size()),
- SyscallSucceedsWithValue(::testing::Gt(0)));
- ASSERT_THAT(write(wfd, buf.data(), buf.size()),
- ::testing::AnyOf(SyscallFailsWithErrno(EPIPE),
- SyscallFailsWithErrno(ECONNRESET)));
- });
-
- // Leave time for write to become blocked.
- absl::SleepFor(absl::Seconds(1));
-
- ASSERT_THAT(close(sockets->release_second_fd()), SyscallSucceeds());
-}
-
-// Random save may interrupt the call to sendmsg() in SendLargeSendMsg(),
-// causing the write to be incomplete and the test to hang.
-TEST_P(BlockingStreamSocketPairTest, SendMsgTooLarge_NoRandomSave) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- int sndbuf;
- socklen_t length = sizeof(sndbuf);
- ASSERT_THAT(
- getsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDBUF, &sndbuf, &length),
- SyscallSucceeds());
-
- // Make the call too large to fit in the send buffer.
- const int buffer_size = 3 * sndbuf;
-
- EXPECT_THAT(SendLargeSendMsg(sockets, buffer_size, true /* reader */),
- SyscallSucceedsWithValue(buffer_size));
-}
-
-TEST_P(BlockingStreamSocketPairTest, RecvLessThanBuffer) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[100];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- ASSERT_THAT(write(sockets->first_fd(), sent_data, sizeof(sent_data)),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- char received_data[200] = {};
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(received_data), 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-}
-
-// Test that MSG_WAITALL causes recv to block until all requested data is
-// received. Random save can interrupt blocking and cause received data to be
-// returned, even if the amount received is less than the full requested amount.
-TEST_P(BlockingStreamSocketPairTest, RecvLessThanBufferWaitAll_NoRandomSave) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[100];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- ASSERT_THAT(write(sockets->first_fd(), sent_data, sizeof(sent_data)),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- constexpr auto kDuration = absl::Milliseconds(200);
- auto before = Now(CLOCK_MONOTONIC);
-
- const ScopedThread t([&]() {
- absl::SleepFor(kDuration);
-
- // Don't let saving after the write interrupt the blocking recv.
- const DisableSave ds;
-
- ASSERT_THAT(write(sockets->first_fd(), sent_data, sizeof(sent_data)),
- SyscallSucceedsWithValue(sizeof(sent_data)));
- });
-
- char received_data[sizeof(sent_data) * 2] = {};
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(received_data), MSG_WAITALL),
- SyscallSucceedsWithValue(sizeof(received_data)));
-
- auto after = Now(CLOCK_MONOTONIC);
- EXPECT_GE(after - before, kDuration);
-}
-
-TEST_P(BlockingStreamSocketPairTest, SendTimeout) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct timeval tv {
- .tv_sec = 0, .tv_usec = 10
- };
- EXPECT_THAT(
- setsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)),
- SyscallSucceeds());
-
- std::vector<char> buf(kPageSize);
- // We don't know how much data the socketpair will buffer, so we may do an
- // arbitrarily large number of writes; saving after each write causes this
- // test's time to explode.
- const DisableSave ds;
- for (;;) {
- int ret;
- ASSERT_THAT(
- ret = RetryEINTR(send)(sockets->first_fd(), buf.data(), buf.size(), 0),
- ::testing::AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(EAGAIN)));
- if (ret == -1) {
- break;
- }
- }
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_stream_blocking.h b/test/syscalls/linux/socket_stream_blocking.h
deleted file mode 100644
index 9fd19ff90..000000000
--- a/test/syscalls/linux/socket_stream_blocking.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_STREAM_BLOCKING_H_
-#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_STREAM_BLOCKING_H_
-
-#include "test/syscalls/linux/socket_test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Test fixture for tests that apply to pairs of blocking connected stream
-// sockets.
-using BlockingStreamSocketPairTest = SocketPairTest;
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_STREAM_BLOCKING_H_
diff --git a/test/syscalls/linux/socket_stream_nonblock.cc b/test/syscalls/linux/socket_stream_nonblock.cc
deleted file mode 100644
index b00748b97..000000000
--- a/test/syscalls/linux/socket_stream_nonblock.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/socket_stream_nonblock.h"
-
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <sys/uio.h>
-#include <sys/un.h>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-using ::testing::Le;
-
-TEST_P(NonBlockingStreamSocketPairTest, SendMsgTooLarge) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- int sndbuf;
- socklen_t length = sizeof(sndbuf);
- ASSERT_THAT(
- getsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDBUF, &sndbuf, &length),
- SyscallSucceeds());
-
- // Make the call too large to fit in the send buffer.
- const int buffer_size = 3 * sndbuf;
-
- EXPECT_THAT(SendLargeSendMsg(sockets, buffer_size, false /* reader */),
- SyscallSucceedsWithValue(Le(buffer_size)));
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_stream_nonblock.h b/test/syscalls/linux/socket_stream_nonblock.h
deleted file mode 100644
index c3b7fad91..000000000
--- a/test/syscalls/linux/socket_stream_nonblock.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_STREAM_NONBLOCK_H_
-#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_STREAM_NONBLOCK_H_
-
-#include "test/syscalls/linux/socket_test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Test fixture for tests that apply to pairs of non-blocking connected stream
-// sockets.
-using NonBlockingStreamSocketPairTest = SocketPairTest;
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_STREAM_NONBLOCK_H_
diff --git a/test/syscalls/linux/socket_test_util.cc b/test/syscalls/linux/socket_test_util.cc
deleted file mode 100644
index eff7d577e..000000000
--- a/test/syscalls/linux/socket_test_util.cc
+++ /dev/null
@@ -1,815 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/socket_test_util.h"
-
-#include <arpa/inet.h>
-#include <poll.h>
-#include <sys/socket.h>
-
-#include "gtest/gtest.h"
-#include "absl/memory/memory.h"
-#include "absl/strings/str_cat.h"
-#include "absl/time/clock.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/posix_error.h"
-#include "test/util/temp_path.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-Creator<SocketPair> SyscallSocketPairCreator(int domain, int type,
- int protocol) {
- return [=]() -> PosixErrorOr<std::unique_ptr<FDSocketPair>> {
- int pair[2];
- RETURN_ERROR_IF_SYSCALL_FAIL(socketpair(domain, type, protocol, pair));
- MaybeSave(); // Save on successful creation.
- return absl::make_unique<FDSocketPair>(pair[0], pair[1]);
- };
-}
-
-Creator<FileDescriptor> SyscallSocketCreator(int domain, int type,
- int protocol) {
- return [=]() -> PosixErrorOr<std::unique_ptr<FileDescriptor>> {
- int fd = 0;
- RETURN_ERROR_IF_SYSCALL_FAIL(fd = socket(domain, type, protocol));
- MaybeSave(); // Save on successful creation.
- return absl::make_unique<FileDescriptor>(fd);
- };
-}
-
-PosixErrorOr<struct sockaddr_un> UniqueUnixAddr(bool abstract, int domain) {
- struct sockaddr_un addr = {};
- std::string path = NewTempAbsPathInDir("/tmp");
- if (path.size() >= sizeof(addr.sun_path)) {
- return PosixError(EINVAL,
- "Unable to generate a temp path of appropriate length");
- }
-
- if (abstract) {
- // Indicate that the path is in the abstract namespace.
- path[0] = 0;
- }
- memcpy(addr.sun_path, path.c_str(), path.length());
- addr.sun_family = domain;
- return addr;
-}
-
-Creator<SocketPair> AcceptBindSocketPairCreator(bool abstract, int domain,
- int type, int protocol) {
- return [=]() -> PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> {
- ASSIGN_OR_RETURN_ERRNO(struct sockaddr_un bind_addr,
- UniqueUnixAddr(abstract, domain));
- ASSIGN_OR_RETURN_ERRNO(struct sockaddr_un extra_addr,
- UniqueUnixAddr(abstract, domain));
-
- int bound;
- RETURN_ERROR_IF_SYSCALL_FAIL(bound = socket(domain, type, protocol));
- MaybeSave(); // Successful socket creation.
- RETURN_ERROR_IF_SYSCALL_FAIL(
- bind(bound, reinterpret_cast<struct sockaddr*>(&bind_addr),
- sizeof(bind_addr)));
- MaybeSave(); // Successful bind.
- RETURN_ERROR_IF_SYSCALL_FAIL(listen(bound, /* backlog = */ 5));
- MaybeSave(); // Successful listen.
-
- int connected;
- RETURN_ERROR_IF_SYSCALL_FAIL(connected = socket(domain, type, protocol));
- MaybeSave(); // Successful socket creation.
- RETURN_ERROR_IF_SYSCALL_FAIL(
- connect(connected, reinterpret_cast<struct sockaddr*>(&bind_addr),
- sizeof(bind_addr)));
- MaybeSave(); // Successful connect.
-
- int accepted;
- RETURN_ERROR_IF_SYSCALL_FAIL(
- accepted = accept4(bound, nullptr, nullptr,
- type & (SOCK_NONBLOCK | SOCK_CLOEXEC)));
- MaybeSave(); // Successful connect.
-
- // Cleanup no longer needed resources.
- RETURN_ERROR_IF_SYSCALL_FAIL(close(bound));
- MaybeSave(); // Dropped original socket.
-
- // Only unlink if path is not in abstract namespace.
- if (bind_addr.sun_path[0] != 0) {
- RETURN_ERROR_IF_SYSCALL_FAIL(unlink(bind_addr.sun_path));
- MaybeSave(); // Unlinked path.
- }
-
- return absl::make_unique<AddrFDSocketPair>(connected, accepted, bind_addr,
- extra_addr);
- };
-}
-
-Creator<SocketPair> FilesystemAcceptBindSocketPairCreator(int domain, int type,
- int protocol) {
- return AcceptBindSocketPairCreator(/* abstract= */ false, domain, type,
- protocol);
-}
-
-Creator<SocketPair> AbstractAcceptBindSocketPairCreator(int domain, int type,
- int protocol) {
- return AcceptBindSocketPairCreator(/* abstract= */ true, domain, type,
- protocol);
-}
-
-Creator<SocketPair> BidirectionalBindSocketPairCreator(bool abstract,
- int domain, int type,
- int protocol) {
- return [=]() -> PosixErrorOr<std::unique_ptr<FDSocketPair>> {
- ASSIGN_OR_RETURN_ERRNO(struct sockaddr_un addr1,
- UniqueUnixAddr(abstract, domain));
- ASSIGN_OR_RETURN_ERRNO(struct sockaddr_un addr2,
- UniqueUnixAddr(abstract, domain));
-
- int sock1;
- RETURN_ERROR_IF_SYSCALL_FAIL(sock1 = socket(domain, type, protocol));
- MaybeSave(); // Successful socket creation.
- RETURN_ERROR_IF_SYSCALL_FAIL(
- bind(sock1, reinterpret_cast<struct sockaddr*>(&addr1), sizeof(addr1)));
- MaybeSave(); // Successful bind.
-
- int sock2;
- RETURN_ERROR_IF_SYSCALL_FAIL(sock2 = socket(domain, type, protocol));
- MaybeSave(); // Successful socket creation.
- RETURN_ERROR_IF_SYSCALL_FAIL(
- bind(sock2, reinterpret_cast<struct sockaddr*>(&addr2), sizeof(addr2)));
- MaybeSave(); // Successful bind.
-
- RETURN_ERROR_IF_SYSCALL_FAIL(connect(
- sock1, reinterpret_cast<struct sockaddr*>(&addr2), sizeof(addr2)));
- MaybeSave(); // Successful connect.
-
- RETURN_ERROR_IF_SYSCALL_FAIL(connect(
- sock2, reinterpret_cast<struct sockaddr*>(&addr1), sizeof(addr1)));
- MaybeSave(); // Successful connect.
-
- // Cleanup no longer needed resources.
-
- // Only unlink if path is not in abstract namespace.
- if (addr1.sun_path[0] != 0) {
- RETURN_ERROR_IF_SYSCALL_FAIL(unlink(addr1.sun_path));
- MaybeSave(); // Successful unlink.
- }
-
- // Only unlink if path is not in abstract namespace.
- if (addr2.sun_path[0] != 0) {
- RETURN_ERROR_IF_SYSCALL_FAIL(unlink(addr2.sun_path));
- MaybeSave(); // Successful unlink.
- }
-
- return absl::make_unique<FDSocketPair>(sock1, sock2);
- };
-}
-
-Creator<SocketPair> FilesystemBidirectionalBindSocketPairCreator(int domain,
- int type,
- int protocol) {
- return BidirectionalBindSocketPairCreator(/* abstract= */ false, domain, type,
- protocol);
-}
-
-Creator<SocketPair> AbstractBidirectionalBindSocketPairCreator(int domain,
- int type,
- int protocol) {
- return BidirectionalBindSocketPairCreator(/* abstract= */ true, domain, type,
- protocol);
-}
-
-Creator<SocketPair> SocketpairGoferSocketPairCreator(int domain, int type,
- int protocol) {
- return [=]() -> PosixErrorOr<std::unique_ptr<FDSocketPair>> {
- struct sockaddr_un addr = {};
- constexpr char kSocketGoferPath[] = "/socket";
- memcpy(addr.sun_path, kSocketGoferPath, sizeof(kSocketGoferPath));
- addr.sun_family = domain;
-
- int sock1;
- RETURN_ERROR_IF_SYSCALL_FAIL(sock1 = socket(domain, type, protocol));
- MaybeSave(); // Successful socket creation.
- RETURN_ERROR_IF_SYSCALL_FAIL(connect(
- sock1, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)));
- MaybeSave(); // Successful connect.
-
- int sock2;
- RETURN_ERROR_IF_SYSCALL_FAIL(sock2 = socket(domain, type, protocol));
- MaybeSave(); // Successful socket creation.
- RETURN_ERROR_IF_SYSCALL_FAIL(connect(
- sock2, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)));
- MaybeSave(); // Successful connect.
-
- // Make and close another socketpair to ensure that the duped ends of the
- // first socketpair get closed.
- //
- // The problem is that there is no way to atomically send and close an FD.
- // The closest that we can do is send and then immediately close the FD,
- // which is what we do in the gofer. The gofer won't respond to another
- // request until the reply is sent and the FD is closed, so forcing the
- // gofer to handle another request will ensure that this has happened.
- for (int i = 0; i < 2; i++) {
- int sock;
- RETURN_ERROR_IF_SYSCALL_FAIL(sock = socket(domain, type, protocol));
- RETURN_ERROR_IF_SYSCALL_FAIL(connect(
- sock, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)));
- RETURN_ERROR_IF_SYSCALL_FAIL(close(sock));
- }
-
- return absl::make_unique<FDSocketPair>(sock1, sock2);
- };
-}
-
-Creator<SocketPair> SocketpairGoferFileSocketPairCreator(int flags) {
- return [=]() -> PosixErrorOr<std::unique_ptr<FDSocketPair>> {
- constexpr char kSocketGoferPath[] = "/socket";
-
- int sock1;
- RETURN_ERROR_IF_SYSCALL_FAIL(sock1 =
- open(kSocketGoferPath, O_RDWR | flags));
- MaybeSave(); // Successful socket creation.
-
- int sock2;
- RETURN_ERROR_IF_SYSCALL_FAIL(sock2 =
- open(kSocketGoferPath, O_RDWR | flags));
- MaybeSave(); // Successful socket creation.
-
- return absl::make_unique<FDSocketPair>(sock1, sock2);
- };
-}
-
-Creator<SocketPair> UnboundSocketPairCreator(bool abstract, int domain,
- int type, int protocol) {
- return [=]() -> PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> {
- ASSIGN_OR_RETURN_ERRNO(struct sockaddr_un addr1,
- UniqueUnixAddr(abstract, domain));
- ASSIGN_OR_RETURN_ERRNO(struct sockaddr_un addr2,
- UniqueUnixAddr(abstract, domain));
-
- int sock1;
- RETURN_ERROR_IF_SYSCALL_FAIL(sock1 = socket(domain, type, protocol));
- MaybeSave(); // Successful socket creation.
- int sock2;
- RETURN_ERROR_IF_SYSCALL_FAIL(sock2 = socket(domain, type, protocol));
- MaybeSave(); // Successful socket creation.
- return absl::make_unique<AddrFDSocketPair>(sock1, sock2, addr1, addr2);
- };
-}
-
-Creator<SocketPair> FilesystemUnboundSocketPairCreator(int domain, int type,
- int protocol) {
- return UnboundSocketPairCreator(/* abstract= */ false, domain, type,
- protocol);
-}
-
-Creator<SocketPair> AbstractUnboundSocketPairCreator(int domain, int type,
- int protocol) {
- return UnboundSocketPairCreator(/* abstract= */ true, domain, type, protocol);
-}
-
-void LocalhostAddr(struct sockaddr_in* addr, bool dual_stack) {
- addr->sin_family = AF_INET;
- addr->sin_port = htons(0);
- inet_pton(AF_INET, "127.0.0.1",
- reinterpret_cast<void*>(&addr->sin_addr.s_addr));
-}
-
-void LocalhostAddr(struct sockaddr_in6* addr, bool dual_stack) {
- addr->sin6_family = AF_INET6;
- addr->sin6_port = htons(0);
- if (dual_stack) {
- inet_pton(AF_INET6, "::ffff:127.0.0.1",
- reinterpret_cast<void*>(&addr->sin6_addr.s6_addr));
- } else {
- inet_pton(AF_INET6, "::1",
- reinterpret_cast<void*>(&addr->sin6_addr.s6_addr));
- }
- addr->sin6_scope_id = 0;
-}
-
-template <typename T>
-PosixErrorOr<T> BindIP(int fd, bool dual_stack) {
- T addr = {};
- LocalhostAddr(&addr, dual_stack);
- RETURN_ERROR_IF_SYSCALL_FAIL(
- bind(fd, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)));
- socklen_t addrlen = sizeof(addr);
- RETURN_ERROR_IF_SYSCALL_FAIL(
- getsockname(fd, reinterpret_cast<struct sockaddr*>(&addr), &addrlen));
- return addr;
-}
-
-template <typename T>
-PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> CreateTCPAcceptBindSocketPair(
- int bound, int connected, int type, bool dual_stack) {
- ASSIGN_OR_RETURN_ERRNO(T bind_addr, BindIP<T>(bound, dual_stack));
- RETURN_ERROR_IF_SYSCALL_FAIL(listen(bound, /* backlog = */ 5));
-
- int connect_result = 0;
- RETURN_ERROR_IF_SYSCALL_FAIL(
- (connect_result = RetryEINTR(connect)(
- connected, reinterpret_cast<struct sockaddr*>(&bind_addr),
- sizeof(bind_addr))) == -1 &&
- errno == EINPROGRESS
- ? 0
- : connect_result);
- MaybeSave(); // Successful connect.
-
- if (connect_result == -1) {
- struct pollfd connect_poll = {connected, POLLOUT | POLLERR | POLLHUP, 0};
- RETURN_ERROR_IF_SYSCALL_FAIL(RetryEINTR(poll)(&connect_poll, 1, 0));
- int error = 0;
- socklen_t errorlen = sizeof(error);
- RETURN_ERROR_IF_SYSCALL_FAIL(
- getsockopt(connected, SOL_SOCKET, SO_ERROR, &error, &errorlen));
- errno = error;
- RETURN_ERROR_IF_SYSCALL_FAIL(
- /* connect */ error == 0 ? 0 : -1);
- }
-
- int accepted = -1;
- struct pollfd accept_poll = {bound, POLLIN, 0};
- while (accepted == -1) {
- RETURN_ERROR_IF_SYSCALL_FAIL(RetryEINTR(poll)(&accept_poll, 1, 0));
-
- RETURN_ERROR_IF_SYSCALL_FAIL(
- (accepted = RetryEINTR(accept4)(
- bound, nullptr, nullptr, type & (SOCK_NONBLOCK | SOCK_CLOEXEC))) ==
- -1 &&
- errno == EAGAIN
- ? 0
- : accepted);
- }
- MaybeSave(); // Successful accept.
-
- // FIXME(b/110484944)
- if (connect_result == -1) {
- absl::SleepFor(absl::Seconds(1));
- }
-
- // Cleanup no longer needed resources.
- RETURN_ERROR_IF_SYSCALL_FAIL(close(bound));
- MaybeSave(); // Successful close.
-
- T extra_addr = {};
- LocalhostAddr(&extra_addr, dual_stack);
- return absl::make_unique<AddrFDSocketPair>(connected, accepted, bind_addr,
- extra_addr);
-}
-
-Creator<SocketPair> TCPAcceptBindSocketPairCreator(int domain, int type,
- int protocol,
- bool dual_stack) {
- return [=]() -> PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> {
- int bound;
- RETURN_ERROR_IF_SYSCALL_FAIL(bound = socket(domain, type, protocol));
- MaybeSave(); // Successful socket creation.
-
- int connected;
- RETURN_ERROR_IF_SYSCALL_FAIL(connected = socket(domain, type, protocol));
- MaybeSave(); // Successful socket creation.
-
- if (domain == AF_INET) {
- return CreateTCPAcceptBindSocketPair<sockaddr_in>(bound, connected, type,
- dual_stack);
- }
- return CreateTCPAcceptBindSocketPair<sockaddr_in6>(bound, connected, type,
- dual_stack);
- };
-}
-
-template <typename T>
-PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> CreateUDPBoundSocketPair(
- int sock1, int sock2, int type, bool dual_stack) {
- ASSIGN_OR_RETURN_ERRNO(T addr1, BindIP<T>(sock1, dual_stack));
- ASSIGN_OR_RETURN_ERRNO(T addr2, BindIP<T>(sock2, dual_stack));
-
- return absl::make_unique<AddrFDSocketPair>(sock1, sock2, addr1, addr2);
-}
-
-template <typename T>
-PosixErrorOr<std::unique_ptr<AddrFDSocketPair>>
-CreateUDPBidirectionalBindSocketPair(int sock1, int sock2, int type,
- bool dual_stack) {
- ASSIGN_OR_RETURN_ERRNO(
- auto socks, CreateUDPBoundSocketPair<T>(sock1, sock2, type, dual_stack));
-
- // Connect sock1 to sock2.
- RETURN_ERROR_IF_SYSCALL_FAIL(connect(socks->first_fd(), socks->second_addr(),
- socks->second_addr_size()));
- MaybeSave(); // Successful connection.
-
- // Connect sock2 to sock1.
- RETURN_ERROR_IF_SYSCALL_FAIL(connect(socks->second_fd(), socks->first_addr(),
- socks->first_addr_size()));
- MaybeSave(); // Successful connection.
-
- return socks;
-}
-
-Creator<SocketPair> UDPBidirectionalBindSocketPairCreator(int domain, int type,
- int protocol,
- bool dual_stack) {
- return [=]() -> PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> {
- int sock1;
- RETURN_ERROR_IF_SYSCALL_FAIL(sock1 = socket(domain, type, protocol));
- MaybeSave(); // Successful socket creation.
-
- int sock2;
- RETURN_ERROR_IF_SYSCALL_FAIL(sock2 = socket(domain, type, protocol));
- MaybeSave(); // Successful socket creation.
-
- if (domain == AF_INET) {
- return CreateUDPBidirectionalBindSocketPair<sockaddr_in>(
- sock1, sock2, type, dual_stack);
- }
- return CreateUDPBidirectionalBindSocketPair<sockaddr_in6>(sock1, sock2,
- type, dual_stack);
- };
-}
-
-Creator<SocketPair> UDPUnboundSocketPairCreator(int domain, int type,
- int protocol, bool dual_stack) {
- return [=]() -> PosixErrorOr<std::unique_ptr<FDSocketPair>> {
- int sock1;
- RETURN_ERROR_IF_SYSCALL_FAIL(sock1 = socket(domain, type, protocol));
- MaybeSave(); // Successful socket creation.
-
- int sock2;
- RETURN_ERROR_IF_SYSCALL_FAIL(sock2 = socket(domain, type, protocol));
- MaybeSave(); // Successful socket creation.
-
- return absl::make_unique<FDSocketPair>(sock1, sock2);
- };
-}
-
-SocketPairKind Reversed(SocketPairKind const& base) {
- auto const& creator = base.creator;
- return SocketPairKind{
- absl::StrCat("reversed ", base.description), base.domain, base.type,
- base.protocol,
- [creator]() -> PosixErrorOr<std::unique_ptr<ReversedSocketPair>> {
- ASSIGN_OR_RETURN_ERRNO(auto creator_value, creator());
- return absl::make_unique<ReversedSocketPair>(std::move(creator_value));
- }};
-}
-
-Creator<FileDescriptor> UnboundSocketCreator(int domain, int type,
- int protocol) {
- return [=]() -> PosixErrorOr<std::unique_ptr<FileDescriptor>> {
- int sock;
- RETURN_ERROR_IF_SYSCALL_FAIL(sock = socket(domain, type, protocol));
- MaybeSave(); // Successful socket creation.
-
- return absl::make_unique<FileDescriptor>(sock);
- };
-}
-
-std::vector<SocketPairKind> IncludeReversals(std::vector<SocketPairKind> vec) {
- return ApplyVecToVec<SocketPairKind>(std::vector<Middleware>{NoOp, Reversed},
- vec);
-}
-
-SocketPairKind NoOp(SocketPairKind const& base) { return base; }
-
-void TransferTest(int fd1, int fd2) {
- char buf1[20];
- RandomizeBuffer(buf1, sizeof(buf1));
- ASSERT_THAT(WriteFd(fd1, buf1, sizeof(buf1)),
- SyscallSucceedsWithValue(sizeof(buf1)));
-
- char buf2[20];
- ASSERT_THAT(ReadFd(fd2, buf2, sizeof(buf2)),
- SyscallSucceedsWithValue(sizeof(buf2)));
-
- EXPECT_EQ(0, memcmp(buf1, buf2, sizeof(buf1)));
-
- RandomizeBuffer(buf1, sizeof(buf1));
- ASSERT_THAT(WriteFd(fd2, buf1, sizeof(buf1)),
- SyscallSucceedsWithValue(sizeof(buf1)));
-
- ASSERT_THAT(ReadFd(fd1, buf2, sizeof(buf2)),
- SyscallSucceedsWithValue(sizeof(buf2)));
-
- EXPECT_EQ(0, memcmp(buf1, buf2, sizeof(buf1)));
-}
-
-// Initializes the given buffer with random data.
-void RandomizeBuffer(char* ptr, size_t len) {
- uint32_t seed = time(nullptr);
- for (size_t i = 0; i < len; ++i) {
- ptr[i] = static_cast<char>(rand_r(&seed));
- }
-}
-
-size_t CalculateUnixSockAddrLen(const char* sun_path) {
- // Abstract addresses always return the full length.
- if (sun_path[0] == 0) {
- return sizeof(sockaddr_un);
- }
- // Filesystem addresses use the address length plus the 2 byte sun_family and
- // null terminator.
- return strlen(sun_path) + 3;
-}
-
-struct sockaddr_storage AddrFDSocketPair::to_storage(const sockaddr_un& addr) {
- struct sockaddr_storage addr_storage = {};
- memcpy(&addr_storage, &addr, sizeof(addr));
- return addr_storage;
-}
-
-struct sockaddr_storage AddrFDSocketPair::to_storage(const sockaddr_in& addr) {
- struct sockaddr_storage addr_storage = {};
- memcpy(&addr_storage, &addr, sizeof(addr));
- return addr_storage;
-}
-
-struct sockaddr_storage AddrFDSocketPair::to_storage(const sockaddr_in6& addr) {
- struct sockaddr_storage addr_storage = {};
- memcpy(&addr_storage, &addr, sizeof(addr));
- return addr_storage;
-}
-
-SocketKind SimpleSocket(int fam, int type, int proto) {
- return SocketKind{
- absl::StrCat("Family ", fam, ", type ", type, ", proto ", proto), fam,
- type, proto, SyscallSocketCreator(fam, type, proto)};
-}
-
-ssize_t SendLargeSendMsg(const std::unique_ptr<SocketPair>& sockets,
- size_t size, bool reader) {
- const int rfd = sockets->second_fd();
- ScopedThread t([rfd, size, reader] {
- if (!reader) {
- return;
- }
-
- // Potentially too many syscalls in the loop.
- const DisableSave ds;
-
- std::vector<char> buf(size);
- size_t total = 0;
-
- while (total < size) {
- int ret = read(rfd, buf.data(), buf.size());
- if (ret == -1 && errno == EAGAIN) {
- continue;
- }
- if (ret > 0) {
- total += ret;
- }
-
- // Assert to return on first failure.
- ASSERT_THAT(ret, SyscallSucceeds());
- }
- });
-
- std::vector<char> buf(size);
-
- struct iovec iov = {};
- iov.iov_base = buf.data();
- iov.iov_len = buf.size();
-
- struct msghdr msg = {};
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- return RetryEINTR(sendmsg)(sockets->first_fd(), &msg, 0);
-}
-
-namespace internal {
-PosixErrorOr<int> TryPortAvailable(int port, AddressFamily family,
- SocketType type, bool reuse_addr) {
- if (port < 0) {
- return PosixError(EINVAL, "Invalid port");
- }
-
- // Both Ipv6 and Dualstack are AF_INET6.
- int sock_fam = (family == AddressFamily::kIpv4 ? AF_INET : AF_INET6);
- int sock_type = (type == SocketType::kTcp ? SOCK_STREAM : SOCK_DGRAM);
- ASSIGN_OR_RETURN_ERRNO(auto fd, Socket(sock_fam, sock_type, 0));
-
- if (reuse_addr) {
- int one = 1;
- RETURN_ERROR_IF_SYSCALL_FAIL(
- setsockopt(fd.get(), SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)));
- }
-
- // Try to bind.
- sockaddr_storage storage = {};
- int storage_size = 0;
- if (family == AddressFamily::kIpv4) {
- sockaddr_in* addr = reinterpret_cast<sockaddr_in*>(&storage);
- storage_size = sizeof(*addr);
- addr->sin_family = AF_INET;
- addr->sin_port = htons(port);
- addr->sin_addr.s_addr = htonl(INADDR_ANY);
- } else {
- sockaddr_in6* addr = reinterpret_cast<sockaddr_in6*>(&storage);
- storage_size = sizeof(*addr);
- addr->sin6_family = AF_INET6;
- addr->sin6_port = htons(port);
- if (family == AddressFamily::kDualStack) {
- inet_pton(AF_INET6, "::ffff:0.0.0.0",
- reinterpret_cast<void*>(&addr->sin6_addr.s6_addr));
- } else {
- addr->sin6_addr = in6addr_any;
- }
- }
-
- RETURN_ERROR_IF_SYSCALL_FAIL(
- bind(fd.get(), reinterpret_cast<sockaddr*>(&storage), storage_size));
-
- // If the user specified 0 as the port, we will return the port that the
- // kernel gave us, otherwise we will validate that this socket bound to the
- // requested port.
- sockaddr_storage bound_storage = {};
- socklen_t bound_storage_size = sizeof(bound_storage);
- RETURN_ERROR_IF_SYSCALL_FAIL(
- getsockname(fd.get(), reinterpret_cast<sockaddr*>(&bound_storage),
- &bound_storage_size));
-
- int available_port = -1;
- if (bound_storage.ss_family == AF_INET) {
- sockaddr_in* addr = reinterpret_cast<sockaddr_in*>(&bound_storage);
- available_port = ntohs(addr->sin_port);
- } else if (bound_storage.ss_family == AF_INET6) {
- sockaddr_in6* addr = reinterpret_cast<sockaddr_in6*>(&bound_storage);
- available_port = ntohs(addr->sin6_port);
- } else {
- return PosixError(EPROTOTYPE, "Getsockname returned invalid family");
- }
-
- // If we requested a specific port make sure our bound port is that port.
- if (port != 0 && available_port != port) {
- return PosixError(EINVAL,
- absl::StrCat("Bound port ", available_port,
- " was not equal to requested port ", port));
- }
-
- // If we're trying to do a TCP socket, let's also try to listen.
- if (type == SocketType::kTcp) {
- RETURN_ERROR_IF_SYSCALL_FAIL(listen(fd.get(), 1));
- }
-
- return available_port;
-}
-} // namespace internal
-
-PosixErrorOr<int> SendMsg(int sock, msghdr* msg, char buf[], int buf_size) {
- struct iovec iov;
- iov.iov_base = buf;
- iov.iov_len = buf_size;
- msg->msg_iov = &iov;
- msg->msg_iovlen = 1;
-
- int ret;
- RETURN_ERROR_IF_SYSCALL_FAIL(ret = RetryEINTR(sendmsg)(sock, msg, 0));
- return ret;
-}
-
-void RecvNoData(int sock) {
- char data = 0;
- struct iovec iov;
- iov.iov_base = &data;
- iov.iov_len = 1;
- struct msghdr msg = {};
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
- ASSERT_THAT(RetryEINTR(recvmsg)(sock, &msg, MSG_DONTWAIT),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TestAddress V4Any() {
- TestAddress t("V4Any");
- t.addr.ss_family = AF_INET;
- t.addr_len = sizeof(sockaddr_in);
- reinterpret_cast<sockaddr_in*>(&t.addr)->sin_addr.s_addr = htonl(INADDR_ANY);
- return t;
-}
-
-TestAddress V4Loopback() {
- TestAddress t("V4Loopback");
- t.addr.ss_family = AF_INET;
- t.addr_len = sizeof(sockaddr_in);
- reinterpret_cast<sockaddr_in*>(&t.addr)->sin_addr.s_addr =
- htonl(INADDR_LOOPBACK);
- return t;
-}
-
-TestAddress V4MappedAny() {
- TestAddress t("V4MappedAny");
- t.addr.ss_family = AF_INET6;
- t.addr_len = sizeof(sockaddr_in6);
- inet_pton(AF_INET6, "::ffff:0.0.0.0",
- reinterpret_cast<sockaddr_in6*>(&t.addr)->sin6_addr.s6_addr);
- return t;
-}
-
-TestAddress V4MappedLoopback() {
- TestAddress t("V4MappedLoopback");
- t.addr.ss_family = AF_INET6;
- t.addr_len = sizeof(sockaddr_in6);
- inet_pton(AF_INET6, "::ffff:127.0.0.1",
- reinterpret_cast<sockaddr_in6*>(&t.addr)->sin6_addr.s6_addr);
- return t;
-}
-
-TestAddress V6Any() {
- TestAddress t("V6Any");
- t.addr.ss_family = AF_INET6;
- t.addr_len = sizeof(sockaddr_in6);
- reinterpret_cast<sockaddr_in6*>(&t.addr)->sin6_addr = in6addr_any;
- return t;
-}
-
-TestAddress V6Loopback() {
- TestAddress t("V6Loopback");
- t.addr.ss_family = AF_INET6;
- t.addr_len = sizeof(sockaddr_in6);
- reinterpret_cast<sockaddr_in6*>(&t.addr)->sin6_addr = in6addr_loopback;
- return t;
-}
-
-// Checksum computes the internet checksum of a buffer.
-uint16_t Checksum(uint16_t* buf, ssize_t buf_size) {
- // Add up the 16-bit values in the buffer.
- uint32_t total = 0;
- for (unsigned int i = 0; i < buf_size; i += sizeof(*buf)) {
- total += *buf;
- buf++;
- }
-
- // If buf has an odd size, add the remaining byte.
- if (buf_size % 2) {
- total += *(reinterpret_cast<unsigned char*>(buf) - 1);
- }
-
- // This carries any bits past the lower 16 until everything fits in 16 bits.
- while (total >> 16) {
- uint16_t lower = total & 0xffff;
- uint16_t upper = total >> 16;
- total = lower + upper;
- }
-
- return ~total;
-}
-
-uint16_t IPChecksum(struct iphdr ip) {
- return Checksum(reinterpret_cast<uint16_t*>(&ip), sizeof(ip));
-}
-
-// The pseudo-header defined in RFC 768 for calculating the UDP checksum.
-struct udp_pseudo_hdr {
- uint32_t srcip;
- uint32_t destip;
- char zero;
- char protocol;
- uint16_t udplen;
-};
-
-uint16_t UDPChecksum(struct iphdr iphdr, struct udphdr udphdr,
- const char* payload, ssize_t payload_len) {
- struct udp_pseudo_hdr phdr = {};
- phdr.srcip = iphdr.saddr;
- phdr.destip = iphdr.daddr;
- phdr.zero = 0;
- phdr.protocol = IPPROTO_UDP;
- phdr.udplen = udphdr.len;
-
- ssize_t buf_size = sizeof(phdr) + sizeof(udphdr) + payload_len;
- char* buf = static_cast<char*>(malloc(buf_size));
- memcpy(buf, &phdr, sizeof(phdr));
- memcpy(buf + sizeof(phdr), &udphdr, sizeof(udphdr));
- memcpy(buf + sizeof(phdr) + sizeof(udphdr), payload, payload_len);
-
- uint16_t csum = Checksum(reinterpret_cast<uint16_t*>(buf), buf_size);
- free(buf);
- return csum;
-}
-
-uint16_t ICMPChecksum(struct icmphdr icmphdr, const char* payload,
- ssize_t payload_len) {
- ssize_t buf_size = sizeof(icmphdr) + payload_len;
- char* buf = static_cast<char*>(malloc(buf_size));
- memcpy(buf, &icmphdr, sizeof(icmphdr));
- memcpy(buf + sizeof(icmphdr), payload, payload_len);
-
- uint16_t csum = Checksum(reinterpret_cast<uint16_t*>(buf), buf_size);
- free(buf);
- return csum;
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_test_util.h b/test/syscalls/linux/socket_test_util.h
deleted file mode 100644
index 6efa8055f..000000000
--- a/test/syscalls/linux/socket_test_util.h
+++ /dev/null
@@ -1,503 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_SOCKET_TEST_UTIL_H_
-#define GVISOR_TEST_SYSCALLS_SOCKET_TEST_UTIL_H_
-
-#include <errno.h>
-#include <netinet/ip.h>
-#include <netinet/ip_icmp.h>
-#include <netinet/udp.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <sys/un.h>
-
-#include <functional>
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "absl/strings/str_format.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/posix_error.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Wrapper for socket(2) that returns a FileDescriptor.
-inline PosixErrorOr<FileDescriptor> Socket(int family, int type, int protocol) {
- int fd = socket(family, type, protocol);
- MaybeSave();
- if (fd < 0) {
- return PosixError(
- errno, absl::StrFormat("socket(%d, %d, %d)", family, type, protocol));
- }
- return FileDescriptor(fd);
-}
-
-// Wrapper for accept(2) that returns a FileDescriptor.
-inline PosixErrorOr<FileDescriptor> Accept(int sockfd, sockaddr* addr,
- socklen_t* addrlen) {
- int fd = RetryEINTR(accept)(sockfd, addr, addrlen);
- MaybeSave();
- if (fd < 0) {
- return PosixError(
- errno, absl::StrFormat("accept(%d, %p, %p)", sockfd, addr, addrlen));
- }
- return FileDescriptor(fd);
-}
-
-// Wrapper for accept4(2) that returns a FileDescriptor.
-inline PosixErrorOr<FileDescriptor> Accept4(int sockfd, sockaddr* addr,
- socklen_t* addrlen, int flags) {
- int fd = RetryEINTR(accept4)(sockfd, addr, addrlen, flags);
- MaybeSave();
- if (fd < 0) {
- return PosixError(errno, absl::StrFormat("accept4(%d, %p, %p, %#x)", sockfd,
- addr, addrlen, flags));
- }
- return FileDescriptor(fd);
-}
-
-inline ssize_t SendFd(int fd, void* buf, size_t count, int flags) {
- return internal::ApplyFileIoSyscall(
- [&](size_t completed) {
- return sendto(fd, static_cast<char*>(buf) + completed,
- count - completed, flags, nullptr, 0);
- },
- count);
-}
-
-// A Creator<T> is a function that attempts to create and return a new T. (This
-// is copy/pasted from cloud/gvisor/api/sandbox_util.h and is just duplicated
-// here for clarity.)
-template <typename T>
-using Creator = std::function<PosixErrorOr<std::unique_ptr<T>>()>;
-
-// A SocketPair represents a pair of socket file descriptors owned by the
-// SocketPair.
-class SocketPair {
- public:
- virtual ~SocketPair() = default;
-
- virtual int first_fd() const = 0;
- virtual int second_fd() const = 0;
- virtual int release_first_fd() = 0;
- virtual int release_second_fd() = 0;
- virtual const struct sockaddr* first_addr() const = 0;
- virtual const struct sockaddr* second_addr() const = 0;
- virtual size_t first_addr_size() const = 0;
- virtual size_t second_addr_size() const = 0;
- virtual size_t first_addr_len() const = 0;
- virtual size_t second_addr_len() const = 0;
-};
-
-// A FDSocketPair is a SocketPair that consists of only a pair of file
-// descriptors.
-class FDSocketPair : public SocketPair {
- public:
- FDSocketPair(int first_fd, int second_fd)
- : first_(first_fd), second_(second_fd) {}
-
- int first_fd() const override { return first_.get(); }
- int second_fd() const override { return second_.get(); }
- int release_first_fd() override { return first_.release(); }
- int release_second_fd() override { return second_.release(); }
- const struct sockaddr* first_addr() const override { return nullptr; }
- const struct sockaddr* second_addr() const override { return nullptr; }
- size_t first_addr_size() const override { return 0; }
- size_t second_addr_size() const override { return 0; }
- size_t first_addr_len() const override { return 0; }
- size_t second_addr_len() const override { return 0; }
-
- private:
- FileDescriptor first_;
- FileDescriptor second_;
-};
-
-// CalculateUnixSockAddrLen calculates the length returned by recvfrom(2) and
-// recvmsg(2) for Unix sockets.
-size_t CalculateUnixSockAddrLen(const char* sun_path);
-
-// A AddrFDSocketPair is a SocketPair that consists of a pair of file
-// descriptors in addition to a pair of socket addresses.
-class AddrFDSocketPair : public SocketPair {
- public:
- AddrFDSocketPair(int first_fd, int second_fd,
- const struct sockaddr_un& first_address,
- const struct sockaddr_un& second_address)
- : first_(first_fd),
- second_(second_fd),
- first_addr_(to_storage(first_address)),
- second_addr_(to_storage(second_address)),
- first_len_(CalculateUnixSockAddrLen(first_address.sun_path)),
- second_len_(CalculateUnixSockAddrLen(second_address.sun_path)),
- first_size_(sizeof(first_address)),
- second_size_(sizeof(second_address)) {}
-
- AddrFDSocketPair(int first_fd, int second_fd,
- const struct sockaddr_in& first_address,
- const struct sockaddr_in& second_address)
- : first_(first_fd),
- second_(second_fd),
- first_addr_(to_storage(first_address)),
- second_addr_(to_storage(second_address)),
- first_len_(sizeof(first_address)),
- second_len_(sizeof(second_address)),
- first_size_(sizeof(first_address)),
- second_size_(sizeof(second_address)) {}
-
- AddrFDSocketPair(int first_fd, int second_fd,
- const struct sockaddr_in6& first_address,
- const struct sockaddr_in6& second_address)
- : first_(first_fd),
- second_(second_fd),
- first_addr_(to_storage(first_address)),
- second_addr_(to_storage(second_address)),
- first_len_(sizeof(first_address)),
- second_len_(sizeof(second_address)),
- first_size_(sizeof(first_address)),
- second_size_(sizeof(second_address)) {}
-
- int first_fd() const override { return first_.get(); }
- int second_fd() const override { return second_.get(); }
- int release_first_fd() override { return first_.release(); }
- int release_second_fd() override { return second_.release(); }
- const struct sockaddr* first_addr() const override {
- return reinterpret_cast<const struct sockaddr*>(&first_addr_);
- }
- const struct sockaddr* second_addr() const override {
- return reinterpret_cast<const struct sockaddr*>(&second_addr_);
- }
- size_t first_addr_size() const override { return first_size_; }
- size_t second_addr_size() const override { return second_size_; }
- size_t first_addr_len() const override { return first_len_; }
- size_t second_addr_len() const override { return second_len_; }
-
- private:
- // to_storage coverts a sockaddr_* to a sockaddr_storage.
- static struct sockaddr_storage to_storage(const sockaddr_un& addr);
- static struct sockaddr_storage to_storage(const sockaddr_in& addr);
- static struct sockaddr_storage to_storage(const sockaddr_in6& addr);
-
- FileDescriptor first_;
- FileDescriptor second_;
- const struct sockaddr_storage first_addr_;
- const struct sockaddr_storage second_addr_;
- const size_t first_len_;
- const size_t second_len_;
- const size_t first_size_;
- const size_t second_size_;
-};
-
-// SyscallSocketPairCreator returns a Creator<SocketPair> that obtains file
-// descriptors by invoking the socketpair() syscall.
-Creator<SocketPair> SyscallSocketPairCreator(int domain, int type,
- int protocol);
-
-// SyscallSocketCreator returns a Creator<FileDescriptor> that obtains a file
-// descriptor by invoking the socket() syscall.
-Creator<FileDescriptor> SyscallSocketCreator(int domain, int type,
- int protocol);
-
-// FilesystemBidirectionalBindSocketPairCreator returns a Creator<SocketPair>
-// that obtains file descriptors by invoking the bind() and connect() syscalls
-// on filesystem paths. Only works for DGRAM sockets.
-Creator<SocketPair> FilesystemBidirectionalBindSocketPairCreator(int domain,
- int type,
- int protocol);
-
-// AbstractBidirectionalBindSocketPairCreator returns a Creator<SocketPair> that
-// obtains file descriptors by invoking the bind() and connect() syscalls on
-// abstract namespace paths. Only works for DGRAM sockets.
-Creator<SocketPair> AbstractBidirectionalBindSocketPairCreator(int domain,
- int type,
- int protocol);
-
-// SocketpairGoferSocketPairCreator returns a Creator<SocketPair> that
-// obtains file descriptors by connect() syscalls on two sockets with socketpair
-// gofer paths.
-Creator<SocketPair> SocketpairGoferSocketPairCreator(int domain, int type,
- int protocol);
-
-// SocketpairGoferFileSocketPairCreator returns a Creator<SocketPair> that
-// obtains file descriptors by open() syscalls on socketpair gofer paths.
-Creator<SocketPair> SocketpairGoferFileSocketPairCreator(int flags);
-
-// FilesystemAcceptBindSocketPairCreator returns a Creator<SocketPair> that
-// obtains file descriptors by invoking the accept() and bind() syscalls on
-// a filesystem path. Only works for STREAM and SEQPACKET sockets.
-Creator<SocketPair> FilesystemAcceptBindSocketPairCreator(int domain, int type,
- int protocol);
-
-// AbstractAcceptBindSocketPairCreator returns a Creator<SocketPair> that
-// obtains file descriptors by invoking the accept() and bind() syscalls on a
-// abstract namespace path. Only works for STREAM and SEQPACKET sockets.
-Creator<SocketPair> AbstractAcceptBindSocketPairCreator(int domain, int type,
- int protocol);
-
-// FilesystemUnboundSocketPairCreator returns a Creator<SocketPair> that obtains
-// file descriptors by invoking the socket() syscall and generates a filesystem
-// path for binding.
-Creator<SocketPair> FilesystemUnboundSocketPairCreator(int domain, int type,
- int protocol);
-
-// AbstractUnboundSocketPairCreator returns a Creator<SocketPair> that obtains
-// file descriptors by invoking the socket() syscall and generates an abstract
-// path for binding.
-Creator<SocketPair> AbstractUnboundSocketPairCreator(int domain, int type,
- int protocol);
-
-// TCPAcceptBindSocketPairCreator returns a Creator<SocketPair> that obtains
-// file descriptors by invoking the accept() and bind() syscalls on TCP sockets.
-Creator<SocketPair> TCPAcceptBindSocketPairCreator(int domain, int type,
- int protocol,
- bool dual_stack);
-
-// UDPBidirectionalBindSocketPairCreator returns a Creator<SocketPair> that
-// obtains file descriptors by invoking the bind() and connect() syscalls on UDP
-// sockets.
-Creator<SocketPair> UDPBidirectionalBindSocketPairCreator(int domain, int type,
- int protocol,
- bool dual_stack);
-
-// UDPUnboundSocketPairCreator returns a Creator<SocketPair> that obtains file
-// descriptors by creating UDP sockets.
-Creator<SocketPair> UDPUnboundSocketPairCreator(int domain, int type,
- int protocol, bool dual_stack);
-
-// UnboundSocketCreator returns a Creator<FileDescriptor> that obtains a file
-// descriptor by creating a socket.
-Creator<FileDescriptor> UnboundSocketCreator(int domain, int type,
- int protocol);
-
-// A SocketPairKind couples a human-readable description of a socket pair with
-// a function that creates such a socket pair.
-struct SocketPairKind {
- std::string description;
- int domain;
- int type;
- int protocol;
- Creator<SocketPair> creator;
-
- // Create creates a socket pair of this kind.
- PosixErrorOr<std::unique_ptr<SocketPair>> Create() const { return creator(); }
-};
-
-// A SocketKind couples a human-readable description of a socket with
-// a function that creates such a socket.
-struct SocketKind {
- std::string description;
- int domain;
- int type;
- int protocol;
- Creator<FileDescriptor> creator;
-
- // Create creates a socket pair of this kind.
- PosixErrorOr<std::unique_ptr<FileDescriptor>> Create() const {
- return creator();
- }
-};
-
-// A ReversedSocketPair wraps another SocketPair but flips the first and second
-// file descriptors. ReversedSocketPair is used to test socket pairs that
-// should be symmetric.
-class ReversedSocketPair : public SocketPair {
- public:
- explicit ReversedSocketPair(std::unique_ptr<SocketPair> base)
- : base_(std::move(base)) {}
-
- int first_fd() const override { return base_->second_fd(); }
- int second_fd() const override { return base_->first_fd(); }
- int release_first_fd() override { return base_->release_second_fd(); }
- int release_second_fd() override { return base_->release_first_fd(); }
- const struct sockaddr* first_addr() const override {
- return base_->second_addr();
- }
- const struct sockaddr* second_addr() const override {
- return base_->first_addr();
- }
- size_t first_addr_size() const override { return base_->second_addr_size(); }
- size_t second_addr_size() const override { return base_->first_addr_size(); }
- size_t first_addr_len() const override { return base_->second_addr_len(); }
- size_t second_addr_len() const override { return base_->first_addr_len(); }
-
- private:
- std::unique_ptr<SocketPair> base_;
-};
-
-// Reversed returns a SocketPairKind that represents SocketPairs created by
-// flipping the file descriptors provided by another SocketPair.
-SocketPairKind Reversed(SocketPairKind const& base);
-
-// IncludeReversals returns a vector<SocketPairKind> that returns all
-// SocketPairKinds in `vec` as well as all SocketPairKinds obtained by flipping
-// the file descriptors provided by the kinds in `vec`.
-std::vector<SocketPairKind> IncludeReversals(std::vector<SocketPairKind> vec);
-
-// A Middleware is a function wraps a SocketPairKind.
-using Middleware = std::function<SocketPairKind(SocketPairKind)>;
-
-// Reversed returns a SocketPairKind that represents SocketPairs created by
-// flipping the file descriptors provided by another SocketPair.
-template <typename T>
-Middleware SetSockOpt(int level, int optname, T* value) {
- return [=](SocketPairKind const& base) {
- auto const& creator = base.creator;
- return SocketPairKind{
- absl::StrCat("setsockopt(", level, ", ", optname, ", ", *value, ") ",
- base.description),
- base.domain, base.type, base.protocol,
- [creator, level, optname,
- value]() -> PosixErrorOr<std::unique_ptr<SocketPair>> {
- ASSIGN_OR_RETURN_ERRNO(auto creator_value, creator());
- if (creator_value->first_fd() >= 0) {
- RETURN_ERROR_IF_SYSCALL_FAIL(setsockopt(
- creator_value->first_fd(), level, optname, value, sizeof(T)));
- }
- if (creator_value->second_fd() >= 0) {
- RETURN_ERROR_IF_SYSCALL_FAIL(setsockopt(
- creator_value->second_fd(), level, optname, value, sizeof(T)));
- }
- return creator_value;
- }};
- };
-}
-
-constexpr int kSockOptOn = 1;
-constexpr int kSockOptOff = 0;
-
-// NoOp returns the same SocketPairKind that it is passed.
-SocketPairKind NoOp(SocketPairKind const& base);
-
-// TransferTest tests that data can be send back and fourth between two
-// specified FDs. Note that calls to this function should be wrapped in
-// ASSERT_NO_FATAL_FAILURE().
-void TransferTest(int fd1, int fd2);
-
-// Fills [buf, buf+len) with random bytes.
-void RandomizeBuffer(char* buf, size_t len);
-
-// Base test fixture for tests that operate on pairs of connected sockets.
-class SocketPairTest : public ::testing::TestWithParam<SocketPairKind> {
- protected:
- SocketPairTest() {
- // gUnit uses printf, so so will we.
- printf("Testing with %s\n", GetParam().description.c_str());
- fflush(stdout);
- }
-
- PosixErrorOr<std::unique_ptr<SocketPair>> NewSocketPair() const {
- return GetParam().Create();
- }
-};
-
-// Base test fixture for tests that operate on simple Sockets.
-class SimpleSocketTest : public ::testing::TestWithParam<SocketKind> {
- protected:
- SimpleSocketTest() {
- // gUnit uses printf, so so will we.
- printf("Testing with %s\n", GetParam().description.c_str());
- }
-
- PosixErrorOr<std::unique_ptr<FileDescriptor>> NewSocket() const {
- return GetParam().Create();
- }
-};
-
-SocketKind SimpleSocket(int fam, int type, int proto);
-
-// Send a buffer of size 'size' to sockets->first_fd(), returning the result of
-// sendmsg.
-//
-// If reader, read from second_fd() until size bytes have been read.
-ssize_t SendLargeSendMsg(const std::unique_ptr<SocketPair>& sockets,
- size_t size, bool reader);
-
-// Initializes the given buffer with random data.
-void RandomizeBuffer(char* ptr, size_t len);
-
-enum class AddressFamily { kIpv4 = 1, kIpv6 = 2, kDualStack = 3 };
-enum class SocketType { kUdp = 1, kTcp = 2 };
-
-// Returns a PosixError or a port that is available. If 0 is specified as the
-// port it will bind port 0 (and allow the kernel to select any free port).
-// Otherwise, it will try to bind the specified port and validate that it can be
-// used for the requested family and socket type. The final option is
-// reuse_addr. This specifies whether SO_REUSEADDR should be applied before a
-// bind(2) attempt. SO_REUSEADDR means that sockets in TIME_WAIT states or other
-// bound UDP sockets would not cause an error on bind(2). This option should be
-// set if subsequent calls to bind on the returned port will also use
-// SO_REUSEADDR.
-//
-// Note: That this test will attempt to bind the ANY address for the respective
-// protocol.
-PosixErrorOr<int> PortAvailable(int port, AddressFamily family, SocketType type,
- bool reuse_addr);
-
-// FreeAvailablePort is used to return a port that was obtained by using
-// the PortAvailable helper with port 0.
-PosixError FreeAvailablePort(int port);
-
-// SendMsg converts a buffer to an iovec and adds it to msg before sending it.
-PosixErrorOr<int> SendMsg(int sock, msghdr* msg, char buf[], int buf_size);
-
-// RecvNoData checks that no data is receivable on sock.
-void RecvNoData(int sock);
-
-// Base test fixture for tests that apply to all kinds of pairs of connected
-// sockets.
-using AllSocketPairTest = SocketPairTest;
-
-struct TestAddress {
- std::string description;
- sockaddr_storage addr;
- socklen_t addr_len;
-
- int family() const { return addr.ss_family; }
- explicit TestAddress(std::string description = "")
- : description(std::move(description)), addr(), addr_len() {}
-};
-
-TestAddress V4Any();
-TestAddress V4Loopback();
-TestAddress V4MappedAny();
-TestAddress V4MappedLoopback();
-TestAddress V6Any();
-TestAddress V6Loopback();
-
-// Compute the internet checksum of an IP header.
-uint16_t IPChecksum(struct iphdr ip);
-
-// Compute the internet checksum of a UDP header.
-uint16_t UDPChecksum(struct iphdr iphdr, struct udphdr udphdr,
- const char* payload, ssize_t payload_len);
-
-// Compute the internet checksum of an ICMP header.
-uint16_t ICMPChecksum(struct icmphdr icmphdr, const char* payload,
- ssize_t payload_len);
-
-namespace internal {
-PosixErrorOr<int> TryPortAvailable(int port, AddressFamily family,
- SocketType type, bool reuse_addr);
-} // namespace internal
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_SOCKET_TEST_UTIL_H_
diff --git a/test/syscalls/linux/socket_test_util_impl.cc b/test/syscalls/linux/socket_test_util_impl.cc
deleted file mode 100644
index ef661a0e3..000000000
--- a/test/syscalls/linux/socket_test_util_impl.cc
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/socket_test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-PosixErrorOr<int> PortAvailable(int port, AddressFamily family, SocketType type,
- bool reuse_addr) {
- return internal::TryPortAvailable(port, family, type, reuse_addr);
-}
-
-PosixError FreeAvailablePort(int port) { return NoError(); }
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_unix.cc b/test/syscalls/linux/socket_unix.cc
deleted file mode 100644
index 875f0391f..000000000
--- a/test/syscalls/linux/socket_unix.cc
+++ /dev/null
@@ -1,259 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/socket_unix.h"
-
-#include <errno.h>
-#include <net/if.h>
-#include <stdio.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <sys/un.h>
-
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "absl/strings/string_view.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-// This file contains tests specific to Unix domain sockets. It does not contain
-// tests for UDS control messages. Those belong in socket_unix_cmsg.cc.
-//
-// This file is a generic socket test file. It must be built with another file
-// that provides the test types.
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST_P(UnixSocketPairTest, InvalidGetSockOpt) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- int opt;
- socklen_t optlen = sizeof(opt);
- EXPECT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, -1, &opt, &optlen),
- SyscallFailsWithErrno(ENOPROTOOPT));
-}
-
-TEST_P(UnixSocketPairTest, BindToBadName) {
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- constexpr char kBadName[] = "/some/path/that/does/not/exist";
- sockaddr_un sockaddr;
- sockaddr.sun_family = AF_LOCAL;
- memcpy(sockaddr.sun_path, kBadName, sizeof(kBadName));
-
- EXPECT_THAT(
- bind(pair->first_fd(), reinterpret_cast<struct sockaddr*>(&sockaddr),
- sizeof(sockaddr)),
- SyscallFailsWithErrno(ENOENT));
-}
-
-TEST_P(UnixSocketPairTest, RecvmmsgTimeoutAfterRecv) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- char sent_data[10];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- char received_data[sizeof(sent_data) * 2];
- std::vector<struct mmsghdr> msgs(2);
- std::vector<struct iovec> iovs(msgs.size());
- const int chunk_size = sizeof(received_data) / msgs.size();
- for (size_t i = 0; i < msgs.size(); i++) {
- iovs[i].iov_len = chunk_size;
- iovs[i].iov_base = &received_data[i * chunk_size];
- msgs[i].msg_hdr.msg_iov = &iovs[i];
- msgs[i].msg_hdr.msg_iovlen = 1;
- }
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data, sizeof(sent_data)),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- struct timespec timeout = {0, 1};
- ASSERT_THAT(RetryEINTR(recvmmsg)(sockets->second_fd(), &msgs[0], msgs.size(),
- 0, &timeout),
- SyscallSucceedsWithValue(1));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-
- EXPECT_EQ(chunk_size, msgs[0].msg_len);
-}
-
-TEST_P(UnixSocketPairTest, TIOCINQSucceeds) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- if (IsRunningOnGvisor()) {
- // TODO(gvisor.dev/issue/273): Inherited host UDS don't support TIOCINQ.
- // Skip the test.
- int size = -1;
- int ret = ioctl(sockets->first_fd(), TIOCINQ, &size);
- SKIP_IF(ret == -1 && errno == ENOTTY);
- }
-
- int size = -1;
- EXPECT_THAT(ioctl(sockets->first_fd(), TIOCINQ, &size), SyscallSucceeds());
- EXPECT_EQ(size, 0);
-
- const char some_data[] = "dangerzone";
- ASSERT_THAT(
- RetryEINTR(send)(sockets->second_fd(), &some_data, sizeof(some_data), 0),
- SyscallSucceeds());
- EXPECT_THAT(ioctl(sockets->first_fd(), TIOCINQ, &size), SyscallSucceeds());
- EXPECT_EQ(size, sizeof(some_data));
-
- // Linux only reports the first message's size, which is wrong. We test for
- // the behavior described in the man page.
- SKIP_IF(!IsRunningOnGvisor());
-
- ASSERT_THAT(
- RetryEINTR(send)(sockets->second_fd(), &some_data, sizeof(some_data), 0),
- SyscallSucceeds());
- EXPECT_THAT(ioctl(sockets->first_fd(), TIOCINQ, &size), SyscallSucceeds());
- EXPECT_EQ(size, sizeof(some_data) * 2);
-}
-
-TEST_P(UnixSocketPairTest, TIOCOUTQSucceeds) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- if (IsRunningOnGvisor()) {
- // TODO(gvisor.dev/issue/273): Inherited host UDS don't support TIOCOUTQ.
- // Skip the test.
- int size = -1;
- int ret = ioctl(sockets->second_fd(), TIOCOUTQ, &size);
- SKIP_IF(ret == -1 && errno == ENOTTY);
- }
-
- int size = -1;
- EXPECT_THAT(ioctl(sockets->second_fd(), TIOCOUTQ, &size), SyscallSucceeds());
- EXPECT_EQ(size, 0);
-
- // Linux reports bogus numbers which are related to its internal allocations.
- // We test for the behavior described in the man page.
- SKIP_IF(!IsRunningOnGvisor());
-
- const char some_data[] = "dangerzone";
- ASSERT_THAT(
- RetryEINTR(send)(sockets->second_fd(), &some_data, sizeof(some_data), 0),
- SyscallSucceeds());
- EXPECT_THAT(ioctl(sockets->second_fd(), TIOCOUTQ, &size), SyscallSucceeds());
- EXPECT_EQ(size, sizeof(some_data));
-
- ASSERT_THAT(
- RetryEINTR(send)(sockets->second_fd(), &some_data, sizeof(some_data), 0),
- SyscallSucceeds());
- EXPECT_THAT(ioctl(sockets->second_fd(), TIOCOUTQ, &size), SyscallSucceeds());
- EXPECT_EQ(size, sizeof(some_data) * 2);
-}
-
-TEST_P(UnixSocketPairTest, NetdeviceIoctlsSucceed) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Prepare the request.
- struct ifreq ifr;
- snprintf(ifr.ifr_name, IFNAMSIZ, "lo");
-
- // Check that the ioctl either succeeds or fails with ENODEV.
- int err = ioctl(sockets->first_fd(), SIOCGIFINDEX, &ifr);
- if (err < 0) {
- ASSERT_EQ(errno, ENODEV);
- }
-}
-
-TEST_P(UnixSocketPairTest, Shutdown) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- const std::string data = "abc";
- ASSERT_THAT(WriteFd(sockets->first_fd(), data.c_str(), data.size()),
- SyscallSucceedsWithValue(data.size()));
-
- ASSERT_THAT(shutdown(sockets->first_fd(), SHUT_RDWR), SyscallSucceeds());
- ASSERT_THAT(shutdown(sockets->second_fd(), SHUT_RDWR), SyscallSucceeds());
-
- // Shutting down a socket does not clear the buffer.
- char buf[3];
- ASSERT_THAT(ReadFd(sockets->second_fd(), buf, data.size()),
- SyscallSucceedsWithValue(data.size()));
- EXPECT_EQ(data, absl::string_view(buf, data.size()));
-}
-
-TEST_P(UnixSocketPairTest, ShutdownRead) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(shutdown(sockets->first_fd(), SHUT_RD), SyscallSucceeds());
-
- // When the socket is shutdown for read, read behavior varies between
- // different socket types. This is covered by the various ReadOneSideClosed
- // test cases.
-
- // ... and the peer cannot write.
- const std::string data = "abc";
- EXPECT_THAT(WriteFd(sockets->second_fd(), data.c_str(), data.size()),
- SyscallFailsWithErrno(EPIPE));
-
- // ... but the socket can still write.
- ASSERT_THAT(WriteFd(sockets->first_fd(), data.c_str(), data.size()),
- SyscallSucceedsWithValue(data.size()));
-
- // ... and the peer can still read.
- char buf[3];
- EXPECT_THAT(ReadFd(sockets->second_fd(), buf, data.size()),
- SyscallSucceedsWithValue(data.size()));
- EXPECT_EQ(data, absl::string_view(buf, data.size()));
-}
-
-TEST_P(UnixSocketPairTest, ShutdownWrite) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(shutdown(sockets->first_fd(), SHUT_WR), SyscallSucceeds());
-
- // When the socket is shutdown for write, it cannot write.
- const std::string data = "abc";
- EXPECT_THAT(WriteFd(sockets->first_fd(), data.c_str(), data.size()),
- SyscallFailsWithErrno(EPIPE));
-
- // ... and the peer read behavior varies between different socket types. This
- // is covered by the various ReadOneSideClosed test cases.
-
- // ... but the peer can still write.
- char buf[3];
- ASSERT_THAT(WriteFd(sockets->second_fd(), data.c_str(), data.size()),
- SyscallSucceedsWithValue(data.size()));
-
- // ... and the socket can still read.
- EXPECT_THAT(ReadFd(sockets->first_fd(), buf, data.size()),
- SyscallSucceedsWithValue(data.size()));
- EXPECT_EQ(data, absl::string_view(buf, data.size()));
-}
-
-TEST_P(UnixSocketPairTest, SocketReopenFromProcfs) {
- // TODO(b/122310852): We should be returning ENXIO and NOT EIO.
- SKIP_IF(IsRunningOnGvisor());
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Opening a socket pair via /proc/self/fd/X is a ENXIO.
- for (const int fd : {sockets->first_fd(), sockets->second_fd()}) {
- ASSERT_THAT(Open(absl::StrCat("/proc/self/fd/", fd), O_WRONLY),
- PosixErrorIs(ENXIO, ::testing::_));
- }
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_unix.h b/test/syscalls/linux/socket_unix.h
deleted file mode 100644
index 3625cc404..000000000
--- a/test/syscalls/linux/socket_unix.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_H_
-#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_H_
-
-#include "test/syscalls/linux/socket_test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Test fixture for tests that apply to pairs of connected unix sockets.
-using UnixSocketPairTest = SocketPairTest;
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_H_
diff --git a/test/syscalls/linux/socket_unix_abstract_nonblock.cc b/test/syscalls/linux/socket_unix_abstract_nonblock.cc
deleted file mode 100644
index be31ab2a7..000000000
--- a/test/syscalls/linux/socket_unix_abstract_nonblock.cc
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <vector>
-
-#include "test/syscalls/linux/socket_non_blocking.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketPairKind> GetSocketPairs() {
- return ApplyVec<SocketPairKind>(
- AbstractBoundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET},
- List<int>{SOCK_NONBLOCK}));
-}
-
-INSTANTIATE_TEST_SUITE_P(
- NonBlockingAbstractUnixSockets, NonBlockingSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_unix_blocking_local.cc b/test/syscalls/linux/socket_unix_blocking_local.cc
deleted file mode 100644
index 1994139e6..000000000
--- a/test/syscalls/linux/socket_unix_blocking_local.cc
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/socket_blocking.h"
-
-#include <vector>
-
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketPairKind> GetSocketPairs() {
- return VecCat<SocketPairKind>(
- ApplyVec<SocketPairKind>(
- UnixDomainSocketPair,
- std::vector<int>{SOCK_STREAM, SOCK_SEQPACKET, SOCK_DGRAM}),
- ApplyVec<SocketPairKind>(
- FilesystemBoundUnixDomainSocketPair,
- std::vector<int>{SOCK_STREAM, SOCK_SEQPACKET, SOCK_DGRAM}),
- ApplyVec<SocketPairKind>(
- AbstractBoundUnixDomainSocketPair,
- std::vector<int>{SOCK_STREAM, SOCK_SEQPACKET, SOCK_DGRAM}));
-}
-
-INSTANTIATE_TEST_SUITE_P(
- NonBlockingUnixDomainSockets, BlockingSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_unix_cmsg.cc b/test/syscalls/linux/socket_unix_cmsg.cc
deleted file mode 100644
index 1092e29b1..000000000
--- a/test/syscalls/linux/socket_unix_cmsg.cc
+++ /dev/null
@@ -1,1473 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/socket_unix_cmsg.h"
-
-#include <errno.h>
-#include <net/if.h>
-#include <stdio.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <sys/un.h>
-
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "absl/strings/string_view.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-// This file contains tests for control message in Unix domain sockets.
-//
-// This file is a generic socket test file. It must be built with another file
-// that provides the test types.
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST_P(UnixSocketPairCmsgTest, BasicFDPass) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(),
- sent_data, sizeof(sent_data)));
-
- char received_data[20];
- int fd = -1;
- ASSERT_NO_FATAL_FAILURE(RecvSingleFD(sockets->second_fd(), &fd, received_data,
- sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-
- ASSERT_NO_FATAL_FAILURE(TransferTest(fd, pair->first_fd()));
-}
-
-TEST_P(UnixSocketPairCmsgTest, BasicTwoFDPass) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair1 =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
- auto pair2 =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
- int sent_fds[] = {pair1->second_fd(), pair2->second_fd()};
-
- ASSERT_NO_FATAL_FAILURE(
- SendFDs(sockets->first_fd(), sent_fds, 2, sent_data, sizeof(sent_data)));
-
- char received_data[20];
- int received_fds[] = {-1, -1};
-
- ASSERT_NO_FATAL_FAILURE(RecvFDs(sockets->second_fd(), received_fds, 2,
- received_data, sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-
- ASSERT_NO_FATAL_FAILURE(TransferTest(received_fds[0], pair1->first_fd()));
- ASSERT_NO_FATAL_FAILURE(TransferTest(received_fds[1], pair2->first_fd()));
-}
-
-TEST_P(UnixSocketPairCmsgTest, BasicThreeFDPass) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair1 =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
- auto pair2 =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
- auto pair3 =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
- int sent_fds[] = {pair1->second_fd(), pair2->second_fd(), pair3->second_fd()};
-
- ASSERT_NO_FATAL_FAILURE(
- SendFDs(sockets->first_fd(), sent_fds, 3, sent_data, sizeof(sent_data)));
-
- char received_data[20];
- int received_fds[] = {-1, -1, -1};
-
- ASSERT_NO_FATAL_FAILURE(RecvFDs(sockets->second_fd(), received_fds, 3,
- received_data, sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-
- ASSERT_NO_FATAL_FAILURE(TransferTest(received_fds[0], pair1->first_fd()));
- ASSERT_NO_FATAL_FAILURE(TransferTest(received_fds[1], pair2->first_fd()));
- ASSERT_NO_FATAL_FAILURE(TransferTest(received_fds[2], pair3->first_fd()));
-}
-
-TEST_P(UnixSocketPairCmsgTest, BadFDPass) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- int sent_fd = -1;
-
- struct msghdr msg = {};
- char control[CMSG_SPACE(sizeof(sent_fd))];
- msg.msg_control = control;
- msg.msg_controllen = sizeof(control);
-
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
- cmsg->cmsg_len = CMSG_LEN(sizeof(sent_fd));
- cmsg->cmsg_level = SOL_SOCKET;
- cmsg->cmsg_type = SCM_RIGHTS;
- memcpy(CMSG_DATA(cmsg), &sent_fd, sizeof(sent_fd));
-
- struct iovec iov;
- iov.iov_base = sent_data;
- iov.iov_len = sizeof(sent_data);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(sendmsg)(sockets->first_fd(), &msg, 0),
- SyscallFailsWithErrno(EBADF));
-}
-
-// BasicFDPassNoSpace starts off by sending a single FD just like BasicFDPass.
-// The difference is that when calling recvmsg, no space for FDs is provided,
-// only space for the cmsg header.
-TEST_P(UnixSocketPairCmsgTest, BasicFDPassNoSpace) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(),
- sent_data, sizeof(sent_data)));
-
- char received_data[20];
-
- struct msghdr msg = {};
- std::vector<char> control(CMSG_SPACE(0));
- msg.msg_control = &control[0];
- msg.msg_controllen = control.size();
-
- struct iovec iov;
- iov.iov_base = received_data;
- iov.iov_len = sizeof(received_data);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0),
- SyscallSucceedsWithValue(sizeof(received_data)));
-
- EXPECT_EQ(msg.msg_controllen, 0);
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-}
-
-// BasicFDPassNoSpaceMsgCtrunc sends an FD, but does not provide any space to
-// receive it. It then verifies that the MSG_CTRUNC flag is set in the msghdr.
-TEST_P(UnixSocketPairCmsgTest, BasicFDPassNoSpaceMsgCtrunc) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(),
- sent_data, sizeof(sent_data)));
-
- struct msghdr msg = {};
- std::vector<char> control(CMSG_SPACE(0));
- msg.msg_control = &control[0];
- msg.msg_controllen = control.size();
-
- char received_data[sizeof(sent_data)];
- struct iovec iov;
- iov.iov_base = received_data;
- iov.iov_len = sizeof(received_data);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0),
- SyscallSucceedsWithValue(sizeof(received_data)));
-
- EXPECT_EQ(msg.msg_controllen, 0);
- EXPECT_EQ(msg.msg_flags, MSG_CTRUNC);
-}
-
-// BasicFDPassNullControlMsgCtrunc sends an FD and sets contradictory values for
-// msg_controllen and msg_control. msg_controllen is set to the correct size to
-// accommodate the FD, but msg_control is set to NULL. In this case, msg_control
-// should override msg_controllen.
-TEST_P(UnixSocketPairCmsgTest, BasicFDPassNullControlMsgCtrunc) {
- // FIXME(gvisor.dev/issue/207): Fix handling of NULL msg_control.
- SKIP_IF(IsRunningOnGvisor());
-
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(),
- sent_data, sizeof(sent_data)));
-
- struct msghdr msg = {};
- msg.msg_controllen = CMSG_SPACE(1);
-
- char received_data[sizeof(sent_data)];
- struct iovec iov;
- iov.iov_base = received_data;
- iov.iov_len = sizeof(received_data);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0),
- SyscallSucceedsWithValue(sizeof(received_data)));
-
- EXPECT_EQ(msg.msg_controllen, 0);
- EXPECT_EQ(msg.msg_flags, MSG_CTRUNC);
-}
-
-// BasicFDPassNotEnoughSpaceMsgCtrunc sends an FD, but does not provide enough
-// space to receive it. It then verifies that the MSG_CTRUNC flag is set in the
-// msghdr.
-TEST_P(UnixSocketPairCmsgTest, BasicFDPassNotEnoughSpaceMsgCtrunc) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(),
- sent_data, sizeof(sent_data)));
-
- struct msghdr msg = {};
- std::vector<char> control(CMSG_SPACE(0) + 1);
- msg.msg_control = &control[0];
- msg.msg_controllen = control.size();
-
- char received_data[sizeof(sent_data)];
- struct iovec iov;
- iov.iov_base = received_data;
- iov.iov_len = sizeof(received_data);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0),
- SyscallSucceedsWithValue(sizeof(received_data)));
-
- EXPECT_EQ(msg.msg_controllen, 0);
- EXPECT_EQ(msg.msg_flags, MSG_CTRUNC);
-}
-
-// BasicThreeFDPassTruncationMsgCtrunc sends three FDs, but only provides enough
-// space to receive two of them. It then verifies that the MSG_CTRUNC flag is
-// set in the msghdr.
-TEST_P(UnixSocketPairCmsgTest, BasicThreeFDPassTruncationMsgCtrunc) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair1 =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
- auto pair2 =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
- auto pair3 =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
- int sent_fds[] = {pair1->second_fd(), pair2->second_fd(), pair3->second_fd()};
-
- ASSERT_NO_FATAL_FAILURE(
- SendFDs(sockets->first_fd(), sent_fds, 3, sent_data, sizeof(sent_data)));
-
- struct msghdr msg = {};
- std::vector<char> control(CMSG_SPACE(2 * sizeof(int)));
- msg.msg_control = &control[0];
- msg.msg_controllen = control.size();
-
- char received_data[sizeof(sent_data)];
- struct iovec iov;
- iov.iov_base = received_data;
- iov.iov_len = sizeof(received_data);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0),
- SyscallSucceedsWithValue(sizeof(received_data)));
-
- EXPECT_EQ(msg.msg_flags, MSG_CTRUNC);
-
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
- ASSERT_NE(cmsg, nullptr);
- EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(2 * sizeof(int)));
- EXPECT_EQ(cmsg->cmsg_level, SOL_SOCKET);
- EXPECT_EQ(cmsg->cmsg_type, SCM_RIGHTS);
-}
-
-// BasicFDPassUnalignedRecv starts off by sending a single FD just like
-// BasicFDPass. The difference is that when calling recvmsg, the length of the
-// receive data is only aligned on a 4 byte boundry instead of the normal 8.
-TEST_P(UnixSocketPairCmsgTest, BasicFDPassUnalignedRecv) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(),
- sent_data, sizeof(sent_data)));
-
- char received_data[20];
- int fd = -1;
- ASSERT_NO_FATAL_FAILURE(RecvSingleFDUnaligned(
- sockets->second_fd(), &fd, received_data, sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-
- ASSERT_NO_FATAL_FAILURE(TransferTest(fd, pair->first_fd()));
-}
-
-// BasicFDPassUnalignedRecvNoMsgTrunc sends one FD and only provides enough
-// space to receive just it. (Normally the minimum amount of space one would
-// provide would be enough space for two FDs.) It then verifies that the
-// MSG_CTRUNC flag is not set in the msghdr.
-TEST_P(UnixSocketPairCmsgTest, BasicFDPassUnalignedRecvNoMsgTrunc) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(),
- sent_data, sizeof(sent_data)));
-
- struct msghdr msg = {};
- char control[CMSG_SPACE(sizeof(int)) - sizeof(int)];
- msg.msg_control = control;
- msg.msg_controllen = sizeof(control);
-
- char received_data[sizeof(sent_data)] = {};
- struct iovec iov;
- iov.iov_base = received_data;
- iov.iov_len = sizeof(received_data);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0),
- SyscallSucceedsWithValue(sizeof(received_data)));
-
- EXPECT_EQ(msg.msg_flags, 0);
-
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
- ASSERT_NE(cmsg, nullptr);
- EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(int)));
- EXPECT_EQ(cmsg->cmsg_level, SOL_SOCKET);
- EXPECT_EQ(cmsg->cmsg_type, SCM_RIGHTS);
-}
-
-// BasicTwoFDPassUnalignedRecvTruncationMsgTrunc sends two FDs, but only
-// provides enough space to receive one of them. It then verifies that the
-// MSG_CTRUNC flag is set in the msghdr.
-TEST_P(UnixSocketPairCmsgTest, BasicTwoFDPassUnalignedRecvTruncationMsgTrunc) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
- int sent_fds[] = {pair->first_fd(), pair->second_fd()};
-
- ASSERT_NO_FATAL_FAILURE(
- SendFDs(sockets->first_fd(), sent_fds, 2, sent_data, sizeof(sent_data)));
-
- struct msghdr msg = {};
- // CMSG_SPACE rounds up to two FDs, we only want one.
- char control[CMSG_SPACE(sizeof(int)) - sizeof(int)];
- msg.msg_control = control;
- msg.msg_controllen = sizeof(control);
-
- char received_data[sizeof(sent_data)] = {};
- struct iovec iov;
- iov.iov_base = received_data;
- iov.iov_len = sizeof(received_data);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0),
- SyscallSucceedsWithValue(sizeof(received_data)));
-
- EXPECT_EQ(msg.msg_flags, MSG_CTRUNC);
-
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
- ASSERT_NE(cmsg, nullptr);
- EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(int)));
- EXPECT_EQ(cmsg->cmsg_level, SOL_SOCKET);
- EXPECT_EQ(cmsg->cmsg_type, SCM_RIGHTS);
-}
-
-TEST_P(UnixSocketPairCmsgTest, ConcurrentBasicFDPass) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- int sockfd1 = sockets->first_fd();
- auto recv_func = [sockfd1, sent_data]() {
- char received_data[20];
- int fd = -1;
- RecvSingleFD(sockfd1, &fd, received_data, sizeof(received_data));
- ASSERT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
- char buf[20];
- ASSERT_THAT(ReadFd(fd, buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
- ASSERT_THAT(WriteFd(fd, buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
- };
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->second_fd(), pair->second_fd(),
- sent_data, sizeof(sent_data)));
-
- ScopedThread t(recv_func);
-
- RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(WriteFd(pair->first_fd(), sent_data, sizeof(sent_data)),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- char received_data[20];
- ASSERT_THAT(ReadFd(pair->first_fd(), received_data, sizeof(received_data)),
- SyscallSucceedsWithValue(sizeof(received_data)));
-
- t.Join();
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-}
-
-// FDPassNoRecv checks that the control message can be safely ignored by using
-// read(2) instead of recvmsg(2).
-TEST_P(UnixSocketPairCmsgTest, FDPassNoRecv) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(),
- sent_data, sizeof(sent_data)));
-
- // Read while ignoring the passed FD.
- char received_data[20];
- ASSERT_THAT(
- ReadFd(sockets->second_fd(), received_data, sizeof(received_data)),
- SyscallSucceedsWithValue(sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-
- // Check that the socket still works for reads and writes.
- ASSERT_NO_FATAL_FAILURE(
- TransferTest(sockets->first_fd(), sockets->second_fd()));
-}
-
-// FDPassInterspersed1 checks that sent control messages cannot be read before
-// their associated data has been read.
-TEST_P(UnixSocketPairCmsgTest, FDPassInterspersed1) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char written_data[20];
- RandomizeBuffer(written_data, sizeof(written_data));
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), written_data, sizeof(written_data)),
- SyscallSucceedsWithValue(sizeof(written_data)));
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(),
- sent_data, sizeof(sent_data)));
-
- // Check that we don't get a control message, but do get the data.
- char received_data[20];
- RecvNoCmsg(sockets->second_fd(), received_data, sizeof(received_data));
- EXPECT_EQ(0, memcmp(written_data, received_data, sizeof(written_data)));
-}
-
-// FDPassInterspersed2 checks that sent control messages cannot be read after
-// their associated data has been read while ignoring the control message by
-// using read(2) instead of recvmsg(2).
-TEST_P(UnixSocketPairCmsgTest, FDPassInterspersed2) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(),
- sent_data, sizeof(sent_data)));
-
- char written_data[20];
- RandomizeBuffer(written_data, sizeof(written_data));
- ASSERT_THAT(WriteFd(sockets->first_fd(), written_data, sizeof(written_data)),
- SyscallSucceedsWithValue(sizeof(written_data)));
-
- char received_data[20];
- ASSERT_THAT(
- ReadFd(sockets->second_fd(), received_data, sizeof(received_data)),
- SyscallSucceedsWithValue(sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-
- ASSERT_NO_FATAL_FAILURE(
- RecvNoCmsg(sockets->second_fd(), received_data, sizeof(received_data)));
- EXPECT_EQ(0, memcmp(written_data, received_data, sizeof(written_data)));
-}
-
-TEST_P(UnixSocketPairCmsgTest, FDPassNotCoalesced) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data1[20];
- RandomizeBuffer(sent_data1, sizeof(sent_data1));
-
- auto pair1 =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair1->second_fd(),
- sent_data1, sizeof(sent_data1)));
-
- char sent_data2[20];
- RandomizeBuffer(sent_data2, sizeof(sent_data2));
-
- auto pair2 =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair2->second_fd(),
- sent_data2, sizeof(sent_data2)));
-
- char received_data1[sizeof(sent_data1) + sizeof(sent_data2)];
- int received_fd1 = -1;
-
- RecvSingleFD(sockets->second_fd(), &received_fd1, received_data1,
- sizeof(received_data1), sizeof(sent_data1));
-
- EXPECT_EQ(0, memcmp(sent_data1, received_data1, sizeof(sent_data1)));
- TransferTest(pair1->first_fd(), pair1->second_fd());
-
- char received_data2[sizeof(sent_data1) + sizeof(sent_data2)];
- int received_fd2 = -1;
-
- RecvSingleFD(sockets->second_fd(), &received_fd2, received_data2,
- sizeof(received_data2), sizeof(sent_data2));
-
- EXPECT_EQ(0, memcmp(sent_data2, received_data2, sizeof(sent_data2)));
- TransferTest(pair2->first_fd(), pair2->second_fd());
-}
-
-TEST_P(UnixSocketPairCmsgTest, FDPassPeek) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(),
- sent_data, sizeof(sent_data)));
-
- char peek_data[20];
- int peek_fd = -1;
- PeekSingleFD(sockets->second_fd(), &peek_fd, peek_data, sizeof(peek_data));
- EXPECT_EQ(0, memcmp(sent_data, peek_data, sizeof(sent_data)));
- TransferTest(peek_fd, pair->first_fd());
- EXPECT_THAT(close(peek_fd), SyscallSucceeds());
-
- char received_data[20];
- int received_fd = -1;
- RecvSingleFD(sockets->second_fd(), &received_fd, received_data,
- sizeof(received_data));
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
- TransferTest(received_fd, pair->first_fd());
- EXPECT_THAT(close(received_fd), SyscallSucceeds());
-}
-
-TEST_P(UnixSocketPairCmsgTest, BasicCredPass) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- struct ucred sent_creds;
-
- ASSERT_THAT(sent_creds.pid = getpid(), SyscallSucceeds());
- ASSERT_THAT(sent_creds.uid = getuid(), SyscallSucceeds());
- ASSERT_THAT(sent_creds.gid = getgid(), SyscallSucceeds());
-
- ASSERT_NO_FATAL_FAILURE(
- SendCreds(sockets->first_fd(), sent_creds, sent_data, sizeof(sent_data)));
-
- SetSoPassCred(sockets->second_fd());
-
- char received_data[20];
- struct ucred received_creds;
- ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds,
- received_data, sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
- EXPECT_EQ(sent_creds.pid, received_creds.pid);
- EXPECT_EQ(sent_creds.uid, received_creds.uid);
- EXPECT_EQ(sent_creds.gid, received_creds.gid);
-}
-
-TEST_P(UnixSocketPairCmsgTest, SendNullCredsBeforeSoPassCredRecvEnd) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- ASSERT_NO_FATAL_FAILURE(
- SendNullCmsg(sockets->first_fd(), sent_data, sizeof(sent_data)));
-
- SetSoPassCred(sockets->second_fd());
-
- char received_data[20];
- struct ucred received_creds;
- ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds,
- received_data, sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-
- struct ucred want_creds {
- 0, 65534, 65534
- };
-
- EXPECT_EQ(want_creds.pid, received_creds.pid);
- EXPECT_EQ(want_creds.uid, received_creds.uid);
- EXPECT_EQ(want_creds.gid, received_creds.gid);
-}
-
-TEST_P(UnixSocketPairCmsgTest, SendNullCredsAfterSoPassCredRecvEnd) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- SetSoPassCred(sockets->second_fd());
-
- ASSERT_NO_FATAL_FAILURE(
- SendNullCmsg(sockets->first_fd(), sent_data, sizeof(sent_data)));
-
- char received_data[20];
- struct ucred received_creds;
- ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds,
- received_data, sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-
- struct ucred want_creds;
- ASSERT_THAT(want_creds.pid = getpid(), SyscallSucceeds());
- ASSERT_THAT(want_creds.uid = getuid(), SyscallSucceeds());
- ASSERT_THAT(want_creds.gid = getgid(), SyscallSucceeds());
-
- EXPECT_EQ(want_creds.pid, received_creds.pid);
- EXPECT_EQ(want_creds.uid, received_creds.uid);
- EXPECT_EQ(want_creds.gid, received_creds.gid);
-}
-
-TEST_P(UnixSocketPairCmsgTest, SendNullCredsBeforeSoPassCredSendEnd) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- ASSERT_NO_FATAL_FAILURE(
- SendNullCmsg(sockets->first_fd(), sent_data, sizeof(sent_data)));
-
- SetSoPassCred(sockets->first_fd());
-
- char received_data[20];
- ASSERT_NO_FATAL_FAILURE(
- RecvNoCmsg(sockets->second_fd(), received_data, sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-}
-
-TEST_P(UnixSocketPairCmsgTest, SendNullCredsAfterSoPassCredSendEnd) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- SetSoPassCred(sockets->first_fd());
-
- ASSERT_NO_FATAL_FAILURE(
- SendNullCmsg(sockets->first_fd(), sent_data, sizeof(sent_data)));
-
- char received_data[20];
- ASSERT_NO_FATAL_FAILURE(
- RecvNoCmsg(sockets->second_fd(), received_data, sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-}
-
-TEST_P(UnixSocketPairCmsgTest,
- SendNullCredsBeforeSoPassCredRecvEndAfterSendEnd) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- SetSoPassCred(sockets->first_fd());
-
- ASSERT_NO_FATAL_FAILURE(
- SendNullCmsg(sockets->first_fd(), sent_data, sizeof(sent_data)));
-
- SetSoPassCred(sockets->second_fd());
-
- char received_data[20];
- struct ucred received_creds;
- ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds,
- received_data, sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-
- struct ucred want_creds;
- ASSERT_THAT(want_creds.pid = getpid(), SyscallSucceeds());
- ASSERT_THAT(want_creds.uid = getuid(), SyscallSucceeds());
- ASSERT_THAT(want_creds.gid = getgid(), SyscallSucceeds());
-
- EXPECT_EQ(want_creds.pid, received_creds.pid);
- EXPECT_EQ(want_creds.uid, received_creds.uid);
- EXPECT_EQ(want_creds.gid, received_creds.gid);
-}
-
-TEST_P(UnixSocketPairCmsgTest, WriteBeforeSoPassCredRecvEnd) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data, sizeof(sent_data)),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- SetSoPassCred(sockets->second_fd());
-
- char received_data[20];
-
- struct ucred received_creds;
- ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds,
- received_data, sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-
- struct ucred want_creds {
- 0, 65534, 65534
- };
-
- EXPECT_EQ(want_creds.pid, received_creds.pid);
- EXPECT_EQ(want_creds.uid, received_creds.uid);
- EXPECT_EQ(want_creds.gid, received_creds.gid);
-}
-
-TEST_P(UnixSocketPairCmsgTest, WriteAfterSoPassCredRecvEnd) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- SetSoPassCred(sockets->second_fd());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data, sizeof(sent_data)),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- char received_data[20];
-
- struct ucred received_creds;
- ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds,
- received_data, sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-
- struct ucred want_creds;
- ASSERT_THAT(want_creds.pid = getpid(), SyscallSucceeds());
- ASSERT_THAT(want_creds.uid = getuid(), SyscallSucceeds());
- ASSERT_THAT(want_creds.gid = getgid(), SyscallSucceeds());
-
- EXPECT_EQ(want_creds.pid, received_creds.pid);
- EXPECT_EQ(want_creds.uid, received_creds.uid);
- EXPECT_EQ(want_creds.gid, received_creds.gid);
-}
-
-TEST_P(UnixSocketPairCmsgTest, WriteBeforeSoPassCredSendEnd) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data, sizeof(sent_data)),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- SetSoPassCred(sockets->first_fd());
-
- char received_data[20];
- ASSERT_NO_FATAL_FAILURE(
- RecvNoCmsg(sockets->second_fd(), received_data, sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-}
-
-TEST_P(UnixSocketPairCmsgTest, WriteAfterSoPassCredSendEnd) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- SetSoPassCred(sockets->first_fd());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data, sizeof(sent_data)),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- char received_data[20];
- ASSERT_NO_FATAL_FAILURE(
- RecvNoCmsg(sockets->second_fd(), received_data, sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-}
-
-TEST_P(UnixSocketPairCmsgTest, WriteBeforeSoPassCredRecvEndAfterSendEnd) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- SetSoPassCred(sockets->first_fd());
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data, sizeof(sent_data)),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- SetSoPassCred(sockets->second_fd());
-
- char received_data[20];
-
- struct ucred received_creds;
- ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds,
- received_data, sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-
- struct ucred want_creds;
- ASSERT_THAT(want_creds.pid = getpid(), SyscallSucceeds());
- ASSERT_THAT(want_creds.uid = getuid(), SyscallSucceeds());
- ASSERT_THAT(want_creds.gid = getgid(), SyscallSucceeds());
-
- EXPECT_EQ(want_creds.pid, received_creds.pid);
- EXPECT_EQ(want_creds.uid, received_creds.uid);
- EXPECT_EQ(want_creds.gid, received_creds.gid);
-}
-
-TEST_P(UnixSocketPairCmsgTest, CredPassTruncated) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- struct ucred sent_creds;
-
- ASSERT_THAT(sent_creds.pid = getpid(), SyscallSucceeds());
- ASSERT_THAT(sent_creds.uid = getuid(), SyscallSucceeds());
- ASSERT_THAT(sent_creds.gid = getgid(), SyscallSucceeds());
-
- ASSERT_NO_FATAL_FAILURE(
- SendCreds(sockets->first_fd(), sent_creds, sent_data, sizeof(sent_data)));
-
- SetSoPassCred(sockets->second_fd());
-
- struct msghdr msg = {};
- char control[CMSG_SPACE(0) + sizeof(pid_t)];
- msg.msg_control = control;
- msg.msg_controllen = sizeof(control);
-
- char received_data[sizeof(sent_data)] = {};
- struct iovec iov;
- iov.iov_base = received_data;
- iov.iov_len = sizeof(received_data);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0),
- SyscallSucceedsWithValue(sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-
- EXPECT_EQ(msg.msg_controllen, sizeof(control));
-
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
- ASSERT_NE(cmsg, nullptr);
- EXPECT_EQ(cmsg->cmsg_len, sizeof(control));
- EXPECT_EQ(cmsg->cmsg_level, SOL_SOCKET);
- EXPECT_EQ(cmsg->cmsg_type, SCM_CREDENTIALS);
-
- pid_t pid = 0;
- memcpy(&pid, CMSG_DATA(cmsg), sizeof(pid));
- EXPECT_EQ(pid, sent_creds.pid);
-}
-
-// CredPassNoMsgCtrunc passes a full set of credentials. It then verifies that
-// receiving the full set does not result in MSG_CTRUNC being set in the msghdr.
-TEST_P(UnixSocketPairCmsgTest, CredPassNoMsgCtrunc) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- struct ucred sent_creds;
-
- ASSERT_THAT(sent_creds.pid = getpid(), SyscallSucceeds());
- ASSERT_THAT(sent_creds.uid = getuid(), SyscallSucceeds());
- ASSERT_THAT(sent_creds.gid = getgid(), SyscallSucceeds());
-
- ASSERT_NO_FATAL_FAILURE(
- SendCreds(sockets->first_fd(), sent_creds, sent_data, sizeof(sent_data)));
-
- SetSoPassCred(sockets->second_fd());
-
- struct msghdr msg = {};
- char control[CMSG_SPACE(sizeof(struct ucred))];
- msg.msg_control = control;
- msg.msg_controllen = sizeof(control);
-
- char received_data[sizeof(sent_data)] = {};
- struct iovec iov;
- iov.iov_base = received_data;
- iov.iov_len = sizeof(received_data);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0),
- SyscallSucceedsWithValue(sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-
- // The control message should not be truncated.
- EXPECT_EQ(msg.msg_flags, 0);
- EXPECT_EQ(msg.msg_controllen, sizeof(control));
-
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
- ASSERT_NE(cmsg, nullptr);
- EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(struct ucred)));
- EXPECT_EQ(cmsg->cmsg_level, SOL_SOCKET);
- EXPECT_EQ(cmsg->cmsg_type, SCM_CREDENTIALS);
-}
-
-// CredPassNoSpaceMsgCtrunc passes a full set of credentials. It then receives
-// the data without providing space for any credentials and verifies that
-// MSG_CTRUNC is set in the msghdr.
-TEST_P(UnixSocketPairCmsgTest, CredPassNoSpaceMsgCtrunc) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- struct ucred sent_creds;
-
- ASSERT_THAT(sent_creds.pid = getpid(), SyscallSucceeds());
- ASSERT_THAT(sent_creds.uid = getuid(), SyscallSucceeds());
- ASSERT_THAT(sent_creds.gid = getgid(), SyscallSucceeds());
-
- ASSERT_NO_FATAL_FAILURE(
- SendCreds(sockets->first_fd(), sent_creds, sent_data, sizeof(sent_data)));
-
- SetSoPassCred(sockets->second_fd());
-
- struct msghdr msg = {};
- char control[CMSG_SPACE(0)];
- msg.msg_control = control;
- msg.msg_controllen = sizeof(control);
-
- char received_data[sizeof(sent_data)] = {};
- struct iovec iov;
- iov.iov_base = received_data;
- iov.iov_len = sizeof(received_data);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0),
- SyscallSucceedsWithValue(sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-
- // The control message should be truncated.
- EXPECT_EQ(msg.msg_flags, MSG_CTRUNC);
- EXPECT_EQ(msg.msg_controllen, sizeof(control));
-
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
- ASSERT_NE(cmsg, nullptr);
- EXPECT_EQ(cmsg->cmsg_len, sizeof(control));
- EXPECT_EQ(cmsg->cmsg_level, SOL_SOCKET);
- EXPECT_EQ(cmsg->cmsg_type, SCM_CREDENTIALS);
-}
-
-// CredPassTruncatedMsgCtrunc passes a full set of credentials. It then receives
-// the data while providing enough space for only the first field of the
-// credentials and verifies that MSG_CTRUNC is set in the msghdr.
-TEST_P(UnixSocketPairCmsgTest, CredPassTruncatedMsgCtrunc) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- struct ucred sent_creds;
-
- ASSERT_THAT(sent_creds.pid = getpid(), SyscallSucceeds());
- ASSERT_THAT(sent_creds.uid = getuid(), SyscallSucceeds());
- ASSERT_THAT(sent_creds.gid = getgid(), SyscallSucceeds());
-
- ASSERT_NO_FATAL_FAILURE(
- SendCreds(sockets->first_fd(), sent_creds, sent_data, sizeof(sent_data)));
-
- SetSoPassCred(sockets->second_fd());
-
- struct msghdr msg = {};
- char control[CMSG_SPACE(0) + sizeof(pid_t)];
- msg.msg_control = control;
- msg.msg_controllen = sizeof(control);
-
- char received_data[sizeof(sent_data)] = {};
- struct iovec iov;
- iov.iov_base = received_data;
- iov.iov_len = sizeof(received_data);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0),
- SyscallSucceedsWithValue(sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-
- // The control message should be truncated.
- EXPECT_EQ(msg.msg_flags, MSG_CTRUNC);
- EXPECT_EQ(msg.msg_controllen, sizeof(control));
-
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
- ASSERT_NE(cmsg, nullptr);
- EXPECT_EQ(cmsg->cmsg_len, sizeof(control));
- EXPECT_EQ(cmsg->cmsg_level, SOL_SOCKET);
- EXPECT_EQ(cmsg->cmsg_type, SCM_CREDENTIALS);
-}
-
-TEST_P(UnixSocketPairCmsgTest, SoPassCred) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- int opt;
- socklen_t optLen = sizeof(opt);
- EXPECT_THAT(
- getsockopt(sockets->first_fd(), SOL_SOCKET, SO_PASSCRED, &opt, &optLen),
- SyscallSucceeds());
- EXPECT_FALSE(opt);
-
- optLen = sizeof(opt);
- EXPECT_THAT(
- getsockopt(sockets->second_fd(), SOL_SOCKET, SO_PASSCRED, &opt, &optLen),
- SyscallSucceeds());
- EXPECT_FALSE(opt);
-
- SetSoPassCred(sockets->first_fd());
-
- optLen = sizeof(opt);
- EXPECT_THAT(
- getsockopt(sockets->first_fd(), SOL_SOCKET, SO_PASSCRED, &opt, &optLen),
- SyscallSucceeds());
- EXPECT_TRUE(opt);
-
- optLen = sizeof(opt);
- EXPECT_THAT(
- getsockopt(sockets->second_fd(), SOL_SOCKET, SO_PASSCRED, &opt, &optLen),
- SyscallSucceeds());
- EXPECT_FALSE(opt);
-
- int zero = 0;
- EXPECT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_PASSCRED, &zero,
- sizeof(zero)),
- SyscallSucceeds());
-
- optLen = sizeof(opt);
- EXPECT_THAT(
- getsockopt(sockets->first_fd(), SOL_SOCKET, SO_PASSCRED, &opt, &optLen),
- SyscallSucceeds());
- EXPECT_FALSE(opt);
-
- optLen = sizeof(opt);
- EXPECT_THAT(
- getsockopt(sockets->second_fd(), SOL_SOCKET, SO_PASSCRED, &opt, &optLen),
- SyscallSucceeds());
- EXPECT_FALSE(opt);
-}
-
-TEST_P(UnixSocketPairCmsgTest, NoDataCredPass) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- struct msghdr msg = {};
-
- struct iovec iov;
- iov.iov_base = sent_data;
- iov.iov_len = sizeof(sent_data);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- char control[CMSG_SPACE(0)];
- msg.msg_control = control;
- msg.msg_controllen = sizeof(control);
-
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
- cmsg->cmsg_level = SOL_SOCKET;
- cmsg->cmsg_type = SCM_CREDENTIALS;
- cmsg->cmsg_len = CMSG_LEN(0);
-
- ASSERT_THAT(RetryEINTR(sendmsg)(sockets->first_fd(), &msg, 0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_P(UnixSocketPairCmsgTest, NoPassCred) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- struct ucred sent_creds;
-
- ASSERT_THAT(sent_creds.pid = getpid(), SyscallSucceeds());
- ASSERT_THAT(sent_creds.uid = getuid(), SyscallSucceeds());
- ASSERT_THAT(sent_creds.gid = getgid(), SyscallSucceeds());
-
- ASSERT_NO_FATAL_FAILURE(
- SendCreds(sockets->first_fd(), sent_creds, sent_data, sizeof(sent_data)));
-
- char received_data[20];
-
- ASSERT_NO_FATAL_FAILURE(
- RecvNoCmsg(sockets->second_fd(), received_data, sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-}
-
-TEST_P(UnixSocketPairCmsgTest, CredAndFDPass) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- struct ucred sent_creds;
-
- ASSERT_THAT(sent_creds.pid = getpid(), SyscallSucceeds());
- ASSERT_THAT(sent_creds.uid = getuid(), SyscallSucceeds());
- ASSERT_THAT(sent_creds.gid = getgid(), SyscallSucceeds());
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendCredsAndFD(sockets->first_fd(), sent_creds,
- pair->second_fd(), sent_data,
- sizeof(sent_data)));
-
- SetSoPassCred(sockets->second_fd());
-
- char received_data[20];
- struct ucred received_creds;
- int fd = -1;
- ASSERT_NO_FATAL_FAILURE(RecvCredsAndFD(sockets->second_fd(), &received_creds,
- &fd, received_data,
- sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-
- EXPECT_EQ(sent_creds.pid, received_creds.pid);
- EXPECT_EQ(sent_creds.uid, received_creds.uid);
- EXPECT_EQ(sent_creds.gid, received_creds.gid);
-
- ASSERT_NO_FATAL_FAILURE(TransferTest(fd, pair->first_fd()));
-}
-
-TEST_P(UnixSocketPairCmsgTest, FDPassBeforeSoPassCred) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(),
- sent_data, sizeof(sent_data)));
-
- SetSoPassCred(sockets->second_fd());
-
- char received_data[20];
- struct ucred received_creds;
- int fd = -1;
- ASSERT_NO_FATAL_FAILURE(RecvCredsAndFD(sockets->second_fd(), &received_creds,
- &fd, received_data,
- sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-
- struct ucred want_creds {
- 0, 65534, 65534
- };
-
- EXPECT_EQ(want_creds.pid, received_creds.pid);
- EXPECT_EQ(want_creds.uid, received_creds.uid);
- EXPECT_EQ(want_creds.gid, received_creds.gid);
-
- ASSERT_NO_FATAL_FAILURE(TransferTest(fd, pair->first_fd()));
-}
-
-TEST_P(UnixSocketPairCmsgTest, FDPassAfterSoPassCred) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- SetSoPassCred(sockets->second_fd());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(),
- sent_data, sizeof(sent_data)));
-
- char received_data[20];
- struct ucred received_creds;
- int fd = -1;
- ASSERT_NO_FATAL_FAILURE(RecvCredsAndFD(sockets->second_fd(), &received_creds,
- &fd, received_data,
- sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-
- struct ucred want_creds;
- ASSERT_THAT(want_creds.pid = getpid(), SyscallSucceeds());
- ASSERT_THAT(want_creds.uid = getuid(), SyscallSucceeds());
- ASSERT_THAT(want_creds.gid = getgid(), SyscallSucceeds());
-
- EXPECT_EQ(want_creds.pid, received_creds.pid);
- EXPECT_EQ(want_creds.uid, received_creds.uid);
- EXPECT_EQ(want_creds.gid, received_creds.gid);
-
- ASSERT_NO_FATAL_FAILURE(TransferTest(fd, pair->first_fd()));
-}
-
-TEST_P(UnixSocketPairCmsgTest, CloexecDroppedWhenFDPassed) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair = ASSERT_NO_ERRNO_AND_VALUE(
- UnixDomainSocketPair(SOCK_SEQPACKET | SOCK_CLOEXEC).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(),
- sent_data, sizeof(sent_data)));
-
- char received_data[20];
- int fd = -1;
- ASSERT_NO_FATAL_FAILURE(RecvSingleFD(sockets->second_fd(), &fd, received_data,
- sizeof(received_data)));
-
- EXPECT_THAT(fcntl(fd, F_GETFD), SyscallSucceedsWithValue(0));
-}
-
-TEST_P(UnixSocketPairCmsgTest, CloexecRecvFDPass) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(),
- sent_data, sizeof(sent_data)));
-
- struct msghdr msg = {};
- char control[CMSG_SPACE(sizeof(int))];
- msg.msg_control = control;
- msg.msg_controllen = sizeof(control);
-
- struct iovec iov;
- char received_data[20];
- iov.iov_base = received_data;
- iov.iov_len = sizeof(received_data);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, MSG_CMSG_CLOEXEC),
- SyscallSucceedsWithValue(sizeof(received_data)));
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
- ASSERT_NE(cmsg, nullptr);
- ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(int)));
- ASSERT_EQ(cmsg->cmsg_level, SOL_SOCKET);
- ASSERT_EQ(cmsg->cmsg_type, SCM_RIGHTS);
-
- int fd = -1;
- memcpy(&fd, CMSG_DATA(cmsg), sizeof(int));
-
- EXPECT_THAT(fcntl(fd, F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC));
-}
-
-TEST_P(UnixSocketPairCmsgTest, FDPassAfterSoPassCredWithoutCredSpace) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- SetSoPassCred(sockets->second_fd());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(),
- sent_data, sizeof(sent_data)));
-
- struct msghdr msg = {};
- char control[CMSG_LEN(0)];
- msg.msg_control = control;
- msg.msg_controllen = sizeof(control);
-
- char received_data[20];
- struct iovec iov;
- iov.iov_base = received_data;
- iov.iov_len = sizeof(received_data);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0),
- SyscallSucceedsWithValue(sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-
- EXPECT_EQ(msg.msg_controllen, sizeof(control));
-
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
- ASSERT_NE(cmsg, nullptr);
- EXPECT_EQ(cmsg->cmsg_len, sizeof(control));
- EXPECT_EQ(cmsg->cmsg_level, SOL_SOCKET);
- EXPECT_EQ(cmsg->cmsg_type, SCM_CREDENTIALS);
-}
-
-// This test will validate that MSG_CTRUNC as an input flag to recvmsg will
-// not appear as an output flag on the control message when truncation doesn't
-// happen.
-TEST_P(UnixSocketPairCmsgTest, MsgCtruncInputIsNoop) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(),
- sent_data, sizeof(sent_data)));
-
- struct msghdr msg = {};
- char control[CMSG_SPACE(sizeof(int)) /* we're passing a single fd */];
- msg.msg_control = control;
- msg.msg_controllen = sizeof(control);
-
- struct iovec iov;
- char received_data[20];
- iov.iov_base = received_data;
- iov.iov_len = sizeof(received_data);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, MSG_CTRUNC),
- SyscallSucceedsWithValue(sizeof(received_data)));
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
- ASSERT_NE(cmsg, nullptr);
- ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(int)));
- ASSERT_EQ(cmsg->cmsg_level, SOL_SOCKET);
- ASSERT_EQ(cmsg->cmsg_type, SCM_RIGHTS);
-
- // Now we should verify that MSG_CTRUNC wasn't set as an output flag.
- EXPECT_EQ(msg.msg_flags & MSG_CTRUNC, 0);
-}
-
-TEST_P(UnixSocketPairCmsgTest, FDPassAfterSoPassCredWithoutCredHeaderSpace) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- SetSoPassCred(sockets->second_fd());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(),
- sent_data, sizeof(sent_data)));
-
- struct msghdr msg = {};
- char control[CMSG_LEN(0) / 2];
- msg.msg_control = control;
- msg.msg_controllen = sizeof(control);
-
- char received_data[20];
- struct iovec iov;
- iov.iov_base = received_data;
- iov.iov_len = sizeof(received_data);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0),
- SyscallSucceedsWithValue(sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
- EXPECT_EQ(msg.msg_controllen, 0);
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_unix_cmsg.h b/test/syscalls/linux/socket_unix_cmsg.h
deleted file mode 100644
index 431606903..000000000
--- a/test/syscalls/linux/socket_unix_cmsg.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_CMSG_H_
-#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_CMSG_H_
-
-#include "test/syscalls/linux/socket_test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Test fixture for tests that apply to pairs of connected unix sockets about
-// control messages.
-using UnixSocketPairCmsgTest = SocketPairTest;
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_CMSG_H_
diff --git a/test/syscalls/linux/socket_unix_dgram.cc b/test/syscalls/linux/socket_unix_dgram.cc
deleted file mode 100644
index 3e0f611d2..000000000
--- a/test/syscalls/linux/socket_unix_dgram.cc
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/socket_unix_dgram.h"
-
-#include <stdio.h>
-#include <sys/un.h>
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST_P(DgramUnixSocketPairTest, WriteOneSideClosed) {
- // FIXME(b/35925052): gVisor datagram sockets return EPIPE instead of
- // ECONNREFUSED.
- SKIP_IF(IsRunningOnGvisor());
-
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds());
- constexpr char kStr[] = "abc";
- ASSERT_THAT(write(sockets->second_fd(), kStr, 3),
- SyscallFailsWithErrno(ECONNREFUSED));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_unix_dgram.h b/test/syscalls/linux/socket_unix_dgram.h
deleted file mode 100644
index 0764ef85b..000000000
--- a/test/syscalls/linux/socket_unix_dgram.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_DGRAM_H_
-#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_DGRAM_H_
-
-#include "test/syscalls/linux/socket_test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Test fixture for tests that apply to pairs of connected dgram unix sockets.
-using DgramUnixSocketPairTest = SocketPairTest;
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_DGRAM_H_
diff --git a/test/syscalls/linux/socket_unix_dgram_local.cc b/test/syscalls/linux/socket_unix_dgram_local.cc
deleted file mode 100644
index 9134fcdf7..000000000
--- a/test/syscalls/linux/socket_unix_dgram_local.cc
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <vector>
-
-#include "test/syscalls/linux/socket_non_stream.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/socket_unix_dgram.h"
-#include "test/syscalls/linux/socket_unix_non_stream.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketPairKind> GetSocketPairs() {
- return VecCat<SocketPairKind>(VecCat<SocketPairKind>(
- ApplyVec<SocketPairKind>(
- UnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_DGRAM, SOCK_RAW},
- List<int>{0, SOCK_NONBLOCK})),
- ApplyVec<SocketPairKind>(
- FilesystemBoundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_DGRAM, SOCK_RAW},
- List<int>{0, SOCK_NONBLOCK})),
- ApplyVec<SocketPairKind>(
- AbstractBoundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_DGRAM, SOCK_RAW},
- List<int>{0, SOCK_NONBLOCK}))));
-}
-
-INSTANTIATE_TEST_SUITE_P(
- DgramUnixSockets, DgramUnixSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-INSTANTIATE_TEST_SUITE_P(
- DgramUnixSockets, UnixNonStreamSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-INSTANTIATE_TEST_SUITE_P(
- DgramUnixSockets, NonStreamSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_unix_dgram_non_blocking.cc b/test/syscalls/linux/socket_unix_dgram_non_blocking.cc
deleted file mode 100644
index 707052af8..000000000
--- a/test/syscalls/linux/socket_unix_dgram_non_blocking.cc
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <stdio.h>
-#include <sys/un.h>
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// Test fixture for tests that apply to pairs of connected non-blocking dgram
-// unix sockets.
-using NonBlockingDgramUnixSocketPairTest = SocketPairTest;
-
-TEST_P(NonBlockingDgramUnixSocketPairTest, ReadOneSideClosed) {
- if (IsRunningOnGvisor()) {
- // FIXME(b/70803293): gVisor datagram sockets return 0 instead of
- // EAGAIN.
- return;
- }
-
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds());
- char data[10] = {};
- ASSERT_THAT(read(sockets->second_fd(), data, sizeof(data)),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-INSTANTIATE_TEST_SUITE_P(
- NonBlockingDgramUnixSockets, NonBlockingDgramUnixSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(std::vector<SocketPairKind>{
- UnixDomainSocketPair(SOCK_DGRAM | SOCK_NONBLOCK),
- FilesystemBoundUnixDomainSocketPair(SOCK_DGRAM | SOCK_NONBLOCK),
- AbstractBoundUnixDomainSocketPair(SOCK_DGRAM | SOCK_NONBLOCK),
- })));
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_unix_domain.cc b/test/syscalls/linux/socket_unix_domain.cc
deleted file mode 100644
index fa3efc7f8..000000000
--- a/test/syscalls/linux/socket_unix_domain.cc
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <vector>
-
-#include "test/syscalls/linux/socket_generic.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketPairKind> GetSocketPairs() {
- return ApplyVec<SocketPairKind>(
- UnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET},
- List<int>{0, SOCK_NONBLOCK}));
-}
-
-INSTANTIATE_TEST_SUITE_P(
- AllUnixDomainSockets, AllSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_unix_filesystem_nonblock.cc b/test/syscalls/linux/socket_unix_filesystem_nonblock.cc
deleted file mode 100644
index 8ba7af971..000000000
--- a/test/syscalls/linux/socket_unix_filesystem_nonblock.cc
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <vector>
-
-#include "test/syscalls/linux/socket_non_blocking.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketPairKind> GetSocketPairs() {
- return ApplyVec<SocketPairKind>(
- FilesystemBoundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET},
- List<int>{SOCK_NONBLOCK}));
-}
-
-INSTANTIATE_TEST_SUITE_P(
- NonBlockingFilesystemUnixSockets, NonBlockingSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_unix_non_stream.cc b/test/syscalls/linux/socket_unix_non_stream.cc
deleted file mode 100644
index dafe82494..000000000
--- a/test/syscalls/linux/socket_unix_non_stream.cc
+++ /dev/null
@@ -1,247 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/socket_unix_non_stream.h"
-
-#include <stdio.h>
-#include <sys/mman.h>
-#include <sys/un.h>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/memory_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-TEST_P(UnixNonStreamSocketPairTest, RecvMsgTooLarge) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- int rcvbuf;
- socklen_t length = sizeof(rcvbuf);
- ASSERT_THAT(
- getsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVBUF, &rcvbuf, &length),
- SyscallSucceeds());
-
- // Make the call larger than the receive buffer.
- const int recv_size = 3 * rcvbuf;
-
- // Write a message that does fit in the receive buffer.
- const int write_size = rcvbuf - kPageSize;
-
- std::vector<char> write_buf(write_size, 'a');
- const int ret = RetryEINTR(write)(sockets->second_fd(), write_buf.data(),
- write_buf.size());
- if (ret < 0 && errno == ENOBUFS) {
- // NOTE(b/116636318): Linux may stall the write for a long time and
- // ultimately return ENOBUFS. Allow this error, since a retry will likely
- // result in the same error.
- return;
- }
- ASSERT_THAT(ret, SyscallSucceeds());
-
- std::vector<char> recv_buf(recv_size);
-
- ASSERT_NO_FATAL_FAILURE(RecvNoCmsg(sockets->first_fd(), recv_buf.data(),
- recv_buf.size(), write_size));
-
- recv_buf.resize(write_size);
- EXPECT_EQ(recv_buf, write_buf);
-}
-
-// Create a region of anonymous memory of size 'size', which is fragmented in
-// FileMem.
-//
-// ptr contains the start address of the region. The returned vector contains
-// all of the mappings to be unmapped when done.
-PosixErrorOr<std::vector<Mapping>> CreateFragmentedRegion(const int size,
- void** ptr) {
- Mapping region;
- ASSIGN_OR_RETURN_ERRNO(region, Mmap(nullptr, size, PROT_NONE,
- MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
-
- *ptr = region.ptr();
-
- // Don't save hundreds of times for all of these mmaps.
- DisableSave ds;
-
- std::vector<Mapping> pages;
-
- // Map and commit a single page at a time, mapping and committing an unrelated
- // page between each call to force FileMem fragmentation.
- for (uintptr_t addr = region.addr(); addr < region.endaddr();
- addr += kPageSize) {
- Mapping page;
- ASSIGN_OR_RETURN_ERRNO(
- page,
- Mmap(reinterpret_cast<void*>(addr), kPageSize, PROT_READ | PROT_WRITE,
- MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0));
- *reinterpret_cast<volatile char*>(page.ptr()) = 42;
-
- pages.emplace_back(std::move(page));
-
- // Unrelated page elsewhere.
- ASSIGN_OR_RETURN_ERRNO(page,
- Mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE,
- MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
- *reinterpret_cast<volatile char*>(page.ptr()) = 42;
-
- pages.emplace_back(std::move(page));
- }
-
- // The mappings above have taken ownership of the region.
- region.release();
-
- return std::move(pages);
-}
-
-// A contiguous iov that is heavily fragmented in FileMem can still be sent
-// successfully.
-TEST_P(UnixNonStreamSocketPairTest, FragmentedSendMsg) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- const int buffer_size = UIO_MAXIOV * kPageSize;
- // Extra page for message header overhead.
- const int sndbuf = buffer_size + kPageSize;
- // N.B. setsockopt(SO_SNDBUF) doubles the passed value.
- const int set_sndbuf = sndbuf / 2;
-
- EXPECT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDBUF,
- &set_sndbuf, sizeof(set_sndbuf)),
- SyscallSucceeds());
-
- int actual_sndbuf = 0;
- socklen_t length = sizeof(actual_sndbuf);
- ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDBUF,
- &actual_sndbuf, &length),
- SyscallSucceeds());
-
- if (actual_sndbuf != sndbuf) {
- // Unable to get the sndbuf we want.
- //
- // N.B. At minimum, the socketpair gofer should provide a socket that is
- // already the correct size.
- //
- // TODO(b/35921550): When internal UDS support SO_SNDBUF, we can assert that
- // we always get the right SO_SNDBUF on gVisor.
- GTEST_SKIP() << "SO_SNDBUF = " << actual_sndbuf << ", want " << sndbuf;
- }
-
- // Create a contiguous region of memory of 2*UIO_MAXIOV*PAGE_SIZE. We'll call
- // sendmsg with a single iov, but the goal is to get the sentry to split this
- // into > UIO_MAXIOV iovs when calling the kernel.
- void* ptr;
- std::vector<Mapping> pages =
- ASSERT_NO_ERRNO_AND_VALUE(CreateFragmentedRegion(buffer_size, &ptr));
-
- struct iovec iov = {};
- iov.iov_base = ptr;
- iov.iov_len = buffer_size;
-
- struct msghdr msg = {};
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- // NOTE(b/116636318,b/115833655): Linux has poor behavior in the presence of
- // physical memory fragmentation. As a result, this may stall for a long time
- // and ultimately return ENOBUFS. Allow this error, since it means that we
- // made it to the host kernel and started the sendmsg.
- EXPECT_THAT(RetryEINTR(sendmsg)(sockets->first_fd(), &msg, 0),
- AnyOf(SyscallSucceedsWithValue(buffer_size),
- SyscallFailsWithErrno(ENOBUFS)));
-}
-
-// A contiguous iov that is heavily fragmented in FileMem can still be received
-// into successfully.
-TEST_P(UnixNonStreamSocketPairTest, FragmentedRecvMsg) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- const int buffer_size = UIO_MAXIOV * kPageSize;
- // Extra page for message header overhead.
- const int sndbuf = buffer_size + kPageSize;
- // N.B. setsockopt(SO_SNDBUF) doubles the passed value.
- const int set_sndbuf = sndbuf / 2;
-
- EXPECT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDBUF,
- &set_sndbuf, sizeof(set_sndbuf)),
- SyscallSucceeds());
-
- int actual_sndbuf = 0;
- socklen_t length = sizeof(actual_sndbuf);
- ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDBUF,
- &actual_sndbuf, &length),
- SyscallSucceeds());
-
- if (actual_sndbuf != sndbuf) {
- // Unable to get the sndbuf we want.
- //
- // N.B. At minimum, the socketpair gofer should provide a socket that is
- // already the correct size.
- //
- // TODO(b/35921550): When internal UDS support SO_SNDBUF, we can assert that
- // we always get the right SO_SNDBUF on gVisor.
- GTEST_SKIP() << "SO_SNDBUF = " << actual_sndbuf << ", want " << sndbuf;
- }
-
- std::vector<char> write_buf(buffer_size, 'a');
- const int ret = RetryEINTR(write)(sockets->first_fd(), write_buf.data(),
- write_buf.size());
- if (ret < 0 && errno == ENOBUFS) {
- // NOTE(b/116636318): Linux may stall the write for a long time and
- // ultimately return ENOBUFS. Allow this error, since a retry will likely
- // result in the same error.
- return;
- }
- ASSERT_THAT(ret, SyscallSucceeds());
-
- // Create a contiguous region of memory of 2*UIO_MAXIOV*PAGE_SIZE. We'll call
- // sendmsg with a single iov, but the goal is to get the sentry to split this
- // into > UIO_MAXIOV iovs when calling the kernel.
- void* ptr;
- std::vector<Mapping> pages =
- ASSERT_NO_ERRNO_AND_VALUE(CreateFragmentedRegion(buffer_size, &ptr));
-
- ASSERT_NO_FATAL_FAILURE(RecvNoCmsg(
- sockets->second_fd(), reinterpret_cast<char*>(ptr), buffer_size));
-
- EXPECT_EQ(0, memcmp(write_buf.data(), ptr, buffer_size));
-}
-
-TEST_P(UnixNonStreamSocketPairTest, SendTimeout) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct timeval tv {
- .tv_sec = 0, .tv_usec = 10
- };
- EXPECT_THAT(
- setsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)),
- SyscallSucceeds());
-
- char buf[100] = {};
- for (;;) {
- int ret;
- ASSERT_THAT(
- ret = RetryEINTR(send)(sockets->first_fd(), buf, sizeof(buf), 0),
- ::testing::AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(EAGAIN)));
- if (ret == -1) {
- break;
- }
- }
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_unix_non_stream.h b/test/syscalls/linux/socket_unix_non_stream.h
deleted file mode 100644
index 7478ab172..000000000
--- a/test/syscalls/linux/socket_unix_non_stream.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_NON_STREAM_H_
-#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_NON_STREAM_H_
-
-#include "test/syscalls/linux/socket_test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Test fixture for tests that apply to pairs of connected non-stream
-// unix-domain sockets.
-using UnixNonStreamSocketPairTest = SocketPairTest;
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_NON_STREAM_H_
diff --git a/test/syscalls/linux/socket_unix_non_stream_blocking_local.cc b/test/syscalls/linux/socket_unix_non_stream_blocking_local.cc
deleted file mode 100644
index da762cd83..000000000
--- a/test/syscalls/linux/socket_unix_non_stream_blocking_local.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/socket_non_stream_blocking.h"
-
-#include <vector>
-
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketPairKind> GetSocketPairs() {
- return VecCat<SocketPairKind>(
- ApplyVec<SocketPairKind>(UnixDomainSocketPair,
- std::vector<int>{SOCK_DGRAM, SOCK_SEQPACKET}),
- ApplyVec<SocketPairKind>(FilesystemBoundUnixDomainSocketPair,
- std::vector<int>{SOCK_DGRAM, SOCK_SEQPACKET}),
- ApplyVec<SocketPairKind>(AbstractBoundUnixDomainSocketPair,
- std::vector<int>{SOCK_DGRAM, SOCK_SEQPACKET}));
-}
-
-INSTANTIATE_TEST_SUITE_P(
- BlockingNonStreamUnixSockets, BlockingNonStreamSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_unix_pair.cc b/test/syscalls/linux/socket_unix_pair.cc
deleted file mode 100644
index 411fb4518..000000000
--- a/test/syscalls/linux/socket_unix_pair.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <vector>
-
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/socket_unix.h"
-#include "test/syscalls/linux/socket_unix_cmsg.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketPairKind> GetSocketPairs() {
- return VecCat<SocketPairKind>(ApplyVec<SocketPairKind>(
- UnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET},
- List<int>{0, SOCK_NONBLOCK})));
-}
-
-INSTANTIATE_TEST_SUITE_P(
- AllUnixDomainSockets, UnixSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-INSTANTIATE_TEST_SUITE_P(
- AllUnixDomainSockets, UnixSocketPairCmsgTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_unix_pair_nonblock.cc b/test/syscalls/linux/socket_unix_pair_nonblock.cc
deleted file mode 100644
index 3135d325f..000000000
--- a/test/syscalls/linux/socket_unix_pair_nonblock.cc
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <vector>
-
-#include "test/syscalls/linux/socket_non_blocking.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketPairKind> GetSocketPairs() {
- return ApplyVec<SocketPairKind>(
- UnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET},
- List<int>{SOCK_NONBLOCK}));
-}
-
-INSTANTIATE_TEST_SUITE_P(
- NonBlockingUnixSockets, NonBlockingSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_unix_seqpacket.cc b/test/syscalls/linux/socket_unix_seqpacket.cc
deleted file mode 100644
index 6f6367dd5..000000000
--- a/test/syscalls/linux/socket_unix_seqpacket.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/socket_unix_seqpacket.h"
-
-#include <stdio.h>
-#include <sys/un.h>
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST_P(SeqpacketUnixSocketPairTest, WriteOneSideClosed) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds());
- constexpr char kStr[] = "abc";
- ASSERT_THAT(write(sockets->second_fd(), kStr, 3),
- SyscallFailsWithErrno(EPIPE));
-}
-
-TEST_P(SeqpacketUnixSocketPairTest, ReadOneSideClosed) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds());
- char data[10] = {};
- ASSERT_THAT(read(sockets->second_fd(), data, sizeof(data)),
- SyscallSucceedsWithValue(0));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_unix_seqpacket.h b/test/syscalls/linux/socket_unix_seqpacket.h
deleted file mode 100644
index 30d9b9edf..000000000
--- a/test/syscalls/linux/socket_unix_seqpacket.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_SEQPACKET_H_
-#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_SEQPACKET_H_
-
-#include "test/syscalls/linux/socket_test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Test fixture for tests that apply to pairs of connected seqpacket unix
-// sockets.
-using SeqpacketUnixSocketPairTest = SocketPairTest;
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_SEQPACKET_H_
diff --git a/test/syscalls/linux/socket_unix_seqpacket_local.cc b/test/syscalls/linux/socket_unix_seqpacket_local.cc
deleted file mode 100644
index dff75a532..000000000
--- a/test/syscalls/linux/socket_unix_seqpacket_local.cc
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <vector>
-
-#include "test/syscalls/linux/socket_non_stream.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/socket_unix_non_stream.h"
-#include "test/syscalls/linux/socket_unix_seqpacket.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketPairKind> GetSocketPairs() {
- return VecCat<SocketPairKind>(VecCat<SocketPairKind>(
- ApplyVec<SocketPairKind>(
- UnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_SEQPACKET},
- List<int>{0, SOCK_NONBLOCK})),
- ApplyVec<SocketPairKind>(
- FilesystemBoundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_SEQPACKET},
- List<int>{0, SOCK_NONBLOCK})),
- ApplyVec<SocketPairKind>(
- AbstractBoundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_SEQPACKET},
- List<int>{0, SOCK_NONBLOCK}))));
-}
-
-INSTANTIATE_TEST_SUITE_P(
- SeqpacketUnixSockets, NonStreamSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-INSTANTIATE_TEST_SUITE_P(
- SeqpacketUnixSockets, SeqpacketUnixSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-INSTANTIATE_TEST_SUITE_P(
- SeqpacketUnixSockets, UnixNonStreamSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_unix_stream.cc b/test/syscalls/linux/socket_unix_stream.cc
deleted file mode 100644
index 659c93945..000000000
--- a/test/syscalls/linux/socket_unix_stream.cc
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <stdio.h>
-#include <sys/un.h>
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// Test fixture for tests that apply to pairs of connected stream unix sockets.
-using StreamUnixSocketPairTest = SocketPairTest;
-
-TEST_P(StreamUnixSocketPairTest, WriteOneSideClosed) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds());
- constexpr char kStr[] = "abc";
- ASSERT_THAT(write(sockets->second_fd(), kStr, 3),
- SyscallFailsWithErrno(EPIPE));
-}
-
-TEST_P(StreamUnixSocketPairTest, ReadOneSideClosed) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds());
- char data[10] = {};
- ASSERT_THAT(read(sockets->second_fd(), data, sizeof(data)),
- SyscallSucceedsWithValue(0));
-}
-
-INSTANTIATE_TEST_SUITE_P(
- AllUnixDomainSockets, StreamUnixSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(VecCat<SocketPairKind>(
- ApplyVec<SocketPairKind>(UnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM},
- List<int>{
- 0, SOCK_NONBLOCK})),
- ApplyVec<SocketPairKind>(FilesystemBoundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM},
- List<int>{
- 0, SOCK_NONBLOCK})),
- ApplyVec<SocketPairKind>(
- AbstractBoundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM},
- List<int>{0, SOCK_NONBLOCK}))))));
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_unix_stream_blocking_local.cc b/test/syscalls/linux/socket_unix_stream_blocking_local.cc
deleted file mode 100644
index fa0a9d367..000000000
--- a/test/syscalls/linux/socket_unix_stream_blocking_local.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/socket_stream_blocking.h"
-
-#include <vector>
-
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketPairKind> GetSocketPairs() {
- return {
- UnixDomainSocketPair(SOCK_STREAM),
- FilesystemBoundUnixDomainSocketPair(SOCK_STREAM),
- AbstractBoundUnixDomainSocketPair(SOCK_STREAM),
- };
-}
-
-INSTANTIATE_TEST_SUITE_P(
- BlockingStreamUnixSockets, BlockingStreamSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_unix_stream_local.cc b/test/syscalls/linux/socket_unix_stream_local.cc
deleted file mode 100644
index 65eef1a81..000000000
--- a/test/syscalls/linux/socket_unix_stream_local.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <vector>
-
-#include "test/syscalls/linux/socket_stream.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketPairKind> GetSocketPairs() {
- return VecCat<SocketPairKind>(
- ApplyVec<SocketPairKind>(
- UnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM},
- List<int>{0, SOCK_NONBLOCK})),
- ApplyVec<SocketPairKind>(
- FilesystemBoundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM},
- List<int>{0, SOCK_NONBLOCK})),
- ApplyVec<SocketPairKind>(
- AbstractBoundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM},
- List<int>{0, SOCK_NONBLOCK})));
-}
-
-INSTANTIATE_TEST_SUITE_P(
- StreamUnixSockets, StreamSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_unix_stream_nonblock_local.cc b/test/syscalls/linux/socket_unix_stream_nonblock_local.cc
deleted file mode 100644
index ec777c59f..000000000
--- a/test/syscalls/linux/socket_unix_stream_nonblock_local.cc
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-#include "test/syscalls/linux/socket_stream_nonblock.h"
-
-#include <vector>
-
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::vector<SocketPairKind> GetSocketPairs() {
- return {
- UnixDomainSocketPair(SOCK_STREAM | SOCK_NONBLOCK),
- FilesystemBoundUnixDomainSocketPair(SOCK_STREAM | SOCK_NONBLOCK),
- AbstractBoundUnixDomainSocketPair(SOCK_STREAM | SOCK_NONBLOCK),
- };
-}
-
-INSTANTIATE_TEST_SUITE_P(
- NonBlockingStreamUnixSockets, NonBlockingStreamSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_unix_unbound_abstract.cc b/test/syscalls/linux/socket_unix_unbound_abstract.cc
deleted file mode 100644
index 4b5832de8..000000000
--- a/test/syscalls/linux/socket_unix_unbound_abstract.cc
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <stdio.h>
-#include <sys/un.h>
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// Test fixture for tests that apply to pairs of unbound abstract unix sockets.
-using UnboundAbstractUnixSocketPairTest = SocketPairTest;
-
-TEST_P(UnboundAbstractUnixSocketPairTest, AddressAfterNull) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct sockaddr_un addr =
- *reinterpret_cast<const struct sockaddr_un*>(sockets->first_addr());
- ASSERT_EQ(addr.sun_path[sizeof(addr.sun_path) - 1], 0);
- SKIP_IF(addr.sun_path[sizeof(addr.sun_path) - 2] != 0 ||
- addr.sun_path[sizeof(addr.sun_path) - 3] != 0);
-
- addr.sun_path[sizeof(addr.sun_path) - 2] = 'a';
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(bind(sockets->second_fd(),
- reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)),
- SyscallSucceeds());
-}
-
-TEST_P(UnboundAbstractUnixSocketPairTest, ShortAddressNotExtended) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct sockaddr_un addr =
- *reinterpret_cast<const struct sockaddr_un*>(sockets->first_addr());
- ASSERT_EQ(addr.sun_path[sizeof(addr.sun_path) - 1], 0);
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size() - 1),
- SyscallSucceeds());
-
- ASSERT_THAT(bind(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-}
-
-TEST_P(UnboundAbstractUnixSocketPairTest, BindNothing) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- struct sockaddr_un addr = {.sun_family = AF_UNIX};
- ASSERT_THAT(bind(sockets->first_fd(),
- reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)),
- SyscallSucceeds());
-}
-
-TEST_P(UnboundAbstractUnixSocketPairTest, GetSockNameFullLength) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- sockaddr_storage addr = {};
- socklen_t addr_len = sizeof(addr);
- ASSERT_THAT(getsockname(sockets->first_fd(),
- reinterpret_cast<struct sockaddr*>(&addr), &addr_len),
- SyscallSucceeds());
- EXPECT_EQ(addr_len, sockets->first_addr_size());
-}
-
-TEST_P(UnboundAbstractUnixSocketPairTest, GetSockNamePartialLength) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size() - 1),
- SyscallSucceeds());
-
- sockaddr_storage addr = {};
- socklen_t addr_len = sizeof(addr);
- ASSERT_THAT(getsockname(sockets->first_fd(),
- reinterpret_cast<struct sockaddr*>(&addr), &addr_len),
- SyscallSucceeds());
- EXPECT_EQ(addr_len, sockets->first_addr_size() - 1);
-}
-
-INSTANTIATE_TEST_SUITE_P(
- AllUnixDomainSockets, UnboundAbstractUnixSocketPairTest,
- ::testing::ValuesIn(ApplyVec<SocketPairKind>(
- AbstractUnboundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_SEQPACKET,
- SOCK_DGRAM},
- List<int>{0, SOCK_NONBLOCK}))));
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_unix_unbound_dgram.cc b/test/syscalls/linux/socket_unix_unbound_dgram.cc
deleted file mode 100644
index 52aef891f..000000000
--- a/test/syscalls/linux/socket_unix_unbound_dgram.cc
+++ /dev/null
@@ -1,184 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <stdio.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// Test fixture for tests that apply to pairs of unbound dgram unix sockets.
-using UnboundDgramUnixSocketPairTest = SocketPairTest;
-
-TEST_P(UnboundDgramUnixSocketPairTest, BindConnect) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-}
-
-TEST_P(UnboundDgramUnixSocketPairTest, SelfConnect) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
- ASSERT_THAT(connect(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-}
-
-TEST_P(UnboundDgramUnixSocketPairTest, DoubleConnect) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-}
-
-TEST_P(UnboundDgramUnixSocketPairTest, GetRemoteAddress) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- socklen_t addressLength = sockets->first_addr_size();
- struct sockaddr_storage address = {};
- ASSERT_THAT(getpeername(sockets->second_fd(), (struct sockaddr*)(&address),
- &addressLength),
- SyscallSucceeds());
- EXPECT_EQ(
- 0, memcmp(&address, sockets->first_addr(), sockets->first_addr_size()));
-}
-
-TEST_P(UnboundDgramUnixSocketPairTest, Sendto) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- ASSERT_THAT(sendto(sockets->second_fd(), sent_data, sizeof(sent_data), 0,
- sockets->first_addr(), sockets->first_addr_size()),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- char received_data[sizeof(sent_data)];
- ASSERT_THAT(ReadFd(sockets->first_fd(), received_data, sizeof(received_data)),
- SyscallSucceedsWithValue(sizeof(received_data)));
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(received_data)));
-}
-
-TEST_P(UnboundDgramUnixSocketPairTest, ZeroWriteAllowed) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
- ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- char sent_data[3];
- // Send a zero length packet.
- ASSERT_THAT(write(sockets->second_fd(), sent_data, 0),
- SyscallSucceedsWithValue(0));
- // Receive the packet.
- char received_data[sizeof(sent_data)];
- ASSERT_THAT(read(sockets->first_fd(), received_data, sizeof(received_data)),
- SyscallSucceedsWithValue(0));
-}
-
-TEST_P(UnboundDgramUnixSocketPairTest, Listen) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- ASSERT_THAT(listen(sockets->first_fd(), 0), SyscallFailsWithErrno(ENOTSUP));
-}
-
-TEST_P(UnboundDgramUnixSocketPairTest, Accept) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- ASSERT_THAT(accept(sockets->first_fd(), nullptr, nullptr),
- SyscallFailsWithErrno(ENOTSUP));
-}
-
-TEST_P(UnboundDgramUnixSocketPairTest, SendtoWithoutConnect) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- char data = 'a';
- ASSERT_THAT(
- RetryEINTR(sendto)(sockets->second_fd(), &data, sizeof(data), 0,
- sockets->first_addr(), sockets->first_addr_size()),
- SyscallSucceedsWithValue(sizeof(data)));
-}
-
-TEST_P(UnboundDgramUnixSocketPairTest, SendtoWithoutConnectPassCreds) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- SetSoPassCred(sockets->first_fd());
- char data = 'a';
- ASSERT_THAT(
- RetryEINTR(sendto)(sockets->second_fd(), &data, sizeof(data), 0,
- sockets->first_addr(), sockets->first_addr_size()),
- SyscallSucceedsWithValue(sizeof(data)));
- ucred creds;
- creds.pid = -1;
- char buf[sizeof(data) + 1];
- ASSERT_NO_FATAL_FAILURE(
- RecvCreds(sockets->first_fd(), &creds, buf, sizeof(buf), sizeof(data)));
- EXPECT_EQ(0, memcmp(&data, buf, sizeof(data)));
- EXPECT_THAT(getpid(), SyscallSucceedsWithValue(creds.pid));
-}
-
-INSTANTIATE_TEST_SUITE_P(
- AllUnixDomainSockets, UnboundDgramUnixSocketPairTest,
- ::testing::ValuesIn(VecCat<SocketPairKind>(
- ApplyVec<SocketPairKind>(FilesystemUnboundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_DGRAM},
- List<int>{
- 0, SOCK_NONBLOCK})),
- ApplyVec<SocketPairKind>(
- AbstractUnboundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_DGRAM},
- List<int>{0, SOCK_NONBLOCK})))));
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_unix_unbound_filesystem.cc b/test/syscalls/linux/socket_unix_unbound_filesystem.cc
deleted file mode 100644
index 8cb03c450..000000000
--- a/test/syscalls/linux/socket_unix_unbound_filesystem.cc
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <stdio.h>
-#include <sys/un.h>
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// Test fixture for tests that apply to pairs of unbound filesystem unix
-// sockets.
-using UnboundFilesystemUnixSocketPairTest = SocketPairTest;
-
-TEST_P(UnboundFilesystemUnixSocketPairTest, AddressAfterNull) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- struct sockaddr_un addr =
- *reinterpret_cast<const struct sockaddr_un*>(sockets->first_addr());
- ASSERT_EQ(addr.sun_path[sizeof(addr.sun_path) - 1], 0);
- SKIP_IF(addr.sun_path[sizeof(addr.sun_path) - 2] != 0 ||
- addr.sun_path[sizeof(addr.sun_path) - 3] != 0);
-
- addr.sun_path[sizeof(addr.sun_path) - 2] = 'a';
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- ASSERT_THAT(bind(sockets->second_fd(),
- reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)),
- SyscallFailsWithErrno(EADDRINUSE));
-}
-
-TEST_P(UnboundFilesystemUnixSocketPairTest, GetSockNameLength) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- sockaddr_storage got_addr = {};
- socklen_t got_addr_len = sizeof(got_addr);
- ASSERT_THAT(
- getsockname(sockets->first_fd(),
- reinterpret_cast<struct sockaddr*>(&got_addr), &got_addr_len),
- SyscallSucceeds());
-
- sockaddr_un want_addr =
- *reinterpret_cast<const struct sockaddr_un*>(sockets->first_addr());
-
- EXPECT_EQ(got_addr_len,
- strlen(want_addr.sun_path) + 1 + sizeof(want_addr.sun_family));
-}
-
-INSTANTIATE_TEST_SUITE_P(
- AllUnixDomainSockets, UnboundFilesystemUnixSocketPairTest,
- ::testing::ValuesIn(ApplyVec<SocketPairKind>(
- FilesystemUnboundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_SEQPACKET,
- SOCK_DGRAM},
- List<int>{0, SOCK_NONBLOCK}))));
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_unix_unbound_seqpacket.cc b/test/syscalls/linux/socket_unix_unbound_seqpacket.cc
deleted file mode 100644
index 0575f2e1d..000000000
--- a/test/syscalls/linux/socket_unix_unbound_seqpacket.cc
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <stdio.h>
-#include <sys/un.h>
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// Test fixture for tests that apply to pairs of unbound seqpacket unix sockets.
-using UnboundUnixSeqpacketSocketPairTest = SocketPairTest;
-
-TEST_P(UnboundUnixSeqpacketSocketPairTest, SendtoWithoutConnect) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- char data = 'a';
- ASSERT_THAT(sendto(sockets->second_fd(), &data, sizeof(data), 0,
- sockets->first_addr(), sockets->first_addr_size()),
- SyscallFailsWithErrno(ENOTCONN));
-}
-
-TEST_P(UnboundUnixSeqpacketSocketPairTest, SendtoWithoutConnectIgnoresAddr) {
- // FIXME(b/68223466): gVisor tries to find /foo/bar and thus returns ENOENT.
- if (IsRunningOnGvisor()) {
- return;
- }
-
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- // Even a bogus address is completely ignored.
- constexpr char kPath[] = "/foo/bar";
-
- // Sanity check that kPath doesn't exist.
- struct stat s;
- ASSERT_THAT(stat(kPath, &s), SyscallFailsWithErrno(ENOENT));
-
- struct sockaddr_un addr = {};
- addr.sun_family = AF_UNIX;
- memcpy(addr.sun_path, kPath, sizeof(kPath));
-
- char data = 'a';
- ASSERT_THAT(
- sendto(sockets->second_fd(), &data, sizeof(data), 0,
- reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)),
- SyscallFailsWithErrno(ENOTCONN));
-}
-
-INSTANTIATE_TEST_SUITE_P(
- AllUnixDomainSockets, UnboundUnixSeqpacketSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(VecCat<SocketPairKind>(
- ApplyVec<SocketPairKind>(
- FilesystemUnboundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_SEQPACKET},
- List<int>{0, SOCK_NONBLOCK})),
- ApplyVec<SocketPairKind>(
- AbstractUnboundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_SEQPACKET},
- List<int>{0, SOCK_NONBLOCK}))))));
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_unix_unbound_stream.cc b/test/syscalls/linux/socket_unix_unbound_stream.cc
deleted file mode 100644
index e483d2777..000000000
--- a/test/syscalls/linux/socket_unix_unbound_stream.cc
+++ /dev/null
@@ -1,733 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <stdio.h>
-#include <sys/un.h>
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// Test fixture for tests that apply to pairs of connected unix stream sockets.
-using UnixStreamSocketPairTest = SocketPairTest;
-
-// FDPassPartialRead checks that sent control messages cannot be read after
-// any of their associated data has been read while ignoring the control message
-// by using read(2) instead of recvmsg(2).
-TEST_P(UnixStreamSocketPairTest, FDPassPartialRead) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(),
- sent_data, sizeof(sent_data)));
-
- char received_data[sizeof(sent_data) / 2];
- ASSERT_THAT(
- ReadFd(sockets->second_fd(), received_data, sizeof(received_data)),
- SyscallSucceedsWithValue(sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(received_data)));
-
- RecvNoCmsg(sockets->second_fd(), received_data, sizeof(received_data));
- EXPECT_EQ(0, memcmp(sent_data + sizeof(received_data), received_data,
- sizeof(received_data)));
-}
-
-TEST_P(UnixStreamSocketPairTest, FDPassCoalescedRead) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data1[20];
- RandomizeBuffer(sent_data1, sizeof(sent_data1));
-
- auto pair1 =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair1->second_fd(),
- sent_data1, sizeof(sent_data1)));
-
- char sent_data2[20];
- RandomizeBuffer(sent_data2, sizeof(sent_data2));
-
- auto pair2 =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair2->second_fd(),
- sent_data2, sizeof(sent_data2)));
-
- char received_data[sizeof(sent_data1) + sizeof(sent_data2)];
- ASSERT_THAT(
- ReadFd(sockets->second_fd(), received_data, sizeof(received_data)),
- SyscallSucceedsWithValue(sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data1, received_data, sizeof(sent_data1)));
- EXPECT_EQ(0, memcmp(sent_data2, received_data + sizeof(sent_data1),
- sizeof(sent_data2)));
-}
-
-// ZeroLengthMessageFDDiscarded checks that control messages associated with
-// zero length messages are discarded.
-TEST_P(UnixStreamSocketPairTest, ZeroLengthMessageFDDiscarded) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- // Zero length arrays are invalid in ISO C++, so allocate one of size 1 and
- // send a length of 0.
- char sent_data1[1] = {};
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(
- SendSingleFD(sockets->first_fd(), pair->second_fd(), sent_data1, 0));
-
- char sent_data2[20];
- RandomizeBuffer(sent_data2, sizeof(sent_data2));
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data2, sizeof(sent_data2)),
- SyscallSucceedsWithValue(sizeof(sent_data2)));
-
- char received_data[sizeof(sent_data2)] = {};
-
- RecvNoCmsg(sockets->second_fd(), received_data, sizeof(received_data));
- EXPECT_EQ(0, memcmp(sent_data2, received_data, sizeof(received_data)));
-}
-
-// FDPassCoalescedRecv checks that control messages not in the first message are
-// preserved in a coalesced recv.
-TEST_P(UnixStreamSocketPairTest, FDPassCoalescedRecv) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data, sizeof(sent_data) / 2),
- SyscallSucceedsWithValue(sizeof(sent_data) / 2));
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(),
- sent_data + sizeof(sent_data) / 2,
- sizeof(sent_data) / 2));
-
- char received_data[sizeof(sent_data)];
-
- int fd = -1;
- ASSERT_NO_FATAL_FAILURE(RecvSingleFD(sockets->second_fd(), &fd, received_data,
- sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-
- ASSERT_NO_FATAL_FAILURE(TransferTest(fd, pair->first_fd()));
-}
-
-// ReadsNotCoalescedAfterFDPass checks that messages after a message containing
-// an FD control message are not coalesced.
-TEST_P(UnixStreamSocketPairTest, ReadsNotCoalescedAfterFDPass) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(),
- sent_data, sizeof(sent_data) / 2));
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data + sizeof(sent_data) / 2,
- sizeof(sent_data) / 2),
- SyscallSucceedsWithValue(sizeof(sent_data) / 2));
-
- char received_data[sizeof(sent_data)];
-
- int fd = -1;
- ASSERT_NO_FATAL_FAILURE(RecvSingleFD(sockets->second_fd(), &fd, received_data,
- sizeof(received_data),
- sizeof(sent_data) / 2));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data) / 2));
-
- ASSERT_NO_FATAL_FAILURE(TransferTest(fd, pair->first_fd()));
- EXPECT_THAT(close(fd), SyscallSucceeds());
-
- ASSERT_NO_FATAL_FAILURE(
- RecvNoCmsg(sockets->second_fd(), received_data, sizeof(sent_data) / 2));
-
- EXPECT_EQ(0, memcmp(sent_data + sizeof(sent_data) / 2, received_data,
- sizeof(sent_data) / 2));
-}
-
-// FDPassNotCombined checks that FD control messages are not combined in a
-// coalesced read.
-TEST_P(UnixStreamSocketPairTest, FDPassNotCombined) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- auto pair1 =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair1->second_fd(),
- sent_data, sizeof(sent_data) / 2));
-
- auto pair2 =
- ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create());
-
- ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair2->second_fd(),
- sent_data + sizeof(sent_data) / 2,
- sizeof(sent_data) / 2));
-
- char received_data[sizeof(sent_data)];
-
- int fd = -1;
- ASSERT_NO_FATAL_FAILURE(RecvSingleFD(sockets->second_fd(), &fd, received_data,
- sizeof(received_data),
- sizeof(sent_data) / 2));
-
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data) / 2));
-
- ASSERT_NO_FATAL_FAILURE(TransferTest(fd, pair1->first_fd()));
-
- EXPECT_THAT(close(fd), SyscallSucceeds());
- fd = -1;
-
- ASSERT_NO_FATAL_FAILURE(RecvSingleFD(sockets->second_fd(), &fd, received_data,
- sizeof(received_data),
- sizeof(sent_data) / 2));
-
- EXPECT_EQ(0, memcmp(sent_data + sizeof(sent_data) / 2, received_data,
- sizeof(sent_data) / 2));
-
- ASSERT_NO_FATAL_FAILURE(TransferTest(fd, pair2->first_fd()));
- EXPECT_THAT(close(fd), SyscallSucceeds());
-}
-
-TEST_P(UnixStreamSocketPairTest, CredPassPartialRead) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data[20];
- RandomizeBuffer(sent_data, sizeof(sent_data));
-
- struct ucred sent_creds;
-
- ASSERT_THAT(sent_creds.pid = getpid(), SyscallSucceeds());
- ASSERT_THAT(sent_creds.uid = getuid(), SyscallSucceeds());
- ASSERT_THAT(sent_creds.gid = getgid(), SyscallSucceeds());
-
- ASSERT_NO_FATAL_FAILURE(
- SendCreds(sockets->first_fd(), sent_creds, sent_data, sizeof(sent_data)));
-
- int one = 1;
- ASSERT_THAT(setsockopt(sockets->second_fd(), SOL_SOCKET, SO_PASSCRED, &one,
- sizeof(one)),
- SyscallSucceeds());
-
- for (int i = 0; i < 2; i++) {
- char received_data[10];
- struct ucred received_creds;
- ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds,
- received_data, sizeof(received_data),
- sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data + i * sizeof(received_data), received_data,
- sizeof(received_data)));
- EXPECT_EQ(sent_creds.pid, received_creds.pid);
- EXPECT_EQ(sent_creds.uid, received_creds.uid);
- EXPECT_EQ(sent_creds.gid, received_creds.gid);
- }
-}
-
-// Unix stream sockets peek in the same way as datagram sockets.
-//
-// SinglePeek checks that only a single message is peekable in a single recv.
-TEST_P(UnixStreamSocketPairTest, SinglePeek) {
- if (!IsRunningOnGvisor()) {
- // Don't run this test on linux kernels newer than 4.3.x Linux kernel commit
- // 9f389e35674f5b086edd70ed524ca0f287259725 which changes this behavior. We
- // used to target 3.11 compatibility, so disable this test on newer kernels.
- //
- // NOTE(b/118902768): Bring this up to Linux 4.4 compatibility.
- auto version = ASSERT_NO_ERRNO_AND_VALUE(GetKernelVersion());
- SKIP_IF(version.major > 4 || (version.major == 4 && version.minor >= 3));
- }
-
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- char sent_data[40];
- RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(RetryEINTR(send)(sockets->first_fd(), sent_data,
- sizeof(sent_data) / 2, 0),
- SyscallSucceedsWithValue(sizeof(sent_data) / 2));
- ASSERT_THAT(
- RetryEINTR(send)(sockets->first_fd(), sent_data + sizeof(sent_data) / 2,
- sizeof(sent_data) / 2, 0),
- SyscallSucceedsWithValue(sizeof(sent_data) / 2));
- char received_data[sizeof(sent_data)];
- for (int i = 0; i < 3; i++) {
- memset(received_data, 0, sizeof(received_data));
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(received_data), MSG_PEEK),
- SyscallSucceedsWithValue(sizeof(sent_data) / 2));
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data) / 2));
- }
- memset(received_data, 0, sizeof(received_data));
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(sent_data) / 2, 0),
- SyscallSucceedsWithValue(sizeof(sent_data) / 2));
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data) / 2));
- memset(received_data, 0, sizeof(received_data));
- ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data,
- sizeof(sent_data) / 2, 0),
- SyscallSucceedsWithValue(sizeof(sent_data) / 2));
- EXPECT_EQ(0, memcmp(sent_data + sizeof(sent_data) / 2, received_data,
- sizeof(sent_data) / 2));
-}
-
-TEST_P(UnixStreamSocketPairTest, CredsNotCoalescedUp) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data1[20];
- RandomizeBuffer(sent_data1, sizeof(sent_data1));
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data1, sizeof(sent_data1)),
- SyscallSucceedsWithValue(sizeof(sent_data1)));
-
- SetSoPassCred(sockets->second_fd());
-
- char sent_data2[20];
- RandomizeBuffer(sent_data2, sizeof(sent_data2));
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data2, sizeof(sent_data2)),
- SyscallSucceedsWithValue(sizeof(sent_data2)));
-
- char received_data[sizeof(sent_data1) + sizeof(sent_data2)];
-
- struct ucred received_creds;
- ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds,
- received_data, sizeof(received_data),
- sizeof(sent_data1)));
-
- EXPECT_EQ(0, memcmp(sent_data1, received_data, sizeof(sent_data1)));
-
- struct ucred want_creds {
- 0, 65534, 65534
- };
-
- EXPECT_EQ(want_creds.pid, received_creds.pid);
- EXPECT_EQ(want_creds.uid, received_creds.uid);
- EXPECT_EQ(want_creds.gid, received_creds.gid);
-
- ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds,
- received_data, sizeof(received_data),
- sizeof(sent_data2)));
-
- EXPECT_EQ(0, memcmp(sent_data2, received_data, sizeof(sent_data2)));
-
- ASSERT_THAT(want_creds.pid = getpid(), SyscallSucceeds());
- ASSERT_THAT(want_creds.uid = getuid(), SyscallSucceeds());
- ASSERT_THAT(want_creds.gid = getgid(), SyscallSucceeds());
-
- EXPECT_EQ(want_creds.pid, received_creds.pid);
- EXPECT_EQ(want_creds.uid, received_creds.uid);
- EXPECT_EQ(want_creds.gid, received_creds.gid);
-}
-
-TEST_P(UnixStreamSocketPairTest, CredsNotCoalescedDown) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- SetSoPassCred(sockets->second_fd());
-
- char sent_data1[20];
- RandomizeBuffer(sent_data1, sizeof(sent_data1));
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data1, sizeof(sent_data1)),
- SyscallSucceedsWithValue(sizeof(sent_data1)));
-
- UnsetSoPassCred(sockets->second_fd());
-
- char sent_data2[20];
- RandomizeBuffer(sent_data2, sizeof(sent_data2));
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data2, sizeof(sent_data2)),
- SyscallSucceedsWithValue(sizeof(sent_data2)));
-
- SetSoPassCred(sockets->second_fd());
-
- char received_data[sizeof(sent_data1) + sizeof(sent_data2)];
- struct ucred received_creds;
-
- ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds,
- received_data, sizeof(received_data),
- sizeof(sent_data1)));
-
- EXPECT_EQ(0, memcmp(sent_data1, received_data, sizeof(sent_data1)));
-
- struct ucred want_creds;
- ASSERT_THAT(want_creds.pid = getpid(), SyscallSucceeds());
- ASSERT_THAT(want_creds.uid = getuid(), SyscallSucceeds());
- ASSERT_THAT(want_creds.gid = getgid(), SyscallSucceeds());
-
- EXPECT_EQ(want_creds.pid, received_creds.pid);
- EXPECT_EQ(want_creds.uid, received_creds.uid);
- EXPECT_EQ(want_creds.gid, received_creds.gid);
-
- ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds,
- received_data, sizeof(received_data),
- sizeof(sent_data2)));
-
- EXPECT_EQ(0, memcmp(sent_data2, received_data, sizeof(sent_data2)));
-
- want_creds = {0, 65534, 65534};
-
- EXPECT_EQ(want_creds.pid, received_creds.pid);
- EXPECT_EQ(want_creds.uid, received_creds.uid);
- EXPECT_EQ(want_creds.gid, received_creds.gid);
-}
-
-TEST_P(UnixStreamSocketPairTest, CoalescedCredsNoPasscred) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- SetSoPassCred(sockets->second_fd());
-
- char sent_data1[20];
- RandomizeBuffer(sent_data1, sizeof(sent_data1));
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data1, sizeof(sent_data1)),
- SyscallSucceedsWithValue(sizeof(sent_data1)));
-
- UnsetSoPassCred(sockets->second_fd());
-
- char sent_data2[20];
- RandomizeBuffer(sent_data2, sizeof(sent_data2));
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data2, sizeof(sent_data2)),
- SyscallSucceedsWithValue(sizeof(sent_data2)));
-
- char received_data[sizeof(sent_data1) + sizeof(sent_data2)];
-
- ASSERT_NO_FATAL_FAILURE(
- RecvNoCmsg(sockets->second_fd(), received_data, sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data1, received_data, sizeof(sent_data1)));
- EXPECT_EQ(0, memcmp(sent_data2, received_data + sizeof(sent_data1),
- sizeof(sent_data2)));
-}
-
-TEST_P(UnixStreamSocketPairTest, CoalescedCreds1) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data1[20];
- RandomizeBuffer(sent_data1, sizeof(sent_data1));
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data1, sizeof(sent_data1)),
- SyscallSucceedsWithValue(sizeof(sent_data1)));
-
- char sent_data2[20];
- RandomizeBuffer(sent_data2, sizeof(sent_data2));
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data2, sizeof(sent_data2)),
- SyscallSucceedsWithValue(sizeof(sent_data2)));
-
- SetSoPassCred(sockets->second_fd());
-
- char received_data[sizeof(sent_data1) + sizeof(sent_data2)];
- struct ucred received_creds;
-
- ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds,
- received_data, sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data1, received_data, sizeof(sent_data1)));
- EXPECT_EQ(0, memcmp(sent_data2, received_data + sizeof(sent_data1),
- sizeof(sent_data2)));
-
- struct ucred want_creds {
- 0, 65534, 65534
- };
-
- EXPECT_EQ(want_creds.pid, received_creds.pid);
- EXPECT_EQ(want_creds.uid, received_creds.uid);
- EXPECT_EQ(want_creds.gid, received_creds.gid);
-}
-
-TEST_P(UnixStreamSocketPairTest, CoalescedCreds2) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- SetSoPassCred(sockets->second_fd());
-
- char sent_data1[20];
- RandomizeBuffer(sent_data1, sizeof(sent_data1));
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data1, sizeof(sent_data1)),
- SyscallSucceedsWithValue(sizeof(sent_data1)));
-
- char sent_data2[20];
- RandomizeBuffer(sent_data2, sizeof(sent_data2));
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data2, sizeof(sent_data2)),
- SyscallSucceedsWithValue(sizeof(sent_data2)));
-
- char received_data[sizeof(sent_data1) + sizeof(sent_data2)];
- struct ucred received_creds;
-
- ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds,
- received_data, sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data1, received_data, sizeof(sent_data1)));
- EXPECT_EQ(0, memcmp(sent_data2, received_data + sizeof(sent_data1),
- sizeof(sent_data2)));
-
- struct ucred want_creds;
- ASSERT_THAT(want_creds.pid = getpid(), SyscallSucceeds());
- ASSERT_THAT(want_creds.uid = getuid(), SyscallSucceeds());
- ASSERT_THAT(want_creds.gid = getgid(), SyscallSucceeds());
-
- EXPECT_EQ(want_creds.pid, received_creds.pid);
- EXPECT_EQ(want_creds.uid, received_creds.uid);
- EXPECT_EQ(want_creds.gid, received_creds.gid);
-}
-
-TEST_P(UnixStreamSocketPairTest, NonCoalescedDifferingCreds1) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- char sent_data1[20];
- RandomizeBuffer(sent_data1, sizeof(sent_data1));
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data1, sizeof(sent_data1)),
- SyscallSucceedsWithValue(sizeof(sent_data1)));
-
- SetSoPassCred(sockets->second_fd());
-
- char sent_data2[20];
- RandomizeBuffer(sent_data2, sizeof(sent_data2));
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data2, sizeof(sent_data2)),
- SyscallSucceedsWithValue(sizeof(sent_data2)));
-
- char received_data1[sizeof(sent_data1) + sizeof(sent_data2)];
- struct ucred received_creds1;
-
- ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds1,
- received_data1, sizeof(sent_data1)));
-
- EXPECT_EQ(0, memcmp(sent_data1, received_data1, sizeof(sent_data1)));
-
- struct ucred want_creds1 {
- 0, 65534, 65534
- };
-
- EXPECT_EQ(want_creds1.pid, received_creds1.pid);
- EXPECT_EQ(want_creds1.uid, received_creds1.uid);
- EXPECT_EQ(want_creds1.gid, received_creds1.gid);
-
- char received_data2[sizeof(sent_data1) + sizeof(sent_data2)];
- struct ucred received_creds2;
-
- ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds2,
- received_data2, sizeof(sent_data2)));
-
- EXPECT_EQ(0, memcmp(sent_data2, received_data2, sizeof(sent_data2)));
-
- struct ucred want_creds2;
- ASSERT_THAT(want_creds2.pid = getpid(), SyscallSucceeds());
- ASSERT_THAT(want_creds2.uid = getuid(), SyscallSucceeds());
- ASSERT_THAT(want_creds2.gid = getgid(), SyscallSucceeds());
-
- EXPECT_EQ(want_creds2.pid, received_creds2.pid);
- EXPECT_EQ(want_creds2.uid, received_creds2.uid);
- EXPECT_EQ(want_creds2.gid, received_creds2.gid);
-}
-
-TEST_P(UnixStreamSocketPairTest, NonCoalescedDifferingCreds2) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- SetSoPassCred(sockets->second_fd());
-
- char sent_data1[20];
- RandomizeBuffer(sent_data1, sizeof(sent_data1));
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data1, sizeof(sent_data1)),
- SyscallSucceedsWithValue(sizeof(sent_data1)));
-
- UnsetSoPassCred(sockets->second_fd());
-
- char sent_data2[20];
- RandomizeBuffer(sent_data2, sizeof(sent_data2));
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data2, sizeof(sent_data2)),
- SyscallSucceedsWithValue(sizeof(sent_data2)));
-
- SetSoPassCred(sockets->second_fd());
-
- char received_data1[sizeof(sent_data1) + sizeof(sent_data2)];
- struct ucred received_creds1;
-
- ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds1,
- received_data1, sizeof(sent_data1)));
-
- EXPECT_EQ(0, memcmp(sent_data1, received_data1, sizeof(sent_data1)));
-
- struct ucred want_creds1;
- ASSERT_THAT(want_creds1.pid = getpid(), SyscallSucceeds());
- ASSERT_THAT(want_creds1.uid = getuid(), SyscallSucceeds());
- ASSERT_THAT(want_creds1.gid = getgid(), SyscallSucceeds());
-
- EXPECT_EQ(want_creds1.pid, received_creds1.pid);
- EXPECT_EQ(want_creds1.uid, received_creds1.uid);
- EXPECT_EQ(want_creds1.gid, received_creds1.gid);
-
- char received_data2[sizeof(sent_data1) + sizeof(sent_data2)];
- struct ucred received_creds2;
-
- ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds2,
- received_data2, sizeof(sent_data2)));
-
- EXPECT_EQ(0, memcmp(sent_data2, received_data2, sizeof(sent_data2)));
-
- struct ucred want_creds2 {
- 0, 65534, 65534
- };
-
- EXPECT_EQ(want_creds2.pid, received_creds2.pid);
- EXPECT_EQ(want_creds2.uid, received_creds2.uid);
- EXPECT_EQ(want_creds2.gid, received_creds2.gid);
-}
-
-TEST_P(UnixStreamSocketPairTest, CoalescedDifferingCreds) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- SetSoPassCred(sockets->second_fd());
-
- char sent_data1[20];
- RandomizeBuffer(sent_data1, sizeof(sent_data1));
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data1, sizeof(sent_data1)),
- SyscallSucceedsWithValue(sizeof(sent_data1)));
-
- char sent_data2[20];
- RandomizeBuffer(sent_data2, sizeof(sent_data2));
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data2, sizeof(sent_data2)),
- SyscallSucceedsWithValue(sizeof(sent_data2)));
-
- UnsetSoPassCred(sockets->second_fd());
-
- char sent_data3[20];
- RandomizeBuffer(sent_data3, sizeof(sent_data3));
-
- ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data3, sizeof(sent_data3)),
- SyscallSucceedsWithValue(sizeof(sent_data3)));
-
- char received_data[sizeof(sent_data1) + sizeof(sent_data2) +
- sizeof(sent_data3)];
-
- ASSERT_NO_FATAL_FAILURE(
- RecvNoCmsg(sockets->second_fd(), received_data, sizeof(received_data)));
-
- EXPECT_EQ(0, memcmp(sent_data1, received_data, sizeof(sent_data1)));
- EXPECT_EQ(0, memcmp(sent_data2, received_data + sizeof(sent_data1),
- sizeof(sent_data2)));
- EXPECT_EQ(0, memcmp(sent_data3,
- received_data + sizeof(sent_data1) + sizeof(sent_data2),
- sizeof(sent_data3)));
-}
-
-INSTANTIATE_TEST_SUITE_P(
- AllUnixDomainSockets, UnixStreamSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(VecCat<SocketPairKind>(
- ApplyVec<SocketPairKind>(UnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM},
- List<int>{
- 0, SOCK_NONBLOCK})),
- ApplyVec<SocketPairKind>(FilesystemBoundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM},
- List<int>{
- 0, SOCK_NONBLOCK})),
- ApplyVec<SocketPairKind>(
- AbstractBoundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM},
- List<int>{0, SOCK_NONBLOCK}))))));
-
-// Test fixture for tests that apply to pairs of unbound unix stream sockets.
-using UnboundUnixStreamSocketPairTest = SocketPairTest;
-
-TEST_P(UnboundUnixStreamSocketPairTest, SendtoWithoutConnect) {
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- char data = 'a';
- ASSERT_THAT(sendto(sockets->second_fd(), &data, sizeof(data), 0,
- sockets->first_addr(), sockets->first_addr_size()),
- SyscallFailsWithErrno(EOPNOTSUPP));
-}
-
-TEST_P(UnboundUnixStreamSocketPairTest, SendtoWithoutConnectIgnoresAddr) {
- // FIXME(b/68223466): gVisor tries to find /foo/bar and thus returns ENOENT.
- if (IsRunningOnGvisor()) {
- return;
- }
-
- auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
-
- ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
- sockets->first_addr_size()),
- SyscallSucceeds());
-
- // Even a bogus address is completely ignored.
- constexpr char kPath[] = "/foo/bar";
-
- // Sanity check that kPath doesn't exist.
- struct stat s;
- ASSERT_THAT(stat(kPath, &s), SyscallFailsWithErrno(ENOENT));
-
- struct sockaddr_un addr = {};
- addr.sun_family = AF_UNIX;
- memcpy(addr.sun_path, kPath, sizeof(kPath));
-
- char data = 'a';
- ASSERT_THAT(
- sendto(sockets->second_fd(), &data, sizeof(data), 0,
- reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)),
- SyscallFailsWithErrno(EOPNOTSUPP));
-}
-
-INSTANTIATE_TEST_SUITE_P(
- AllUnixDomainSockets, UnboundUnixStreamSocketPairTest,
- ::testing::ValuesIn(IncludeReversals(VecCat<SocketPairKind>(
- ApplyVec<SocketPairKind>(FilesystemUnboundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM},
- List<int>{
- 0, SOCK_NONBLOCK})),
- ApplyVec<SocketPairKind>(
- AbstractUnboundUnixDomainSocketPair,
- AllBitwiseCombinations(List<int>{SOCK_STREAM},
- List<int>{0, SOCK_NONBLOCK}))))));
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/splice.cc b/test/syscalls/linux/splice.cc
deleted file mode 100644
index 85232cb1f..000000000
--- a/test/syscalls/linux/splice.cc
+++ /dev/null
@@ -1,593 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <sys/eventfd.h>
-#include <sys/resource.h>
-#include <sys/sendfile.h>
-#include <sys/time.h>
-#include <unistd.h>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/strings/string_view.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(SpliceTest, TwoRegularFiles) {
- // Create temp files.
- const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- // Open the input file as read only.
- const FileDescriptor in_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY));
-
- // Open the output file as write only.
- const FileDescriptor out_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY));
-
- // Verify that it is rejected as expected; regardless of offsets.
- loff_t in_offset = 0;
- loff_t out_offset = 0;
- EXPECT_THAT(splice(in_fd.get(), &in_offset, out_fd.get(), &out_offset, 1, 0),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(splice(in_fd.get(), nullptr, out_fd.get(), &out_offset, 1, 0),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(splice(in_fd.get(), &in_offset, out_fd.get(), nullptr, 1, 0),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(splice(in_fd.get(), nullptr, out_fd.get(), nullptr, 1, 0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(SpliceTest, SamePipe) {
- // Create a new pipe.
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
- const FileDescriptor rfd(fds[0]);
- const FileDescriptor wfd(fds[1]);
-
- // Fill the pipe.
- std::vector<char> buf(kPageSize);
- RandomizeBuffer(buf.data(), buf.size());
- ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(kPageSize));
-
- // Attempt to splice to itself.
- EXPECT_THAT(splice(rfd.get(), nullptr, wfd.get(), nullptr, kPageSize, 0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(TeeTest, SamePipe) {
- // Create a new pipe.
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
- const FileDescriptor rfd(fds[0]);
- const FileDescriptor wfd(fds[1]);
-
- // Fill the pipe.
- std::vector<char> buf(kPageSize);
- RandomizeBuffer(buf.data(), buf.size());
- ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(kPageSize));
-
- // Attempt to tee to itself.
- EXPECT_THAT(tee(rfd.get(), wfd.get(), kPageSize, 0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(TeeTest, RegularFile) {
- // Open some file.
- const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const FileDescriptor in_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDWR));
-
- // Create a new pipe.
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
- const FileDescriptor rfd(fds[0]);
- const FileDescriptor wfd(fds[1]);
-
- // Attempt to tee from the file.
- EXPECT_THAT(tee(in_fd.get(), wfd.get(), kPageSize, 0),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(tee(rfd.get(), in_fd.get(), kPageSize, 0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(SpliceTest, PipeOffsets) {
- // Create two new pipes.
- int first[2], second[2];
- ASSERT_THAT(pipe(first), SyscallSucceeds());
- const FileDescriptor rfd1(first[0]);
- const FileDescriptor wfd1(first[1]);
- ASSERT_THAT(pipe(second), SyscallSucceeds());
- const FileDescriptor rfd2(second[0]);
- const FileDescriptor wfd2(second[1]);
-
- // All pipe offsets should be rejected.
- loff_t in_offset = 0;
- loff_t out_offset = 0;
- EXPECT_THAT(splice(rfd1.get(), &in_offset, wfd2.get(), &out_offset, 1, 0),
- SyscallFailsWithErrno(ESPIPE));
- EXPECT_THAT(splice(rfd1.get(), nullptr, wfd2.get(), &out_offset, 1, 0),
- SyscallFailsWithErrno(ESPIPE));
- EXPECT_THAT(splice(rfd1.get(), &in_offset, wfd2.get(), nullptr, 1, 0),
- SyscallFailsWithErrno(ESPIPE));
-}
-
-// Event FDs may be used with splice without an offset.
-TEST(SpliceTest, FromEventFD) {
- // Open the input eventfd with an initial value so that it is readable.
- constexpr uint64_t kEventFDValue = 1;
- int efd;
- ASSERT_THAT(efd = eventfd(kEventFDValue, 0), SyscallSucceeds());
- const FileDescriptor in_fd(efd);
-
- // Create a new pipe.
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
- const FileDescriptor rfd(fds[0]);
- const FileDescriptor wfd(fds[1]);
-
- // Splice 8-byte eventfd value to pipe.
- constexpr int kEventFDSize = 8;
- EXPECT_THAT(splice(in_fd.get(), nullptr, wfd.get(), nullptr, kEventFDSize, 0),
- SyscallSucceedsWithValue(kEventFDSize));
-
- // Contents should be equal.
- std::vector<char> rbuf(kEventFDSize);
- ASSERT_THAT(read(rfd.get(), rbuf.data(), rbuf.size()),
- SyscallSucceedsWithValue(kEventFDSize));
- EXPECT_EQ(memcmp(rbuf.data(), &kEventFDValue, rbuf.size()), 0);
-}
-
-// Event FDs may not be used with splice with an offset.
-TEST(SpliceTest, FromEventFDOffset) {
- int efd;
- ASSERT_THAT(efd = eventfd(0, 0), SyscallSucceeds());
- const FileDescriptor in_fd(efd);
-
- // Create a new pipe.
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
- const FileDescriptor rfd(fds[0]);
- const FileDescriptor wfd(fds[1]);
-
- // Attempt to splice 8-byte eventfd value to pipe with offset.
- //
- // This is not allowed because eventfd doesn't support pread.
- constexpr int kEventFDSize = 8;
- loff_t in_off = 0;
- EXPECT_THAT(splice(in_fd.get(), &in_off, wfd.get(), nullptr, kEventFDSize, 0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-// Event FDs may not be used with splice with an offset.
-TEST(SpliceTest, ToEventFDOffset) {
- // Create a new pipe.
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
- const FileDescriptor rfd(fds[0]);
- const FileDescriptor wfd(fds[1]);
-
- // Fill with a value.
- constexpr int kEventFDSize = 8;
- std::vector<char> buf(kEventFDSize);
- buf[0] = 1;
- ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(kEventFDSize));
-
- int efd;
- ASSERT_THAT(efd = eventfd(0, 0), SyscallSucceeds());
- const FileDescriptor out_fd(efd);
-
- // Attempt to splice 8-byte eventfd value to pipe with offset.
- //
- // This is not allowed because eventfd doesn't support pwrite.
- loff_t out_off = 0;
- EXPECT_THAT(
- splice(rfd.get(), nullptr, out_fd.get(), &out_off, kEventFDSize, 0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(SpliceTest, ToPipe) {
- // Open the input file.
- const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const FileDescriptor in_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDWR));
-
- // Fill with some random data.
- std::vector<char> buf(kPageSize);
- RandomizeBuffer(buf.data(), buf.size());
- ASSERT_THAT(write(in_fd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(kPageSize));
- ASSERT_THAT(lseek(in_fd.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0));
-
- // Create a new pipe.
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
- const FileDescriptor rfd(fds[0]);
- const FileDescriptor wfd(fds[1]);
-
- // Splice to the pipe.
- EXPECT_THAT(splice(in_fd.get(), nullptr, wfd.get(), nullptr, kPageSize, 0),
- SyscallSucceedsWithValue(kPageSize));
-
- // Contents should be equal.
- std::vector<char> rbuf(kPageSize);
- ASSERT_THAT(read(rfd.get(), rbuf.data(), rbuf.size()),
- SyscallSucceedsWithValue(kPageSize));
- EXPECT_EQ(memcmp(rbuf.data(), buf.data(), buf.size()), 0);
-}
-
-TEST(SpliceTest, ToPipeOffset) {
- // Open the input file.
- const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const FileDescriptor in_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDWR));
-
- // Fill with some random data.
- std::vector<char> buf(kPageSize);
- RandomizeBuffer(buf.data(), buf.size());
- ASSERT_THAT(write(in_fd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(kPageSize));
-
- // Create a new pipe.
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
- const FileDescriptor rfd(fds[0]);
- const FileDescriptor wfd(fds[1]);
-
- // Splice to the pipe.
- loff_t in_offset = kPageSize / 2;
- EXPECT_THAT(
- splice(in_fd.get(), &in_offset, wfd.get(), nullptr, kPageSize / 2, 0),
- SyscallSucceedsWithValue(kPageSize / 2));
-
- // Contents should be equal to only the second part.
- std::vector<char> rbuf(kPageSize / 2);
- ASSERT_THAT(read(rfd.get(), rbuf.data(), rbuf.size()),
- SyscallSucceedsWithValue(kPageSize / 2));
- EXPECT_EQ(memcmp(rbuf.data(), buf.data() + (kPageSize / 2), rbuf.size()), 0);
-}
-
-TEST(SpliceTest, FromPipe) {
- // Create a new pipe.
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
- const FileDescriptor rfd(fds[0]);
- const FileDescriptor wfd(fds[1]);
-
- // Fill with some random data.
- std::vector<char> buf(kPageSize);
- RandomizeBuffer(buf.data(), buf.size());
- ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(kPageSize));
-
- // Open the input file.
- const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const FileDescriptor out_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDWR));
-
- // Splice to the output file.
- EXPECT_THAT(splice(rfd.get(), nullptr, out_fd.get(), nullptr, kPageSize, 0),
- SyscallSucceedsWithValue(kPageSize));
-
- // The offset of the output should be equal to kPageSize. We assert that and
- // reset to zero so that we can read the contents and ensure they match.
- EXPECT_THAT(lseek(out_fd.get(), 0, SEEK_CUR),
- SyscallSucceedsWithValue(kPageSize));
- ASSERT_THAT(lseek(out_fd.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0));
-
- // Contents should be equal.
- std::vector<char> rbuf(kPageSize);
- ASSERT_THAT(read(out_fd.get(), rbuf.data(), rbuf.size()),
- SyscallSucceedsWithValue(kPageSize));
- EXPECT_EQ(memcmp(rbuf.data(), buf.data(), buf.size()), 0);
-}
-
-TEST(SpliceTest, FromPipeOffset) {
- // Create a new pipe.
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
- const FileDescriptor rfd(fds[0]);
- const FileDescriptor wfd(fds[1]);
-
- // Fill with some random data.
- std::vector<char> buf(kPageSize);
- RandomizeBuffer(buf.data(), buf.size());
- ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(kPageSize));
-
- // Open the input file.
- const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const FileDescriptor out_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDWR));
-
- // Splice to the output file.
- loff_t out_offset = kPageSize / 2;
- EXPECT_THAT(
- splice(rfd.get(), nullptr, out_fd.get(), &out_offset, kPageSize, 0),
- SyscallSucceedsWithValue(kPageSize));
-
- // Content should reflect the splice. We write to a specific offset in the
- // file, so the internals should now be allocated sparsely.
- std::vector<char> rbuf(kPageSize);
- ASSERT_THAT(read(out_fd.get(), rbuf.data(), rbuf.size()),
- SyscallSucceedsWithValue(kPageSize));
- std::vector<char> zbuf(kPageSize / 2);
- memset(zbuf.data(), 0, zbuf.size());
- EXPECT_EQ(memcmp(rbuf.data(), zbuf.data(), zbuf.size()), 0);
- EXPECT_EQ(memcmp(rbuf.data() + kPageSize / 2, buf.data(), kPageSize / 2), 0);
-}
-
-TEST(SpliceTest, TwoPipes) {
- // Create two new pipes.
- int first[2], second[2];
- ASSERT_THAT(pipe(first), SyscallSucceeds());
- const FileDescriptor rfd1(first[0]);
- const FileDescriptor wfd1(first[1]);
- ASSERT_THAT(pipe(second), SyscallSucceeds());
- const FileDescriptor rfd2(second[0]);
- const FileDescriptor wfd2(second[1]);
-
- // Fill with some random data.
- std::vector<char> buf(kPageSize);
- RandomizeBuffer(buf.data(), buf.size());
- ASSERT_THAT(write(wfd1.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(kPageSize));
-
- // Splice to the second pipe, using two operations.
- EXPECT_THAT(
- splice(rfd1.get(), nullptr, wfd2.get(), nullptr, kPageSize / 2, 0),
- SyscallSucceedsWithValue(kPageSize / 2));
- EXPECT_THAT(
- splice(rfd1.get(), nullptr, wfd2.get(), nullptr, kPageSize / 2, 0),
- SyscallSucceedsWithValue(kPageSize / 2));
-
- // Content should reflect the splice.
- std::vector<char> rbuf(kPageSize);
- ASSERT_THAT(read(rfd2.get(), rbuf.data(), rbuf.size()),
- SyscallSucceedsWithValue(kPageSize));
- EXPECT_EQ(memcmp(rbuf.data(), buf.data(), kPageSize), 0);
-}
-
-TEST(SpliceTest, Blocking) {
- // Create two new pipes.
- int first[2], second[2];
- ASSERT_THAT(pipe(first), SyscallSucceeds());
- const FileDescriptor rfd1(first[0]);
- const FileDescriptor wfd1(first[1]);
- ASSERT_THAT(pipe(second), SyscallSucceeds());
- const FileDescriptor rfd2(second[0]);
- const FileDescriptor wfd2(second[1]);
-
- // This thread writes to the main pipe.
- std::vector<char> buf(kPageSize);
- RandomizeBuffer(buf.data(), buf.size());
- ScopedThread t([&]() {
- ASSERT_THAT(write(wfd1.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(kPageSize));
- });
-
- // Attempt a splice immediately; it should block.
- EXPECT_THAT(splice(rfd1.get(), nullptr, wfd2.get(), nullptr, kPageSize, 0),
- SyscallSucceedsWithValue(kPageSize));
-
- // Thread should be joinable.
- t.Join();
-
- // Content should reflect the splice.
- std::vector<char> rbuf(kPageSize);
- ASSERT_THAT(read(rfd2.get(), rbuf.data(), rbuf.size()),
- SyscallSucceedsWithValue(kPageSize));
- EXPECT_EQ(memcmp(rbuf.data(), buf.data(), kPageSize), 0);
-}
-
-TEST(TeeTest, Blocking) {
- // Create two new pipes.
- int first[2], second[2];
- ASSERT_THAT(pipe(first), SyscallSucceeds());
- const FileDescriptor rfd1(first[0]);
- const FileDescriptor wfd1(first[1]);
- ASSERT_THAT(pipe(second), SyscallSucceeds());
- const FileDescriptor rfd2(second[0]);
- const FileDescriptor wfd2(second[1]);
-
- // This thread writes to the main pipe.
- std::vector<char> buf(kPageSize);
- RandomizeBuffer(buf.data(), buf.size());
- ScopedThread t([&]() {
- ASSERT_THAT(write(wfd1.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(kPageSize));
- });
-
- // Attempt a tee immediately; it should block.
- EXPECT_THAT(tee(rfd1.get(), wfd2.get(), kPageSize, 0),
- SyscallSucceedsWithValue(kPageSize));
-
- // Thread should be joinable.
- t.Join();
-
- // Content should reflect the splice, in both pipes.
- std::vector<char> rbuf(kPageSize);
- ASSERT_THAT(read(rfd2.get(), rbuf.data(), rbuf.size()),
- SyscallSucceedsWithValue(kPageSize));
- EXPECT_EQ(memcmp(rbuf.data(), buf.data(), kPageSize), 0);
- ASSERT_THAT(read(rfd1.get(), rbuf.data(), rbuf.size()),
- SyscallSucceedsWithValue(kPageSize));
- EXPECT_EQ(memcmp(rbuf.data(), buf.data(), kPageSize), 0);
-}
-
-TEST(TeeTest, BlockingWrite) {
- // Create two new pipes.
- int first[2], second[2];
- ASSERT_THAT(pipe(first), SyscallSucceeds());
- const FileDescriptor rfd1(first[0]);
- const FileDescriptor wfd1(first[1]);
- ASSERT_THAT(pipe(second), SyscallSucceeds());
- const FileDescriptor rfd2(second[0]);
- const FileDescriptor wfd2(second[1]);
-
- // Make some data available to be read.
- std::vector<char> buf1(kPageSize);
- RandomizeBuffer(buf1.data(), buf1.size());
- ASSERT_THAT(write(wfd1.get(), buf1.data(), buf1.size()),
- SyscallSucceedsWithValue(kPageSize));
-
- // Fill up the write pipe's buffer.
- int pipe_size = -1;
- ASSERT_THAT(pipe_size = fcntl(wfd2.get(), F_GETPIPE_SZ), SyscallSucceeds());
- std::vector<char> buf2(pipe_size);
- ASSERT_THAT(write(wfd2.get(), buf2.data(), buf2.size()),
- SyscallSucceedsWithValue(pipe_size));
-
- ScopedThread t([&]() {
- absl::SleepFor(absl::Milliseconds(100));
- ASSERT_THAT(read(rfd2.get(), buf2.data(), buf2.size()),
- SyscallSucceedsWithValue(pipe_size));
- });
-
- // Attempt a tee immediately; it should block.
- EXPECT_THAT(tee(rfd1.get(), wfd2.get(), kPageSize, 0),
- SyscallSucceedsWithValue(kPageSize));
-
- // Thread should be joinable.
- t.Join();
-
- // Content should reflect the tee.
- std::vector<char> rbuf(kPageSize);
- ASSERT_THAT(read(rfd2.get(), rbuf.data(), rbuf.size()),
- SyscallSucceedsWithValue(kPageSize));
- EXPECT_EQ(memcmp(rbuf.data(), buf1.data(), kPageSize), 0);
-}
-
-TEST(SpliceTest, NonBlocking) {
- // Create two new pipes.
- int first[2], second[2];
- ASSERT_THAT(pipe(first), SyscallSucceeds());
- const FileDescriptor rfd1(first[0]);
- const FileDescriptor wfd1(first[1]);
- ASSERT_THAT(pipe(second), SyscallSucceeds());
- const FileDescriptor rfd2(second[0]);
- const FileDescriptor wfd2(second[1]);
-
- // Splice with no data to back it.
- EXPECT_THAT(splice(rfd1.get(), nullptr, wfd2.get(), nullptr, kPageSize,
- SPLICE_F_NONBLOCK),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST(TeeTest, NonBlocking) {
- // Create two new pipes.
- int first[2], second[2];
- ASSERT_THAT(pipe(first), SyscallSucceeds());
- const FileDescriptor rfd1(first[0]);
- const FileDescriptor wfd1(first[1]);
- ASSERT_THAT(pipe(second), SyscallSucceeds());
- const FileDescriptor rfd2(second[0]);
- const FileDescriptor wfd2(second[1]);
-
- // Splice with no data to back it.
- EXPECT_THAT(tee(rfd1.get(), wfd2.get(), kPageSize, SPLICE_F_NONBLOCK),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST(TeeTest, MultiPage) {
- // Create two new pipes.
- int first[2], second[2];
- ASSERT_THAT(pipe(first), SyscallSucceeds());
- const FileDescriptor rfd1(first[0]);
- const FileDescriptor wfd1(first[1]);
- ASSERT_THAT(pipe(second), SyscallSucceeds());
- const FileDescriptor rfd2(second[0]);
- const FileDescriptor wfd2(second[1]);
-
- // Make some data available to be read.
- std::vector<char> wbuf(8 * kPageSize);
- RandomizeBuffer(wbuf.data(), wbuf.size());
- ASSERT_THAT(write(wfd1.get(), wbuf.data(), wbuf.size()),
- SyscallSucceedsWithValue(wbuf.size()));
-
- // Attempt a tee immediately; it should complete.
- EXPECT_THAT(tee(rfd1.get(), wfd2.get(), wbuf.size(), 0),
- SyscallSucceedsWithValue(wbuf.size()));
-
- // Content should reflect the tee.
- std::vector<char> rbuf(wbuf.size());
- ASSERT_THAT(read(rfd2.get(), rbuf.data(), rbuf.size()),
- SyscallSucceedsWithValue(rbuf.size()));
- EXPECT_EQ(memcmp(rbuf.data(), wbuf.data(), rbuf.size()), 0);
- ASSERT_THAT(read(rfd1.get(), rbuf.data(), rbuf.size()),
- SyscallSucceedsWithValue(rbuf.size()));
- EXPECT_EQ(memcmp(rbuf.data(), wbuf.data(), rbuf.size()), 0);
-}
-
-TEST(SpliceTest, FromPipeMaxFileSize) {
- // Create a new pipe.
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
- const FileDescriptor rfd(fds[0]);
- const FileDescriptor wfd(fds[1]);
-
- // Fill with some random data.
- std::vector<char> buf(kPageSize);
- RandomizeBuffer(buf.data(), buf.size());
- ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(kPageSize));
-
- // Open the input file.
- const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const FileDescriptor out_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDWR));
-
- EXPECT_THAT(ftruncate(out_fd.get(), 13 << 20), SyscallSucceeds());
- EXPECT_THAT(lseek(out_fd.get(), 0, SEEK_END),
- SyscallSucceedsWithValue(13 << 20));
-
- // Set our file size limit.
- sigset_t set;
- sigemptyset(&set);
- sigaddset(&set, SIGXFSZ);
- TEST_PCHECK(sigprocmask(SIG_BLOCK, &set, nullptr) == 0);
- rlimit rlim = {};
- rlim.rlim_cur = rlim.rlim_max = (13 << 20);
- EXPECT_THAT(setrlimit(RLIMIT_FSIZE, &rlim), SyscallSucceeds());
-
- // Splice to the output file.
- EXPECT_THAT(
- splice(rfd.get(), nullptr, out_fd.get(), nullptr, 3 * kPageSize, 0),
- SyscallFailsWithErrno(EFBIG));
-
- // Contents should be equal.
- std::vector<char> rbuf(kPageSize);
- ASSERT_THAT(read(rfd.get(), rbuf.data(), rbuf.size()),
- SyscallSucceedsWithValue(kPageSize));
- EXPECT_EQ(memcmp(rbuf.data(), buf.data(), buf.size()), 0);
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/stat.cc b/test/syscalls/linux/stat.cc
deleted file mode 100644
index 88ab90b5b..000000000
--- a/test/syscalls/linux/stat.cc
+++ /dev/null
@@ -1,658 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/statfs.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <string>
-#include <vector>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "gtest/gtest.h"
-#include "absl/strings/match.h"
-#include "absl/strings/str_cat.h"
-#include "absl/strings/string_view.h"
-#include "test/syscalls/linux/file_base.h"
-#include "test/util/cleanup.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-class StatTest : public FileTest {};
-
-TEST_F(StatTest, FstatatAbs) {
- struct stat st;
-
- // Check that the stat works.
- EXPECT_THAT(fstatat(AT_FDCWD, test_file_name_.c_str(), &st, 0),
- SyscallSucceeds());
- EXPECT_TRUE(S_ISREG(st.st_mode));
-}
-
-TEST_F(StatTest, FstatatEmptyPath) {
- struct stat st;
- const auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDONLY));
-
- // Check that the stat works.
- EXPECT_THAT(fstatat(fd.get(), "", &st, AT_EMPTY_PATH), SyscallSucceeds());
- EXPECT_TRUE(S_ISREG(st.st_mode));
-}
-
-TEST_F(StatTest, FstatatRel) {
- struct stat st;
- int dirfd;
- auto filename = std::string(Basename(test_file_name_));
-
- // Open the temporary directory read-only.
- ASSERT_THAT(dirfd = open(GetAbsoluteTestTmpdir().c_str(), O_RDONLY),
- SyscallSucceeds());
-
- // Check that the stat works.
- EXPECT_THAT(fstatat(dirfd, filename.c_str(), &st, 0), SyscallSucceeds());
- EXPECT_TRUE(S_ISREG(st.st_mode));
- close(dirfd);
-}
-
-TEST_F(StatTest, FstatatSymlink) {
- struct stat st;
-
- // Check that the link is followed.
- EXPECT_THAT(fstatat(AT_FDCWD, "/proc/self", &st, 0), SyscallSucceeds());
- EXPECT_TRUE(S_ISDIR(st.st_mode));
- EXPECT_FALSE(S_ISLNK(st.st_mode));
-
- // Check that the flag works.
- EXPECT_THAT(fstatat(AT_FDCWD, "/proc/self", &st, AT_SYMLINK_NOFOLLOW),
- SyscallSucceeds());
- EXPECT_TRUE(S_ISLNK(st.st_mode));
- EXPECT_FALSE(S_ISDIR(st.st_mode));
-}
-
-TEST_F(StatTest, Nlinks) {
- TempPath basedir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- // Directory is initially empty, it should contain 2 links (one from itself,
- // one from ".").
- EXPECT_THAT(Links(basedir.path()), IsPosixErrorOkAndHolds(2));
-
- // Create a file in the test directory. Files shouldn't increase the link
- // count on the base directory.
- TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(basedir.path()));
- EXPECT_THAT(Links(basedir.path()), IsPosixErrorOkAndHolds(2));
-
- // Create subdirectories. This should increase the link count by 1 per
- // subdirectory.
- TempPath dir1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(basedir.path()));
- EXPECT_THAT(Links(basedir.path()), IsPosixErrorOkAndHolds(3));
- TempPath dir2 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(basedir.path()));
- EXPECT_THAT(Links(basedir.path()), IsPosixErrorOkAndHolds(4));
-
- // Removing directories should reduce the link count.
- dir1.reset();
- EXPECT_THAT(Links(basedir.path()), IsPosixErrorOkAndHolds(3));
- dir2.reset();
- EXPECT_THAT(Links(basedir.path()), IsPosixErrorOkAndHolds(2));
-
- // Removing files should have no effect on link count.
- file1.reset();
- EXPECT_THAT(Links(basedir.path()), IsPosixErrorOkAndHolds(2));
-}
-
-TEST_F(StatTest, BlocksIncreaseOnWrite) {
- struct stat st;
-
- // Stat the empty file.
- ASSERT_THAT(fstat(test_file_fd_.get(), &st), SyscallSucceeds());
-
- const int initial_blocks = st.st_blocks;
-
- // Write to the file, making sure to exceed the block size.
- std::vector<char> buf(2 * st.st_blksize, 'a');
- ASSERT_THAT(write(test_file_fd_.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(buf.size()));
-
- // Stat the file again, and verify that number of allocated blocks has
- // increased.
- ASSERT_THAT(fstat(test_file_fd_.get(), &st), SyscallSucceeds());
- EXPECT_GT(st.st_blocks, initial_blocks);
-}
-
-TEST_F(StatTest, PathNotCleaned) {
- TempPath basedir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- // Create a file in the basedir.
- TempPath file =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(basedir.path()));
-
- // Stating the file directly should succeed.
- struct stat buf;
- EXPECT_THAT(lstat(file.path().c_str(), &buf), SyscallSucceeds());
-
- // Try to stat the file using a directory that does not exist followed by
- // "..". If the path is cleaned prior to stating (which it should not be)
- // then this will succeed.
- const std::string bad_path = JoinPath("/does_not_exist/..", file.path());
- EXPECT_THAT(lstat(bad_path.c_str(), &buf), SyscallFailsWithErrno(ENOENT));
-}
-
-TEST_F(StatTest, PathCanContainDotDot) {
- TempPath basedir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- TempPath subdir =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(basedir.path()));
- const std::string subdir_name = std::string(Basename(subdir.path()));
-
- // Create a file in the subdir.
- TempPath file =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(subdir.path()));
- const std::string file_name = std::string(Basename(file.path()));
-
- // Stat the file through a path that includes '..' and '.' but still resolves
- // to the file.
- const std::string good_path =
- JoinPath(basedir.path(), subdir_name, "..", subdir_name, ".", file_name);
- struct stat buf;
- EXPECT_THAT(lstat(good_path.c_str(), &buf), SyscallSucceeds());
-}
-
-TEST_F(StatTest, PathCanContainEmptyComponent) {
- TempPath basedir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- // Create a file in the basedir.
- TempPath file =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(basedir.path()));
- const std::string file_name = std::string(Basename(file.path()));
-
- // Stat the file through a path that includes an empty component. We have to
- // build this ourselves because JoinPath automatically removes empty
- // components.
- const std::string good_path = absl::StrCat(basedir.path(), "//", file_name);
- struct stat buf;
- EXPECT_THAT(lstat(good_path.c_str(), &buf), SyscallSucceeds());
-}
-
-TEST_F(StatTest, TrailingSlashNotCleanedReturnsENOTDIR) {
- TempPath basedir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- // Create a file in the basedir.
- TempPath file =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(basedir.path()));
-
- // Stat the file with an extra "/" on the end of it. Since file is not a
- // directory, this should return ENOTDIR.
- const std::string bad_path = absl::StrCat(file.path(), "/");
- struct stat buf;
- EXPECT_THAT(lstat(bad_path.c_str(), &buf), SyscallFailsWithErrno(ENOTDIR));
-}
-
-// Test fstatating a symlink directory.
-TEST_F(StatTest, FstatatSymlinkDir) {
- // Create a directory and symlink to it.
- const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- const std::string symlink_to_dir = NewTempAbsPath();
- EXPECT_THAT(symlink(dir.path().c_str(), symlink_to_dir.c_str()),
- SyscallSucceeds());
- auto cleanup = Cleanup([&symlink_to_dir]() {
- EXPECT_THAT(unlink(symlink_to_dir.c_str()), SyscallSucceeds());
- });
-
- // Fstatat the link with AT_SYMLINK_NOFOLLOW should return symlink data.
- struct stat st = {};
- EXPECT_THAT(
- fstatat(AT_FDCWD, symlink_to_dir.c_str(), &st, AT_SYMLINK_NOFOLLOW),
- SyscallSucceeds());
- EXPECT_FALSE(S_ISDIR(st.st_mode));
- EXPECT_TRUE(S_ISLNK(st.st_mode));
-
- // Fstatat the link should return dir data.
- EXPECT_THAT(fstatat(AT_FDCWD, symlink_to_dir.c_str(), &st, 0),
- SyscallSucceeds());
- EXPECT_TRUE(S_ISDIR(st.st_mode));
- EXPECT_FALSE(S_ISLNK(st.st_mode));
-}
-
-// Test fstatating a symlink directory with trailing slash.
-TEST_F(StatTest, FstatatSymlinkDirWithTrailingSlash) {
- // Create a directory and symlink to it.
- const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const std::string symlink_to_dir = NewTempAbsPath();
- EXPECT_THAT(symlink(dir.path().c_str(), symlink_to_dir.c_str()),
- SyscallSucceeds());
- auto cleanup = Cleanup([&symlink_to_dir]() {
- EXPECT_THAT(unlink(symlink_to_dir.c_str()), SyscallSucceeds());
- });
-
- // Fstatat on the symlink with a trailing slash should return the directory
- // data.
- struct stat st = {};
- EXPECT_THAT(
- fstatat(AT_FDCWD, absl::StrCat(symlink_to_dir, "/").c_str(), &st, 0),
- SyscallSucceeds());
- EXPECT_TRUE(S_ISDIR(st.st_mode));
- EXPECT_FALSE(S_ISLNK(st.st_mode));
-
- // Fstatat on the symlink with a trailing slash with AT_SYMLINK_NOFOLLOW
- // should return the directory data.
- // Symlink to directory with trailing slash will ignore AT_SYMLINK_NOFOLLOW.
- EXPECT_THAT(fstatat(AT_FDCWD, absl::StrCat(symlink_to_dir, "/").c_str(), &st,
- AT_SYMLINK_NOFOLLOW),
- SyscallSucceeds());
- EXPECT_TRUE(S_ISDIR(st.st_mode));
- EXPECT_FALSE(S_ISLNK(st.st_mode));
-}
-
-// Test fstatating a symlink directory with a trailing slash
-// should return same stat data with fstatating directory.
-TEST_F(StatTest, FstatatSymlinkDirWithTrailingSlashSameInode) {
- // Create a directory and symlink to it.
- const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- // We are going to assert that the symlink inode id is the same as the linked
- // dir's inode id. In order for the inode id to be stable across
- // save/restore, it must be kept open. The FileDescriptor type will do that
- // for us automatically.
- auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_RDONLY | O_DIRECTORY));
-
- const std::string symlink_to_dir = NewTempAbsPath();
- EXPECT_THAT(symlink(dir.path().c_str(), symlink_to_dir.c_str()),
- SyscallSucceeds());
- auto cleanup = Cleanup([&symlink_to_dir]() {
- EXPECT_THAT(unlink(symlink_to_dir.c_str()), SyscallSucceeds());
- });
-
- // Fstatat on the symlink with a trailing slash should return the directory
- // data.
- struct stat st = {};
- EXPECT_THAT(fstatat(AT_FDCWD, absl::StrCat(symlink_to_dir, "/").c_str(), &st,
- AT_SYMLINK_NOFOLLOW),
- SyscallSucceeds());
- EXPECT_TRUE(S_ISDIR(st.st_mode));
-
- // Dir and symlink should point to same inode.
- struct stat st_dir = {};
- EXPECT_THAT(
- fstatat(AT_FDCWD, dir.path().c_str(), &st_dir, AT_SYMLINK_NOFOLLOW),
- SyscallSucceeds());
- EXPECT_EQ(st.st_ino, st_dir.st_ino);
-}
-
-TEST_F(StatTest, LeadingDoubleSlash) {
- // Create a file, and make sure we can stat it.
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- struct stat st;
- ASSERT_THAT(lstat(file.path().c_str(), &st), SyscallSucceeds());
-
- // Now add an extra leading slash.
- const std::string double_slash_path = absl::StrCat("/", file.path());
- ASSERT_TRUE(absl::StartsWith(double_slash_path, "//"));
-
- // We should be able to stat the new path, and it should resolve to the same
- // file (same device and inode).
- struct stat double_slash_st;
- ASSERT_THAT(lstat(double_slash_path.c_str(), &double_slash_st),
- SyscallSucceeds());
- EXPECT_EQ(st.st_dev, double_slash_st.st_dev);
- EXPECT_EQ(st.st_ino, double_slash_st.st_ino);
-}
-
-// Test that a rename doesn't change the underlying file.
-TEST_F(StatTest, StatDoesntChangeAfterRename) {
- const TempPath old_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath new_path(NewTempAbsPath());
-
- struct stat st_old = {};
- struct stat st_new = {};
-
- ASSERT_THAT(stat(old_dir.path().c_str(), &st_old), SyscallSucceeds());
- ASSERT_THAT(rename(old_dir.path().c_str(), new_path.path().c_str()),
- SyscallSucceeds());
- ASSERT_THAT(stat(new_path.path().c_str(), &st_new), SyscallSucceeds());
-
- EXPECT_EQ(st_old.st_nlink, st_new.st_nlink);
- EXPECT_EQ(st_old.st_dev, st_new.st_dev);
- EXPECT_EQ(st_old.st_ino, st_new.st_ino);
- EXPECT_EQ(st_old.st_mode, st_new.st_mode);
- EXPECT_EQ(st_old.st_uid, st_new.st_uid);
- EXPECT_EQ(st_old.st_gid, st_new.st_gid);
- EXPECT_EQ(st_old.st_size, st_new.st_size);
-}
-
-// Test link counts with a regular file as the child.
-TEST_F(StatTest, LinkCountsWithRegularFileChild) {
- const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- struct stat st_parent_before = {};
- ASSERT_THAT(stat(dir.path().c_str(), &st_parent_before), SyscallSucceeds());
- EXPECT_EQ(st_parent_before.st_nlink, 2);
-
- // Adding a regular file doesn't adjust the parent's link count.
- const TempPath child =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
-
- struct stat st_parent_after = {};
- ASSERT_THAT(stat(dir.path().c_str(), &st_parent_after), SyscallSucceeds());
- EXPECT_EQ(st_parent_after.st_nlink, 2);
-
- // The child should have a single link from the parent.
- struct stat st_child = {};
- ASSERT_THAT(stat(child.path().c_str(), &st_child), SyscallSucceeds());
- EXPECT_TRUE(S_ISREG(st_child.st_mode));
- EXPECT_EQ(st_child.st_nlink, 1);
-
- // Finally unlinking the child should not affect the parent's link count.
- ASSERT_THAT(unlink(child.path().c_str()), SyscallSucceeds());
- ASSERT_THAT(stat(dir.path().c_str(), &st_parent_after), SyscallSucceeds());
- EXPECT_EQ(st_parent_after.st_nlink, 2);
-}
-
-// This test verifies that inodes remain around when there is an open fd
-// after link count hits 0.
-TEST_F(StatTest, ZeroLinksOpenFdRegularFileChild_NoRandomSave) {
- // Setting the enviornment variable GVISOR_GOFER_UNCACHED to any value
- // will prevent this test from running, see the tmpfs lifecycle.
- //
- // We need to support this because when a file is unlinked and we forward
- // the stat to the gofer it would return ENOENT.
- const char* uncached_gofer = getenv("GVISOR_GOFER_UNCACHED");
- SKIP_IF(uncached_gofer != nullptr);
-
- // We don't support saving unlinked files.
- const DisableSave ds;
-
- const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath child = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- dir.path(), "hello", TempPath::kDefaultFileMode));
-
- // The child should have a single link from the parent.
- struct stat st_child_before = {};
- ASSERT_THAT(stat(child.path().c_str(), &st_child_before), SyscallSucceeds());
- EXPECT_TRUE(S_ISREG(st_child_before.st_mode));
- EXPECT_EQ(st_child_before.st_nlink, 1);
- EXPECT_EQ(st_child_before.st_size, 5); // Hello is 5 bytes.
-
- // Open the file so we can fstat after unlinking.
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(child.path(), O_RDONLY));
-
- // Now a stat should return ENOENT but we should still be able to stat
- // via the open fd and fstat.
- ASSERT_THAT(unlink(child.path().c_str()), SyscallSucceeds());
-
- // Since the file has no more links stat should fail.
- struct stat st_child_after = {};
- ASSERT_THAT(stat(child.path().c_str(), &st_child_after),
- SyscallFailsWithErrno(ENOENT));
-
- // Fstat should still allow us to access the same file via the fd.
- struct stat st_child_fd = {};
- ASSERT_THAT(fstat(fd.get(), &st_child_fd), SyscallSucceeds());
- EXPECT_EQ(st_child_before.st_dev, st_child_fd.st_dev);
- EXPECT_EQ(st_child_before.st_ino, st_child_fd.st_ino);
- EXPECT_EQ(st_child_before.st_mode, st_child_fd.st_mode);
- EXPECT_EQ(st_child_before.st_uid, st_child_fd.st_uid);
- EXPECT_EQ(st_child_before.st_gid, st_child_fd.st_gid);
- EXPECT_EQ(st_child_before.st_size, st_child_fd.st_size);
-
- // TODO(b/34861058): This isn't ideal but since fstatfs(2) will always return
- // OVERLAYFS_SUPER_MAGIC we have no way to know if this fs is backed by a
- // gofer which doesn't support links.
- EXPECT_TRUE(st_child_fd.st_nlink == 0 || st_child_fd.st_nlink == 1);
-}
-
-// Test link counts with a directory as the child.
-TEST_F(StatTest, LinkCountsWithDirChild) {
- const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- // Before a child is added the two links are "." and the link from the parent.
- struct stat st_parent_before = {};
- ASSERT_THAT(stat(dir.path().c_str(), &st_parent_before), SyscallSucceeds());
- EXPECT_EQ(st_parent_before.st_nlink, 2);
-
- // Create a subdirectory and stat for the parent link counts.
- const TempPath sub_dir =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path()));
-
- // The three links are ".", the link from the parent, and the link from
- // the child as "..".
- struct stat st_parent_after = {};
- ASSERT_THAT(stat(dir.path().c_str(), &st_parent_after), SyscallSucceeds());
- EXPECT_EQ(st_parent_after.st_nlink, 3);
-
- // The child will have 1 link from the parent and 1 link which represents ".".
- struct stat st_child = {};
- ASSERT_THAT(stat(sub_dir.path().c_str(), &st_child), SyscallSucceeds());
- EXPECT_TRUE(S_ISDIR(st_child.st_mode));
- EXPECT_EQ(st_child.st_nlink, 2);
-
- // Finally delete the child dir and the parent link count should return to 2.
- ASSERT_THAT(rmdir(sub_dir.path().c_str()), SyscallSucceeds());
- ASSERT_THAT(stat(dir.path().c_str(), &st_parent_after), SyscallSucceeds());
-
- // Now we should only have links from the parent and "." since the subdir
- // has been removed.
- EXPECT_EQ(st_parent_after.st_nlink, 2);
-}
-
-// Test statting a child of a non-directory.
-TEST_F(StatTest, ChildOfNonDir) {
- // Create a path that has a child of a regular file.
- const std::string filename = JoinPath(test_file_name_, "child");
-
- // Statting the path should return ENOTDIR.
- struct stat st;
- EXPECT_THAT(lstat(filename.c_str(), &st), SyscallFailsWithErrno(ENOTDIR));
-}
-
-// Test lstating a symlink directory.
-TEST_F(StatTest, LstatSymlinkDir) {
- // Create a directory and symlink to it.
- const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const std::string symlink_to_dir = NewTempAbsPath();
- EXPECT_THAT(symlink(dir.path().c_str(), symlink_to_dir.c_str()),
- SyscallSucceeds());
- auto cleanup = Cleanup([&symlink_to_dir]() {
- EXPECT_THAT(unlink(symlink_to_dir.c_str()), SyscallSucceeds());
- });
-
- // Lstat on the symlink should return symlink data.
- struct stat st = {};
- ASSERT_THAT(lstat(symlink_to_dir.c_str(), &st), SyscallSucceeds());
- EXPECT_FALSE(S_ISDIR(st.st_mode));
- EXPECT_TRUE(S_ISLNK(st.st_mode));
-
- // Lstat on the symlink with a trailing slash should return the directory
- // data.
- ASSERT_THAT(lstat(absl::StrCat(symlink_to_dir, "/").c_str(), &st),
- SyscallSucceeds());
- EXPECT_TRUE(S_ISDIR(st.st_mode));
- EXPECT_FALSE(S_ISLNK(st.st_mode));
-}
-
-// Verify that we get an ELOOP from too many symbolic links even when there
-// are directories in the middle.
-TEST_F(StatTest, LstatELOOPPath) {
- const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- std::string subdir_base = "subdir";
- ASSERT_THAT(mkdir(JoinPath(dir.path(), subdir_base).c_str(), 0755),
- SyscallSucceeds());
-
- std::string target = JoinPath(dir.path(), subdir_base, subdir_base);
- std::string dst = JoinPath("..", subdir_base);
- ASSERT_THAT(symlink(dst.c_str(), target.c_str()), SyscallSucceeds());
- auto cleanup = Cleanup(
- [&target]() { EXPECT_THAT(unlink(target.c_str()), SyscallSucceeds()); });
-
- // Now build a path which is /subdir/subdir/... repeated many times so that
- // we can build a path that is shorter than PATH_MAX but can still cause
- // too many symbolic links. Note: Every other subdir is actually a directory
- // so we're not in a situation where it's a -> b -> a -> b, where a and b
- // are symbolic links.
- std::string path = dir.path();
- std::string subdir_append = absl::StrCat("/", subdir_base);
- do {
- absl::StrAppend(&path, subdir_append);
- // Keep appending /subdir until we would overflow PATH_MAX.
- } while ((path.size() + subdir_append.size()) < PATH_MAX);
-
- struct stat s = {};
- ASSERT_THAT(lstat(path.c_str(), &s), SyscallFailsWithErrno(ELOOP));
-}
-
-// Ensure that inode allocation for anonymous devices work correctly across
-// save/restore. In particular, inode numbers should be unique across S/R.
-TEST(SimpleStatTest, AnonDeviceAllocatesUniqueInodesAcrossSaveRestore) {
- // Use sockets as a convenient way to create inodes on an anonymous device.
- int fd;
- ASSERT_THAT(fd = socket(AF_UNIX, SOCK_STREAM, 0), SyscallSucceeds());
- FileDescriptor fd1(fd);
- MaybeSave();
- ASSERT_THAT(fd = socket(AF_UNIX, SOCK_STREAM, 0), SyscallSucceeds());
- FileDescriptor fd2(fd);
-
- struct stat st1;
- struct stat st2;
- ASSERT_THAT(fstat(fd1.get(), &st1), SyscallSucceeds());
- ASSERT_THAT(fstat(fd2.get(), &st2), SyscallSucceeds());
-
- // The two fds should have different inode numbers.
- EXPECT_NE(st2.st_ino, st1.st_ino);
-
- // Verify again after another S/R cycle. The inode numbers should remain the
- // same.
- MaybeSave();
-
- struct stat st1_after;
- struct stat st2_after;
- ASSERT_THAT(fstat(fd1.get(), &st1_after), SyscallSucceeds());
- ASSERT_THAT(fstat(fd2.get(), &st2_after), SyscallSucceeds());
-
- EXPECT_EQ(st1_after.st_ino, st1.st_ino);
- EXPECT_EQ(st2_after.st_ino, st2.st_ino);
-}
-
-#ifndef SYS_statx
-#if defined(__x86_64__)
-#define SYS_statx 332
-#else
-#error "Unknown architecture"
-#endif
-#endif // SYS_statx
-
-#ifndef STATX_ALL
-#define STATX_ALL 0x00000fffU
-#endif // STATX_ALL
-
-// struct kernel_statx_timestamp is a Linux statx_timestamp struct.
-struct kernel_statx_timestamp {
- int64_t tv_sec;
- uint32_t tv_nsec;
- int32_t __reserved;
-};
-
-// struct kernel_statx is a Linux statx struct. Old versions of glibc do not
-// expose it. See include/uapi/linux/stat.h
-struct kernel_statx {
- uint32_t stx_mask;
- uint32_t stx_blksize;
- uint64_t stx_attributes;
- uint32_t stx_nlink;
- uint32_t stx_uid;
- uint32_t stx_gid;
- uint16_t stx_mode;
- uint16_t __spare0[1];
- uint64_t stx_ino;
- uint64_t stx_size;
- uint64_t stx_blocks;
- uint64_t stx_attributes_mask;
- struct kernel_statx_timestamp stx_atime;
- struct kernel_statx_timestamp stx_btime;
- struct kernel_statx_timestamp stx_ctime;
- struct kernel_statx_timestamp stx_mtime;
- uint32_t stx_rdev_major;
- uint32_t stx_rdev_minor;
- uint32_t stx_dev_major;
- uint32_t stx_dev_minor;
- uint64_t __spare2[14];
-};
-
-int statx(int dirfd, const char *pathname, int flags, unsigned int mask,
- struct kernel_statx *statxbuf) {
- return syscall(SYS_statx, dirfd, pathname, flags, mask, statxbuf);
-}
-
-TEST_F(StatTest, StatxAbsPath) {
- SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, 0) < 0 &&
- errno == ENOSYS);
-
- struct kernel_statx stx;
- EXPECT_THAT(statx(-1, test_file_name_.c_str(), 0, STATX_ALL, &stx),
- SyscallSucceeds());
- EXPECT_TRUE(S_ISREG(stx.stx_mode));
-}
-
-TEST_F(StatTest, StatxRelPathDirFD) {
- SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, 0) < 0 &&
- errno == ENOSYS);
-
- struct kernel_statx stx;
- auto const dirfd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(GetAbsoluteTestTmpdir(), O_RDONLY));
- auto filename = std::string(Basename(test_file_name_));
-
- EXPECT_THAT(statx(dirfd.get(), filename.c_str(), 0, STATX_ALL, &stx),
- SyscallSucceeds());
- EXPECT_TRUE(S_ISREG(stx.stx_mode));
-}
-
-TEST_F(StatTest, StatxRelPathCwd) {
- SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, 0) < 0 &&
- errno == ENOSYS);
-
- ASSERT_THAT(chdir(GetAbsoluteTestTmpdir().c_str()), SyscallSucceeds());
- auto filename = std::string(Basename(test_file_name_));
- struct kernel_statx stx;
- EXPECT_THAT(statx(AT_FDCWD, filename.c_str(), 0, STATX_ALL, &stx),
- SyscallSucceeds());
- EXPECT_TRUE(S_ISREG(stx.stx_mode));
-}
-
-TEST_F(StatTest, StatxEmptyPath) {
- SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, 0) < 0 &&
- errno == ENOSYS);
-
- const auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDONLY));
- struct kernel_statx stx;
- EXPECT_THAT(statx(fd.get(), "", AT_EMPTY_PATH, STATX_ALL, &stx),
- SyscallSucceeds());
- EXPECT_TRUE(S_ISREG(stx.stx_mode));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/stat_times.cc b/test/syscalls/linux/stat_times.cc
deleted file mode 100644
index 68c0bef09..000000000
--- a/test/syscalls/linux/stat_times.cc
+++ /dev/null
@@ -1,303 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <sys/stat.h>
-
-#include <tuple>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-using ::testing::IsEmpty;
-using ::testing::Not;
-
-std::tuple<absl::Time, absl::Time, absl::Time> GetTime(const TempPath& file) {
- struct stat statbuf = {};
- EXPECT_THAT(stat(file.path().c_str(), &statbuf), SyscallSucceeds());
-
- const auto atime = absl::TimeFromTimespec(statbuf.st_atim);
- const auto mtime = absl::TimeFromTimespec(statbuf.st_mtim);
- const auto ctime = absl::TimeFromTimespec(statbuf.st_ctim);
- return std::make_tuple(atime, mtime, ctime);
-}
-
-enum class AtimeEffect {
- Unchanged,
- Changed,
-};
-
-enum class MtimeEffect {
- Unchanged,
- Changed,
-};
-
-enum class CtimeEffect {
- Unchanged,
- Changed,
-};
-
-// Tests that fn modifies the atime/mtime/ctime of path as specified.
-void CheckTimes(const TempPath& path, std::function<void()> fn,
- AtimeEffect atime_effect, MtimeEffect mtime_effect,
- CtimeEffect ctime_effect) {
- absl::Time atime, mtime, ctime;
- std::tie(atime, mtime, ctime) = GetTime(path);
-
- // FIXME(b/132819225): gVisor filesystem timestamps inconsistently use the
- // internal or host clock, which may diverge slightly. Allow some slack on
- // times to account for the difference.
- //
- // Here we sleep for 1s so that initial creation of path doesn't fall within
- // the before slack window.
- absl::SleepFor(absl::Seconds(1));
-
- const absl::Time before = absl::Now() - absl::Seconds(1);
-
- // Perform the op.
- fn();
-
- const absl::Time after = absl::Now() + absl::Seconds(1);
-
- absl::Time atime2, mtime2, ctime2;
- std::tie(atime2, mtime2, ctime2) = GetTime(path);
-
- if (atime_effect == AtimeEffect::Changed) {
- EXPECT_LE(before, atime2);
- EXPECT_GE(after, atime2);
- EXPECT_GT(atime2, atime);
- } else {
- EXPECT_EQ(atime2, atime);
- }
-
- if (mtime_effect == MtimeEffect::Changed) {
- EXPECT_LE(before, mtime2);
- EXPECT_GE(after, mtime2);
- EXPECT_GT(mtime2, mtime);
- } else {
- EXPECT_EQ(mtime2, mtime);
- }
-
- if (ctime_effect == CtimeEffect::Changed) {
- EXPECT_LE(before, ctime2);
- EXPECT_GE(after, ctime2);
- EXPECT_GT(ctime2, ctime);
- } else {
- EXPECT_EQ(ctime2, ctime);
- }
-}
-
-// File creation time is reflected in atime, mtime, and ctime.
-TEST(StatTimesTest, FileCreation) {
- const DisableSave ds; // Timing-related test.
-
- // Get a time for when the file is created.
- //
- // FIXME(b/132819225): See above.
- const absl::Time before = absl::Now() - absl::Seconds(1);
- const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const absl::Time after = absl::Now() + absl::Seconds(1);
-
- absl::Time atime, mtime, ctime;
- std::tie(atime, mtime, ctime) = GetTime(file);
-
- EXPECT_LE(before, atime);
- EXPECT_LE(before, mtime);
- EXPECT_LE(before, ctime);
- EXPECT_GE(after, atime);
- EXPECT_GE(after, mtime);
- EXPECT_GE(after, ctime);
-}
-
-// Calling chmod on a file changes ctime.
-TEST(StatTimesTest, FileChmod) {
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- auto fn = [&] {
- EXPECT_THAT(chmod(file.path().c_str(), 0666), SyscallSucceeds());
- };
- CheckTimes(file, fn, AtimeEffect::Unchanged, MtimeEffect::Unchanged,
- CtimeEffect::Changed);
-}
-
-// Renaming a file changes ctime.
-TEST(StatTimesTest, FileRename) {
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- const std::string newpath = NewTempAbsPath();
-
- auto fn = [&] {
- ASSERT_THAT(rename(file.release().c_str(), newpath.c_str()),
- SyscallSucceeds());
- file.reset(newpath);
- };
- CheckTimes(file, fn, AtimeEffect::Unchanged, MtimeEffect::Unchanged,
- CtimeEffect::Changed);
-}
-
-// Renaming a file changes ctime, even with an open FD.
-//
-// NOTE(b/132732387): This is a regression test for fs/gofer failing to update
-// cached ctime.
-TEST(StatTimesTest, FileRenameOpenFD) {
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- // Holding an FD shouldn't affect behavior.
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY));
-
- const std::string newpath = NewTempAbsPath();
-
- // FIXME(b/132814682): Restore fails with an uncached gofer and an open FD
- // across rename.
- //
- // N.B. The logic here looks backwards because it isn't possible to
- // conditionally disable save, only conditionally re-enable it.
- DisableSave ds;
- if (!getenv("GVISOR_GOFER_UNCACHED")) {
- ds.reset();
- }
-
- auto fn = [&] {
- ASSERT_THAT(rename(file.release().c_str(), newpath.c_str()),
- SyscallSucceeds());
- file.reset(newpath);
- };
- CheckTimes(file, fn, AtimeEffect::Unchanged, MtimeEffect::Unchanged,
- CtimeEffect::Changed);
-}
-
-// Calling utimes on a file changes ctime and the time that we ask to change
-// (atime to now in this case).
-TEST(StatTimesTest, FileUtimes) {
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- auto fn = [&] {
- const struct timespec ts[2] = {{0, UTIME_NOW}, {0, UTIME_OMIT}};
- ASSERT_THAT(utimensat(AT_FDCWD, file.path().c_str(), ts, 0),
- SyscallSucceeds());
- };
- CheckTimes(file, fn, AtimeEffect::Changed, MtimeEffect::Unchanged,
- CtimeEffect::Changed);
-}
-
-// Truncating a file changes mtime and ctime.
-TEST(StatTimesTest, FileTruncate) {
- const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateFileWith(GetAbsoluteTestTmpdir(), "yaaass", 0666));
-
- auto fn = [&] {
- EXPECT_THAT(truncate(file.path().c_str(), 0), SyscallSucceeds());
- };
- CheckTimes(file, fn, AtimeEffect::Unchanged, MtimeEffect::Changed,
- CtimeEffect::Changed);
-}
-
-// Writing a file changes mtime and ctime.
-TEST(StatTimesTest, FileWrite) {
- const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateFileWith(GetAbsoluteTestTmpdir(), "yaaass", 0666));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0));
-
- auto fn = [&] {
- const std::string contents = "all the single dollars";
- EXPECT_THAT(WriteFd(fd.get(), contents.data(), contents.size()),
- SyscallSucceeds());
- };
- CheckTimes(file, fn, AtimeEffect::Unchanged, MtimeEffect::Changed,
- CtimeEffect::Changed);
-}
-
-// Reading a file changes atime.
-TEST(StatTimesTest, FileRead) {
- const std::string contents = "bills bills bills";
- const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateFileWith(GetAbsoluteTestTmpdir(), contents, 0666));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY, 0));
-
- auto fn = [&] {
- char buf[20];
- ASSERT_THAT(ReadFd(fd.get(), buf, sizeof(buf)),
- SyscallSucceedsWithValue(contents.size()));
- };
- CheckTimes(file, fn, AtimeEffect::Changed, MtimeEffect::Unchanged,
- CtimeEffect::Unchanged);
-}
-
-// Listing files in a directory changes atime.
-TEST(StatTimesTest, DirList) {
- const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
-
- auto fn = [&] {
- const auto contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir(dir.path(), false));
- EXPECT_THAT(contents, Not(IsEmpty()));
- };
- CheckTimes(dir, fn, AtimeEffect::Changed, MtimeEffect::Unchanged,
- CtimeEffect::Unchanged);
-}
-
-// Creating a file in a directory changes mtime and ctime.
-TEST(StatTimesTest, DirCreateFile) {
- const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- TempPath file;
- auto fn = [&] {
- file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
- };
- CheckTimes(dir, fn, AtimeEffect::Unchanged, MtimeEffect::Changed,
- CtimeEffect::Changed);
-}
-
-// Creating a directory in a directory changes mtime and ctime.
-TEST(StatTimesTest, DirCreateDir) {
- const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- TempPath dir2;
- auto fn = [&] {
- dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path()));
- };
- CheckTimes(dir, fn, AtimeEffect::Unchanged, MtimeEffect::Changed,
- CtimeEffect::Changed);
-}
-
-// Removing a file from a directory changes mtime and ctime.
-TEST(StatTimesTest, DirRemoveFile) {
- const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
- auto fn = [&] { file.reset(); };
- CheckTimes(dir, fn, AtimeEffect::Unchanged, MtimeEffect::Changed,
- CtimeEffect::Changed);
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/statfs.cc b/test/syscalls/linux/statfs.cc
deleted file mode 100644
index aca51d30f..000000000
--- a/test/syscalls/linux/statfs.cc
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <sys/statfs.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(StatfsTest, CannotStatBadPath) {
- auto temp_file = NewTempAbsPathInDir("/tmp");
-
- struct statfs st;
- EXPECT_THAT(statfs(temp_file.c_str(), &st), SyscallFailsWithErrno(ENOENT));
-}
-
-TEST(StatfsTest, InternalTmpfs) {
- auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- struct statfs st;
- EXPECT_THAT(statfs(temp_file.path().c_str(), &st), SyscallSucceeds());
-}
-
-TEST(StatfsTest, InternalDevShm) {
- struct statfs st;
- EXPECT_THAT(statfs("/dev/shm", &st), SyscallSucceeds());
-}
-
-TEST(StatfsTest, NameLen) {
- struct statfs st;
- EXPECT_THAT(statfs("/dev/shm", &st), SyscallSucceeds());
-
- // This assumes that /dev/shm is tmpfs.
- EXPECT_EQ(st.f_namelen, NAME_MAX);
-}
-
-TEST(FstatfsTest, CannotStatBadFd) {
- struct statfs st;
- EXPECT_THAT(fstatfs(-1, &st), SyscallFailsWithErrno(EBADF));
-}
-
-TEST(FstatfsTest, InternalTmpfs) {
- auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(temp_file.path(), O_RDONLY));
-
- struct statfs st;
- EXPECT_THAT(fstatfs(fd.get(), &st), SyscallSucceeds());
-}
-
-TEST(FstatfsTest, InternalDevShm) {
- auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/shm", O_RDONLY));
-
- struct statfs st;
- EXPECT_THAT(fstatfs(fd.get(), &st), SyscallSucceeds());
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/sticky.cc b/test/syscalls/linux/sticky.cc
deleted file mode 100644
index 59fb5dfe6..000000000
--- a/test/syscalls/linux/sticky.cc
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <grp.h>
-#include <sys/prctl.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "test/util/capability_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-DEFINE_int32(scratch_uid, 65534, "first scratch UID");
-DEFINE_int32(scratch_gid, 65534, "first scratch GID");
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(StickyTest, StickyBitPermDenied) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID)));
-
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- EXPECT_THAT(chmod(dir.path().c_str(), 0777 | S_ISVTX), SyscallSucceeds());
- std::string path = JoinPath(dir.path(), "NewDir");
- ASSERT_THAT(mkdir(path.c_str(), 0755), SyscallSucceeds());
-
- // Drop privileges and change IDs only in child thread, or else this parent
- // thread won't be able to open some log files after the test ends.
- ScopedThread([&] {
- // Drop privileges.
- if (HaveCapability(CAP_FOWNER).ValueOrDie()) {
- EXPECT_NO_ERRNO(SetCapability(CAP_FOWNER, false));
- }
-
- // Change EUID and EGID.
- EXPECT_THAT(syscall(SYS_setresgid, -1, FLAGS_scratch_gid, -1),
- SyscallSucceeds());
- EXPECT_THAT(syscall(SYS_setresuid, -1, FLAGS_scratch_uid, -1),
- SyscallSucceeds());
-
- EXPECT_THAT(rmdir(path.c_str()), SyscallFailsWithErrno(EPERM));
- });
-}
-
-TEST(StickyTest, StickyBitSameUID) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID)));
-
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- EXPECT_THAT(chmod(dir.path().c_str(), 0777 | S_ISVTX), SyscallSucceeds());
- std::string path = JoinPath(dir.path(), "NewDir");
- ASSERT_THAT(mkdir(path.c_str(), 0755), SyscallSucceeds());
-
- // Drop privileges and change IDs only in child thread, or else this parent
- // thread won't be able to open some log files after the test ends.
- ScopedThread([&] {
- // Drop privileges.
- if (HaveCapability(CAP_FOWNER).ValueOrDie()) {
- EXPECT_NO_ERRNO(SetCapability(CAP_FOWNER, false));
- }
-
- // Change EGID.
- EXPECT_THAT(syscall(SYS_setresgid, -1, FLAGS_scratch_gid, -1),
- SyscallSucceeds());
-
- // We still have the same EUID.
- EXPECT_THAT(rmdir(path.c_str()), SyscallSucceeds());
- });
-}
-
-TEST(StickyTest, StickyBitCapFOWNER) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID)));
-
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- EXPECT_THAT(chmod(dir.path().c_str(), 0777 | S_ISVTX), SyscallSucceeds());
- std::string path = JoinPath(dir.path(), "NewDir");
- ASSERT_THAT(mkdir(path.c_str(), 0755), SyscallSucceeds());
-
- // Drop privileges and change IDs only in child thread, or else this parent
- // thread won't be able to open some log files after the test ends.
- ScopedThread([&] {
- // Set PR_SET_KEEPCAPS.
- EXPECT_THAT(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0), SyscallSucceeds());
-
- // Change EUID and EGID.
- EXPECT_THAT(syscall(SYS_setresgid, -1, FLAGS_scratch_gid, -1),
- SyscallSucceeds());
- EXPECT_THAT(syscall(SYS_setresuid, -1, FLAGS_scratch_uid, -1),
- SyscallSucceeds());
-
- EXPECT_NO_ERRNO(SetCapability(CAP_FOWNER, true));
- EXPECT_THAT(rmdir(path.c_str()), SyscallSucceeds());
- });
-}
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/symlink.cc b/test/syscalls/linux/symlink.cc
deleted file mode 100644
index b249ff91f..000000000
--- a/test/syscalls/linux/symlink.cc
+++ /dev/null
@@ -1,377 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <fcntl.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <string>
-
-#include "gtest/gtest.h"
-#include "test/util/capability_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-mode_t FilePermission(const std::string& path) {
- struct stat buf = {0};
- TEST_CHECK(lstat(path.c_str(), &buf) == 0);
- return buf.st_mode & 0777;
-}
-
-// Test that name collisions are checked on the new link path, not the source
-// path.
-TEST(SymlinkTest, CanCreateSymlinkWithCachedSourceDirent) {
- const std::string srcname = NewTempAbsPath();
- const std::string newname = NewTempAbsPath();
- const std::string basedir = std::string(Dirname(srcname));
- ASSERT_EQ(basedir, Dirname(newname));
-
- ASSERT_THAT(chdir(basedir.c_str()), SyscallSucceeds());
-
- // Open the source node to cause the underlying dirent to be cached. It will
- // remain cached while we have the file open.
- int fd;
- ASSERT_THAT(fd = open(srcname.c_str(), O_CREAT | O_RDWR, 0666),
- SyscallSucceeds());
- FileDescriptor fd_closer(fd);
-
- // Attempt to create a symlink. If the bug exists, this will fail since the
- // dirent link creation code will check for a name collision on the source
- // link name.
- EXPECT_THAT(symlink(std::string(Basename(srcname)).c_str(),
- std::string(Basename(newname)).c_str()),
- SyscallSucceeds());
-}
-
-TEST(SymlinkTest, CanCreateSymlinkFile) {
- const std::string oldname = NewTempAbsPath();
- const std::string newname = NewTempAbsPath();
-
- int fd;
- ASSERT_THAT(fd = open(oldname.c_str(), O_CREAT | O_RDWR, 0666),
- SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
-
- EXPECT_THAT(symlink(oldname.c_str(), newname.c_str()), SyscallSucceeds());
- EXPECT_EQ(FilePermission(newname), 0777);
-
- auto link = ASSERT_NO_ERRNO_AND_VALUE(ReadLink(newname));
- EXPECT_EQ(oldname, link);
-
- EXPECT_THAT(unlink(newname.c_str()), SyscallSucceeds());
- EXPECT_THAT(unlink(oldname.c_str()), SyscallSucceeds());
-}
-
-TEST(SymlinkTest, CanCreateSymlinkDir) {
- const std::string olddir = NewTempAbsPath();
- const std::string newdir = NewTempAbsPath();
-
- EXPECT_THAT(mkdir(olddir.c_str(), 0777), SyscallSucceeds());
- EXPECT_THAT(symlink(olddir.c_str(), newdir.c_str()), SyscallSucceeds());
- EXPECT_EQ(FilePermission(newdir), 0777);
-
- auto link = ASSERT_NO_ERRNO_AND_VALUE(ReadLink(newdir));
- EXPECT_EQ(olddir, link);
-
- EXPECT_THAT(unlink(newdir.c_str()), SyscallSucceeds());
-
- ASSERT_THAT(rmdir(olddir.c_str()), SyscallSucceeds());
-}
-
-TEST(SymlinkTest, CannotCreateSymlinkInReadOnlyDir) {
- // Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
-
- const std::string olddir = NewTempAbsPath();
- ASSERT_THAT(mkdir(olddir.c_str(), 0444), SyscallSucceeds());
-
- const std::string newdir = NewTempAbsPathInDir(olddir);
- EXPECT_THAT(symlink(olddir.c_str(), newdir.c_str()),
- SyscallFailsWithErrno(EACCES));
-
- ASSERT_THAT(rmdir(olddir.c_str()), SyscallSucceeds());
-}
-
-TEST(SymlinkTest, CannotSymlinkOverExistingFile) {
- const auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const auto newfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- EXPECT_THAT(symlink(oldfile.path().c_str(), newfile.path().c_str()),
- SyscallFailsWithErrno(EEXIST));
-}
-
-TEST(SymlinkTest, CannotSymlinkOverExistingDir) {
- const auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const auto newdir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- EXPECT_THAT(symlink(oldfile.path().c_str(), newdir.path().c_str()),
- SyscallFailsWithErrno(EEXIST));
-}
-
-TEST(SymlinkTest, OldnameIsEmpty) {
- const std::string newname = NewTempAbsPath();
- EXPECT_THAT(symlink("", newname.c_str()), SyscallFailsWithErrno(ENOENT));
-}
-
-TEST(SymlinkTest, OldnameIsDangling) {
- const std::string newname = NewTempAbsPath();
- EXPECT_THAT(symlink("/dangling", newname.c_str()), SyscallSucceeds());
-
- // This is required for S/R random save tests, which pre-run this test
- // in the same TEST_TMPDIR, which means that we need to clean it for any
- // operations exclusively creating files, like symlink above.
- EXPECT_THAT(unlink(newname.c_str()), SyscallSucceeds());
-}
-
-TEST(SymlinkTest, NewnameCannotExist) {
- const std::string newname =
- JoinPath(GetAbsoluteTestTmpdir(), "thisdoesnotexist", "foo");
- EXPECT_THAT(symlink("/thisdoesnotmatter", newname.c_str()),
- SyscallFailsWithErrno(ENOENT));
-}
-
-TEST(SymlinkTest, CanEvaluateLink) {
- const auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
-
- // We are going to assert that the symlink inode id is the same as the linked
- // file's inode id. In order for the inode id to be stable across
- // save/restore, it must be kept open. The FileDescriptor type will do that
- // for us automatically.
- auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
- struct stat file_st;
- EXPECT_THAT(fstat(fd.get(), &file_st), SyscallSucceeds());
-
- const std::string link = NewTempAbsPath();
- EXPECT_THAT(symlink(file.path().c_str(), link.c_str()), SyscallSucceeds());
- EXPECT_EQ(FilePermission(link), 0777);
-
- auto linkfd = ASSERT_NO_ERRNO_AND_VALUE(Open(link.c_str(), O_RDWR));
- struct stat link_st;
- EXPECT_THAT(fstat(linkfd.get(), &link_st), SyscallSucceeds());
-
- // Check that in fact newname points to the file we expect.
- EXPECT_EQ(file_st.st_dev, link_st.st_dev);
- EXPECT_EQ(file_st.st_ino, link_st.st_ino);
-}
-
-TEST(SymlinkTest, TargetIsNotMapped) {
- const std::string oldname = NewTempAbsPath();
- const std::string newname = NewTempAbsPath();
-
- int fd;
- // Create the target so that when we read the link, it exists.
- ASSERT_THAT(fd = open(oldname.c_str(), O_CREAT | O_RDWR, 0666),
- SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
-
- // Create a symlink called newname that points to oldname.
- EXPECT_THAT(symlink(oldname.c_str(), newname.c_str()), SyscallSucceeds());
-
- std::vector<char> buf(1024);
- int linksize;
- // Read the link and assert that the oldname is still the same.
- EXPECT_THAT(linksize = readlink(newname.c_str(), buf.data(), 1024),
- SyscallSucceeds());
- EXPECT_EQ(0, strncmp(oldname.c_str(), buf.data(), linksize));
-
- EXPECT_THAT(unlink(newname.c_str()), SyscallSucceeds());
- EXPECT_THAT(unlink(oldname.c_str()), SyscallSucceeds());
-}
-
-TEST(SymlinkTest, PreadFromSymlink) {
- std::string name = NewTempAbsPath();
- int fd;
- ASSERT_THAT(fd = open(name.c_str(), O_CREAT, 0644), SyscallSucceeds());
- ASSERT_THAT(close(fd), SyscallSucceeds());
-
- std::string linkname = NewTempAbsPath();
- ASSERT_THAT(symlink(name.c_str(), linkname.c_str()), SyscallSucceeds());
-
- ASSERT_THAT(fd = open(linkname.c_str(), O_RDONLY), SyscallSucceeds());
-
- char buf[1024];
- EXPECT_THAT(pread64(fd, buf, 1024, 0), SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
-
- EXPECT_THAT(unlink(name.c_str()), SyscallSucceeds());
- EXPECT_THAT(unlink(linkname.c_str()), SyscallSucceeds());
-}
-
-TEST(SymlinkTest, SymlinkAtDegradedPermissions_NoRandomSave) {
- // Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
-
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
-
- int dirfd;
- ASSERT_THAT(dirfd = open(dir.path().c_str(), O_DIRECTORY, 0),
- SyscallSucceeds());
-
- const DisableSave ds; // Permissions are dropped.
- EXPECT_THAT(fchmod(dirfd, 0), SyscallSucceeds());
-
- std::string basename = std::string(Basename(file.path()));
- EXPECT_THAT(symlinkat("/dangling", dirfd, basename.c_str()),
- SyscallFailsWithErrno(EACCES));
- EXPECT_THAT(close(dirfd), SyscallSucceeds());
-}
-
-TEST(SymlinkTest, ReadlinkAtDegradedPermissions_NoRandomSave) {
- // Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
-
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const std::string oldpath = NewTempAbsPathInDir(dir.path());
- const std::string oldbase = std::string(Basename(oldpath));
- ASSERT_THAT(symlink("/dangling", oldpath.c_str()), SyscallSucceeds());
-
- int dirfd;
- EXPECT_THAT(dirfd = open(dir.path().c_str(), O_DIRECTORY, 0),
- SyscallSucceeds());
-
- const DisableSave ds; // Permissions are dropped.
- EXPECT_THAT(fchmod(dirfd, 0), SyscallSucceeds());
-
- char buf[1024];
- int linksize;
- EXPECT_THAT(linksize = readlinkat(dirfd, oldbase.c_str(), buf, 1024),
- SyscallFailsWithErrno(EACCES));
- EXPECT_THAT(close(dirfd), SyscallSucceeds());
-}
-
-TEST(SymlinkTest, ChmodSymlink) {
- auto target = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const std::string newpath = NewTempAbsPath();
- ASSERT_THAT(symlink(target.path().c_str(), newpath.c_str()),
- SyscallSucceeds());
- EXPECT_EQ(FilePermission(newpath), 0777);
- EXPECT_THAT(chmod(newpath.c_str(), 0666), SyscallSucceeds());
- EXPECT_EQ(FilePermission(newpath), 0777);
-}
-
-class ParamSymlinkTest : public ::testing::TestWithParam<std::string> {};
-
-// Test that creating an existing symlink with creat will create the target.
-TEST_P(ParamSymlinkTest, CreatLinkCreatesTarget) {
- const std::string target = GetParam();
- const std::string linkpath = NewTempAbsPath();
-
- ASSERT_THAT(symlink(target.c_str(), linkpath.c_str()), SyscallSucceeds());
-
- int fd;
- EXPECT_THAT(fd = creat(linkpath.c_str(), 0666), SyscallSucceeds());
- ASSERT_THAT(close(fd), SyscallSucceeds());
-
- ASSERT_THAT(chdir(GetAbsoluteTestTmpdir().c_str()), SyscallSucceeds());
- struct stat st;
- EXPECT_THAT(stat(target.c_str(), &st), SyscallSucceeds());
-
- ASSERT_THAT(unlink(linkpath.c_str()), SyscallSucceeds());
- ASSERT_THAT(unlink(target.c_str()), SyscallSucceeds());
-}
-
-// Test that opening an existing symlink with O_CREAT will create the target.
-TEST_P(ParamSymlinkTest, OpenLinkCreatesTarget) {
- const std::string target = GetParam();
- const std::string linkpath = NewTempAbsPath();
-
- ASSERT_THAT(symlink(target.c_str(), linkpath.c_str()), SyscallSucceeds());
-
- int fd;
- EXPECT_THAT(fd = open(linkpath.c_str(), O_CREAT, 0666), SyscallSucceeds());
- ASSERT_THAT(close(fd), SyscallSucceeds());
-
- ASSERT_THAT(chdir(GetAbsoluteTestTmpdir().c_str()), SyscallSucceeds());
- struct stat st;
- EXPECT_THAT(stat(target.c_str(), &st), SyscallSucceeds());
-
- ASSERT_THAT(unlink(linkpath.c_str()), SyscallSucceeds());
- ASSERT_THAT(unlink(target.c_str()), SyscallSucceeds());
-}
-
-// Test that opening a self-symlink with O_CREAT will fail with ELOOP.
-TEST_P(ParamSymlinkTest, CreateExistingSelfLink) {
- ASSERT_THAT(chdir(GetAbsoluteTestTmpdir().c_str()), SyscallSucceeds());
-
- const std::string linkpath = GetParam();
- ASSERT_THAT(symlink(linkpath.c_str(), linkpath.c_str()), SyscallSucceeds());
-
- EXPECT_THAT(open(linkpath.c_str(), O_CREAT, 0666),
- SyscallFailsWithErrno(ELOOP));
-
- ASSERT_THAT(unlink(linkpath.c_str()), SyscallSucceeds());
-}
-
-// Test that opening a file that is a symlink to its parent directory fails
-// with ELOOP.
-TEST_P(ParamSymlinkTest, CreateExistingParentLink) {
- ASSERT_THAT(chdir(GetAbsoluteTestTmpdir().c_str()), SyscallSucceeds());
-
- const std::string linkpath = GetParam();
- const std::string target = JoinPath(linkpath, "child");
- ASSERT_THAT(symlink(target.c_str(), linkpath.c_str()), SyscallSucceeds());
-
- EXPECT_THAT(open(linkpath.c_str(), O_CREAT, 0666),
- SyscallFailsWithErrno(ELOOP));
-
- ASSERT_THAT(unlink(linkpath.c_str()), SyscallSucceeds());
-}
-
-// Test that opening an existing symlink with O_CREAT|O_EXCL will fail with
-// EEXIST.
-TEST_P(ParamSymlinkTest, OpenLinkExclFails) {
- const std::string target = GetParam();
- const std::string linkpath = NewTempAbsPath();
-
- ASSERT_THAT(symlink(target.c_str(), linkpath.c_str()), SyscallSucceeds());
-
- EXPECT_THAT(open(linkpath.c_str(), O_CREAT | O_EXCL, 0666),
- SyscallFailsWithErrno(EEXIST));
-
- ASSERT_THAT(unlink(linkpath.c_str()), SyscallSucceeds());
-}
-
-// Test that opening an existing symlink with O_CREAT|O_NOFOLLOW will fail with
-// ELOOP.
-TEST_P(ParamSymlinkTest, OpenLinkNoFollowFails) {
- const std::string target = GetParam();
- const std::string linkpath = NewTempAbsPath();
-
- ASSERT_THAT(symlink(target.c_str(), linkpath.c_str()), SyscallSucceeds());
-
- EXPECT_THAT(open(linkpath.c_str(), O_CREAT | O_NOFOLLOW, 0666),
- SyscallFailsWithErrno(ELOOP));
-
- ASSERT_THAT(unlink(linkpath.c_str()), SyscallSucceeds());
-}
-
-INSTANTIATE_TEST_SUITE_P(AbsAndRelTarget, ParamSymlinkTest,
- ::testing::Values(NewTempAbsPath(), NewTempRelPath()));
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/sync.cc b/test/syscalls/linux/sync.cc
deleted file mode 100644
index fe479390d..000000000
--- a/test/syscalls/linux/sync.cc
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <stdio.h>
-#include <unistd.h>
-
-#include <sys/syscall.h>
-#include <unistd.h>
-#include <string>
-
-#include "gtest/gtest.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(SyncTest, SyncEverything) {
- ASSERT_THAT(syscall(SYS_sync), SyscallSucceeds());
-}
-
-TEST(SyncTest, SyncFileSytem) {
- int fd;
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- ASSERT_THAT(fd = open(f.path().c_str(), O_RDONLY), SyscallSucceeds());
- EXPECT_THAT(syncfs(fd), SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
-}
-
-TEST(SyncTest, SyncFromPipe) {
- int pipes[2];
- EXPECT_THAT(pipe(pipes), SyscallSucceeds());
- EXPECT_THAT(syncfs(pipes[0]), SyscallSucceeds());
- EXPECT_THAT(syncfs(pipes[1]), SyscallSucceeds());
- EXPECT_THAT(close(pipes[0]), SyscallSucceeds());
- EXPECT_THAT(close(pipes[1]), SyscallSucceeds());
-}
-
-TEST(SyncTest, CannotSyncFileSytemAtBadFd) {
- EXPECT_THAT(syncfs(-1), SyscallFailsWithErrno(EBADF));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/sync_file_range.cc b/test/syscalls/linux/sync_file_range.cc
deleted file mode 100644
index 36cc42043..000000000
--- a/test/syscalls/linux/sync_file_range.cc
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <stdio.h>
-#include <unistd.h>
-
-#include <string>
-
-#include "gtest/gtest.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(SyncFileRangeTest, TempFileSucceeds) {
- auto tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- auto f = ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path(), O_RDWR));
- constexpr char data[] = "some data to sync";
- int fd = f.get();
-
- EXPECT_THAT(write(fd, data, sizeof(data)),
- SyscallSucceedsWithValue(sizeof(data)));
- EXPECT_THAT(sync_file_range(fd, 0, 0, SYNC_FILE_RANGE_WRITE),
- SyscallSucceeds());
- EXPECT_THAT(sync_file_range(fd, 0, 0, 0), SyscallSucceeds());
- EXPECT_THAT(
- sync_file_range(fd, 0, 0,
- SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_AFTER |
- SYNC_FILE_RANGE_WAIT_BEFORE),
- SyscallSucceeds());
- EXPECT_THAT(sync_file_range(
- fd, 0, 1, SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_AFTER),
- SyscallSucceeds());
- EXPECT_THAT(sync_file_range(
- fd, 1, 0, SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_AFTER),
- SyscallSucceeds());
-}
-
-TEST(SyncFileRangeTest, CannotSyncFileRangeOnUnopenedFd) {
- auto tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- auto f = ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path(), O_RDWR));
- constexpr char data[] = "some data to sync";
- int fd = f.get();
-
- EXPECT_THAT(write(fd, data, sizeof(data)),
- SyscallSucceedsWithValue(sizeof(data)));
-
- pid_t pid = fork();
- if (pid == 0) {
- f.reset();
-
- // fd is now invalid.
- TEST_CHECK(sync_file_range(fd, 0, 0, SYNC_FILE_RANGE_WRITE) == -1);
- TEST_PCHECK(errno == EBADF);
- _exit(0);
- }
- ASSERT_THAT(pid, SyscallSucceeds());
-
- int status = 0;
- ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFEXITED(status));
- EXPECT_EQ(WEXITSTATUS(status), 0);
-}
-
-TEST(SyncFileRangeTest, BadArgs) {
- auto tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- auto f = ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path(), O_RDWR));
- int fd = f.get();
-
- EXPECT_THAT(sync_file_range(fd, -1, 0, 0), SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(sync_file_range(fd, 0, -1, 0), SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(sync_file_range(fd, 8912, INT64_MAX - 4096, 0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(SyncFileRangeTest, CannotSyncFileRangeWithWaitBefore) {
- auto tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- auto f = ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path(), O_RDWR));
- constexpr char data[] = "some data to sync";
- int fd = f.get();
-
- EXPECT_THAT(write(fd, data, sizeof(data)),
- SyscallSucceedsWithValue(sizeof(data)));
- if (IsRunningOnGvisor()) {
- EXPECT_THAT(sync_file_range(fd, 0, 0, SYNC_FILE_RANGE_WAIT_BEFORE),
- SyscallFailsWithErrno(ENOSYS));
- EXPECT_THAT(
- sync_file_range(fd, 0, 0,
- SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE),
- SyscallFailsWithErrno(ENOSYS));
- }
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/sysinfo.cc b/test/syscalls/linux/sysinfo.cc
deleted file mode 100644
index 1a71256da..000000000
--- a/test/syscalls/linux/sysinfo.cc
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// This is a very simple sanity test to validate that the sysinfo syscall is
-// supported by gvisor and returns sane values.
-#include <sys/syscall.h>
-#include <sys/sysinfo.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(SysinfoTest, SysinfoIsCallable) {
- struct sysinfo ignored = {};
- EXPECT_THAT(syscall(SYS_sysinfo, &ignored), SyscallSucceedsWithValue(0));
-}
-
-TEST(SysinfoTest, EfaultProducedOnBadAddress) {
- // Validate that we return EFAULT when a bad address is provided.
- // specified by man 2 sysinfo
- EXPECT_THAT(syscall(SYS_sysinfo, nullptr), SyscallFailsWithErrno(EFAULT));
-}
-
-TEST(SysinfoTest, TotalRamSaneValue) {
- struct sysinfo s = {};
- EXPECT_THAT(sysinfo(&s), SyscallSucceedsWithValue(0));
- EXPECT_GT(s.totalram, 0);
-}
-
-TEST(SysinfoTest, MemunitSet) {
- struct sysinfo s = {};
- EXPECT_THAT(sysinfo(&s), SyscallSucceedsWithValue(0));
- EXPECT_GE(s.mem_unit, 1);
-}
-
-TEST(SysinfoTest, UptimeSaneValue) {
- struct sysinfo s = {};
- EXPECT_THAT(sysinfo(&s), SyscallSucceedsWithValue(0));
- EXPECT_GE(s.uptime, 0);
-}
-
-TEST(SysinfoTest, UptimeIncreasingValue) {
- struct sysinfo s = {};
- EXPECT_THAT(sysinfo(&s), SyscallSucceedsWithValue(0));
- absl::SleepFor(absl::Seconds(2));
- struct sysinfo s2 = {};
- EXPECT_THAT(sysinfo(&s2), SyscallSucceedsWithValue(0));
- EXPECT_LT(s.uptime, s2.uptime);
-}
-
-TEST(SysinfoTest, FreeRamSaneValue) {
- struct sysinfo s = {};
- EXPECT_THAT(sysinfo(&s), SyscallSucceedsWithValue(0));
- EXPECT_GT(s.freeram, 0);
- EXPECT_LT(s.freeram, s.totalram);
-}
-
-TEST(SysinfoTest, NumProcsSaneValue) {
- struct sysinfo s = {};
- EXPECT_THAT(sysinfo(&s), SyscallSucceedsWithValue(0));
- EXPECT_GT(s.procs, 0);
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/syslog.cc b/test/syscalls/linux/syslog.cc
deleted file mode 100644
index 9a7407d96..000000000
--- a/test/syscalls/linux/syslog.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sys/klog.h>
-#include <sys/syscall.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-constexpr int SYSLOG_ACTION_READ_ALL = 3;
-constexpr int SYSLOG_ACTION_SIZE_BUFFER = 10;
-
-int Syslog(int type, char* buf, int len) {
- return syscall(__NR_syslog, type, buf, len);
-}
-
-// Only SYSLOG_ACTION_SIZE_BUFFER and SYSLOG_ACTION_READ_ALL are implemented in
-// gVisor.
-
-TEST(Syslog, Size) {
- EXPECT_THAT(Syslog(SYSLOG_ACTION_SIZE_BUFFER, nullptr, 0), SyscallSucceeds());
-}
-
-TEST(Syslog, ReadAll) {
- // There might not be anything to read, so we can't check the write count.
- char buf[100];
- EXPECT_THAT(Syslog(SYSLOG_ACTION_READ_ALL, buf, sizeof(buf)),
- SyscallSucceeds());
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/sysret.cc b/test/syscalls/linux/sysret.cc
deleted file mode 100644
index 819fa655a..000000000
--- a/test/syscalls/linux/sysret.cc
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Tests to verify that the behavior of linux and gvisor matches when
-// 'sysret' returns to bad (aka non-canonical) %rip or %rsp.
-#include <sys/ptrace.h>
-#include <sys/user.h>
-
-#include "gtest/gtest.h"
-#include "test/util/logging.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-constexpr uint64_t kNonCanonicalRip = 0xCCCC000000000000;
-constexpr uint64_t kNonCanonicalRsp = 0xFFFF000000000000;
-
-class SysretTest : public ::testing::Test {
- protected:
- struct user_regs_struct regs_;
- pid_t child_;
-
- void SetUp() override {
- pid_t pid = fork();
-
- // Child.
- if (pid == 0) {
- TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0);
- MaybeSave();
- TEST_PCHECK(raise(SIGSTOP) == 0);
- MaybeSave();
- _exit(0);
- }
-
- // Parent.
- int status;
- ASSERT_THAT(pid, SyscallSucceeds()); // Might still be < 0.
- ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP);
- ASSERT_THAT(ptrace(PTRACE_GETREGS, pid, 0, &regs_), SyscallSucceeds());
-
- child_ = pid;
- }
-
- void Detach() {
- ASSERT_THAT(ptrace(PTRACE_DETACH, child_, 0, 0), SyscallSucceeds());
- }
-
- void SetRip(uint64_t newrip) {
- regs_.rip = newrip;
- ASSERT_THAT(ptrace(PTRACE_SETREGS, child_, 0, &regs_), SyscallSucceeds());
- }
-
- void SetRsp(uint64_t newrsp) {
- regs_.rsp = newrsp;
- ASSERT_THAT(ptrace(PTRACE_SETREGS, child_, 0, &regs_), SyscallSucceeds());
- }
-
- // Wait waits for the child pid and returns the exit status.
- int Wait() {
- int status;
- while (true) {
- int rval = wait4(child_, &status, 0, NULL);
- if (rval < 0) {
- return rval;
- }
- if (rval == child_) {
- return status;
- }
- }
- }
-};
-
-TEST_F(SysretTest, JustDetach) {
- Detach();
- int status = Wait();
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << "status = " << status;
-}
-
-TEST_F(SysretTest, BadRip) {
- SetRip(kNonCanonicalRip);
- Detach();
- int status = Wait();
- EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV)
- << "status = " << status;
-}
-
-TEST_F(SysretTest, BadRsp) {
- SetRsp(kNonCanonicalRsp);
- Detach();
- int status = Wait();
- EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGBUS)
- << "status = " << status;
-}
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/tcp_socket.cc b/test/syscalls/linux/tcp_socket.cc
deleted file mode 100644
index bfa031bce..000000000
--- a/test/syscalls/linux/tcp_socket.cc
+++ /dev/null
@@ -1,1159 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <poll.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <unistd.h>
-
-#include <limits>
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/posix_error.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-PosixErrorOr<sockaddr_storage> InetLoopbackAddr(int family) {
- struct sockaddr_storage addr;
- memset(&addr, 0, sizeof(addr));
- addr.ss_family = family;
- switch (family) {
- case AF_INET:
- reinterpret_cast<struct sockaddr_in*>(&addr)->sin_addr.s_addr =
- htonl(INADDR_LOOPBACK);
- break;
- case AF_INET6:
- reinterpret_cast<struct sockaddr_in6*>(&addr)->sin6_addr =
- in6addr_loopback;
- break;
- default:
- return PosixError(EINVAL,
- absl::StrCat("unknown socket family: ", family));
- }
- return addr;
-}
-
-// Fixture for tests parameterized by the address family to use (AF_INET and
-// AF_INET6) when creating sockets.
-class TcpSocketTest : public ::testing::TestWithParam<int> {
- protected:
- // Creates three sockets that will be used by test cases -- a listener, one
- // that connects, and the accepted one.
- void SetUp() override;
-
- // Closes the sockets created by SetUp().
- void TearDown() override;
-
- // Listening socket.
- int listener_ = -1;
-
- // Socket connected via connect().
- int s_ = -1;
-
- // Socket connected via accept().
- int t_ = -1;
-
- // Initial size of the send buffer.
- int sendbuf_size_ = -1;
-};
-
-void TcpSocketTest::SetUp() {
- ASSERT_THAT(listener_ = socket(GetParam(), SOCK_STREAM, IPPROTO_TCP),
- SyscallSucceeds());
-
- ASSERT_THAT(s_ = socket(GetParam(), SOCK_STREAM, IPPROTO_TCP),
- SyscallSucceeds());
-
- // Initialize address to the loopback one.
- sockaddr_storage addr =
- ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam()));
- socklen_t addrlen = sizeof(addr);
-
- // Bind to some port then start listening.
- ASSERT_THAT(
- bind(listener_, reinterpret_cast<struct sockaddr*>(&addr), addrlen),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(listener_, SOMAXCONN), SyscallSucceeds());
-
- // Get the address we're listening on, then connect to it. We need to do this
- // because we're allowing the stack to pick a port for us.
- ASSERT_THAT(getsockname(listener_, reinterpret_cast<struct sockaddr*>(&addr),
- &addrlen),
- SyscallSucceeds());
-
- ASSERT_THAT(RetryEINTR(connect)(s_, reinterpret_cast<struct sockaddr*>(&addr),
- addrlen),
- SyscallSucceeds());
-
- // Get the initial send buffer size.
- socklen_t optlen = sizeof(sendbuf_size_);
- ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &sendbuf_size_, &optlen),
- SyscallSucceeds());
-
- // Accept the connection.
- ASSERT_THAT(t_ = RetryEINTR(accept)(listener_, nullptr, nullptr),
- SyscallSucceeds());
-}
-
-void TcpSocketTest::TearDown() {
- EXPECT_THAT(close(listener_), SyscallSucceeds());
- if (s_ >= 0) {
- EXPECT_THAT(close(s_), SyscallSucceeds());
- }
- if (t_ >= 0) {
- EXPECT_THAT(close(t_), SyscallSucceeds());
- }
-}
-
-TEST_P(TcpSocketTest, DataCoalesced) {
- char buf[10];
-
- // Write in two steps.
- ASSERT_THAT(RetryEINTR(write)(s_, buf, sizeof(buf) / 2),
- SyscallSucceedsWithValue(sizeof(buf) / 2));
- ASSERT_THAT(RetryEINTR(write)(s_, buf, sizeof(buf) / 2),
- SyscallSucceedsWithValue(sizeof(buf) / 2));
-
- // Allow stack to process both packets.
- absl::SleepFor(absl::Seconds(1));
-
- // Read in one shot.
- EXPECT_THAT(RetryEINTR(recv)(t_, buf, sizeof(buf), 0),
- SyscallSucceedsWithValue(sizeof(buf)));
-}
-
-TEST_P(TcpSocketTest, SenderAddressIgnored) {
- char buf[3];
- ASSERT_THAT(RetryEINTR(write)(s_, buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- struct sockaddr_storage addr;
- socklen_t addrlen = sizeof(addr);
- memset(&addr, 0, sizeof(addr));
-
- ASSERT_THAT(
- RetryEINTR(recvfrom)(t_, buf, sizeof(buf), 0,
- reinterpret_cast<struct sockaddr*>(&addr), &addrlen),
- SyscallSucceedsWithValue(3));
-
- // Check that addr remains zeroed-out.
- const char* ptr = reinterpret_cast<char*>(&addr);
- for (size_t i = 0; i < sizeof(addr); i++) {
- EXPECT_EQ(ptr[i], 0);
- }
-}
-
-TEST_P(TcpSocketTest, SenderAddressIgnoredOnPeek) {
- char buf[3];
- ASSERT_THAT(RetryEINTR(write)(s_, buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- struct sockaddr_storage addr;
- socklen_t addrlen = sizeof(addr);
- memset(&addr, 0, sizeof(addr));
-
- ASSERT_THAT(
- RetryEINTR(recvfrom)(t_, buf, sizeof(buf), MSG_PEEK,
- reinterpret_cast<struct sockaddr*>(&addr), &addrlen),
- SyscallSucceedsWithValue(3));
-
- // Check that addr remains zeroed-out.
- const char* ptr = reinterpret_cast<char*>(&addr);
- for (size_t i = 0; i < sizeof(addr); i++) {
- EXPECT_EQ(ptr[i], 0);
- }
-}
-
-TEST_P(TcpSocketTest, SendtoAddressIgnored) {
- struct sockaddr_storage addr;
- memset(&addr, 0, sizeof(addr));
- addr.ss_family = GetParam(); // FIXME(b/63803955)
-
- char data = '\0';
- EXPECT_THAT(
- RetryEINTR(sendto)(s_, &data, sizeof(data), 0,
- reinterpret_cast<sockaddr*>(&addr), sizeof(addr)),
- SyscallSucceedsWithValue(1));
-}
-
-TEST_P(TcpSocketTest, WritevZeroIovec) {
- // 2 bytes just to be safe and have vecs[1] not point to something random
- // (even though length is 0).
- char buf[2];
- char recv_buf[1];
-
- // Construct a vec where the final vector is of length 0.
- iovec vecs[2] = {};
- vecs[0].iov_base = buf;
- vecs[0].iov_len = 1;
- vecs[1].iov_base = buf + 1;
- vecs[1].iov_len = 0;
-
- EXPECT_THAT(RetryEINTR(writev)(s_, vecs, 2), SyscallSucceedsWithValue(1));
-
- EXPECT_THAT(RetryEINTR(recv)(t_, recv_buf, 1, 0),
- SyscallSucceedsWithValue(1));
- EXPECT_EQ(memcmp(recv_buf, buf, 1), 0);
-}
-
-TEST_P(TcpSocketTest, ZeroWriteAllowed) {
- char buf[3];
- // Send a zero length packet.
- ASSERT_THAT(RetryEINTR(write)(s_, buf, 0), SyscallSucceedsWithValue(0));
- // Verify that there is no packet available.
- EXPECT_THAT(RetryEINTR(recv)(t_, buf, sizeof(buf), MSG_DONTWAIT),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-// Test that a non-blocking write with a buffer that is larger than the send
-// buffer size will not actually write the whole thing at once.
-TEST_P(TcpSocketTest, NonblockingLargeWrite) {
- // Set the FD to O_NONBLOCK.
- int opts;
- ASSERT_THAT(opts = fcntl(s_, F_GETFL), SyscallSucceeds());
- opts |= O_NONBLOCK;
- ASSERT_THAT(fcntl(s_, F_SETFL, opts), SyscallSucceeds());
-
- // Allocate a buffer three times the size of the send buffer. We do this with
- // a vector to avoid allocating on the stack.
- int size = 3 * sendbuf_size_;
- std::vector<char> buf(size);
-
- // Try to write the whole thing.
- int n;
- ASSERT_THAT(n = RetryEINTR(write)(s_, buf.data(), size), SyscallSucceeds());
-
- // We should have written something, but not the whole thing.
- EXPECT_GT(n, 0);
- EXPECT_LT(n, size);
-}
-
-// Test that a blocking write with a buffer that is larger than the send buffer
-// will block until the entire buffer is sent.
-TEST_P(TcpSocketTest, BlockingLargeWrite_NoRandomSave) {
- // Allocate a buffer three times the size of the send buffer on the heap. We
- // do this as a vector to avoid allocating on the stack.
- int size = 3 * sendbuf_size_;
- std::vector<char> writebuf(size);
-
- // Start reading the response in a loop.
- int read_bytes = 0;
- ScopedThread t([this, &read_bytes]() {
- // Avoid interrupting the blocking write in main thread.
- const DisableSave ds;
-
- // Take ownership of the FD so that we close it on failure. This will
- // unblock the blocking write below.
- FileDescriptor fd(t_);
- t_ = -1;
-
- char readbuf[2500] = {};
- int n = -1;
- while (n != 0) {
- ASSERT_THAT(n = RetryEINTR(read)(fd.get(), &readbuf, sizeof(readbuf)),
- SyscallSucceeds());
- read_bytes += n;
- }
- });
-
- // Try to write the whole thing.
- int n;
- ASSERT_THAT(n = WriteFd(s_, writebuf.data(), size), SyscallSucceeds());
-
- // We should have written the whole thing.
- EXPECT_EQ(n, size);
- EXPECT_THAT(close(s_), SyscallSucceedsWithValue(0));
- s_ = -1;
- t.Join();
-
- // We should have read the whole thing.
- EXPECT_EQ(read_bytes, size);
-}
-
-// Test that a send with MSG_DONTWAIT flag and buffer that larger than the send
-// buffer size will not write the whole thing.
-TEST_P(TcpSocketTest, LargeSendDontWait) {
- // Allocate a buffer three times the size of the send buffer. We do this on
- // with a vector to avoid allocating on the stack.
- int size = 3 * sendbuf_size_;
- std::vector<char> buf(size);
-
- // Try to write the whole thing with MSG_DONTWAIT flag, which can
- // return a partial write.
- int n;
- ASSERT_THAT(n = RetryEINTR(send)(s_, buf.data(), size, MSG_DONTWAIT),
- SyscallSucceeds());
-
- // We should have written something, but not the whole thing.
- EXPECT_GT(n, 0);
- EXPECT_LT(n, size);
-}
-
-// Test that a send on a non-blocking socket with a buffer that larger than the
-// send buffer will not write the whole thing at once.
-TEST_P(TcpSocketTest, NonblockingLargeSend) {
- // Set the FD to O_NONBLOCK.
- int opts;
- ASSERT_THAT(opts = fcntl(s_, F_GETFL), SyscallSucceeds());
- opts |= O_NONBLOCK;
- ASSERT_THAT(fcntl(s_, F_SETFL, opts), SyscallSucceeds());
-
- // Allocate a buffer three times the size of the send buffer. We do this on
- // with a vector to avoid allocating on the stack.
- int size = 3 * sendbuf_size_;
- std::vector<char> buf(size);
-
- // Try to write the whole thing.
- int n;
- ASSERT_THAT(n = RetryEINTR(send)(s_, buf.data(), size, 0), SyscallSucceeds());
-
- // We should have written something, but not the whole thing.
- EXPECT_GT(n, 0);
- EXPECT_LT(n, size);
-}
-
-// Same test as above, but calls send instead of write.
-TEST_P(TcpSocketTest, BlockingLargeSend_NoRandomSave) {
- // Allocate a buffer three times the size of the send buffer. We do this on
- // with a vector to avoid allocating on the stack.
- int size = 3 * sendbuf_size_;
- std::vector<char> writebuf(size);
-
- // Start reading the response in a loop.
- int read_bytes = 0;
- ScopedThread t([this, &read_bytes]() {
- // Avoid interrupting the blocking write in main thread.
- const DisableSave ds;
-
- // Take ownership of the FD so that we close it on failure. This will
- // unblock the blocking write below.
- FileDescriptor fd(t_);
- t_ = -1;
-
- char readbuf[2500] = {};
- int n = -1;
- while (n != 0) {
- ASSERT_THAT(n = RetryEINTR(read)(fd.get(), &readbuf, sizeof(readbuf)),
- SyscallSucceeds());
- read_bytes += n;
- }
- });
-
- // Try to send the whole thing.
- int n;
- ASSERT_THAT(n = SendFd(s_, writebuf.data(), size, 0), SyscallSucceeds());
-
- // We should have written the whole thing.
- EXPECT_EQ(n, size);
- EXPECT_THAT(close(s_), SyscallSucceedsWithValue(0));
- s_ = -1;
- t.Join();
-
- // We should have read the whole thing.
- EXPECT_EQ(read_bytes, size);
-}
-
-// Test that polling on a socket with a full send buffer will block.
-TEST_P(TcpSocketTest, PollWithFullBufferBlocks) {
- // Set the FD to O_NONBLOCK.
- int opts;
- ASSERT_THAT(opts = fcntl(s_, F_GETFL), SyscallSucceeds());
- opts |= O_NONBLOCK;
- ASSERT_THAT(fcntl(s_, F_SETFL, opts), SyscallSucceeds());
-
- // Set TCP_NODELAY, which will cause linux to fill the receive buffer from the
- // send buffer as quickly as possibly. This way we can fill up both buffers
- // faster.
- constexpr int tcp_nodelay_flag = 1;
- ASSERT_THAT(setsockopt(s_, IPPROTO_TCP, TCP_NODELAY, &tcp_nodelay_flag,
- sizeof(tcp_nodelay_flag)),
- SyscallSucceeds());
-
- // Create a large buffer that will be used for sending.
- std::vector<char> buf(10 * sendbuf_size_);
-
- // Write until we receive an error.
- while (RetryEINTR(send)(s_, buf.data(), buf.size(), 0) != -1) {
- // Sleep to give linux a chance to move data from the send buffer to the
- // receive buffer.
- usleep(10000); // 10ms.
- }
- // The last error should have been EWOULDBLOCK.
- ASSERT_EQ(errno, EWOULDBLOCK);
-}
-
-TEST_P(TcpSocketTest, MsgTrunc) {
- char sent_data[512];
- RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(RetryEINTR(send)(s_, sent_data, sizeof(sent_data), 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
- char received_data[sizeof(sent_data)] = {};
- ASSERT_THAT(
- RetryEINTR(recv)(t_, received_data, sizeof(received_data) / 2, MSG_TRUNC),
- SyscallSucceedsWithValue(sizeof(sent_data) / 2));
-
- // Check that we didn't get anything.
- char zeros[sizeof(received_data)] = {};
- EXPECT_EQ(0, memcmp(zeros, received_data, sizeof(received_data)));
-}
-
-// MSG_CTRUNC is a return flag but linux allows it to be set on input flags
-// without returning an error.
-TEST_P(TcpSocketTest, MsgTruncWithCtrunc) {
- char sent_data[512];
- RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(RetryEINTR(send)(s_, sent_data, sizeof(sent_data), 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
- char received_data[sizeof(sent_data)] = {};
- ASSERT_THAT(RetryEINTR(recv)(t_, received_data, sizeof(received_data) / 2,
- MSG_TRUNC | MSG_CTRUNC),
- SyscallSucceedsWithValue(sizeof(sent_data) / 2));
-
- // Check that we didn't get anything.
- char zeros[sizeof(received_data)] = {};
- EXPECT_EQ(0, memcmp(zeros, received_data, sizeof(received_data)));
-}
-
-// This test will verify that MSG_CTRUNC doesn't do anything when specified
-// on input.
-TEST_P(TcpSocketTest, MsgTruncWithCtruncOnly) {
- char sent_data[512];
- RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(RetryEINTR(send)(s_, sent_data, sizeof(sent_data), 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
- char received_data[sizeof(sent_data)] = {};
- ASSERT_THAT(RetryEINTR(recv)(t_, received_data, sizeof(received_data) / 2,
- MSG_CTRUNC),
- SyscallSucceedsWithValue(sizeof(sent_data) / 2));
-
- // Since MSG_CTRUNC here had no affect, it should not behave like MSG_TRUNC.
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data) / 2));
-}
-
-TEST_P(TcpSocketTest, MsgTruncLargeSize) {
- char sent_data[512];
- RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(RetryEINTR(send)(s_, sent_data, sizeof(sent_data), 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
- char received_data[sizeof(sent_data) * 2] = {};
- ASSERT_THAT(
- RetryEINTR(recv)(t_, received_data, sizeof(received_data), MSG_TRUNC),
- SyscallSucceedsWithValue(sizeof(sent_data)));
-
- // Check that we didn't get anything.
- char zeros[sizeof(received_data)] = {};
- EXPECT_EQ(0, memcmp(zeros, received_data, sizeof(received_data)));
-}
-
-TEST_P(TcpSocketTest, MsgTruncPeek) {
- char sent_data[512];
- RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(RetryEINTR(send)(s_, sent_data, sizeof(sent_data), 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
- char received_data[sizeof(sent_data)] = {};
- ASSERT_THAT(RetryEINTR(recv)(t_, received_data, sizeof(received_data) / 2,
- MSG_TRUNC | MSG_PEEK),
- SyscallSucceedsWithValue(sizeof(sent_data) / 2));
-
- // Check that we didn't get anything.
- char zeros[sizeof(received_data)] = {};
- EXPECT_EQ(0, memcmp(zeros, received_data, sizeof(received_data)));
-
- // Check that we can still get all of the data.
- ASSERT_THAT(RetryEINTR(recv)(t_, received_data, sizeof(received_data), 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
- EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
-}
-
-TEST_P(TcpSocketTest, NoDelayDefault) {
- int get = -1;
- socklen_t get_len = sizeof(get);
- EXPECT_THAT(getsockopt(s_, IPPROTO_TCP, TCP_NODELAY, &get, &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, kSockOptOff);
-}
-
-TEST_P(TcpSocketTest, SetNoDelay) {
- ASSERT_THAT(
- setsockopt(s_, IPPROTO_TCP, TCP_NODELAY, &kSockOptOn, sizeof(kSockOptOn)),
- SyscallSucceeds());
-
- int get = -1;
- socklen_t get_len = sizeof(get);
- EXPECT_THAT(getsockopt(s_, IPPROTO_TCP, TCP_NODELAY, &get, &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, kSockOptOn);
-
- ASSERT_THAT(setsockopt(s_, IPPROTO_TCP, TCP_NODELAY, &kSockOptOff,
- sizeof(kSockOptOff)),
- SyscallSucceeds());
-
- EXPECT_THAT(getsockopt(s_, IPPROTO_TCP, TCP_NODELAY, &get, &get_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, kSockOptOff);
-}
-
-#ifndef TCP_INQ
-#define TCP_INQ 36
-#endif
-
-TEST_P(TcpSocketTest, TcpInqSetSockOpt) {
- char buf[1024];
- ASSERT_THAT(RetryEINTR(write)(s_, buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- // TCP_INQ is disabled by default.
- int val = -1;
- socklen_t slen = sizeof(val);
- EXPECT_THAT(getsockopt(t_, SOL_TCP, TCP_INQ, &val, &slen),
- SyscallSucceedsWithValue(0));
- ASSERT_EQ(val, 0);
-
- // Try to set TCP_INQ.
- val = 1;
- EXPECT_THAT(setsockopt(t_, SOL_TCP, TCP_INQ, &val, sizeof(val)),
- SyscallSucceedsWithValue(0));
- val = -1;
- slen = sizeof(val);
- EXPECT_THAT(getsockopt(t_, SOL_TCP, TCP_INQ, &val, &slen),
- SyscallSucceedsWithValue(0));
- ASSERT_EQ(val, 1);
-
- // Try to unset TCP_INQ.
- val = 0;
- EXPECT_THAT(setsockopt(t_, SOL_TCP, TCP_INQ, &val, sizeof(val)),
- SyscallSucceedsWithValue(0));
- val = -1;
- slen = sizeof(val);
- EXPECT_THAT(getsockopt(t_, SOL_TCP, TCP_INQ, &val, &slen),
- SyscallSucceedsWithValue(0));
- ASSERT_EQ(val, 0);
-}
-
-TEST_P(TcpSocketTest, TcpInq) {
- char buf[1024];
- // Write more than one TCP segment.
- int size = sizeof(buf);
- int kChunk = sizeof(buf) / 4;
- for (int i = 0; i < size; i += kChunk) {
- ASSERT_THAT(RetryEINTR(write)(s_, buf, kChunk),
- SyscallSucceedsWithValue(kChunk));
- }
-
- int val = 1;
- kChunk = sizeof(buf) / 2;
- EXPECT_THAT(setsockopt(t_, SOL_TCP, TCP_INQ, &val, sizeof(val)),
- SyscallSucceedsWithValue(0));
-
- // Wait when all data will be in the received queue.
- while (true) {
- ASSERT_THAT(ioctl(t_, TIOCINQ, &size), SyscallSucceeds());
- if (size == sizeof(buf)) {
- break;
- }
- absl::SleepFor(absl::Milliseconds(10));
- }
-
- struct msghdr msg = {};
- std::vector<char> control(CMSG_SPACE(sizeof(int)));
- size = sizeof(buf);
- struct iovec iov;
- for (int i = 0; size != 0; i += kChunk) {
- msg.msg_control = &control[0];
- msg.msg_controllen = control.size();
-
- iov.iov_base = buf;
- iov.iov_len = kChunk;
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
- ASSERT_THAT(RetryEINTR(recvmsg)(t_, &msg, 0),
- SyscallSucceedsWithValue(kChunk));
- size -= kChunk;
-
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
- ASSERT_NE(cmsg, nullptr);
- ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(int)));
- ASSERT_EQ(cmsg->cmsg_level, SOL_TCP);
- ASSERT_EQ(cmsg->cmsg_type, TCP_INQ);
-
- int inq = 0;
- memcpy(&inq, CMSG_DATA(cmsg), sizeof(int));
- ASSERT_EQ(inq, size);
- }
-}
-
-TEST_P(TcpSocketTest, Tiocinq) {
- char buf[1024];
- size_t size = sizeof(buf);
- ASSERT_THAT(RetryEINTR(write)(s_, buf, size), SyscallSucceedsWithValue(size));
-
- uint32_t seed = time(nullptr);
- const size_t max_chunk = size / 10;
- while (size > 0) {
- size_t chunk = (rand_r(&seed) % max_chunk) + 1;
- ssize_t read = RetryEINTR(recvfrom)(t_, buf, chunk, 0, nullptr, nullptr);
- ASSERT_THAT(read, SyscallSucceeds());
- size -= read;
-
- int inq = 0;
- ASSERT_THAT(ioctl(t_, TIOCINQ, &inq), SyscallSucceeds());
- ASSERT_EQ(inq, size);
- }
-}
-
-TEST_P(TcpSocketTest, TcpSCMPriority) {
- char buf[1024];
- ASSERT_THAT(RetryEINTR(write)(s_, buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- int val = 1;
- EXPECT_THAT(setsockopt(t_, SOL_TCP, TCP_INQ, &val, sizeof(val)),
- SyscallSucceedsWithValue(0));
- EXPECT_THAT(setsockopt(t_, SOL_SOCKET, SO_TIMESTAMP, &val, sizeof(val)),
- SyscallSucceedsWithValue(0));
-
- struct msghdr msg = {};
- std::vector<char> control(
- CMSG_SPACE(sizeof(struct timeval) + CMSG_SPACE(sizeof(int))));
- struct iovec iov;
- msg.msg_control = &control[0];
- msg.msg_controllen = control.size();
-
- iov.iov_base = buf;
- iov.iov_len = sizeof(buf);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
- ASSERT_THAT(RetryEINTR(recvmsg)(t_, &msg, 0),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
- ASSERT_NE(cmsg, nullptr);
- // TODO(b/78348848): SO_TIMESTAMP isn't implemented for TCP sockets.
- if (!IsRunningOnGvisor() || cmsg->cmsg_level == SOL_SOCKET) {
- ASSERT_EQ(cmsg->cmsg_level, SOL_SOCKET);
- ASSERT_EQ(cmsg->cmsg_type, SO_TIMESTAMP);
- ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(struct timeval)));
-
- cmsg = CMSG_NXTHDR(&msg, cmsg);
- ASSERT_NE(cmsg, nullptr);
- }
- ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(int)));
- ASSERT_EQ(cmsg->cmsg_level, SOL_TCP);
- ASSERT_EQ(cmsg->cmsg_type, TCP_INQ);
-
- int inq = 0;
- memcpy(&inq, CMSG_DATA(cmsg), sizeof(int));
- ASSERT_EQ(inq, 0);
-
- cmsg = CMSG_NXTHDR(&msg, cmsg);
- ASSERT_EQ(cmsg, nullptr);
-}
-
-INSTANTIATE_TEST_SUITE_P(AllInetTests, TcpSocketTest,
- ::testing::Values(AF_INET, AF_INET6));
-
-// Fixture for tests parameterized by address family that don't want the fixture
-// to do things.
-using SimpleTcpSocketTest = ::testing::TestWithParam<int>;
-
-TEST_P(SimpleTcpSocketTest, SendUnconnected) {
- int fd;
- ASSERT_THAT(fd = socket(GetParam(), SOCK_STREAM, IPPROTO_TCP),
- SyscallSucceeds());
- FileDescriptor sock_fd(fd);
-
- char data = '\0';
- EXPECT_THAT(RetryEINTR(send)(fd, &data, sizeof(data), 0),
- SyscallFailsWithErrno(EPIPE));
-}
-
-TEST_P(SimpleTcpSocketTest, SendtoWithoutAddressUnconnected) {
- int fd;
- ASSERT_THAT(fd = socket(GetParam(), SOCK_STREAM, IPPROTO_TCP),
- SyscallSucceeds());
- FileDescriptor sock_fd(fd);
-
- char data = '\0';
- EXPECT_THAT(RetryEINTR(sendto)(fd, &data, sizeof(data), 0, nullptr, 0),
- SyscallFailsWithErrno(EPIPE));
-}
-
-TEST_P(SimpleTcpSocketTest, SendtoWithAddressUnconnected) {
- int fd;
- ASSERT_THAT(fd = socket(GetParam(), SOCK_STREAM, IPPROTO_TCP),
- SyscallSucceeds());
- FileDescriptor sock_fd(fd);
-
- sockaddr_storage addr =
- ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam()));
- char data = '\0';
- EXPECT_THAT(
- RetryEINTR(sendto)(fd, &data, sizeof(data), 0,
- reinterpret_cast<sockaddr*>(&addr), sizeof(addr)),
- SyscallFailsWithErrno(EPIPE));
-}
-
-TEST_P(SimpleTcpSocketTest, GetPeerNameUnconnected) {
- int fd;
- ASSERT_THAT(fd = socket(GetParam(), SOCK_STREAM, IPPROTO_TCP),
- SyscallSucceeds());
- FileDescriptor sock_fd(fd);
-
- sockaddr_storage addr;
- socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(getpeername(fd, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallFailsWithErrno(ENOTCONN));
-}
-
-TEST_P(TcpSocketTest, FullBuffer) {
- // Set both FDs to be blocking.
- int flags = 0;
- ASSERT_THAT(flags = fcntl(s_, F_GETFL), SyscallSucceeds());
- EXPECT_THAT(fcntl(s_, F_SETFL, flags & ~O_NONBLOCK), SyscallSucceeds());
- flags = 0;
- ASSERT_THAT(flags = fcntl(t_, F_GETFL), SyscallSucceeds());
- EXPECT_THAT(fcntl(t_, F_SETFL, flags & ~O_NONBLOCK), SyscallSucceeds());
-
- // 2500 was chosen as a small value that can be set on Linux.
- int set_snd = 2500;
- EXPECT_THAT(setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &set_snd, sizeof(set_snd)),
- SyscallSucceedsWithValue(0));
- int get_snd = -1;
- socklen_t get_snd_len = sizeof(get_snd);
- EXPECT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &get_snd, &get_snd_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_snd_len, sizeof(get_snd));
- EXPECT_GT(get_snd, 0);
-
- // 2500 was chosen as a small value that can be set on Linux and gVisor.
- int set_rcv = 2500;
- EXPECT_THAT(setsockopt(t_, SOL_SOCKET, SO_RCVBUF, &set_rcv, sizeof(set_rcv)),
- SyscallSucceedsWithValue(0));
- int get_rcv = -1;
- socklen_t get_rcv_len = sizeof(get_rcv);
- EXPECT_THAT(getsockopt(t_, SOL_SOCKET, SO_RCVBUF, &get_rcv, &get_rcv_len),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get_rcv_len, sizeof(get_rcv));
- EXPECT_GE(get_rcv, 2500);
-
- // Quick sanity test.
- EXPECT_LT(get_snd + get_rcv, 2500 * IOV_MAX);
-
- char data[2500] = {};
- std::vector<struct iovec> iovecs;
- for (int i = 0; i < IOV_MAX; i++) {
- struct iovec iov = {};
- iov.iov_base = data;
- iov.iov_len = sizeof(data);
- iovecs.push_back(iov);
- }
- ScopedThread t([this, &iovecs]() {
- int result = -1;
- EXPECT_THAT(result = RetryEINTR(writev)(s_, iovecs.data(), iovecs.size()),
- SyscallSucceeds());
- EXPECT_GT(result, 1);
- EXPECT_LT(result, sizeof(data) * iovecs.size());
- });
-
- char recv = 0;
- EXPECT_THAT(RetryEINTR(read)(t_, &recv, 1), SyscallSucceedsWithValue(1));
- EXPECT_THAT(close(t_), SyscallSucceedsWithValue(0));
- t_ = -1;
-}
-
-TEST_P(SimpleTcpSocketTest, NonBlockingConnectNoListener) {
- // Initialize address to the loopback one.
- sockaddr_storage addr =
- ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam()));
- socklen_t addrlen = sizeof(addr);
-
- const FileDescriptor s =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP));
-
- // Set the FD to O_NONBLOCK.
- int opts;
- ASSERT_THAT(opts = fcntl(s.get(), F_GETFL), SyscallSucceeds());
- opts |= O_NONBLOCK;
- ASSERT_THAT(fcntl(s.get(), F_SETFL, opts), SyscallSucceeds());
-
- ASSERT_THAT(RetryEINTR(connect)(
- s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen),
- SyscallFailsWithErrno(EINPROGRESS));
-
- // Now polling on the FD with a timeout should return 0 corresponding to no
- // FDs ready.
- struct pollfd poll_fd = {s.get(), POLLOUT, 0};
- EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 10000),
- SyscallSucceedsWithValue(1));
-
- int err;
- socklen_t optlen = sizeof(err);
- ASSERT_THAT(getsockopt(s.get(), SOL_SOCKET, SO_ERROR, &err, &optlen),
- SyscallSucceeds());
-
- EXPECT_EQ(err, ECONNREFUSED);
-}
-
-TEST_P(SimpleTcpSocketTest, NonBlockingConnect) {
- const FileDescriptor listener =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP));
-
- // Initialize address to the loopback one.
- sockaddr_storage addr =
- ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam()));
- socklen_t addrlen = sizeof(addr);
-
- // Bind to some port then start listening.
- ASSERT_THAT(
- bind(listener.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(listener.get(), SOMAXCONN), SyscallSucceeds());
-
- FileDescriptor s =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP));
-
- // Set the FD to O_NONBLOCK.
- int opts;
- ASSERT_THAT(opts = fcntl(s.get(), F_GETFL), SyscallSucceeds());
- opts |= O_NONBLOCK;
- ASSERT_THAT(fcntl(s.get(), F_SETFL, opts), SyscallSucceeds());
-
- ASSERT_THAT(getsockname(listener.get(),
- reinterpret_cast<struct sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
-
- ASSERT_THAT(RetryEINTR(connect)(
- s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen),
- SyscallFailsWithErrno(EINPROGRESS));
-
- int t;
- ASSERT_THAT(t = RetryEINTR(accept)(listener.get(), nullptr, nullptr),
- SyscallSucceeds());
-
- // Now polling on the FD with a timeout should return 0 corresponding to no
- // FDs ready.
- struct pollfd poll_fd = {s.get(), POLLOUT, 0};
- EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 10000),
- SyscallSucceedsWithValue(1));
-
- int err;
- socklen_t optlen = sizeof(err);
- ASSERT_THAT(getsockopt(s.get(), SOL_SOCKET, SO_ERROR, &err, &optlen),
- SyscallSucceeds());
-
- EXPECT_EQ(err, 0);
-
- EXPECT_THAT(close(t), SyscallSucceeds());
-}
-
-TEST_P(SimpleTcpSocketTest, NonBlockingConnectRemoteClose) {
- const FileDescriptor listener =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP));
-
- // Initialize address to the loopback one.
- sockaddr_storage addr =
- ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam()));
- socklen_t addrlen = sizeof(addr);
-
- // Bind to some port then start listening.
- ASSERT_THAT(
- bind(listener.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen),
- SyscallSucceeds());
-
- ASSERT_THAT(listen(listener.get(), SOMAXCONN), SyscallSucceeds());
-
- FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(GetParam(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP));
-
- ASSERT_THAT(getsockname(listener.get(),
- reinterpret_cast<struct sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
-
- ASSERT_THAT(RetryEINTR(connect)(
- s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen),
- SyscallFailsWithErrno(EINPROGRESS));
-
- int t;
- ASSERT_THAT(t = RetryEINTR(accept)(listener.get(), nullptr, nullptr),
- SyscallSucceeds());
-
- EXPECT_THAT(close(t), SyscallSucceeds());
-
- // Now polling on the FD with a timeout should return 0 corresponding to no
- // FDs ready.
- struct pollfd poll_fd = {s.get(), POLLOUT, 0};
- EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 10000),
- SyscallSucceedsWithValue(1));
-
- ASSERT_THAT(RetryEINTR(connect)(
- s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen),
- SyscallSucceeds());
-
- ASSERT_THAT(RetryEINTR(connect)(
- s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen),
- SyscallFailsWithErrno(EISCONN));
-}
-
-// Test that we get an ECONNREFUSED with a blocking socket when no one is
-// listening on the other end.
-TEST_P(SimpleTcpSocketTest, BlockingConnectRefused) {
- FileDescriptor s =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP));
-
- // Initialize address to the loopback one.
- sockaddr_storage addr =
- ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam()));
- socklen_t addrlen = sizeof(addr);
-
- ASSERT_THAT(RetryEINTR(connect)(
- s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen),
- SyscallFailsWithErrno(ECONNREFUSED));
-
- // Avoiding triggering save in destructor of s.
- EXPECT_THAT(close(s.release()), SyscallSucceeds());
-}
-
-// Test that we get an ECONNREFUSED with a nonblocking socket.
-TEST_P(SimpleTcpSocketTest, NonBlockingConnectRefused) {
- FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(GetParam(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP));
-
- // Initialize address to the loopback one.
- sockaddr_storage addr =
- ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam()));
- socklen_t addrlen = sizeof(addr);
-
- ASSERT_THAT(RetryEINTR(connect)(
- s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen),
- SyscallFailsWithErrno(EINPROGRESS));
-
- // We don't need to specify any events to get POLLHUP or POLLERR as these
- // are added before the poll.
- struct pollfd poll_fd = {s.get(), /*events=*/0, 0};
- EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 1000), SyscallSucceedsWithValue(1));
-
- // The ECONNREFUSED should cause us to be woken up with POLLHUP.
- EXPECT_NE(poll_fd.revents & (POLLHUP | POLLERR), 0);
-
- // Avoiding triggering save in destructor of s.
- EXPECT_THAT(close(s.release()), SyscallSucceeds());
-}
-
-// Test that setting a supported congestion control algorithm succeeds for an
-// unconnected TCP socket
-TEST_P(SimpleTcpSocketTest, SetCongestionControlSucceedsForSupported) {
- // This is Linux's net/tcp.h TCP_CA_NAME_MAX.
- const int kTcpCaNameMax = 16;
-
- FileDescriptor s =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP));
- {
- const char kSetCC[kTcpCaNameMax] = "reno";
- ASSERT_THAT(setsockopt(s.get(), IPPROTO_TCP, TCP_CONGESTION, &kSetCC,
- strlen(kSetCC)),
- SyscallSucceedsWithValue(0));
-
- char got_cc[kTcpCaNameMax];
- memset(got_cc, '1', sizeof(got_cc));
- socklen_t optlen = sizeof(got_cc);
- ASSERT_THAT(
- getsockopt(s.get(), IPPROTO_TCP, TCP_CONGESTION, &got_cc, &optlen),
- SyscallSucceedsWithValue(0));
- // We ignore optlen here as the linux kernel sets optlen to the lower of the
- // size of the buffer passed in or kTcpCaNameMax and not the length of the
- // congestion control algorithm's actual name.
- EXPECT_EQ(0, memcmp(got_cc, kSetCC, sizeof(kTcpCaNameMax)));
- }
- {
- const char kSetCC[kTcpCaNameMax] = "cubic";
- ASSERT_THAT(setsockopt(s.get(), IPPROTO_TCP, TCP_CONGESTION, &kSetCC,
- strlen(kSetCC)),
- SyscallSucceedsWithValue(0));
-
- char got_cc[kTcpCaNameMax];
- memset(got_cc, '1', sizeof(got_cc));
- socklen_t optlen = sizeof(got_cc);
- ASSERT_THAT(
- getsockopt(s.get(), IPPROTO_TCP, TCP_CONGESTION, &got_cc, &optlen),
- SyscallSucceedsWithValue(0));
- // We ignore optlen here as the linux kernel sets optlen to the lower of the
- // size of the buffer passed in or kTcpCaNameMax and not the length of the
- // congestion control algorithm's actual name.
- EXPECT_EQ(0, memcmp(got_cc, kSetCC, sizeof(kTcpCaNameMax)));
- }
-}
-
-// This test verifies that a getsockopt(...TCP_CONGESTION) behaviour is
-// consistent between linux and gvisor when the passed in buffer is smaller than
-// kTcpCaNameMax.
-TEST_P(SimpleTcpSocketTest, SetGetTCPCongestionShortReadBuffer) {
- FileDescriptor s =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP));
- {
- // Verify that getsockopt/setsockopt work with buffers smaller than
- // kTcpCaNameMax.
- const char kSetCC[] = "cubic";
- ASSERT_THAT(setsockopt(s.get(), IPPROTO_TCP, TCP_CONGESTION, &kSetCC,
- strlen(kSetCC)),
- SyscallSucceedsWithValue(0));
-
- char got_cc[sizeof(kSetCC)];
- socklen_t optlen = sizeof(got_cc);
- ASSERT_THAT(
- getsockopt(s.get(), IPPROTO_TCP, TCP_CONGESTION, &got_cc, &optlen),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(sizeof(got_cc), optlen);
- EXPECT_EQ(0, memcmp(got_cc, kSetCC, sizeof(got_cc)));
- }
-}
-
-// This test verifies that a getsockopt(...TCP_CONGESTION) behaviour is
-// consistent between linux and gvisor when the passed in buffer is larger than
-// kTcpCaNameMax.
-TEST_P(SimpleTcpSocketTest, SetGetTCPCongestionLargeReadBuffer) {
- // This is Linux's net/tcp.h TCP_CA_NAME_MAX.
- const int kTcpCaNameMax = 16;
-
- FileDescriptor s =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP));
- {
- // Verify that getsockopt works with buffers larger than
- // kTcpCaNameMax.
- const char kSetCC[] = "cubic";
- ASSERT_THAT(setsockopt(s.get(), IPPROTO_TCP, TCP_CONGESTION, &kSetCC,
- strlen(kSetCC)),
- SyscallSucceedsWithValue(0));
-
- char got_cc[kTcpCaNameMax + 5];
- socklen_t optlen = sizeof(got_cc);
- ASSERT_THAT(
- getsockopt(s.get(), IPPROTO_TCP, TCP_CONGESTION, &got_cc, &optlen),
- SyscallSucceedsWithValue(0));
- // Linux copies the minimum of kTcpCaNameMax or the length of the passed in
- // buffer and sets optlen to the number of bytes actually copied
- // irrespective of the actual length of the congestion control name.
- EXPECT_EQ(kTcpCaNameMax, optlen);
- EXPECT_EQ(0, memcmp(got_cc, kSetCC, sizeof(kSetCC)));
- }
-}
-
-// Test that setting an unsupported congestion control algorithm fails for an
-// unconnected TCP socket.
-TEST_P(SimpleTcpSocketTest, SetCongestionControlFailsForUnsupported) {
- // This is Linux's net/tcp.h TCP_CA_NAME_MAX.
- const int kTcpCaNameMax = 16;
-
- FileDescriptor s =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP));
- char old_cc[kTcpCaNameMax];
- socklen_t optlen = sizeof(old_cc);
- ASSERT_THAT(
- getsockopt(s.get(), IPPROTO_TCP, TCP_CONGESTION, &old_cc, &optlen),
- SyscallSucceedsWithValue(0));
-
- const char kSetCC[] = "invalid_ca_kSetCC";
- ASSERT_THAT(
- setsockopt(s.get(), SOL_TCP, TCP_CONGESTION, &kSetCC, strlen(kSetCC)),
- SyscallFailsWithErrno(ENOENT));
-
- char got_cc[kTcpCaNameMax];
- ASSERT_THAT(
- getsockopt(s.get(), IPPROTO_TCP, TCP_CONGESTION, &got_cc, &optlen),
- SyscallSucceedsWithValue(0));
- // We ignore optlen here as the linux kernel sets optlen to the lower of the
- // size of the buffer passed in or kTcpCaNameMax and not the length of the
- // congestion control algorithm's actual name.
- EXPECT_EQ(0, memcmp(got_cc, old_cc, sizeof(kTcpCaNameMax)));
-}
-
-TEST_P(SimpleTcpSocketTest, MaxSegDefault) {
- FileDescriptor s =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP));
-
- constexpr int kDefaultMSS = 536;
- int tcp_max_seg;
- socklen_t optlen = sizeof(tcp_max_seg);
- ASSERT_THAT(
- getsockopt(s.get(), IPPROTO_TCP, TCP_MAXSEG, &tcp_max_seg, &optlen),
- SyscallSucceedsWithValue(0));
-
- EXPECT_EQ(kDefaultMSS, tcp_max_seg);
- EXPECT_EQ(sizeof(tcp_max_seg), optlen);
-}
-
-TEST_P(SimpleTcpSocketTest, SetMaxSeg) {
- FileDescriptor s =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP));
-
- constexpr int kDefaultMSS = 536;
- constexpr int kTCPMaxSeg = 1024;
- ASSERT_THAT(setsockopt(s.get(), IPPROTO_TCP, TCP_MAXSEG, &kTCPMaxSeg,
- sizeof(kTCPMaxSeg)),
- SyscallSucceedsWithValue(0));
-
- // Linux actually never returns the user_mss value. It will always return the
- // default MSS value defined above for an unconnected socket and always return
- // the actual current MSS for a connected one.
- int optval;
- socklen_t optlen = sizeof(optval);
- ASSERT_THAT(getsockopt(s.get(), IPPROTO_TCP, TCP_MAXSEG, &optval, &optlen),
- SyscallSucceedsWithValue(0));
-
- EXPECT_EQ(kDefaultMSS, optval);
- EXPECT_EQ(sizeof(optval), optlen);
-}
-
-TEST_P(SimpleTcpSocketTest, SetMaxSegFailsForInvalidMSSValues) {
- FileDescriptor s =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP));
-
- {
- constexpr int tcp_max_seg = 10;
- ASSERT_THAT(setsockopt(s.get(), IPPROTO_TCP, TCP_MAXSEG, &tcp_max_seg,
- sizeof(tcp_max_seg)),
- SyscallFailsWithErrno(EINVAL));
- }
- {
- constexpr int tcp_max_seg = 75000;
- ASSERT_THAT(setsockopt(s.get(), IPPROTO_TCP, TCP_MAXSEG, &tcp_max_seg,
- sizeof(tcp_max_seg)),
- SyscallFailsWithErrno(EINVAL));
- }
-}
-
-INSTANTIATE_TEST_SUITE_P(AllInetTests, SimpleTcpSocketTest,
- ::testing::Values(AF_INET, AF_INET6));
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/temp_umask.h b/test/syscalls/linux/temp_umask.h
deleted file mode 100644
index 81a25440c..000000000
--- a/test/syscalls/linux/temp_umask.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_TEMP_UMASK_H_
-#define GVISOR_TEST_SYSCALLS_TEMP_UMASK_H_
-
-#include <sys/stat.h>
-#include <sys/types.h>
-
-namespace gvisor {
-namespace testing {
-
-class TempUmask {
- public:
- // Sets the process umask to `mask`.
- explicit TempUmask(mode_t mask) : old_mask_(umask(mask)) {}
-
- // Sets the process umask to its previous value.
- ~TempUmask() { umask(old_mask_); }
-
- private:
- mode_t old_mask_;
-};
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_TEMP_UMASK_H_
diff --git a/test/syscalls/linux/tgkill.cc b/test/syscalls/linux/tgkill.cc
deleted file mode 100644
index 80acae5de..000000000
--- a/test/syscalls/linux/tgkill.cc
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <sys/syscall.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "test/util/signal_util.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(TgkillTest, InvalidTID) {
- EXPECT_THAT(tgkill(getpid(), -1, 0), SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(tgkill(getpid(), 0, 0), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(TgkillTest, InvalidTGID) {
- EXPECT_THAT(tgkill(-1, gettid(), 0), SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(tgkill(0, gettid(), 0), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(TgkillTest, ValidInput) {
- EXPECT_THAT(tgkill(getpid(), gettid(), 0), SyscallSucceeds());
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/time.cc b/test/syscalls/linux/time.cc
deleted file mode 100644
index c7eead17e..000000000
--- a/test/syscalls/linux/time.cc
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <time.h>
-
-#include "gtest/gtest.h"
-#include "test/util/proc_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-constexpr long kFudgeSeconds = 5;
-
-// Mimics the time(2) wrapper from glibc prior to 2.15.
-time_t vsyscall_time(time_t* t) {
- constexpr uint64_t kVsyscallTimeEntry = 0xffffffffff600400;
- return reinterpret_cast<time_t (*)(time_t*)>(kVsyscallTimeEntry)(t);
-}
-
-TEST(TimeTest, VsyscallTime_Succeeds) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsVsyscallEnabled()));
-
- time_t t1, t2;
-
- {
- const DisableSave ds; // Timing assertions.
- EXPECT_THAT(time(&t1), SyscallSucceeds());
- EXPECT_THAT(vsyscall_time(&t2), SyscallSucceeds());
- }
-
- // Time should be monotonic.
- EXPECT_LE(static_cast<long>(t1), static_cast<long>(t2));
-
- // Check that it's within kFudge seconds.
- EXPECT_LE(static_cast<long>(t2), static_cast<long>(t1) + kFudgeSeconds);
-
- // Redo with save.
- EXPECT_THAT(time(&t1), SyscallSucceeds());
- EXPECT_THAT(vsyscall_time(&t2), SyscallSucceeds());
-
- // Time should be monotonic.
- EXPECT_LE(static_cast<long>(t1), static_cast<long>(t2));
-}
-
-TEST(TimeTest, VsyscallTime_InvalidAddressSIGSEGV) {
- EXPECT_EXIT(vsyscall_time(reinterpret_cast<time_t*>(0x1)),
- ::testing::KilledBySignal(SIGSEGV), "");
-}
-
-int vsyscall_gettimeofday(struct timeval* tv, struct timezone* tz) {
- constexpr uint64_t kVsyscallGettimeofdayEntry = 0xffffffffff600000;
- return reinterpret_cast<int (*)(struct timeval*, struct timezone*)>(
- kVsyscallGettimeofdayEntry)(tv, tz);
-}
-
-TEST(TimeTest, VsyscallGettimeofday_Succeeds) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsVsyscallEnabled()));
-
- struct timeval tv1, tv2;
- struct timezone tz1, tz2;
-
- {
- const DisableSave ds; // Timing assertions.
- EXPECT_THAT(gettimeofday(&tv1, &tz1), SyscallSucceeds());
- EXPECT_THAT(vsyscall_gettimeofday(&tv2, &tz2), SyscallSucceeds());
- }
-
- // See above.
- EXPECT_LE(static_cast<long>(tv1.tv_sec), static_cast<long>(tv2.tv_sec));
- EXPECT_LE(static_cast<long>(tv2.tv_sec),
- static_cast<long>(tv1.tv_sec) + kFudgeSeconds);
-
- // Redo with save.
- EXPECT_THAT(gettimeofday(&tv1, &tz1), SyscallSucceeds());
- EXPECT_THAT(vsyscall_gettimeofday(&tv2, &tz2), SyscallSucceeds());
-}
-
-TEST(TimeTest, VsyscallGettimeofday_InvalidAddressSIGSEGV) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsVsyscallEnabled()));
-
- EXPECT_EXIT(vsyscall_gettimeofday(reinterpret_cast<struct timeval*>(0x1),
- reinterpret_cast<struct timezone*>(0x1)),
- ::testing::KilledBySignal(SIGSEGV), "");
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/timerfd.cc b/test/syscalls/linux/timerfd.cc
deleted file mode 100644
index 86ed87b7c..000000000
--- a/test/syscalls/linux/timerfd.cc
+++ /dev/null
@@ -1,256 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <poll.h>
-#include <sys/timerfd.h>
-#include <time.h>
-
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/posix_error.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// Wrapper around timerfd_create(2) that returns a FileDescriptor.
-PosixErrorOr<FileDescriptor> TimerfdCreate(int clockid, int flags) {
- int fd = timerfd_create(clockid, flags);
- MaybeSave();
- if (fd < 0) {
- return PosixError(errno, "timerfd_create failed");
- }
- return FileDescriptor(fd);
-}
-
-// In tests that race a timerfd with a sleep, some slack is required because:
-//
-// - Timerfd expirations are asynchronous with respect to nanosleeps.
-//
-// - Because clock_gettime(CLOCK_MONOTONIC) is implemented through the VDSO,
-// it technically uses a closely-related, but distinct, time domain from the
-// CLOCK_MONOTONIC used to trigger timerfd expirations. The same applies to
-// CLOCK_BOOTTIME which is an alias for CLOCK_MONOTONIC.
-absl::Duration TimerSlack() { return absl::Milliseconds(500); }
-
-class TimerfdTest : public ::testing::TestWithParam<int> {};
-
-TEST_P(TimerfdTest, IsInitiallyStopped) {
- auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0));
- struct itimerspec its = {};
- ASSERT_THAT(timerfd_gettime(tfd.get(), &its), SyscallSucceeds());
- EXPECT_EQ(0, its.it_value.tv_sec);
- EXPECT_EQ(0, its.it_value.tv_nsec);
-}
-
-TEST_P(TimerfdTest, SingleShot) {
- constexpr absl::Duration kDelay = absl::Seconds(1);
-
- auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0));
- struct itimerspec its = {};
- its.it_value = absl::ToTimespec(kDelay);
- ASSERT_THAT(timerfd_settime(tfd.get(), /* flags = */ 0, &its, nullptr),
- SyscallSucceeds());
-
- // The timer should fire exactly once since the interval is zero.
- absl::SleepFor(kDelay + TimerSlack());
- uint64_t val = 0;
- ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)),
- SyscallSucceedsWithValue(sizeof(uint64_t)));
- EXPECT_EQ(1, val);
-}
-
-TEST_P(TimerfdTest, Periodic) {
- constexpr absl::Duration kDelay = absl::Seconds(1);
- constexpr int kPeriods = 3;
-
- auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0));
- struct itimerspec its = {};
- its.it_value = absl::ToTimespec(kDelay);
- its.it_interval = absl::ToTimespec(kDelay);
- ASSERT_THAT(timerfd_settime(tfd.get(), /* flags = */ 0, &its, nullptr),
- SyscallSucceeds());
-
- // Expect to see at least kPeriods expirations. More may occur due to the
- // timer slack, or due to delays from scheduling or save/restore.
- absl::SleepFor(kPeriods * kDelay + TimerSlack());
- uint64_t val = 0;
- ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)),
- SyscallSucceedsWithValue(sizeof(uint64_t)));
- EXPECT_GE(val, kPeriods);
-}
-
-TEST_P(TimerfdTest, BlockingRead) {
- constexpr absl::Duration kDelay = absl::Seconds(3);
-
- auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0));
- struct itimerspec its = {};
- its.it_value.tv_sec = absl::ToInt64Seconds(kDelay);
- auto const start_time = absl::Now();
- ASSERT_THAT(timerfd_settime(tfd.get(), /* flags = */ 0, &its, nullptr),
- SyscallSucceeds());
-
- // read should block until the timer fires.
- uint64_t val = 0;
- ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)),
- SyscallSucceedsWithValue(sizeof(uint64_t)));
- auto const end_time = absl::Now();
- EXPECT_EQ(1, val);
- EXPECT_GE((end_time - start_time) + TimerSlack(), kDelay);
-}
-
-TEST_P(TimerfdTest, NonblockingRead_NoRandomSave) {
- constexpr absl::Duration kDelay = absl::Seconds(5);
-
- auto const tfd =
- ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), TFD_NONBLOCK));
-
- // Since the timer is initially disabled and has never fired, read should
- // return EAGAIN.
- uint64_t val = 0;
- ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)),
- SyscallFailsWithErrno(EAGAIN));
-
- DisableSave ds; // Timing-sensitive.
-
- // Arm the timer.
- struct itimerspec its = {};
- its.it_value.tv_sec = absl::ToInt64Seconds(kDelay);
- ASSERT_THAT(timerfd_settime(tfd.get(), /* flags = */ 0, &its, nullptr),
- SyscallSucceeds());
-
- // Since the timer has not yet fired, read should return EAGAIN.
- ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)),
- SyscallFailsWithErrno(EAGAIN));
-
- ds.reset(); // No longer timing-sensitive.
-
- // After the timer fires, read should indicate 1 expiration.
- absl::SleepFor(kDelay + TimerSlack());
- ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)),
- SyscallSucceedsWithValue(sizeof(uint64_t)));
- EXPECT_EQ(1, val);
-
- // The successful read should have reset the number of expirations.
- ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST_P(TimerfdTest, BlockingPoll_SetTimeResetsExpirations) {
- constexpr absl::Duration kDelay = absl::Seconds(3);
-
- auto const tfd =
- ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), TFD_NONBLOCK));
- struct itimerspec its = {};
- its.it_value.tv_sec = absl::ToInt64Seconds(kDelay);
- auto const start_time = absl::Now();
- ASSERT_THAT(timerfd_settime(tfd.get(), /* flags = */ 0, &its, nullptr),
- SyscallSucceeds());
-
- // poll should block until the timer fires.
- struct pollfd pfd = {};
- pfd.fd = tfd.get();
- pfd.events = POLLIN;
- ASSERT_THAT(poll(&pfd, /* nfds = */ 1,
- /* timeout = */ 2 * absl::ToInt64Seconds(kDelay) * 1000),
- SyscallSucceedsWithValue(1));
- auto const end_time = absl::Now();
- EXPECT_EQ(POLLIN, pfd.revents);
- EXPECT_GE((end_time - start_time) + TimerSlack(), kDelay);
-
- // Call timerfd_settime again with a value of 0. This should reset the number
- // of expirations to 0, causing read to return EAGAIN since the timerfd is
- // non-blocking.
- its.it_value.tv_sec = 0;
- ASSERT_THAT(timerfd_settime(tfd.get(), /* flags = */ 0, &its, nullptr),
- SyscallSucceeds());
- uint64_t val = 0;
- ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST_P(TimerfdTest, SetAbsoluteTime) {
- constexpr absl::Duration kDelay = absl::Seconds(3);
-
- // Use a non-blocking timerfd so that if TFD_TIMER_ABSTIME is incorrectly
- // non-functional, we get EAGAIN rather than a test timeout.
- auto const tfd =
- ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), TFD_NONBLOCK));
- struct itimerspec its = {};
- ASSERT_THAT(clock_gettime(GetParam(), &its.it_value), SyscallSucceeds());
- its.it_value.tv_sec += absl::ToInt64Seconds(kDelay);
- ASSERT_THAT(timerfd_settime(tfd.get(), TFD_TIMER_ABSTIME, &its, nullptr),
- SyscallSucceeds());
-
- absl::SleepFor(kDelay + TimerSlack());
- uint64_t val = 0;
- ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)),
- SyscallSucceedsWithValue(sizeof(uint64_t)));
- EXPECT_EQ(1, val);
-}
-
-TEST_P(TimerfdTest, IllegalReadWrite) {
- auto const tfd =
- ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), TFD_NONBLOCK));
- uint64_t val = 0;
- EXPECT_THAT(PreadFd(tfd.get(), &val, sizeof(val), 0),
- SyscallFailsWithErrno(ESPIPE));
- EXPECT_THAT(WriteFd(tfd.get(), &val, sizeof(val)),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(PwriteFd(tfd.get(), &val, sizeof(val), 0),
- SyscallFailsWithErrno(ESPIPE));
-}
-
-std::string PrintClockId(::testing::TestParamInfo<int> info) {
- switch (info.param) {
- case CLOCK_MONOTONIC:
- return "CLOCK_MONOTONIC";
- case CLOCK_BOOTTIME:
- return "CLOCK_BOOTTIME";
- default:
- return absl::StrCat(info.param);
- }
-}
-
-INSTANTIATE_TEST_SUITE_P(AllTimerTypes, TimerfdTest,
- ::testing::Values(CLOCK_MONOTONIC, CLOCK_BOOTTIME),
- PrintClockId);
-
-TEST(TimerfdClockRealtimeTest, ClockRealtime) {
- // Since CLOCK_REALTIME can, by definition, change, we can't make any
- // non-flaky assertions about the amount of time it takes for a
- // CLOCK_REALTIME-based timer to expire. Just check that it expires at all,
- // and hope it happens before the test times out.
- constexpr int kDelaySecs = 1;
-
- auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(CLOCK_REALTIME, 0));
- struct itimerspec its = {};
- its.it_value.tv_sec = kDelaySecs;
- ASSERT_THAT(timerfd_settime(tfd.get(), /* flags = */ 0, &its, nullptr),
- SyscallSucceeds());
-
- uint64_t val = 0;
- ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)),
- SyscallSucceedsWithValue(sizeof(uint64_t)));
- EXPECT_EQ(1, val);
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/timers.cc b/test/syscalls/linux/timers.cc
deleted file mode 100644
index fd42e81e1..000000000
--- a/test/syscalls/linux/timers.cc
+++ /dev/null
@@ -1,645 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <signal.h>
-#include <sys/resource.h>
-#include <sys/time.h>
-#include <syscall.h>
-#include <time.h>
-#include <unistd.h>
-
-#include <atomic>
-
-#include "gtest/gtest.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/cleanup.h"
-#include "test/util/logging.h"
-#include "test/util/multiprocess_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/signal_util.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-DEFINE_bool(timers_test_sleep, false,
- "If true, sleep forever instead of running tests.");
-
-using ::testing::_;
-using ::testing::AnyOf;
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-#ifndef CPUCLOCK_PROF
-#define CPUCLOCK_PROF 0
-#endif // CPUCLOCK_PROF
-
-PosixErrorOr<absl::Duration> ProcessCPUTime(pid_t pid) {
- // Use pid-specific CPUCLOCK_PROF, which is the clock used to enforce
- // RLIMIT_CPU.
- clockid_t clockid = (~static_cast<clockid_t>(pid) << 3) | CPUCLOCK_PROF;
-
- struct timespec ts;
- int ret = clock_gettime(clockid, &ts);
- if (ret < 0) {
- return PosixError(errno, "clock_gettime failed");
- }
-
- return absl::DurationFromTimespec(ts);
-}
-
-void NoopSignalHandler(int signo) {
- TEST_CHECK_MSG(SIGXCPU == signo,
- "NoopSigHandler did not receive expected signal");
-}
-
-void UninstallingSignalHandler(int signo) {
- TEST_CHECK_MSG(SIGXCPU == signo,
- "UninstallingSignalHandler did not receive expected signal");
- struct sigaction rev_action;
- rev_action.sa_handler = SIG_DFL;
- rev_action.sa_flags = 0;
- sigemptyset(&rev_action.sa_mask);
- sigaction(SIGXCPU, &rev_action, nullptr);
-}
-
-TEST(TimerTest, ProcessKilledOnCPUSoftLimit) {
- constexpr absl::Duration kSoftLimit = absl::Seconds(1);
- constexpr absl::Duration kHardLimit = absl::Seconds(3);
-
- struct rlimit cpu_limits;
- cpu_limits.rlim_cur = absl::ToInt64Seconds(kSoftLimit);
- cpu_limits.rlim_max = absl::ToInt64Seconds(kHardLimit);
-
- int pid = fork();
- MaybeSave();
- if (pid == 0) {
- TEST_PCHECK(setrlimit(RLIMIT_CPU, &cpu_limits) == 0);
- MaybeSave();
- for (;;) {
- }
- }
- ASSERT_THAT(pid, SyscallSucceeds());
- auto c = Cleanup([pid] {
- int status;
- EXPECT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFSIGNALED(status));
- EXPECT_EQ(WTERMSIG(status), SIGXCPU);
- });
-
- // Wait for the child to exit, but do not reap it. This will allow us to check
- // its CPU usage while it is zombied.
- EXPECT_THAT(waitid(P_PID, pid, nullptr, WEXITED | WNOWAIT),
- SyscallSucceeds());
-
- // Assert that the child spent 1s of CPU before getting killed.
- //
- // We must be careful to use CPUCLOCK_PROF, the same clock used for RLIMIT_CPU
- // enforcement, to get correct results. Note that this is slightly different
- // from rusage-reported CPU usage:
- //
- // RLIMIT_CPU, CPUCLOCK_PROF use kernel/sched/cputime.c:thread_group_cputime.
- // rusage uses kernel/sched/cputime.c:thread_group_cputime_adjusted.
- absl::Duration cpu = ASSERT_NO_ERRNO_AND_VALUE(ProcessCPUTime(pid));
- EXPECT_GE(cpu, kSoftLimit);
-
- // Child did not make it to the hard limit.
- //
- // Linux sends SIGXCPU synchronously with CPU tick updates. See
- // kernel/time/timer.c:update_process_times:
- // => account_process_tick // update task CPU usage.
- // => run_posix_cpu_timers // enforce RLIMIT_CPU, sending signal.
- //
- // Thus, only chance for this to flake is if the system time required to
- // deliver the signal exceeds 2s.
- EXPECT_LT(cpu, kHardLimit);
-}
-
-TEST(TimerTest, ProcessPingedRepeatedlyAfterCPUSoftLimit) {
- struct sigaction new_action;
- new_action.sa_handler = UninstallingSignalHandler;
- new_action.sa_flags = 0;
- sigemptyset(&new_action.sa_mask);
-
- constexpr absl::Duration kSoftLimit = absl::Seconds(1);
- constexpr absl::Duration kHardLimit = absl::Seconds(10);
-
- struct rlimit cpu_limits;
- cpu_limits.rlim_cur = absl::ToInt64Seconds(kSoftLimit);
- cpu_limits.rlim_max = absl::ToInt64Seconds(kHardLimit);
-
- int pid = fork();
- MaybeSave();
- if (pid == 0) {
- TEST_PCHECK(sigaction(SIGXCPU, &new_action, nullptr) == 0);
- MaybeSave();
- TEST_PCHECK(setrlimit(RLIMIT_CPU, &cpu_limits) == 0);
- MaybeSave();
- for (;;) {
- }
- }
- ASSERT_THAT(pid, SyscallSucceeds());
- auto c = Cleanup([pid] {
- int status;
- EXPECT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFSIGNALED(status));
- EXPECT_EQ(WTERMSIG(status), SIGXCPU);
- });
-
- // Wait for the child to exit, but do not reap it. This will allow us to check
- // its CPU usage while it is zombied.
- EXPECT_THAT(waitid(P_PID, pid, nullptr, WEXITED | WNOWAIT),
- SyscallSucceeds());
-
- absl::Duration cpu = ASSERT_NO_ERRNO_AND_VALUE(ProcessCPUTime(pid));
- // Following signals come every CPU second.
- EXPECT_GE(cpu, kSoftLimit + absl::Seconds(1));
-
- // Child did not make it to the hard limit.
- //
- // As above, should not flake.
- EXPECT_LT(cpu, kHardLimit);
-}
-
-TEST(TimerTest, ProcessKilledOnCPUHardLimit) {
- struct sigaction new_action;
- new_action.sa_handler = NoopSignalHandler;
- new_action.sa_flags = 0;
- sigemptyset(&new_action.sa_mask);
-
- constexpr absl::Duration kSoftLimit = absl::Seconds(1);
- constexpr absl::Duration kHardLimit = absl::Seconds(3);
-
- struct rlimit cpu_limits;
- cpu_limits.rlim_cur = absl::ToInt64Seconds(kSoftLimit);
- cpu_limits.rlim_max = absl::ToInt64Seconds(kHardLimit);
-
- int pid = fork();
- MaybeSave();
- if (pid == 0) {
- TEST_PCHECK(sigaction(SIGXCPU, &new_action, nullptr) == 0);
- MaybeSave();
- TEST_PCHECK(setrlimit(RLIMIT_CPU, &cpu_limits) == 0);
- MaybeSave();
- for (;;) {
- }
- }
- ASSERT_THAT(pid, SyscallSucceeds());
- auto c = Cleanup([pid] {
- int status;
- EXPECT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid));
- EXPECT_TRUE(WIFSIGNALED(status));
- EXPECT_EQ(WTERMSIG(status), SIGKILL);
- });
-
- // Wait for the child to exit, but do not reap it. This will allow us to check
- // its CPU usage while it is zombied.
- EXPECT_THAT(waitid(P_PID, pid, nullptr, WEXITED | WNOWAIT),
- SyscallSucceeds());
-
- absl::Duration cpu = ASSERT_NO_ERRNO_AND_VALUE(ProcessCPUTime(pid));
- EXPECT_GE(cpu, kHardLimit);
-}
-
-// RAII type for a kernel "POSIX" interval timer. (The kernel provides system
-// calls such as timer_create that behave very similarly, but not identically,
-// to those described by timer_create(2); in particular, the kernel does not
-// implement SIGEV_THREAD. glibc builds POSIX-compliant interval timers based on
-// these kernel interval timers.)
-//
-// Compare implementation to FileDescriptor.
-class IntervalTimer {
- public:
- IntervalTimer() = default;
-
- explicit IntervalTimer(int id) { set_id(id); }
-
- IntervalTimer(IntervalTimer&& orig) : id_(orig.release()) {}
-
- IntervalTimer& operator=(IntervalTimer&& orig) {
- if (this == &orig) return *this;
- reset(orig.release());
- return *this;
- }
-
- IntervalTimer(const IntervalTimer& other) = delete;
- IntervalTimer& operator=(const IntervalTimer& other) = delete;
-
- ~IntervalTimer() { reset(); }
-
- int get() const { return id_; }
-
- int release() {
- int const id = id_;
- id_ = -1;
- return id;
- }
-
- void reset() { reset(-1); }
-
- void reset(int id) {
- if (id_ >= 0) {
- TEST_PCHECK(syscall(SYS_timer_delete, id_) == 0);
- MaybeSave();
- }
- set_id(id);
- }
-
- PosixErrorOr<struct itimerspec> Set(
- int flags, const struct itimerspec& new_value) const {
- struct itimerspec old_value = {};
- if (syscall(SYS_timer_settime, id_, flags, &new_value, &old_value) < 0) {
- return PosixError(errno, "timer_settime");
- }
- MaybeSave();
- return old_value;
- }
-
- PosixErrorOr<struct itimerspec> Get() const {
- struct itimerspec curr_value = {};
- if (syscall(SYS_timer_gettime, id_, &curr_value) < 0) {
- return PosixError(errno, "timer_gettime");
- }
- MaybeSave();
- return curr_value;
- }
-
- PosixErrorOr<int> Overruns() const {
- int rv = syscall(SYS_timer_getoverrun, id_);
- if (rv < 0) {
- return PosixError(errno, "timer_getoverrun");
- }
- MaybeSave();
- return rv;
- }
-
- private:
- void set_id(int id) { id_ = std::max(id, -1); }
-
- // Kernel timer_t is int; glibc timer_t is void*.
- int id_ = -1;
-};
-
-PosixErrorOr<IntervalTimer> TimerCreate(clockid_t clockid,
- const struct sigevent& sev) {
- int timerid;
- if (syscall(SYS_timer_create, clockid, &sev, &timerid) < 0) {
- return PosixError(errno, "timer_create");
- }
- MaybeSave();
- return IntervalTimer(timerid);
-}
-
-// See timerfd.cc:TimerSlack() for rationale.
-constexpr absl::Duration kTimerSlack = absl::Milliseconds(500);
-
-TEST(IntervalTimerTest, IsInitiallyStopped) {
- struct sigevent sev = {};
- sev.sigev_notify = SIGEV_NONE;
- const auto timer =
- ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev));
- const struct itimerspec its = ASSERT_NO_ERRNO_AND_VALUE(timer.Get());
- EXPECT_EQ(0, its.it_value.tv_sec);
- EXPECT_EQ(0, its.it_value.tv_nsec);
-}
-
-TEST(IntervalTimerTest, SingleShotSilent) {
- struct sigevent sev = {};
- sev.sigev_notify = SIGEV_NONE;
- const auto timer =
- ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev));
-
- constexpr absl::Duration kDelay = absl::Seconds(1);
- struct itimerspec its = {};
- its.it_value = absl::ToTimespec(kDelay);
- ASSERT_NO_ERRNO(timer.Set(0, its));
-
- // The timer should count down to 0 and stop since the interval is zero. No
- // overruns should be counted.
- absl::SleepFor(kDelay + kTimerSlack);
- its = ASSERT_NO_ERRNO_AND_VALUE(timer.Get());
- EXPECT_EQ(0, its.it_value.tv_sec);
- EXPECT_EQ(0, its.it_value.tv_nsec);
- EXPECT_THAT(timer.Overruns(), IsPosixErrorOkAndHolds(0));
-}
-
-TEST(IntervalTimerTest, PeriodicSilent) {
- struct sigevent sev = {};
- sev.sigev_notify = SIGEV_NONE;
- const auto timer =
- ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev));
-
- constexpr absl::Duration kPeriod = absl::Seconds(1);
- struct itimerspec its = {};
- its.it_value = its.it_interval = absl::ToTimespec(kPeriod);
- ASSERT_NO_ERRNO(timer.Set(0, its));
-
- absl::SleepFor(kPeriod * 3 + kTimerSlack);
-
- // The timer should still be running.
- its = ASSERT_NO_ERRNO_AND_VALUE(timer.Get());
- EXPECT_TRUE(its.it_value.tv_nsec != 0 || its.it_value.tv_sec != 0);
-
- // Timer expirations are not counted as overruns under SIGEV_NONE.
- EXPECT_THAT(timer.Overruns(), IsPosixErrorOkAndHolds(0));
-}
-
-std::atomic<int> counted_signals;
-
-void IntervalTimerCountingSignalHandler(int sig, siginfo_t* info,
- void* ucontext) {
- counted_signals.fetch_add(1 + info->si_overrun);
-}
-
-TEST(IntervalTimerTest, PeriodicGroupDirectedSignal) {
- constexpr int kSigno = SIGUSR1;
- constexpr int kSigvalue = 42;
-
- // Install our signal handler.
- counted_signals.store(0);
- struct sigaction sa = {};
- sa.sa_sigaction = IntervalTimerCountingSignalHandler;
- sigemptyset(&sa.sa_mask);
- sa.sa_flags = SA_SIGINFO;
- const auto scoped_sigaction =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(kSigno, sa));
-
- // Ensure that kSigno is unblocked on at least one thread.
- const auto scoped_sigmask =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, kSigno));
-
- struct sigevent sev = {};
- sev.sigev_notify = SIGEV_SIGNAL;
- sev.sigev_signo = kSigno;
- sev.sigev_value.sival_int = kSigvalue;
- auto timer = ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev));
-
- constexpr absl::Duration kPeriod = absl::Seconds(1);
- constexpr int kCycles = 3;
- struct itimerspec its = {};
- its.it_value = its.it_interval = absl::ToTimespec(kPeriod);
- ASSERT_NO_ERRNO(timer.Set(0, its));
-
- absl::SleepFor(kPeriod * kCycles + kTimerSlack);
- EXPECT_GE(counted_signals.load(), kCycles);
-}
-
-// From Linux's include/uapi/asm-generic/siginfo.h.
-#ifndef sigev_notify_thread_id
-#define sigev_notify_thread_id _sigev_un._tid
-#endif
-
-TEST(IntervalTimerTest, PeriodicThreadDirectedSignal) {
- constexpr int kSigno = SIGUSR1;
- constexpr int kSigvalue = 42;
-
- // Block kSigno so that we can accumulate overruns.
- sigset_t mask;
- sigemptyset(&mask);
- sigaddset(&mask, kSigno);
- const auto scoped_sigmask =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_BLOCK, mask));
-
- struct sigevent sev = {};
- sev.sigev_notify = SIGEV_THREAD_ID;
- sev.sigev_signo = kSigno;
- sev.sigev_value.sival_int = kSigvalue;
- sev.sigev_notify_thread_id = gettid();
- auto timer = ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev));
-
- constexpr absl::Duration kPeriod = absl::Seconds(1);
- constexpr int kCycles = 3;
- struct itimerspec its = {};
- its.it_value = its.it_interval = absl::ToTimespec(kPeriod);
- ASSERT_NO_ERRNO(timer.Set(0, its));
- absl::SleepFor(kPeriod * kCycles + kTimerSlack);
-
- // At least kCycles expirations should have occurred, resulting in kCycles-1
- // overruns (the first expiration sent the signal successfully).
- siginfo_t si;
- struct timespec zero_ts = absl::ToTimespec(absl::ZeroDuration());
- ASSERT_THAT(sigtimedwait(&mask, &si, &zero_ts),
- SyscallSucceedsWithValue(kSigno));
- EXPECT_EQ(si.si_signo, kSigno);
- EXPECT_EQ(si.si_code, SI_TIMER);
- EXPECT_EQ(si.si_timerid, timer.get());
- EXPECT_GE(si.si_overrun, kCycles - 1);
- EXPECT_EQ(si.si_int, kSigvalue);
-
- // Kill the timer, then drain any additional signal it may have enqueued. We
- // can't do this before the preceding sigtimedwait because stopping or
- // deleting the timer resets si_overrun to 0.
- timer.reset();
- sigtimedwait(&mask, &si, &zero_ts);
-}
-
-TEST(IntervalTimerTest, OtherThreadGroup) {
- constexpr int kSigno = SIGUSR1;
-
- // Create a subprocess that does nothing until killed.
- pid_t child_pid;
- const auto sp = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec(
- "/proc/self/exe", ExecveArray({"timers", "--timers_test_sleep"}),
- ExecveArray(), &child_pid, nullptr));
-
- // Verify that we can't create a timer that would send signals to it.
- struct sigevent sev = {};
- sev.sigev_notify = SIGEV_THREAD_ID;
- sev.sigev_signo = kSigno;
- sev.sigev_notify_thread_id = child_pid;
- EXPECT_THAT(TimerCreate(CLOCK_MONOTONIC, sev), PosixErrorIs(EINVAL, _));
-}
-
-TEST(IntervalTimerTest, RealTimeSignalsAreNotDuplicated) {
- const int kSigno = SIGRTMIN;
- constexpr int kSigvalue = 42;
-
- // Block signo so that we can accumulate overruns.
- sigset_t mask;
- sigemptyset(&mask);
- sigaddset(&mask, kSigno);
- const auto scoped_sigmask = ScopedSignalMask(SIG_BLOCK, mask);
-
- struct sigevent sev = {};
- sev.sigev_notify = SIGEV_THREAD_ID;
- sev.sigev_signo = kSigno;
- sev.sigev_value.sival_int = kSigvalue;
- sev.sigev_notify_thread_id = gettid();
- const auto timer =
- ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev));
-
- constexpr absl::Duration kPeriod = absl::Seconds(1);
- constexpr int kCycles = 3;
- struct itimerspec its = {};
- its.it_value = its.it_interval = absl::ToTimespec(kPeriod);
- ASSERT_NO_ERRNO(timer.Set(0, its));
- absl::SleepFor(kPeriod * kCycles + kTimerSlack);
-
- // Stop the timer so that no further signals are enqueued after sigtimedwait.
- struct timespec zero_ts = absl::ToTimespec(absl::ZeroDuration());
- its.it_value = its.it_interval = zero_ts;
- ASSERT_NO_ERRNO(timer.Set(0, its));
-
- // The timer should have sent only a single signal, even though the kernel
- // supports enqueueing of multiple RT signals.
- siginfo_t si;
- ASSERT_THAT(sigtimedwait(&mask, &si, &zero_ts),
- SyscallSucceedsWithValue(kSigno));
- EXPECT_EQ(si.si_signo, kSigno);
- EXPECT_EQ(si.si_code, SI_TIMER);
- EXPECT_EQ(si.si_timerid, timer.get());
- // si_overrun was reset by timer_settime.
- EXPECT_EQ(si.si_overrun, 0);
- EXPECT_EQ(si.si_int, kSigvalue);
- EXPECT_THAT(sigtimedwait(&mask, &si, &zero_ts),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST(IntervalTimerTest, AlreadyPendingSignal) {
- constexpr int kSigno = SIGUSR1;
- constexpr int kSigvalue = 42;
-
- // Block kSigno so that we can accumulate overruns.
- sigset_t mask;
- sigemptyset(&mask);
- sigaddset(&mask, kSigno);
- const auto scoped_sigmask =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_BLOCK, mask));
-
- // Send ourselves a signal, preventing the timer from enqueuing.
- ASSERT_THAT(tgkill(getpid(), gettid(), kSigno), SyscallSucceeds());
-
- struct sigevent sev = {};
- sev.sigev_notify = SIGEV_THREAD_ID;
- sev.sigev_signo = kSigno;
- sev.sigev_value.sival_int = kSigvalue;
- sev.sigev_notify_thread_id = gettid();
- auto timer = ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev));
-
- constexpr absl::Duration kPeriod = absl::Seconds(1);
- constexpr int kCycles = 3;
- struct itimerspec its = {};
- its.it_value = its.it_interval = absl::ToTimespec(kPeriod);
- ASSERT_NO_ERRNO(timer.Set(0, its));
-
- // End the sleep one cycle short; we will sleep for one more cycle below.
- absl::SleepFor(kPeriod * (kCycles - 1));
-
- // Dequeue the first signal, which we sent to ourselves with tgkill.
- siginfo_t si;
- struct timespec zero_ts = absl::ToTimespec(absl::ZeroDuration());
- ASSERT_THAT(sigtimedwait(&mask, &si, &zero_ts),
- SyscallSucceedsWithValue(kSigno));
- EXPECT_EQ(si.si_signo, kSigno);
- // glibc sigtimedwait silently replaces SI_TKILL with SI_USER:
- // sysdeps/unix/sysv/linux/sigtimedwait.c:__sigtimedwait(). This isn't
- // documented, so we don't depend on it.
- EXPECT_THAT(si.si_code, AnyOf(SI_USER, SI_TKILL));
-
- // Sleep for 1 more cycle to give the timer time to send a signal.
- absl::SleepFor(kPeriod + kTimerSlack);
-
- // At least kCycles expirations should have occurred, resulting in kCycles-1
- // overruns (the last expiration sent the signal successfully).
- ASSERT_THAT(sigtimedwait(&mask, &si, &zero_ts),
- SyscallSucceedsWithValue(kSigno));
- EXPECT_EQ(si.si_signo, kSigno);
- EXPECT_EQ(si.si_code, SI_TIMER);
- EXPECT_EQ(si.si_timerid, timer.get());
- EXPECT_GE(si.si_overrun, kCycles - 1);
- EXPECT_EQ(si.si_int, kSigvalue);
-
- // Kill the timer, then drain any additional signal it may have enqueued. We
- // can't do this before the preceding sigtimedwait because stopping or
- // deleting the timer resets si_overrun to 0.
- timer.reset();
- sigtimedwait(&mask, &si, &zero_ts);
-}
-
-TEST(IntervalTimerTest, IgnoredSignalCountsAsOverrun) {
- constexpr int kSigno = SIGUSR1;
- constexpr int kSigvalue = 42;
-
- // Ignore kSigno.
- struct sigaction sa = {};
- sa.sa_handler = SIG_IGN;
- const auto scoped_sigaction =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(kSigno, sa));
-
- // Unblock kSigno so that ignored signals will be discarded.
- sigset_t mask;
- sigemptyset(&mask);
- sigaddset(&mask, kSigno);
- auto scoped_sigmask =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, mask));
-
- struct sigevent sev = {};
- sev.sigev_notify = SIGEV_THREAD_ID;
- sev.sigev_signo = kSigno;
- sev.sigev_value.sival_int = kSigvalue;
- sev.sigev_notify_thread_id = gettid();
- auto timer = ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev));
-
- constexpr absl::Duration kPeriod = absl::Seconds(1);
- constexpr int kCycles = 3;
- struct itimerspec its = {};
- its.it_value = its.it_interval = absl::ToTimespec(kPeriod);
- ASSERT_NO_ERRNO(timer.Set(0, its));
-
- // End the sleep one cycle short; we will sleep for one more cycle below.
- absl::SleepFor(kPeriod * (kCycles - 1));
-
- // Block kSigno so that ignored signals will be enqueued.
- scoped_sigmask.Release()();
- scoped_sigmask = ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_BLOCK, mask));
-
- // Sleep for 1 more cycle to give the timer time to send a signal.
- absl::SleepFor(kPeriod + kTimerSlack);
-
- // At least kCycles expirations should have occurred, resulting in kCycles-1
- // overruns (the last expiration sent the signal successfully).
- siginfo_t si;
- struct timespec zero_ts = absl::ToTimespec(absl::ZeroDuration());
- ASSERT_THAT(sigtimedwait(&mask, &si, &zero_ts),
- SyscallSucceedsWithValue(kSigno));
- EXPECT_EQ(si.si_signo, kSigno);
- EXPECT_EQ(si.si_code, SI_TIMER);
- EXPECT_EQ(si.si_timerid, timer.get());
- EXPECT_GE(si.si_overrun, kCycles - 1);
- EXPECT_EQ(si.si_int, kSigvalue);
-
- // Kill the timer, then drain any additional signal it may have enqueued. We
- // can't do this before the preceding sigtimedwait because stopping or
- // deleting the timer resets si_overrun to 0.
- timer.reset();
- sigtimedwait(&mask, &si, &zero_ts);
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor
-
-int main(int argc, char** argv) {
- gvisor::testing::TestInit(&argc, &argv);
-
- if (FLAGS_timers_test_sleep) {
- while (true) {
- absl::SleepFor(absl::Seconds(10));
- }
- }
-
- return RUN_ALL_TESTS();
-}
diff --git a/test/syscalls/linux/tkill.cc b/test/syscalls/linux/tkill.cc
deleted file mode 100644
index bae377c69..000000000
--- a/test/syscalls/linux/tkill.cc
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sys/syscall.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <cerrno>
-#include <csignal>
-
-#include "gtest/gtest.h"
-#include "test/util/logging.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-static int tkill(pid_t tid, int sig) {
- int ret;
- do {
- // NOTE(b/25434735): tkill(2) could return EAGAIN for RT signals.
- ret = syscall(SYS_tkill, tid, sig);
- } while (ret == -1 && errno == EAGAIN);
- return ret;
-}
-
-TEST(TkillTest, InvalidTID) {
- EXPECT_THAT(tkill(-1, 0), SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(tkill(0, 0), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(TkillTest, ValidTID) {
- EXPECT_THAT(tkill(gettid(), 0), SyscallSucceeds());
-}
-
-void SigHandler(int sig, siginfo_t* info, void* context) {
- TEST_CHECK(sig == SIGRTMAX);
- TEST_CHECK(info->si_pid == getpid());
- TEST_CHECK(info->si_uid == getuid());
- TEST_CHECK(info->si_code == SI_TKILL);
-}
-
-// Test with a real signal.
-TEST(TkillTest, ValidTIDAndRealSignal) {
- struct sigaction sa;
- sa.sa_sigaction = SigHandler;
- sigfillset(&sa.sa_mask);
- sa.sa_flags = SA_SIGINFO;
- ASSERT_THAT(sigaction(SIGRTMAX, &sa, nullptr), SyscallSucceeds());
- // InitGoogle blocks all RT signals, so we need undo it.
- sigset_t unblock;
- sigemptyset(&unblock);
- sigaddset(&unblock, SIGRTMAX);
- ASSERT_THAT(sigprocmask(SIG_UNBLOCK, &unblock, nullptr), SyscallSucceeds());
- EXPECT_THAT(tkill(gettid(), SIGRTMAX), SyscallSucceeds());
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/truncate.cc b/test/syscalls/linux/truncate.cc
deleted file mode 100644
index e5cc5d97c..000000000
--- a/test/syscalls/linux/truncate.cc
+++ /dev/null
@@ -1,217 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <signal.h>
-#include <sys/resource.h>
-#include <sys/stat.h>
-#include <sys/vfs.h>
-#include <time.h>
-#include <unistd.h>
-#include <iostream>
-#include <string>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/strings/string_view.h"
-#include "test/syscalls/linux/file_base.h"
-#include "test/util/capability_util.h"
-#include "test/util/cleanup.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-class FixtureTruncateTest : public FileTest {
- void SetUp() override { FileTest::SetUp(); }
-};
-
-TEST_F(FixtureTruncateTest, Truncate) {
- // Get the current rlimit and restore after test run.
- struct rlimit initial_lim;
- ASSERT_THAT(getrlimit(RLIMIT_FSIZE, &initial_lim), SyscallSucceeds());
- auto cleanup = Cleanup([&initial_lim] {
- EXPECT_THAT(setrlimit(RLIMIT_FSIZE, &initial_lim), SyscallSucceeds());
- });
-
- // Check that it starts at size zero.
- struct stat buf;
- ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds());
- EXPECT_EQ(buf.st_size, 0);
-
- // Stay at size zero.
- EXPECT_THAT(truncate(test_file_name_.c_str(), 0), SyscallSucceeds());
- ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds());
- EXPECT_EQ(buf.st_size, 0);
-
- // Grow to ten bytes.
- EXPECT_THAT(truncate(test_file_name_.c_str(), 10), SyscallSucceeds());
- ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds());
- EXPECT_EQ(buf.st_size, 10);
-
- // Can't be truncated to a negative number.
- EXPECT_THAT(truncate(test_file_name_.c_str(), -1),
- SyscallFailsWithErrno(EINVAL));
-
- // Try growing past the file size limit.
- sigset_t new_mask;
- sigemptyset(&new_mask);
- sigaddset(&new_mask, SIGXFSZ);
- sigprocmask(SIG_BLOCK, &new_mask, nullptr);
- struct timespec timelimit;
- timelimit.tv_sec = 10;
- timelimit.tv_nsec = 0;
-
- struct rlimit setlim;
- setlim.rlim_cur = 1024;
- setlim.rlim_max = RLIM_INFINITY;
- ASSERT_THAT(setrlimit(RLIMIT_FSIZE, &setlim), SyscallSucceeds());
- EXPECT_THAT(truncate(test_file_name_.c_str(), 1025),
- SyscallFailsWithErrno(EFBIG));
- EXPECT_EQ(sigtimedwait(&new_mask, nullptr, &timelimit), SIGXFSZ);
- ASSERT_THAT(sigprocmask(SIG_UNBLOCK, &new_mask, nullptr), SyscallSucceeds());
-
- // Shrink back down to zero.
- EXPECT_THAT(truncate(test_file_name_.c_str(), 0), SyscallSucceeds());
- ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds());
- EXPECT_EQ(buf.st_size, 0);
-}
-
-TEST_F(FixtureTruncateTest, Ftruncate) {
- // Get the current rlimit and restore after test run.
- struct rlimit initial_lim;
- ASSERT_THAT(getrlimit(RLIMIT_FSIZE, &initial_lim), SyscallSucceeds());
- auto cleanup = Cleanup([&initial_lim] {
- EXPECT_THAT(setrlimit(RLIMIT_FSIZE, &initial_lim), SyscallSucceeds());
- });
-
- // Check that it starts at size zero.
- struct stat buf;
- ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds());
- EXPECT_EQ(buf.st_size, 0);
-
- // Stay at size zero.
- EXPECT_THAT(ftruncate(test_file_fd_.get(), 0), SyscallSucceeds());
- ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds());
- EXPECT_EQ(buf.st_size, 0);
-
- // Grow to ten bytes.
- EXPECT_THAT(ftruncate(test_file_fd_.get(), 10), SyscallSucceeds());
- ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds());
- EXPECT_EQ(buf.st_size, 10);
-
- // Can't be truncated to a negative number.
- EXPECT_THAT(ftruncate(test_file_fd_.get(), -1),
- SyscallFailsWithErrno(EINVAL));
-
- // Try growing past the file size limit.
- sigset_t new_mask;
- sigemptyset(&new_mask);
- sigaddset(&new_mask, SIGXFSZ);
- sigprocmask(SIG_BLOCK, &new_mask, nullptr);
- struct timespec timelimit;
- timelimit.tv_sec = 10;
- timelimit.tv_nsec = 0;
-
- struct rlimit setlim;
- setlim.rlim_cur = 1024;
- setlim.rlim_max = RLIM_INFINITY;
- ASSERT_THAT(setrlimit(RLIMIT_FSIZE, &setlim), SyscallSucceeds());
- EXPECT_THAT(ftruncate(test_file_fd_.get(), 1025),
- SyscallFailsWithErrno(EFBIG));
- EXPECT_EQ(sigtimedwait(&new_mask, nullptr, &timelimit), SIGXFSZ);
- ASSERT_THAT(sigprocmask(SIG_UNBLOCK, &new_mask, nullptr), SyscallSucceeds());
-
- // Shrink back down to zero.
- EXPECT_THAT(ftruncate(test_file_fd_.get(), 0), SyscallSucceeds());
- ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds());
- EXPECT_EQ(buf.st_size, 0);
-}
-
-// Truncating a file down clears that portion of the file.
-TEST_F(FixtureTruncateTest, FtruncateShrinkGrow) {
- std::vector<char> buf(10, 'a');
- EXPECT_THAT(WriteFd(test_file_fd_.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(buf.size()));
-
- // Shrink then regrow the file. This should clear the second half of the file.
- EXPECT_THAT(ftruncate(test_file_fd_.get(), 5), SyscallSucceeds());
- EXPECT_THAT(ftruncate(test_file_fd_.get(), 10), SyscallSucceeds());
-
- EXPECT_THAT(lseek(test_file_fd_.get(), 0, SEEK_SET), SyscallSucceeds());
-
- std::vector<char> buf2(10);
- EXPECT_THAT(ReadFd(test_file_fd_.get(), buf2.data(), buf2.size()),
- SyscallSucceedsWithValue(buf2.size()));
-
- std::vector<char> expect = {'a', 'a', 'a', 'a', 'a',
- '\0', '\0', '\0', '\0', '\0'};
- EXPECT_EQ(expect, buf2);
-}
-
-TEST(TruncateTest, TruncateDir) {
- auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- EXPECT_THAT(truncate(temp_dir.path().c_str(), 0),
- SyscallFailsWithErrno(EISDIR));
-}
-
-TEST(TruncateTest, FtruncateDir) {
- auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(temp_dir.path(), O_DIRECTORY | O_RDONLY));
- EXPECT_THAT(ftruncate(fd.get(), 0), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(TruncateTest, TruncateNonWriteable) {
- // Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to
- // always override write permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), absl::string_view(), 0555 /* mode */));
- EXPECT_THAT(truncate(temp_file.path().c_str(), 0),
- SyscallFailsWithErrno(EACCES));
-}
-
-TEST(TruncateTest, FtruncateNonWriteable) {
- auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), absl::string_view(), 0555 /* mode */));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(temp_file.path(), O_RDONLY));
- EXPECT_THAT(ftruncate(fd.get(), 0), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(TruncateTest, TruncateNonExist) {
- EXPECT_THAT(truncate("/foo/bar", 0), SyscallFailsWithErrno(ENOENT));
-}
-
-TEST(TruncateTest, FtruncateVirtualTmp_NoRandomSave) {
- auto temp_file = NewTempAbsPathInDir("/dev/shm");
- const DisableSave ds; // Incompatible permissions.
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(temp_file, O_RDWR | O_CREAT | O_EXCL, 0));
- EXPECT_THAT(ftruncate(fd.get(), 100), SyscallSucceeds());
-}
-
-// NOTE: There are additional truncate(2)/ftruncate(2) tests in mknod.cc
-// which are there to avoid running the tests on a number of different
-// filesystems which may not support mknod.
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/udp_bind.cc b/test/syscalls/linux/udp_bind.cc
deleted file mode 100644
index 6d92bdbeb..000000000
--- a/test/syscalls/linux/udp_bind.cc
+++ /dev/null
@@ -1,316 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <arpa/inet.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-struct sockaddr_in_common {
- sa_family_t sin_family;
- in_port_t sin_port;
-};
-
-struct SendtoTestParam {
- // Human readable description of test parameter.
- std::string description;
-
- // Test is broken in gVisor, skip.
- bool skip_on_gvisor;
-
- // Domain for the socket that will do the sending.
- int send_domain;
-
- // Address to bind for the socket that will do the sending.
- struct sockaddr_storage send_addr;
- socklen_t send_addr_len; // 0 for unbound.
-
- // Address to connect to for the socket that will do the sending.
- struct sockaddr_storage connect_addr;
- socklen_t connect_addr_len; // 0 for no connection.
-
- // Domain for the socket that will do the receiving.
- int recv_domain;
-
- // Address to bind for the socket that will do the receiving.
- struct sockaddr_storage recv_addr;
- socklen_t recv_addr_len;
-
- // Address to send to.
- struct sockaddr_storage sendto_addr;
- socklen_t sendto_addr_len;
-
- // Expected errno for the sendto call.
- std::vector<int> sendto_errnos; // empty on success.
-};
-
-class SendtoTest : public ::testing::TestWithParam<SendtoTestParam> {
- protected:
- SendtoTest() {
- // gUnit uses printf, so so will we.
- printf("Testing with %s\n", GetParam().description.c_str());
- }
-};
-
-TEST_P(SendtoTest, Sendto) {
- auto param = GetParam();
-
- SKIP_IF(param.skip_on_gvisor && IsRunningOnGvisor());
-
- const FileDescriptor s1 =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(param.send_domain, SOCK_DGRAM, 0));
- const FileDescriptor s2 =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(param.recv_domain, SOCK_DGRAM, 0));
-
- if (param.send_addr_len > 0) {
- ASSERT_THAT(bind(s1.get(), reinterpret_cast<sockaddr*>(&param.send_addr),
- param.send_addr_len),
- SyscallSucceeds());
- }
-
- if (param.connect_addr_len > 0) {
- ASSERT_THAT(
- connect(s1.get(), reinterpret_cast<sockaddr*>(&param.connect_addr),
- param.connect_addr_len),
- SyscallSucceeds());
- }
-
- ASSERT_THAT(bind(s2.get(), reinterpret_cast<sockaddr*>(&param.recv_addr),
- param.recv_addr_len),
- SyscallSucceeds());
-
- struct sockaddr_storage real_recv_addr = {};
- socklen_t real_recv_addr_len = param.recv_addr_len;
- ASSERT_THAT(
- getsockname(s2.get(), reinterpret_cast<sockaddr*>(&real_recv_addr),
- &real_recv_addr_len),
- SyscallSucceeds());
-
- ASSERT_EQ(real_recv_addr_len, param.recv_addr_len);
-
- int recv_port =
- reinterpret_cast<sockaddr_in_common*>(&real_recv_addr)->sin_port;
-
- struct sockaddr_storage sendto_addr = param.sendto_addr;
- reinterpret_cast<sockaddr_in_common*>(&sendto_addr)->sin_port = recv_port;
-
- char buf[20] = {};
- if (!param.sendto_errnos.empty()) {
- ASSERT_THAT(RetryEINTR(sendto)(s1.get(), buf, sizeof(buf), 0,
- reinterpret_cast<sockaddr*>(&sendto_addr),
- param.sendto_addr_len),
- SyscallFailsWithErrno(ElementOf(param.sendto_errnos)));
- return;
- }
-
- ASSERT_THAT(RetryEINTR(sendto)(s1.get(), buf, sizeof(buf), 0,
- reinterpret_cast<sockaddr*>(&sendto_addr),
- param.sendto_addr_len),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- struct sockaddr_storage got_addr = {};
- socklen_t got_addr_len = sizeof(sockaddr_storage);
- ASSERT_THAT(RetryEINTR(recvfrom)(s2.get(), buf, sizeof(buf), 0,
- reinterpret_cast<sockaddr*>(&got_addr),
- &got_addr_len),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- ASSERT_GT(got_addr_len, sizeof(sockaddr_in_common));
- int got_port = reinterpret_cast<sockaddr_in_common*>(&got_addr)->sin_port;
-
- struct sockaddr_storage sender_addr = {};
- socklen_t sender_addr_len = sizeof(sockaddr_storage);
- ASSERT_THAT(getsockname(s1.get(), reinterpret_cast<sockaddr*>(&sender_addr),
- &sender_addr_len),
- SyscallSucceeds());
-
- ASSERT_GT(sender_addr_len, sizeof(sockaddr_in_common));
- int sender_port =
- reinterpret_cast<sockaddr_in_common*>(&sender_addr)->sin_port;
-
- EXPECT_EQ(got_port, sender_port);
-}
-
-socklen_t Ipv4Addr(sockaddr_storage* addr, int port = 0) {
- auto addr4 = reinterpret_cast<sockaddr_in*>(addr);
- addr4->sin_family = AF_INET;
- addr4->sin_port = port;
- inet_pton(AF_INET, "127.0.0.1", &addr4->sin_addr.s_addr);
- return sizeof(struct sockaddr_in);
-}
-
-socklen_t Ipv6Addr(sockaddr_storage* addr, int port = 0) {
- auto addr6 = reinterpret_cast<sockaddr_in6*>(addr);
- addr6->sin6_family = AF_INET6;
- addr6->sin6_port = port;
- inet_pton(AF_INET6, "::1", &addr6->sin6_addr.s6_addr);
- return sizeof(struct sockaddr_in6);
-}
-
-socklen_t Ipv4MappedIpv6Addr(sockaddr_storage* addr, int port = 0) {
- auto addr6 = reinterpret_cast<sockaddr_in6*>(addr);
- addr6->sin6_family = AF_INET6;
- addr6->sin6_port = port;
- inet_pton(AF_INET6, "::ffff:127.0.0.1", &addr6->sin6_addr.s6_addr);
- return sizeof(struct sockaddr_in6);
-}
-
-INSTANTIATE_TEST_SUITE_P(
- UdpBindTest, SendtoTest,
- ::testing::Values(
- []() {
- SendtoTestParam param = {};
- param.description = "IPv4 mapped IPv6 sendto IPv4 mapped IPv6";
- param.send_domain = AF_INET6;
- param.send_addr_len = Ipv4MappedIpv6Addr(&param.send_addr);
- param.recv_domain = AF_INET6;
- param.recv_addr_len = Ipv4MappedIpv6Addr(&param.recv_addr);
- param.sendto_addr_len = Ipv4MappedIpv6Addr(&param.sendto_addr);
- return param;
- }(),
- []() {
- SendtoTestParam param = {};
- param.description = "IPv6 sendto IPv6";
- param.send_domain = AF_INET6;
- param.send_addr_len = Ipv6Addr(&param.send_addr);
- param.recv_domain = AF_INET6;
- param.recv_addr_len = Ipv6Addr(&param.recv_addr);
- param.sendto_addr_len = Ipv6Addr(&param.sendto_addr);
- return param;
- }(),
- []() {
- SendtoTestParam param = {};
- param.description = "IPv4 sendto IPv4";
- param.send_domain = AF_INET;
- param.send_addr_len = Ipv4Addr(&param.send_addr);
- param.recv_domain = AF_INET;
- param.recv_addr_len = Ipv4Addr(&param.recv_addr);
- param.sendto_addr_len = Ipv4Addr(&param.sendto_addr);
- return param;
- }(),
- []() {
- SendtoTestParam param = {};
- param.description = "IPv4 mapped IPv6 sendto IPv4";
- param.send_domain = AF_INET6;
- param.send_addr_len = Ipv4MappedIpv6Addr(&param.send_addr);
- param.recv_domain = AF_INET;
- param.recv_addr_len = Ipv4Addr(&param.recv_addr);
- param.sendto_addr_len = Ipv4MappedIpv6Addr(&param.sendto_addr);
- return param;
- }(),
- []() {
- SendtoTestParam param = {};
- param.description = "IPv4 sendto IPv4 mapped IPv6";
- param.send_domain = AF_INET;
- param.send_addr_len = Ipv4Addr(&param.send_addr);
- param.recv_domain = AF_INET6;
- param.recv_addr_len = Ipv4MappedIpv6Addr(&param.recv_addr);
- param.sendto_addr_len = Ipv4Addr(&param.sendto_addr);
- return param;
- }(),
- []() {
- SendtoTestParam param = {};
- param.description = "unbound IPv6 sendto IPv4 mapped IPv6";
- param.send_domain = AF_INET6;
- param.recv_domain = AF_INET6;
- param.recv_addr_len = Ipv4MappedIpv6Addr(&param.recv_addr);
- param.sendto_addr_len = Ipv4MappedIpv6Addr(&param.sendto_addr);
- return param;
- }(),
- []() {
- SendtoTestParam param = {};
- param.description = "unbound IPv6 sendto IPv4";
- param.send_domain = AF_INET6;
- param.recv_domain = AF_INET;
- param.recv_addr_len = Ipv4Addr(&param.recv_addr);
- param.sendto_addr_len = Ipv4MappedIpv6Addr(&param.sendto_addr);
- return param;
- }(),
- []() {
- SendtoTestParam param = {};
- param.description = "IPv6 sendto IPv4";
- param.send_domain = AF_INET6;
- param.send_addr_len = Ipv6Addr(&param.send_addr);
- param.recv_domain = AF_INET;
- param.recv_addr_len = Ipv4Addr(&param.recv_addr);
- param.sendto_addr_len = Ipv4MappedIpv6Addr(&param.sendto_addr);
- param.sendto_errnos = {ENETUNREACH};
- return param;
- }(),
- []() {
- SendtoTestParam param = {};
- param.description = "IPv4 mapped IPv6 sendto IPv6";
- param.send_domain = AF_INET6;
- param.send_addr_len = Ipv4MappedIpv6Addr(&param.send_addr);
- param.recv_domain = AF_INET6;
- param.recv_addr_len = Ipv6Addr(&param.recv_addr);
- param.sendto_addr_len = Ipv6Addr(&param.sendto_addr);
- param.sendto_errnos = {EAFNOSUPPORT};
- // The errno returned changed in Linux commit c8e6ad0829a723.
- param.sendto_errnos = {EINVAL, EAFNOSUPPORT};
- return param;
- }(),
- []() {
- SendtoTestParam param = {};
- param.description = "connected IPv4 mapped IPv6 sendto IPv6";
- param.send_domain = AF_INET6;
- param.connect_addr_len =
- Ipv4MappedIpv6Addr(&param.connect_addr, 5000);
- param.recv_domain = AF_INET6;
- param.recv_addr_len = Ipv6Addr(&param.recv_addr);
- param.sendto_addr_len = Ipv6Addr(&param.sendto_addr);
- // The errno returned changed in Linux commit c8e6ad0829a723.
- param.sendto_errnos = {EINVAL, EAFNOSUPPORT};
- return param;
- }(),
- []() {
- SendtoTestParam param = {};
- param.description = "connected IPv6 sendto IPv4 mapped IPv6";
- // TODO(igudger): Determine if this inconsistent behavior is worth
- // implementing.
- param.skip_on_gvisor = true;
- param.send_domain = AF_INET6;
- param.connect_addr_len = Ipv6Addr(&param.connect_addr, 5000);
- param.recv_domain = AF_INET6;
- param.recv_addr_len = Ipv4MappedIpv6Addr(&param.recv_addr);
- param.sendto_addr_len = Ipv4MappedIpv6Addr(&param.sendto_addr);
- return param;
- }(),
- []() {
- SendtoTestParam param = {};
- param.description = "connected IPv6 sendto IPv4";
- // TODO(igudger): Determine if this inconsistent behavior is worth
- // implementing.
- param.skip_on_gvisor = true;
- param.send_domain = AF_INET6;
- param.connect_addr_len = Ipv6Addr(&param.connect_addr, 5000);
- param.recv_domain = AF_INET;
- param.recv_addr_len = Ipv4Addr(&param.recv_addr);
- param.sendto_addr_len = Ipv4MappedIpv6Addr(&param.sendto_addr);
- return param;
- }()));
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/udp_socket.cc b/test/syscalls/linux/udp_socket.cc
deleted file mode 100644
index 111dbacdf..000000000
--- a/test/syscalls/linux/udp_socket.cc
+++ /dev/null
@@ -1,1349 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <arpa/inet.h>
-#include <fcntl.h>
-#include <linux/errqueue.h>
-#include <netinet/in.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-
-#include "gtest/gtest.h"
-#include "absl/base/macros.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// The initial port to be be used on gvisor.
-constexpr int TestPort = 40000;
-
-// Fixture for tests parameterized by the address family to use (AF_INET and
-// AF_INET6) when creating sockets.
-class UdpSocketTest : public ::testing::TestWithParam<AddressFamily> {
- protected:
- // Creates two sockets that will be used by test cases.
- void SetUp() override;
-
- // Closes the sockets created by SetUp().
- void TearDown() override {
- EXPECT_THAT(close(s_), SyscallSucceeds());
- EXPECT_THAT(close(t_), SyscallSucceeds());
-
- for (size_t i = 0; i < ABSL_ARRAYSIZE(ports_); ++i) {
- ASSERT_NO_ERRNO(FreeAvailablePort(ports_[i]));
- }
- }
-
- // First UDP socket.
- int s_;
-
- // Second UDP socket.
- int t_;
-
- // The length of the socket address.
- socklen_t addrlen_;
-
- // Initialized address pointing to loopback and port TestPort+i.
- struct sockaddr* addr_[3];
-
- // Initialize "any" address.
- struct sockaddr* anyaddr_;
-
- // Used ports.
- int ports_[3];
-
- private:
- // Storage for the loopback addresses.
- struct sockaddr_storage addr_storage_[3];
-
- // Storage for the "any" address.
- struct sockaddr_storage anyaddr_storage_;
-};
-
-// Gets a pointer to the port component of the given address.
-uint16_t* Port(struct sockaddr_storage* addr) {
- switch (addr->ss_family) {
- case AF_INET: {
- auto sin = reinterpret_cast<struct sockaddr_in*>(addr);
- return &sin->sin_port;
- }
- case AF_INET6: {
- auto sin6 = reinterpret_cast<struct sockaddr_in6*>(addr);
- return &sin6->sin6_port;
- }
- }
-
- return nullptr;
-}
-
-void UdpSocketTest::SetUp() {
- int type;
- if (GetParam() == AddressFamily::kIpv4) {
- type = AF_INET;
- auto sin = reinterpret_cast<struct sockaddr_in*>(&anyaddr_storage_);
- addrlen_ = sizeof(*sin);
- sin->sin_addr.s_addr = htonl(INADDR_ANY);
- } else {
- type = AF_INET6;
- auto sin6 = reinterpret_cast<struct sockaddr_in6*>(&anyaddr_storage_);
- addrlen_ = sizeof(*sin6);
- if (GetParam() == AddressFamily::kIpv6) {
- sin6->sin6_addr = IN6ADDR_ANY_INIT;
- } else {
- TestAddress const& v4_mapped_any = V4MappedAny();
- sin6->sin6_addr =
- reinterpret_cast<const struct sockaddr_in6*>(&v4_mapped_any.addr)
- ->sin6_addr;
- }
- }
- ASSERT_THAT(s_ = socket(type, SOCK_DGRAM, IPPROTO_UDP), SyscallSucceeds());
-
- ASSERT_THAT(t_ = socket(type, SOCK_DGRAM, IPPROTO_UDP), SyscallSucceeds());
-
- memset(&anyaddr_storage_, 0, sizeof(anyaddr_storage_));
- anyaddr_ = reinterpret_cast<struct sockaddr*>(&anyaddr_storage_);
- anyaddr_->sa_family = type;
-
- if (gvisor::testing::IsRunningOnGvisor()) {
- for (size_t i = 0; i < ABSL_ARRAYSIZE(ports_); ++i) {
- ports_[i] = TestPort + i;
- }
- } else {
- // When not under gvisor, use utility function to pick port. Assert that
- // all ports are different.
- std::string error;
- for (size_t i = 0; i < ABSL_ARRAYSIZE(ports_); ++i) {
- // Find an unused port, we specify port 0 to allow the kernel to provide
- // the port.
- bool unique = true;
- do {
- ports_[i] = ASSERT_NO_ERRNO_AND_VALUE(PortAvailable(
- 0, AddressFamily::kDualStack, SocketType::kUdp, false));
- ASSERT_GT(ports_[i], 0);
- for (size_t j = 0; j < i; ++j) {
- if (ports_[j] == ports_[i]) {
- unique = false;
- break;
- }
- }
- } while (!unique);
- }
- }
-
- // Initialize the sockaddrs.
- for (size_t i = 0; i < ABSL_ARRAYSIZE(addr_); ++i) {
- memset(&addr_storage_[i], 0, sizeof(addr_storage_[i]));
-
- addr_[i] = reinterpret_cast<struct sockaddr*>(&addr_storage_[i]);
- addr_[i]->sa_family = type;
-
- switch (type) {
- case AF_INET: {
- auto sin = reinterpret_cast<struct sockaddr_in*>(addr_[i]);
- sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- sin->sin_port = htons(ports_[i]);
- break;
- }
- case AF_INET6: {
- auto sin6 = reinterpret_cast<struct sockaddr_in6*>(addr_[i]);
- sin6->sin6_addr = in6addr_loopback;
- sin6->sin6_port = htons(ports_[i]);
- break;
- }
- }
- }
-}
-
-TEST_P(UdpSocketTest, Creation) {
- int type = AF_INET6;
- if (GetParam() == AddressFamily::kIpv4) {
- type = AF_INET;
- }
-
- int s_;
-
- ASSERT_THAT(s_ = socket(type, SOCK_DGRAM, IPPROTO_UDP), SyscallSucceeds());
- EXPECT_THAT(close(s_), SyscallSucceeds());
-
- ASSERT_THAT(s_ = socket(type, SOCK_DGRAM, 0), SyscallSucceeds());
- EXPECT_THAT(close(s_), SyscallSucceeds());
-
- ASSERT_THAT(s_ = socket(type, SOCK_STREAM, IPPROTO_UDP), SyscallFails());
-}
-
-TEST_P(UdpSocketTest, Getsockname) {
- // Check that we're not bound.
- struct sockaddr_storage addr;
- socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(getsockname(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
- EXPECT_EQ(addrlen, addrlen_);
- EXPECT_EQ(memcmp(&addr, anyaddr_, addrlen_), 0);
-
- // Bind, then check that we get the right address.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
-
- addrlen = sizeof(addr);
- EXPECT_THAT(getsockname(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
- EXPECT_EQ(addrlen, addrlen_);
- EXPECT_EQ(memcmp(&addr, addr_[0], addrlen_), 0);
-}
-
-TEST_P(UdpSocketTest, Getpeername) {
- // Check that we're not connected.
- struct sockaddr_storage addr;
- socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(getpeername(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallFailsWithErrno(ENOTCONN));
-
- // Connect, then check that we get the right address.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
-
- addrlen = sizeof(addr);
- EXPECT_THAT(getpeername(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
- EXPECT_EQ(addrlen, addrlen_);
- EXPECT_EQ(memcmp(&addr, addr_[0], addrlen_), 0);
-}
-
-TEST_P(UdpSocketTest, SendNotConnected) {
- // Do send & write, they must fail.
- char buf[512];
- EXPECT_THAT(send(s_, buf, sizeof(buf), 0),
- SyscallFailsWithErrno(EDESTADDRREQ));
-
- EXPECT_THAT(write(s_, buf, sizeof(buf)), SyscallFailsWithErrno(EDESTADDRREQ));
-
- // Use sendto.
- ASSERT_THAT(sendto(s_, buf, sizeof(buf), 0, addr_[0], addrlen_),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- // Check that we're bound now.
- struct sockaddr_storage addr;
- socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(getsockname(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
- EXPECT_EQ(addrlen, addrlen_);
- EXPECT_NE(*Port(&addr), 0);
-}
-
-TEST_P(UdpSocketTest, ConnectBinds) {
- // Connect the socket.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
-
- // Check that we're bound now.
- struct sockaddr_storage addr;
- socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(getsockname(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
- EXPECT_EQ(addrlen, addrlen_);
- EXPECT_NE(*Port(&addr), 0);
-}
-
-TEST_P(UdpSocketTest, ReceiveNotBound) {
- char buf[512];
- EXPECT_THAT(recv(s_, buf, sizeof(buf), MSG_DONTWAIT),
- SyscallFailsWithErrno(EWOULDBLOCK));
-}
-
-TEST_P(UdpSocketTest, Bind) {
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
-
- // Try to bind again.
- EXPECT_THAT(bind(s_, addr_[1], addrlen_), SyscallFailsWithErrno(EINVAL));
-
- // Check that we're still bound to the original address.
- struct sockaddr_storage addr;
- socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(getsockname(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
- EXPECT_EQ(addrlen, addrlen_);
- EXPECT_EQ(memcmp(&addr, addr_[0], addrlen_), 0);
-}
-
-TEST_P(UdpSocketTest, BindInUse) {
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
-
- // Try to bind again.
- EXPECT_THAT(bind(t_, addr_[0], addrlen_), SyscallFailsWithErrno(EADDRINUSE));
-}
-
-TEST_P(UdpSocketTest, ReceiveAfterConnect) {
- // Connect s_ to loopback:TestPort, and bind t_ to loopback:TestPort.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(bind(t_, addr_[0], addrlen_), SyscallSucceeds());
-
- // Get the address s_ was bound to during connect.
- struct sockaddr_storage addr;
- socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(getsockname(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
- EXPECT_EQ(addrlen, addrlen_);
-
- // Send from t_ to s_.
- char buf[512];
- RandomizeBuffer(buf, sizeof(buf));
- ASSERT_THAT(sendto(t_, buf, sizeof(buf), 0,
- reinterpret_cast<sockaddr*>(&addr), addrlen),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- // Receive the data.
- char received[sizeof(buf)];
- EXPECT_THAT(recv(s_, received, sizeof(received), 0),
- SyscallSucceedsWithValue(sizeof(received)));
- EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0);
-}
-
-TEST_P(UdpSocketTest, ReceiveAfterDisconnect) {
- // Connect s_ to loopback:TestPort, and bind t_ to loopback:TestPort.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(bind(t_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(t_, addr_[1], addrlen_), SyscallSucceeds());
-
- // Get the address s_ was bound to during connect.
- struct sockaddr_storage addr;
- socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(getsockname(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
- EXPECT_EQ(addrlen, addrlen_);
-
- for (int i = 0; i < 2; i++) {
- // Send from t_ to s_.
- char buf[512];
- RandomizeBuffer(buf, sizeof(buf));
- EXPECT_THAT(getsockname(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
- ASSERT_THAT(sendto(t_, buf, sizeof(buf), 0,
- reinterpret_cast<sockaddr*>(&addr), addrlen),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- // Receive the data.
- char received[sizeof(buf)];
- EXPECT_THAT(recv(s_, received, sizeof(received), 0),
- SyscallSucceedsWithValue(sizeof(received)));
- EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0);
-
- // Disconnect s_.
- struct sockaddr addr = {};
- addr.sa_family = AF_UNSPEC;
- ASSERT_THAT(connect(s_, &addr, sizeof(addr.sa_family)), SyscallSucceeds());
- // Connect s_ loopback:TestPort.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
- }
-}
-
-TEST_P(UdpSocketTest, Connect) {
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
-
- // Check that we're connected to the right peer.
- struct sockaddr_storage peer;
- socklen_t peerlen = sizeof(peer);
- EXPECT_THAT(getpeername(s_, reinterpret_cast<sockaddr*>(&peer), &peerlen),
- SyscallSucceeds());
- EXPECT_EQ(peerlen, addrlen_);
- EXPECT_EQ(memcmp(&peer, addr_[0], addrlen_), 0);
-
- // Try to bind after connect.
- EXPECT_THAT(bind(s_, addr_[1], addrlen_), SyscallFailsWithErrno(EINVAL));
-
- // Try to connect again.
- EXPECT_THAT(connect(s_, addr_[2], addrlen_), SyscallSucceeds());
-
- // Check that peer name changed.
- peerlen = sizeof(peer);
- EXPECT_THAT(getpeername(s_, reinterpret_cast<sockaddr*>(&peer), &peerlen),
- SyscallSucceeds());
- EXPECT_EQ(peerlen, addrlen_);
- EXPECT_EQ(memcmp(&peer, addr_[2], addrlen_), 0);
-}
-
-void ConnectAny(AddressFamily family, int sockfd, uint16_t port) {
- struct sockaddr_storage addr = {};
-
- // Precondition check.
- {
- socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(
- getsockname(sockfd, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
-
- if (family == AddressFamily::kIpv4) {
- auto addr_out = reinterpret_cast<struct sockaddr_in*>(&addr);
- EXPECT_EQ(addrlen, sizeof(*addr_out));
- EXPECT_EQ(addr_out->sin_addr.s_addr, htonl(INADDR_ANY));
- } else {
- auto addr_out = reinterpret_cast<struct sockaddr_in6*>(&addr);
- EXPECT_EQ(addrlen, sizeof(*addr_out));
- struct in6_addr any = IN6ADDR_ANY_INIT;
- EXPECT_EQ(memcmp(&addr_out->sin6_addr, &any, sizeof(in6_addr)), 0);
- }
-
- {
- socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(
- getpeername(sockfd, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallFailsWithErrno(ENOTCONN));
- }
-
- struct sockaddr_storage baddr = {};
- if (family == AddressFamily::kIpv4) {
- auto addr_in = reinterpret_cast<struct sockaddr_in*>(&baddr);
- addrlen = sizeof(*addr_in);
- addr_in->sin_family = AF_INET;
- addr_in->sin_addr.s_addr = htonl(INADDR_ANY);
- addr_in->sin_port = port;
- } else {
- auto addr_in = reinterpret_cast<struct sockaddr_in6*>(&baddr);
- addrlen = sizeof(*addr_in);
- addr_in->sin6_family = AF_INET6;
- addr_in->sin6_port = port;
- if (family == AddressFamily::kIpv6) {
- addr_in->sin6_addr = IN6ADDR_ANY_INIT;
- } else {
- TestAddress const& v4_mapped_any = V4MappedAny();
- addr_in->sin6_addr =
- reinterpret_cast<const struct sockaddr_in6*>(&v4_mapped_any.addr)
- ->sin6_addr;
- }
- }
-
- // TODO(b/138658473): gVisor doesn't allow connecting to the zero port.
- if (port == 0) {
- SKIP_IF(IsRunningOnGvisor());
- }
-
- ASSERT_THAT(connect(sockfd, reinterpret_cast<sockaddr*>(&baddr), addrlen),
- SyscallSucceeds());
- }
-
- // Postcondition check.
- {
- socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(
- getsockname(sockfd, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
-
- if (family == AddressFamily::kIpv4) {
- auto addr_out = reinterpret_cast<struct sockaddr_in*>(&addr);
- EXPECT_EQ(addrlen, sizeof(*addr_out));
- EXPECT_EQ(addr_out->sin_addr.s_addr, htonl(INADDR_LOOPBACK));
- } else {
- auto addr_out = reinterpret_cast<struct sockaddr_in6*>(&addr);
- EXPECT_EQ(addrlen, sizeof(*addr_out));
- struct in6_addr loopback;
- if (family == AddressFamily::kIpv6) {
- loopback = IN6ADDR_LOOPBACK_INIT;
- } else {
- TestAddress const& v4_mapped_loopback = V4MappedLoopback();
- loopback = reinterpret_cast<const struct sockaddr_in6*>(
- &v4_mapped_loopback.addr)
- ->sin6_addr;
- }
-
- EXPECT_EQ(memcmp(&addr_out->sin6_addr, &loopback, sizeof(in6_addr)), 0);
- }
-
- addrlen = sizeof(addr);
- if (port == 0) {
- EXPECT_THAT(
- getpeername(sockfd, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallFailsWithErrno(ENOTCONN));
- } else {
- EXPECT_THAT(
- getpeername(sockfd, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
- }
- }
-}
-
-TEST_P(UdpSocketTest, ConnectAny) { ConnectAny(GetParam(), s_, 0); }
-
-TEST_P(UdpSocketTest, ConnectAnyWithPort) {
- auto port = *Port(reinterpret_cast<struct sockaddr_storage*>(addr_[1]));
- ConnectAny(GetParam(), s_, port);
-}
-
-void DisconnectAfterConnectAny(AddressFamily family, int sockfd, int port) {
- struct sockaddr_storage addr = {};
-
- socklen_t addrlen = sizeof(addr);
- struct sockaddr_storage baddr = {};
- if (family == AddressFamily::kIpv4) {
- auto addr_in = reinterpret_cast<struct sockaddr_in*>(&baddr);
- addrlen = sizeof(*addr_in);
- addr_in->sin_family = AF_INET;
- addr_in->sin_addr.s_addr = htonl(INADDR_ANY);
- addr_in->sin_port = port;
- } else {
- auto addr_in = reinterpret_cast<struct sockaddr_in6*>(&baddr);
- addrlen = sizeof(*addr_in);
- addr_in->sin6_family = AF_INET6;
- addr_in->sin6_port = port;
- if (family == AddressFamily::kIpv6) {
- addr_in->sin6_addr = IN6ADDR_ANY_INIT;
- } else {
- TestAddress const& v4_mapped_any = V4MappedAny();
- addr_in->sin6_addr =
- reinterpret_cast<const struct sockaddr_in6*>(&v4_mapped_any.addr)
- ->sin6_addr;
- }
- }
-
- // TODO(b/138658473): gVisor doesn't allow connecting to the zero port.
- if (port == 0) {
- SKIP_IF(IsRunningOnGvisor());
- }
-
- ASSERT_THAT(connect(sockfd, reinterpret_cast<sockaddr*>(&baddr), addrlen),
- SyscallSucceeds());
- // Now the socket is bound to the loopback address.
-
- // Disconnect
- addrlen = sizeof(addr);
- addr.ss_family = AF_UNSPEC;
- ASSERT_THAT(connect(sockfd, reinterpret_cast<sockaddr*>(&addr), addrlen),
- SyscallSucceeds());
-
- // Check that after disconnect the socket is bound to the ANY address.
- EXPECT_THAT(getsockname(sockfd, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
- if (family == AddressFamily::kIpv4) {
- auto addr_out = reinterpret_cast<struct sockaddr_in*>(&addr);
- EXPECT_EQ(addrlen, sizeof(*addr_out));
- EXPECT_EQ(addr_out->sin_addr.s_addr, htonl(INADDR_ANY));
- } else {
- auto addr_out = reinterpret_cast<struct sockaddr_in6*>(&addr);
- EXPECT_EQ(addrlen, sizeof(*addr_out));
- struct in6_addr loopback = IN6ADDR_ANY_INIT;
-
- EXPECT_EQ(memcmp(&addr_out->sin6_addr, &loopback, sizeof(in6_addr)), 0);
- }
-}
-
-TEST_P(UdpSocketTest, DisconnectAfterConnectAny) {
- DisconnectAfterConnectAny(GetParam(), s_, 0);
-}
-
-TEST_P(UdpSocketTest, DisconnectAfterConnectAnyWithPort) {
- auto port = *Port(reinterpret_cast<struct sockaddr_storage*>(addr_[1]));
- DisconnectAfterConnectAny(GetParam(), s_, port);
-}
-
-TEST_P(UdpSocketTest, DisconnectAfterBind) {
- ASSERT_THAT(bind(s_, addr_[1], addrlen_), SyscallSucceeds());
- // Connect the socket.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
-
- struct sockaddr_storage addr = {};
- addr.ss_family = AF_UNSPEC;
- EXPECT_THAT(
- connect(s_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr.ss_family)),
- SyscallSucceeds());
-
- // Check that we're still bound.
- socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(getsockname(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
-
- EXPECT_EQ(addrlen, addrlen_);
- EXPECT_EQ(memcmp(&addr, addr_[1], addrlen_), 0);
-
- addrlen = sizeof(addr);
- EXPECT_THAT(getpeername(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallFailsWithErrno(ENOTCONN));
-}
-
-TEST_P(UdpSocketTest, DisconnectAfterBindToAny) {
- struct sockaddr_storage baddr = {};
- socklen_t addrlen;
- auto port = *Port(reinterpret_cast<struct sockaddr_storage*>(addr_[1]));
- if (GetParam() == AddressFamily::kIpv4) {
- auto addr_in = reinterpret_cast<struct sockaddr_in*>(&baddr);
- addr_in->sin_family = AF_INET;
- addr_in->sin_port = port;
- addr_in->sin_addr.s_addr = htonl(INADDR_ANY);
- } else {
- auto addr_in = reinterpret_cast<struct sockaddr_in6*>(&baddr);
- addr_in->sin6_family = AF_INET6;
- addr_in->sin6_port = port;
- addr_in->sin6_scope_id = 0;
- addr_in->sin6_addr = IN6ADDR_ANY_INIT;
- }
- ASSERT_THAT(bind(s_, reinterpret_cast<sockaddr*>(&baddr), addrlen_),
- SyscallSucceeds());
- // Connect the socket.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
-
- struct sockaddr_storage addr = {};
- addr.ss_family = AF_UNSPEC;
- EXPECT_THAT(
- connect(s_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr.ss_family)),
- SyscallSucceeds());
-
- // Check that we're still bound.
- addrlen = sizeof(addr);
- EXPECT_THAT(getsockname(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
-
- EXPECT_EQ(addrlen, addrlen_);
- EXPECT_EQ(memcmp(&addr, &baddr, addrlen), 0);
-
- addrlen = sizeof(addr);
- EXPECT_THAT(getpeername(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallFailsWithErrno(ENOTCONN));
-}
-
-TEST_P(UdpSocketTest, Disconnect) {
- for (int i = 0; i < 2; i++) {
- // Try to connect again.
- EXPECT_THAT(connect(s_, addr_[2], addrlen_), SyscallSucceeds());
-
- // Check that we're connected to the right peer.
- struct sockaddr_storage peer;
- socklen_t peerlen = sizeof(peer);
- EXPECT_THAT(getpeername(s_, reinterpret_cast<sockaddr*>(&peer), &peerlen),
- SyscallSucceeds());
- EXPECT_EQ(peerlen, addrlen_);
- EXPECT_EQ(memcmp(&peer, addr_[2], addrlen_), 0);
-
- // Try to disconnect.
- struct sockaddr_storage addr = {};
- addr.ss_family = AF_UNSPEC;
- EXPECT_THAT(
- connect(s_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr.ss_family)),
- SyscallSucceeds());
-
- peerlen = sizeof(peer);
- EXPECT_THAT(getpeername(s_, reinterpret_cast<sockaddr*>(&peer), &peerlen),
- SyscallFailsWithErrno(ENOTCONN));
-
- // Check that we're still bound.
- socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(getsockname(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
- EXPECT_EQ(addrlen, addrlen_);
- EXPECT_EQ(*Port(&addr), 0);
- }
-}
-
-TEST_P(UdpSocketTest, ConnectBadAddress) {
- struct sockaddr addr = {};
- addr.sa_family = addr_[0]->sa_family;
- ASSERT_THAT(connect(s_, &addr, sizeof(addr.sa_family)),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST_P(UdpSocketTest, SendToAddressOtherThanConnected) {
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
-
- // Send to a different destination than we're connected to.
- char buf[512];
- EXPECT_THAT(sendto(s_, buf, sizeof(buf), 0, addr_[1], addrlen_),
- SyscallSucceedsWithValue(sizeof(buf)));
-}
-
-TEST_P(UdpSocketTest, ZerolengthWriteAllowed) {
- // Bind s_ to loopback:TestPort, and connect to loopback:TestPort+1.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(s_, addr_[1], addrlen_), SyscallSucceeds());
-
- // Bind t_ to loopback:TestPort+1.
- ASSERT_THAT(bind(t_, addr_[1], addrlen_), SyscallSucceeds());
-
- char buf[3];
- // Send zero length packet from s_ to t_.
- ASSERT_THAT(write(s_, buf, 0), SyscallSucceedsWithValue(0));
- // Receive the packet.
- char received[3];
- EXPECT_THAT(read(t_, received, sizeof(received)),
- SyscallSucceedsWithValue(0));
-}
-
-TEST_P(UdpSocketTest, ZerolengthWriteAllowedNonBlockRead) {
- // Bind s_ to loopback:TestPort, and connect to loopback:TestPort+1.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(s_, addr_[1], addrlen_), SyscallSucceeds());
-
- // Bind t_ to loopback:TestPort+1.
- ASSERT_THAT(bind(t_, addr_[1], addrlen_), SyscallSucceeds());
-
- // Set t_ to non-blocking.
- int opts = 0;
- ASSERT_THAT(opts = fcntl(t_, F_GETFL), SyscallSucceeds());
- ASSERT_THAT(fcntl(t_, F_SETFL, opts | O_NONBLOCK), SyscallSucceeds());
-
- char buf[3];
- // Send zero length packet from s_ to t_.
- ASSERT_THAT(write(s_, buf, 0), SyscallSucceedsWithValue(0));
- // Receive the packet.
- char received[3];
- EXPECT_THAT(read(t_, received, sizeof(received)),
- SyscallSucceedsWithValue(0));
- EXPECT_THAT(read(t_, received, sizeof(received)),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST_P(UdpSocketTest, SendAndReceiveNotConnected) {
- // Bind s_ to loopback.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
-
- // Send some data to s_.
- char buf[512];
- RandomizeBuffer(buf, sizeof(buf));
-
- ASSERT_THAT(sendto(t_, buf, sizeof(buf), 0, addr_[0], addrlen_),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- // Receive the data.
- char received[sizeof(buf)];
- EXPECT_THAT(recv(s_, received, sizeof(received), 0),
- SyscallSucceedsWithValue(sizeof(received)));
- EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0);
-}
-
-TEST_P(UdpSocketTest, SendAndReceiveConnected) {
- // Bind s_ to loopback:TestPort, and connect to loopback:TestPort+1.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(s_, addr_[1], addrlen_), SyscallSucceeds());
-
- // Bind t_ to loopback:TestPort+1.
- ASSERT_THAT(bind(t_, addr_[1], addrlen_), SyscallSucceeds());
-
- // Send some data from t_ to s_.
- char buf[512];
- RandomizeBuffer(buf, sizeof(buf));
-
- ASSERT_THAT(sendto(t_, buf, sizeof(buf), 0, addr_[0], addrlen_),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- // Receive the data.
- char received[sizeof(buf)];
- EXPECT_THAT(recv(s_, received, sizeof(received), 0),
- SyscallSucceedsWithValue(sizeof(received)));
- EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0);
-}
-
-TEST_P(UdpSocketTest, ReceiveFromNotConnected) {
- // Bind s_ to loopback:TestPort, and connect to loopback:TestPort+1.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(s_, addr_[1], addrlen_), SyscallSucceeds());
-
- // Bind t_ to loopback:TestPort+2.
- ASSERT_THAT(bind(t_, addr_[2], addrlen_), SyscallSucceeds());
-
- // Send some data from t_ to s_.
- char buf[512];
- ASSERT_THAT(sendto(t_, buf, sizeof(buf), 0, addr_[0], addrlen_),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- // Check that the data isn't_ received because it was sent from a different
- // address than we're connected.
- EXPECT_THAT(recv(s_, buf, sizeof(buf), MSG_DONTWAIT),
- SyscallFailsWithErrno(EWOULDBLOCK));
-}
-
-TEST_P(UdpSocketTest, ReceiveBeforeConnect) {
- // Bind s_ to loopback:TestPort.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
-
- // Bind t_ to loopback:TestPort+2.
- ASSERT_THAT(bind(t_, addr_[2], addrlen_), SyscallSucceeds());
-
- // Send some data from t_ to s_.
- char buf[512];
- RandomizeBuffer(buf, sizeof(buf));
-
- ASSERT_THAT(sendto(t_, buf, sizeof(buf), 0, addr_[0], addrlen_),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- // Connect to loopback:TestPort+1.
- ASSERT_THAT(connect(s_, addr_[1], addrlen_), SyscallSucceeds());
-
- // Receive the data. It works because it was sent before the connect.
- char received[sizeof(buf)];
- EXPECT_THAT(recv(s_, received, sizeof(received), 0),
- SyscallSucceedsWithValue(sizeof(received)));
- EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0);
-
- // Send again. This time it should not be received.
- ASSERT_THAT(sendto(t_, buf, sizeof(buf), 0, addr_[0], addrlen_),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- EXPECT_THAT(recv(s_, buf, sizeof(buf), MSG_DONTWAIT),
- SyscallFailsWithErrno(EWOULDBLOCK));
-}
-
-TEST_P(UdpSocketTest, ReceiveFrom) {
- // Bind s_ to loopback:TestPort, and connect to loopback:TestPort+1.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(s_, addr_[1], addrlen_), SyscallSucceeds());
-
- // Bind t_ to loopback:TestPort+1.
- ASSERT_THAT(bind(t_, addr_[1], addrlen_), SyscallSucceeds());
-
- // Send some data from t_ to s_.
- char buf[512];
- RandomizeBuffer(buf, sizeof(buf));
-
- ASSERT_THAT(sendto(t_, buf, sizeof(buf), 0, addr_[0], addrlen_),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- // Receive the data and sender address.
- char received[sizeof(buf)];
- struct sockaddr_storage addr;
- socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(recvfrom(s_, received, sizeof(received), 0,
- reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceedsWithValue(sizeof(received)));
- EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0);
- EXPECT_EQ(addrlen, addrlen_);
- EXPECT_EQ(memcmp(&addr, addr_[1], addrlen_), 0);
-}
-
-TEST_P(UdpSocketTest, Listen) {
- ASSERT_THAT(listen(s_, SOMAXCONN), SyscallFailsWithErrno(EOPNOTSUPP));
-}
-
-TEST_P(UdpSocketTest, Accept) {
- ASSERT_THAT(accept(s_, nullptr, nullptr), SyscallFailsWithErrno(EOPNOTSUPP));
-}
-
-// This test validates that a read shutdown with pending data allows the read
-// to proceed with the data before returning EAGAIN.
-TEST_P(UdpSocketTest, ReadShutdownNonblockPendingData) {
- char received[512];
-
- // Bind t_ to loopback:TestPort+2.
- ASSERT_THAT(bind(t_, addr_[2], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(t_, addr_[1], addrlen_), SyscallSucceeds());
-
- // Connect the socket, then try to shutdown again.
- ASSERT_THAT(bind(s_, addr_[1], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(s_, addr_[2], addrlen_), SyscallSucceeds());
-
- // Verify that we get EWOULDBLOCK when there is nothing to read.
- EXPECT_THAT(recv(s_, received, sizeof(received), MSG_DONTWAIT),
- SyscallFailsWithErrno(EWOULDBLOCK));
-
- const char* buf = "abc";
- EXPECT_THAT(write(t_, buf, 3), SyscallSucceedsWithValue(3));
-
- int opts = 0;
- ASSERT_THAT(opts = fcntl(s_, F_GETFL), SyscallSucceeds());
- ASSERT_THAT(fcntl(s_, F_SETFL, opts | O_NONBLOCK), SyscallSucceeds());
- ASSERT_THAT(opts = fcntl(s_, F_GETFL), SyscallSucceeds());
- ASSERT_NE(opts & O_NONBLOCK, 0);
-
- EXPECT_THAT(shutdown(s_, SHUT_RD), SyscallSucceeds());
-
- // We should get the data even though read has been shutdown.
- EXPECT_THAT(recv(s_, received, 2, 0), SyscallSucceedsWithValue(2));
-
- // Because we read less than the entire packet length, since it's a packet
- // based socket any subsequent reads should return EWOULDBLOCK.
- EXPECT_THAT(recv(s_, received, 1, 0), SyscallFailsWithErrno(EWOULDBLOCK));
-}
-
-// This test is validating that even after a socket is shutdown if it's
-// reconnected it will reset the shutdown state.
-TEST_P(UdpSocketTest, ReadShutdownSameSocketResetsShutdownState) {
- char received[512];
- EXPECT_THAT(recv(s_, received, sizeof(received), MSG_DONTWAIT),
- SyscallFailsWithErrno(EWOULDBLOCK));
-
- EXPECT_THAT(shutdown(s_, SHUT_RD), SyscallFailsWithErrno(ENOTCONN));
-
- EXPECT_THAT(recv(s_, received, sizeof(received), MSG_DONTWAIT),
- SyscallFailsWithErrno(EWOULDBLOCK));
-
- // Connect the socket, then try to shutdown again.
- ASSERT_THAT(bind(s_, addr_[1], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(s_, addr_[2], addrlen_), SyscallSucceeds());
-
- EXPECT_THAT(recv(s_, received, sizeof(received), MSG_DONTWAIT),
- SyscallFailsWithErrno(EWOULDBLOCK));
-}
-
-TEST_P(UdpSocketTest, ReadShutdown) {
- char received[512];
- EXPECT_THAT(recv(s_, received, sizeof(received), MSG_DONTWAIT),
- SyscallFailsWithErrno(EWOULDBLOCK));
-
- EXPECT_THAT(shutdown(s_, SHUT_RD), SyscallFailsWithErrno(ENOTCONN));
-
- EXPECT_THAT(recv(s_, received, sizeof(received), MSG_DONTWAIT),
- SyscallFailsWithErrno(EWOULDBLOCK));
-
- // Connect the socket, then try to shutdown again.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
-
- EXPECT_THAT(recv(s_, received, sizeof(received), MSG_DONTWAIT),
- SyscallFailsWithErrno(EWOULDBLOCK));
-
- EXPECT_THAT(shutdown(s_, SHUT_RD), SyscallSucceeds());
-
- EXPECT_THAT(recv(s_, received, sizeof(received), 0),
- SyscallSucceedsWithValue(0));
-}
-
-TEST_P(UdpSocketTest, ReadShutdownDifferentThread) {
- char received[512];
- EXPECT_THAT(recv(s_, received, sizeof(received), MSG_DONTWAIT),
- SyscallFailsWithErrno(EWOULDBLOCK));
-
- // Connect the socket, then shutdown from another thread.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
-
- EXPECT_THAT(recv(s_, received, sizeof(received), MSG_DONTWAIT),
- SyscallFailsWithErrno(EWOULDBLOCK));
-
- ScopedThread t([&] {
- absl::SleepFor(absl::Milliseconds(200));
- EXPECT_THAT(shutdown(this->s_, SHUT_RD), SyscallSucceeds());
- });
- EXPECT_THAT(RetryEINTR(recv)(s_, received, sizeof(received), 0),
- SyscallSucceedsWithValue(0));
- t.Join();
-
- EXPECT_THAT(RetryEINTR(recv)(s_, received, sizeof(received), 0),
- SyscallSucceedsWithValue(0));
-}
-
-TEST_P(UdpSocketTest, WriteShutdown) {
- EXPECT_THAT(shutdown(s_, SHUT_WR), SyscallFailsWithErrno(ENOTCONN));
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
- EXPECT_THAT(shutdown(s_, SHUT_WR), SyscallSucceeds());
-}
-
-TEST_P(UdpSocketTest, SynchronousReceive) {
- // Bind s_ to loopback.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
-
- // Send some data to s_ from another thread.
- char buf[512];
- RandomizeBuffer(buf, sizeof(buf));
-
- // Receive the data prior to actually starting the other thread.
- char received[512];
- EXPECT_THAT(RetryEINTR(recv)(s_, received, sizeof(received), MSG_DONTWAIT),
- SyscallFailsWithErrno(EWOULDBLOCK));
-
- // Start the thread.
- ScopedThread t([&] {
- absl::SleepFor(absl::Milliseconds(200));
- ASSERT_THAT(
- sendto(this->t_, buf, sizeof(buf), 0, this->addr_[0], this->addrlen_),
- SyscallSucceedsWithValue(sizeof(buf)));
- });
-
- EXPECT_THAT(RetryEINTR(recv)(s_, received, sizeof(received), 0),
- SyscallSucceedsWithValue(512));
- EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0);
-}
-
-TEST_P(UdpSocketTest, BoundaryPreserved_SendRecv) {
- // Bind s_ to loopback:TestPort.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
-
- // Send 3 packets from t_ to s_.
- constexpr int psize = 100;
- char buf[3 * psize];
- RandomizeBuffer(buf, sizeof(buf));
-
- for (int i = 0; i < 3; ++i) {
- ASSERT_THAT(sendto(t_, buf + i * psize, psize, 0, addr_[0], addrlen_),
- SyscallSucceedsWithValue(psize));
- }
-
- // Receive the data as 3 separate packets.
- char received[6 * psize];
- for (int i = 0; i < 3; ++i) {
- EXPECT_THAT(recv(s_, received + i * psize, 3 * psize, 0),
- SyscallSucceedsWithValue(psize));
- }
- EXPECT_EQ(memcmp(buf, received, 3 * psize), 0);
-}
-
-TEST_P(UdpSocketTest, BoundaryPreserved_WritevReadv) {
- // Bind s_ to loopback:TestPort.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
-
- // Direct writes from t_ to s_.
- ASSERT_THAT(connect(t_, addr_[0], addrlen_), SyscallSucceeds());
-
- // Send 2 packets from t_ to s_, where each packet's data consists of 2
- // discontiguous iovecs.
- constexpr size_t kPieceSize = 100;
- char buf[4 * kPieceSize];
- RandomizeBuffer(buf, sizeof(buf));
-
- for (int i = 0; i < 2; i++) {
- struct iovec iov[2];
- for (int j = 0; j < 2; j++) {
- iov[j].iov_base = reinterpret_cast<void*>(
- reinterpret_cast<uintptr_t>(buf) + (i + 2 * j) * kPieceSize);
- iov[j].iov_len = kPieceSize;
- }
- ASSERT_THAT(writev(t_, iov, 2), SyscallSucceedsWithValue(2 * kPieceSize));
- }
-
- // Receive the data as 2 separate packets.
- char received[6 * kPieceSize];
- for (int i = 0; i < 2; i++) {
- struct iovec iov[3];
- for (int j = 0; j < 3; j++) {
- iov[j].iov_base = reinterpret_cast<void*>(
- reinterpret_cast<uintptr_t>(received) + (i + 2 * j) * kPieceSize);
- iov[j].iov_len = kPieceSize;
- }
- ASSERT_THAT(readv(s_, iov, 3), SyscallSucceedsWithValue(2 * kPieceSize));
- }
- EXPECT_EQ(memcmp(buf, received, 4 * kPieceSize), 0);
-}
-
-TEST_P(UdpSocketTest, BoundaryPreserved_SendMsgRecvMsg) {
- // Bind s_ to loopback:TestPort.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
-
- // Send 2 packets from t_ to s_, where each packet's data consists of 2
- // discontiguous iovecs.
- constexpr size_t kPieceSize = 100;
- char buf[4 * kPieceSize];
- RandomizeBuffer(buf, sizeof(buf));
-
- for (int i = 0; i < 2; i++) {
- struct iovec iov[2];
- for (int j = 0; j < 2; j++) {
- iov[j].iov_base = reinterpret_cast<void*>(
- reinterpret_cast<uintptr_t>(buf) + (i + 2 * j) * kPieceSize);
- iov[j].iov_len = kPieceSize;
- }
- struct msghdr msg = {};
- msg.msg_name = addr_[0];
- msg.msg_namelen = addrlen_;
- msg.msg_iov = iov;
- msg.msg_iovlen = 2;
- ASSERT_THAT(sendmsg(t_, &msg, 0), SyscallSucceedsWithValue(2 * kPieceSize));
- }
-
- // Receive the data as 2 separate packets.
- char received[6 * kPieceSize];
- for (int i = 0; i < 2; i++) {
- struct iovec iov[3];
- for (int j = 0; j < 3; j++) {
- iov[j].iov_base = reinterpret_cast<void*>(
- reinterpret_cast<uintptr_t>(received) + (i + 2 * j) * kPieceSize);
- iov[j].iov_len = kPieceSize;
- }
- struct msghdr msg = {};
- msg.msg_iov = iov;
- msg.msg_iovlen = 3;
- ASSERT_THAT(recvmsg(s_, &msg, 0), SyscallSucceedsWithValue(2 * kPieceSize));
- }
- EXPECT_EQ(memcmp(buf, received, 4 * kPieceSize), 0);
-}
-
-TEST_P(UdpSocketTest, FIONREADShutdown) {
- int n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
- EXPECT_EQ(n, 0);
-
- // A UDP socket must be connected before it can be shutdown.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
-
- n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
- EXPECT_EQ(n, 0);
-
- EXPECT_THAT(shutdown(s_, SHUT_RD), SyscallSucceeds());
-
- n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
- EXPECT_EQ(n, 0);
-}
-
-TEST_P(UdpSocketTest, FIONREADWriteShutdown) {
- int n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
- EXPECT_EQ(n, 0);
-
- // Bind s_ to loopback:TestPort.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
-
- // A UDP socket must be connected before it can be shutdown.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
-
- n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
- EXPECT_EQ(n, 0);
-
- const char str[] = "abc";
- ASSERT_THAT(send(s_, str, sizeof(str), 0),
- SyscallSucceedsWithValue(sizeof(str)));
-
- n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
- EXPECT_EQ(n, sizeof(str));
-
- EXPECT_THAT(shutdown(s_, SHUT_RD), SyscallSucceeds());
-
- n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
- EXPECT_EQ(n, sizeof(str));
-}
-
-TEST_P(UdpSocketTest, FIONREAD) {
- // Bind s_ to loopback:TestPort.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
-
- // Check that the bound socket with an empty buffer reports an empty first
- // packet.
- int n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
- EXPECT_EQ(n, 0);
-
- // Send 3 packets from t_ to s_.
- constexpr int psize = 100;
- char buf[3 * psize];
- RandomizeBuffer(buf, sizeof(buf));
-
- for (int i = 0; i < 3; ++i) {
- ASSERT_THAT(sendto(t_, buf + i * psize, psize, 0, addr_[0], addrlen_),
- SyscallSucceedsWithValue(psize));
-
- // Check that regardless of how many packets are in the queue, the size
- // reported is that of a single packet.
- n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
- EXPECT_EQ(n, psize);
- }
-}
-
-TEST_P(UdpSocketTest, FIONREADZeroLengthPacket) {
- // Bind s_ to loopback:TestPort.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
-
- // Check that the bound socket with an empty buffer reports an empty first
- // packet.
- int n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
- EXPECT_EQ(n, 0);
-
- // Send 3 packets from t_ to s_.
- constexpr int psize = 100;
- char buf[3 * psize];
- RandomizeBuffer(buf, sizeof(buf));
-
- for (int i = 0; i < 3; ++i) {
- ASSERT_THAT(sendto(t_, buf + i * psize, 0, 0, addr_[0], addrlen_),
- SyscallSucceedsWithValue(0));
-
- // Check that regardless of how many packets are in the queue, the size
- // reported is that of a single packet.
- n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
- EXPECT_EQ(n, 0);
- }
-}
-
-TEST_P(UdpSocketTest, FIONREADZeroLengthWriteShutdown) {
- int n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
- EXPECT_EQ(n, 0);
-
- // Bind s_ to loopback:TestPort.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
-
- // A UDP socket must be connected before it can be shutdown.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
-
- n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
- EXPECT_EQ(n, 0);
-
- const char str[] = "abc";
- ASSERT_THAT(send(s_, str, 0, 0), SyscallSucceedsWithValue(0));
-
- n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
- EXPECT_EQ(n, 0);
-
- EXPECT_THAT(shutdown(s_, SHUT_RD), SyscallSucceeds());
-
- n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
- EXPECT_EQ(n, 0);
-}
-
-TEST_P(UdpSocketTest, ErrorQueue) {
- char cmsgbuf[CMSG_SPACE(sizeof(sock_extended_err))];
- msghdr msg;
- memset(&msg, 0, sizeof(msg));
- iovec iov;
- memset(&iov, 0, sizeof(iov));
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
- msg.msg_control = cmsgbuf;
- msg.msg_controllen = sizeof(cmsgbuf);
-
- // recv*(MSG_ERRQUEUE) never blocks, even without MSG_DONTWAIT.
- EXPECT_THAT(RetryEINTR(recvmsg)(s_, &msg, MSG_ERRQUEUE),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST_P(UdpSocketTest, SoTimestampOffByDefault) {
- int v = -1;
- socklen_t optlen = sizeof(v);
- ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_TIMESTAMP, &v, &optlen),
- SyscallSucceeds());
- ASSERT_EQ(v, kSockOptOff);
- ASSERT_EQ(optlen, sizeof(v));
-}
-
-TEST_P(UdpSocketTest, SoTimestamp) {
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(t_, addr_[0], addrlen_), SyscallSucceeds());
-
- int v = 1;
- ASSERT_THAT(setsockopt(s_, SOL_SOCKET, SO_TIMESTAMP, &v, sizeof(v)),
- SyscallSucceeds());
-
- char buf[3];
- // Send zero length packet from t_ to s_.
- ASSERT_THAT(RetryEINTR(write)(t_, buf, 0), SyscallSucceedsWithValue(0));
-
- char cmsgbuf[CMSG_SPACE(sizeof(struct timeval))];
- msghdr msg;
- memset(&msg, 0, sizeof(msg));
- iovec iov;
- memset(&iov, 0, sizeof(iov));
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
- msg.msg_control = cmsgbuf;
- msg.msg_controllen = sizeof(cmsgbuf);
-
- ASSERT_THAT(RetryEINTR(recvmsg)(s_, &msg, 0), SyscallSucceedsWithValue(0));
-
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
- ASSERT_NE(cmsg, nullptr);
- ASSERT_EQ(cmsg->cmsg_level, SOL_SOCKET);
- ASSERT_EQ(cmsg->cmsg_type, SO_TIMESTAMP);
- ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(struct timeval)));
-
- struct timeval tv = {};
- memcpy(&tv, CMSG_DATA(cmsg), sizeof(struct timeval));
-
- ASSERT_TRUE(tv.tv_sec != 0 || tv.tv_usec != 0);
-
- // There should be nothing to get via ioctl.
- ASSERT_THAT(ioctl(s_, SIOCGSTAMP, &tv), SyscallFailsWithErrno(ENOENT));
-}
-
-TEST_P(UdpSocketTest, WriteShutdownNotConnected) {
- EXPECT_THAT(shutdown(s_, SHUT_WR), SyscallFailsWithErrno(ENOTCONN));
-}
-
-TEST_P(UdpSocketTest, TimestampIoctl) {
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(t_, addr_[0], addrlen_), SyscallSucceeds());
-
- char buf[3];
- // Send packet from t_ to s_.
- ASSERT_THAT(RetryEINTR(write)(t_, buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
-
- // There should be no control messages.
- char recv_buf[sizeof(buf)];
- ASSERT_NO_FATAL_FAILURE(RecvNoCmsg(s_, recv_buf, sizeof(recv_buf)));
-
- // A nonzero timeval should be available via ioctl.
- struct timeval tv = {};
- ASSERT_THAT(ioctl(s_, SIOCGSTAMP, &tv), SyscallSucceeds());
- ASSERT_TRUE(tv.tv_sec != 0 || tv.tv_usec != 0);
-}
-
-TEST_P(UdpSocketTest, TimetstampIoctlNothingRead) {
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(t_, addr_[0], addrlen_), SyscallSucceeds());
-
- struct timeval tv = {};
- ASSERT_THAT(ioctl(s_, SIOCGSTAMP, &tv), SyscallFailsWithErrno(ENOENT));
-}
-
-// Test that the timestamp accessed via SIOCGSTAMP is still accessible after
-// SO_TIMESTAMP is enabled and used to retrieve a timestamp.
-TEST_P(UdpSocketTest, TimestampIoctlPersistence) {
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(t_, addr_[0], addrlen_), SyscallSucceeds());
-
- char buf[3];
- // Send packet from t_ to s_.
- ASSERT_THAT(RetryEINTR(write)(t_, buf, sizeof(buf)),
- SyscallSucceedsWithValue(sizeof(buf)));
- ASSERT_THAT(RetryEINTR(write)(t_, buf, 0), SyscallSucceedsWithValue(0));
-
- // There should be no control messages.
- char recv_buf[sizeof(buf)];
- ASSERT_NO_FATAL_FAILURE(RecvNoCmsg(s_, recv_buf, sizeof(recv_buf)));
-
- // A nonzero timeval should be available via ioctl.
- struct timeval tv = {};
- ASSERT_THAT(ioctl(s_, SIOCGSTAMP, &tv), SyscallSucceeds());
- ASSERT_TRUE(tv.tv_sec != 0 || tv.tv_usec != 0);
-
- // Enable SO_TIMESTAMP and send a message.
- int v = 1;
- EXPECT_THAT(setsockopt(s_, SOL_SOCKET, SO_TIMESTAMP, &v, sizeof(v)),
- SyscallSucceeds());
- ASSERT_THAT(RetryEINTR(write)(t_, buf, 0), SyscallSucceedsWithValue(0));
-
- // There should be a message for SO_TIMESTAMP.
- char cmsgbuf[CMSG_SPACE(sizeof(struct timeval))];
- msghdr msg = {};
- iovec iov = {};
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
- msg.msg_control = cmsgbuf;
- msg.msg_controllen = sizeof(cmsgbuf);
- ASSERT_THAT(RetryEINTR(recvmsg)(s_, &msg, 0), SyscallSucceedsWithValue(0));
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
- cmsg = CMSG_FIRSTHDR(&msg);
- ASSERT_NE(cmsg, nullptr);
-
- // The ioctl should return the exact same values as before.
- struct timeval tv2 = {};
- ASSERT_THAT(ioctl(s_, SIOCGSTAMP, &tv2), SyscallSucceeds());
- ASSERT_EQ(tv.tv_sec, tv2.tv_sec);
- ASSERT_EQ(tv.tv_usec, tv2.tv_usec);
-}
-
-INSTANTIATE_TEST_SUITE_P(AllInetTests, UdpSocketTest,
- ::testing::Values(AddressFamily::kIpv4,
- AddressFamily::kIpv6,
- AddressFamily::kDualStack));
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/uidgid.cc b/test/syscalls/linux/uidgid.cc
deleted file mode 100644
index bf1ca8679..000000000
--- a/test/syscalls/linux/uidgid.cc
+++ /dev/null
@@ -1,277 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <grp.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "absl/strings/str_cat.h"
-#include "absl/strings/str_join.h"
-#include "test/util/capability_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-DEFINE_int32(scratch_uid1, 65534, "first scratch UID");
-DEFINE_int32(scratch_uid2, 65533, "second scratch UID");
-DEFINE_int32(scratch_gid1, 65534, "first scratch GID");
-DEFINE_int32(scratch_gid2, 65533, "second scratch GID");
-
-using ::testing::UnorderedElementsAreArray;
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(UidGidTest, Getuid) {
- uid_t ruid, euid, suid;
- EXPECT_THAT(getresuid(&ruid, &euid, &suid), SyscallSucceeds());
- EXPECT_THAT(getuid(), SyscallSucceedsWithValue(ruid));
- EXPECT_THAT(geteuid(), SyscallSucceedsWithValue(euid));
-}
-
-TEST(UidGidTest, Getgid) {
- gid_t rgid, egid, sgid;
- EXPECT_THAT(getresgid(&rgid, &egid, &sgid), SyscallSucceeds());
- EXPECT_THAT(getgid(), SyscallSucceedsWithValue(rgid));
- EXPECT_THAT(getegid(), SyscallSucceedsWithValue(egid));
-}
-
-TEST(UidGidTest, Getgroups) {
- // "If size is zero, list is not modified, but the total number of
- // supplementary group IDs for the process is returned." - getgroups(2)
- int nr_groups;
- ASSERT_THAT(nr_groups = getgroups(0, nullptr), SyscallSucceeds());
- std::vector<gid_t> list(nr_groups);
- EXPECT_THAT(getgroups(list.size(), list.data()), SyscallSucceeds());
-
- // "EINVAL: size is less than the number of supplementary group IDs, but is
- // not zero."
- EXPECT_THAT(getgroups(-1, nullptr), SyscallFailsWithErrno(EINVAL));
-
- // Testing for EFAULT requires actually having groups, which isn't guaranteed
- // here; see the setgroups test below.
-}
-
-// If the caller's real/effective/saved user/group IDs are all 0, IsRoot returns
-// true. Otherwise IsRoot logs an explanatory message and returns false.
-PosixErrorOr<bool> IsRoot() {
- uid_t ruid, euid, suid;
- int rc = getresuid(&ruid, &euid, &suid);
- MaybeSave();
- if (rc < 0) {
- return PosixError(errno, "getresuid");
- }
- if (ruid != 0 || euid != 0 || suid != 0) {
- return false;
- }
- gid_t rgid, egid, sgid;
- rc = getresgid(&rgid, &egid, &sgid);
- MaybeSave();
- if (rc < 0) {
- return PosixError(errno, "getresgid");
- }
- if (rgid != 0 || egid != 0 || sgid != 0) {
- return false;
- }
- return true;
-}
-
-// Checks that the calling process' real/effective/saved user IDs are
-// ruid/euid/suid respectively.
-PosixError CheckUIDs(uid_t ruid, uid_t euid, uid_t suid) {
- uid_t actual_ruid, actual_euid, actual_suid;
- int rc = getresuid(&actual_ruid, &actual_euid, &actual_suid);
- MaybeSave();
- if (rc < 0) {
- return PosixError(errno, "getresuid");
- }
- if (ruid != actual_ruid || euid != actual_euid || suid != actual_suid) {
- return PosixError(
- EPERM, absl::StrCat(
- "incorrect user IDs: got (",
- absl::StrJoin({actual_ruid, actual_euid, actual_suid}, ", "),
- ", wanted (", absl::StrJoin({ruid, euid, suid}, ", "), ")"));
- }
- return NoError();
-}
-
-PosixError CheckGIDs(gid_t rgid, gid_t egid, gid_t sgid) {
- gid_t actual_rgid, actual_egid, actual_sgid;
- int rc = getresgid(&actual_rgid, &actual_egid, &actual_sgid);
- MaybeSave();
- if (rc < 0) {
- return PosixError(errno, "getresgid");
- }
- if (rgid != actual_rgid || egid != actual_egid || sgid != actual_sgid) {
- return PosixError(
- EPERM, absl::StrCat(
- "incorrect group IDs: got (",
- absl::StrJoin({actual_rgid, actual_egid, actual_sgid}, ", "),
- ", wanted (", absl::StrJoin({rgid, egid, sgid}, ", "), ")"));
- }
- return NoError();
-}
-
-// N.B. These tests may break horribly unless run via a gVisor test runner,
-// because changing UID in one test may forfeit permissions required by other
-// tests. (The test runner runs each test in a separate process.)
-
-TEST(UidGidRootTest, Setuid) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot()));
-
- // Do setuid in a separate thread so that after finishing this test, the
- // process can still open files the test harness created before starting this
- // test. Otherwise, the files are created by root (UID before the test), but
- // cannot be opened by the `uid` set below after the test. After calling
- // setuid(non-zero-UID), there is no way to get root privileges back.
- ScopedThread([&] {
- // Use syscall instead of glibc setuid wrapper because we want this setuid
- // call to only apply to this task. POSIX threads, however, require that all
- // threads have the same UIDs, so using the setuid wrapper sets all threads'
- // real UID.
- EXPECT_THAT(syscall(SYS_setuid, -1), SyscallFailsWithErrno(EINVAL));
-
- const uid_t uid = FLAGS_scratch_uid1;
- EXPECT_THAT(syscall(SYS_setuid, uid), SyscallSucceeds());
- // "If the effective UID of the caller is root (more precisely: if the
- // caller has the CAP_SETUID capability), the real UID and saved set-user-ID
- // are also set." - setuid(2)
- EXPECT_NO_ERRNO(CheckUIDs(uid, uid, uid));
- });
-}
-
-TEST(UidGidRootTest, Setgid) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot()));
-
- EXPECT_THAT(setgid(-1), SyscallFailsWithErrno(EINVAL));
-
- const gid_t gid = FLAGS_scratch_gid1;
- ASSERT_THAT(setgid(gid), SyscallSucceeds());
- EXPECT_NO_ERRNO(CheckGIDs(gid, gid, gid));
-}
-
-TEST(UidGidRootTest, SetgidNotFromThreadGroupLeader) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot()));
-
- const gid_t gid = FLAGS_scratch_gid1;
- // NOTE(b/64676707): Do setgid in a separate thread so that we can test if
- // info.si_pid is set correctly.
- ScopedThread([gid] { ASSERT_THAT(setgid(gid), SyscallSucceeds()); });
- EXPECT_NO_ERRNO(CheckGIDs(gid, gid, gid));
-}
-
-TEST(UidGidRootTest, Setreuid) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot()));
-
- // "Supplying a value of -1 for either the real or effective user ID forces
- // the system to leave that ID unchanged." - setreuid(2)
- EXPECT_THAT(setreuid(-1, -1), SyscallSucceeds());
- EXPECT_NO_ERRNO(CheckUIDs(0, 0, 0));
-
- // Do setuid in a separate thread so that after finishing this test, the
- // process can still open files the test harness created before starting this
- // test. Otherwise, the files are created by root (UID before the test), but
- // cannot be opened by the `uid` set below after the test. After calling
- // setuid(non-zero-UID), there is no way to get root privileges back.
- ScopedThread([&] {
- const uid_t ruid = FLAGS_scratch_uid1;
- const uid_t euid = FLAGS_scratch_uid2;
-
- // Use syscall instead of glibc setuid wrapper because we want this setuid
- // call to only apply to this task. posix threads, however, require that all
- // threads have the same UIDs, so using the setuid wrapper sets all threads'
- // real UID.
- EXPECT_THAT(syscall(SYS_setreuid, ruid, euid), SyscallSucceeds());
-
- // "If the real user ID is set or the effective user ID is set to a value
- // not equal to the previous real user ID, the saved set-user-ID will be set
- // to the new effective user ID." - setreuid(2)
- EXPECT_NO_ERRNO(CheckUIDs(ruid, euid, euid));
- });
-}
-
-TEST(UidGidRootTest, Setregid) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot()));
-
- EXPECT_THAT(setregid(-1, -1), SyscallSucceeds());
- EXPECT_NO_ERRNO(CheckGIDs(0, 0, 0));
-
- const gid_t rgid = FLAGS_scratch_gid1;
- const gid_t egid = FLAGS_scratch_gid2;
- ASSERT_THAT(setregid(rgid, egid), SyscallSucceeds());
- EXPECT_NO_ERRNO(CheckGIDs(rgid, egid, egid));
-}
-
-TEST(UidGidRootTest, Setresuid) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot()));
-
- // "If one of the arguments equals -1, the corresponding value is not
- // changed." - setresuid(2)
- EXPECT_THAT(setresuid(-1, -1, -1), SyscallSucceeds());
- EXPECT_NO_ERRNO(CheckUIDs(0, 0, 0));
-
- // Do setuid in a separate thread so that after finishing this test, the
- // process can still open files the test harness created before starting this
- // test. Otherwise, the files are created by root (UID before the test), but
- // cannot be opened by the `uid` set below after the test. After calling
- // setuid(non-zero-UID), there is no way to get root privileges back.
- ScopedThread([&] {
- const uid_t ruid = 12345;
- const uid_t euid = 23456;
- const uid_t suid = 34567;
-
- // Use syscall instead of glibc setuid wrapper because we want this setuid
- // call to only apply to this task. posix threads, however, require that all
- // threads have the same UIDs, so using the setuid wrapper sets all threads'
- // real UID.
- EXPECT_THAT(syscall(SYS_setresuid, ruid, euid, suid), SyscallSucceeds());
- EXPECT_NO_ERRNO(CheckUIDs(ruid, euid, suid));
- });
-}
-
-TEST(UidGidRootTest, Setresgid) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot()));
-
- EXPECT_THAT(setresgid(-1, -1, -1), SyscallSucceeds());
- EXPECT_NO_ERRNO(CheckGIDs(0, 0, 0));
-
- const gid_t rgid = 12345;
- const gid_t egid = 23456;
- const gid_t sgid = 34567;
- ASSERT_THAT(setresgid(rgid, egid, sgid), SyscallSucceeds());
- EXPECT_NO_ERRNO(CheckGIDs(rgid, egid, sgid));
-}
-
-TEST(UidGidRootTest, Setgroups) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot()));
-
- std::vector<gid_t> list = {123, 500};
- ASSERT_THAT(setgroups(list.size(), list.data()), SyscallSucceeds());
- std::vector<gid_t> list2(list.size());
- ASSERT_THAT(getgroups(list2.size(), list2.data()), SyscallSucceeds());
- EXPECT_THAT(list, UnorderedElementsAreArray(list2));
-
- // "EFAULT: list has an invalid address."
- EXPECT_THAT(getgroups(100, reinterpret_cast<gid_t*>(-1)),
- SyscallFailsWithErrno(EFAULT));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/uname.cc b/test/syscalls/linux/uname.cc
deleted file mode 100644
index 0a5d91017..000000000
--- a/test/syscalls/linux/uname.cc
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sched.h>
-#include <sys/utsname.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "absl/strings/string_view.h"
-#include "test/util/capability_util.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(UnameTest, Sanity) {
- struct utsname buf;
- ASSERT_THAT(uname(&buf), SyscallSucceeds());
- EXPECT_NE(strlen(buf.release), 0);
- EXPECT_NE(strlen(buf.version), 0);
- EXPECT_NE(strlen(buf.machine), 0);
- EXPECT_NE(strlen(buf.sysname), 0);
- EXPECT_NE(strlen(buf.nodename), 0);
- EXPECT_NE(strlen(buf.domainname), 0);
-}
-
-TEST(UnameTest, SetNames) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- constexpr char kHostname[] = "wubbalubba";
- ASSERT_THAT(sethostname(kHostname, sizeof(kHostname)), SyscallSucceeds());
-
- constexpr char kDomainname[] = "dubdub.com";
- ASSERT_THAT(setdomainname(kDomainname, sizeof(kDomainname)),
- SyscallSucceeds());
-
- struct utsname buf;
- EXPECT_THAT(uname(&buf), SyscallSucceeds());
- EXPECT_EQ(absl::string_view(buf.nodename), kHostname);
- EXPECT_EQ(absl::string_view(buf.domainname), kDomainname);
-
- // These should just be glibc wrappers that also call uname(2).
- char hostname[65];
- EXPECT_THAT(gethostname(hostname, sizeof(hostname)), SyscallSucceeds());
- EXPECT_EQ(absl::string_view(hostname), kHostname);
-
- char domainname[65];
- EXPECT_THAT(getdomainname(domainname, sizeof(domainname)), SyscallSucceeds());
- EXPECT_EQ(absl::string_view(domainname), kDomainname);
-}
-
-TEST(UnameTest, UnprivilegedSetNames) {
- if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))) {
- EXPECT_NO_ERRNO(SetCapability(CAP_SYS_ADMIN, false));
- }
-
- EXPECT_THAT(sethostname("", 0), SyscallFailsWithErrno(EPERM));
- EXPECT_THAT(setdomainname("", 0), SyscallFailsWithErrno(EPERM));
-}
-
-TEST(UnameTest, UnshareUTS) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- struct utsname init;
- ASSERT_THAT(uname(&init), SyscallSucceeds());
-
- ScopedThread([&]() {
- EXPECT_THAT(unshare(CLONE_NEWUTS), SyscallSucceeds());
-
- constexpr char kHostname[] = "wubbalubba";
- EXPECT_THAT(sethostname(kHostname, sizeof(kHostname)), SyscallSucceeds());
-
- char hostname[65];
- EXPECT_THAT(gethostname(hostname, sizeof(hostname)), SyscallSucceeds());
- });
-
- struct utsname after;
- EXPECT_THAT(uname(&after), SyscallSucceeds());
- EXPECT_EQ(absl::string_view(after.nodename), init.nodename);
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/unix_domain_socket_test_util.cc b/test/syscalls/linux/unix_domain_socket_test_util.cc
deleted file mode 100644
index 7fb9eed8d..000000000
--- a/test/syscalls/linux/unix_domain_socket_test_util.cc
+++ /dev/null
@@ -1,350 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-
-#include <sys/un.h>
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "absl/strings/str_cat.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-std::string DescribeUnixDomainSocketType(int type) {
- const char* type_str = nullptr;
- switch (type & ~(SOCK_NONBLOCK | SOCK_CLOEXEC)) {
- case SOCK_STREAM:
- type_str = "SOCK_STREAM";
- break;
- case SOCK_DGRAM:
- type_str = "SOCK_DGRAM";
- break;
- case SOCK_SEQPACKET:
- type_str = "SOCK_SEQPACKET";
- break;
- }
- if (!type_str) {
- return absl::StrCat("Unix domain socket with unknown type ", type);
- } else {
- return absl::StrCat(((type & SOCK_NONBLOCK) != 0) ? "non-blocking " : "",
- ((type & SOCK_CLOEXEC) != 0) ? "close-on-exec " : "",
- type_str, " Unix domain socket");
- }
-}
-
-SocketPairKind UnixDomainSocketPair(int type) {
- return SocketPairKind{DescribeUnixDomainSocketType(type), AF_UNIX, type, 0,
- SyscallSocketPairCreator(AF_UNIX, type, 0)};
-}
-
-SocketPairKind FilesystemBoundUnixDomainSocketPair(int type) {
- std::string description = absl::StrCat(DescribeUnixDomainSocketType(type),
- " created with filesystem binding");
- if ((type & SOCK_DGRAM) == SOCK_DGRAM) {
- return SocketPairKind{
- description, AF_UNIX, type, 0,
- FilesystemBidirectionalBindSocketPairCreator(AF_UNIX, type, 0)};
- }
- return SocketPairKind{
- description, AF_UNIX, type, 0,
- FilesystemAcceptBindSocketPairCreator(AF_UNIX, type, 0)};
-}
-
-SocketPairKind AbstractBoundUnixDomainSocketPair(int type) {
- std::string description =
- absl::StrCat(DescribeUnixDomainSocketType(type),
- " created with abstract namespace binding");
- if ((type & SOCK_DGRAM) == SOCK_DGRAM) {
- return SocketPairKind{
- description, AF_UNIX, type, 0,
- AbstractBidirectionalBindSocketPairCreator(AF_UNIX, type, 0)};
- }
- return SocketPairKind{description, AF_UNIX, type, 0,
- AbstractAcceptBindSocketPairCreator(AF_UNIX, type, 0)};
-}
-
-SocketPairKind SocketpairGoferUnixDomainSocketPair(int type) {
- std::string description = absl::StrCat(DescribeUnixDomainSocketType(type),
- " created with the socketpair gofer");
- return SocketPairKind{description, AF_UNIX, type, 0,
- SocketpairGoferSocketPairCreator(AF_UNIX, type, 0)};
-}
-
-SocketPairKind SocketpairGoferFileSocketPair(int type) {
- std::string description =
- absl::StrCat(((type & O_NONBLOCK) != 0) ? "non-blocking " : "",
- ((type & O_CLOEXEC) != 0) ? "close-on-exec " : "",
- "file socket created with the socketpair gofer");
- // The socketpair gofer always creates SOCK_STREAM sockets on open(2).
- return SocketPairKind{description, AF_UNIX, SOCK_STREAM, 0,
- SocketpairGoferFileSocketPairCreator(type)};
-}
-
-SocketPairKind FilesystemUnboundUnixDomainSocketPair(int type) {
- return SocketPairKind{absl::StrCat(DescribeUnixDomainSocketType(type),
- " unbound with a filesystem address"),
- AF_UNIX, type, 0,
- FilesystemUnboundSocketPairCreator(AF_UNIX, type, 0)};
-}
-
-SocketPairKind AbstractUnboundUnixDomainSocketPair(int type) {
- return SocketPairKind{
- absl::StrCat(DescribeUnixDomainSocketType(type),
- " unbound with an abstract namespace address"),
- AF_UNIX, type, 0, AbstractUnboundSocketPairCreator(AF_UNIX, type, 0)};
-}
-
-void SendSingleFD(int sock, int fd, char buf[], int buf_size) {
- ASSERT_NO_FATAL_FAILURE(SendFDs(sock, &fd, 1, buf, buf_size));
-}
-
-void SendFDs(int sock, int fds[], int fds_size, char buf[], int buf_size) {
- struct msghdr msg = {};
- std::vector<char> control(CMSG_SPACE(fds_size * sizeof(int)));
- msg.msg_control = &control[0];
- msg.msg_controllen = control.size();
-
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
- cmsg->cmsg_len = CMSG_LEN(fds_size * sizeof(int));
- cmsg->cmsg_level = SOL_SOCKET;
- cmsg->cmsg_type = SCM_RIGHTS;
- for (int i = 0; i < fds_size; i++) {
- memcpy(CMSG_DATA(cmsg) + i * sizeof(int), &fds[i], sizeof(int));
- }
-
- ASSERT_THAT(SendMsg(sock, &msg, buf, buf_size),
- IsPosixErrorOkAndHolds(buf_size));
-}
-
-void RecvSingleFD(int sock, int* fd, char buf[], int buf_size) {
- ASSERT_NO_FATAL_FAILURE(RecvFDs(sock, fd, 1, buf, buf_size, buf_size));
-}
-
-void RecvSingleFD(int sock, int* fd, char buf[], int buf_size,
- int expected_size) {
- ASSERT_NO_FATAL_FAILURE(RecvFDs(sock, fd, 1, buf, buf_size, expected_size));
-}
-
-void RecvFDs(int sock, int fds[], int fds_size, char buf[], int buf_size) {
- ASSERT_NO_FATAL_FAILURE(
- RecvFDs(sock, fds, fds_size, buf, buf_size, buf_size));
-}
-
-void RecvFDs(int sock, int fds[], int fds_size, char buf[], int buf_size,
- int expected_size, bool peek) {
- struct msghdr msg = {};
- std::vector<char> control(CMSG_SPACE(fds_size * sizeof(int)));
- msg.msg_control = &control[0];
- msg.msg_controllen = control.size();
-
- struct iovec iov;
- iov.iov_base = buf;
- iov.iov_len = buf_size;
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- int flags = 0;
- if (peek) {
- flags |= MSG_PEEK;
- }
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sock, &msg, flags),
- SyscallSucceedsWithValue(expected_size));
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
- ASSERT_NE(cmsg, nullptr);
- ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(fds_size * sizeof(int)));
- ASSERT_EQ(cmsg->cmsg_level, SOL_SOCKET);
- ASSERT_EQ(cmsg->cmsg_type, SCM_RIGHTS);
-
- for (int i = 0; i < fds_size; i++) {
- memcpy(&fds[i], CMSG_DATA(cmsg) + i * sizeof(int), sizeof(int));
- }
-}
-
-void RecvFDs(int sock, int fds[], int fds_size, char buf[], int buf_size,
- int expected_size) {
- ASSERT_NO_FATAL_FAILURE(
- RecvFDs(sock, fds, fds_size, buf, buf_size, expected_size, false));
-}
-
-void PeekSingleFD(int sock, int* fd, char buf[], int buf_size) {
- ASSERT_NO_FATAL_FAILURE(RecvFDs(sock, fd, 1, buf, buf_size, buf_size, true));
-}
-
-void RecvNoCmsg(int sock, char buf[], int buf_size, int expected_size) {
- struct msghdr msg = {};
- char control[CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred))];
- msg.msg_control = control;
- msg.msg_controllen = sizeof(control);
-
- struct iovec iov;
- iov.iov_base = buf;
- iov.iov_len = buf_size;
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sock, &msg, 0),
- SyscallSucceedsWithValue(expected_size));
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
- EXPECT_EQ(cmsg, nullptr);
-}
-
-void SendNullCmsg(int sock, char buf[], int buf_size) {
- struct msghdr msg = {};
- msg.msg_control = nullptr;
- msg.msg_controllen = 0;
-
- ASSERT_THAT(SendMsg(sock, &msg, buf, buf_size),
- IsPosixErrorOkAndHolds(buf_size));
-}
-
-void SendCreds(int sock, ucred creds, char buf[], int buf_size) {
- struct msghdr msg = {};
-
- char control[CMSG_SPACE(sizeof(struct ucred))];
- msg.msg_control = control;
- msg.msg_controllen = sizeof(control);
-
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
- cmsg->cmsg_level = SOL_SOCKET;
- cmsg->cmsg_type = SCM_CREDENTIALS;
- cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred));
- memcpy(CMSG_DATA(cmsg), &creds, sizeof(struct ucred));
-
- ASSERT_THAT(SendMsg(sock, &msg, buf, buf_size),
- IsPosixErrorOkAndHolds(buf_size));
-}
-
-void SendCredsAndFD(int sock, ucred creds, int fd, char buf[], int buf_size) {
- struct msghdr msg = {};
-
- char control[CMSG_SPACE(sizeof(struct ucred)) + CMSG_SPACE(sizeof(int))] = {};
- msg.msg_control = control;
- msg.msg_controllen = sizeof(control);
-
- struct cmsghdr* cmsg1 = CMSG_FIRSTHDR(&msg);
- cmsg1->cmsg_level = SOL_SOCKET;
- cmsg1->cmsg_type = SCM_CREDENTIALS;
- cmsg1->cmsg_len = CMSG_LEN(sizeof(struct ucred));
- memcpy(CMSG_DATA(cmsg1), &creds, sizeof(struct ucred));
-
- struct cmsghdr* cmsg2 = CMSG_NXTHDR(&msg, cmsg1);
- cmsg2->cmsg_level = SOL_SOCKET;
- cmsg2->cmsg_type = SCM_RIGHTS;
- cmsg2->cmsg_len = CMSG_LEN(sizeof(int));
- memcpy(CMSG_DATA(cmsg2), &fd, sizeof(int));
-
- ASSERT_THAT(SendMsg(sock, &msg, buf, buf_size),
- IsPosixErrorOkAndHolds(buf_size));
-}
-
-void RecvCreds(int sock, ucred* creds, char buf[], int buf_size) {
- ASSERT_NO_FATAL_FAILURE(RecvCreds(sock, creds, buf, buf_size, buf_size));
-}
-
-void RecvCreds(int sock, ucred* creds, char buf[], int buf_size,
- int expected_size) {
- struct msghdr msg = {};
- char control[CMSG_SPACE(sizeof(struct ucred))];
- msg.msg_control = control;
- msg.msg_controllen = sizeof(control);
-
- struct iovec iov;
- iov.iov_base = buf;
- iov.iov_len = buf_size;
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sock, &msg, 0),
- SyscallSucceedsWithValue(expected_size));
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
- ASSERT_NE(cmsg, nullptr);
- ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(struct ucred)));
- ASSERT_EQ(cmsg->cmsg_level, SOL_SOCKET);
- ASSERT_EQ(cmsg->cmsg_type, SCM_CREDENTIALS);
-
- memcpy(creds, CMSG_DATA(cmsg), sizeof(struct ucred));
-}
-
-void RecvCredsAndFD(int sock, ucred* creds, int* fd, char buf[], int buf_size) {
- struct msghdr msg = {};
- char control[CMSG_SPACE(sizeof(struct ucred)) + CMSG_SPACE(sizeof(int))];
- msg.msg_control = control;
- msg.msg_controllen = sizeof(control);
-
- struct iovec iov;
- iov.iov_base = buf;
- iov.iov_len = buf_size;
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sock, &msg, 0),
- SyscallSucceedsWithValue(buf_size));
-
- struct cmsghdr* cmsg1 = CMSG_FIRSTHDR(&msg);
- ASSERT_NE(cmsg1, nullptr);
- ASSERT_EQ(cmsg1->cmsg_len, CMSG_LEN(sizeof(struct ucred)));
- ASSERT_EQ(cmsg1->cmsg_level, SOL_SOCKET);
- ASSERT_EQ(cmsg1->cmsg_type, SCM_CREDENTIALS);
- memcpy(creds, CMSG_DATA(cmsg1), sizeof(struct ucred));
-
- struct cmsghdr* cmsg2 = CMSG_NXTHDR(&msg, cmsg1);
- ASSERT_NE(cmsg2, nullptr);
- ASSERT_EQ(cmsg2->cmsg_len, CMSG_LEN(sizeof(int)));
- ASSERT_EQ(cmsg2->cmsg_level, SOL_SOCKET);
- ASSERT_EQ(cmsg2->cmsg_type, SCM_RIGHTS);
- memcpy(fd, CMSG_DATA(cmsg2), sizeof(int));
-}
-
-void RecvSingleFDUnaligned(int sock, int* fd, char buf[], int buf_size) {
- struct msghdr msg = {};
- char control[CMSG_SPACE(sizeof(int)) - sizeof(int)];
- msg.msg_control = control;
- msg.msg_controllen = sizeof(control);
-
- struct iovec iov;
- iov.iov_base = buf;
- iov.iov_len = buf_size;
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- ASSERT_THAT(RetryEINTR(recvmsg)(sock, &msg, 0),
- SyscallSucceedsWithValue(buf_size));
-
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
- ASSERT_NE(cmsg, nullptr);
- ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(int)));
- ASSERT_EQ(cmsg->cmsg_level, SOL_SOCKET);
- ASSERT_EQ(cmsg->cmsg_type, SCM_RIGHTS);
-
- memcpy(fd, CMSG_DATA(cmsg), sizeof(int));
-}
-
-void SetSoPassCred(int sock) {
- int one = 1;
- EXPECT_THAT(setsockopt(sock, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)),
- SyscallSucceeds());
-}
-
-void UnsetSoPassCred(int sock) {
- int zero = 0;
- EXPECT_THAT(setsockopt(sock, SOL_SOCKET, SO_PASSCRED, &zero, sizeof(zero)),
- SyscallSucceeds());
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/unix_domain_socket_test_util.h b/test/syscalls/linux/unix_domain_socket_test_util.h
deleted file mode 100644
index 5eca0b7f0..000000000
--- a/test/syscalls/linux/unix_domain_socket_test_util.h
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_SYSCALLS_UNIX_DOMAIN_SOCKET_TEST_UTIL_H_
-#define GVISOR_TEST_SYSCALLS_UNIX_DOMAIN_SOCKET_TEST_UTIL_H_
-
-#include <string>
-#include "test/syscalls/linux/socket_test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// DescribeUnixDomainSocketType returns a human-readable string explaining the
-// given Unix domain socket type.
-std::string DescribeUnixDomainSocketType(int type);
-
-// UnixDomainSocketPair returns a SocketPairKind that represents SocketPairs
-// created by invoking the socketpair() syscall with AF_UNIX and the given type.
-SocketPairKind UnixDomainSocketPair(int type);
-
-// FilesystemBoundUnixDomainSocketPair returns a SocketPairKind that represents
-// SocketPairs created with bind() and accept() syscalls with a temp file path,
-// AF_UNIX and the given type.
-SocketPairKind FilesystemBoundUnixDomainSocketPair(int type);
-
-// AbstractBoundUnixDomainSocketPair returns a SocketPairKind that represents
-// SocketPairs created with bind() and accept() syscalls with a temp abstract
-// path, AF_UNIX and the given type.
-SocketPairKind AbstractBoundUnixDomainSocketPair(int type);
-
-// SocketpairGoferUnixDomainSocketPair returns a SocketPairKind that was created
-// with two sockets connected to the socketpair gofer.
-SocketPairKind SocketpairGoferUnixDomainSocketPair(int type);
-
-// SocketpairGoferFileSocketPair returns a SocketPairKind that was created with
-// two open() calls on paths backed by the socketpair gofer.
-SocketPairKind SocketpairGoferFileSocketPair(int type);
-
-// FilesystemUnboundUnixDomainSocketPair returns a SocketPairKind that
-// represents two unbound sockets and a filesystem path for binding.
-SocketPairKind FilesystemUnboundUnixDomainSocketPair(int type);
-
-// AbstractUnboundUnixDomainSocketPair returns a SocketPairKind that represents
-// two unbound sockets and an abstract namespace path for binding.
-SocketPairKind AbstractUnboundUnixDomainSocketPair(int type);
-
-// SendSingleFD sends both a single FD and some data over a unix domain socket
-// specified by an FD. Note that calls to this function must be wrapped in
-// ASSERT_NO_FATAL_FAILURE for internal assertions to halt the test.
-void SendSingleFD(int sock, int fd, char buf[], int buf_size);
-
-// SendFDs sends an arbitrary number of FDs and some data over a unix domain
-// socket specified by an FD. Note that calls to this function must be wrapped
-// in ASSERT_NO_FATAL_FAILURE for internal assertions to halt the test.
-void SendFDs(int sock, int fds[], int fds_size, char buf[], int buf_size);
-
-// RecvSingleFD receives both a single FD and some data over a unix domain
-// socket specified by an FD. Note that calls to this function must be wrapped
-// in ASSERT_NO_FATAL_FAILURE for internal assertions to halt the test.
-void RecvSingleFD(int sock, int* fd, char buf[], int buf_size);
-
-// RecvSingleFD receives both a single FD and some data over a unix domain
-// socket specified by an FD. This version allows the expected amount of data
-// received to be different than the buffer size. Note that calls to this
-// function must be wrapped in ASSERT_NO_FATAL_FAILURE for internal assertions
-// to halt the test.
-void RecvSingleFD(int sock, int* fd, char buf[], int buf_size,
- int expected_size);
-
-// PeekSingleFD peeks at both a single FD and some data over a unix domain
-// socket specified by an FD. Note that calls to this function must be wrapped
-// in ASSERT_NO_FATAL_FAILURE for internal assertions to halt the test.
-void PeekSingleFD(int sock, int* fd, char buf[], int buf_size);
-
-// RecvFDs receives both an arbitrary number of FDs and some data over a unix
-// domain socket specified by an FD. Note that calls to this function must be
-// wrapped in ASSERT_NO_FATAL_FAILURE for internal assertions to halt the test.
-void RecvFDs(int sock, int fds[], int fds_size, char buf[], int buf_size);
-
-// RecvFDs receives both an arbitrary number of FDs and some data over a unix
-// domain socket specified by an FD. This version allows the expected amount of
-// data received to be different than the buffer size. Note that calls to this
-// function must be wrapped in ASSERT_NO_FATAL_FAILURE for internal assertions
-// to halt the test.
-void RecvFDs(int sock, int fds[], int fds_size, char buf[], int buf_size,
- int expected_size);
-
-// RecvNoCmsg receives some data over a unix domain socket specified by an FD
-// and asserts that no control messages are available for receiving. Note that
-// calls to this function must be wrapped in ASSERT_NO_FATAL_FAILURE for
-// internal assertions to halt the test.
-void RecvNoCmsg(int sock, char buf[], int buf_size, int expected_size);
-
-inline void RecvNoCmsg(int sock, char buf[], int buf_size) {
- RecvNoCmsg(sock, buf, buf_size, buf_size);
-}
-
-// SendCreds sends the credentials of the current process and some data over a
-// unix domain socket specified by an FD. Note that calls to this function must
-// be wrapped in ASSERT_NO_FATAL_FAILURE for internal assertions to halt the
-// test.
-void SendCreds(int sock, ucred creds, char buf[], int buf_size);
-
-// SendCredsAndFD sends the credentials of the current process, a single FD, and
-// some data over a unix domain socket specified by an FD. Note that calls to
-// this function must be wrapped in ASSERT_NO_FATAL_FAILURE for internal
-// assertions to halt the test.
-void SendCredsAndFD(int sock, ucred creds, int fd, char buf[], int buf_size);
-
-// RecvCreds receives some credentials and some data over a unix domain socket
-// specified by an FD. Note that calls to this function must be wrapped in
-// ASSERT_NO_FATAL_FAILURE for internal assertions to halt the test.
-void RecvCreds(int sock, ucred* creds, char buf[], int buf_size);
-
-// RecvCreds receives some credentials and some data over a unix domain socket
-// specified by an FD. This version allows the expected amount of data received
-// to be different than the buffer size. Note that calls to this function must
-// be wrapped in ASSERT_NO_FATAL_FAILURE for internal assertions to halt the
-// test.
-void RecvCreds(int sock, ucred* creds, char buf[], int buf_size,
- int expected_size);
-
-// RecvCredsAndFD receives some credentials, a single FD, and some data over a
-// unix domain socket specified by an FD. Note that calls to this function must
-// be wrapped in ASSERT_NO_FATAL_FAILURE for internal assertions to halt the
-// test.
-void RecvCredsAndFD(int sock, ucred* creds, int* fd, char buf[], int buf_size);
-
-// SendNullCmsg sends a null control message and some data over a unix domain
-// socket specified by an FD. Note that calls to this function must be wrapped
-// in ASSERT_NO_FATAL_FAILURE for internal assertions to halt the test.
-void SendNullCmsg(int sock, char buf[], int buf_size);
-
-// RecvSingleFDUnaligned sends both a single FD and some data over a unix domain
-// socket specified by an FD. This function does not obey the spec, but Linux
-// allows it and the apphosting code depends on this quirk. Note that calls to
-// this function must be wrapped in ASSERT_NO_FATAL_FAILURE for internal
-// assertions to halt the test.
-void RecvSingleFDUnaligned(int sock, int* fd, char buf[], int buf_size);
-
-// SetSoPassCred sets the SO_PASSCRED option on the specified socket.
-void SetSoPassCred(int sock);
-
-// UnsetSoPassCred clears the SO_PASSCRED option on the specified socket.
-void UnsetSoPassCred(int sock);
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_SYSCALLS_UNIX_DOMAIN_SOCKET_TEST_UTIL_H_
diff --git a/test/syscalls/linux/unlink.cc b/test/syscalls/linux/unlink.cc
deleted file mode 100644
index 2040375c9..000000000
--- a/test/syscalls/linux/unlink.cc
+++ /dev/null
@@ -1,214 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <fcntl.h>
-#include <unistd.h>
-
-#include "gtest/gtest.h"
-#include "absl/strings/str_cat.h"
-#include "test/util/capability_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(UnlinkTest, IsDir) {
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- EXPECT_THAT(unlink(dir.path().c_str()), SyscallFailsWithErrno(EISDIR));
-}
-
-TEST(UnlinkTest, DirNotEmpty) {
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- int fd;
- std::string path = JoinPath(dir.path(), "ExistingFile");
- EXPECT_THAT(fd = open(path.c_str(), O_RDWR | O_CREAT, 0666),
- SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
- EXPECT_THAT(rmdir(dir.path().c_str()), SyscallFailsWithErrno(ENOTEMPTY));
-}
-
-TEST(UnlinkTest, Rmdir) {
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- EXPECT_THAT(rmdir(dir.path().c_str()), SyscallSucceeds());
-}
-
-TEST(UnlinkTest, AtDir) {
- int dirfd;
- auto tmpdir = GetAbsoluteTestTmpdir();
- EXPECT_THAT(dirfd = open(tmpdir.c_str(), O_DIRECTORY, 0), SyscallSucceeds());
-
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(tmpdir));
- auto dir_relpath =
- ASSERT_NO_ERRNO_AND_VALUE(GetRelativePath(tmpdir, dir.path()));
- EXPECT_THAT(unlinkat(dirfd, dir_relpath.c_str(), AT_REMOVEDIR),
- SyscallSucceeds());
- ASSERT_THAT(close(dirfd), SyscallSucceeds());
-}
-
-TEST(UnlinkTest, AtDirDegradedPermissions_NoRandomSave) {
- // Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
-
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- int dirfd;
- ASSERT_THAT(dirfd = open(dir.path().c_str(), O_DIRECTORY, 0),
- SyscallSucceeds());
-
- std::string sub_dir = JoinPath(dir.path(), "NewDir");
- EXPECT_THAT(mkdir(sub_dir.c_str(), 0755), SyscallSucceeds());
- EXPECT_THAT(fchmod(dirfd, 0444), SyscallSucceeds());
- EXPECT_THAT(unlinkat(dirfd, "NewDir", AT_REMOVEDIR),
- SyscallFailsWithErrno(EACCES));
- ASSERT_THAT(close(dirfd), SyscallSucceeds());
-}
-
-// Files cannot be unlinked if the parent is not writable and executable.
-TEST(UnlinkTest, ParentDegradedPermissions) {
- // Drop capabilities that allow us to override file and directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
-
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
-
- ASSERT_THAT(chmod(dir.path().c_str(), 0000), SyscallSucceeds());
-
- struct stat st;
- ASSERT_THAT(stat(file.path().c_str(), &st), SyscallFailsWithErrno(EACCES));
- ASSERT_THAT(unlinkat(AT_FDCWD, file.path().c_str(), 0),
- SyscallFailsWithErrno(EACCES));
-
- // Non-existent files also return EACCES.
- const std::string nonexist = JoinPath(dir.path(), "doesnotexist");
- ASSERT_THAT(stat(nonexist.c_str(), &st), SyscallFailsWithErrno(EACCES));
- ASSERT_THAT(unlinkat(AT_FDCWD, nonexist.c_str(), 0),
- SyscallFailsWithErrno(EACCES));
-}
-
-TEST(UnlinkTest, AtBad) {
- int dirfd;
- EXPECT_THAT(dirfd = open(GetAbsoluteTestTmpdir().c_str(), O_DIRECTORY, 0),
- SyscallSucceeds());
-
- // Try removing a directory as a file.
- std::string path = JoinPath(GetAbsoluteTestTmpdir(), "NewDir");
- EXPECT_THAT(mkdir(path.c_str(), 0755), SyscallSucceeds());
- EXPECT_THAT(unlinkat(dirfd, "NewDir", 0), SyscallFailsWithErrno(EISDIR));
- EXPECT_THAT(unlinkat(dirfd, "NewDir", AT_REMOVEDIR), SyscallSucceeds());
-
- // Try removing a file as a directory.
- int fd;
- EXPECT_THAT(fd = openat(dirfd, "UnlinkAtFile", O_RDWR | O_CREAT, 0666),
- SyscallSucceeds());
- EXPECT_THAT(unlinkat(dirfd, "UnlinkAtFile", AT_REMOVEDIR),
- SyscallFailsWithErrno(ENOTDIR));
- EXPECT_THAT(unlinkat(dirfd, "UnlinkAtFile/", 0),
- SyscallFailsWithErrno(ENOTDIR));
- ASSERT_THAT(close(fd), SyscallSucceeds());
- EXPECT_THAT(unlinkat(dirfd, "UnlinkAtFile", 0), SyscallSucceeds());
-
- // Cleanup.
- ASSERT_THAT(close(dirfd), SyscallSucceeds());
-}
-
-TEST(UnlinkTest, AbsTmpFile) {
- int fd;
- std::string path = JoinPath(GetAbsoluteTestTmpdir(), "ExistingFile");
- EXPECT_THAT(fd = open(path.c_str(), O_RDWR | O_CREAT, 0666),
- SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
- EXPECT_THAT(unlink(path.c_str()), SyscallSucceeds());
-}
-
-TEST(UnlinkTest, TooLongName) {
- EXPECT_THAT(unlink(std::vector<char>(16384, '0').data()),
- SyscallFailsWithErrno(ENAMETOOLONG));
-}
-
-TEST(UnlinkTest, BadNamePtr) {
- EXPECT_THAT(unlink(reinterpret_cast<char*>(1)),
- SyscallFailsWithErrno(EFAULT));
-}
-
-TEST(UnlinkTest, AtFile) {
- int dirfd;
- EXPECT_THAT(dirfd = open(GetAbsoluteTestTmpdir().c_str(), O_DIRECTORY, 0666),
- SyscallSucceeds());
- int fd;
- EXPECT_THAT(fd = openat(dirfd, "UnlinkAtFile", O_RDWR | O_CREAT, 0666),
- SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
- EXPECT_THAT(unlinkat(dirfd, "UnlinkAtFile", 0), SyscallSucceeds());
-}
-
-TEST(UnlinkTest, OpenFile_NoRandomSave) {
- // We can't save unlinked file unless they are on tmpfs.
- const DisableSave ds;
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- int fd;
- EXPECT_THAT(fd = open(file.path().c_str(), O_RDWR, 0666), SyscallSucceeds());
- EXPECT_THAT(unlink(file.path().c_str()), SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
-}
-
-TEST(UnlinkTest, CannotRemoveDots) {
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const std::string self = JoinPath(file.path(), ".");
- ASSERT_THAT(unlink(self.c_str()), SyscallFailsWithErrno(ENOTDIR));
- const std::string parent = JoinPath(file.path(), "..");
- ASSERT_THAT(unlink(parent.c_str()), SyscallFailsWithErrno(ENOTDIR));
-}
-
-TEST(UnlinkTest, CannotRemoveRoot) {
- ASSERT_THAT(unlinkat(-1, "/", AT_REMOVEDIR), SyscallFailsWithErrno(EBUSY));
-}
-
-TEST(UnlinkTest, CannotRemoveRootWithAtDir) {
- const FileDescriptor dirfd = ASSERT_NO_ERRNO_AND_VALUE(
- Open(GetAbsoluteTestTmpdir(), O_DIRECTORY, 0666));
- ASSERT_THAT(unlinkat(dirfd.get(), "/", AT_REMOVEDIR),
- SyscallFailsWithErrno(EBUSY));
-}
-
-TEST(RmdirTest, CannotRemoveDots) {
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const std::string self = JoinPath(dir.path(), ".");
- ASSERT_THAT(rmdir(self.c_str()), SyscallFailsWithErrno(EINVAL));
- const std::string parent = JoinPath(dir.path(), "..");
- ASSERT_THAT(rmdir(parent.c_str()), SyscallFailsWithErrno(ENOTEMPTY));
-}
-
-TEST(RmdirTest, CanRemoveWithTrailingSlashes) {
- auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const std::string slash = absl::StrCat(dir1.path(), "/");
- ASSERT_THAT(rmdir(slash.c_str()), SyscallSucceeds());
- auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const std::string slashslash = absl::StrCat(dir2.path(), "//");
- ASSERT_THAT(rmdir(slashslash.c_str()), SyscallSucceeds());
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/unshare.cc b/test/syscalls/linux/unshare.cc
deleted file mode 100644
index e32619efe..000000000
--- a/test/syscalls/linux/unshare.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <sched.h>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/synchronization/mutex.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(UnshareTest, AllowsZeroFlags) {
- ASSERT_THAT(unshare(0), SyscallSucceeds());
-}
-
-TEST(UnshareTest, ThreadFlagFailsIfMultithreaded) {
- absl::Mutex mu;
- bool finished = false;
- ScopedThread t([&] {
- mu.Lock();
- mu.Await(absl::Condition(&finished));
- mu.Unlock();
- });
- ASSERT_THAT(unshare(CLONE_THREAD), SyscallFailsWithErrno(EINVAL));
- mu.Lock();
- finished = true;
- mu.Unlock();
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/utimes.cc b/test/syscalls/linux/utimes.cc
deleted file mode 100644
index 80716859a..000000000
--- a/test/syscalls/linux/utimes.cc
+++ /dev/null
@@ -1,330 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/syscall.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <time.h>
-#include <unistd.h>
-#include <utime.h>
-#include <string>
-
-#include "absl/time/time.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// TODO(b/36516566): utimes(nullptr) does not pick the "now" time in the
-// application's time domain, so when asserting that times are within a window,
-// we expand the window to allow for differences between the time domains.
-constexpr absl::Duration kClockSlack = absl::Milliseconds(100);
-
-// TimeBoxed runs fn, setting before and after to (coarse realtime) times
-// guaranteed* to come before and after fn started and completed, respectively.
-//
-// fn may be called more than once if the clock is adjusted.
-//
-// * See the comment on kClockSlack. gVisor breaks this guarantee.
-void TimeBoxed(absl::Time* before, absl::Time* after,
- std::function<void()> const& fn) {
- do {
- // N.B. utimes and friends use CLOCK_REALTIME_COARSE for setting time (i.e.,
- // current_kernel_time()). See fs/attr.c:notify_change.
- //
- // notify_change truncates the time to a multiple of s_time_gran, but most
- // filesystems set it to 1, so we don't do any truncation.
- struct timespec ts;
- EXPECT_THAT(clock_gettime(CLOCK_REALTIME_COARSE, &ts), SyscallSucceeds());
- *before = absl::TimeFromTimespec(ts);
-
- fn();
-
- EXPECT_THAT(clock_gettime(CLOCK_REALTIME_COARSE, &ts), SyscallSucceeds());
- *after = absl::TimeFromTimespec(ts);
-
- if (*after < *before) {
- // Clock jumped backwards; retry.
- //
- // Technically this misses jumps small enough to keep after > before,
- // which could lead to test failures, but that is very unlikely to happen.
- continue;
- }
-
- if (IsRunningOnGvisor()) {
- // See comment on kClockSlack.
- *before -= kClockSlack;
- *after += kClockSlack;
- }
- } while (*after < *before);
-}
-
-void TestUtimesOnPath(std::string const& path) {
- struct stat statbuf;
-
- struct timeval times[2] = {{1, 0}, {2, 0}};
- EXPECT_THAT(utimes(path.c_str(), times), SyscallSucceeds());
- EXPECT_THAT(stat(path.c_str(), &statbuf), SyscallSucceeds());
- EXPECT_EQ(1, statbuf.st_atime);
- EXPECT_EQ(2, statbuf.st_mtime);
-
- absl::Time before;
- absl::Time after;
- TimeBoxed(&before, &after, [&] {
- EXPECT_THAT(utimes(path.c_str(), nullptr), SyscallSucceeds());
- });
-
- EXPECT_THAT(stat(path.c_str(), &statbuf), SyscallSucceeds());
-
- absl::Time atime = absl::TimeFromTimespec(statbuf.st_atim);
- EXPECT_GE(atime, before);
- EXPECT_LE(atime, after);
-
- absl::Time mtime = absl::TimeFromTimespec(statbuf.st_mtim);
- EXPECT_GE(mtime, before);
- EXPECT_LE(mtime, after);
-}
-
-TEST(UtimesTest, OnFile) {
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- TestUtimesOnPath(f.path());
-}
-
-TEST(UtimesTest, OnDir) {
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- TestUtimesOnPath(dir.path());
-}
-
-TEST(UtimesTest, MissingPath) {
- auto path = NewTempAbsPath();
- struct timeval times[2] = {{1, 0}, {2, 0}};
- EXPECT_THAT(utimes(path.c_str(), times), SyscallFailsWithErrno(ENOENT));
-}
-
-void TestFutimesat(int dirFd, std::string const& path) {
- struct stat statbuf;
-
- struct timeval times[2] = {{1, 0}, {2, 0}};
- EXPECT_THAT(futimesat(dirFd, path.c_str(), times), SyscallSucceeds());
- EXPECT_THAT(fstatat(dirFd, path.c_str(), &statbuf, 0), SyscallSucceeds());
- EXPECT_EQ(1, statbuf.st_atime);
- EXPECT_EQ(2, statbuf.st_mtime);
-
- absl::Time before;
- absl::Time after;
- TimeBoxed(&before, &after, [&] {
- EXPECT_THAT(futimesat(dirFd, path.c_str(), nullptr), SyscallSucceeds());
- });
-
- EXPECT_THAT(fstatat(dirFd, path.c_str(), &statbuf, 0), SyscallSucceeds());
-
- absl::Time atime = absl::TimeFromTimespec(statbuf.st_atim);
- EXPECT_GE(atime, before);
- EXPECT_LE(atime, after);
-
- absl::Time mtime = absl::TimeFromTimespec(statbuf.st_mtim);
- EXPECT_GE(mtime, before);
- EXPECT_LE(mtime, after);
-}
-
-TEST(FutimesatTest, OnAbsPath) {
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- TestFutimesat(0, f.path());
-}
-
-TEST(FutimesatTest, OnRelPath) {
- auto d = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(d.path()));
- auto basename = std::string(Basename(f.path()));
- const FileDescriptor dirFd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(d.path(), O_RDONLY | O_DIRECTORY));
- TestFutimesat(dirFd.get(), basename);
-}
-
-TEST(FutimesatTest, InvalidNsec) {
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- struct timeval times[4][2] = {{
- {0, 1}, // Valid
- {1, static_cast<int64_t>(1e7)} // Invalid
- },
- {
- {1, static_cast<int64_t>(1e7)}, // Invalid
- {0, 1} // Valid
- },
- {
- {0, 1}, // Valid
- {1, -1} // Invalid
- },
- {
- {1, -1}, // Invalid
- {0, 1} // Valid
- }};
-
- for (unsigned int i = 0; i < sizeof(times) / sizeof(times[0]); i++) {
- std::cout << "test:" << i << "\n";
- EXPECT_THAT(futimesat(0, f.path().c_str(), times[i]),
- SyscallFailsWithErrno(EINVAL));
- }
-}
-
-void TestUtimensat(int dirFd, std::string const& path) {
- struct stat statbuf;
- const struct timespec times[2] = {{1, 0}, {2, 0}};
- EXPECT_THAT(utimensat(dirFd, path.c_str(), times, 0), SyscallSucceeds());
- EXPECT_THAT(fstatat(dirFd, path.c_str(), &statbuf, 0), SyscallSucceeds());
- EXPECT_EQ(1, statbuf.st_atime);
- EXPECT_EQ(2, statbuf.st_mtime);
-
- // Test setting with UTIME_NOW and UTIME_OMIT.
- struct stat statbuf2;
- const struct timespec times2[2] = {
- {0, UTIME_NOW}, // Should set atime to now.
- {0, UTIME_OMIT} // Should not change mtime.
- };
-
- absl::Time before;
- absl::Time after;
- TimeBoxed(&before, &after, [&] {
- EXPECT_THAT(utimensat(dirFd, path.c_str(), times2, 0), SyscallSucceeds());
- });
-
- EXPECT_THAT(fstatat(dirFd, path.c_str(), &statbuf2, 0), SyscallSucceeds());
-
- absl::Time atime2 = absl::TimeFromTimespec(statbuf2.st_atim);
- EXPECT_GE(atime2, before);
- EXPECT_LE(atime2, after);
-
- absl::Time mtime = absl::TimeFromTimespec(statbuf.st_mtim);
- absl::Time mtime2 = absl::TimeFromTimespec(statbuf2.st_mtim);
- // mtime should not be changed.
- EXPECT_EQ(mtime, mtime2);
-
- // Test setting with times = NULL. Should set both atime and mtime to the
- // current system time.
- struct stat statbuf3;
- TimeBoxed(&before, &after, [&] {
- EXPECT_THAT(utimensat(dirFd, path.c_str(), nullptr, 0), SyscallSucceeds());
- });
-
- EXPECT_THAT(fstatat(dirFd, path.c_str(), &statbuf3, 0), SyscallSucceeds());
-
- absl::Time atime3 = absl::TimeFromTimespec(statbuf3.st_atim);
- EXPECT_GE(atime3, before);
- EXPECT_LE(atime3, after);
-
- absl::Time mtime3 = absl::TimeFromTimespec(statbuf3.st_mtim);
- EXPECT_GE(mtime3, before);
- EXPECT_LE(mtime3, after);
-
- if (!IsRunningOnGvisor()) {
- // FIXME(b/36516566): Gofers set atime and mtime to different "now" times.
- EXPECT_EQ(atime3, mtime3);
- }
-}
-
-TEST(UtimensatTest, OnAbsPath) {
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- TestUtimensat(0, f.path());
-}
-
-TEST(UtimensatTest, OnRelPath) {
- auto d = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(d.path()));
- auto basename = std::string(Basename(f.path()));
- const FileDescriptor dirFd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(d.path(), O_RDONLY | O_DIRECTORY));
- TestUtimensat(dirFd.get(), basename);
-}
-
-TEST(UtimensatTest, OmitNoop) {
- // Setting both timespecs to UTIME_OMIT on a nonexistant path should succeed.
- auto path = NewTempAbsPath();
- const struct timespec times[2] = {{0, UTIME_OMIT}, {0, UTIME_OMIT}};
- EXPECT_THAT(utimensat(0, path.c_str(), times, 0), SyscallSucceeds());
-}
-
-// Verify that we can actually set atime and mtime to 0.
-TEST(UtimeTest, ZeroAtimeandMtime) {
- const auto tmp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const auto tmp_file =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(tmp_dir.path()));
-
- // Stat the file before and after updating atime and mtime.
- struct stat stat_before = {};
- EXPECT_THAT(stat(tmp_file.path().c_str(), &stat_before), SyscallSucceeds());
-
- ASSERT_NE(stat_before.st_atime, 0);
- ASSERT_NE(stat_before.st_mtime, 0);
-
- const struct utimbuf times = {}; // Zero for both atime and mtime.
- EXPECT_THAT(utime(tmp_file.path().c_str(), &times), SyscallSucceeds());
-
- struct stat stat_after = {};
- EXPECT_THAT(stat(tmp_file.path().c_str(), &stat_after), SyscallSucceeds());
-
- // We should see the atime and mtime changed when we set them to 0.
- ASSERT_EQ(stat_after.st_atime, 0);
- ASSERT_EQ(stat_after.st_mtime, 0);
-}
-
-TEST(UtimensatTest, InvalidNsec) {
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- struct timespec times[2][2] = {{
- {0, UTIME_OMIT}, // Valid
- {2, static_cast<int64_t>(1e10)} // Invalid
- },
- {
- {2, static_cast<int64_t>(1e10)}, // Invalid
- {0, UTIME_OMIT} // Valid
- }};
-
- for (unsigned int i = 0; i < sizeof(times) / sizeof(times[0]); i++) {
- std::cout << "test:" << i << "\n";
- EXPECT_THAT(utimensat(0, f.path().c_str(), times[i], 0),
- SyscallFailsWithErrno(EINVAL));
- }
-}
-
-TEST(Utimensat, NullPath) {
- // From man utimensat(2):
- // "the Linux utimensat() system call implements a nonstandard feature: if
- // pathname is NULL, then the call modifies the timestamps of the file
- // referred to by the file descriptor dirfd (which may refer to any type of
- // file).
- // Note, however, that the glibc wrapper for utimensat() disallows
- // passing NULL as the value for file: the wrapper function returns the error
- // EINVAL in this case."
- auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDWR));
- struct stat statbuf;
- const struct timespec times[2] = {{1, 0}, {2, 0}};
- // Call syscall directly.
- EXPECT_THAT(syscall(SYS_utimensat, fd.get(), NULL, times, 0),
- SyscallSucceeds());
- EXPECT_THAT(fstatat(0, f.path().c_str(), &statbuf, 0), SyscallSucceeds());
- EXPECT_EQ(1, statbuf.st_atime);
- EXPECT_EQ(2, statbuf.st_mtime);
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/vdso.cc b/test/syscalls/linux/vdso.cc
deleted file mode 100644
index 19c80add8..000000000
--- a/test/syscalls/linux/vdso.cc
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <string.h>
-#include <sys/mman.h>
-
-#include <algorithm>
-
-#include "gtest/gtest.h"
-#include "test/util/fs_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/proc_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// Ensure that the vvar page cannot be made writable.
-TEST(VvarTest, WriteVvar) {
- auto contents = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps"));
- auto maps = ASSERT_NO_ERRNO_AND_VALUE(ParseProcMaps(contents));
- auto it = std::find_if(maps.begin(), maps.end(), [](const ProcMapsEntry& e) {
- return e.filename == "[vvar]";
- });
-
- SKIP_IF(it == maps.end());
- EXPECT_THAT(mprotect(reinterpret_cast<void*>(it->start), kPageSize,
- PROT_READ | PROT_WRITE),
- SyscallFailsWithErrno(EACCES));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/vdso_clock_gettime.cc b/test/syscalls/linux/vdso_clock_gettime.cc
deleted file mode 100644
index 40c0014b9..000000000
--- a/test/syscalls/linux/vdso_clock_gettime.cc
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <stdint.h>
-#include <sys/time.h>
-#include <syscall.h>
-#include <time.h>
-#include <unistd.h>
-#include <map>
-#include <string>
-#include <utility>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/strings/numbers.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-std::string PrintClockId(::testing::TestParamInfo<clockid_t> info) {
- switch (info.param) {
- case CLOCK_MONOTONIC:
- return "CLOCK_MONOTONIC";
- case CLOCK_REALTIME:
- return "CLOCK_REALTIME";
- case CLOCK_BOOTTIME:
- return "CLOCK_BOOTTIME";
- default:
- return absl::StrCat(info.param);
- }
-}
-
-class CorrectVDSOClockTest : public ::testing::TestWithParam<clockid_t> {};
-
-TEST_P(CorrectVDSOClockTest, IsCorrect) {
- struct timespec tvdso, tsys;
- absl::Time vdso_time, sys_time;
- uint64_t total_calls = 0;
-
- // It is expected that 82.5% of clock_gettime calls will be less than 100us
- // skewed from the system time.
- // Unfortunately this is not only influenced by the VDSO clock skew, but also
- // by arbitrary scheduling delays and the like. The test is therefore
- // regularly disabled.
- std::map<absl::Duration, std::tuple<double, uint64_t, uint64_t>> confidence =
- {
- {absl::Microseconds(100), std::make_tuple(0.825, 0, 0)},
- {absl::Microseconds(250), std::make_tuple(0.94, 0, 0)},
- {absl::Milliseconds(1), std::make_tuple(0.999, 0, 0)},
- };
-
- absl::Time start = absl::Now();
- while (absl::Now() < start + absl::Seconds(30)) {
- EXPECT_THAT(clock_gettime(GetParam(), &tvdso), SyscallSucceeds());
- EXPECT_THAT(syscall(__NR_clock_gettime, GetParam(), &tsys),
- SyscallSucceeds());
-
- vdso_time = absl::TimeFromTimespec(tvdso);
-
- for (auto const& conf : confidence) {
- std::get<1>(confidence[conf.first]) +=
- (sys_time - vdso_time) < conf.first;
- }
-
- sys_time = absl::TimeFromTimespec(tsys);
-
- for (auto const& conf : confidence) {
- std::get<2>(confidence[conf.first]) +=
- (vdso_time - sys_time) < conf.first;
- }
-
- ++total_calls;
- }
-
- for (auto const& conf : confidence) {
- EXPECT_GE(std::get<1>(conf.second) / static_cast<double>(total_calls),
- std::get<0>(conf.second));
- EXPECT_GE(std::get<2>(conf.second) / static_cast<double>(total_calls),
- std::get<0>(conf.second));
- }
-}
-
-INSTANTIATE_TEST_SUITE_P(ClockGettime, CorrectVDSOClockTest,
- ::testing::Values(CLOCK_MONOTONIC, CLOCK_REALTIME,
- CLOCK_BOOTTIME),
- PrintClockId);
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/vfork.cc b/test/syscalls/linux/vfork.cc
deleted file mode 100644
index f67b06f37..000000000
--- a/test/syscalls/linux/vfork.cc
+++ /dev/null
@@ -1,194 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include <string>
-#include <utility>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/time/time.h"
-#include "test/util/logging.h"
-#include "test/util/multiprocess_util.h"
-#include "test/util/test_util.h"
-#include "test/util/time_util.h"
-
-DEFINE_bool(vfork_test_child, false,
- "If true, run the VforkTest child workload.");
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// We don't test with raw CLONE_VFORK to avoid interacting with glibc's use of
-// TLS.
-//
-// Even with vfork(2), we must be careful to do little more in the child than
-// call execve(2). We use the simplest sleep function possible, though this is
-// still precarious, as we're officially only allowed to call execve(2) and
-// _exit(2).
-constexpr absl::Duration kChildDelay = absl::Seconds(10);
-
-// Exit code for successful child subprocesses. We don't want to use 0 since
-// it's too common, and an execve(2) failure causes the child to exit with the
-// errno, so kChildExitCode is chosen to be an unlikely errno:
-constexpr int kChildExitCode = 118; // ENOTNAM: Not a XENIX named type file
-
-int64_t MonotonicNow() {
- struct timespec now;
- TEST_PCHECK(clock_gettime(CLOCK_MONOTONIC, &now) == 0);
- return now.tv_sec * 1000000000ll + now.tv_nsec;
-}
-
-TEST(VforkTest, ParentStopsUntilChildExits) {
- const auto test = [] {
- // N.B. Run the test in a single-threaded subprocess because
- // vfork is not safe in a multi-threaded process.
-
- const int64_t start = MonotonicNow();
-
- pid_t pid = vfork();
- if (pid == 0) {
- SleepSafe(kChildDelay);
- _exit(kChildExitCode);
- }
- TEST_PCHECK_MSG(pid > 0, "vfork failed");
- MaybeSave();
-
- const int64_t end = MonotonicNow();
-
- absl::Duration dur = absl::Nanoseconds(end - start);
-
- TEST_CHECK(dur >= kChildDelay);
-
- int status = 0;
- TEST_PCHECK(RetryEINTR(waitpid)(pid, &status, 0));
- TEST_CHECK(WIFEXITED(status));
- TEST_CHECK(WEXITSTATUS(status) == kChildExitCode);
- };
-
- EXPECT_THAT(InForkedProcess(test), IsPosixErrorOkAndHolds(0));
-}
-
-TEST(VforkTest, ParentStopsUntilChildExecves_NoRandomSave) {
- ExecveArray const owned_child_argv = {"/proc/self/exe", "--vfork_test_child"};
- char* const* const child_argv = owned_child_argv.get();
-
- const auto test = [&] {
- const int64_t start = MonotonicNow();
-
- pid_t pid = vfork();
- if (pid == 0) {
- SleepSafe(kChildDelay);
- execve(child_argv[0], child_argv, /* envp = */ nullptr);
- _exit(errno);
- }
- // Don't attempt save/restore until after recording end_time,
- // since the test expects an upper bound on the time spent
- // stopped.
- int saved_errno = errno;
- const int64_t end = MonotonicNow();
- errno = saved_errno;
- TEST_PCHECK_MSG(pid > 0, "vfork failed");
- MaybeSave();
-
- absl::Duration dur = absl::Nanoseconds(end - start);
-
- // The parent should resume execution after execve, but before
- // the post-execve test child exits.
- TEST_CHECK(dur >= kChildDelay);
- TEST_CHECK(dur <= 2 * kChildDelay);
-
- int status = 0;
- TEST_PCHECK(RetryEINTR(waitpid)(pid, &status, 0));
- TEST_CHECK(WIFEXITED(status));
- TEST_CHECK(WEXITSTATUS(status) == kChildExitCode);
- };
-
- EXPECT_THAT(InForkedProcess(test), IsPosixErrorOkAndHolds(0));
-}
-
-// A vfork child does not unstop the parent a second time when it exits after
-// exec.
-TEST(VforkTest, ExecedChildExitDoesntUnstopParent_NoRandomSave) {
- ExecveArray const owned_child_argv = {"/proc/self/exe", "--vfork_test_child"};
- char* const* const child_argv = owned_child_argv.get();
-
- const auto test = [&] {
- pid_t pid1 = vfork();
- if (pid1 == 0) {
- execve(child_argv[0], child_argv, /* envp = */ nullptr);
- _exit(errno);
- }
- TEST_PCHECK_MSG(pid1 > 0, "vfork failed");
- MaybeSave();
-
- // pid1 exec'd and is now sleeping.
- SleepSafe(kChildDelay / 2);
-
- const int64_t start = MonotonicNow();
-
- pid_t pid2 = vfork();
- if (pid2 == 0) {
- SleepSafe(kChildDelay);
- _exit(kChildExitCode);
- }
- TEST_PCHECK_MSG(pid2 > 0, "vfork failed");
- MaybeSave();
-
- const int64_t end = MonotonicNow();
-
- absl::Duration dur = absl::Nanoseconds(end - start);
-
- // The parent should resume execution only after pid2 exits, not
- // when pid1 exits.
- TEST_CHECK(dur >= kChildDelay);
-
- int status = 0;
- TEST_PCHECK(RetryEINTR(waitpid)(pid1, &status, 0));
- TEST_CHECK(WIFEXITED(status));
- TEST_CHECK(WEXITSTATUS(status) == kChildExitCode);
-
- TEST_PCHECK(RetryEINTR(waitpid)(pid2, &status, 0));
- TEST_CHECK(WIFEXITED(status));
- TEST_CHECK(WEXITSTATUS(status) == kChildExitCode);
- };
-
- EXPECT_THAT(InForkedProcess(test), IsPosixErrorOkAndHolds(0));
-}
-
-int RunChild() {
- SleepSafe(kChildDelay);
- return kChildExitCode;
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
-
-int main(int argc, char** argv) {
- gvisor::testing::TestInit(&argc, &argv);
-
- if (FLAGS_vfork_test_child) {
- return gvisor::testing::RunChild();
- }
-
- return RUN_ALL_TESTS();
-}
diff --git a/test/syscalls/linux/vsyscall.cc b/test/syscalls/linux/vsyscall.cc
deleted file mode 100644
index 2c2303358..000000000
--- a/test/syscalls/linux/vsyscall.cc
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <time.h>
-
-#include "gtest/gtest.h"
-#include "test/util/proc_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-time_t vsyscall_time(time_t* t) {
- constexpr uint64_t kVsyscallTimeEntry = 0xffffffffff600400;
- return reinterpret_cast<time_t (*)(time_t*)>(kVsyscallTimeEntry)(t);
-}
-
-TEST(VsyscallTest, VsyscallAlwaysAvailableOnGvisor) {
- SKIP_IF(!IsRunningOnGvisor());
- // Vsyscall is always advertised by gvisor.
- EXPECT_TRUE(ASSERT_NO_ERRNO_AND_VALUE(IsVsyscallEnabled()));
- // Vsyscall should always works on gvisor.
- time_t t;
- EXPECT_THAT(vsyscall_time(&t), SyscallSucceeds());
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/wait.cc b/test/syscalls/linux/wait.cc
deleted file mode 100644
index 944149d5e..000000000
--- a/test/syscalls/linux/wait.cc
+++ /dev/null
@@ -1,913 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <signal.h>
-#include <sys/mman.h>
-#include <sys/ptrace.h>
-#include <sys/resource.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include <functional>
-#include <tuple>
-#include <vector>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/strings/str_cat.h"
-#include "absl/synchronization/mutex.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/cleanup.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/logging.h"
-#include "test/util/multiprocess_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/signal_util.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-#include "test/util/time_util.h"
-
-using ::testing::UnorderedElementsAre;
-
-// These unit tests focus on the wait4(2) system call, but include a basic
-// checks for the i386 waitpid(2) syscall, which is a subset of wait4(2).
-//
-// NOTE(b/22640830,b/27680907,b/29049891): Some functionality is not tested as
-// it is not currently supported by gVisor:
-// * Process groups.
-// * Core dump status (WCOREDUMP).
-//
-// Tests for waiting on stopped/continued children are in sigstop.cc.
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// The CloneChild function seems to need more than one page of stack space.
-static const size_t kStackSize = 2 * kPageSize;
-
-// The child thread created in CloneAndExit runs this function.
-// This child does not have the TLS setup, so it must not use glibc functions.
-int CloneChild(void* priv) {
- int64_t sleep = reinterpret_cast<int64_t>(priv);
- SleepSafe(absl::Seconds(sleep));
-
- // glibc's _exit(2) function wrapper will helpfully call exit_group(2),
- // exiting the entire process.
- syscall(__NR_exit, 0);
- return 1;
-}
-
-// ForkAndExit forks a child process which exits with exit_code, after
-// sleeping for the specified duration (seconds).
-pid_t ForkAndExit(int exit_code, int64_t sleep) {
- pid_t child = fork();
- if (child == 0) {
- SleepSafe(absl::Seconds(sleep));
- _exit(exit_code);
- }
- return child;
-}
-
-int64_t clock_gettime_nsecs(clockid_t id) {
- struct timespec ts;
- TEST_PCHECK(clock_gettime(id, &ts) == 0);
- return (ts.tv_sec * 1000000000 + ts.tv_nsec);
-}
-
-void spin(int64_t sec) {
- int64_t ns = sec * 1000000000;
- int64_t start = clock_gettime_nsecs(CLOCK_THREAD_CPUTIME_ID);
- int64_t end = start + ns;
-
- do {
- constexpr int kLoopCount = 1000000; // large and arbitrary
- // volatile to prevent the compiler from skipping this loop.
- for (volatile int i = 0; i < kLoopCount; i++) {
- }
- } while (clock_gettime_nsecs(CLOCK_THREAD_CPUTIME_ID) < end);
-}
-
-// ForkSpinAndExit forks a child process which exits with exit_code, after
-// spinning for the specified duration (seconds).
-pid_t ForkSpinAndExit(int exit_code, int64_t spintime) {
- pid_t child = fork();
- if (child == 0) {
- spin(spintime);
- _exit(exit_code);
- }
- return child;
-}
-
-absl::Duration RusageCpuTime(const struct rusage& ru) {
- return absl::DurationFromTimeval(ru.ru_utime) +
- absl::DurationFromTimeval(ru.ru_stime);
-}
-
-// Returns the address of the top of the stack.
-// Free with FreeStack.
-uintptr_t AllocStack() {
- void* addr = mmap(nullptr, kStackSize, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
-
- if (addr == MAP_FAILED) {
- return reinterpret_cast<uintptr_t>(MAP_FAILED);
- }
-
- return reinterpret_cast<uintptr_t>(addr) + kStackSize;
-}
-
-// Frees a stack page allocated with AllocStack.
-int FreeStack(uintptr_t addr) {
- addr -= kStackSize;
- return munmap(reinterpret_cast<void*>(addr), kPageSize);
-}
-
-// CloneAndExit clones a child thread, which exits with 0 after sleeping for
-// the specified duration (must be in seconds). extra_flags are ORed against
-// the standard clone(2) flags.
-int CloneAndExit(int64_t sleep, uintptr_t stack, int extra_flags) {
- return clone(CloneChild, reinterpret_cast<void*>(stack),
- CLONE_FILES | CLONE_FS | CLONE_SIGHAND | CLONE_VM | extra_flags,
- reinterpret_cast<void*>(sleep));
-}
-
-// Simple wrappers around wait4(2) and waitid(2) that ignore interrupts.
-constexpr auto Wait4 = RetryEINTR(wait4);
-constexpr auto Waitid = RetryEINTR(waitid);
-
-// Fixture for tests parameterized by a function that waits for any child to
-// exit with the given options, checks that it exited with the given code, and
-// then returns its PID.
-//
-// N.B. These tests run in a multi-threaded environment. We assume that
-// background threads do not create child processes and are not themselves
-// created with clone(... | SIGCHLD). Either may cause these tests to
-// erroneously wait on child processes/threads.
-class WaitAnyChildTest : public ::testing::TestWithParam<
- std::function<PosixErrorOr<pid_t>(int, int)>> {
- protected:
- PosixErrorOr<pid_t> WaitAny(int code) { return WaitAnyWithOptions(code, 0); }
-
- PosixErrorOr<pid_t> WaitAnyWithOptions(int code, int options) {
- return GetParam()(code, options);
- }
-};
-
-// Wait for any child to exit.
-TEST_P(WaitAnyChildTest, Fork) {
- pid_t child;
- ASSERT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds());
-
- EXPECT_THAT(WaitAny(0), IsPosixErrorOkAndHolds(child));
-}
-
-// Call wait4 for any process after the child has already exited.
-TEST_P(WaitAnyChildTest, AfterExit) {
- pid_t child;
- ASSERT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds());
-
- absl::SleepFor(absl::Seconds(5));
-
- EXPECT_THAT(WaitAny(0), IsPosixErrorOkAndHolds(child));
-}
-
-// Wait for multiple children to exit, waiting for either at a time.
-TEST_P(WaitAnyChildTest, MultipleFork) {
- pid_t child1, child2;
- ASSERT_THAT(child1 = ForkAndExit(0, 0), SyscallSucceeds());
- ASSERT_THAT(child2 = ForkAndExit(0, 0), SyscallSucceeds());
-
- std::vector<pid_t> pids;
- pids.push_back(ASSERT_NO_ERRNO_AND_VALUE(WaitAny(0)));
- pids.push_back(ASSERT_NO_ERRNO_AND_VALUE(WaitAny(0)));
- EXPECT_THAT(pids, UnorderedElementsAre(child1, child2));
-}
-
-// Wait for any child to exit.
-// A non-CLONE_THREAD child which sends SIGCHLD upon exit behaves much like
-// a forked process.
-TEST_P(WaitAnyChildTest, CloneSIGCHLD) {
- uintptr_t stack;
- ASSERT_THAT(stack = AllocStack(), SyscallSucceeds());
- auto free =
- Cleanup([stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); });
-
- int child;
- ASSERT_THAT(child = CloneAndExit(0, stack, SIGCHLD), SyscallSucceeds());
-
- EXPECT_THAT(WaitAny(0), IsPosixErrorOkAndHolds(child));
-}
-
-// Wait for a child thread and process.
-TEST_P(WaitAnyChildTest, ForkAndClone) {
- pid_t process;
- ASSERT_THAT(process = ForkAndExit(0, 0), SyscallSucceeds());
-
- uintptr_t stack;
- ASSERT_THAT(stack = AllocStack(), SyscallSucceeds());
- auto free =
- Cleanup([stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); });
-
- int thread;
- // Send SIGCHLD for normal wait semantics.
- ASSERT_THAT(thread = CloneAndExit(0, stack, SIGCHLD), SyscallSucceeds());
-
- std::vector<pid_t> pids;
- pids.push_back(ASSERT_NO_ERRNO_AND_VALUE(WaitAny(0)));
- pids.push_back(ASSERT_NO_ERRNO_AND_VALUE(WaitAny(0)));
- EXPECT_THAT(pids, UnorderedElementsAre(process, thread));
-}
-
-// Return immediately if no child has exited.
-TEST_P(WaitAnyChildTest, WaitWNOHANG) {
- EXPECT_THAT(WaitAnyWithOptions(0, WNOHANG),
- PosixErrorIs(ECHILD, ::testing::_));
-}
-
-// Bad options passed
-TEST_P(WaitAnyChildTest, BadOption) {
- EXPECT_THAT(WaitAnyWithOptions(0, 123456),
- PosixErrorIs(EINVAL, ::testing::_));
-}
-
-TEST_P(WaitAnyChildTest, WaitedChildRusage) {
- struct rusage before;
- ASSERT_THAT(getrusage(RUSAGE_CHILDREN, &before), SyscallSucceeds());
-
- pid_t child;
- constexpr absl::Duration kSpin = absl::Seconds(3);
- ASSERT_THAT(child = ForkSpinAndExit(0, absl::ToInt64Seconds(kSpin)),
- SyscallSucceeds());
- ASSERT_THAT(WaitAny(0), IsPosixErrorOkAndHolds(child));
-
- struct rusage after;
- ASSERT_THAT(getrusage(RUSAGE_CHILDREN, &after), SyscallSucceeds());
-
- EXPECT_GE(RusageCpuTime(after) - RusageCpuTime(before), kSpin);
-}
-
-TEST_P(WaitAnyChildTest, IgnoredChildRusage) {
- // "POSIX.1-2001 specifies that if the disposition of SIGCHLD is
- // set to SIG_IGN or the SA_NOCLDWAIT flag is set for SIGCHLD (see
- // sigaction(2)), then children that terminate do not become zombies and a
- // call to wait() or waitpid() will block until all children have terminated,
- // and then fail with errno set to ECHILD." - waitpid(2)
- //
- // "RUSAGE_CHILDREN: Return resource usage statistics for all children of the
- // calling process that have terminated *and been waited for*." -
- // getrusage(2), emphasis added
-
- struct sigaction sa;
- sa.sa_handler = SIG_IGN;
- const auto cleanup_sigact =
- ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGCHLD, sa));
-
- struct rusage before;
- ASSERT_THAT(getrusage(RUSAGE_CHILDREN, &before), SyscallSucceeds());
-
- const absl::Duration start =
- absl::Nanoseconds(clock_gettime_nsecs(CLOCK_MONOTONIC));
-
- constexpr absl::Duration kSpin = absl::Seconds(3);
-
- // ForkAndSpin uses CLOCK_THREAD_CPUTIME_ID, which is lower resolution than,
- // and may diverge from, CLOCK_MONOTONIC, so we allow a small grace period but
- // still check that we blocked for a while.
- constexpr absl::Duration kSpinGrace = absl::Milliseconds(100);
-
- pid_t child;
- ASSERT_THAT(child = ForkSpinAndExit(0, absl::ToInt64Seconds(kSpin)),
- SyscallSucceeds());
- ASSERT_THAT(WaitAny(0), PosixErrorIs(ECHILD, ::testing::_));
- const absl::Duration end =
- absl::Nanoseconds(clock_gettime_nsecs(CLOCK_MONOTONIC));
- EXPECT_GE(end - start, kSpin - kSpinGrace);
-
- struct rusage after;
- ASSERT_THAT(getrusage(RUSAGE_CHILDREN, &after), SyscallSucceeds());
- EXPECT_EQ(before.ru_utime.tv_sec, after.ru_utime.tv_sec);
- EXPECT_EQ(before.ru_utime.tv_usec, after.ru_utime.tv_usec);
- EXPECT_EQ(before.ru_stime.tv_sec, after.ru_stime.tv_sec);
- EXPECT_EQ(before.ru_stime.tv_usec, after.ru_stime.tv_usec);
-}
-
-INSTANTIATE_TEST_SUITE_P(
- Waiters, WaitAnyChildTest,
- ::testing::Values(
- [](int code, int options) -> PosixErrorOr<pid_t> {
- int status;
- auto const pid = Wait4(-1, &status, options, nullptr);
- MaybeSave();
- if (pid < 0) {
- return PosixError(errno, "wait4");
- }
- if (!WIFEXITED(status) || WEXITSTATUS(status) != code) {
- return PosixError(
- EINVAL, absl::StrCat("unexpected wait status: got ", status,
- ", wanted ", code));
- }
- return static_cast<pid_t>(pid);
- },
- [](int code, int options) -> PosixErrorOr<pid_t> {
- siginfo_t si;
- auto const rv = Waitid(P_ALL, 0, &si, WEXITED | options);
- MaybeSave();
- if (rv < 0) {
- return PosixError(errno, "waitid");
- }
- if (si.si_signo != SIGCHLD) {
- return PosixError(
- EINVAL, absl::StrCat("unexpected signo: got ", si.si_signo,
- ", wanted ", SIGCHLD));
- }
- if (si.si_status != code) {
- return PosixError(
- EINVAL, absl::StrCat("unexpected status: got ", si.si_status,
- ", wanted ", code));
- }
- if (si.si_code != CLD_EXITED) {
- return PosixError(EINVAL,
- absl::StrCat("unexpected code: got ", si.si_code,
- ", wanted ", CLD_EXITED));
- }
- auto const uid = getuid();
- if (si.si_uid != uid) {
- return PosixError(EINVAL,
- absl::StrCat("unexpected uid: got ", si.si_uid,
- ", wanted ", uid));
- }
- return static_cast<pid_t>(si.si_pid);
- }));
-
-// Fixture for tests parameterized by a (sysno, function) tuple. The function
-// takes the PID of a specific child to wait for, waits for it to exit, and
-// checks that it exits with the given code.
-class WaitSpecificChildTest
- : public ::testing::TestWithParam<
- std::tuple<int, std::function<PosixError(pid_t, int, int)>>> {
- protected:
- int Sysno() { return std::get<0>(GetParam()); }
-
- PosixError WaitForWithOptions(pid_t pid, int options, int code) {
- return std::get<1>(GetParam())(pid, options, code);
- }
-
- PosixError WaitFor(pid_t pid, int code) {
- return std::get<1>(GetParam())(pid, 0, code);
- }
-};
-
-// Wait for specific child to exit.
-TEST_P(WaitSpecificChildTest, Fork) {
- pid_t child;
- ASSERT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds());
-
- EXPECT_NO_ERRNO(WaitFor(child, 0));
-}
-
-// Non-zero exit codes are correctly propagated.
-TEST_P(WaitSpecificChildTest, NormalExit) {
- pid_t child;
- ASSERT_THAT(child = ForkAndExit(42, 0), SyscallSucceeds());
-
- EXPECT_NO_ERRNO(WaitFor(child, 42));
-}
-
-// Wait for multiple children to exit.
-TEST_P(WaitSpecificChildTest, MultipleFork) {
- pid_t child1, child2;
- ASSERT_THAT(child1 = ForkAndExit(0, 0), SyscallSucceeds());
- ASSERT_THAT(child2 = ForkAndExit(0, 0), SyscallSucceeds());
-
- EXPECT_NO_ERRNO(WaitFor(child1, 0));
- EXPECT_NO_ERRNO(WaitFor(child2, 0));
-}
-
-// Wait for multiple children to exit, out of the order they were created.
-TEST_P(WaitSpecificChildTest, MultipleForkOutOfOrder) {
- pid_t child1, child2;
- ASSERT_THAT(child1 = ForkAndExit(0, 0), SyscallSucceeds());
- ASSERT_THAT(child2 = ForkAndExit(0, 0), SyscallSucceeds());
-
- EXPECT_NO_ERRNO(WaitFor(child2, 0));
- EXPECT_NO_ERRNO(WaitFor(child1, 0));
-}
-
-// Wait for specific child to exit, entering wait4 before the exit occurs.
-TEST_P(WaitSpecificChildTest, ForkSleep) {
- pid_t child;
- ASSERT_THAT(child = ForkAndExit(0, 5), SyscallSucceeds());
-
- EXPECT_NO_ERRNO(WaitFor(child, 0));
-}
-
-// Wait should block until the child exits.
-TEST_P(WaitSpecificChildTest, ForkBlock) {
- pid_t child;
-
- auto start = absl::Now();
- ASSERT_THAT(child = ForkAndExit(0, 5), SyscallSucceeds());
-
- EXPECT_NO_ERRNO(WaitFor(child, 0));
-
- EXPECT_GE(absl::Now() - start, absl::Seconds(5));
-}
-
-// Waiting after the child has already exited returns immediately.
-TEST_P(WaitSpecificChildTest, AfterExit) {
- pid_t child;
- ASSERT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds());
-
- absl::SleepFor(absl::Seconds(5));
-
- EXPECT_NO_ERRNO(WaitFor(child, 0));
-}
-
-// Wait for child of sibling thread.
-TEST_P(WaitSpecificChildTest, SiblingChildren) {
- absl::Mutex mu;
- pid_t child;
- bool ready = false;
- bool stop = false;
-
- ScopedThread t([&] {
- absl::MutexLock ml(&mu);
- EXPECT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds());
- ready = true;
- mu.Await(absl::Condition(&stop));
- });
-
- // N.B. This must be declared after ScopedThread, so it is destructed first,
- // thus waking the thread.
- absl::MutexLock ml(&mu);
- mu.Await(absl::Condition(&ready));
-
- EXPECT_NO_ERRNO(WaitFor(child, 0));
-
- // Keep the sibling alive until after we've waited so the child isn't
- // reparented.
- stop = true;
-}
-
-// Waiting for child of sibling thread not allowed with WNOTHREAD.
-TEST_P(WaitSpecificChildTest, SiblingChildrenWNOTHREAD) {
- // Linux added WNOTHREAD support to waitid(2) in
- // 91c4e8ea8f05916df0c8a6f383508ac7c9e10dba ("wait: allow sys_waitid() to
- // accept __WNOTHREAD/__WCLONE/__WALL"). i.e., Linux 4.7.
- //
- // Skip the test if it isn't supported yet.
- if (Sysno() == SYS_waitid) {
- int ret = waitid(P_ALL, 0, nullptr, WEXITED | WNOHANG | __WNOTHREAD);
- SKIP_IF(ret < 0 && errno == EINVAL);
- }
-
- absl::Mutex mu;
- pid_t child;
- bool ready = false;
- bool stop = false;
-
- ScopedThread t([&] {
- absl::MutexLock ml(&mu);
- EXPECT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds());
- ready = true;
- mu.Await(absl::Condition(&stop));
-
- // This thread can wait on child.
- EXPECT_NO_ERRNO(WaitForWithOptions(child, __WNOTHREAD, 0));
- });
-
- // N.B. This must be declared after ScopedThread, so it is destructed first,
- // thus waking the thread.
- absl::MutexLock ml(&mu);
- mu.Await(absl::Condition(&ready));
-
- // This thread can't wait on child.
- EXPECT_THAT(WaitForWithOptions(child, __WNOTHREAD, 0),
- PosixErrorIs(ECHILD, ::testing::_));
-
- // Keep the sibling alive until after we've waited so the child isn't
- // reparented.
- stop = true;
-}
-
-// Wait for specific child to exit.
-// A non-CLONE_THREAD child which sends SIGCHLD upon exit behaves much like
-// a forked process.
-TEST_P(WaitSpecificChildTest, CloneSIGCHLD) {
- uintptr_t stack;
- ASSERT_THAT(stack = AllocStack(), SyscallSucceeds());
- auto free =
- Cleanup([stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); });
-
- int child;
- ASSERT_THAT(child = CloneAndExit(0, stack, SIGCHLD), SyscallSucceeds());
-
- EXPECT_NO_ERRNO(WaitFor(child, 0));
-}
-
-// Wait for specific child to exit.
-// A non-CLONE_THREAD child which does not send SIGCHLD upon exit can be waited
-// on, but returns ECHILD.
-TEST_P(WaitSpecificChildTest, CloneNoSIGCHLD) {
- uintptr_t stack;
- ASSERT_THAT(stack = AllocStack(), SyscallSucceeds());
- auto free =
- Cleanup([stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); });
-
- int child;
- ASSERT_THAT(child = CloneAndExit(0, stack, 0), SyscallSucceeds());
-
- EXPECT_THAT(WaitFor(child, 0), PosixErrorIs(ECHILD, ::testing::_));
-}
-
-// Waiting after the child has already exited returns immediately.
-TEST_P(WaitSpecificChildTest, CloneAfterExit) {
- uintptr_t stack;
- ASSERT_THAT(stack = AllocStack(), SyscallSucceeds());
- auto free =
- Cleanup([stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); });
-
- int child;
- // Send SIGCHLD for normal wait semantics.
- ASSERT_THAT(child = CloneAndExit(0, stack, SIGCHLD), SyscallSucceeds());
-
- absl::SleepFor(absl::Seconds(5));
-
- EXPECT_NO_ERRNO(WaitFor(child, 0));
-}
-
-// A CLONE_THREAD child cannot be waited on.
-TEST_P(WaitSpecificChildTest, CloneThread) {
- uintptr_t stack;
- ASSERT_THAT(stack = AllocStack(), SyscallSucceeds());
- auto free =
- Cleanup([stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); });
-
- int child;
- ASSERT_THAT(child = CloneAndExit(15, stack, CLONE_THREAD), SyscallSucceeds());
- auto start = absl::Now();
-
- EXPECT_THAT(WaitFor(child, 0), PosixErrorIs(ECHILD, ::testing::_));
-
- // Ensure wait4 didn't block.
- EXPECT_LE(absl::Now() - start, absl::Seconds(10));
-
- // Since we can't wait on the child, we sleep to try to avoid freeing its
- // stack before it exits.
- absl::SleepFor(absl::Seconds(5));
-}
-
-// A child that does not send a SIGCHLD on exit may be waited on with
-// the __WCLONE flag.
-TEST_P(WaitSpecificChildTest, CloneWCLONE) {
- // Linux added WCLONE support to waitid(2) in
- // 91c4e8ea8f05916df0c8a6f383508ac7c9e10dba ("wait: allow sys_waitid() to
- // accept __WNOTHREAD/__WCLONE/__WALL"). i.e., Linux 4.7.
- //
- // Skip the test if it isn't supported yet.
- if (Sysno() == SYS_waitid) {
- int ret = waitid(P_ALL, 0, nullptr, WEXITED | WNOHANG | __WCLONE);
- SKIP_IF(ret < 0 && errno == EINVAL);
- }
-
- uintptr_t stack;
- ASSERT_THAT(stack = AllocStack(), SyscallSucceeds());
- auto free =
- Cleanup([stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); });
-
- int child;
- ASSERT_THAT(child = CloneAndExit(0, stack, 0), SyscallSucceeds());
-
- EXPECT_NO_ERRNO(WaitForWithOptions(child, __WCLONE, 0));
-}
-
-// A forked child cannot be waited on with WCLONE.
-TEST_P(WaitSpecificChildTest, ForkWCLONE) {
- // Linux added WCLONE support to waitid(2) in
- // 91c4e8ea8f05916df0c8a6f383508ac7c9e10dba ("wait: allow sys_waitid() to
- // accept __WNOTHREAD/__WCLONE/__WALL"). i.e., Linux 4.7.
- //
- // Skip the test if it isn't supported yet.
- if (Sysno() == SYS_waitid) {
- int ret = waitid(P_ALL, 0, nullptr, WEXITED | WNOHANG | __WCLONE);
- SKIP_IF(ret < 0 && errno == EINVAL);
- }
-
- pid_t child;
- ASSERT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds());
-
- EXPECT_THAT(WaitForWithOptions(child, WNOHANG | __WCLONE, 0),
- PosixErrorIs(ECHILD, ::testing::_));
-
- EXPECT_NO_ERRNO(WaitFor(child, 0));
-}
-
-// Any type of child can be waited on with WALL.
-TEST_P(WaitSpecificChildTest, WALL) {
- // Linux added WALL support to waitid(2) in
- // 91c4e8ea8f05916df0c8a6f383508ac7c9e10dba ("wait: allow sys_waitid() to
- // accept __WNOTHREAD/__WCLONE/__WALL"). i.e., Linux 4.7.
- //
- // Skip the test if it isn't supported yet.
- if (Sysno() == SYS_waitid) {
- int ret = waitid(P_ALL, 0, nullptr, WEXITED | WNOHANG | __WALL);
- SKIP_IF(ret < 0 && errno == EINVAL);
- }
-
- pid_t child;
- ASSERT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds());
-
- EXPECT_NO_ERRNO(WaitForWithOptions(child, __WALL, 0));
-
- uintptr_t stack;
- ASSERT_THAT(stack = AllocStack(), SyscallSucceeds());
- auto free =
- Cleanup([stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); });
-
- ASSERT_THAT(child = CloneAndExit(0, stack, 0), SyscallSucceeds());
-
- EXPECT_NO_ERRNO(WaitForWithOptions(child, __WALL, 0));
-}
-
-// Return ECHILD for bad child.
-TEST_P(WaitSpecificChildTest, BadChild) {
- EXPECT_THAT(WaitFor(42, 0), PosixErrorIs(ECHILD, ::testing::_));
-}
-
-// Wait for a child process that only exits after calling execve(2) from a
-// non-leader thread.
-TEST_P(WaitSpecificChildTest, AfterChildExecve) {
- ExecveArray const owned_child_argv = {"/bin/true"};
- char* const* const child_argv = owned_child_argv.get();
-
- uintptr_t stack;
- ASSERT_THAT(stack = AllocStack(), SyscallSucceeds());
- auto free =
- Cleanup([stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); });
-
- pid_t const child = fork();
- if (child == 0) {
- // Give the parent some time to start waiting.
- SleepSafe(absl::Seconds(5));
- // Pass CLONE_VFORK to block the original thread in the child process until
- // the clone thread calls execve, annihilating them both. (This means that
- // if clone returns at all, something went wrong.)
- //
- // N.B. clone(2) is not officially async-signal-safe, but at minimum glibc's
- // x86_64 implementation is safe. See glibc
- // sysdeps/unix/sysv/linux/x86_64/clone.S.
- clone(
- +[](void* arg) {
- auto child_argv = static_cast<char* const*>(arg);
- execve(child_argv[0], child_argv, /* envp = */ nullptr);
- return errno;
- },
- reinterpret_cast<void*>(stack),
- CLONE_FILES | CLONE_FS | CLONE_SIGHAND | CLONE_THREAD | CLONE_VM |
- CLONE_VFORK,
- const_cast<char**>(child_argv));
- _exit(errno);
- }
- ASSERT_THAT(child, SyscallSucceeds());
- EXPECT_NO_ERRNO(WaitFor(child, 0));
-}
-
-PosixError CheckWait4(pid_t pid, int options, int code) {
- int status;
- auto const rv = Wait4(pid, &status, options, nullptr);
- MaybeSave();
- if (rv < 0) {
- return PosixError(errno, "wait4");
- } else if (rv != pid) {
- return PosixError(
- EINVAL, absl::StrCat("unexpected pid: got ", rv, ", wanted ", pid));
- }
- if (!WIFEXITED(status) || WEXITSTATUS(status) != code) {
- return PosixError(EINVAL, absl::StrCat("unexpected wait status: got ",
- status, ", wanted ", code));
- }
- return NoError();
-};
-
-PosixError CheckWaitid(pid_t pid, int options, int code) {
- siginfo_t si;
- auto const rv = Waitid(P_PID, pid, &si, options | WEXITED);
- MaybeSave();
- if (rv < 0) {
- return PosixError(errno, "waitid");
- }
- if (si.si_pid != pid) {
- return PosixError(EINVAL, absl::StrCat("unexpected pid: got ", si.si_pid,
- ", wanted ", pid));
- }
- if (si.si_signo != SIGCHLD) {
- return PosixError(EINVAL, absl::StrCat("unexpected signo: got ",
- si.si_signo, ", wanted ", SIGCHLD));
- }
- if (si.si_status != code) {
- return PosixError(EINVAL, absl::StrCat("unexpected status: got ",
- si.si_status, ", wanted ", code));
- }
- if (si.si_code != CLD_EXITED) {
- return PosixError(EINVAL, absl::StrCat("unexpected code: got ", si.si_code,
- ", wanted ", CLD_EXITED));
- }
- return NoError();
-}
-
-INSTANTIATE_TEST_SUITE_P(
- Waiters, WaitSpecificChildTest,
- ::testing::Values(std::make_tuple(SYS_wait4, CheckWait4),
- std::make_tuple(SYS_waitid, CheckWaitid)));
-
-// WIFEXITED, WIFSIGNALED, WTERMSIG indicate signal exit.
-TEST(WaitTest, SignalExit) {
- pid_t child;
- ASSERT_THAT(child = ForkAndExit(0, 10), SyscallSucceeds());
-
- EXPECT_THAT(kill(child, SIGKILL), SyscallSucceeds());
-
- int status;
- EXPECT_THAT(Wait4(child, &status, 0, nullptr),
- SyscallSucceedsWithValue(child));
-
- EXPECT_FALSE(WIFEXITED(status));
- EXPECT_TRUE(WIFSIGNALED(status));
- EXPECT_EQ(SIGKILL, WTERMSIG(status));
-}
-
-// waitid requires at least one option.
-TEST(WaitTest, WaitidOptions) {
- EXPECT_THAT(Waitid(P_ALL, 0, nullptr, 0), SyscallFailsWithErrno(EINVAL));
-}
-
-// waitid does not wait for a child to exit if not passed WEXITED.
-TEST(WaitTest, WaitidNoWEXITED) {
- pid_t child;
- ASSERT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds());
- EXPECT_THAT(Waitid(P_ALL, 0, nullptr, WSTOPPED),
- SyscallFailsWithErrno(ECHILD));
- EXPECT_THAT(Waitid(P_ALL, 0, nullptr, WEXITED), SyscallSucceeds());
-}
-
-// WNOWAIT allows the same wait result to be returned again.
-TEST(WaitTest, WaitidWNOWAIT) {
- pid_t child;
- ASSERT_THAT(child = ForkAndExit(42, 0), SyscallSucceeds());
-
- siginfo_t info;
- ASSERT_THAT(Waitid(P_PID, child, &info, WEXITED | WNOWAIT),
- SyscallSucceeds());
- EXPECT_EQ(child, info.si_pid);
- EXPECT_EQ(SIGCHLD, info.si_signo);
- EXPECT_EQ(CLD_EXITED, info.si_code);
- EXPECT_EQ(42, info.si_status);
-
- ASSERT_THAT(Waitid(P_PID, child, &info, WEXITED), SyscallSucceeds());
- EXPECT_EQ(child, info.si_pid);
- EXPECT_EQ(SIGCHLD, info.si_signo);
- EXPECT_EQ(CLD_EXITED, info.si_code);
- EXPECT_EQ(42, info.si_status);
-
- EXPECT_THAT(Waitid(P_PID, child, &info, WEXITED),
- SyscallFailsWithErrno(ECHILD));
-}
-
-// waitpid(pid, status, options) is equivalent to
-// wait4(pid, status, options, nullptr).
-// This is a dedicated syscall on i386, glibc maps it to wait4 on amd64.
-TEST(WaitTest, WaitPid) {
- pid_t child;
- ASSERT_THAT(child = ForkAndExit(42, 0), SyscallSucceeds());
-
- int status;
- EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0),
- SyscallSucceedsWithValue(child));
-
- EXPECT_TRUE(WIFEXITED(status));
- EXPECT_EQ(42, WEXITSTATUS(status));
-}
-
-// Test that signaling a zombie succeeds. This is a signals test that is in this
-// file for some reason.
-TEST(WaitTest, KillZombie) {
- pid_t child;
- ASSERT_THAT(child = ForkAndExit(42, 0), SyscallSucceeds());
-
- // Sleep for three seconds to ensure the child has exited.
- absl::SleepFor(absl::Seconds(3));
-
- // The child is now a zombie. Check that killing it returns 0.
- EXPECT_THAT(kill(child, SIGTERM), SyscallSucceeds());
- EXPECT_THAT(kill(child, 0), SyscallSucceeds());
-
- EXPECT_THAT(Wait4(child, nullptr, 0, nullptr),
- SyscallSucceedsWithValue(child));
-}
-
-TEST(WaitTest, Wait4Rusage) {
- pid_t child;
- constexpr absl::Duration kSpin = absl::Seconds(3);
- ASSERT_THAT(child = ForkSpinAndExit(21, absl::ToInt64Seconds(kSpin)),
- SyscallSucceeds());
-
- int status;
- struct rusage rusage = {};
- ASSERT_THAT(Wait4(child, &status, 0, &rusage),
- SyscallSucceedsWithValue(child));
-
- EXPECT_TRUE(WIFEXITED(status));
- EXPECT_EQ(21, WEXITSTATUS(status));
-
- EXPECT_GE(RusageCpuTime(rusage), kSpin);
-}
-
-TEST(WaitTest, WaitidRusage) {
- pid_t child;
- constexpr absl::Duration kSpin = absl::Seconds(3);
- ASSERT_THAT(child = ForkSpinAndExit(27, absl::ToInt64Seconds(kSpin)),
- SyscallSucceeds());
-
- siginfo_t si = {};
- struct rusage rusage = {};
-
- // From waitid(2):
- // The raw waitid() system call takes a fifth argument, of type
- // struct rusage *. If this argument is non-NULL, then it is used
- // to return resource usage information about the child, in the
- // same manner as wait4(2).
- EXPECT_THAT(
- RetryEINTR(syscall)(SYS_waitid, P_PID, child, &si, WEXITED, &rusage),
- SyscallSucceeds());
- EXPECT_EQ(si.si_signo, SIGCHLD);
- EXPECT_EQ(si.si_code, CLD_EXITED);
- EXPECT_EQ(si.si_status, 27);
- EXPECT_EQ(si.si_pid, child);
-
- EXPECT_GE(RusageCpuTime(rusage), kSpin);
-}
-
-// After bf959931ddb88c4e4366e96dd22e68fa0db9527c ("wait/ptrace: assume __WALL
-// if the child is traced") (Linux 4.7), tracees are always eligible for
-// waiting, regardless of type.
-TEST(WaitTest, TraceeWALL) {
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
- FileDescriptor rfd(fds[0]);
- FileDescriptor wfd(fds[1]);
-
- pid_t child = fork();
- if (child == 0) {
- // Child.
- rfd.reset();
-
- TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, nullptr, nullptr) == 0);
-
- // Notify parent that we're now a tracee.
- wfd.reset();
-
- _exit(0);
- }
- ASSERT_THAT(child, SyscallSucceeds());
-
- wfd.reset();
-
- // Wait for child to become tracee.
- char c;
- EXPECT_THAT(ReadFd(rfd.get(), &c, sizeof(c)), SyscallSucceedsWithValue(0));
-
- // We can wait on the fork child with WCLONE, as it is a tracee.
- int status;
- if (IsRunningOnGvisor()) {
- ASSERT_THAT(Wait4(child, &status, __WCLONE, nullptr),
- SyscallSucceedsWithValue(child));
-
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) << status;
- } else {
- // On older versions of Linux, we may get ECHILD.
- ASSERT_THAT(Wait4(child, &status, __WCLONE, nullptr),
- ::testing::AnyOf(SyscallSucceedsWithValue(child),
- SyscallFailsWithErrno(ECHILD)));
- }
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/write.cc b/test/syscalls/linux/write.cc
deleted file mode 100644
index 9b219cfd6..000000000
--- a/test/syscalls/linux/write.cc
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <sys/resource.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <time.h>
-#include <unistd.h>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "test/util/cleanup.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-// This test is currently very rudimentary.
-//
-// TODO(edahlgren):
-// * bad buffer states (EFAULT).
-// * bad fds (wrong permission, wrong type of file, EBADF).
-// * check offset is incremented.
-// * check for EOF.
-// * writing to pipes, symlinks, special files.
-class WriteTest : public ::testing::Test {
- public:
- ssize_t WriteBytes(int fd, int bytes) {
- std::vector<char> buf(bytes);
- std::fill(buf.begin(), buf.end(), 'a');
- return WriteFd(fd, buf.data(), buf.size());
- }
-};
-
-TEST_F(WriteTest, WriteNoExceedsRLimit) {
- // Get the current rlimit and restore after test run.
- struct rlimit initial_lim;
- ASSERT_THAT(getrlimit(RLIMIT_FSIZE, &initial_lim), SyscallSucceeds());
- auto cleanup = Cleanup([&initial_lim] {
- EXPECT_THAT(setrlimit(RLIMIT_FSIZE, &initial_lim), SyscallSucceeds());
- });
-
- int fd;
- struct rlimit setlim;
- const int target_lim = 1024;
- setlim.rlim_cur = target_lim;
- setlim.rlim_max = RLIM_INFINITY;
- const std::string pathname = NewTempAbsPath();
- ASSERT_THAT(fd = open(pathname.c_str(), O_WRONLY | O_CREAT, S_IRWXU),
- SyscallSucceeds());
- ASSERT_THAT(setrlimit(RLIMIT_FSIZE, &setlim), SyscallSucceeds());
-
- EXPECT_THAT(WriteBytes(fd, target_lim), SyscallSucceedsWithValue(target_lim));
-
- std::vector<char> buf(target_lim + 1);
- std::fill(buf.begin(), buf.end(), 'a');
- EXPECT_THAT(pwrite(fd, buf.data(), target_lim, 1), SyscallSucceeds());
- EXPECT_THAT(pwrite64(fd, buf.data(), target_lim, 1), SyscallSucceeds());
-
- EXPECT_THAT(close(fd), SyscallSucceeds());
-}
-
-TEST_F(WriteTest, WriteExceedsRLimit) {
- // Get the current rlimit and restore after test run.
- struct rlimit initial_lim;
- ASSERT_THAT(getrlimit(RLIMIT_FSIZE, &initial_lim), SyscallSucceeds());
- auto cleanup = Cleanup([&initial_lim] {
- EXPECT_THAT(setrlimit(RLIMIT_FSIZE, &initial_lim), SyscallSucceeds());
- });
-
- int fd;
- sigset_t filesize_mask;
- sigemptyset(&filesize_mask);
- sigaddset(&filesize_mask, SIGXFSZ);
-
- struct rlimit setlim;
- const int target_lim = 1024;
- setlim.rlim_cur = target_lim;
- setlim.rlim_max = RLIM_INFINITY;
-
- const std::string pathname = NewTempAbsPath();
- ASSERT_THAT(fd = open(pathname.c_str(), O_WRONLY | O_CREAT, S_IRWXU),
- SyscallSucceeds());
- ASSERT_THAT(setrlimit(RLIMIT_FSIZE, &setlim), SyscallSucceeds());
- ASSERT_THAT(sigprocmask(SIG_BLOCK, &filesize_mask, nullptr),
- SyscallSucceeds());
- std::vector<char> buf(target_lim + 2);
- std::fill(buf.begin(), buf.end(), 'a');
-
- EXPECT_THAT(write(fd, buf.data(), target_lim + 1),
- SyscallSucceedsWithValue(target_lim));
- EXPECT_THAT(write(fd, buf.data(), 1), SyscallFailsWithErrno(EFBIG));
- siginfo_t info;
- struct timespec timelimit = {0, 0};
- ASSERT_THAT(RetryEINTR(sigtimedwait)(&filesize_mask, &info, &timelimit),
- SyscallSucceedsWithValue(SIGXFSZ));
- EXPECT_EQ(info.si_code, SI_USER);
- EXPECT_EQ(info.si_pid, getpid());
- EXPECT_EQ(info.si_uid, getuid());
-
- EXPECT_THAT(pwrite(fd, buf.data(), target_lim + 1, 1),
- SyscallSucceedsWithValue(target_lim - 1));
- EXPECT_THAT(pwrite(fd, buf.data(), 1, target_lim),
- SyscallFailsWithErrno(EFBIG));
- ASSERT_THAT(RetryEINTR(sigtimedwait)(&filesize_mask, &info, &timelimit),
- SyscallSucceedsWithValue(SIGXFSZ));
- EXPECT_EQ(info.si_code, SI_USER);
- EXPECT_EQ(info.si_pid, getpid());
- EXPECT_EQ(info.si_uid, getuid());
-
- EXPECT_THAT(pwrite64(fd, buf.data(), target_lim + 1, 1),
- SyscallSucceedsWithValue(target_lim - 1));
- EXPECT_THAT(pwrite64(fd, buf.data(), 1, target_lim),
- SyscallFailsWithErrno(EFBIG));
- ASSERT_THAT(RetryEINTR(sigtimedwait)(&filesize_mask, &info, &timelimit),
- SyscallSucceedsWithValue(SIGXFSZ));
- EXPECT_EQ(info.si_code, SI_USER);
- EXPECT_EQ(info.si_pid, getpid());
- EXPECT_EQ(info.si_uid, getuid());
-
- ASSERT_THAT(sigprocmask(SIG_UNBLOCK, &filesize_mask, nullptr),
- SyscallSucceeds());
- EXPECT_THAT(close(fd), SyscallSucceeds());
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/syscall_test_runner.go b/test/syscalls/syscall_test_runner.go
deleted file mode 100644
index e900f8abc..000000000
--- a/test/syscalls/syscall_test_runner.go
+++ /dev/null
@@ -1,410 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Binary syscall_test_runner runs the syscall test suites in gVisor
-// containers and on the host platform.
-package main
-
-import (
- "flag"
- "fmt"
- "io/ioutil"
- "math"
- "os"
- "os/exec"
- "os/signal"
- "path/filepath"
- "strconv"
- "strings"
- "syscall"
- "testing"
- "time"
-
- specs "github.com/opencontainers/runtime-spec/specs-go"
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/log"
- "gvisor.dev/gvisor/runsc/specutils"
- "gvisor.dev/gvisor/runsc/testutil"
- "gvisor.dev/gvisor/test/syscalls/gtest"
-)
-
-// Location of syscall tests, relative to the repo root.
-const testDir = "test/syscalls/linux"
-
-var (
- testName = flag.String("test-name", "", "name of test binary to run")
- debug = flag.Bool("debug", false, "enable debug logs")
- strace = flag.Bool("strace", false, "enable strace logs")
- platform = flag.String("platform", "ptrace", "platform to run on")
- useTmpfs = flag.Bool("use-tmpfs", false, "mounts tmpfs for /tmp")
- fileAccess = flag.String("file-access", "exclusive", "mounts root in exclusive or shared mode")
- overlay = flag.Bool("overlay", false, "wrap filesystem mounts with writable tmpfs overlay")
- parallel = flag.Bool("parallel", false, "run tests in parallel")
- runscPath = flag.String("runsc", "", "path to runsc binary")
-)
-
-// runTestCaseNative runs the test case directly on the host machine.
-func runTestCaseNative(testBin string, tc gtest.TestCase, t *testing.T) {
- // These tests might be running in parallel, so make sure they have a
- // unique test temp dir.
- tmpDir, err := ioutil.TempDir(testutil.TmpDir(), "")
- if err != nil {
- t.Fatalf("could not create temp dir: %v", err)
- }
- defer os.RemoveAll(tmpDir)
-
- // Replace TEST_TMPDIR in the current environment with something
- // unique.
- env := os.Environ()
- newEnvVar := "TEST_TMPDIR=" + tmpDir
- var found bool
- for i, kv := range env {
- if strings.HasPrefix(kv, "TEST_TMPDIR=") {
- env[i] = newEnvVar
- found = true
- break
- }
- }
- if !found {
- env = append(env, newEnvVar)
- }
- // Remove env variables that cause the gunit binary to write output
- // files, since they will stomp on eachother, and on the output files
- // from this go test.
- env = filterEnv(env, []string{"GUNIT_OUTPUT", "TEST_PREMATURE_EXIT_FILE", "XML_OUTPUT_FILE"})
-
- // Remove shard env variables so that the gunit binary does not try to
- // intepret them.
- env = filterEnv(env, []string{"TEST_SHARD_INDEX", "TEST_TOTAL_SHARDS", "GTEST_SHARD_INDEX", "GTEST_TOTAL_SHARDS"})
-
- cmd := exec.Command(testBin, gtest.FilterTestFlag+"="+tc.FullName())
- cmd.Env = env
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- if err := cmd.Run(); err != nil {
- ws := err.(*exec.ExitError).Sys().(syscall.WaitStatus)
- t.Errorf("test %q exited with status %d, want 0", tc.FullName(), ws.ExitStatus())
- }
-}
-
-// runsTestCaseRunsc runs the test case in runsc.
-func runTestCaseRunsc(testBin string, tc gtest.TestCase, t *testing.T) {
- rootDir, err := testutil.SetupRootDir()
- if err != nil {
- t.Fatalf("SetupRootDir failed: %v", err)
- }
- defer os.RemoveAll(rootDir)
-
- // Run a new container with the test executable and filter for the
- // given test suite and name.
- spec := testutil.NewSpecWithArgs(testBin, gtest.FilterTestFlag+"="+tc.FullName())
-
- // Mark the root as writeable, as some tests attempt to
- // write to the rootfs, and expect EACCES, not EROFS.
- spec.Root.Readonly = false
-
- // Test spec comes with pre-defined mounts that we don't want. Reset it.
- spec.Mounts = nil
- if *useTmpfs {
- // Forces '/tmp' to be mounted as tmpfs, otherwise test that rely on
- // features only available in gVisor's internal tmpfs may fail.
- spec.Mounts = append(spec.Mounts, specs.Mount{
- Destination: "/tmp",
- Type: "tmpfs",
- })
- } else {
- // Use a gofer-backed directory as '/tmp'.
- //
- // Tests might be running in parallel, so make sure each has a
- // unique test temp dir.
- //
- // Some tests (e.g., sticky) access this mount from other
- // users, so make sure it is world-accessible.
- tmpDir, err := ioutil.TempDir(testutil.TmpDir(), "")
- if err != nil {
- t.Fatalf("could not create temp dir: %v", err)
- }
- defer os.RemoveAll(tmpDir)
-
- if err := os.Chmod(tmpDir, 0777); err != nil {
- t.Fatalf("could not chmod temp dir: %v", err)
- }
-
- spec.Mounts = append(spec.Mounts, specs.Mount{
- Type: "bind",
- Destination: "/tmp",
- Source: tmpDir,
- })
- }
-
- // Set environment variable that indicates we are
- // running in gVisor and with the given platform.
- platformVar := "TEST_ON_GVISOR"
- env := append(os.Environ(), platformVar+"="+*platform)
-
- // Remove env variables that cause the gunit binary to write output
- // files, since they will stomp on eachother, and on the output files
- // from this go test.
- env = filterEnv(env, []string{"GUNIT_OUTPUT", "TEST_PREMATURE_EXIT_FILE", "XML_OUTPUT_FILE"})
-
- // Remove shard env variables so that the gunit binary does not try to
- // intepret them.
- env = filterEnv(env, []string{"TEST_SHARD_INDEX", "TEST_TOTAL_SHARDS", "GTEST_SHARD_INDEX", "GTEST_TOTAL_SHARDS"})
-
- // Set TEST_TMPDIR to /tmp, as some of the syscall tests require it to
- // be backed by tmpfs.
- for i, kv := range env {
- if strings.HasPrefix(kv, "TEST_TMPDIR=") {
- env[i] = "TEST_TMPDIR=/tmp"
- break
- }
- }
-
- spec.Process.Env = env
-
- bundleDir, err := testutil.SetupBundleDir(spec)
- if err != nil {
- t.Fatalf("SetupBundleDir failed: %v", err)
- }
- defer os.RemoveAll(bundleDir)
-
- id := testutil.UniqueContainerID()
- log.Infof("Running test %q in container %q", tc.FullName(), id)
- specutils.LogSpec(spec)
-
- args := []string{
- "-platform", *platform,
- "-root", rootDir,
- "-file-access", *fileAccess,
- "-network=none",
- "-log-format=text",
- "-TESTONLY-unsafe-nonroot=true",
- "-net-raw=true",
- fmt.Sprintf("-panic-signal=%d", syscall.SIGTERM),
- "-watchdog-action=panic",
- }
- if *overlay {
- args = append(args, "-overlay")
- }
- if *debug {
- args = append(args, "-debug", "-log-packets=true")
- }
- if *strace {
- args = append(args, "-strace")
- }
- if outDir, ok := syscall.Getenv("TEST_UNDECLARED_OUTPUTS_DIR"); ok {
- tdir := filepath.Join(outDir, strings.Replace(tc.FullName(), "/", "_", -1))
- if err := os.MkdirAll(tdir, 0755); err != nil {
- t.Fatalf("could not create test dir: %v", err)
- }
- debugLogDir, err := ioutil.TempDir(tdir, "runsc")
- if err != nil {
- t.Fatalf("could not create temp dir: %v", err)
- }
- debugLogDir += "/"
- log.Infof("runsc logs: %s", debugLogDir)
- args = append(args, "-debug-log", debugLogDir)
-
- // Default -log sends messages to stderr which makes reading the test log
- // difficult. Instead, drop them when debug log is enabled given it's a
- // better place for these messages.
- args = append(args, "-log=/dev/null")
- }
-
- // Current process doesn't have CAP_SYS_ADMIN, create user namespace and run
- // as root inside that namespace to get it.
- rArgs := append(args, "run", "--bundle", bundleDir, id)
- cmd := exec.Command(*runscPath, rArgs...)
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Cloneflags: syscall.CLONE_NEWUSER | syscall.CLONE_NEWNS,
- // Set current user/group as root inside the namespace.
- UidMappings: []syscall.SysProcIDMap{
- {ContainerID: 0, HostID: os.Getuid(), Size: 1},
- },
- GidMappings: []syscall.SysProcIDMap{
- {ContainerID: 0, HostID: os.Getgid(), Size: 1},
- },
- GidMappingsEnableSetgroups: false,
- Credential: &syscall.Credential{
- Uid: 0,
- Gid: 0,
- },
- }
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- sig := make(chan os.Signal, 1)
- signal.Notify(sig, syscall.SIGTERM)
- go func() {
- s, ok := <-sig
- if !ok {
- return
- }
- t.Errorf("%s: Got signal: %v", tc.FullName(), s)
- done := make(chan bool)
- go func() {
- dArgs := append(args, "-alsologtostderr=true", "debug", "--stacks", id)
- cmd := exec.Command(*runscPath, dArgs...)
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- cmd.Run()
- done <- true
- }()
-
- timeout := time.Tick(3 * time.Second)
- select {
- case <-timeout:
- t.Logf("runsc debug --stacks is timeouted")
- case <-done:
- }
-
- t.Logf("Send SIGTERM to the sandbox process")
- dArgs := append(args, "debug",
- fmt.Sprintf("--signal=%d", syscall.SIGTERM),
- id)
- cmd = exec.Command(*runscPath, dArgs...)
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- cmd.Run()
- }()
- if err = cmd.Run(); err != nil {
- t.Errorf("test %q exited with status %v, want 0", tc.FullName(), err)
- }
- signal.Stop(sig)
- close(sig)
-}
-
-// filterEnv returns an environment with the blacklisted variables removed.
-func filterEnv(env, blacklist []string) []string {
- var out []string
- for _, kv := range env {
- ok := true
- for _, k := range blacklist {
- if strings.HasPrefix(kv, k+"=") {
- ok = false
- break
- }
- }
- if ok {
- out = append(out, kv)
- }
- }
- return out
-}
-
-func fatalf(s string, args ...interface{}) {
- fmt.Fprintf(os.Stderr, s+"\n", args...)
- os.Exit(1)
-}
-
-func matchString(a, b string) (bool, error) {
- return a == b, nil
-}
-
-func main() {
- flag.Parse()
- if *testName == "" {
- fatalf("test-name flag must be provided")
- }
-
- log.SetLevel(log.Info)
- if *debug {
- log.SetLevel(log.Debug)
- }
-
- if *platform != "native" && *runscPath == "" {
- if err := testutil.ConfigureExePath(); err != nil {
- panic(err.Error())
- }
- *runscPath = specutils.ExePath
- }
-
- // Make sure stdout and stderr are opened with O_APPEND, otherwise logs
- // from outside the sandbox can (and will) stomp on logs from inside
- // the sandbox.
- for _, f := range []*os.File{os.Stdout, os.Stderr} {
- flags, err := unix.FcntlInt(f.Fd(), unix.F_GETFL, 0)
- if err != nil {
- fatalf("error getting file flags for %v: %v", f, err)
- }
- if flags&unix.O_APPEND == 0 {
- flags |= unix.O_APPEND
- if _, err := unix.FcntlInt(f.Fd(), unix.F_SETFL, flags); err != nil {
- fatalf("error setting file flags for %v: %v", f, err)
- }
- }
- }
-
- // Get path to test binary.
- fullTestName := filepath.Join(testDir, *testName)
- testBin, err := testutil.FindFile(fullTestName)
- if err != nil {
- fatalf("FindFile(%q) failed: %v", fullTestName, err)
- }
-
- // Get all test cases in each binary.
- testCases, err := gtest.ParseTestCases(testBin)
- if err != nil {
- fatalf("ParseTestCases(%q) failed: %v", testBin, err)
- }
-
- // If sharding, then get the subset of tests to run based on the shard index.
- if indexStr, totalStr := os.Getenv("TEST_SHARD_INDEX"), os.Getenv("TEST_TOTAL_SHARDS"); indexStr != "" && totalStr != "" {
- // Parse index and total to ints.
- index, err := strconv.Atoi(indexStr)
- if err != nil {
- fatalf("invalid TEST_SHARD_INDEX %q: %v", indexStr, err)
- }
- total, err := strconv.Atoi(totalStr)
- if err != nil {
- fatalf("invalid TEST_TOTAL_SHARDS %q: %v", totalStr, err)
- }
- // Calculate subslice of tests to run.
- shardSize := int(math.Ceil(float64(len(testCases)) / float64(total)))
- begin := index * shardSize
- // Set end as begin of next subslice.
- end := ((index + 1) * shardSize)
- if begin > len(testCases) {
- // Nothing to run.
- return
- }
- if end > len(testCases) {
- end = len(testCases)
- }
- testCases = testCases[begin:end]
- }
-
- var tests []testing.InternalTest
- for _, tc := range testCases {
- // Capture tc.
- tc := tc
- testName := fmt.Sprintf("%s_%s", tc.Suite, tc.Name)
- tests = append(tests, testing.InternalTest{
- Name: testName,
- F: func(t *testing.T) {
- if *parallel {
- t.Parallel()
- }
- if *platform == "native" {
- // Run the test case on host.
- runTestCaseNative(testBin, tc, t)
- } else {
- // Run the test case in runsc.
- runTestCaseRunsc(testBin, tc, t)
- }
- },
- })
- }
-
- testing.Main(matchString, tests, nil, nil)
-}
diff --git a/test/syscalls/syscall_test_runner.sh b/test/syscalls/syscall_test_runner.sh
deleted file mode 100755
index 864bb2de4..000000000
--- a/test/syscalls/syscall_test_runner.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/bash
-
-# Copyright 2018 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# syscall_test_runner.sh is a simple wrapper around the go syscall test runner.
-# It exists so that we can build the syscall test runner once, and use it for
-# all syscall tests, rather than build it for each test run.
-
-set -euf -x -o pipefail
-
-echo -- "$@"
-
-if [[ -n "${TEST_UNDECLARED_OUTPUTS_DIR}" ]]; then
- mkdir -p "${TEST_UNDECLARED_OUTPUTS_DIR}"
- chmod a+rwx "${TEST_UNDECLARED_OUTPUTS_DIR}"
-fi
-
-# Get location of syscall_test_runner binary.
-readonly runner=$(find "${TEST_SRCDIR}" -name syscall_test_runner)
-
-# Pass the arguments of this script directly to the runner.
-exec "${runner}" "$@"
diff --git a/test/util/BUILD b/test/util/BUILD
deleted file mode 100644
index 64daa0597..000000000
--- a/test/util/BUILD
+++ /dev/null
@@ -1,314 +0,0 @@
-load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
-load("//test/syscalls:build_defs.bzl", "select_for_linux")
-
-package(
- default_visibility = ["//:sandbox"],
- licenses = ["notice"],
-)
-
-cc_library(
- name = "capability_util",
- testonly = 1,
- srcs = ["capability_util.cc"],
- hdrs = ["capability_util.h"],
- deps = [
- ":cleanup",
- ":memory_util",
- ":posix_error",
- ":save_util",
- ":test_util",
- "@com_google_absl//absl/strings",
- ],
-)
-
-cc_library(
- name = "eventfd_util",
- testonly = 1,
- hdrs = ["eventfd_util.h"],
- deps = [
- ":file_descriptor",
- ":posix_error",
- ":save_util",
- ],
-)
-
-cc_library(
- name = "file_descriptor",
- testonly = 1,
- hdrs = ["file_descriptor.h"],
- deps = [
- ":logging",
- ":posix_error",
- ":save_util",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/strings:str_format",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_library(
- name = "proc_util",
- testonly = 1,
- srcs = ["proc_util.cc"],
- hdrs = ["proc_util.h"],
- deps = [
- ":fs_util",
- ":posix_error",
- ":test_util",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_test(
- name = "proc_util_test",
- size = "small",
- srcs = ["proc_util_test.cc"],
- deps = [
- ":proc_util",
- ":test_main",
- ":test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_library(
- name = "cleanup",
- testonly = 1,
- hdrs = ["cleanup.h"],
-)
-
-cc_library(
- name = "fs_util",
- testonly = 1,
- srcs = ["fs_util.cc"],
- hdrs = ["fs_util.h"],
- deps = [
- ":cleanup",
- ":file_descriptor",
- ":posix_error",
- "@com_google_absl//absl/strings",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_test(
- name = "fs_util_test",
- size = "small",
- srcs = ["fs_util_test.cc"],
- deps = [
- ":fs_util",
- ":posix_error",
- ":temp_path",
- ":test_main",
- ":test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_library(
- name = "logging",
- testonly = 1,
- srcs = ["logging.cc"],
- hdrs = ["logging.h"],
-)
-
-cc_library(
- name = "memory_util",
- testonly = 1,
- hdrs = ["memory_util.h"],
- deps = [
- ":logging",
- ":posix_error",
- ":save_util",
- ":test_util",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/strings:str_format",
- ],
-)
-
-cc_library(
- name = "mount_util",
- testonly = 1,
- hdrs = ["mount_util.h"],
- deps = [
- ":cleanup",
- ":posix_error",
- ":test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_library(
- name = "save_util",
- testonly = 1,
- srcs = ["save_util.cc"] +
- select_for_linux(
- ["save_util_linux.cc"],
- ["save_util_other.cc"],
- ),
- hdrs = ["save_util.h"],
-)
-
-cc_library(
- name = "multiprocess_util",
- testonly = 1,
- srcs = ["multiprocess_util.cc"],
- hdrs = ["multiprocess_util.h"],
- deps = [
- ":cleanup",
- ":file_descriptor",
- ":posix_error",
- ":save_util",
- ":test_util",
- "@com_google_absl//absl/strings",
- ],
-)
-
-cc_library(
- name = "posix_error",
- testonly = 1,
- srcs = ["posix_error.cc"],
- hdrs = ["posix_error.h"],
- deps = [
- ":logging",
- "@com_google_absl//absl/base:core_headers",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/types:variant",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_test(
- name = "posix_error_test",
- size = "small",
- srcs = ["posix_error_test.cc"],
- deps = [
- ":posix_error",
- ":test_main",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_library(
- name = "signal_util",
- testonly = 1,
- srcs = ["signal_util.cc"],
- hdrs = ["signal_util.h"],
- deps = [
- ":cleanup",
- ":posix_error",
- ":test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_library(
- name = "temp_path",
- testonly = 1,
- srcs = ["temp_path.cc"],
- hdrs = ["temp_path.h"],
- deps = [
- ":fs_util",
- ":posix_error",
- ":test_util",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_library(
- name = "test_util",
- testonly = 1,
- srcs = ["test_util.cc"],
- hdrs = ["test_util.h"],
- deps = [
- ":fs_util",
- ":logging",
- ":posix_error",
- ":save_util",
- "@com_github_gflags_gflags//:gflags",
- "@com_google_absl//absl/base:core_headers",
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/strings:str_format",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_library(
- name = "thread_util",
- testonly = 1,
- hdrs = ["thread_util.h"],
- deps = [":logging"],
-)
-
-cc_library(
- name = "time_util",
- testonly = 1,
- srcs = ["time_util.cc"],
- hdrs = ["time_util.h"],
- deps = [
- "@com_google_absl//absl/time",
- ],
-)
-
-cc_library(
- name = "timer_util",
- testonly = 1,
- srcs = ["timer_util.cc"],
- hdrs = ["timer_util.h"],
- deps = [
- ":cleanup",
- ":logging",
- ":posix_error",
- ":test_util",
- "@com_google_absl//absl/time",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_test(
- name = "test_util_test",
- size = "small",
- srcs = ["test_util_test.cc"],
- deps = [
- ":test_main",
- ":test_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_library(
- name = "test_main",
- testonly = 1,
- srcs = ["test_main.cc"],
- deps = [":test_util"],
-)
-
-cc_library(
- name = "epoll_util",
- testonly = 1,
- srcs = ["epoll_util.cc"],
- hdrs = ["epoll_util.h"],
- deps = [
- ":file_descriptor",
- ":posix_error",
- ":save_util",
- "@com_google_googletest//:gtest",
- ],
-)
-
-cc_library(
- name = "rlimit_util",
- testonly = 1,
- srcs = ["rlimit_util.cc"],
- hdrs = ["rlimit_util.h"],
- deps = [
- ":cleanup",
- ":logging",
- ":posix_error",
- ":test_util",
- ],
-)
diff --git a/test/util/capability_util.cc b/test/util/capability_util.cc
deleted file mode 100644
index 5d733887b..000000000
--- a/test/util/capability_util.cc
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/util/capability_util.h"
-
-#include <linux/capability.h>
-#include <sched.h>
-#include <sys/mman.h>
-#include <sys/wait.h>
-
-#include <iostream>
-
-#include "absl/strings/str_cat.h"
-#include "test/util/memory_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/save_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-PosixErrorOr<bool> CanCreateUserNamespace() {
- // The most reliable way to determine if userns creation is possible is by
- // trying to create one; see below.
- ASSIGN_OR_RETURN_ERRNO(
- auto child_stack,
- MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
- int const child_pid =
- clone(+[](void*) { return 0; },
- reinterpret_cast<void*>(child_stack.addr() + kPageSize),
- CLONE_NEWUSER | SIGCHLD, /* arg = */ nullptr);
- if (child_pid > 0) {
- int status;
- int const ret = waitpid(child_pid, &status, /* options = */ 0);
- MaybeSave();
- if (ret < 0) {
- return PosixError(errno, "waitpid");
- }
- if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
- return PosixError(
- ESRCH, absl::StrCat("child process exited with status ", status));
- }
- return true;
- } else if (errno == EPERM) {
- // Per clone(2), EPERM can be returned if:
- //
- // - "CLONE_NEWUSER was specified in flags, but either the effective user ID
- // or the effective group ID of the caller does not have a mapping in the
- // parent namespace (see user_namespaces(7))."
- //
- // - "(since Linux 3.9) CLONE_NEWUSER was specified in flags and the caller
- // is in a chroot environment (i.e., the caller's root directory does
- // not match the root directory of the mount namespace in which it
- // resides)."
- std::cerr << "clone(CLONE_NEWUSER) failed with EPERM";
- return false;
- } else if (errno == EUSERS) {
- // "(since Linux 3.11) CLONE_NEWUSER was specified in flags, and the call
- // would cause the limit on the number of nested user namespaces to be
- // exceeded. See user_namespaces(7)."
- std::cerr << "clone(CLONE_NEWUSER) failed with EUSERS";
- return false;
- } else {
- // Unexpected error code; indicate an actual error.
- return PosixError(errno, "clone(CLONE_NEWUSER)");
- }
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/util/capability_util.h b/test/util/capability_util.h
deleted file mode 100644
index bb9ea1fe5..000000000
--- a/test/util/capability_util.h
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Utilities for testing capabilities.
-
-#ifndef GVISOR_TEST_UTIL_CAPABILITY_UTIL_H_
-#define GVISOR_TEST_UTIL_CAPABILITY_UTIL_H_
-
-#include <errno.h>
-#include <linux/capability.h>
-#include <sys/syscall.h>
-#include <unistd.h>
-
-#include "test/util/cleanup.h"
-#include "test/util/posix_error.h"
-#include "test/util/save_util.h"
-#include "test/util/test_util.h"
-
-#ifndef _LINUX_CAPABILITY_VERSION_3
-#error Expecting _LINUX_CAPABILITY_VERSION_3 support
-#endif
-
-namespace gvisor {
-namespace testing {
-
-// HaveCapability returns true if the process has the specified EFFECTIVE
-// capability.
-inline PosixErrorOr<bool> HaveCapability(int cap) {
- if (!cap_valid(cap)) {
- return PosixError(EINVAL, "Invalid capability");
- }
-
- struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0};
- struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {};
- RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capget, &header, &caps));
- MaybeSave();
-
- return (caps[CAP_TO_INDEX(cap)].effective & CAP_TO_MASK(cap)) != 0;
-}
-
-// SetCapability sets the specified EFFECTIVE capability.
-inline PosixError SetCapability(int cap, bool set) {
- if (!cap_valid(cap)) {
- return PosixError(EINVAL, "Invalid capability");
- }
-
- struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0};
- struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {};
- RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capget, &header, &caps));
- MaybeSave();
-
- if (set) {
- caps[CAP_TO_INDEX(cap)].effective |= CAP_TO_MASK(cap);
- } else {
- caps[CAP_TO_INDEX(cap)].effective &= ~CAP_TO_MASK(cap);
- }
- header = {_LINUX_CAPABILITY_VERSION_3, 0};
- RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capset, &header, &caps));
- MaybeSave();
-
- return NoError();
-}
-
-// DropPermittedCapability drops the specified PERMITTED. The EFFECTIVE
-// capabilities must be a subset of PERMITTED, so those are dropped as well.
-inline PosixError DropPermittedCapability(int cap) {
- if (!cap_valid(cap)) {
- return PosixError(EINVAL, "Invalid capability");
- }
-
- struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0};
- struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {};
- RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capget, &header, &caps));
- MaybeSave();
-
- caps[CAP_TO_INDEX(cap)].effective &= ~CAP_TO_MASK(cap);
- caps[CAP_TO_INDEX(cap)].permitted &= ~CAP_TO_MASK(cap);
-
- header = {_LINUX_CAPABILITY_VERSION_3, 0};
- RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capset, &header, &caps));
- MaybeSave();
-
- return NoError();
-}
-
-PosixErrorOr<bool> CanCreateUserNamespace();
-
-} // namespace testing
-} // namespace gvisor
-#endif // GVISOR_TEST_UTIL_CAPABILITY_UTIL_H_
diff --git a/test/util/cleanup.h b/test/util/cleanup.h
deleted file mode 100644
index c76482ef4..000000000
--- a/test/util/cleanup.h
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_UTIL_CLEANUP_H_
-#define GVISOR_TEST_UTIL_CLEANUP_H_
-
-#include <functional>
-#include <utility>
-
-namespace gvisor {
-namespace testing {
-
-class Cleanup {
- public:
- Cleanup() : released_(true) {}
- explicit Cleanup(std::function<void()>&& callback) : cb_(callback) {}
-
- Cleanup(Cleanup&& other) {
- released_ = other.released_;
- cb_ = other.Release();
- }
-
- Cleanup& operator=(Cleanup&& other) {
- released_ = other.released_;
- cb_ = other.Release();
- return *this;
- }
-
- ~Cleanup() {
- if (!released_) {
- cb_();
- }
- }
-
- std::function<void()>&& Release() {
- released_ = true;
- return std::move(cb_);
- }
-
- private:
- Cleanup(Cleanup const& other) = delete;
-
- bool released_ = false;
- std::function<void(void)> cb_;
-};
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_UTIL_CLEANUP_H_
diff --git a/test/util/epoll_util.cc b/test/util/epoll_util.cc
deleted file mode 100644
index 2e5051468..000000000
--- a/test/util/epoll_util.cc
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/util/epoll_util.h"
-
-#include <sys/epoll.h>
-
-#include "gmock/gmock.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/posix_error.h"
-#include "test/util/save_util.h"
-
-namespace gvisor {
-namespace testing {
-
-PosixErrorOr<FileDescriptor> NewEpollFD(int size) {
- // "Since Linux 2.6.8, the size argument is ignored, but must be greater than
- // zero." - epoll_create(2)
- int fd = epoll_create(size);
- MaybeSave();
- if (fd < 0) {
- return PosixError(errno, "epoll_create");
- }
- return FileDescriptor(fd);
-}
-
-PosixError RegisterEpollFD(int epoll_fd, int target_fd, int events,
- uint64_t data) {
- struct epoll_event event;
- event.events = events;
- event.data.u64 = data;
- int rc = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, target_fd, &event);
- MaybeSave();
- if (rc < 0) {
- return PosixError(errno, "epoll_ctl");
- }
- return NoError();
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/util/epoll_util.h b/test/util/epoll_util.h
deleted file mode 100644
index f233b37d5..000000000
--- a/test/util/epoll_util.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_UTIL_EPOLL_UTIL_H_
-#define GVISOR_TEST_UTIL_EPOLL_UTIL_H_
-
-#include "test/util/file_descriptor.h"
-#include "test/util/posix_error.h"
-
-namespace gvisor {
-namespace testing {
-
-// Returns a new epoll file descriptor.
-PosixErrorOr<FileDescriptor> NewEpollFD(int size = 1);
-
-// Registers `target_fd` with the epoll instance represented by `epoll_fd` for
-// the epoll events `events`. Events on `target_fd` will be indicated by setting
-// data.u64 to `data` in the returned epoll_event.
-PosixError RegisterEpollFD(int epoll_fd, int target_fd, int events,
- uint64_t data);
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_UTIL_EPOLL_UTIL_H_
diff --git a/test/util/eventfd_util.h b/test/util/eventfd_util.h
deleted file mode 100644
index cb9ce829c..000000000
--- a/test/util/eventfd_util.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_UTIL_EVENTFD_UTIL_H_
-#define GVISOR_TEST_UTIL_EVENTFD_UTIL_H_
-
-#include <sys/eventfd.h>
-
-#include <cerrno>
-
-#include "test/util/file_descriptor.h"
-#include "test/util/posix_error.h"
-#include "test/util/save_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Returns a new eventfd with the given initial value and flags.
-inline PosixErrorOr<FileDescriptor> NewEventFD(unsigned int initval = 0,
- int flags = 0) {
- int fd = eventfd(initval, flags);
- MaybeSave();
- if (fd < 0) {
- return PosixError(errno, "eventfd");
- }
- return FileDescriptor(fd);
-}
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_UTIL_EVENTFD_UTIL_H_
diff --git a/test/util/file_descriptor.h b/test/util/file_descriptor.h
deleted file mode 100644
index fc5caa55b..000000000
--- a/test/util/file_descriptor.h
+++ /dev/null
@@ -1,134 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_UTIL_FILE_DESCRIPTOR_H_
-#define GVISOR_TEST_UTIL_FILE_DESCRIPTOR_H_
-
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <algorithm>
-#include <string>
-
-#include "gmock/gmock.h"
-#include "absl/strings/str_cat.h"
-#include "absl/strings/str_format.h"
-#include "test/util/logging.h"
-#include "test/util/posix_error.h"
-#include "test/util/save_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// FileDescriptor is an RAII type class which takes ownership of a file
-// descriptor. It will close the FD when this object goes out of scope.
-class FileDescriptor {
- public:
- // Constructs an empty FileDescriptor (one that does not own a file
- // descriptor).
- FileDescriptor() = default;
-
- // Constructs a FileDescriptor that owns fd. If fd is negative, constructs an
- // empty FileDescriptor.
- explicit FileDescriptor(int fd) { set_fd(fd); }
-
- FileDescriptor(FileDescriptor&& orig) : fd_(orig.release()) {}
-
- FileDescriptor& operator=(FileDescriptor&& orig) {
- reset(orig.release());
- return *this;
- }
-
- PosixErrorOr<FileDescriptor> Dup() const {
- if (fd_ < 0) {
- return PosixError(EINVAL, "Attempting to Dup unset fd");
- }
-
- int fd = dup(fd_);
- if (fd < 0) {
- return PosixError(errno, absl::StrCat("dup ", fd_));
- }
- MaybeSave();
- return FileDescriptor(fd);
- }
-
- FileDescriptor(FileDescriptor const& other) = delete;
- FileDescriptor& operator=(FileDescriptor const& other) = delete;
-
- ~FileDescriptor() { reset(); }
-
- // If this object is non-empty, returns the owned file descriptor. (Ownership
- // is retained by the FileDescriptor.) Otherwise returns -1.
- int get() const { return fd_; }
-
- // If this object is non-empty, transfers ownership of the file descriptor to
- // the caller and returns it. Otherwise returns -1.
- int release() {
- int const fd = fd_;
- fd_ = -1;
- return fd;
- }
-
- // If this object is non-empty, closes the owned file descriptor (recording a
- // test failure if the close fails).
- void reset() { reset(-1); }
-
- // Like no-arg reset(), but the FileDescriptor takes ownership of fd after
- // closing its existing file descriptor.
- void reset(int fd) {
- if (fd_ >= 0) {
- TEST_PCHECK(close(fd_) == 0);
- MaybeSave();
- }
- set_fd(fd);
- }
-
- private:
- // Wrapper that coerces negative fd values other than -1 to -1 so that get()
- // etc. return -1.
- void set_fd(int fd) { fd_ = std::max(fd, -1); }
-
- int fd_ = -1;
-};
-
-// Wrapper around open(2) that returns a FileDescriptor.
-inline PosixErrorOr<FileDescriptor> Open(std::string const& path, int flags,
- mode_t mode = 0) {
- int fd = open(path.c_str(), flags, mode);
- if (fd < 0) {
- return PosixError(errno, absl::StrFormat("open(%s, %#x, %#o)", path.c_str(),
- flags, mode));
- }
- MaybeSave();
- return FileDescriptor(fd);
-}
-
-// Wrapper around openat(2) that returns a FileDescriptor.
-inline PosixErrorOr<FileDescriptor> OpenAt(int dirfd, std::string const& path,
- int flags, mode_t mode = 0) {
- int fd = openat(dirfd, path.c_str(), flags, mode);
- if (fd < 0) {
- return PosixError(errno, absl::StrFormat("openat(%d, %s, %#x, %#o)", dirfd,
- path, flags, mode));
- }
- MaybeSave();
- return FileDescriptor(fd);
-}
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_UTIL_FILE_DESCRIPTOR_H_
diff --git a/test/util/fs_util.cc b/test/util/fs_util.cc
deleted file mode 100644
index f7d231b14..000000000
--- a/test/util/fs_util.cc
+++ /dev/null
@@ -1,604 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/util/fs_util.h"
-
-#include <dirent.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "gmock/gmock.h"
-#include "absl/strings/match.h"
-#include "absl/strings/str_cat.h"
-#include "absl/strings/str_split.h"
-#include "absl/strings/string_view.h"
-#include "test/util/cleanup.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/posix_error.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-PosixError WriteContentsToFD(int fd, absl::string_view contents) {
- int written = 0;
- while (static_cast<absl::string_view::size_type>(written) < contents.size()) {
- int wrote = write(fd, contents.data() + written, contents.size() - written);
- if (wrote < 0) {
- if (errno == EINTR) {
- continue;
- }
- return PosixError(
- errno, absl::StrCat("WriteContentsToFD fd: ", fd, " write failure."));
- }
- written += wrote;
- }
- return NoError();
-}
-} // namespace
-
-namespace internal {
-
-// Given a collection of file paths, append them all together,
-// ensuring that the proper path separators are inserted between them.
-std::string JoinPathImpl(std::initializer_list<absl::string_view> paths) {
- std::string result;
-
- if (paths.size() != 0) {
- // This size calculation is worst-case: it assumes one extra "/" for every
- // path other than the first.
- size_t total_size = paths.size() - 1;
- for (const absl::string_view path : paths) total_size += path.size();
- result.resize(total_size);
-
- auto begin = result.begin();
- auto out = begin;
- bool trailing_slash = false;
- for (absl::string_view path : paths) {
- if (path.empty()) continue;
- if (path.front() == '/') {
- if (trailing_slash) {
- path.remove_prefix(1);
- }
- } else {
- if (!trailing_slash && out != begin) *out++ = '/';
- }
- const size_t this_size = path.size();
- memcpy(&*out, path.data(), this_size);
- out += this_size;
- trailing_slash = out[-1] == '/';
- }
- result.erase(out - begin);
- }
- return result;
-}
-} // namespace internal
-
-// Returns a status or the current working directory.
-PosixErrorOr<std::string> GetCWD() {
- char buffer[PATH_MAX + 1] = {};
- if (getcwd(buffer, PATH_MAX) == nullptr) {
- return PosixError(errno, "GetCWD() failed");
- }
-
- return std::string(buffer);
-}
-
-PosixErrorOr<struct stat> Stat(absl::string_view path) {
- struct stat stat_buf;
- int res = stat(std::string(path).c_str(), &stat_buf);
- if (res < 0) {
- return PosixError(errno, absl::StrCat("stat ", path));
- }
- return stat_buf;
-}
-
-PosixErrorOr<struct stat> Fstat(int fd) {
- struct stat stat_buf;
- int res = fstat(fd, &stat_buf);
- if (res < 0) {
- return PosixError(errno, absl::StrCat("fstat ", fd));
- }
- return stat_buf;
-}
-
-PosixErrorOr<bool> Exists(absl::string_view path) {
- struct stat stat_buf;
- int res = stat(std::string(path).c_str(), &stat_buf);
- if (res < 0) {
- if (errno == ENOENT) {
- return false;
- }
- return PosixError(errno, absl::StrCat("stat ", path));
- }
- return true;
-}
-
-PosixErrorOr<bool> IsDirectory(absl::string_view path) {
- ASSIGN_OR_RETURN_ERRNO(struct stat stat_buf, Stat(path));
- if (S_ISDIR(stat_buf.st_mode)) {
- return true;
- }
-
- return false;
-}
-
-PosixError Delete(absl::string_view path) {
- int res = unlink(std::string(path).c_str());
- if (res < 0) {
- return PosixError(errno, absl::StrCat("unlink ", path));
- }
-
- return NoError();
-}
-
-PosixError Truncate(absl::string_view path, int length) {
- int res = truncate(std::string(path).c_str(), length);
- if (res < 0) {
- return PosixError(errno,
- absl::StrCat("truncate ", path, " to length ", length));
- }
-
- return NoError();
-}
-
-PosixError Chmod(absl::string_view path, int mode) {
- int res = chmod(std::string(path).c_str(), mode);
- if (res < 0) {
- return PosixError(errno, absl::StrCat("chmod ", path));
- }
-
- return NoError();
-}
-
-PosixError Mkdir(absl::string_view path, int mode) {
- int res = mkdir(std::string(path).c_str(), mode);
- if (res < 0) {
- return PosixError(errno, absl::StrCat("mkdir ", path, " mode ", mode));
- }
-
- return NoError();
-}
-
-PosixError Rmdir(absl::string_view path) {
- int res = rmdir(std::string(path).c_str());
- if (res < 0) {
- return PosixError(errno, absl::StrCat("rmdir ", path));
- }
-
- return NoError();
-}
-
-PosixError SetContents(absl::string_view path, absl::string_view contents) {
- ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(path));
- if (!exists) {
- return PosixError(
- ENOENT, absl::StrCat("SetContents file ", path, " doesn't exist."));
- }
-
- ASSIGN_OR_RETURN_ERRNO(auto fd, Open(std::string(path), O_WRONLY | O_TRUNC));
- return WriteContentsToFD(fd.get(), contents);
-}
-
-// Create a file with the given contents (if it does not already exist with the
-// given mode) and then set the contents.
-PosixError CreateWithContents(absl::string_view path,
- absl::string_view contents, int mode) {
- ASSIGN_OR_RETURN_ERRNO(
- auto fd, Open(std::string(path), O_WRONLY | O_CREAT | O_TRUNC, mode));
- return WriteContentsToFD(fd.get(), contents);
-}
-
-PosixError GetContents(absl::string_view path, std::string* output) {
- ASSIGN_OR_RETURN_ERRNO(auto fd, Open(std::string(path), O_RDONLY));
- output->clear();
-
- // Keep reading until we hit an EOF or an error.
- return GetContentsFD(fd.get(), output);
-}
-
-PosixErrorOr<std::string> GetContents(absl::string_view path) {
- std::string ret;
- RETURN_IF_ERRNO(GetContents(path, &ret));
- return ret;
-}
-
-PosixErrorOr<std::string> GetContentsFD(int fd) {
- std::string ret;
- RETURN_IF_ERRNO(GetContentsFD(fd, &ret));
- return ret;
-}
-
-PosixError GetContentsFD(int fd, std::string* output) {
- // Keep reading until we hit an EOF or an error.
- while (true) {
- char buf[16 * 1024] = {}; // Read in 16KB chunks.
- int bytes_read = read(fd, buf, sizeof(buf));
- if (bytes_read < 0) {
- if (errno == EINTR) {
- continue;
- }
- return PosixError(errno, "GetContentsFD read failure.");
- }
-
- if (bytes_read == 0) {
- break; // EOF.
- }
-
- output->append(buf, bytes_read);
- }
- return NoError();
-}
-
-PosixErrorOr<std::string> ReadLink(absl::string_view path) {
- char buf[PATH_MAX + 1] = {};
- int ret = readlink(std::string(path).c_str(), buf, PATH_MAX);
- if (ret < 0) {
- return PosixError(errno, absl::StrCat("readlink ", path));
- }
-
- return std::string(buf, ret);
-}
-
-PosixError WalkTree(
- absl::string_view path, bool recursive,
- const std::function<void(absl::string_view, const struct stat&)>& cb) {
- DIR* dir = opendir(std::string(path).c_str());
- if (dir == nullptr) {
- return PosixError(errno, absl::StrCat("opendir ", path));
- }
- auto dir_closer = Cleanup([&dir]() { closedir(dir); });
- while (true) {
- // Readdir(3): If the end of the directory stream is reached, NULL is
- // returned and errno is not changed. If an error occurs, NULL is returned
- // and errno is set appropriately. To distinguish end of stream and from an
- // error, set errno to zero before calling readdir() and then check the
- // value of errno if NULL is returned.
- errno = 0;
- struct dirent* dp = readdir(dir);
- if (dp == nullptr) {
- if (errno != 0) {
- return PosixError(errno, absl::StrCat("readdir ", path));
- }
- break; // We're done.
- }
-
- if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) {
- // Skip dots.
- continue;
- }
-
- auto full_path = JoinPath(path, dp->d_name);
- ASSIGN_OR_RETURN_ERRNO(struct stat s, Stat(full_path));
- if (S_ISDIR(s.st_mode) && recursive) {
- RETURN_IF_ERRNO(WalkTree(full_path, recursive, cb));
- } else {
- cb(full_path, s);
- }
- }
- // We're done walking so let's invoke our cleanup callback now.
- dir_closer.Release()();
-
- // And we have to dispatch the callback on the base directory.
- ASSIGN_OR_RETURN_ERRNO(struct stat s, Stat(path));
- cb(path, s);
-
- return NoError();
-}
-
-PosixErrorOr<std::vector<std::string>> ListDir(absl::string_view abspath,
- bool skipdots) {
- std::vector<std::string> files;
-
- DIR* dir = opendir(std::string(abspath).c_str());
- if (dir == nullptr) {
- return PosixError(errno, absl::StrCat("opendir ", abspath));
- }
- auto dir_closer = Cleanup([&dir]() { closedir(dir); });
- while (true) {
- // Readdir(3): If the end of the directory stream is reached, NULL is
- // returned and errno is not changed. If an error occurs, NULL is returned
- // and errno is set appropriately. To distinguish end of stream and from an
- // error, set errno to zero before calling readdir() and then check the
- // value of errno if NULL is returned.
- errno = 0;
- struct dirent* dp = readdir(dir);
- if (dp == nullptr) {
- if (errno != 0) {
- return PosixError(errno, absl::StrCat("readdir ", abspath));
- }
- break; // We're done.
- }
-
- if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) {
- if (skipdots) {
- continue;
- }
- }
- files.push_back(std::string(dp->d_name));
- }
-
- return files;
-}
-
-PosixError RecursivelyDelete(absl::string_view path, int* undeleted_dirs,
- int* undeleted_files) {
- ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(path));
- if (!exists) {
- return PosixError(ENOENT, absl::StrCat(path, " does not exist"));
- }
-
- ASSIGN_OR_RETURN_ERRNO(bool dir, IsDirectory(path));
- if (!dir) {
- // Nothing recursive needs to happen we can just call Delete.
- auto status = Delete(path);
- if (!status.ok() && undeleted_files) {
- (*undeleted_files)++;
- }
- return status;
- }
-
- return WalkTree(path, /*recursive=*/true,
- [&](absl::string_view absolute_path, const struct stat& s) {
- if (S_ISDIR(s.st_mode)) {
- auto rm_status = Rmdir(absolute_path);
- if (!rm_status.ok() && undeleted_dirs) {
- (*undeleted_dirs)++;
- }
- } else {
- auto delete_status = Delete(absolute_path);
- if (!delete_status.ok() && undeleted_files) {
- (*undeleted_files)++;
- }
- }
- });
-}
-
-PosixError RecursivelyCreateDir(absl::string_view path) {
- if (path.empty() || path == "/") {
- return PosixError(EINVAL, "Cannot create root!");
- }
-
- // Does it already exist, if so we're done.
- ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(path));
- if (exists) {
- return NoError();
- }
-
- // Do we need to create directories under us?
- auto dirname = Dirname(path);
- ASSIGN_OR_RETURN_ERRNO(exists, Exists(dirname));
- if (!exists) {
- RETURN_IF_ERRNO(RecursivelyCreateDir(dirname));
- }
-
- return Mkdir(path);
-}
-
-// Makes a path absolute with respect to an optional base. If no base is
-// provided it will use the current working directory.
-PosixErrorOr<std::string> MakeAbsolute(absl::string_view filename,
- absl::string_view base) {
- if (filename.empty()) {
- return PosixError(EINVAL, "filename cannot be empty.");
- }
-
- if (filename[0] == '/') {
- // This path is already absolute.
- return std::string(filename);
- }
-
- std::string actual_base;
- if (!base.empty()) {
- actual_base = std::string(base);
- } else {
- auto cwd_or = GetCWD();
- RETURN_IF_ERRNO(cwd_or.error());
- actual_base = cwd_or.ValueOrDie();
- }
-
- // Reverse iterate removing trailing slashes, effectively right trim '/'.
- for (int i = actual_base.size() - 1; i >= 0 && actual_base[i] == '/'; --i) {
- actual_base.erase(i, 1);
- }
-
- if (filename == ".") {
- return actual_base.empty() ? "/" : actual_base;
- }
-
- return absl::StrCat(actual_base, "/", filename);
-}
-
-std::string CleanPath(const absl::string_view unclean_path) {
- std::string path = std::string(unclean_path);
- const char *src = path.c_str();
- std::string::iterator dst = path.begin();
-
- // Check for absolute path and determine initial backtrack limit.
- const bool is_absolute_path = *src == '/';
- if (is_absolute_path) {
- *dst++ = *src++;
- while (*src == '/') ++src;
- }
- std::string::const_iterator backtrack_limit = dst;
-
- // Process all parts
- while (*src) {
- bool parsed = false;
-
- if (src[0] == '.') {
- // 1dot ".<whateverisnext>", check for END or SEP.
- if (src[1] == '/' || !src[1]) {
- if (*++src) {
- ++src;
- }
- parsed = true;
- } else if (src[1] == '.' && (src[2] == '/' || !src[2])) {
- // 2dot END or SEP (".." | "../<whateverisnext>").
- src += 2;
- if (dst != backtrack_limit) {
- // We can backtrack the previous part
- for (--dst; dst != backtrack_limit && dst[-1] != '/'; --dst) {
- // Empty.
- }
- } else if (!is_absolute_path) {
- // Failed to backtrack and we can't skip it either. Rewind and copy.
- src -= 2;
- *dst++ = *src++;
- *dst++ = *src++;
- if (*src) {
- *dst++ = *src;
- }
- // We can never backtrack over a copied "../" part so set new limit.
- backtrack_limit = dst;
- }
- if (*src) {
- ++src;
- }
- parsed = true;
- }
- }
-
- // If not parsed, copy entire part until the next SEP or EOS.
- if (!parsed) {
- while (*src && *src != '/') {
- *dst++ = *src++;
- }
- if (*src) {
- *dst++ = *src++;
- }
- }
-
- // Skip consecutive SEP occurrences
- while (*src == '/') {
- ++src;
- }
- }
-
- // Calculate and check the length of the cleaned path.
- int path_length = dst - path.begin();
- if (path_length != 0) {
- // Remove trailing '/' except if it is root path ("/" ==> path_length := 1)
- if (path_length > 1 && path[path_length - 1] == '/') {
- --path_length;
- }
- path.resize(path_length);
- } else {
- // The cleaned path is empty; assign "." as per the spec.
- path.assign(1, '.');
- }
- return path;
-}
-
-PosixErrorOr<std::string> GetRelativePath(absl::string_view source,
- absl::string_view dest) {
- if (!absl::StartsWith(source, "/") || !absl::StartsWith(dest, "/")) {
- // At least one of the inputs is not an absolute path.
- return PosixError(
- EINVAL,
- "GetRelativePath: At least one of the inputs is not an absolute path.");
- }
- const std::string clean_source = CleanPath(source);
- const std::string clean_dest = CleanPath(dest);
- auto source_parts = absl::StrSplit(clean_source, '/', absl::SkipEmpty());
- auto dest_parts = absl::StrSplit(clean_dest, '/', absl::SkipEmpty());
- auto source_iter = source_parts.begin();
- auto dest_iter = dest_parts.begin();
-
- // Advance past common prefix.
- while (source_iter != source_parts.end() && dest_iter != dest_parts.end() &&
- *source_iter == *dest_iter) {
- ++source_iter;
- ++dest_iter;
- }
-
- // Build result backtracking.
- std::string result = "";
- while (source_iter != source_parts.end()) {
- absl::StrAppend(&result, "../");
- ++source_iter;
- }
-
- // Add remaining path to dest.
- while (dest_iter != dest_parts.end()) {
- absl::StrAppend(&result, *dest_iter, "/");
- ++dest_iter;
- }
-
- if (result.empty()) {
- return std::string(".");
- }
-
- // Remove trailing slash.
- result.erase(result.size() - 1);
- return result;
-}
-
-absl::string_view Dirname(absl::string_view path) {
- return SplitPath(path).first;
-}
-
-absl::string_view Basename(absl::string_view path) {
- return SplitPath(path).second;
-}
-
-std::pair<absl::string_view, absl::string_view> SplitPath(
- absl::string_view path) {
- std::string::size_type pos = path.find_last_of('/');
-
- // Handle the case with no '/' in 'path'.
- if (pos == absl::string_view::npos) {
- return std::make_pair(path.substr(0, 0), path);
- }
-
- // Handle the case with a single leading '/' in 'path'.
- if (pos == 0) {
- return std::make_pair(path.substr(0, 1), absl::ClippedSubstr(path, 1));
- }
-
- return std::make_pair(path.substr(0, pos),
- absl::ClippedSubstr(path, pos + 1));
-}
-
-std::string JoinPath(absl::string_view path1, absl::string_view path2) {
- if (path1.empty()) {
- return std::string(path2);
- }
- if (path2.empty()) {
- return std::string(path1);
- }
-
- if (path1.back() == '/') {
- if (path2.front() == '/') {
- return absl::StrCat(path1, absl::ClippedSubstr(path2, 1));
- }
- } else {
- if (path2.front() != '/') {
- return absl::StrCat(path1, "/", path2);
- }
- }
- return absl::StrCat(path1, path2);
-}
-
-PosixErrorOr<std::string> ProcessExePath(int pid) {
- if (pid <= 0) {
- return PosixError(EINVAL, "Invalid pid specified");
- }
-
- return ReadLink(absl::StrCat("/proc/", pid, "/exe"));
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/util/fs_util.h b/test/util/fs_util.h
deleted file mode 100644
index e5b555891..000000000
--- a/test/util/fs_util.h
+++ /dev/null
@@ -1,185 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_UTIL_FS_UTIL_H_
-#define GVISOR_TEST_UTIL_FS_UTIL_H_
-
-#include <dirent.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "absl/strings/string_view.h"
-#include "test/util/posix_error.h"
-
-namespace gvisor {
-namespace testing {
-// Returns a status or the current working directory.
-PosixErrorOr<std::string> GetCWD();
-
-// Returns true/false depending on whether or not path exists, or an error if it
-// can't be determined.
-PosixErrorOr<bool> Exists(absl::string_view path);
-
-// Returns a stat structure for the given path or an error.
-PosixErrorOr<struct stat> Stat(absl::string_view path);
-
-// Returns a stat struct for the given fd.
-PosixErrorOr<struct stat> Fstat(int fd);
-
-// Deletes the file or directory at path or returns an error.
-PosixError Delete(absl::string_view path);
-
-// Changes the mode of a file or returns an error.
-PosixError Chmod(absl::string_view path, int mode);
-
-// Truncates a file to the given length or returns an error.
-PosixError Truncate(absl::string_view path, int length);
-
-// Returns true/false depending on whether or not the path is a directory or
-// returns an error.
-PosixErrorOr<bool> IsDirectory(absl::string_view path);
-
-// Makes a directory or returns an error.
-PosixError Mkdir(absl::string_view path, int mode = 0755);
-
-// Removes a directory or returns an error.
-PosixError Rmdir(absl::string_view path);
-
-// Attempts to set the contents of a file or returns an error.
-PosixError SetContents(absl::string_view path, absl::string_view contents);
-
-// Creates a file with the given contents and mode or returns an error.
-PosixError CreateWithContents(absl::string_view path,
- absl::string_view contents, int mode = 0666);
-
-// Attempts to read the entire contents of the file into the provided string
-// buffer or returns an error.
-PosixError GetContents(absl::string_view path, std::string* output);
-
-// Attempts to read the entire contents of the file or returns an error.
-PosixErrorOr<std::string> GetContents(absl::string_view path);
-
-// Attempts to read the entire contents of the provided fd into the provided
-// string or returns an error.
-PosixError GetContentsFD(int fd, std::string* output);
-
-// Attempts to read the entire contents of the provided fd or returns an error.
-PosixErrorOr<std::string> GetContentsFD(int fd);
-
-// Executes the readlink(2) system call or returns an error.
-PosixErrorOr<std::string> ReadLink(absl::string_view path);
-
-// WalkTree will walk a directory tree in a depth first search manner (if
-// recursive). It will invoke a provided callback for each file and directory,
-// the parent will always be invoked last making this appropriate for things
-// such as deleting an entire directory tree.
-//
-// This method will return an error when it's unable to access the provided
-// path, or when the path is not a directory.
-PosixError WalkTree(
- absl::string_view path, bool recursive,
- const std::function<void(absl::string_view, const struct stat&)>& cb);
-
-// Returns the base filenames for all files under a given absolute path. If
-// skipdots is true the returned vector will not contain "." or "..". This
-// method does not walk the tree recursively it only returns the elements
-// in that directory.
-PosixErrorOr<std::vector<std::string>> ListDir(absl::string_view abspath,
- bool skipdots);
-
-// Attempt to recursively delete a directory or file. Returns an error and
-// the number of undeleted directories and files. If either
-// undeleted_dirs or undeleted_files is nullptr then it will not be used.
-PosixError RecursivelyDelete(absl::string_view path, int* undeleted_dirs,
- int* undeleted_files);
-
-// Recursively create the directory provided or return an error.
-PosixError RecursivelyCreateDir(absl::string_view path);
-
-// Makes a path absolute with respect to an optional base. If no base is
-// provided it will use the current working directory.
-PosixErrorOr<std::string> MakeAbsolute(absl::string_view filename,
- absl::string_view base);
-
-// Generates a relative path from the source directory to the destination
-// (dest) file or directory. This uses ../ when necessary for destinations
-// which are not nested within the source. Both source and dest are required
-// to be absolute paths, and an empty string will be returned if they are not.
-PosixErrorOr<std::string> GetRelativePath(absl::string_view source,
- absl::string_view dest);
-
-// Returns the part of the path before the final "/", EXCEPT:
-// * If there is a single leading "/" in the path, the result will be the
-// leading "/".
-// * If there is no "/" in the path, the result is the empty prefix of the
-// input string.
-absl::string_view Dirname(absl::string_view path);
-
-// Return the parts of the path, split on the final "/". If there is no
-// "/" in the path, the first part of the output is empty and the second
-// is the input. If the only "/" in the path is the first character, it is
-// the first part of the output.
-std::pair<absl::string_view, absl::string_view> SplitPath(
- absl::string_view path);
-
-// Returns the part of the path after the final "/". If there is no
-// "/" in the path, the result is the same as the input.
-// Note that this function's behavior differs from the Unix basename
-// command if path ends with "/". For such paths, this function returns the
-// empty string.
-absl::string_view Basename(absl::string_view path);
-
-// Collapse duplicate "/"s, resolve ".." and "." path elements, remove
-// trailing "/".
-//
-// NOTE: This respects relative vs. absolute paths, but does not
-// invoke any system calls (getcwd(2)) in order to resolve relative
-// paths wrt actual working directory. That is, this is purely a
-// string manipulation, completely independent of process state.
-std::string CleanPath(absl::string_view path);
-
-// Returns the full path to the executable of the given pid or a PosixError.
-PosixErrorOr<std::string> ProcessExePath(int pid);
-
-namespace internal {
-// Not part of the public API.
-std::string JoinPathImpl(std::initializer_list<absl::string_view> paths);
-} // namespace internal
-
-// Join multiple paths together.
-// All paths will be treated as relative paths, regardless of whether or not
-// they start with a leading '/'. That is, all paths will be concatenated
-// together, with the appropriate path separator inserted in between.
-// Arguments must be convertible to absl::string_view.
-//
-// Usage:
-// std::string path = JoinPath("/foo", dirname, filename);
-// std::string path = JoinPath(FLAGS_test_srcdir, filename);
-//
-// 0, 1, 2-path specializations exist to optimize common cases.
-inline std::string JoinPath() { return std::string(); }
-inline std::string JoinPath(absl::string_view path) {
- return std::string(path.data(), path.size());
-}
-
-std::string JoinPath(absl::string_view path1, absl::string_view path2);
-template <typename... T>
-inline std::string JoinPath(absl::string_view path1, absl::string_view path2,
- absl::string_view path3, const T&... args) {
- return internal::JoinPathImpl({path1, path2, path3, args...});
-}
-} // namespace testing
-} // namespace gvisor
-#endif // GVISOR_TEST_UTIL_FS_UTIL_H_
diff --git a/test/util/fs_util_test.cc b/test/util/fs_util_test.cc
deleted file mode 100644
index 2a200320a..000000000
--- a/test/util/fs_util_test.cc
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <vector>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "test/util/fs_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(FsUtilTest, RecursivelyCreateDirManualDelete) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const std::string base_path =
- JoinPath(root.path(), "/a/b/c/d/e/f/g/h/i/j/k/l/m");
-
- ASSERT_THAT(Exists(base_path), IsPosixErrorOkAndHolds(false));
- ASSERT_NO_ERRNO(RecursivelyCreateDir(base_path));
-
- // Delete everything until we hit root and then stop, we want to try this
- // without using RecursivelyDelete.
- std::string cur_path = base_path;
- while (cur_path != root.path()) {
- ASSERT_THAT(Exists(cur_path), IsPosixErrorOkAndHolds(true));
- ASSERT_NO_ERRNO(Rmdir(cur_path));
- ASSERT_THAT(Exists(cur_path), IsPosixErrorOkAndHolds(false));
- auto dir = Dirname(cur_path);
- cur_path = std::string(dir);
- }
-}
-
-TEST(FsUtilTest, RecursivelyCreateAndDeleteDir) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const std::string base_path =
- JoinPath(root.path(), "/a/b/c/d/e/f/g/h/i/j/k/l/m");
-
- ASSERT_THAT(Exists(base_path), IsPosixErrorOkAndHolds(false));
- ASSERT_NO_ERRNO(RecursivelyCreateDir(base_path));
-
- const std::string sub_path = JoinPath(root.path(), "a");
- ASSERT_NO_ERRNO(RecursivelyDelete(sub_path, nullptr, nullptr));
- ASSERT_THAT(Exists(sub_path), IsPosixErrorOkAndHolds(false));
-}
-
-TEST(FsUtilTest, RecursivelyCreateAndDeletePartial) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const std::string base_path =
- JoinPath(root.path(), "/a/b/c/d/e/f/g/h/i/j/k/l/m");
-
- ASSERT_THAT(Exists(base_path), IsPosixErrorOkAndHolds(false));
- ASSERT_NO_ERRNO(RecursivelyCreateDir(base_path));
-
- const std::string a = JoinPath(root.path(), "a");
- auto listing = ASSERT_NO_ERRNO_AND_VALUE(ListDir(a, true));
- ASSERT_THAT(listing, ::testing::Contains("b"));
- ASSERT_EQ(listing.size(), 1);
-
- listing = ASSERT_NO_ERRNO_AND_VALUE(ListDir(a, false));
- ASSERT_THAT(listing, ::testing::Contains("."));
- ASSERT_THAT(listing, ::testing::Contains(".."));
- ASSERT_THAT(listing, ::testing::Contains("b"));
- ASSERT_EQ(listing.size(), 3);
-
- const std::string sub_path = JoinPath(root.path(), "/a/b/c/d/e/f");
-
- ASSERT_NO_ERRNO(
- CreateWithContents(JoinPath(Dirname(sub_path), "file"), "Hello World"));
- std::string contents = "";
- ASSERT_NO_ERRNO(GetContents(JoinPath(Dirname(sub_path), "file"), &contents));
- ASSERT_EQ(contents, "Hello World");
-
- ASSERT_NO_ERRNO(RecursivelyDelete(sub_path, nullptr, nullptr));
- ASSERT_THAT(Exists(sub_path), IsPosixErrorOkAndHolds(false));
-
- // The parent of the subpath (directory e) should still exist.
- ASSERT_THAT(Exists(Dirname(sub_path)), IsPosixErrorOkAndHolds(true));
-
- // The file we created along side f should also still exist.
- ASSERT_THAT(Exists(JoinPath(Dirname(sub_path), "file")),
- IsPosixErrorOkAndHolds(true));
-}
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/util/logging.cc b/test/util/logging.cc
deleted file mode 100644
index 5d5e76c46..000000000
--- a/test/util/logging.cc
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/util/logging.h"
-
-#include <errno.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// We implement this here instead of using test_util to avoid cyclic
-// dependencies.
-int Write(int fd, const char* buf, size_t size) {
- size_t written = 0;
- while (written < size) {
- int res = write(fd, buf + written, size - written);
- if (res < 0 && errno == EINTR) {
- continue;
- } else if (res <= 0) {
- break;
- }
-
- written += res;
- }
- return static_cast<int>(written);
-}
-
-// Write 32-bit decimal number to fd.
-int WriteNumber(int fd, uint32_t val) {
- constexpr char kDigits[] = "0123456789";
- constexpr int kBase = 10;
-
- // 10 chars for 32-bit number in decimal, 1 char for the NUL-terminator.
- constexpr int kBufferSize = 11;
- char buf[kBufferSize];
-
- // Convert the number to string.
- char* s = buf + sizeof(buf) - 1;
- size_t size = 0;
-
- *s = '\0';
- do {
- s--;
- size++;
-
- *s = kDigits[val % kBase];
- val /= kBase;
- } while (val);
-
- return Write(fd, s, size);
-}
-
-} // namespace
-
-void CheckFailure(const char* cond, size_t cond_size, const char* msg,
- size_t msg_size, bool include_errno) {
- int saved_errno = errno;
-
- constexpr char kCheckFailure[] = "Check failed: ";
- Write(2, kCheckFailure, sizeof(kCheckFailure) - 1);
- Write(2, cond, cond_size);
-
- if (msg != nullptr) {
- Write(2, ": ", 2);
- Write(2, msg, msg_size);
- }
-
- if (include_errno) {
- constexpr char kErrnoMessage[] = " (errno ";
- Write(2, kErrnoMessage, sizeof(kErrnoMessage) - 1);
- WriteNumber(2, saved_errno);
- Write(2, ")", 1);
- }
-
- Write(2, "\n", 1);
-
- abort();
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/util/logging.h b/test/util/logging.h
deleted file mode 100644
index 589166fab..000000000
--- a/test/util/logging.h
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_UTIL_LOGGING_H_
-#define GVISOR_TEST_UTIL_LOGGING_H_
-
-#include <stddef.h>
-
-namespace gvisor {
-namespace testing {
-
-void CheckFailure(const char* cond, size_t cond_size, const char* msg,
- size_t msg_size, bool include_errno);
-
-// If cond is false, aborts the current process.
-//
-// This macro is async-signal-safe.
-#define TEST_CHECK(cond) \
- do { \
- if (!(cond)) { \
- ::gvisor::testing::CheckFailure(#cond, sizeof(#cond) - 1, nullptr, \
- 0, false); \
- } \
- } while (0)
-
-// If cond is false, logs msg then aborts the current process.
-//
-// This macro is async-signal-safe.
-#define TEST_CHECK_MSG(cond, msg) \
- do { \
- if (!(cond)) { \
- ::gvisor::testing::CheckFailure(#cond, sizeof(#cond) - 1, msg, \
- sizeof(msg) - 1, false); \
- } \
- } while (0)
-
-// If cond is false, logs errno, then aborts the current process.
-//
-// This macro is async-signal-safe.
-#define TEST_PCHECK(cond) \
- do { \
- if (!(cond)) { \
- ::gvisor::testing::CheckFailure(#cond, sizeof(#cond) - 1, nullptr, \
- 0, true); \
- } \
- } while (0)
-
-// If cond is false, logs msg and errno, then aborts the current process.
-//
-// This macro is async-signal-safe.
-#define TEST_PCHECK_MSG(cond, msg) \
- do { \
- if (!(cond)) { \
- ::gvisor::testing::CheckFailure(#cond, sizeof(#cond) - 1, msg, \
- sizeof(msg) - 1, true); \
- } \
- } while (0)
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_UTIL_LOGGING_H_
diff --git a/test/util/memory_util.h b/test/util/memory_util.h
deleted file mode 100644
index e189b73e8..000000000
--- a/test/util/memory_util.h
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_UTIL_MEMORY_UTIL_H_
-#define GVISOR_TEST_UTIL_MEMORY_UTIL_H_
-
-#include <errno.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <sys/mman.h>
-
-#include "absl/strings/str_format.h"
-#include "absl/strings/string_view.h"
-#include "test/util/logging.h"
-#include "test/util/posix_error.h"
-#include "test/util/save_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// RAII type for mmap'ed memory. Only usable in tests due to use of a test-only
-// macro that can't be named without invoking the presubmit's wrath.
-class Mapping {
- public:
- // Constructs a mapping that owns nothing.
- Mapping() = default;
-
- // Constructs a mapping that owns the mmapped memory [ptr, ptr+len). Most
- // users should use Mmap or MmapAnon instead.
- Mapping(void* ptr, size_t len) : ptr_(ptr), len_(len) {}
-
- Mapping(Mapping&& orig) : ptr_(orig.ptr_), len_(orig.len_) { orig.release(); }
-
- Mapping& operator=(Mapping&& orig) {
- ptr_ = orig.ptr_;
- len_ = orig.len_;
- orig.release();
- return *this;
- }
-
- Mapping(Mapping const&) = delete;
- Mapping& operator=(Mapping const&) = delete;
-
- ~Mapping() { reset(); }
-
- void* ptr() const { return ptr_; }
- size_t len() const { return len_; }
-
- // Returns a pointer to the end of the mapping. Useful for when the mapping
- // is used as a thread stack.
- void* endptr() const { return reinterpret_cast<void*>(addr() + len_); }
-
- // Returns the start of this mapping cast to uintptr_t for ease of pointer
- // arithmetic.
- uintptr_t addr() const { return reinterpret_cast<uintptr_t>(ptr_); }
-
- // Returns the end of this mapping cast to uintptr_t for ease of pointer
- // arithmetic.
- uintptr_t endaddr() const { return reinterpret_cast<uintptr_t>(endptr()); }
-
- // Returns this mapping as a StringPiece for ease of comparison.
- //
- // This function is named view in anticipation of the eventual replacement of
- // StringPiece with std::string_view.
- absl::string_view view() const {
- return absl::string_view(static_cast<char const*>(ptr_), len_);
- }
-
- // These are both named reset for consistency with standard smart pointers.
-
- void reset(void* ptr, size_t len) {
- if (len_) {
- TEST_PCHECK(munmap(ptr_, len_) == 0);
- }
- ptr_ = ptr;
- len_ = len;
- }
-
- void reset() { reset(nullptr, 0); }
-
- void release() {
- ptr_ = nullptr;
- len_ = 0;
- }
-
- private:
- void* ptr_ = nullptr;
- size_t len_ = 0;
-};
-
-// Wrapper around mmap(2) that returns a Mapping.
-inline PosixErrorOr<Mapping> Mmap(void* addr, size_t length, int prot,
- int flags, int fd, off_t offset) {
- void* ptr = mmap(addr, length, prot, flags, fd, offset);
- if (ptr == MAP_FAILED) {
- return PosixError(
- errno, absl::StrFormat("mmap(%p, %d, %x, %x, %d, %d)", addr, length,
- prot, flags, fd, offset));
- }
- MaybeSave();
- return Mapping(ptr, length);
-}
-
-// Convenience wrapper around Mmap for anonymous mappings.
-inline PosixErrorOr<Mapping> MmapAnon(size_t length, int prot, int flags) {
- return Mmap(nullptr, length, prot, flags | MAP_ANONYMOUS, -1, 0);
-}
-
-// Wrapper for mremap that returns a PosixErrorOr<>, since the return type of
-// void* isn't directly compatible with SyscallSucceeds.
-inline PosixErrorOr<void*> Mremap(void* old_address, size_t old_size,
- size_t new_size, int flags,
- void* new_address) {
- void* rv = mremap(old_address, old_size, new_size, flags, new_address);
- if (rv == MAP_FAILED) {
- return PosixError(errno, "mremap failed");
- }
- return rv;
-}
-
-// Returns true if the page containing addr is mapped.
-inline bool IsMapped(uintptr_t addr) {
- int const rv = msync(reinterpret_cast<void*>(addr & ~(kPageSize - 1)),
- kPageSize, MS_ASYNC);
- if (rv == 0) {
- return true;
- }
- TEST_PCHECK_MSG(errno == ENOMEM, "msync failed with unexpected errno");
- return false;
-}
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_UTIL_MEMORY_UTIL_H_
diff --git a/test/util/mount_util.h b/test/util/mount_util.h
deleted file mode 100644
index 38ec6c8a1..000000000
--- a/test/util/mount_util.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_UTIL_MOUNT_UTIL_H_
-#define GVISOR_TEST_UTIL_MOUNT_UTIL_H_
-
-#include <errno.h>
-#include <sys/mount.h>
-#include <functional>
-#include <string>
-
-#include "gmock/gmock.h"
-#include "test/util/cleanup.h"
-#include "test/util/posix_error.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Mount mounts the filesystem, and unmounts when the returned reference is
-// destroyed.
-inline PosixErrorOr<Cleanup> Mount(const std::string &source,
- const std::string &target,
- const std::string &fstype, uint64_t mountflags,
- const std::string &data,
- uint64_t umountflags) {
- if (mount(source.c_str(), target.c_str(), fstype.c_str(), mountflags,
- data.c_str()) == -1) {
- return PosixError(errno, "mount failed");
- }
- return Cleanup([target, umountflags]() {
- EXPECT_THAT(umount2(target.c_str(), umountflags), SyscallSucceeds());
- });
-}
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_UTIL_MOUNT_UTIL_H_
diff --git a/test/util/multiprocess_util.cc b/test/util/multiprocess_util.cc
deleted file mode 100644
index 95f5f3b4f..000000000
--- a/test/util/multiprocess_util.cc
+++ /dev/null
@@ -1,139 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/util/multiprocess_util.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <sys/prctl.h>
-#include <unistd.h>
-
-#include "absl/strings/str_cat.h"
-#include "test/util/cleanup.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/posix_error.h"
-#include "test/util/save_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-PosixErrorOr<Cleanup> ForkAndExec(const std::string& filename,
- const ExecveArray& argv,
- const ExecveArray& envv,
- const std::function<void()>& fn, pid_t* child,
- int* execve_errno) {
- int pfds[2];
- int ret = pipe2(pfds, O_CLOEXEC);
- if (ret < 0) {
- return PosixError(errno, "pipe failed");
- }
- FileDescriptor rfd(pfds[0]);
- FileDescriptor wfd(pfds[1]);
-
- int parent_stdout = dup(STDOUT_FILENO);
- if (parent_stdout < 0) {
- return PosixError(errno, "dup stdout");
- }
- int parent_stderr = dup(STDERR_FILENO);
- if (parent_stdout < 0) {
- return PosixError(errno, "dup stderr");
- }
-
- pid_t pid = fork();
- if (pid < 0) {
- return PosixError(errno, "fork failed");
- } else if (pid == 0) {
- // Child.
- rfd.reset();
- if (dup2(parent_stdout, STDOUT_FILENO) < 0) {
- _exit(3);
- }
- if (dup2(parent_stderr, STDERR_FILENO) < 0) {
- _exit(4);
- }
- close(parent_stdout);
- close(parent_stderr);
-
- // Clean ourself up in case the parent doesn't.
- if (prctl(PR_SET_PDEATHSIG, SIGKILL)) {
- _exit(3);
- }
-
- if (fn) {
- fn();
- }
-
- execve(filename.c_str(), argv.get(), envv.get());
- int error = errno;
- if (WriteFd(pfds[1], &error, sizeof(error)) != sizeof(error)) {
- // We can't do much if the write fails, but we can at least exit with a
- // different code.
- _exit(2);
- }
- _exit(1);
- }
-
- // Parent.
- if (child) {
- *child = pid;
- }
-
- auto cleanup = Cleanup([pid] {
- kill(pid, SIGKILL);
- RetryEINTR(waitpid)(pid, nullptr, 0);
- });
-
- wfd.reset();
-
- int read_errno;
- ret = ReadFd(rfd.get(), &read_errno, sizeof(read_errno));
- if (ret == 0) {
- // Other end of the pipe closed, execve must have succeeded.
- read_errno = 0;
- } else if (ret < 0) {
- return PosixError(errno, "read pipe failed");
- } else if (ret != sizeof(read_errno)) {
- return PosixError(EPIPE, absl::StrCat("pipe read wrong size ", ret));
- }
-
- if (execve_errno) {
- *execve_errno = read_errno;
- }
-
- return std::move(cleanup);
-}
-
-PosixErrorOr<int> InForkedProcess(const std::function<void()>& fn) {
- pid_t pid = fork();
- if (pid == 0) {
- fn();
- _exit(0);
- }
- MaybeSave();
- if (pid < 0) {
- return PosixError(errno, "fork failed");
- }
-
- int status;
- if (waitpid(pid, &status, 0) < 0) {
- return PosixError(errno, "waitpid failed");
- }
-
- return status;
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/util/multiprocess_util.h b/test/util/multiprocess_util.h
deleted file mode 100644
index 0aecd3439..000000000
--- a/test/util/multiprocess_util.h
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_UTIL_MULTIPROCESS_UTIL_H_
-#define GVISOR_TEST_UTIL_MULTIPROCESS_UTIL_H_
-
-#include <unistd.h>
-
-#include <algorithm>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "absl/strings/string_view.h"
-#include "test/util/cleanup.h"
-#include "test/util/posix_error.h"
-
-namespace gvisor {
-namespace testing {
-
-// Immutable holder for a dynamically-sized array of pointers to mutable char,
-// terminated by a null pointer, as required for the argv and envp arguments to
-// execve(2).
-class ExecveArray {
- public:
- // Constructs an empty ExecveArray.
- ExecveArray() = default;
-
- // Constructs an ExecveArray by copying strings from the given range. T must
- // be a range over ranges of char.
- template <typename T>
- explicit ExecveArray(T const& strs) : ExecveArray(strs.begin(), strs.end()) {}
-
- // Constructs an ExecveArray by copying strings from [first, last). InputIt
- // must be an input iterator over a range over char.
- template <typename InputIt>
- ExecveArray(InputIt first, InputIt last) {
- std::vector<size_t> offsets;
- auto output_it = std::back_inserter(str_);
- for (InputIt it = first; it != last; ++it) {
- offsets.push_back(str_.size());
- auto const& s = *it;
- std::copy(s.begin(), s.end(), output_it);
- str_.push_back('\0');
- }
- ptrs_.reserve(offsets.size() + 1);
- for (auto offset : offsets) {
- ptrs_.push_back(str_.data() + offset);
- }
- ptrs_.push_back(nullptr);
- }
-
- // Constructs an ExecveArray by copying strings from list. This overload must
- // exist independently of the single-argument template constructor because
- // std::initializer_list does not participate in template argument deduction
- // (i.e. cannot be type-inferred in an invocation of the templated
- // constructor).
- /* implicit */ ExecveArray(std::initializer_list<absl::string_view> list)
- : ExecveArray(list.begin(), list.end()) {}
-
- // Disable move construction and assignment since ptrs_ points into str_.
- ExecveArray(ExecveArray&&) = delete;
- ExecveArray& operator=(ExecveArray&&) = delete;
-
- char* const* get() const { return ptrs_.data(); }
- size_t get_size() { return str_.size(); }
-
- private:
- std::vector<char> str_;
- std::vector<char*> ptrs_;
-};
-
-// Simplified version of SubProcess. Returns OK and a cleanup function to kill
-// the child if it made it to execve.
-//
-// fn is run between fork and exec. If it needs to fail, it should exit the
-// process.
-//
-// The child pid is returned via child, if provided.
-// execve's error code is returned via execve_errno, if provided.
-PosixErrorOr<Cleanup> ForkAndExec(const std::string& filename,
- const ExecveArray& argv,
- const ExecveArray& envv,
- const std::function<void()>& fn, pid_t* child,
- int* execve_errno);
-
-inline PosixErrorOr<Cleanup> ForkAndExec(const std::string& filename,
- const ExecveArray& argv,
- const ExecveArray& envv, pid_t* child,
- int* execve_errno) {
- return ForkAndExec(filename, argv, envv, [] {}, child, execve_errno);
-}
-
-// Calls fn in a forked subprocess and returns the exit status of the
-// subprocess.
-//
-// fn must be async-signal-safe.
-PosixErrorOr<int> InForkedProcess(const std::function<void()>& fn);
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_UTIL_MULTIPROCESS_UTIL_H_
diff --git a/test/util/posix_error.cc b/test/util/posix_error.cc
deleted file mode 100644
index cebf7e0ac..000000000
--- a/test/util/posix_error.cc
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/util/posix_error.h"
-
-#include <cassert>
-#include <cerrno>
-#include <cstring>
-#include <string>
-
-#include "absl/strings/str_cat.h"
-
-namespace gvisor {
-namespace testing {
-
-std::string PosixError::ToString() const {
- if (ok()) {
- return "No Error";
- }
-
- std::string ret;
-
- char strerrno_buf[1024] = {};
-
- auto res = strerror_r(errno_, strerrno_buf, sizeof(strerrno_buf));
-
-// The GNU version of strerror_r always returns a non-null char* pointing to a
-// buffer containing the stringified errno; the XSI version returns a positive
-// errno which indicates the result of writing the stringified errno into the
-// supplied buffer. The gymnastics below are needed to support both.
-#ifndef _GNU_SOURCE
- if (res != 0) {
- ret = absl::StrCat("PosixError(errno=", errno_, " strerror_r FAILED(", ret,
- "))");
- } else {
- ret = absl::StrCat("PosixError(errno=", errno_, " ", strerrno_buf, ")");
- }
-#else
- ret = absl::StrCat("PosixError(errno=", errno_, " ", res, ")");
-#endif
-
- if (!msg_.empty()) {
- ret.append(" ");
- ret.append(msg_);
- }
-
- return ret;
-}
-
-::std::ostream& operator<<(::std::ostream& os, const PosixError& e) {
- os << e.ToString();
- return os;
-}
-
-void PosixErrorIsMatcherCommonImpl::DescribeTo(std::ostream* os) const {
- *os << "has an errno value that ";
- code_matcher_.DescribeTo(os);
- *os << ", and has an error message that ";
- message_matcher_.DescribeTo(os);
-}
-
-void PosixErrorIsMatcherCommonImpl::DescribeNegationTo(std::ostream* os) const {
- *os << "has an errno value that ";
- code_matcher_.DescribeNegationTo(os);
- *os << ", or has an error message that ";
- message_matcher_.DescribeNegationTo(os);
-}
-
-bool PosixErrorIsMatcherCommonImpl::MatchAndExplain(
- const PosixError& error,
- ::testing::MatchResultListener* result_listener) const {
- ::testing::StringMatchResultListener inner_listener;
-
- inner_listener.Clear();
- if (!code_matcher_.MatchAndExplain(error.errno_value(), &inner_listener)) {
- return false;
- }
-
- if (!message_matcher_.Matches(error.error_message())) {
- return false;
- }
-
- return true;
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/util/posix_error.h b/test/util/posix_error.h
deleted file mode 100644
index ad666bce0..000000000
--- a/test/util/posix_error.h
+++ /dev/null
@@ -1,462 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_UTIL_POSIX_ERROR_H_
-#define GVISOR_TEST_UTIL_POSIX_ERROR_H_
-
-#include <string>
-
-#include "gmock/gmock.h"
-#include "absl/base/attributes.h"
-#include "absl/strings/string_view.h"
-#include "absl/types/variant.h"
-#include "test/util/logging.h"
-
-namespace gvisor {
-namespace testing {
-
-class PosixErrorIsMatcherCommonImpl;
-
-template <typename T>
-class PosixErrorOr;
-
-class ABSL_MUST_USE_RESULT PosixError {
- public:
- PosixError() {}
- explicit PosixError(int errno_value) : errno_(errno_value) {}
- PosixError(int errno_value, std::string msg)
- : errno_(errno_value), msg_(std::move(msg)) {}
-
- PosixError(PosixError&& other) = default;
- PosixError& operator=(PosixError&& other) = default;
- PosixError(const PosixError&) = default;
- PosixError& operator=(const PosixError&) = default;
-
- bool ok() const { return errno_ == 0; }
-
- // Returns a reference to *this to make matchers compatible with
- // PosixErrorOr.
- const PosixError& error() const { return *this; }
-
- std::string error_message() const { return msg_; }
-
- // ToString produces a full string representation of this posix error
- // including the printable representation of the errno and the error message.
- std::string ToString() const;
-
- // Ignores any errors. This method does nothing except potentially suppress
- // complaints from any tools that are checking that errors are not dropped on
- // the floor.
- void IgnoreError() const {}
-
- private:
- int errno_value() const { return errno_; }
- int errno_ = 0;
- std::string msg_;
-
- friend class PosixErrorIsMatcherCommonImpl;
-
- template <typename T>
- friend class PosixErrorOr;
-};
-
-template <typename T>
-class ABSL_MUST_USE_RESULT PosixErrorOr {
- public:
- // A PosixErrorOr will check fail if it is constructed with NoError().
- PosixErrorOr(const PosixError& error);
- PosixErrorOr(const T& value);
- PosixErrorOr(T&& value);
-
- PosixErrorOr(PosixErrorOr&& other) = default;
- PosixErrorOr& operator=(PosixErrorOr&& other) = default;
- PosixErrorOr(const PosixErrorOr&) = default;
- PosixErrorOr& operator=(const PosixErrorOr&) = default;
-
- // Conversion copy/move constructor, T must be convertible from U.
- template <typename U>
- friend class PosixErrorOr;
-
- template <typename U>
- PosixErrorOr(PosixErrorOr<U> other);
-
- template <typename U>
- PosixErrorOr& operator=(PosixErrorOr<U> other);
-
- // Return a reference to the error or NoError().
- PosixError error() const;
-
- // Returns this->error().error_message();
- std::string error_message() const;
-
- // Returns true if this PosixErrorOr contains some T.
- bool ok() const;
-
- // Returns a reference to our current value, or CHECK-fails if !this->ok().
- const T& ValueOrDie() const&;
- T& ValueOrDie() &;
- const T&& ValueOrDie() const&&;
- T&& ValueOrDie() &&;
-
- // Ignores any errors. This method does nothing except potentially suppress
- // complaints from any tools that are checking that errors are not dropped on
- // the floor.
- void IgnoreError() const {}
-
- private:
- int errno_value() const;
- absl::variant<T, PosixError> value_;
-
- friend class PosixErrorIsMatcherCommonImpl;
-};
-
-template <typename T>
-PosixErrorOr<T>::PosixErrorOr(const PosixError& error) : value_(error) {
- TEST_CHECK_MSG(
- !error.ok(),
- "Constructing PosixErrorOr with NoError, eg. errno 0 is not allowed.");
-}
-
-template <typename T>
-PosixErrorOr<T>::PosixErrorOr(const T& value) : value_(value) {}
-
-template <typename T>
-PosixErrorOr<T>::PosixErrorOr(T&& value) : value_(std::move(value)) {}
-
-// Conversion copy/move constructor, T must be convertible from U.
-template <typename T>
-template <typename U>
-inline PosixErrorOr<T>::PosixErrorOr(PosixErrorOr<U> other) {
- if (absl::holds_alternative<U>(other.value_)) {
- // T is convertible from U.
- value_ = absl::get<U>(std::move(other.value_));
- } else if (absl::holds_alternative<PosixError>(other.value_)) {
- value_ = absl::get<PosixError>(std::move(other.value_));
- } else {
- TEST_CHECK_MSG(false, "PosixErrorOr does not contain PosixError or value");
- }
-}
-
-template <typename T>
-template <typename U>
-inline PosixErrorOr<T>& PosixErrorOr<T>::operator=(PosixErrorOr<U> other) {
- if (absl::holds_alternative<U>(other.value_)) {
- // T is convertible from U.
- value_ = absl::get<U>(std::move(other.value_));
- } else if (absl::holds_alternative<PosixError>(other.value_)) {
- value_ = absl::get<PosixError>(std::move(other.value_));
- } else {
- TEST_CHECK_MSG(false, "PosixErrorOr does not contain PosixError or value");
- }
- return *this;
-}
-
-template <typename T>
-PosixError PosixErrorOr<T>::error() const {
- if (!absl::holds_alternative<PosixError>(value_)) {
- return PosixError();
- }
- return absl::get<PosixError>(value_);
-}
-
-template <typename T>
-int PosixErrorOr<T>::errno_value() const {
- return error().errno_value();
-}
-
-template <typename T>
-std::string PosixErrorOr<T>::error_message() const {
- return error().error_message();
-}
-
-template <typename T>
-bool PosixErrorOr<T>::ok() const {
- return absl::holds_alternative<T>(value_);
-}
-
-template <typename T>
-const T& PosixErrorOr<T>::ValueOrDie() const& {
- TEST_CHECK(absl::holds_alternative<T>(value_));
- return absl::get<T>(value_);
-}
-
-template <typename T>
-T& PosixErrorOr<T>::ValueOrDie() & {
- TEST_CHECK(absl::holds_alternative<T>(value_));
- return absl::get<T>(value_);
-}
-
-template <typename T>
-const T&& PosixErrorOr<T>::ValueOrDie() const&& {
- TEST_CHECK(absl::holds_alternative<T>(value_));
- return std::move(absl::get<T>(value_));
-}
-
-template <typename T>
-T&& PosixErrorOr<T>::ValueOrDie() && {
- TEST_CHECK(absl::holds_alternative<T>(value_));
- return std::move(absl::get<T>(value_));
-}
-
-extern ::std::ostream& operator<<(::std::ostream& os, const PosixError& e);
-
-template <typename T>
-::std::ostream& operator<<(::std::ostream& os, const PosixErrorOr<T>& e) {
- os << e.error();
- return os;
-}
-
-// NoError is a PosixError that represents a successful state, i.e. No Error.
-inline PosixError NoError() { return PosixError(); }
-
-// Monomorphic implementation of matcher IsPosixErrorOk() for a given type T.
-// T can be PosixError, PosixErrorOr<>, or a reference to either of them.
-template <typename T>
-class MonoPosixErrorIsOkMatcherImpl : public ::testing::MatcherInterface<T> {
- public:
- void DescribeTo(std::ostream* os) const override { *os << "is OK"; }
- void DescribeNegationTo(std::ostream* os) const override {
- *os << "is not OK";
- }
- bool MatchAndExplain(T actual_value,
- ::testing::MatchResultListener*) const override {
- return actual_value.ok();
- }
-};
-
-// Implements IsPosixErrorOkMatcher() as a polymorphic matcher.
-class IsPosixErrorOkMatcher {
- public:
- template <typename T>
- operator ::testing::Matcher<T>() const { // NOLINT
- return MakeMatcher(new MonoPosixErrorIsOkMatcherImpl<T>());
- }
-};
-
-// Monomorphic implementation of a matcher for a PosixErrorOr.
-template <typename PosixErrorOrType>
-class IsPosixErrorOkAndHoldsMatcherImpl
- : public ::testing::MatcherInterface<PosixErrorOrType> {
- public:
- using ValueType = typename std::remove_reference<decltype(
- std::declval<PosixErrorOrType>().ValueOrDie())>::type;
-
- template <typename InnerMatcher>
- explicit IsPosixErrorOkAndHoldsMatcherImpl(InnerMatcher&& inner_matcher)
- : inner_matcher_(::testing::SafeMatcherCast<const ValueType&>(
- std::forward<InnerMatcher>(inner_matcher))) {}
-
- void DescribeTo(std::ostream* os) const override {
- *os << "is OK and has a value that ";
- inner_matcher_.DescribeTo(os);
- }
-
- void DescribeNegationTo(std::ostream* os) const override {
- *os << "isn't OK or has a value that ";
- inner_matcher_.DescribeNegationTo(os);
- }
-
- bool MatchAndExplain(
- PosixErrorOrType actual_value,
- ::testing::MatchResultListener* listener) const override {
- // We can't extract the value if it doesn't contain one.
- if (!actual_value.ok()) {
- return false;
- }
-
- ::testing::StringMatchResultListener inner_listener;
- const bool matches = inner_matcher_.MatchAndExplain(
- actual_value.ValueOrDie(), &inner_listener);
- const std::string inner_explanation = inner_listener.str();
- *listener << "has a value "
- << ::testing::PrintToString(actual_value.ValueOrDie());
-
- if (!inner_explanation.empty()) {
- *listener << " " << inner_explanation;
- }
- return matches;
- }
-
- private:
- const ::testing::Matcher<const ValueType&> inner_matcher_;
-};
-
-// Implements IsOkAndHolds() as a polymorphic matcher.
-template <typename InnerMatcher>
-class IsPosixErrorOkAndHoldsMatcher {
- public:
- explicit IsPosixErrorOkAndHoldsMatcher(InnerMatcher inner_matcher)
- : inner_matcher_(std::move(inner_matcher)) {}
-
- // Converts this polymorphic matcher to a monomorphic one of the given type.
- // PosixErrorOrType can be either PosixErrorOr<T> or a reference to
- // PosixErrorOr<T>.
- template <typename PosixErrorOrType>
- operator ::testing::Matcher<PosixErrorOrType>() const { // NOLINT
- return ::testing::MakeMatcher(
- new IsPosixErrorOkAndHoldsMatcherImpl<PosixErrorOrType>(
- inner_matcher_));
- }
-
- private:
- const InnerMatcher inner_matcher_;
-};
-
-// PosixErrorIs() is a polymorphic matcher. This class is the common
-// implementation of it shared by all types T where PosixErrorIs() can be
-// used as a Matcher<T>.
-class PosixErrorIsMatcherCommonImpl {
- public:
- PosixErrorIsMatcherCommonImpl(
- ::testing::Matcher<int> code_matcher,
- ::testing::Matcher<const std::string&> message_matcher)
- : code_matcher_(std::move(code_matcher)),
- message_matcher_(std::move(message_matcher)) {}
-
- void DescribeTo(std::ostream* os) const;
-
- void DescribeNegationTo(std::ostream* os) const;
-
- bool MatchAndExplain(const PosixError& error,
- ::testing::MatchResultListener* result_listener) const;
-
- template <typename T>
- bool MatchAndExplain(const PosixErrorOr<T>& error_or,
- ::testing::MatchResultListener* result_listener) const {
- if (error_or.ok()) {
- *result_listener << "has a value "
- << ::testing::PrintToString(error_or.ValueOrDie());
- return false;
- }
-
- return MatchAndExplain(error_or.error(), result_listener);
- }
-
- private:
- const ::testing::Matcher<int> code_matcher_;
- const ::testing::Matcher<const std::string&> message_matcher_;
-};
-
-// Monomorphic implementation of matcher PosixErrorIs() for a given type
-// T. T can be PosixError, PosixErrorOr<>, or a reference to either of them.
-template <typename T>
-class MonoPosixErrorIsMatcherImpl : public ::testing::MatcherInterface<T> {
- public:
- explicit MonoPosixErrorIsMatcherImpl(
- PosixErrorIsMatcherCommonImpl common_impl)
- : common_impl_(std::move(common_impl)) {}
-
- void DescribeTo(std::ostream* os) const override {
- common_impl_.DescribeTo(os);
- }
-
- void DescribeNegationTo(std::ostream* os) const override {
- common_impl_.DescribeNegationTo(os);
- }
-
- bool MatchAndExplain(
- T actual_value,
- ::testing::MatchResultListener* result_listener) const override {
- return common_impl_.MatchAndExplain(actual_value, result_listener);
- }
-
- private:
- PosixErrorIsMatcherCommonImpl common_impl_;
-};
-
-inline ::testing::Matcher<int> ToErrorCodeMatcher(
- const ::testing::Matcher<int>& m) {
- return m;
-}
-
-// Implements PosixErrorIs() as a polymorphic matcher.
-class PosixErrorIsMatcher {
- public:
- template <typename ErrorCodeMatcher>
- PosixErrorIsMatcher(ErrorCodeMatcher&& code_matcher,
- ::testing::Matcher<const std::string&> message_matcher)
- : common_impl_(
- ToErrorCodeMatcher(std::forward<ErrorCodeMatcher>(code_matcher)),
- std::move(message_matcher)) {}
-
- // Converts this polymorphic matcher to a monomorphic matcher of the
- // given type. T can be StatusOr<>, Status, or a reference to
- // either of them.
- template <typename T>
- operator ::testing::Matcher<T>() const { // NOLINT
- return MakeMatcher(new MonoPosixErrorIsMatcherImpl<T>(common_impl_));
- }
-
- private:
- const PosixErrorIsMatcherCommonImpl common_impl_;
-};
-
-// Returns a gMock matcher that matches a PosixError or PosixErrorOr<> whose
-// whose error code matches code_matcher, and whose error message matches
-// message_matcher.
-template <typename ErrorCodeMatcher>
-PosixErrorIsMatcher PosixErrorIs(
- ErrorCodeMatcher&& code_matcher,
- ::testing::Matcher<const std::string&> message_matcher) {
- return PosixErrorIsMatcher(std::forward<ErrorCodeMatcher>(code_matcher),
- std::move(message_matcher));
-}
-
-// Returns a gMock matcher that matches a PosixErrorOr<> which is ok() and
-// value matches the inner matcher.
-template <typename InnerMatcher>
-IsPosixErrorOkAndHoldsMatcher<typename std::decay<InnerMatcher>::type>
-IsPosixErrorOkAndHolds(InnerMatcher&& inner_matcher) {
- return IsPosixErrorOkAndHoldsMatcher<typename std::decay<InnerMatcher>::type>(
- std::forward<InnerMatcher>(inner_matcher));
-}
-
-// Internal helper for concatenating macro values.
-#define POSIX_ERROR_IMPL_CONCAT_INNER_(x, y) x##y
-#define POSIX_ERROR_IMPL_CONCAT_(x, y) POSIX_ERROR_IMPL_CONCAT_INNER_(x, y)
-
-#define POSIX_ERROR_IMPL_ASSIGN_OR_RETURN_(posixerroror, lhs, rexpr) \
- auto posixerroror = (rexpr); \
- if (!posixerroror.ok()) { \
- return (posixerroror.error()); \
- } \
- lhs = std::move(posixerroror).ValueOrDie()
-
-#define EXPECT_NO_ERRNO(expression) \
- EXPECT_THAT(expression, IsPosixErrorOkMatcher())
-#define ASSERT_NO_ERRNO(expression) \
- ASSERT_THAT(expression, IsPosixErrorOkMatcher())
-
-#define ASSIGN_OR_RETURN_ERRNO(lhs, rexpr) \
- POSIX_ERROR_IMPL_ASSIGN_OR_RETURN_( \
- POSIX_ERROR_IMPL_CONCAT_(_status_or_value, __LINE__), lhs, rexpr)
-
-#define RETURN_IF_ERRNO(s) \
- do { \
- if (!s.ok()) { \
- return s; \
- } \
- } while (false);
-
-#define ASSERT_NO_ERRNO_AND_VALUE(expr) \
- ({ \
- auto _expr_result = (expr); \
- ASSERT_NO_ERRNO(_expr_result); \
- std::move(_expr_result).ValueOrDie(); \
- })
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_UTIL_POSIX_ERROR_H_
diff --git a/test/util/posix_error_test.cc b/test/util/posix_error_test.cc
deleted file mode 100644
index d67270842..000000000
--- a/test/util/posix_error_test.cc
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/util/posix_error.h"
-
-#include <errno.h>
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(PosixErrorTest, PosixError) {
- auto err = PosixError(EAGAIN);
- EXPECT_THAT(err, PosixErrorIs(EAGAIN, ""));
-}
-
-TEST(PosixErrorTest, PosixErrorOrPosixError) {
- auto err = PosixErrorOr<std::nullptr_t>(PosixError(EAGAIN));
- EXPECT_THAT(err, PosixErrorIs(EAGAIN, ""));
-}
-
-TEST(PosixErrorTest, PosixErrorOrNullptr) {
- auto err = PosixErrorOr<std::nullptr_t>(nullptr);
- EXPECT_TRUE(err.ok());
- EXPECT_NO_ERRNO(err);
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/util/proc_util.cc b/test/util/proc_util.cc
deleted file mode 100644
index 75b24da37..000000000
--- a/test/util/proc_util.cc
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/util/proc_util.h"
-
-#include <algorithm>
-#include <iostream>
-#include <vector>
-
-#include "absl/strings/ascii.h"
-#include "absl/strings/str_cat.h"
-#include "absl/strings/str_split.h"
-#include "absl/strings/string_view.h"
-#include "test/util/fs_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Parses a single line from /proc/<xxx>/maps.
-PosixErrorOr<ProcMapsEntry> ParseProcMapsLine(absl::string_view line) {
- ProcMapsEntry map_entry = {};
-
- // Limit splitting to 6 parts so that if there is a file path and it contains
- // spaces, the file path is not split.
- std::vector<std::string> parts =
- absl::StrSplit(line, absl::MaxSplits(' ', 5), absl::SkipEmpty());
-
- // parts.size() should be 6 if there is a file name specified, and 5
- // otherwise.
- if (parts.size() < 5) {
- return PosixError(EINVAL, absl::StrCat("Invalid line: ", line));
- }
-
- // Address range in the form X-X where X are hex values without leading 0x.
- std::vector<std::string> addresses = absl::StrSplit(parts[0], '-');
- if (addresses.size() != 2) {
- return PosixError(EINVAL,
- absl::StrCat("Invalid address range: ", parts[0]));
- }
- ASSIGN_OR_RETURN_ERRNO(map_entry.start, AtoiBase(addresses[0], 16));
- ASSIGN_OR_RETURN_ERRNO(map_entry.end, AtoiBase(addresses[1], 16));
-
- // Permissions are four bytes of the form rwxp or - if permission not set.
- if (parts[1].size() != 4) {
- return PosixError(EINVAL,
- absl::StrCat("Invalid permission field: ", parts[1]));
- }
-
- map_entry.readable = parts[1][0] == 'r';
- map_entry.writable = parts[1][1] == 'w';
- map_entry.executable = parts[1][2] == 'x';
- map_entry.priv = parts[1][3] == 'p';
-
- ASSIGN_OR_RETURN_ERRNO(map_entry.offset, AtoiBase(parts[2], 16));
-
- std::vector<std::string> device = absl::StrSplit(parts[3], ':');
- if (device.size() != 2) {
- return PosixError(EINVAL, absl::StrCat("Invalid device: ", parts[3]));
- }
- ASSIGN_OR_RETURN_ERRNO(map_entry.major, AtoiBase(device[0], 16));
- ASSIGN_OR_RETURN_ERRNO(map_entry.minor, AtoiBase(device[1], 16));
-
- ASSIGN_OR_RETURN_ERRNO(map_entry.inode, Atoi<int64_t>(parts[4]));
- if (parts.size() == 6) {
- // A filename is present. However, absl::StrSplit retained the whitespace
- // between the inode number and the filename.
- map_entry.filename =
- std::string(absl::StripLeadingAsciiWhitespace(parts[5]));
- }
-
- return map_entry;
-}
-
-PosixErrorOr<std::vector<ProcMapsEntry>> ParseProcMaps(
- absl::string_view contents) {
- std::vector<ProcMapsEntry> entries;
- auto lines = absl::StrSplit(contents, '\n', absl::SkipEmpty());
- for (const auto& l : lines) {
- std::cout << "line: " << l;
- ASSIGN_OR_RETURN_ERRNO(auto entry, ParseProcMapsLine(l));
- entries.push_back(entry);
- }
- return entries;
-}
-
-PosixErrorOr<bool> IsVsyscallEnabled() {
- ASSIGN_OR_RETURN_ERRNO(auto contents, GetContents("/proc/self/maps"));
- ASSIGN_OR_RETURN_ERRNO(auto maps, ParseProcMaps(contents));
- return std::any_of(maps.begin(), maps.end(), [](const ProcMapsEntry& e) {
- return e.filename == "[vsyscall]";
- });
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/util/proc_util.h b/test/util/proc_util.h
deleted file mode 100644
index af209a51e..000000000
--- a/test/util/proc_util.h
+++ /dev/null
@@ -1,150 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_UTIL_PROC_UTIL_H_
-#define GVISOR_TEST_UTIL_PROC_UTIL_H_
-
-#include <ostream>
-#include <string>
-#include <vector>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/strings/str_cat.h"
-#include "absl/strings/string_view.h"
-#include "test/util/fs_util.h"
-#include "test/util/posix_error.h"
-
-namespace gvisor {
-namespace testing {
-
-// ProcMapsEntry contains the data from a single line in /proc/<xxx>/maps.
-struct ProcMapsEntry {
- uint64_t start;
- uint64_t end;
- bool readable;
- bool writable;
- bool executable;
- bool priv;
- uint64_t offset;
- int major;
- int minor;
- int64_t inode;
- std::string filename;
-};
-
-// Parses a ProcMaps line or returns an error.
-PosixErrorOr<ProcMapsEntry> ParseProcMapsLine(absl::string_view line);
-PosixErrorOr<std::vector<ProcMapsEntry>> ParseProcMaps(
- absl::string_view contents);
-
-// Returns true if vsyscall (emmulation or not) is enabled.
-PosixErrorOr<bool> IsVsyscallEnabled();
-
-// Printer for ProcMapsEntry.
-inline std::ostream& operator<<(std::ostream& os, const ProcMapsEntry& entry) {
- std::string str =
- absl::StrCat(absl::Hex(entry.start, absl::PadSpec::kZeroPad8), "-",
- absl::Hex(entry.end, absl::PadSpec::kZeroPad8), " ");
-
- absl::StrAppend(&str, entry.readable ? "r" : "-");
- absl::StrAppend(&str, entry.writable ? "w" : "-");
- absl::StrAppend(&str, entry.executable ? "x" : "-");
- absl::StrAppend(&str, entry.priv ? "p" : "s");
-
- absl::StrAppend(&str, " ", absl::Hex(entry.offset, absl::PadSpec::kZeroPad8),
- " ", absl::Hex(entry.major, absl::PadSpec::kZeroPad2), ":",
- absl::Hex(entry.minor, absl::PadSpec::kZeroPad2), " ",
- entry.inode);
- if (absl::string_view(entry.filename) != "") {
- // Pad to column 74
- int pad = 73 - str.length();
- if (pad > 0) {
- absl::StrAppend(&str, std::string(pad, ' '));
- }
- absl::StrAppend(&str, entry.filename);
- }
- os << str;
- return os;
-}
-
-// Printer for std::vector<ProcMapsEntry>.
-inline std::ostream& operator<<(std::ostream& os,
- const std::vector<ProcMapsEntry>& vec) {
- for (unsigned int i = 0; i < vec.size(); i++) {
- os << vec[i];
- if (i != vec.size() - 1) {
- os << "\n";
- }
- }
- return os;
-}
-
-// GMock printer for std::vector<ProcMapsEntry>.
-inline void PrintTo(const std::vector<ProcMapsEntry>& vec, std::ostream* os) {
- *os << vec;
-}
-
-// Checks that /proc/pid/maps contains all of the passed mappings.
-//
-// The major, minor, and inode fields are ignored.
-MATCHER_P(ContainsMappings, mappings,
- "contains mappings:\n" + ::testing::PrintToString(mappings)) {
- auto contents_or = GetContents(absl::StrCat("/proc/", arg, "/maps"));
- if (!contents_or.ok()) {
- *result_listener << "Unable to read mappings: "
- << contents_or.error().ToString();
- return false;
- }
-
- auto maps_or = ParseProcMaps(contents_or.ValueOrDie());
- if (!maps_or.ok()) {
- *result_listener << "Unable to parse mappings: "
- << maps_or.error().ToString();
- return false;
- }
-
- auto maps = std::move(maps_or).ValueOrDie();
-
- // Does maps contain all elements in mappings? The comparator ignores
- // the major, minor, and inode fields.
- bool all_present = true;
- std::for_each(mappings.begin(), mappings.end(), [&](const ProcMapsEntry& e1) {
- auto it =
- std::find_if(maps.begin(), maps.end(), [&e1](const ProcMapsEntry& e2) {
- return e1.start == e2.start && e1.end == e2.end &&
- e1.readable == e2.readable && e1.writable == e2.writable &&
- e1.executable == e2.executable && e1.priv == e2.priv &&
- e1.offset == e2.offset && e1.filename == e2.filename;
- });
- if (it == maps.end()) {
- // It wasn't found.
- if (all_present) {
- // We will output the message once and then a line for each mapping
- // that wasn't found.
- all_present = false;
- *result_listener << "Got mappings:\n"
- << maps << "\nThat were missing:\n";
- }
- *result_listener << e1 << "\n";
- }
- });
-
- return all_present;
-}
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_UTIL_PROC_UTIL_H_
diff --git a/test/util/proc_util_test.cc b/test/util/proc_util_test.cc
deleted file mode 100644
index 71dd2355e..000000000
--- a/test/util/proc_util_test.cc
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/util/proc_util.h"
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "test/util/test_util.h"
-
-using ::testing::IsEmpty;
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(ParseProcMapsLineTest, WithoutFilename) {
- auto entry = ASSERT_NO_ERRNO_AND_VALUE(
- ParseProcMapsLine("2ab4f00b7000-2ab4f00b9000 r-xp 00000000 00:00 0 "));
- EXPECT_EQ(entry.start, 0x2ab4f00b7000);
- EXPECT_EQ(entry.end, 0x2ab4f00b9000);
- EXPECT_TRUE(entry.readable);
- EXPECT_FALSE(entry.writable);
- EXPECT_TRUE(entry.executable);
- EXPECT_TRUE(entry.priv);
- EXPECT_EQ(entry.offset, 0);
- EXPECT_EQ(entry.major, 0);
- EXPECT_EQ(entry.minor, 0);
- EXPECT_EQ(entry.inode, 0);
- EXPECT_THAT(entry.filename, IsEmpty());
-}
-
-TEST(ParseProcMapsLineTest, WithFilename) {
- auto entry = ASSERT_NO_ERRNO_AND_VALUE(
- ParseProcMapsLine("00407000-00408000 rw-p 00006000 00:0e 10 "
- " /bin/cat"));
- EXPECT_EQ(entry.start, 0x407000);
- EXPECT_EQ(entry.end, 0x408000);
- EXPECT_TRUE(entry.readable);
- EXPECT_TRUE(entry.writable);
- EXPECT_FALSE(entry.executable);
- EXPECT_TRUE(entry.priv);
- EXPECT_EQ(entry.offset, 0x6000);
- EXPECT_EQ(entry.major, 0);
- EXPECT_EQ(entry.minor, 0x0e);
- EXPECT_EQ(entry.inode, 10);
- EXPECT_EQ(entry.filename, "/bin/cat");
-}
-
-TEST(ParseProcMapsLineTest, WithFilenameContainingSpaces) {
- auto entry = ASSERT_NO_ERRNO_AND_VALUE(
- ParseProcMapsLine("7f26b3b12000-7f26b3b13000 rw-s 00000000 00:05 1432484 "
- " /dev/zero (deleted)"));
- EXPECT_EQ(entry.start, 0x7f26b3b12000);
- EXPECT_EQ(entry.end, 0x7f26b3b13000);
- EXPECT_TRUE(entry.readable);
- EXPECT_TRUE(entry.writable);
- EXPECT_FALSE(entry.executable);
- EXPECT_FALSE(entry.priv);
- EXPECT_EQ(entry.offset, 0);
- EXPECT_EQ(entry.major, 0);
- EXPECT_EQ(entry.minor, 0x05);
- EXPECT_EQ(entry.inode, 1432484);
- EXPECT_EQ(entry.filename, "/dev/zero (deleted)");
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/util/rlimit_util.cc b/test/util/rlimit_util.cc
deleted file mode 100644
index 684253f78..000000000
--- a/test/util/rlimit_util.cc
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/util/rlimit_util.h"
-
-#include <sys/resource.h>
-#include <cerrno>
-
-#include "test/util/cleanup.h"
-#include "test/util/logging.h"
-#include "test/util/posix_error.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-PosixErrorOr<Cleanup> ScopedSetSoftRlimit(int resource, rlim_t newval) {
- struct rlimit old_rlim;
- if (getrlimit(resource, &old_rlim) != 0) {
- return PosixError(errno, "getrlimit failed");
- }
- struct rlimit new_rlim = old_rlim;
- new_rlim.rlim_cur = newval;
- if (setrlimit(resource, &new_rlim) != 0) {
- return PosixError(errno, "setrlimit failed");
- }
- return Cleanup([resource, old_rlim] {
- TEST_PCHECK(setrlimit(resource, &old_rlim) == 0);
- });
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/util/rlimit_util.h b/test/util/rlimit_util.h
deleted file mode 100644
index 873252a32..000000000
--- a/test/util/rlimit_util.h
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_UTIL_RLIMIT_UTIL_H_
-#define GVISOR_TEST_UTIL_RLIMIT_UTIL_H_
-
-#include <sys/resource.h>
-#include <sys/time.h>
-
-#include "test/util/cleanup.h"
-#include "test/util/posix_error.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-PosixErrorOr<Cleanup> ScopedSetSoftRlimit(int resource, rlim_t newval);
-
-} // namespace testing
-} // namespace gvisor
-#endif // GVISOR_TEST_UTIL_RLIMIT_UTIL_H_
diff --git a/test/util/save_util.cc b/test/util/save_util.cc
deleted file mode 100644
index 384d626f0..000000000
--- a/test/util/save_util.cc
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/util/save_util.h"
-
-#include <stddef.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-#include <atomic>
-#include <cerrno>
-
-#define GVISOR_COOPERATIVE_SAVE_TEST "GVISOR_COOPERATIVE_SAVE_TEST"
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-enum class CooperativeSaveMode {
- kUnknown = 0, // cooperative_save_mode is statically-initialized to 0
- kAvailable,
- kNotAvailable,
-};
-
-std::atomic<CooperativeSaveMode> cooperative_save_mode;
-
-bool CooperativeSaveEnabled() {
- auto mode = cooperative_save_mode.load();
- if (mode == CooperativeSaveMode::kUnknown) {
- mode = (getenv(GVISOR_COOPERATIVE_SAVE_TEST) != nullptr)
- ? CooperativeSaveMode::kAvailable
- : CooperativeSaveMode::kNotAvailable;
- cooperative_save_mode.store(mode);
- }
- return mode == CooperativeSaveMode::kAvailable;
-}
-
-std::atomic<int> save_disable;
-
-} // namespace
-
-DisableSave::DisableSave() { save_disable++; }
-
-DisableSave::~DisableSave() { reset(); }
-
-void DisableSave::reset() {
- if (!reset_) {
- reset_ = true;
- save_disable--;
- }
-}
-
-namespace internal {
-bool ShouldSave() {
- return CooperativeSaveEnabled() && (save_disable.load() == 0);
-}
-} // namespace internal
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/util/save_util.h b/test/util/save_util.h
deleted file mode 100644
index bddad6120..000000000
--- a/test/util/save_util.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_UTIL_SAVE_UTIL_H_
-#define GVISOR_TEST_UTIL_SAVE_UTIL_H_
-
-namespace gvisor {
-namespace testing {
-// Disable save prevents saving while the given function executes.
-//
-// This lasts the duration of the object, unless reset is called.
-class DisableSave {
- public:
- DisableSave();
- ~DisableSave();
- DisableSave(DisableSave const&) = delete;
- DisableSave(DisableSave&&) = delete;
- DisableSave& operator=(DisableSave const&) = delete;
- DisableSave& operator=(DisableSave&&) = delete;
-
- // reset allows saves to continue, and is called implicitly by the destructor.
- // It may be called multiple times safely, but is not thread-safe.
- void reset();
-
- private:
- bool reset_ = false;
-};
-
-// May perform a co-operative save cycle.
-//
-// errno is guaranteed to be preserved.
-void MaybeSave();
-
-namespace internal {
-bool ShouldSave();
-} // namespace internal
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_UTIL_SAVE_UTIL_H_
diff --git a/test/util/save_util_linux.cc b/test/util/save_util_linux.cc
deleted file mode 100644
index 7a0f14342..000000000
--- a/test/util/save_util_linux.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <errno.h>
-#include <sys/syscall.h>
-#include <unistd.h>
-
-#include "test/util/save_util.h"
-
-namespace gvisor {
-namespace testing {
-
-void MaybeSave() {
- if (internal::ShouldSave()) {
- int orig_errno = errno;
- syscall(SYS_create_module, nullptr, 0);
- errno = orig_errno;
- }
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/util/save_util_other.cc b/test/util/save_util_other.cc
deleted file mode 100644
index 1aca663b7..000000000
--- a/test/util/save_util_other.cc
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-namespace gvisor {
-namespace testing {
-
-void MaybeSave() {
- // Saving is never available in a non-linux environment.
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/util/signal_util.cc b/test/util/signal_util.cc
deleted file mode 100644
index 26738864f..000000000
--- a/test/util/signal_util.cc
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/util/signal_util.h"
-
-#include <signal.h>
-#include <ostream>
-
-#include "gtest/gtest.h"
-#include "test/util/cleanup.h"
-#include "test/util/posix_error.h"
-#include "test/util/test_util.h"
-
-namespace {
-
-struct Range {
- int start;
- int end;
-};
-
-// Format a Range as "start-end" or "start" for single value Ranges.
-static ::std::ostream& operator<<(::std::ostream& os, const Range& range) {
- if (range.end > range.start) {
- return os << range.start << '-' << range.end;
- }
-
- return os << range.start;
-}
-
-} // namespace
-
-// Format a sigset_t as a comma separated list of numeric ranges.
-// Empty sigset: []
-// Full sigset: [1-31,34-64]
-::std::ostream& operator<<(::std::ostream& os, const sigset_t& sigset) {
- const char* delim = "";
- Range range = {0, 0};
-
- os << '[';
-
- for (int sig = 1; sig <= gvisor::testing::kMaxSignal; ++sig) {
- if (sigismember(&sigset, sig)) {
- if (range.start) {
- range.end = sig;
- } else {
- range.start = sig;
- range.end = sig;
- }
- } else if (range.start) {
- os << delim << range;
- delim = ",";
- range.start = 0;
- range.end = 0;
- }
- }
-
- if (range.start) {
- os << delim << range;
- }
-
- return os << ']';
-}
-
-namespace gvisor {
-namespace testing {
-
-PosixErrorOr<Cleanup> ScopedSigaction(int sig, struct sigaction const& sa) {
- struct sigaction old_sa;
- int rc = sigaction(sig, &sa, &old_sa);
- MaybeSave();
- if (rc < 0) {
- return PosixError(errno, "sigaction failed");
- }
- return Cleanup([sig, old_sa] {
- EXPECT_THAT(sigaction(sig, &old_sa, nullptr), SyscallSucceeds());
- });
-}
-
-PosixErrorOr<Cleanup> ScopedSignalMask(int how, sigset_t const& set) {
- sigset_t old;
- int rc = sigprocmask(how, &set, &old);
- MaybeSave();
- if (rc < 0) {
- return PosixError(errno, "sigprocmask failed");
- }
- return Cleanup([old] {
- EXPECT_THAT(sigprocmask(SIG_SETMASK, &old, nullptr), SyscallSucceeds());
- });
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/util/signal_util.h b/test/util/signal_util.h
deleted file mode 100644
index 7fd2af015..000000000
--- a/test/util/signal_util.h
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_UTIL_SIGNAL_UTIL_H_
-#define GVISOR_TEST_UTIL_SIGNAL_UTIL_H_
-
-#include <signal.h>
-#include <sys/syscall.h>
-#include <unistd.h>
-#include <ostream>
-
-#include "gmock/gmock.h"
-#include "test/util/cleanup.h"
-#include "test/util/posix_error.h"
-
-// Format a sigset_t as a comma separated list of numeric ranges.
-::std::ostream& operator<<(::std::ostream& os, const sigset_t& sigset);
-
-namespace gvisor {
-namespace testing {
-
-// The maximum signal number.
-static constexpr int kMaxSignal = 64;
-
-// Wrapper for the tgkill(2) syscall, which glibc does not provide.
-inline int tgkill(pid_t tgid, pid_t tid, int sig) {
- return syscall(__NR_tgkill, tgid, tid, sig);
-}
-
-// Installs the passed sigaction and returns a cleanup function to restore the
-// previous handler when it goes out of scope.
-PosixErrorOr<Cleanup> ScopedSigaction(int sig, struct sigaction const& sa);
-
-// Updates the signal mask as per sigprocmask(2) and returns a cleanup function
-// to restore the previous signal mask when it goes out of scope.
-PosixErrorOr<Cleanup> ScopedSignalMask(int how, sigset_t const& set);
-
-// ScopedSignalMask variant that creates a mask of the single signal 'sig'.
-inline PosixErrorOr<Cleanup> ScopedSignalMask(int how, int sig) {
- sigset_t set;
- sigemptyset(&set);
- sigaddset(&set, sig);
- return ScopedSignalMask(how, set);
-}
-
-// Asserts equality of two sigset_t values.
-MATCHER_P(EqualsSigset, value, "equals " + ::testing::PrintToString(value)) {
- for (int sig = 1; sig <= kMaxSignal; ++sig) {
- if (sigismember(&arg, sig) != sigismember(&value, sig)) {
- return false;
- }
- }
- return true;
-}
-
-#ifdef __x86_64__
-// Fault can be used to generate a synchronous SIGSEGV.
-//
-// This fault can be fixed up in a handler via fixup, below.
-inline void Fault() {
- // Zero and dereference %ax.
- asm("movabs $0, %%rax\r\n"
- "mov 0(%%rax), %%rax\r\n"
- :
- :
- : "ax");
-}
-
-// FixupFault fixes up a fault generated by fault, above.
-inline void FixupFault(ucontext_t* ctx) {
- // Skip the bad instruction above.
- //
- // The encoding is 0x48 0xab 0x00.
- ctx->uc_mcontext.gregs[REG_RIP] += 3;
-}
-#endif
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_UTIL_SIGNAL_UTIL_H_
diff --git a/test/util/temp_path.cc b/test/util/temp_path.cc
deleted file mode 100644
index 35aacb172..000000000
--- a/test/util/temp_path.cc
+++ /dev/null
@@ -1,163 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/util/temp_path.h"
-
-#include <unistd.h>
-
-#include <atomic>
-#include <cstdlib>
-#include <iostream>
-
-#include "gtest/gtest.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/fs_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-std::atomic<uint64_t> global_temp_file_number = ATOMIC_VAR_INIT(1);
-
-// Return a new temp filename, intended to be unique system-wide.
-//
-// The global file number helps maintain file naming consistency across
-// different runs of a test.
-//
-// The timestamp is necessary because the test infrastructure invokes each
-// test case in a separate process (resetting global_temp_file_number) and
-// potentially in parallel, which allows for races between selecting and using a
-// name.
-std::string NextTempBasename() {
- return absl::StrCat("gvisor_test_temp_", global_temp_file_number++, "_",
- absl::ToUnixNanos(absl::Now()));
-}
-
-void TryDeleteRecursively(std::string const& path) {
- if (!path.empty()) {
- int undeleted_dirs = 0;
- int undeleted_files = 0;
- auto status = RecursivelyDelete(path, &undeleted_dirs, &undeleted_files);
- if (undeleted_dirs || undeleted_files || !status.ok()) {
- std::cerr << path << ": failed to delete " << undeleted_dirs
- << " directories and " << undeleted_files
- << " files: " << status;
- }
- }
-}
-
-} // namespace
-
-constexpr mode_t TempPath::kDefaultFileMode;
-constexpr mode_t TempPath::kDefaultDirMode;
-
-std::string NewTempAbsPathInDir(absl::string_view const dir) {
- return JoinPath(dir, NextTempBasename());
-}
-
-std::string NewTempAbsPath() {
- return NewTempAbsPathInDir(GetAbsoluteTestTmpdir());
-}
-
-std::string NewTempRelPath() { return NextTempBasename(); }
-
-std::string GetAbsoluteTestTmpdir() {
- char* env_tmpdir = getenv("TEST_TMPDIR");
- std::string tmp_dir =
- env_tmpdir != nullptr ? std::string(env_tmpdir) : "/tmp";
-
- return MakeAbsolute(tmp_dir, "").ValueOrDie();
-}
-
-PosixErrorOr<TempPath> TempPath::CreateFileWith(absl::string_view const parent,
- absl::string_view const content,
- mode_t const mode) {
- return CreateIn(parent, [=](absl::string_view path) -> PosixError {
- // CreateWithContents will call open(O_WRONLY) with the given mode. If the
- // mode is not user-writable, save/restore cannot preserve the fd. Hence
- // the little permission dance that's done here.
- auto res = CreateWithContents(path, content, mode | 0200);
- RETURN_IF_ERRNO(res);
-
- return Chmod(path, mode);
- });
-}
-
-PosixErrorOr<TempPath> TempPath::CreateDirWith(absl::string_view const parent,
- mode_t const mode) {
- return CreateIn(parent,
- [=](absl::string_view path) { return Mkdir(path, mode); });
-}
-
-PosixErrorOr<TempPath> TempPath::CreateSymlinkTo(absl::string_view const parent,
- std::string const& dest) {
- return CreateIn(parent, [=](absl::string_view path) {
- int ret = symlink(dest.c_str(), std::string(path).c_str());
- if (ret != 0) {
- return PosixError(errno, "symlink failed");
- }
- return NoError();
- });
-}
-
-PosixErrorOr<TempPath> TempPath::CreateFileIn(absl::string_view const parent) {
- return TempPath::CreateFileWith(parent, absl::string_view(),
- kDefaultFileMode);
-}
-
-PosixErrorOr<TempPath> TempPath::CreateDirIn(absl::string_view const parent) {
- return TempPath::CreateDirWith(parent, kDefaultDirMode);
-}
-
-PosixErrorOr<TempPath> TempPath::CreateFileMode(mode_t mode) {
- return TempPath::CreateFileWith(GetAbsoluteTestTmpdir(), absl::string_view(),
- mode);
-}
-
-PosixErrorOr<TempPath> TempPath::CreateFile() {
- return TempPath::CreateFileIn(GetAbsoluteTestTmpdir());
-}
-
-PosixErrorOr<TempPath> TempPath::CreateDir() {
- return TempPath::CreateDirIn(GetAbsoluteTestTmpdir());
-}
-
-TempPath::~TempPath() { TryDeleteRecursively(path_); }
-
-TempPath::TempPath(TempPath&& orig) { reset(orig.release()); }
-
-TempPath& TempPath::operator=(TempPath&& orig) {
- reset(orig.release());
- return *this;
-}
-
-std::string TempPath::reset(std::string newpath) {
- std::string path = path_;
- TryDeleteRecursively(path_);
- path_ = std::move(newpath);
- return path;
-}
-
-std::string TempPath::release() {
- std::string path = path_;
- path_ = std::string();
- return path;
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/util/temp_path.h b/test/util/temp_path.h
deleted file mode 100644
index 92d669503..000000000
--- a/test/util/temp_path.h
+++ /dev/null
@@ -1,134 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_UTIL_TEMP_PATH_H_
-#define GVISOR_TEST_UTIL_TEMP_PATH_H_
-
-#include <sys/stat.h>
-#include <string>
-#include <utility>
-
-#include "absl/strings/str_cat.h"
-#include "absl/strings/string_view.h"
-#include "test/util/posix_error.h"
-
-namespace gvisor {
-namespace testing {
-
-// Returns an absolute path for a file in `dir` that does not yet exist.
-// Distinct calls to NewTempAbsPathInDir from the same process, even from
-// multiple threads, are guaranteed to return different paths. Distinct calls to
-// NewTempAbsPathInDir from different processes are not synchronized.
-std::string NewTempAbsPathInDir(absl::string_view const dir);
-
-// Like NewTempAbsPathInDir, but the returned path is in the test's temporary
-// directory, as provided by the testing framework.
-std::string NewTempAbsPath();
-
-// Like NewTempAbsPathInDir, but the returned path is relative (to the current
-// working directory).
-std::string NewTempRelPath();
-
-// Returns the absolute path for the test temp dir.
-std::string GetAbsoluteTestTmpdir();
-
-// Represents a temporary file or directory.
-class TempPath {
- public:
- // Default creation mode for files.
- static constexpr mode_t kDefaultFileMode = 0644;
-
- // Default creation mode for directories.
- static constexpr mode_t kDefaultDirMode = 0755;
-
- // Creates a temporary file in directory `parent` with mode `mode` and
- // contents `content`.
- static PosixErrorOr<TempPath> CreateFileWith(absl::string_view parent,
- absl::string_view content,
- mode_t mode);
-
- // Creates an empty temporary subdirectory in directory `parent` with mode
- // `mode`.
- static PosixErrorOr<TempPath> CreateDirWith(absl::string_view parent,
- mode_t mode);
-
- // Creates a temporary symlink in directory `parent` to destination `dest`.
- static PosixErrorOr<TempPath> CreateSymlinkTo(absl::string_view parent,
- std::string const& dest);
-
- // Creates an empty temporary file in directory `parent` with mode
- // kDefaultFileMode.
- static PosixErrorOr<TempPath> CreateFileIn(absl::string_view parent);
-
- // Creates an empty temporary subdirectory in directory `parent` with mode
- // kDefaultDirMode.
- static PosixErrorOr<TempPath> CreateDirIn(absl::string_view parent);
-
- // Creates an empty temporary file in the test's temporary directory with mode
- // `mode`.
- static PosixErrorOr<TempPath> CreateFileMode(mode_t mode);
-
- // Creates an empty temporary file in the test's temporary directory with
- // mode kDefaultFileMode.
- static PosixErrorOr<TempPath> CreateFile();
-
- // Creates an empty temporary subdirectory in the test's temporary directory
- // with mode kDefaultDirMode.
- static PosixErrorOr<TempPath> CreateDir();
-
- // Constructs a TempPath that represents nothing.
- TempPath() = default;
-
- // Constructs a TempPath that represents the given path, which will be deleted
- // when the TempPath is destroyed.
- explicit TempPath(std::string path) : path_(std::move(path)) {}
-
- // Attempts to delete the represented temporary file or directory (in the
- // latter case, also attempts to delete its contents).
- ~TempPath();
-
- // Attempts to delete the represented temporary file or directory, then
- // transfers ownership of the path represented by orig to this TempPath.
- TempPath(TempPath&& orig);
- TempPath& operator=(TempPath&& orig);
-
- // Changes the path this TempPath represents. If the TempPath already
- // represented a path, deletes and returns that path. Otherwise returns the
- // empty string.
- std::string reset(std::string newpath);
- std::string reset() { return reset(""); }
-
- // Forgets and returns the path this TempPath represents. The path is not
- // deleted.
- std::string release();
-
- // Returns the path this TempPath represents.
- std::string path() const { return path_; }
-
- private:
- template <typename F>
- static PosixErrorOr<TempPath> CreateIn(absl::string_view const parent,
- F const& f) {
- std::string path = NewTempAbsPathInDir(parent);
- RETURN_IF_ERRNO(f(path));
- return TempPath(std::move(path));
- }
-
- std::string path_;
-};
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_UTIL_TEMP_PATH_H_
diff --git a/test/util/test_main.cc b/test/util/test_main.cc
deleted file mode 100644
index 5c7ee0064..000000000
--- a/test/util/test_main.cc
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/util/test_util.h"
-
-int main(int argc, char** argv) {
- gvisor::testing::TestInit(&argc, &argv);
- return RUN_ALL_TESTS();
-}
diff --git a/test/util/test_util.cc b/test/util/test_util.cc
deleted file mode 100644
index e42bba04a..000000000
--- a/test/util/test_util.cc
+++ /dev/null
@@ -1,236 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/util/test_util.h"
-
-#include <limits.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/uio.h>
-#include <sys/utsname.h>
-#include <unistd.h>
-
-#include <ctime>
-#include <iostream>
-#include <vector>
-
-#include "absl/base/attributes.h"
-#include "absl/strings/numbers.h"
-#include "absl/strings/str_cat.h"
-#include "absl/strings/str_split.h"
-#include "absl/time/time.h"
-#include "test/util/fs_util.h"
-#include "test/util/posix_error.h"
-
-namespace gvisor {
-namespace testing {
-
-#define TEST_ON_GVISOR "TEST_ON_GVISOR"
-
-bool IsRunningOnGvisor() { return GvisorPlatform() != Platform::kNative; }
-
-Platform GvisorPlatform() {
- // Set by runner.go.
- char* env = getenv(TEST_ON_GVISOR);
- if (!env) {
- return Platform::kNative;
- }
- if (strcmp(env, "ptrace") == 0) {
- return Platform::kPtrace;
- }
- if (strcmp(env, "kvm") == 0) {
- return Platform::kKVM;
- }
- std::cerr << "unknown platform " << env;
- abort();
-}
-
-// Inline cpuid instruction. Preserve %ebx/%rbx register. In PIC compilations
-// %ebx contains the address of the global offset table. %rbx is occasionally
-// used to address stack variables in presence of dynamic allocas.
-#if defined(__x86_64__)
-#define GETCPUID(a, b, c, d, a_inp, c_inp) \
- asm("mov %%rbx, %%rdi\n" \
- "cpuid\n" \
- "xchg %%rdi, %%rbx\n" \
- : "=a"(a), "=D"(b), "=c"(c), "=d"(d) \
- : "a"(a_inp), "2"(c_inp))
-#endif // defined(__x86_64__)
-
-CPUVendor GetCPUVendor() {
- uint32_t eax, ebx, ecx, edx;
- std::string vendor_str;
- // Get vendor string (issue CPUID with eax = 0)
- GETCPUID(eax, ebx, ecx, edx, 0, 0);
- vendor_str.append(reinterpret_cast<char*>(&ebx), 4);
- vendor_str.append(reinterpret_cast<char*>(&edx), 4);
- vendor_str.append(reinterpret_cast<char*>(&ecx), 4);
- if (vendor_str == "GenuineIntel") {
- return CPUVendor::kIntel;
- } else if (vendor_str == "AuthenticAMD") {
- return CPUVendor::kAMD;
- }
- return CPUVendor::kUnknownVendor;
-}
-
-bool operator==(const KernelVersion& first, const KernelVersion& second) {
- return first.major == second.major && first.minor == second.minor &&
- first.micro == second.micro;
-}
-
-PosixErrorOr<KernelVersion> ParseKernelVersion(absl::string_view vers_str) {
- KernelVersion version = {};
- std::vector<std::string> values =
- absl::StrSplit(vers_str, absl::ByAnyChar(".-"));
- if (values.size() == 2) {
- ASSIGN_OR_RETURN_ERRNO(version.major, Atoi<int>(values[0]));
- ASSIGN_OR_RETURN_ERRNO(version.minor, Atoi<int>(values[1]));
- return version;
- } else if (values.size() >= 3) {
- ASSIGN_OR_RETURN_ERRNO(version.major, Atoi<int>(values[0]));
- ASSIGN_OR_RETURN_ERRNO(version.minor, Atoi<int>(values[1]));
- ASSIGN_OR_RETURN_ERRNO(version.micro, Atoi<int>(values[2]));
- return version;
- }
- return PosixError(EINVAL, absl::StrCat("Unknown kernel release: ", vers_str));
-}
-
-PosixErrorOr<KernelVersion> GetKernelVersion() {
- utsname buf;
- RETURN_ERROR_IF_SYSCALL_FAIL(uname(&buf));
- return ParseKernelVersion(buf.release);
-}
-
-void SetupGvisorDeathTest() {
-}
-
-std::string CPUSetToString(const cpu_set_t& set, size_t cpus) {
- std::string str = "cpuset[";
- for (unsigned int n = 0; n < cpus; n++) {
- if (CPU_ISSET(n, &set)) {
- if (n != 0) {
- absl::StrAppend(&str, " ");
- }
- absl::StrAppend(&str, n);
- }
- }
- absl::StrAppend(&str, "]");
- return str;
-}
-
-// An overloaded operator<< makes it easy to dump the value of an OpenFd.
-std::ostream& operator<<(std::ostream& out, OpenFd const& ofd) {
- out << ofd.fd << " -> " << ofd.link;
- return out;
-}
-
-// An overloaded operator<< makes it easy to dump a vector of OpenFDs.
-std::ostream& operator<<(std::ostream& out, std::vector<OpenFd> const& v) {
- for (const auto& ofd : v) {
- out << ofd << std::endl;
- }
- return out;
-}
-
-PosixErrorOr<std::vector<OpenFd>> GetOpenFDs() {
- // Get the results from /proc/self/fd.
- ASSIGN_OR_RETURN_ERRNO(auto dir_list,
- ListDir("/proc/self/fd", /*skipdots=*/true));
-
- std::vector<OpenFd> ret_fds;
- for (const auto& str_fd : dir_list) {
- OpenFd open_fd = {};
- ASSIGN_OR_RETURN_ERRNO(open_fd.fd, Atoi<int>(str_fd));
- std::string path = absl::StrCat("/proc/self/fd/", open_fd.fd);
-
- // Resolve the link.
- char buf[PATH_MAX] = {};
- int ret = readlink(path.c_str(), buf, sizeof(buf));
- if (ret < 0) {
- if (errno == ENOENT) {
- // The FD may have been closed, let's be resilient.
- continue;
- }
-
- return PosixError(
- errno, absl::StrCat("readlink of ", path, " returned errno ", errno));
- }
- open_fd.link = std::string(buf, ret);
- ret_fds.emplace_back(std::move(open_fd));
- }
- return ret_fds;
-}
-
-PosixErrorOr<uint64_t> Links(const std::string& path) {
- struct stat st;
- if (stat(path.c_str(), &st)) {
- return PosixError(errno, absl::StrCat("Failed to stat ", path));
- }
- return static_cast<uint64_t>(st.st_nlink);
-}
-
-void RandomizeBuffer(void* buffer, size_t len) {
- struct timespec ts = {};
- clock_gettime(CLOCK_MONOTONIC, &ts);
- uint32_t seed = static_cast<uint32_t>(ts.tv_nsec);
- char* const buf = static_cast<char*>(buffer);
- for (size_t i = 0; i < len; i++) {
- buf[i] = rand_r(&seed) % 255;
- }
-}
-
-std::vector<std::vector<struct iovec>> GenerateIovecs(uint64_t total_size,
- void* buf,
- size_t buflen) {
- std::vector<std::vector<struct iovec>> result;
- for (uint64_t offset = 0; offset < total_size;) {
- auto& iovec_array = *result.emplace(result.end());
-
- for (; offset < total_size && iovec_array.size() < IOV_MAX;
- offset += buflen) {
- struct iovec iov = {};
- iov.iov_base = buf;
- iov.iov_len = std::min<uint64_t>(total_size - offset, buflen);
- iovec_array.push_back(iov);
- }
- }
-
- return result;
-}
-
-uint64_t Megabytes(uint64_t n) {
- // Overflow check, upper 20 bits in n shouldn't be set.
- TEST_CHECK(!(0xfffff00000000000 & n));
- return n << 20;
-}
-
-bool Equivalent(uint64_t current, uint64_t target, double tolerance) {
- auto abs_diff = target > current ? target - current : current - target;
- return abs_diff <= static_cast<uint64_t>(tolerance * target);
-}
-
-void TestInit(int* argc, char*** argv) {
- ::testing::InitGoogleTest(argc, *argv);
- ::gflags::ParseCommandLineFlags(argc, argv, true);
-
- // Always mask SIGPIPE as it's common and tests aren't expected to handle it.
- struct sigaction sa = {};
- sa.sa_handler = SIG_IGN;
- TEST_CHECK(sigaction(SIGPIPE, &sa, nullptr) == 0);
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/util/test_util.h b/test/util/test_util.h
deleted file mode 100644
index cdbe8bfd1..000000000
--- a/test/util/test_util.h
+++ /dev/null
@@ -1,771 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Utilities for syscall testing.
-//
-// Initialization
-// ==============
-//
-// Prior to calling RUN_ALL_TESTS, all tests must use TestInit(&argc, &argv).
-// See the TestInit function for exact side-effects and semantics.
-//
-// Configuration
-// =============
-//
-// IsRunningOnGvisor returns true if the test is known to be running on gVisor.
-// GvisorPlatform can be used to get more detail:
-//
-// switch (GvisorPlatform()) {
-// case Platform::kNative:
-// case Platform::kGvisor:
-// EXPECT_THAT(mmap(...), SyscallSucceeds());
-// break;
-// case Platform::kPtrace:
-// EXPECT_THAT(mmap(...), SyscallFailsWithErrno(ENOSYS));
-// break;
-// }
-//
-// Matchers
-// ========
-//
-// ElementOf(xs) matches if the matched value is equal to an element of the
-// container xs. Example:
-//
-// // PASS
-// EXPECT_THAT(1, ElementOf({0, 1, 2}));
-//
-// // FAIL
-// // Value of: 3
-// // Expected: one of {0, 1, 2}
-// // Actual: 3
-// EXPECT_THAT(3, ElementOf({0, 1, 2}));
-//
-// SyscallSucceeds() matches if the syscall is successful. A successful syscall
-// is defined by either a return value not equal to -1, or a return value of -1
-// with an errno of 0 (which is a possible successful return for e.g.
-// PTRACE_PEEK). Example:
-//
-// // PASS
-// EXPECT_THAT(open("/dev/null", O_RDONLY), SyscallSucceeds());
-//
-// // FAIL
-// // Value of: open("/", O_RDWR)
-// // Expected: not -1 (success)
-// // Actual: -1 (of type int), with errno 21 (Is a directory)
-// EXPECT_THAT(open("/", O_RDWR), SyscallSucceeds());
-//
-// SyscallSucceedsWithValue(m) matches if the syscall is successful, and the
-// value also matches m. Example:
-//
-// // PASS
-// EXPECT_THAT(read(4, buf, 8192), SyscallSucceedsWithValue(8192));
-//
-// // FAIL
-// // Value of: read(-1, buf, 8192)
-// // Expected: is equal to 8192
-// // Actual: -1 (of type long), with errno 9 (Bad file number)
-// EXPECT_THAT(read(-1, buf, 8192), SyscallSucceedsWithValue(8192));
-//
-// // FAIL
-// // Value of: read(4, buf, 1)
-// // Expected: is > 4096
-// // Actual: 1 (of type long)
-// EXPECT_THAT(read(4, buf, 1), SyscallSucceedsWithValue(Gt(4096)));
-//
-// SyscallFails() matches if the syscall is unsuccessful. An unsuccessful
-// syscall is defined by a return value of -1 with a non-zero errno. Example:
-//
-// // PASS
-// EXPECT_THAT(open("/", O_RDWR), SyscallFails());
-//
-// // FAIL
-// // Value of: open("/dev/null", O_RDONLY)
-// // Expected: -1 (failure)
-// // Actual: 0 (of type int)
-// EXPECT_THAT(open("/dev/null", O_RDONLY), SyscallFails());
-//
-// SyscallFailsWithErrno(m) matches if the syscall is unsuccessful, and errno
-// matches m. Example:
-//
-// // PASS
-// EXPECT_THAT(open("/", O_RDWR), SyscallFailsWithErrno(EISDIR));
-//
-// // PASS
-// EXPECT_THAT(open("/etc/passwd", O_RDWR | O_DIRECTORY),
-// SyscallFailsWithErrno(AnyOf(EACCES, ENOTDIR)));
-//
-// // FAIL
-// // Value of: open("/dev/null", O_RDONLY)
-// // Expected: -1 (failure) with errno 21 (Is a directory)
-// // Actual: 0 (of type int)
-// EXPECT_THAT(open("/dev/null", O_RDONLY), SyscallFailsWithErrno(EISDIR));
-//
-// // FAIL
-// // Value of: open("/", O_RDWR)
-// // Expected: -1 (failure) with errno 22 (Invalid argument)
-// // Actual: -1 (of type int), failure, but with errno 21 (Is a directory)
-// EXPECT_THAT(open("/", O_RDWR), SyscallFailsWithErrno(EINVAL));
-//
-// Because the syscall matchers encode save/restore functionality, their meaning
-// should not be inverted via Not. That is, AnyOf(SyscallSucceedsWithValue(1),
-// SyscallSucceedsWithValue(2)) is permitted, but not
-// Not(SyscallFailsWithErrno(EPERM)).
-//
-// Syscalls
-// ========
-//
-// RetryEINTR wraps a function that returns -1 and sets errno on failure
-// to be automatically retried when EINTR occurs. Example:
-//
-// auto rv = RetryEINTR(waitpid)(pid, &status, 0);
-//
-// ReadFd/WriteFd/PreadFd/PwriteFd are interface-compatible wrappers around the
-// read/write/pread/pwrite syscalls to handle both EINTR and partial
-// reads/writes. Example:
-//
-// EXPECT_THAT(ReadFd(fd, &buf, size), SyscallSucceedsWithValue(size));
-//
-// General Utilities
-// =================
-//
-// ApplyVec(f, xs) returns a vector containing the result of applying function
-// `f` to each value in `xs`.
-//
-// AllBitwiseCombinations takes a variadic number of ranges containing integers
-// and returns a vector containing every integer that can be formed by ORing
-// together exactly one integer from each list. List<T> is an alias for
-// std::initializer_list<T> that makes AllBitwiseCombinations more ergonomic to
-// use with list literals (initializer lists do not otherwise participate in
-// template argument deduction). Example:
-//
-// EXPECT_THAT(
-// AllBitwiseCombinations<int>(
-// List<int>{SOCK_DGRAM, SOCK_STREAM},
-// List<int>{0, SOCK_NONBLOCK}),
-// Contains({SOCK_DGRAM, SOCK_STREAM, SOCK_DGRAM | SOCK_NONBLOCK,
-// SOCK_STREAM | SOCK_NONBLOCK}));
-//
-// VecCat takes a variadic number of containers and returns a vector containing
-// the concatenated contents.
-//
-// VecAppend takes an initial container and a variadic number of containers and
-// appends each to the initial container.
-//
-// RandomizeBuffer will use MTRandom to fill the given buffer with random bytes.
-//
-// GenerateIovecs will return the smallest number of iovec arrays for writing a
-// given total number of bytes to a file, each iovec array size up to IOV_MAX,
-// each iovec in each array pointing to the same buffer.
-
-#ifndef GVISOR_TEST_UTIL_TEST_UTIL_H_
-#define GVISOR_TEST_UTIL_TEST_UTIL_H_
-
-#include <stddef.h>
-#include <stdlib.h>
-#include <sys/uio.h>
-#include <unistd.h>
-
-#include <algorithm>
-#include <cerrno>
-#include <initializer_list>
-#include <iterator>
-#include <string>
-#include <thread> // NOLINT: using std::thread::hardware_concurrency().
-#include <utility>
-#include <vector>
-
-#include <gflags/gflags.h>
-#include "gmock/gmock.h"
-#include "absl/strings/str_cat.h"
-#include "absl/strings/str_format.h"
-#include "absl/strings/string_view.h"
-#include "absl/time/time.h"
-#include "test/util/fs_util.h"
-#include "test/util/logging.h"
-#include "test/util/posix_error.h"
-#include "test/util/save_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// TestInit must be called prior to RUN_ALL_TESTS.
-//
-// This parses all arguments and adjusts argc and argv appropriately.
-//
-// TestInit may create background threads.
-void TestInit(int* argc, char*** argv);
-
-// SKIP_IF may be used to skip a test case.
-//
-// These cases are still emitted, but a SKIPPED line will appear.
-#define SKIP_IF(expr) \
- do { \
- if (expr) GTEST_SKIP() << #expr; \
- } while (0)
-
-enum class Platform {
- kNative,
- kKVM,
- kPtrace,
-};
-bool IsRunningOnGvisor();
-Platform GvisorPlatform();
-
-void SetupGvisorDeathTest();
-
-struct KernelVersion {
- int major;
- int minor;
- int micro;
-};
-
-bool operator==(const KernelVersion& first, const KernelVersion& second);
-
-PosixErrorOr<KernelVersion> ParseKernelVersion(absl::string_view vers_string);
-PosixErrorOr<KernelVersion> GetKernelVersion();
-
-static const size_t kPageSize = sysconf(_SC_PAGESIZE);
-
-enum class CPUVendor { kIntel, kAMD, kUnknownVendor };
-
-CPUVendor GetCPUVendor();
-
-inline int NumCPUs() { return std::thread::hardware_concurrency(); }
-
-// Converts cpu_set_t to a std::string for easy examination.
-std::string CPUSetToString(const cpu_set_t& set, size_t cpus = CPU_SETSIZE);
-
-struct OpenFd {
- // fd is the open file descriptor number.
- int fd = -1;
-
- // link is the resolution of the symbolic link.
- std::string link;
-};
-
-// Make it easier to log OpenFds to error streams.
-std::ostream& operator<<(std::ostream& out, std::vector<OpenFd> const& v);
-std::ostream& operator<<(std::ostream& out, OpenFd const& ofd);
-
-// Gets a detailed list of open fds for this process.
-PosixErrorOr<std::vector<OpenFd>> GetOpenFDs();
-
-// Returns the number of hard links to a path.
-PosixErrorOr<uint64_t> Links(const std::string& path);
-
-namespace internal {
-
-template <typename Container>
-class ElementOfMatcher {
- public:
- explicit ElementOfMatcher(Container container)
- : container_(::std::move(container)) {}
-
- template <typename T>
- bool MatchAndExplain(T const& rv,
- ::testing::MatchResultListener* const listener) const {
- using std::count;
- return count(container_.begin(), container_.end(), rv) != 0;
- }
-
- void DescribeTo(::std::ostream* const os) const {
- *os << "one of {";
- char const* sep = "";
- for (auto const& elem : container_) {
- *os << sep << elem;
- sep = ", ";
- }
- *os << "}";
- }
-
- void DescribeNegationTo(::std::ostream* const os) const {
- *os << "none of {";
- char const* sep = "";
- for (auto const& elem : container_) {
- *os << sep << elem;
- sep = ", ";
- }
- *os << "}";
- }
-
- private:
- Container const container_;
-};
-
-template <typename E>
-class SyscallSuccessMatcher {
- public:
- explicit SyscallSuccessMatcher(E expected)
- : expected_(::std::move(expected)) {}
-
- template <typename T>
- operator ::testing::Matcher<T>() const {
- // E is one of three things:
- // - T, or a type losslessly and implicitly convertible to T.
- // - A monomorphic Matcher<T>.
- // - A polymorphic matcher.
- // SafeMatcherCast handles any of the above correctly.
- //
- // Similarly, gMock will invoke this conversion operator to obtain a
- // monomorphic matcher (this is how polymorphic matchers are implemented).
- return ::testing::MakeMatcher(
- new Impl<T>(::testing::SafeMatcherCast<T>(expected_)));
- }
-
- private:
- template <typename T>
- class Impl : public ::testing::MatcherInterface<T> {
- public:
- explicit Impl(::testing::Matcher<T> matcher)
- : matcher_(::std::move(matcher)) {}
-
- bool MatchAndExplain(
- T const& rv,
- ::testing::MatchResultListener* const listener) const override {
- if (rv == static_cast<decltype(rv)>(-1) && errno != 0) {
- *listener << "with errno " << PosixError(errno);
- return false;
- }
- bool match = matcher_.MatchAndExplain(rv, listener);
- if (match) {
- MaybeSave();
- }
- return match;
- }
-
- void DescribeTo(::std::ostream* const os) const override {
- matcher_.DescribeTo(os);
- }
-
- void DescribeNegationTo(::std::ostream* const os) const override {
- matcher_.DescribeNegationTo(os);
- }
-
- private:
- ::testing::Matcher<T> matcher_;
- };
-
- private:
- E expected_;
-};
-
-// A polymorphic matcher equivalent to ::testing::internal::AnyMatcher, except
-// not in namespace ::testing::internal, and describing SyscallSucceeds()'s
-// match constraints (which are enforced by SyscallSuccessMatcher::Impl).
-class AnySuccessValueMatcher {
- public:
- template <typename T>
- operator ::testing::Matcher<T>() const {
- return ::testing::MakeMatcher(new Impl<T>());
- }
-
- private:
- template <typename T>
- class Impl : public ::testing::MatcherInterface<T> {
- public:
- bool MatchAndExplain(
- T const& rv,
- ::testing::MatchResultListener* const listener) const override {
- return true;
- }
-
- void DescribeTo(::std::ostream* const os) const override {
- *os << "not -1 (success)";
- }
-
- void DescribeNegationTo(::std::ostream* const os) const override {
- *os << "-1 (failure)";
- }
- };
-};
-
-class SyscallFailureMatcher {
- public:
- explicit SyscallFailureMatcher(::testing::Matcher<int> errno_matcher)
- : errno_matcher_(std::move(errno_matcher)) {}
-
- template <typename T>
- bool MatchAndExplain(T const& rv,
- ::testing::MatchResultListener* const listener) const {
- if (rv != static_cast<decltype(rv)>(-1)) {
- return false;
- }
- int actual_errno = errno;
- *listener << "with errno " << PosixError(actual_errno);
- bool match = errno_matcher_.MatchAndExplain(actual_errno, listener);
- if (match) {
- MaybeSave();
- }
- return match;
- }
-
- void DescribeTo(::std::ostream* const os) const {
- *os << "-1 (failure), with errno ";
- errno_matcher_.DescribeTo(os);
- }
-
- void DescribeNegationTo(::std::ostream* const os) const {
- *os << "not -1 (success), with errno ";
- errno_matcher_.DescribeNegationTo(os);
- }
-
- private:
- ::testing::Matcher<int> errno_matcher_;
-};
-
-class SpecificErrnoMatcher : public ::testing::MatcherInterface<int> {
- public:
- explicit SpecificErrnoMatcher(int const expected) : expected_(expected) {}
-
- bool MatchAndExplain(
- int const actual_errno,
- ::testing::MatchResultListener* const listener) const override {
- return actual_errno == expected_;
- }
-
- void DescribeTo(::std::ostream* const os) const override {
- *os << PosixError(expected_);
- }
-
- void DescribeNegationTo(::std::ostream* const os) const override {
- *os << "not " << PosixError(expected_);
- }
-
- private:
- int const expected_;
-};
-
-inline ::testing::Matcher<int> SpecificErrno(int const expected) {
- return ::testing::MakeMatcher(new SpecificErrnoMatcher(expected));
-}
-
-} // namespace internal
-
-template <typename Container>
-inline ::testing::PolymorphicMatcher<internal::ElementOfMatcher<Container>>
-ElementOf(Container container) {
- return ::testing::MakePolymorphicMatcher(
- internal::ElementOfMatcher<Container>(::std::move(container)));
-}
-
-template <typename T>
-inline ::testing::PolymorphicMatcher<
- internal::ElementOfMatcher<::std::vector<T>>>
-ElementOf(::std::initializer_list<T> elems) {
- return ::testing::MakePolymorphicMatcher(
- internal::ElementOfMatcher<::std::vector<T>>(::std::vector<T>(elems)));
-}
-
-template <typename E>
-inline internal::SyscallSuccessMatcher<E> SyscallSucceedsWithValue(E expected) {
- return internal::SyscallSuccessMatcher<E>(::std::move(expected));
-}
-
-inline internal::SyscallSuccessMatcher<internal::AnySuccessValueMatcher>
-SyscallSucceeds() {
- return SyscallSucceedsWithValue(
- ::gvisor::testing::internal::AnySuccessValueMatcher());
-}
-
-inline ::testing::PolymorphicMatcher<internal::SyscallFailureMatcher>
-SyscallFailsWithErrno(::testing::Matcher<int> expected) {
- return ::testing::MakePolymorphicMatcher(
- internal::SyscallFailureMatcher(::std::move(expected)));
-}
-
-// Overload taking an int so that SyscallFailsWithErrno(<specific errno>) uses
-// internal::SpecificErrno (which stringifies the errno) rather than
-// ::testing::Eq (which doesn't).
-inline ::testing::PolymorphicMatcher<internal::SyscallFailureMatcher>
-SyscallFailsWithErrno(int const expected) {
- return SyscallFailsWithErrno(internal::SpecificErrno(expected));
-}
-
-inline ::testing::PolymorphicMatcher<internal::SyscallFailureMatcher>
-SyscallFails() {
- return SyscallFailsWithErrno(::testing::Gt(0));
-}
-
-// As of GCC 7.2, -Wall => -Wc++17-compat => -Wnoexcept-type generates an
-// irrelevant, non-actionable warning about ABI compatibility when
-// RetryEINTRImpl is constructed with a noexcept function, such as glibc's
-// syscall(). See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80985.
-#if defined(__GNUC__) && !defined(__clang__) && \
- (__GNUC__ > 7 || (__GNUC__ == 7 && __GNUC_MINOR__ >= 2))
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wnoexcept-type"
-#endif
-
-namespace internal {
-
-template <typename F>
-struct RetryEINTRImpl {
- F const f;
-
- explicit constexpr RetryEINTRImpl(F f) : f(std::move(f)) {}
-
- template <typename... Args>
- auto operator()(Args&&... args) const
- -> decltype(f(std::forward<Args>(args)...)) {
- while (true) {
- errno = 0;
- auto const ret = f(std::forward<Args>(args)...);
- if (ret != -1 || errno != EINTR) {
- return ret;
- }
- }
- }
-};
-
-} // namespace internal
-
-template <typename F>
-constexpr internal::RetryEINTRImpl<F> RetryEINTR(F&& f) {
- return internal::RetryEINTRImpl<F>(std::forward<F>(f));
-}
-
-#if defined(__GNUC__) && !defined(__clang__) && \
- (__GNUC__ > 7 || (__GNUC__ == 7 && __GNUC_MINOR__ >= 2))
-#pragma GCC diagnostic pop
-#endif
-
-namespace internal {
-
-template <typename F>
-ssize_t ApplyFileIoSyscall(F const& f, size_t const count) {
- size_t completed = 0;
- // `do ... while` because some callers actually want to make a syscall with a
- // count of 0.
- do {
- auto const cur = RetryEINTR(f)(completed);
- if (cur < 0) {
- return cur;
- } else if (cur == 0) {
- break;
- }
- completed += cur;
- } while (completed < count);
- return completed;
-}
-
-} // namespace internal
-
-inline ssize_t ReadFd(int fd, void* buf, size_t count) {
- return internal::ApplyFileIoSyscall(
- [&](size_t completed) {
- return read(fd, static_cast<char*>(buf) + completed, count - completed);
- },
- count);
-}
-
-inline ssize_t WriteFd(int fd, void const* buf, size_t count) {
- return internal::ApplyFileIoSyscall(
- [&](size_t completed) {
- return write(fd, static_cast<char const*>(buf) + completed,
- count - completed);
- },
- count);
-}
-
-inline ssize_t PreadFd(int fd, void* buf, size_t count, off_t offset) {
- return internal::ApplyFileIoSyscall(
- [&](size_t completed) {
- return pread(fd, static_cast<char*>(buf) + completed, count - completed,
- offset + completed);
- },
- count);
-}
-
-inline ssize_t PwriteFd(int fd, void const* buf, size_t count, off_t offset) {
- return internal::ApplyFileIoSyscall(
- [&](size_t completed) {
- return pwrite(fd, static_cast<char const*>(buf) + completed,
- count - completed, offset + completed);
- },
- count);
-}
-
-template <typename T>
-using List = std::initializer_list<T>;
-
-namespace internal {
-
-template <typename T>
-void AppendAllBitwiseCombinations(std::vector<T>* combinations, T current) {
- combinations->push_back(current);
-}
-
-template <typename T, typename Arg, typename... Args>
-void AppendAllBitwiseCombinations(std::vector<T>* combinations, T current,
- Arg&& next, Args&&... rest) {
- for (auto const option : next) {
- AppendAllBitwiseCombinations(combinations, current | option, rest...);
- }
-}
-
-inline size_t CombinedSize(size_t accum) { return accum; }
-
-template <typename T, typename... Args>
-size_t CombinedSize(size_t accum, T const& x, Args&&... xs) {
- return CombinedSize(accum + x.size(), std::forward<Args>(xs)...);
-}
-
-// Base case: no more containers, so do nothing.
-template <typename T>
-void DoMoveExtendContainer(T* c) {}
-
-// Append each container next to c.
-template <typename T, typename U, typename... Args>
-void DoMoveExtendContainer(T* c, U&& next, Args&&... rest) {
- std::move(std::begin(next), std::end(next), std::back_inserter(*c));
- DoMoveExtendContainer(c, std::forward<Args>(rest)...);
-}
-
-} // namespace internal
-
-template <typename T = int>
-std::vector<T> AllBitwiseCombinations() {
- return std::vector<T>();
-}
-
-template <typename T = int, typename... Args>
-std::vector<T> AllBitwiseCombinations(Args&&... args) {
- std::vector<T> combinations;
- internal::AppendAllBitwiseCombinations(&combinations, 0, args...);
- return combinations;
-}
-
-template <typename T, typename U, typename F>
-std::vector<T> ApplyVec(F const& f, std::vector<U> const& us) {
- std::vector<T> vec;
- vec.reserve(us.size());
- for (auto const& u : us) {
- vec.push_back(f(u));
- }
- return vec;
-}
-
-template <typename T, typename U>
-std::vector<T> ApplyVecToVec(std::vector<std::function<T(U)>> const& fs,
- std::vector<U> const& us) {
- std::vector<T> vec;
- vec.reserve(us.size() * fs.size());
- for (auto const& f : fs) {
- for (auto const& u : us) {
- vec.push_back(f(u));
- }
- }
- return vec;
-}
-
-// Moves all elements from the containers `args` to the end of `c`.
-template <typename T, typename... Args>
-void VecAppend(T* c, Args&&... args) {
- c->reserve(internal::CombinedSize(c->size(), args...));
- internal::DoMoveExtendContainer(c, std::forward<Args>(args)...);
-}
-
-// Returns a vector containing the concatenated contents of the containers
-// `args`.
-template <typename T, typename... Args>
-std::vector<T> VecCat(Args&&... args) {
- std::vector<T> combined;
- VecAppend(&combined, std::forward<Args>(args)...);
- return combined;
-}
-
-#define RETURN_ERROR_IF_SYSCALL_FAIL(syscall) \
- do { \
- if ((syscall) < 0 && errno != 0) { \
- return PosixError(errno, #syscall); \
- } \
- } while (false)
-
-// Fill the given buffer with random bytes.
-void RandomizeBuffer(void* buffer, size_t len);
-
-template <typename T>
-inline PosixErrorOr<T> Atoi(absl::string_view str) {
- T ret;
- if (!absl::SimpleAtoi<T>(str, &ret)) {
- return PosixError(EINVAL, "String not a number.");
- }
- return ret;
-}
-
-inline PosixErrorOr<uint64_t> AtoiBase(absl::string_view str, int base) {
- if (base > 255 || base < 2) {
- return PosixError(EINVAL, "Invalid Base");
- }
-
- uint64_t ret = 0;
- if (!absl::numbers_internal::safe_strtou64_base(str, &ret, base)) {
- return PosixError(EINVAL, "String not a number.");
- }
-
- return ret;
-}
-
-inline PosixErrorOr<double> Atod(absl::string_view str) {
- double ret;
- if (!absl::SimpleAtod(str, &ret)) {
- return PosixError(EINVAL, "String not a double type.");
- }
- return ret;
-}
-
-inline PosixErrorOr<float> Atof(absl::string_view str) {
- float ret;
- if (!absl::SimpleAtof(str, &ret)) {
- return PosixError(EINVAL, "String not a float type.");
- }
- return ret;
-}
-
-// Return the smallest number of iovec arrays that can be used to write
-// "total_bytes" number of bytes, each iovec writing one "buf".
-std::vector<std::vector<struct iovec>> GenerateIovecs(uint64_t total_size,
- void* buf, size_t buflen);
-
-// Returns bytes in 'n' megabytes. Used for readability.
-uint64_t Megabytes(uint64_t n);
-
-// Predicate for checking that a value is within some tolerance of another
-// value. Returns true iff current is in the range [target * (1 - tolerance),
-// target * (1 + tolerance)].
-bool Equivalent(uint64_t current, uint64_t target, double tolerance);
-
-// Matcher wrapping the Equivalent predicate.
-MATCHER_P2(EquivalentWithin, target, tolerance,
- std::string(negation ? "Isn't" : "Is") +
- ::absl::StrFormat(" within %.2f%% of the target of %zd bytes",
- tolerance * 100, target)) {
- if (target == 0) {
- *result_listener << ::absl::StreamFormat("difference of infinity%%");
- } else {
- int64_t delta = static_cast<int64_t>(arg) - static_cast<int64_t>(target);
- double delta_percent =
- static_cast<double>(delta) / static_cast<double>(target) * 100;
- *result_listener << ::absl::StreamFormat("difference of %.2f%%",
- delta_percent);
- }
- return Equivalent(arg, target, tolerance);
-}
-
-void TestInit(int* argc, char*** argv);
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_UTIL_TEST_UTIL_H_
diff --git a/test/util/test_util_test.cc b/test/util/test_util_test.cc
deleted file mode 100644
index b7300d9e5..000000000
--- a/test/util/test_util_test.cc
+++ /dev/null
@@ -1,250 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/util/test_util.h"
-
-#include <errno.h>
-#include <vector>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-
-using ::testing::AnyOf;
-using ::testing::Gt;
-using ::testing::IsEmpty;
-using ::testing::Lt;
-using ::testing::Not;
-using ::testing::TypedEq;
-using ::testing::UnorderedElementsAre;
-using ::testing::UnorderedElementsAreArray;
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-TEST(KernelVersionParsing, ValidateParsing) {
- KernelVersion v = ASSERT_NO_ERRNO_AND_VALUE(
- ParseKernelVersion("4.18.10-1foo2-amd64 baz blah"));
- ASSERT_TRUE(v == KernelVersion({4, 18, 10}));
-
- v = ASSERT_NO_ERRNO_AND_VALUE(ParseKernelVersion("4.18.10-1foo2-amd64"));
- ASSERT_TRUE(v == KernelVersion({4, 18, 10}));
-
- v = ASSERT_NO_ERRNO_AND_VALUE(ParseKernelVersion("4.18.10-14-amd64"));
- ASSERT_TRUE(v == KernelVersion({4, 18, 10}));
-
- v = ASSERT_NO_ERRNO_AND_VALUE(ParseKernelVersion("4.18.10-amd64"));
- ASSERT_TRUE(v == KernelVersion({4, 18, 10}));
-
- v = ASSERT_NO_ERRNO_AND_VALUE(ParseKernelVersion("4.18.10"));
- ASSERT_TRUE(v == KernelVersion({4, 18, 10}));
-
- v = ASSERT_NO_ERRNO_AND_VALUE(ParseKernelVersion("4.0.10"));
- ASSERT_TRUE(v == KernelVersion({4, 0, 10}));
-
- v = ASSERT_NO_ERRNO_AND_VALUE(ParseKernelVersion("4.0"));
- ASSERT_TRUE(v == KernelVersion({4, 0, 0}));
-
- ASSERT_THAT(ParseKernelVersion("4.a"), PosixErrorIs(EINVAL, ::testing::_));
- ASSERT_THAT(ParseKernelVersion("3"), PosixErrorIs(EINVAL, ::testing::_));
- ASSERT_THAT(ParseKernelVersion(""), PosixErrorIs(EINVAL, ::testing::_));
- ASSERT_THAT(ParseKernelVersion("version 3.3.10"),
- PosixErrorIs(EINVAL, ::testing::_));
-}
-
-TEST(MatchersTest, SyscallSucceeds) {
- EXPECT_THAT(0, SyscallSucceeds());
- EXPECT_THAT(0L, SyscallSucceeds());
-
- errno = 0;
- EXPECT_THAT(-1, SyscallSucceeds());
- EXPECT_THAT(-1L, SyscallSucceeds());
-
- errno = ENOMEM;
- EXPECT_THAT(-1, Not(SyscallSucceeds()));
- EXPECT_THAT(-1L, Not(SyscallSucceeds()));
-}
-
-TEST(MatchersTest, SyscallSucceedsWithValue) {
- EXPECT_THAT(0, SyscallSucceedsWithValue(0));
- EXPECT_THAT(1, SyscallSucceedsWithValue(Lt(3)));
- EXPECT_THAT(-1, Not(SyscallSucceedsWithValue(Lt(3))));
- EXPECT_THAT(4, Not(SyscallSucceedsWithValue(Lt(3))));
-
- // Non-int -1
- EXPECT_THAT(-1L, Not(SyscallSucceedsWithValue(0)));
-
- // Non-int, truncates to -1 if converted to int, with expected value
- EXPECT_THAT(0xffffffffL, SyscallSucceedsWithValue(0xffffffffL));
-
- // Non-int, truncates to -1 if converted to int, with monomorphic matcher
- EXPECT_THAT(0xffffffffL,
- SyscallSucceedsWithValue(TypedEq<long>(0xffffffffL)));
-
- // Non-int, truncates to -1 if converted to int, with polymorphic matcher
- EXPECT_THAT(0xffffffffL, SyscallSucceedsWithValue(Gt(1)));
-}
-
-TEST(MatchersTest, SyscallFails) {
- EXPECT_THAT(0, Not(SyscallFails()));
- EXPECT_THAT(0L, Not(SyscallFails()));
-
- errno = 0;
- EXPECT_THAT(-1, Not(SyscallFails()));
- EXPECT_THAT(-1L, Not(SyscallFails()));
-
- errno = ENOMEM;
- EXPECT_THAT(-1, SyscallFails());
- EXPECT_THAT(-1L, SyscallFails());
-}
-
-TEST(MatchersTest, SyscallFailsWithErrno) {
- EXPECT_THAT(0, Not(SyscallFailsWithErrno(EINVAL)));
- EXPECT_THAT(0L, Not(SyscallFailsWithErrno(EINVAL)));
-
- errno = ENOMEM;
- EXPECT_THAT(-1, Not(SyscallFailsWithErrno(EINVAL)));
- EXPECT_THAT(-1L, Not(SyscallFailsWithErrno(EINVAL)));
-
- errno = EINVAL;
- EXPECT_THAT(-1, SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(-1L, SyscallFailsWithErrno(EINVAL));
-
- EXPECT_THAT(-1, SyscallFailsWithErrno(AnyOf(EINVAL, ENOMEM)));
- EXPECT_THAT(-1L, SyscallFailsWithErrno(AnyOf(EINVAL, ENOMEM)));
-
- std::vector<int> expected_errnos({EINVAL, ENOMEM});
- errno = ENOMEM;
- EXPECT_THAT(-1, SyscallFailsWithErrno(ElementOf(expected_errnos)));
- EXPECT_THAT(-1L, SyscallFailsWithErrno(ElementOf(expected_errnos)));
-}
-
-TEST(AllBitwiseCombinationsTest, NoArguments) {
- EXPECT_THAT(AllBitwiseCombinations(), IsEmpty());
-}
-
-TEST(AllBitwiseCombinationsTest, EmptyList) {
- EXPECT_THAT(AllBitwiseCombinations(List<int>{}), IsEmpty());
-}
-
-TEST(AllBitwiseCombinationsTest, SingleElementList) {
- EXPECT_THAT(AllBitwiseCombinations(List<int>{5}), UnorderedElementsAre(5));
-}
-
-TEST(AllBitwiseCombinationsTest, SingleList) {
- EXPECT_THAT(AllBitwiseCombinations(List<int>{0, 1, 2, 4}),
- UnorderedElementsAre(0, 1, 2, 4));
-}
-
-TEST(AllBitwiseCombinationsTest, MultipleLists) {
- EXPECT_THAT(
- AllBitwiseCombinations(List<int>{0, 1, 2, 3}, List<int>{0, 4, 8, 12}),
- UnorderedElementsAreArray(
- {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}));
-}
-
-TEST(RandomizeBuffer, Works) {
- const std::vector<char> original(4096);
- std::vector<char> buffer = original;
- RandomizeBuffer(buffer.data(), buffer.size());
- EXPECT_NE(buffer, original);
-}
-
-// Enable comparison of vectors of iovec arrays for the following test.
-MATCHER_P(IovecsListEq, expected, "") {
- if (arg.size() != expected.size()) {
- *result_listener << "sizes are different (actual: " << arg.size()
- << ", expected: " << expected.size() << ")";
- return false;
- }
-
- for (uint64_t i = 0; i < expected.size(); ++i) {
- const std::vector<struct iovec>& actual_iovecs = arg[i];
- const std::vector<struct iovec>& expected_iovecs = expected[i];
- if (actual_iovecs.size() != expected_iovecs.size()) {
- *result_listener << "iovec array size at position " << i
- << " is different (actual: " << actual_iovecs.size()
- << ", expected: " << expected_iovecs.size() << ")";
- return false;
- }
-
- for (uint64_t j = 0; j < expected_iovecs.size(); ++j) {
- const struct iovec& actual_iov = actual_iovecs[j];
- const struct iovec& expected_iov = expected_iovecs[j];
- if (actual_iov.iov_base != expected_iov.iov_base) {
- *result_listener << "iovecs in array " << i << " at position " << j
- << " are different (expected iov_base: "
- << expected_iov.iov_base
- << ", got: " << actual_iov.iov_base << ")";
- return false;
- }
- if (actual_iov.iov_len != expected_iov.iov_len) {
- *result_listener << "iovecs in array " << i << " at position " << j
- << " are different (expected iov_len: "
- << expected_iov.iov_len
- << ", got: " << actual_iov.iov_len << ")";
- return false;
- }
- }
- }
-
- return true;
-}
-
-// Verify empty iovec list generation.
-TEST(GenerateIovecs, EmptyList) {
- std::vector<char> buffer = {'a', 'b', 'c'};
-
- EXPECT_THAT(GenerateIovecs(0, buffer.data(), buffer.size()),
- IovecsListEq(std::vector<std::vector<struct iovec>>()));
-}
-
-// Verify generating a single array of only one, partial, iovec.
-TEST(GenerateIovecs, OneArray) {
- std::vector<char> buffer = {'a', 'b', 'c'};
-
- std::vector<std::vector<struct iovec>> expected;
- struct iovec iov = {};
- iov.iov_base = buffer.data();
- iov.iov_len = 2;
- expected.push_back(std::vector<struct iovec>({iov}));
- EXPECT_THAT(GenerateIovecs(2, buffer.data(), buffer.size()),
- IovecsListEq(expected));
-}
-
-// Verify that it wraps around after IOV_MAX iovecs.
-TEST(GenerateIovecs, WrapsAtIovMax) {
- std::vector<char> buffer = {'a', 'b', 'c'};
-
- std::vector<std::vector<struct iovec>> expected;
- struct iovec iov = {};
- iov.iov_base = buffer.data();
- iov.iov_len = buffer.size();
- expected.emplace_back();
- for (int i = 0; i < IOV_MAX; ++i) {
- expected[0].push_back(iov);
- }
- iov.iov_len = 1;
- expected.push_back(std::vector<struct iovec>({iov}));
-
- EXPECT_THAT(
- GenerateIovecs(IOV_MAX * buffer.size() + 1, buffer.data(), buffer.size()),
- IovecsListEq(expected));
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/util/thread_util.h b/test/util/thread_util.h
deleted file mode 100644
index 923c4fe10..000000000
--- a/test/util/thread_util.h
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_UTIL_THREAD_UTIL_H_
-#define GVISOR_TEST_UTIL_THREAD_UTIL_H_
-
-#include <pthread.h>
-#ifdef __linux__
-#include <sys/syscall.h>
-#endif
-#include <unistd.h>
-
-#include <functional>
-#include <utility>
-
-#include "test/util/logging.h"
-
-namespace gvisor {
-namespace testing {
-
-// ScopedThread is a minimal wrapper around pthreads.
-//
-// This is used in lieu of more complex mechanisms because it provides very
-// predictable behavior (no messing with timers, etc.) The thread will
-// automatically joined when it is destructed (goes out of scope), but can be
-// joined manually as well.
-class ScopedThread {
- public:
- // Constructs a thread that executes f exactly once.
- explicit ScopedThread(std::function<void*()> f) : f_(std::move(f)) {
- CreateThread();
- }
-
- explicit ScopedThread(const std::function<void()>& f) {
- f_ = [=] {
- f();
- return nullptr;
- };
- CreateThread();
- }
-
- ScopedThread(const ScopedThread& other) = delete;
- ScopedThread& operator=(const ScopedThread& other) = delete;
-
- // Joins the thread.
- ~ScopedThread() { Join(); }
-
- // Waits until this thread has finished executing. Join is idempotent and may
- // be called multiple times, however Join itself is not thread-safe.
- void* Join() {
- if (!joined_) {
- TEST_PCHECK(pthread_join(pt_, &retval_) == 0);
- joined_ = true;
- }
- return retval_;
- }
-
- private:
- void CreateThread() {
- TEST_PCHECK_MSG(pthread_create(
- &pt_, /* attr = */ nullptr,
- +[](void* arg) -> void* {
- return static_cast<ScopedThread*>(arg)->f_();
- },
- this) == 0,
- "thread creation failed");
- }
-
- std::function<void*()> f_;
- pthread_t pt_;
- bool joined_ = false;
- void* retval_ = nullptr;
-};
-
-#ifdef __linux__
-inline pid_t gettid() { return syscall(SYS_gettid); }
-#endif
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_UTIL_THREAD_UTIL_H_
diff --git a/test/util/time_util.cc b/test/util/time_util.cc
deleted file mode 100644
index 1ddfbfc9c..000000000
--- a/test/util/time_util.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/util/time_util.h"
-
-#include <sys/syscall.h>
-#include <unistd.h>
-
-#include "absl/time/time.h"
-
-namespace gvisor {
-namespace testing {
-
-void SleepSafe(absl::Duration duration) {
- if (duration == absl::ZeroDuration()) {
- return;
- }
-
- struct timespec ts = absl::ToTimespec(duration);
- int ret;
- while (1) {
- ret = syscall(__NR_nanosleep, &ts, &ts);
- if (ret == 0 || (ret <= 0 && errno != EINTR)) {
- break;
- }
- }
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/util/time_util.h b/test/util/time_util.h
deleted file mode 100644
index f3ddc9fde..000000000
--- a/test/util/time_util.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_UTIL_TIME_UTIL_H_
-#define GVISOR_TEST_UTIL_TIME_UTIL_H_
-
-#include "absl/time/time.h"
-
-namespace gvisor {
-namespace testing {
-
-// Sleep for at least the specified duration. Avoids glibc.
-void SleepSafe(absl::Duration duration);
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_UTIL_TIME_UTIL_H_
diff --git a/test/util/timer_util.cc b/test/util/timer_util.cc
deleted file mode 100644
index 43a26b0d3..000000000
--- a/test/util/timer_util.cc
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "test/util/timer_util.h"
-
-namespace gvisor {
-namespace testing {
-
-absl::Time Now(clockid_t id) {
- struct timespec now;
- TEST_PCHECK(clock_gettime(id, &now) == 0);
- return absl::TimeFromTimespec(now);
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/util/timer_util.h b/test/util/timer_util.h
deleted file mode 100644
index 31aea4fc6..000000000
--- a/test/util/timer_util.h
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef GVISOR_TEST_UTIL_TIMER_UTIL_H_
-#define GVISOR_TEST_UTIL_TIMER_UTIL_H_
-
-#include <errno.h>
-#include <sys/time.h>
-
-#include <functional>
-
-#include "gmock/gmock.h"
-#include "absl/time/time.h"
-#include "test/util/cleanup.h"
-#include "test/util/logging.h"
-#include "test/util/posix_error.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// MonotonicTimer is a simple timer that uses a monotonic clock.
-class MonotonicTimer {
- public:
- MonotonicTimer() {}
- absl::Duration Duration() {
- struct timespec ts;
- TEST_CHECK(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
- return absl::TimeFromTimespec(ts) - start_;
- }
-
- void Start() {
- struct timespec ts;
- TEST_CHECK(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
- start_ = absl::TimeFromTimespec(ts);
- }
-
- protected:
- absl::Time start_;
-};
-
-// Sets the given itimer and returns a cleanup function that restores the
-// previous itimer when it goes out of scope.
-inline PosixErrorOr<Cleanup> ScopedItimer(int which,
- struct itimerval const& new_value) {
- struct itimerval old_value;
- int rc = setitimer(which, &new_value, &old_value);
- MaybeSave();
- if (rc < 0) {
- return PosixError(errno, "setitimer failed");
- }
- return Cleanup(std::function<void(void)>([which, old_value] {
- EXPECT_THAT(setitimer(which, &old_value, nullptr), SyscallSucceeds());
- }));
-}
-
-// Returns the current time.
-absl::Time Now(clockid_t id);
-
-} // namespace testing
-} // namespace gvisor
-
-#endif // GVISOR_TEST_UTIL_TIMER_UTIL_H_
diff --git a/third_party/gvsync/BUILD b/third_party/gvsync/BUILD
deleted file mode 100644
index 8dab51daa..000000000
--- a/third_party/gvsync/BUILD
+++ /dev/null
@@ -1,54 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
-
-package(
- default_visibility = ["//:sandbox"],
- licenses = ["notice"],
-)
-
-exports_files(["LICENSE"])
-
-load("//tools/go_generics:defs.bzl", "go_template")
-
-go_template(
- name = "generic_atomicptr",
- srcs = ["atomicptr_unsafe.go"],
- types = [
- "Value",
- ],
-)
-
-go_template(
- name = "generic_seqatomic",
- srcs = ["seqatomic_unsafe.go"],
- types = [
- "Value",
- ],
- deps = [
- ":sync",
- ],
-)
-
-go_library(
- name = "gvsync",
- srcs = [
- "downgradable_rwmutex_1_12_unsafe.go",
- "downgradable_rwmutex_1_13_unsafe.go",
- "downgradable_rwmutex_unsafe.go",
- "gvsync.go",
- "memmove_unsafe.go",
- "norace_unsafe.go",
- "race_unsafe.go",
- "seqcount.go",
- ],
- importpath = "gvisor.dev/gvisor/third_party/gvsync",
-)
-
-go_test(
- name = "gvsync_test",
- size = "small",
- srcs = [
- "downgradable_rwmutex_test.go",
- "seqcount_test.go",
- ],
- embed = [":gvsync"],
-)
diff --git a/third_party/gvsync/LICENSE b/third_party/gvsync/LICENSE
deleted file mode 100644
index 6a66aea5e..000000000
--- a/third_party/gvsync/LICENSE
+++ /dev/null
@@ -1,27 +0,0 @@
-Copyright (c) 2009 The Go Authors. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
- * Redistributions of source code must retain the above copyright
-notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above
-copyright notice, this list of conditions and the following disclaimer
-in the documentation and/or other materials provided with the
-distribution.
- * Neither the name of Google Inc. nor the names of its
-contributors may be used to endorse or promote products derived from
-this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/third_party/gvsync/README.md b/third_party/gvsync/README.md
deleted file mode 100644
index fcc7e6f44..000000000
--- a/third_party/gvsync/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-This package provides additional synchronization primitives not provided by the
-Go stdlib 'sync' package. It is partially derived from the upstream 'sync'
-package.
diff --git a/third_party/gvsync/atomicptrtest/BUILD b/third_party/gvsync/atomicptrtest/BUILD
deleted file mode 100644
index 6cf69ea91..000000000
--- a/third_party/gvsync/atomicptrtest/BUILD
+++ /dev/null
@@ -1,29 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-
-go_template_instance(
- name = "atomicptr_int",
- out = "atomicptr_int_unsafe.go",
- package = "atomicptr",
- suffix = "Int",
- template = "//third_party/gvsync:generic_atomicptr",
- types = {
- "Value": "int",
- },
-)
-
-go_library(
- name = "atomicptr",
- srcs = ["atomicptr_int_unsafe.go"],
- importpath = "gvisor.dev/gvisor/third_party/gvsync/atomicptr",
-)
-
-go_test(
- name = "atomicptr_test",
- size = "small",
- srcs = ["atomicptr_test.go"],
- embed = [":atomicptr"],
-)
diff --git a/third_party/gvsync/atomicptrtest/atomicptr_test.go b/third_party/gvsync/atomicptrtest/atomicptr_test.go
deleted file mode 100644
index 8fdc5112e..000000000
--- a/third_party/gvsync/atomicptrtest/atomicptr_test.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package atomicptr
-
-import (
- "testing"
-)
-
-func newInt(val int) *int {
- return &val
-}
-
-func TestAtomicPtr(t *testing.T) {
- var p AtomicPtrInt
- if got := p.Load(); got != nil {
- t.Errorf("initial value is %p (%v), wanted nil", got, got)
- }
- want := newInt(42)
- p.Store(want)
- if got := p.Load(); got != want {
- t.Errorf("wrong value: got %p (%v), wanted %p (%v)", got, got, want, want)
- }
- want = newInt(100)
- p.Store(want)
- if got := p.Load(); got != want {
- t.Errorf("wrong value: got %p (%v), wanted %p (%v)", got, got, want, want)
- }
-}
diff --git a/third_party/gvsync/downgradable_rwmutex_test.go b/third_party/gvsync/downgradable_rwmutex_test.go
deleted file mode 100644
index 40c384b8b..000000000
--- a/third_party/gvsync/downgradable_rwmutex_test.go
+++ /dev/null
@@ -1,150 +0,0 @@
-// Copyright 2009 The Go Authors. All rights reserved.
-// Copyright 2019 The gVisor Authors.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// GOMAXPROCS=10 go test
-
-// Copy/pasted from the standard library's sync/rwmutex_test.go, except for the
-// addition of downgradingWriter and the renaming of num_iterations to
-// numIterations to shut up Golint.
-
-package gvsync
-
-import (
- "fmt"
- "runtime"
- "sync/atomic"
- "testing"
-)
-
-func parallelReader(m *DowngradableRWMutex, clocked, cunlock, cdone chan bool) {
- m.RLock()
- clocked <- true
- <-cunlock
- m.RUnlock()
- cdone <- true
-}
-
-func doTestParallelReaders(numReaders, gomaxprocs int) {
- runtime.GOMAXPROCS(gomaxprocs)
- var m DowngradableRWMutex
- clocked := make(chan bool)
- cunlock := make(chan bool)
- cdone := make(chan bool)
- for i := 0; i < numReaders; i++ {
- go parallelReader(&m, clocked, cunlock, cdone)
- }
- // Wait for all parallel RLock()s to succeed.
- for i := 0; i < numReaders; i++ {
- <-clocked
- }
- for i := 0; i < numReaders; i++ {
- cunlock <- true
- }
- // Wait for the goroutines to finish.
- for i := 0; i < numReaders; i++ {
- <-cdone
- }
-}
-
-func TestParallelReaders(t *testing.T) {
- defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(-1))
- doTestParallelReaders(1, 4)
- doTestParallelReaders(3, 4)
- doTestParallelReaders(4, 2)
-}
-
-func reader(rwm *DowngradableRWMutex, numIterations int, activity *int32, cdone chan bool) {
- for i := 0; i < numIterations; i++ {
- rwm.RLock()
- n := atomic.AddInt32(activity, 1)
- if n < 1 || n >= 10000 {
- panic(fmt.Sprintf("wlock(%d)\n", n))
- }
- for i := 0; i < 100; i++ {
- }
- atomic.AddInt32(activity, -1)
- rwm.RUnlock()
- }
- cdone <- true
-}
-
-func writer(rwm *DowngradableRWMutex, numIterations int, activity *int32, cdone chan bool) {
- for i := 0; i < numIterations; i++ {
- rwm.Lock()
- n := atomic.AddInt32(activity, 10000)
- if n != 10000 {
- panic(fmt.Sprintf("wlock(%d)\n", n))
- }
- for i := 0; i < 100; i++ {
- }
- atomic.AddInt32(activity, -10000)
- rwm.Unlock()
- }
- cdone <- true
-}
-
-func downgradingWriter(rwm *DowngradableRWMutex, numIterations int, activity *int32, cdone chan bool) {
- for i := 0; i < numIterations; i++ {
- rwm.Lock()
- n := atomic.AddInt32(activity, 10000)
- if n != 10000 {
- panic(fmt.Sprintf("wlock(%d)\n", n))
- }
- for i := 0; i < 100; i++ {
- }
- atomic.AddInt32(activity, -10000)
- rwm.DowngradeLock()
- n = atomic.AddInt32(activity, 1)
- if n < 1 || n >= 10000 {
- panic(fmt.Sprintf("wlock(%d)\n", n))
- }
- for i := 0; i < 100; i++ {
- }
- n = atomic.AddInt32(activity, -1)
- rwm.RUnlock()
- }
- cdone <- true
-}
-
-func HammerDowngradableRWMutex(gomaxprocs, numReaders, numIterations int) {
- runtime.GOMAXPROCS(gomaxprocs)
- // Number of active readers + 10000 * number of active writers.
- var activity int32
- var rwm DowngradableRWMutex
- cdone := make(chan bool)
- go writer(&rwm, numIterations, &activity, cdone)
- go downgradingWriter(&rwm, numIterations, &activity, cdone)
- var i int
- for i = 0; i < numReaders/2; i++ {
- go reader(&rwm, numIterations, &activity, cdone)
- }
- go writer(&rwm, numIterations, &activity, cdone)
- go downgradingWriter(&rwm, numIterations, &activity, cdone)
- for ; i < numReaders; i++ {
- go reader(&rwm, numIterations, &activity, cdone)
- }
- // Wait for the 4 writers and all readers to finish.
- for i := 0; i < 4+numReaders; i++ {
- <-cdone
- }
-}
-
-func TestDowngradableRWMutex(t *testing.T) {
- defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(-1))
- n := 1000
- if testing.Short() {
- n = 5
- }
- HammerDowngradableRWMutex(1, 1, n)
- HammerDowngradableRWMutex(1, 3, n)
- HammerDowngradableRWMutex(1, 10, n)
- HammerDowngradableRWMutex(4, 1, n)
- HammerDowngradableRWMutex(4, 3, n)
- HammerDowngradableRWMutex(4, 10, n)
- HammerDowngradableRWMutex(10, 1, n)
- HammerDowngradableRWMutex(10, 3, n)
- HammerDowngradableRWMutex(10, 10, n)
- HammerDowngradableRWMutex(10, 5, n)
-}
diff --git a/third_party/gvsync/seqatomictest/BUILD b/third_party/gvsync/seqatomictest/BUILD
deleted file mode 100644
index 9e87e0bc5..000000000
--- a/third_party/gvsync/seqatomictest/BUILD
+++ /dev/null
@@ -1,35 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-
-go_template_instance(
- name = "seqatomic_int",
- out = "seqatomic_int_unsafe.go",
- package = "seqatomic",
- suffix = "Int",
- template = "//third_party/gvsync:generic_seqatomic",
- types = {
- "Value": "int",
- },
-)
-
-go_library(
- name = "seqatomic",
- srcs = ["seqatomic_int_unsafe.go"],
- importpath = "gvisor.dev/gvisor/third_party/gvsync/seqatomic",
- deps = [
- "//third_party/gvsync",
- ],
-)
-
-go_test(
- name = "seqatomic_test",
- size = "small",
- srcs = ["seqatomic_test.go"],
- embed = [":seqatomic"],
- deps = [
- "//third_party/gvsync",
- ],
-)
diff --git a/third_party/gvsync/seqatomictest/seqatomic_test.go b/third_party/gvsync/seqatomictest/seqatomic_test.go
deleted file mode 100644
index a5447f589..000000000
--- a/third_party/gvsync/seqatomictest/seqatomic_test.go
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package seqatomic
-
-import (
- "sync/atomic"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/third_party/gvsync"
-)
-
-func TestSeqAtomicLoadUncontended(t *testing.T) {
- var seq gvsync.SeqCount
- const want = 1
- data := want
- if got := SeqAtomicLoadInt(&seq, &data); got != want {
- t.Errorf("SeqAtomicLoadInt: got %v, wanted %v", got, want)
- }
-}
-
-func TestSeqAtomicLoadAfterWrite(t *testing.T) {
- var seq gvsync.SeqCount
- var data int
- const want = 1
- seq.BeginWrite()
- data = want
- seq.EndWrite()
- if got := SeqAtomicLoadInt(&seq, &data); got != want {
- t.Errorf("SeqAtomicLoadInt: got %v, wanted %v", got, want)
- }
-}
-
-func TestSeqAtomicLoadDuringWrite(t *testing.T) {
- var seq gvsync.SeqCount
- var data int
- const want = 1
- seq.BeginWrite()
- go func() {
- time.Sleep(time.Second)
- data = want
- seq.EndWrite()
- }()
- if got := SeqAtomicLoadInt(&seq, &data); got != want {
- t.Errorf("SeqAtomicLoadInt: got %v, wanted %v", got, want)
- }
-}
-
-func TestSeqAtomicTryLoadUncontended(t *testing.T) {
- var seq gvsync.SeqCount
- const want = 1
- data := want
- epoch := seq.BeginRead()
- if got, ok := SeqAtomicTryLoadInt(&seq, epoch, &data); !ok || got != want {
- t.Errorf("SeqAtomicTryLoadInt: got (%v, %v), wanted (%v, true)", got, ok, want)
- }
-}
-
-func TestSeqAtomicTryLoadDuringWrite(t *testing.T) {
- var seq gvsync.SeqCount
- var data int
- epoch := seq.BeginRead()
- seq.BeginWrite()
- if got, ok := SeqAtomicTryLoadInt(&seq, epoch, &data); ok {
- t.Errorf("SeqAtomicTryLoadInt: got (%v, true), wanted (_, false)", got)
- }
- seq.EndWrite()
-}
-
-func TestSeqAtomicTryLoadAfterWrite(t *testing.T) {
- var seq gvsync.SeqCount
- var data int
- epoch := seq.BeginRead()
- seq.BeginWrite()
- seq.EndWrite()
- if got, ok := SeqAtomicTryLoadInt(&seq, epoch, &data); ok {
- t.Errorf("SeqAtomicTryLoadInt: got (%v, true), wanted (_, false)", got)
- }
-}
-
-func BenchmarkSeqAtomicLoadIntUncontended(b *testing.B) {
- var seq gvsync.SeqCount
- const want = 42
- data := want
- b.RunParallel(func(pb *testing.PB) {
- for pb.Next() {
- if got := SeqAtomicLoadInt(&seq, &data); got != want {
- b.Fatalf("SeqAtomicLoadInt: got %v, wanted %v", got, want)
- }
- }
- })
-}
-
-func BenchmarkSeqAtomicTryLoadIntUncontended(b *testing.B) {
- var seq gvsync.SeqCount
- const want = 42
- data := want
- b.RunParallel(func(pb *testing.PB) {
- epoch := seq.BeginRead()
- for pb.Next() {
- if got, ok := SeqAtomicTryLoadInt(&seq, epoch, &data); !ok || got != want {
- b.Fatalf("SeqAtomicTryLoadInt: got (%v, %v), wanted (%v, true)", got, ok, want)
- }
- }
- })
-}
-
-// For comparison:
-func BenchmarkAtomicValueLoadIntUncontended(b *testing.B) {
- var a atomic.Value
- const want = 42
- a.Store(int(want))
- b.RunParallel(func(pb *testing.PB) {
- for pb.Next() {
- if got := a.Load().(int); got != want {
- b.Fatalf("atomic.Value.Load: got %v, wanted %v", got, want)
- }
- }
- })
-}
diff --git a/third_party/gvsync/seqcount_test.go b/third_party/gvsync/seqcount_test.go
deleted file mode 100644
index 085e574b3..000000000
--- a/third_party/gvsync/seqcount_test.go
+++ /dev/null
@@ -1,153 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package gvsync
-
-import (
- "reflect"
- "testing"
- "time"
-)
-
-func TestSeqCountWriteUncontended(t *testing.T) {
- var seq SeqCount
- seq.BeginWrite()
- seq.EndWrite()
-}
-
-func TestSeqCountReadUncontended(t *testing.T) {
- var seq SeqCount
- epoch := seq.BeginRead()
- if !seq.ReadOk(epoch) {
- t.Errorf("ReadOk: got false, wanted true")
- }
-}
-
-func TestSeqCountBeginReadAfterWrite(t *testing.T) {
- var seq SeqCount
- var data int32
- const want = 1
- seq.BeginWrite()
- data = want
- seq.EndWrite()
- epoch := seq.BeginRead()
- if data != want {
- t.Errorf("Reader: got %v, wanted %v", data, want)
- }
- if !seq.ReadOk(epoch) {
- t.Errorf("ReadOk: got false, wanted true")
- }
-}
-
-func TestSeqCountBeginReadDuringWrite(t *testing.T) {
- var seq SeqCount
- var data int
- const want = 1
- seq.BeginWrite()
- go func() {
- time.Sleep(time.Second)
- data = want
- seq.EndWrite()
- }()
- epoch := seq.BeginRead()
- if data != want {
- t.Errorf("Reader: got %v, wanted %v", data, want)
- }
- if !seq.ReadOk(epoch) {
- t.Errorf("ReadOk: got false, wanted true")
- }
-}
-
-func TestSeqCountReadOkAfterWrite(t *testing.T) {
- var seq SeqCount
- epoch := seq.BeginRead()
- seq.BeginWrite()
- seq.EndWrite()
- if seq.ReadOk(epoch) {
- t.Errorf("ReadOk: got true, wanted false")
- }
-}
-
-func TestSeqCountReadOkDuringWrite(t *testing.T) {
- var seq SeqCount
- epoch := seq.BeginRead()
- seq.BeginWrite()
- if seq.ReadOk(epoch) {
- t.Errorf("ReadOk: got true, wanted false")
- }
- seq.EndWrite()
-}
-
-func BenchmarkSeqCountWriteUncontended(b *testing.B) {
- var seq SeqCount
- for i := 0; i < b.N; i++ {
- seq.BeginWrite()
- seq.EndWrite()
- }
-}
-
-func BenchmarkSeqCountReadUncontended(b *testing.B) {
- var seq SeqCount
- b.RunParallel(func(pb *testing.PB) {
- for pb.Next() {
- epoch := seq.BeginRead()
- if !seq.ReadOk(epoch) {
- b.Fatalf("ReadOk: got false, wanted true")
- }
- }
- })
-}
-
-func TestPointersInType(t *testing.T) {
- for _, test := range []struct {
- name string // used for both test and value name
- val interface{}
- ptrs []string
- }{
- {
- name: "EmptyStruct",
- val: struct{}{},
- },
- {
- name: "Int",
- val: int(0),
- },
- {
- name: "MixedStruct",
- val: struct {
- b bool
- I int
- ExportedPtr *struct{}
- unexportedPtr *struct{}
- arr [2]int
- ptrArr [2]*int
- nestedStruct struct {
- nestedNonptr int
- nestedPtr *int
- }
- structArr [1]struct {
- nonptr int
- ptr *int
- }
- }{},
- ptrs: []string{
- "MixedStruct.ExportedPtr",
- "MixedStruct.unexportedPtr",
- "MixedStruct.ptrArr[]",
- "MixedStruct.nestedStruct.nestedPtr",
- "MixedStruct.structArr[].ptr",
- },
- },
- } {
- t.Run(test.name, func(t *testing.T) {
- typ := reflect.TypeOf(test.val)
- ptrs := PointersInType(typ, test.name)
- t.Logf("Found pointers: %v", ptrs)
- if (len(ptrs) != 0 || len(test.ptrs) != 0) && !reflect.DeepEqual(ptrs, test.ptrs) {
- t.Errorf("Got %v, wanted %v", ptrs, test.ptrs)
- }
- })
- }
-}
diff --git a/tools/checkunsafe/BUILD b/tools/checkunsafe/BUILD
deleted file mode 100644
index d85c56131..000000000
--- a/tools/checkunsafe/BUILD
+++ /dev/null
@@ -1,13 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_tool_library")
-
-package(licenses = ["notice"])
-
-go_tool_library(
- name = "checkunsafe",
- srcs = ["check_unsafe.go"],
- importpath = "checkunsafe",
- visibility = ["//visibility:public"],
- deps = [
- "@org_golang_x_tools//go/analysis:go_tool_library",
- ],
-)
diff --git a/tools/checkunsafe/check_unsafe.go b/tools/checkunsafe/check_unsafe.go
deleted file mode 100644
index 4ccd7cc5a..000000000
--- a/tools/checkunsafe/check_unsafe.go
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package checkunsafe allows unsafe imports only in files named appropriately.
-package checkunsafe
-
-import (
- "fmt"
- "path"
- "strconv"
- "strings"
-
- "golang.org/x/tools/go/analysis"
-)
-
-// Analyzer defines the entrypoint.
-var Analyzer = &analysis.Analyzer{
- Name: "checkunsafe",
- Doc: "allows unsafe use only in specified files",
- Run: run,
-}
-
-func run(pass *analysis.Pass) (interface{}, error) {
- for _, f := range pass.Files {
- for _, imp := range f.Imports {
- // Is this an unsafe import?
- pkg, err := strconv.Unquote(imp.Path.Value)
- if err != nil || pkg != "unsafe" {
- continue
- }
-
- // Extract the filename.
- filename := pass.Fset.File(imp.Pos()).Name()
-
- // Allow files named _unsafe.go or _test.go to opt out.
- if strings.HasSuffix(filename, "_unsafe.go") || strings.HasSuffix(filename, "_test.go") {
- continue
- }
-
- // Throw the error.
- pass.Reportf(imp.Pos(), fmt.Sprintf("package unsafe imported by %s; must end with _unsafe.go", path.Base(filename)))
- }
- }
- return nil, nil
-}
diff --git a/tools/go_branch.sh b/tools/go_branch.sh
deleted file mode 100755
index ddb9b6e7b..000000000
--- a/tools/go_branch.sh
+++ /dev/null
@@ -1,82 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -eo pipefail
-
-# Discovery the package name from the go.mod file.
-declare -r gomod="$(pwd)/go.mod"
-declare -r module=$(cat "${gomod}" | grep -E "^module" | cut -d' ' -f2)
-declare -r gosum="$(pwd)/go.sum"
-
-# Check that gopath has been built.
-declare -r gopath_dir="$(pwd)/bazel-bin/gopath/src/${module}"
-if ! [ -d "${gopath_dir}" ]; then
- echo "No gopath directory found; build the :gopath target." >&2
- exit 1
-fi
-
-# Create a temporary working directory, and ensure that this directory and all
-# subdirectories are cleaned up upon exit.
-declare -r tmp_dir=$(mktemp -d)
-finish() {
- cd # Leave tmp_dir.
- rm -rf "${tmp_dir}"
-}
-trap finish EXIT
-
-# Record the current working commit.
-declare -r head=$(git describe --always)
-
-# We expect to have an existing go branch that we will use as the basis for
-# this commit. That branch may be empty, but it must exist.
-declare -r go_branch=$(git show-ref --hash origin/go)
-
-# Clone the current repository to the temporary directory, and check out the
-# current go_branch directory. We move to the new repository for convenience.
-declare -r repo_orig="$(pwd)"
-declare -r repo_new="${tmp_dir}/repository"
-git clone . "${repo_new}"
-cd "${repo_new}"
-
-# Setup the repository and checkout the branch.
-git config user.email "gvisor-bot@google.com"
-git config user.name "gVisor bot"
-git fetch origin "${go_branch}"
-git checkout -b go "${go_branch}"
-
-# Start working on a merge commit that combines the previous history with the
-# current history. Note that we don't actually want any changes yet.
-#
-# N.B. The git behavior changed at some point and the relevant flag was added
-# to allow for override, so try the only behavior first then pass the flag.
-git merge --no-commit --strategy ours ${head} || \
- git merge --allow-unrelated-histories --no-commit --strategy ours ${head}
-
-# Sync the entire gopath_dir and go.mod.
-rsync --recursive --verbose --delete --exclude .git --exclude README.md -L "${gopath_dir}/" .
-cp "${gomod}" .
-cp "${gosum}" .
-
-# There are a few solitary files that can get left behind due to the way bazel
-# constructs the gopath target. Note that we don't find all Go files here
-# because they may correspond to unused templates, etc.
-cp "${repo_orig}"/runsc/*.go runsc/
-
-# Update the current working set and commit.
-git add . && git commit -m "Merge ${head} (automated)"
-
-# Push the branch back to the original repository.
-git remote add orig "${repo_orig}" && git push -f orig go:go
diff --git a/tools/go_generics/BUILD b/tools/go_generics/BUILD
deleted file mode 100644
index 39318b877..000000000
--- a/tools/go_generics/BUILD
+++ /dev/null
@@ -1,38 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary")
-
-package(licenses = ["notice"])
-
-go_binary(
- name = "go_generics",
- srcs = [
- "generics.go",
- "imports.go",
- "remove.go",
- ],
- visibility = ["//visibility:public"],
- deps = ["//tools/go_generics/globals"],
-)
-
-genrule(
- name = "go_generics_tests",
- srcs = glob(["generics_tests/**"]) + [":go_generics"],
- outs = ["go_generics_tests.tgz"],
- cmd = "tar -czvhf $@ $(SRCS)",
-)
-
-genrule(
- name = "go_generics_test_bundle",
- srcs = [
- ":go_generics_tests.tgz",
- ":go_generics_unittest.sh",
- ],
- outs = ["go_generics_test.sh"],
- cmd = "cat $(location :go_generics_unittest.sh) $(location :go_generics_tests.tgz) > $@",
- executable = True,
-)
-
-sh_test(
- name = "go_generics_test",
- size = "small",
- srcs = ["go_generics_test.sh"],
-)
diff --git a/tools/go_generics/defs.bzl b/tools/go_generics/defs.bzl
deleted file mode 100644
index c5be52ecd..000000000
--- a/tools/go_generics/defs.bzl
+++ /dev/null
@@ -1,140 +0,0 @@
-def _go_template_impl(ctx):
- input = ctx.files.srcs
- output = ctx.outputs.out
-
- args = ["-o=%s" % output.path] + [f.path for f in input]
-
- ctx.actions.run(
- inputs = input,
- outputs = [output],
- mnemonic = "GoGenericsTemplate",
- progress_message = "Building Go template %s" % ctx.label,
- arguments = args,
- executable = ctx.executable._tool,
- )
-
- return struct(
- types = ctx.attr.types,
- opt_types = ctx.attr.opt_types,
- consts = ctx.attr.consts,
- opt_consts = ctx.attr.opt_consts,
- deps = ctx.attr.deps,
- file = output,
- )
-
-"""
-Generates a Go template from a set of Go files.
-
-A Go template is similar to a go library, except that it has certain types that
-can be replaced before usage. For example, one could define a templatized List
-struct, whose elements are of type T, then instantiate that template for
-T=segment, where "segment" is the concrete type.
-
-Args:
- name: the name of the template.
- srcs: the list of source files that comprise the template.
- types: the list of generic types in the template that are required to be specified.
- opt_types: the list of generic types in the template that can but aren't required to be specified.
- consts: the list of constants in the template that are required to be specified.
- opt_consts: the list of constants in the template that can but aren't required to be specified.
- deps: the list of dependencies.
-"""
-go_template = rule(
- implementation = _go_template_impl,
- attrs = {
- "srcs": attr.label_list(mandatory = True, allow_files = True),
- "deps": attr.label_list(allow_files = True),
- "types": attr.string_list(),
- "opt_types": attr.string_list(),
- "consts": attr.string_list(),
- "opt_consts": attr.string_list(),
- "_tool": attr.label(executable = True, cfg = "host", default = Label("//tools/go_generics/go_merge")),
- },
- outputs = {
- "out": "%{name}_template.go",
- },
-)
-
-def _go_template_instance_impl(ctx):
- template = ctx.attr.template
- output = ctx.outputs.out
-
- # Check that all required types are defined.
- for t in template.types:
- if t not in ctx.attr.types:
- fail("Missing value for type %s in %s" % (t, ctx.attr.template.label))
-
- # Check that all defined types are expected by the template.
- for t in ctx.attr.types:
- if (t not in template.types) and (t not in template.opt_types):
- fail("Type %s it not a parameter to %s" % (t, ctx.attr.template.label))
-
- # Check that all required consts are defined.
- for t in template.consts:
- if t not in ctx.attr.consts:
- fail("Missing value for constant %s in %s" % (t, ctx.attr.template.label))
-
- # Check that all defined consts are expected by the template.
- for t in ctx.attr.consts:
- if (t not in template.consts) and (t not in template.opt_consts):
- fail("Const %s it not a parameter to %s" % (t, ctx.attr.template.label))
-
- # Build the argument list.
- args = ["-i=%s" % template.file.path, "-o=%s" % output.path]
- args += ["-p=%s" % ctx.attr.package]
-
- if len(ctx.attr.prefix) > 0:
- args += ["-prefix=%s" % ctx.attr.prefix]
-
- if len(ctx.attr.suffix) > 0:
- args += ["-suffix=%s" % ctx.attr.suffix]
-
- args += [("-t=%s=%s" % (p[0], p[1])) for p in ctx.attr.types.items()]
- args += [("-c=%s=%s" % (p[0], p[1])) for p in ctx.attr.consts.items()]
- args += [("-import=%s=%s" % (p[0], p[1])) for p in ctx.attr.imports.items()]
-
- if ctx.attr.anon:
- args += ["-anon"]
-
- ctx.actions.run(
- inputs = [template.file],
- outputs = [output],
- mnemonic = "GoGenericsInstance",
- progress_message = "Building Go template instance %s" % ctx.label,
- arguments = args,
- executable = ctx.executable._tool,
- )
-
- # TODO: How can we get the dependencies out?
- return struct(
- files = depset([output]),
- )
-
-"""
-Instantiates a Go template by replacing all generic types with concrete ones.
-
-Args:
- name: the name of the template instance.
- template: the label of the template to be instatiated.
- prefix: a prefix to be added to globals in the template.
- suffix: a suffix to be added to global in the template.
- types: the map from generic type names to concrete ones.
- consts: the map from constant names to their values.
- imports: the map from imports used in types/consts to their import paths.
- package: the name of the package the instantiated template will be compiled into.
-"""
-go_template_instance = rule(
- implementation = _go_template_instance_impl,
- attrs = {
- "template": attr.label(mandatory = True, providers = ["types"]),
- "prefix": attr.string(),
- "suffix": attr.string(),
- "types": attr.string_dict(),
- "consts": attr.string_dict(),
- "imports": attr.string_dict(),
- "anon": attr.bool(mandatory = False, default = False),
- "package": attr.string(mandatory = True),
- "out": attr.output(mandatory = True),
- "_tool": attr.label(executable = True, cfg = "host", default = Label("//tools/go_generics")),
- },
-)
diff --git a/tools/go_generics/generics.go b/tools/go_generics/generics.go
deleted file mode 100644
index e9cc2c753..000000000
--- a/tools/go_generics/generics.go
+++ /dev/null
@@ -1,284 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// go_generics reads a Go source file and writes a new version of that file with
-// a few transformations applied to each. Namely:
-//
-// 1. Global types can be explicitly renamed with the -t option. For example,
-// if -t=A=B is passed in, all references to A will be replaced with
-// references to B; a function declaration like:
-//
-// func f(arg *A)
-//
-// would be renamed to:
-//
-// func f(arg *B)
-//
-// 2. Global type definitions and their method sets will be removed when they're
-// being renamed with -t. For example, if -t=A=B is passed in, the following
-// definition and methods that existed in the input file wouldn't exist at
-// all in the output file:
-//
-// type A struct{}
-//
-// func (*A) f() {}
-//
-// 3. All global types, variables, constants and functions (not methods) are
-// prefixed and suffixed based on the option -prefix and -suffix arguments.
-// For example, if -suffix=A is passed in, the following globals:
-//
-// func f()
-// type t struct{}
-//
-// would be renamed to:
-//
-// func fA()
-// type tA struct{}
-//
-// Some special tags are also modified. For example:
-//
-// "state:.(t)"
-//
-// would become:
-//
-// "state:.(tA)"
-//
-// 4. The package is renamed to the value via the -p argument.
-// 5. Value of constants can be modified with -c argument.
-//
-// Note that not just the top-level declarations are renamed, all references to
-// them are also properly renamed as well, taking into account visibility rules
-// and shadowing. For example, if -suffix=A is passed in, the following:
-//
-// var b = 100
-//
-// func f() {
-// g(b)
-// b := 0
-// g(b)
-// }
-//
-// Would be replaced with:
-//
-// var bA = 100
-//
-// func f() {
-// g(bA)
-// b := 0
-// g(b)
-// }
-//
-// Note that the second call to g() kept "b" as an argument because it refers to
-// the local variable "b".
-//
-// Note that go_generics can handle anonymous fields with renamed types if
-// -anon is passed in, however it does not perform strict checking on parameter
-// types that share the same name as the global type and therefore will rename
-// them as well.
-//
-// You can see an example in the tools/go_generics/generics_tests/interface test.
-package main
-
-import (
- "bytes"
- "flag"
- "fmt"
- "go/ast"
- "go/format"
- "go/parser"
- "go/token"
- "io/ioutil"
- "os"
- "regexp"
- "strings"
-
- "gvisor.dev/gvisor/tools/go_generics/globals"
-)
-
-var (
- input = flag.String("i", "", "input `file`")
- output = flag.String("o", "", "output `file`")
- suffix = flag.String("suffix", "", "`suffix` to add to each global symbol")
- prefix = flag.String("prefix", "", "`prefix` to add to each global symbol")
- packageName = flag.String("p", "main", "output package `name`")
- printAST = flag.Bool("ast", false, "prints the AST")
- processAnon = flag.Bool("anon", false, "process anonymous fields")
- types = make(mapValue)
- consts = make(mapValue)
- imports = make(mapValue)
-)
-
-// mapValue implements flag.Value. We use a mapValue flag instead of a regular
-// string flag when we want to allow more than one instance of the flag. For
-// example, we allow several "-t A=B" arguments, and will rename them all.
-type mapValue map[string]string
-
-func (m mapValue) String() string {
- var b bytes.Buffer
- first := true
- for k, v := range m {
- if !first {
- b.WriteRune(',')
- } else {
- first = false
- }
- b.WriteString(k)
- b.WriteRune('=')
- b.WriteString(v)
- }
- return b.String()
-}
-
-func (m mapValue) Set(s string) error {
- sep := strings.Index(s, "=")
- if sep == -1 {
- return fmt.Errorf("missing '=' from '%s'", s)
- }
-
- m[s[:sep]] = s[sep+1:]
-
- return nil
-}
-
-// stateTagRegexp matches against the 'typed' state tags.
-var stateTagRegexp = regexp.MustCompile(`^(.*[^a-z0-9_])state:"\.\(([^\)]*)\)"(.*)$`)
-
-var identifierRegexp = regexp.MustCompile(`^(.*[^a-zA-Z_])([a-zA-Z_][a-zA-Z0-9_]*)(.*)$`)
-
-func main() {
- flag.Usage = func() {
- fmt.Fprintf(os.Stderr, "Usage: %s [options]\n", os.Args[0])
- flag.PrintDefaults()
- }
-
- flag.Var(types, "t", "rename type A to B when `A=B` is passed in. Multiple such mappings are allowed.")
- flag.Var(consts, "c", "reassign constant A to value B when `A=B` is passed in. Multiple such mappings are allowed.")
- flag.Var(imports, "import", "specifies the import libraries to use when types are not local. `name=path` specifies that 'name', used in types as name.type, refers to the package living in 'path'.")
- flag.Parse()
-
- if *input == "" || *output == "" {
- flag.Usage()
- os.Exit(1)
- }
-
- // Parse the input file.
- fset := token.NewFileSet()
- f, err := parser.ParseFile(fset, *input, nil, parser.ParseComments|parser.DeclarationErrors|parser.SpuriousErrors)
- if err != nil {
- fmt.Fprintf(os.Stderr, "%v\n", err)
- os.Exit(1)
- }
-
- // Print the AST if requested.
- if *printAST {
- ast.Print(fset, f)
- }
-
- cmap := ast.NewCommentMap(fset, f, f.Comments)
-
- // Update imports based on what's used in types and consts.
- maps := []mapValue{types, consts}
- importDecl, err := updateImports(maps, imports)
- if err != nil {
- fmt.Fprintf(os.Stderr, "%v\n", err)
- os.Exit(1)
- }
- types = maps[0]
- consts = maps[1]
-
- // Reassign all specified constants.
- for _, decl := range f.Decls {
- d, ok := decl.(*ast.GenDecl)
- if !ok || d.Tok != token.CONST {
- continue
- }
-
- for _, gs := range d.Specs {
- s := gs.(*ast.ValueSpec)
- for i, id := range s.Names {
- if n, ok := consts[id.Name]; ok {
- s.Values[i] = &ast.BasicLit{Value: n}
- }
- }
- }
- }
-
- // Go through all globals and their uses in the AST and rename the types
- // with explicitly provided names, and rename all types, variables,
- // consts and functions with the provided prefix and suffix.
- globals.Visit(fset, f, func(ident *ast.Ident, kind globals.SymKind) {
- if n, ok := types[ident.Name]; ok && kind == globals.KindType {
- ident.Name = n
- } else {
- switch kind {
- case globals.KindType, globals.KindVar, globals.KindConst, globals.KindFunction:
- ident.Name = *prefix + ident.Name + *suffix
- case globals.KindTag:
- // Modify the state tag appropriately.
- if m := stateTagRegexp.FindStringSubmatch(ident.Name); m != nil {
- if t := identifierRegexp.FindStringSubmatch(m[2]); t != nil {
- typeName := *prefix + t[2] + *suffix
- if n, ok := types[t[2]]; ok {
- typeName = n
- }
- ident.Name = m[1] + `state:".(` + t[1] + typeName + t[3] + `)"` + m[3]
- }
- }
- }
- }
- }, *processAnon)
-
- // Remove the definition of all types that are being remapped.
- set := make(typeSet)
- for _, v := range types {
- set[v] = struct{}{}
- }
- removeTypes(set, f)
-
- // Add the new imports, if any, to the top.
- if importDecl != nil {
- newDecls := make([]ast.Decl, 0, len(f.Decls)+1)
- newDecls = append(newDecls, importDecl)
- newDecls = append(newDecls, f.Decls...)
- f.Decls = newDecls
- }
-
- // Update comments to remove the ones potentially associated with the
- // type T that we removed.
- f.Comments = cmap.Filter(f).Comments()
-
- // If there are file (package) comments, delete them.
- if f.Doc != nil {
- for i, cg := range f.Comments {
- if cg == f.Doc {
- f.Comments = append(f.Comments[:i], f.Comments[i+1:]...)
- break
- }
- }
- }
-
- // Write the output file.
- f.Name.Name = *packageName
-
- var buf bytes.Buffer
- if err := format.Node(&buf, fset, f); err != nil {
- fmt.Fprintf(os.Stderr, "%v\n", err)
- os.Exit(1)
- }
-
- if err := ioutil.WriteFile(*output, buf.Bytes(), 0644); err != nil {
- fmt.Fprintf(os.Stderr, "%v\n", err)
- os.Exit(1)
- }
-}
diff --git a/tools/go_generics/generics_tests/all_stmts/input.go b/tools/go_generics/generics_tests/all_stmts/input.go
deleted file mode 100644
index 4791d1ff1..000000000
--- a/tools/go_generics/generics_tests/all_stmts/input.go
+++ /dev/null
@@ -1,290 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tests
-
-import (
- "sync"
-)
-
-type T int
-
-func h(T) {
-}
-
-type s struct {
- a, b int
- c []int
-}
-
-func g(T) *s {
- return &s{}
-}
-
-func f() (T, []int) {
- // Branch.
- goto T
- goto R
-
- // Labeled.
-T:
- _ = T(0)
-
- // Empty.
-R:
- ;
-
- // Assignment with definition.
- a, b, c := T(1), T(2), T(3)
- _, _, _ = a, b, c
-
- // Assignment without definition.
- g(T(0)).a, g(T(1)).b, c = int(T(1)), int(T(2)), T(3)
- _, _, _ = a, b, c
-
- // Block.
- {
- var T T
- T = 0
- _ = T
- }
-
- // Declarations.
- type Type T
- const Const T = 10
- var g1 func(T, int, ...T) (int, T)
- var v T
- var w = T(0)
- {
- var T struct {
- f []T
- }
- _ = T
- }
-
- // Defer.
- defer g1(T(0), 1)
-
- // Expression.
- h(v + w + T(1))
-
- // For statements.
- for i := T(0); i < T(10); i++ {
- var T func(int) T
- v := T(0)
- _ = v
- }
-
- for {
- var T func(int) T
- v := T(0)
- _ = v
- }
-
- // Go.
- go g1(T(0), 1)
-
- // If statements.
- if a != T(1) {
- var T func(int) T
- v := T(0)
- _ = v
- }
-
- if a := T(0); a != T(1) {
- var T func(int) T
- v := T(0)
- _ = v
- }
-
- if a := T(0); a != T(1) {
- var T func(int) T
- v := T(0)
- _ = v
- } else if b := T(0); b != T(1) {
- var T func(int) T
- v := T(0)
- _ = v
- } else if T := T(0); T != 1 {
- T++
- } else {
- T--
- }
-
- if a := T(0); a != T(1) {
- var T func(int) T
- v := T(0)
- _ = v
- } else {
- var T func(int) T
- v := T(0)
- _ = v
- }
-
- // Inc/Dec statements.
- (*(*T)(nil))++
- (*(*T)(nil))--
-
- // Range statements.
- for g(T(0)).a, g(T(1)).b = range g(T(10)).c {
- var d T
- _ = d
- }
-
- for T, b := range g(T(10)).c {
- _ = T
- _ = b
- }
-
- // Select statement.
- {
- var fch func(T) chan int
-
- select {
- case <-fch(T(30)):
- var T T
- T = 0
- _ = T
- default:
- var T T
- T = 0
- _ = T
- case T := <-fch(T(30)):
- T = 0
- _ = T
- case g(T(0)).a = <-fch(T(30)):
- var T T
- T = 0
- _ = T
- case fch(T(30)) <- int(T(0)):
- var T T
- T = 0
- _ = T
- }
- }
-
- // Send statements.
- {
- var ch chan T
- var fch func(T) chan int
-
- ch <- T(0)
- fch(T(1)) <- g(T(10)).a
- }
-
- // Switch statements.
- {
- var a T
- var b int
- switch {
- case a == T(0):
- var T T
- T = 0
- _ = T
- case a < T(0), b < g(T(10)).a:
- var T T
- T = 0
- _ = T
- default:
- var T T
- T = 0
- _ = T
- }
- }
-
- switch T(g(T(10)).a) {
- case T(0):
- var T T
- T = 0
- _ = T
- case T(1), T(g(T(10)).a):
- var T T
- T = 0
- _ = T
- default:
- var T T
- T = 0
- _ = T
- }
-
- switch b := g(T(10)); T(b.a) + T(10) {
- case T(0):
- var T T
- T = 0
- _ = T
- case T(1), T(g(T(10)).a):
- var T T
- T = 0
- _ = T
- default:
- var T T
- T = 0
- _ = T
- }
-
- // Type switch statements.
- {
- var interfaceFunc func(T) interface{}
-
- switch interfaceFunc(T(0)).(type) {
- case *T, T, int:
- var T T
- T = 0
- _ = T
- case sync.Mutex, **T:
- var T T
- T = 0
- _ = T
- default:
- var T T
- T = 0
- _ = T
- }
-
- switch x := interfaceFunc(T(0)).(type) {
- case *T, T, int:
- var T T
- T = 0
- _ = T
- _ = x
- case sync.Mutex, **T:
- var T T
- T = 0
- _ = T
- default:
- var T T
- T = 0
- _ = T
- }
-
- switch t := T(0); x := interfaceFunc(T(0) + t).(type) {
- case *T, T, int:
- var T T
- T = 0
- _ = T
- _ = x
- case sync.Mutex, **T:
- var T T
- T = 0
- _ = T
- default:
- var T T
- T = 0
- _ = T
- }
- }
-
- // Return statement.
- return T(10), g(T(11)).c
-}
diff --git a/tools/go_generics/generics_tests/all_stmts/opts.txt b/tools/go_generics/generics_tests/all_stmts/opts.txt
deleted file mode 100644
index c9d0e09bf..000000000
--- a/tools/go_generics/generics_tests/all_stmts/opts.txt
+++ /dev/null
@@ -1 +0,0 @@
--t=T=Q
diff --git a/tools/go_generics/generics_tests/all_stmts/output/output.go b/tools/go_generics/generics_tests/all_stmts/output/output.go
deleted file mode 100644
index a53d84535..000000000
--- a/tools/go_generics/generics_tests/all_stmts/output/output.go
+++ /dev/null
@@ -1,288 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package main
-
-import (
- "sync"
-)
-
-func h(Q) {
-}
-
-type s struct {
- a, b int
- c []int
-}
-
-func g(Q) *s {
- return &s{}
-}
-
-func f() (Q, []int) {
- // Branch.
- goto T
- goto R
-
- // Labeled.
-T:
- _ = Q(0)
-
- // Empty.
-R:
- ;
-
- // Assignment with definition.
- a, b, c := Q(1), Q(2), Q(3)
- _, _, _ = a, b, c
-
- // Assignment without definition.
- g(Q(0)).a, g(Q(1)).b, c = int(Q(1)), int(Q(2)), Q(3)
- _, _, _ = a, b, c
-
- // Block.
- {
- var T Q
- T = 0
- _ = T
- }
-
- // Declarations.
- type Type Q
- const Const Q = 10
- var g1 func(Q, int, ...Q) (int, Q)
- var v Q
- var w = Q(0)
- {
- var T struct {
- f []Q
- }
- _ = T
- }
-
- // Defer.
- defer g1(Q(0), 1)
-
- // Expression.
- h(v + w + Q(1))
-
- // For statements.
- for i := Q(0); i < Q(10); i++ {
- var T func(int) Q
- v := T(0)
- _ = v
- }
-
- for {
- var T func(int) Q
- v := T(0)
- _ = v
- }
-
- // Go.
- go g1(Q(0), 1)
-
- // If statements.
- if a != Q(1) {
- var T func(int) Q
- v := T(0)
- _ = v
- }
-
- if a := Q(0); a != Q(1) {
- var T func(int) Q
- v := T(0)
- _ = v
- }
-
- if a := Q(0); a != Q(1) {
- var T func(int) Q
- v := T(0)
- _ = v
- } else if b := Q(0); b != Q(1) {
- var T func(int) Q
- v := T(0)
- _ = v
- } else if T := Q(0); T != 1 {
- T++
- } else {
- T--
- }
-
- if a := Q(0); a != Q(1) {
- var T func(int) Q
- v := T(0)
- _ = v
- } else {
- var T func(int) Q
- v := T(0)
- _ = v
- }
-
- // Inc/Dec statements.
- (*(*Q)(nil))++
- (*(*Q)(nil))--
-
- // Range statements.
- for g(Q(0)).a, g(Q(1)).b = range g(Q(10)).c {
- var d Q
- _ = d
- }
-
- for T, b := range g(Q(10)).c {
- _ = T
- _ = b
- }
-
- // Select statement.
- {
- var fch func(Q) chan int
-
- select {
- case <-fch(Q(30)):
- var T Q
- T = 0
- _ = T
- default:
- var T Q
- T = 0
- _ = T
- case T := <-fch(Q(30)):
- T = 0
- _ = T
- case g(Q(0)).a = <-fch(Q(30)):
- var T Q
- T = 0
- _ = T
- case fch(Q(30)) <- int(Q(0)):
- var T Q
- T = 0
- _ = T
- }
- }
-
- // Send statements.
- {
- var ch chan Q
- var fch func(Q) chan int
-
- ch <- Q(0)
- fch(Q(1)) <- g(Q(10)).a
- }
-
- // Switch statements.
- {
- var a Q
- var b int
- switch {
- case a == Q(0):
- var T Q
- T = 0
- _ = T
- case a < Q(0), b < g(Q(10)).a:
- var T Q
- T = 0
- _ = T
- default:
- var T Q
- T = 0
- _ = T
- }
- }
-
- switch Q(g(Q(10)).a) {
- case Q(0):
- var T Q
- T = 0
- _ = T
- case Q(1), Q(g(Q(10)).a):
- var T Q
- T = 0
- _ = T
- default:
- var T Q
- T = 0
- _ = T
- }
-
- switch b := g(Q(10)); Q(b.a) + Q(10) {
- case Q(0):
- var T Q
- T = 0
- _ = T
- case Q(1), Q(g(Q(10)).a):
- var T Q
- T = 0
- _ = T
- default:
- var T Q
- T = 0
- _ = T
- }
-
- // Type switch statements.
- {
- var interfaceFunc func(Q) interface{}
-
- switch interfaceFunc(Q(0)).(type) {
- case *Q, Q, int:
- var T Q
- T = 0
- _ = T
- case sync.Mutex, **Q:
- var T Q
- T = 0
- _ = T
- default:
- var T Q
- T = 0
- _ = T
- }
-
- switch x := interfaceFunc(Q(0)).(type) {
- case *Q, Q, int:
- var T Q
- T = 0
- _ = T
- _ = x
- case sync.Mutex, **Q:
- var T Q
- T = 0
- _ = T
- default:
- var T Q
- T = 0
- _ = T
- }
-
- switch t := Q(0); x := interfaceFunc(Q(0) + t).(type) {
- case *Q, Q, int:
- var T Q
- T = 0
- _ = T
- _ = x
- case sync.Mutex, **Q:
- var T Q
- T = 0
- _ = T
- default:
- var T Q
- T = 0
- _ = T
- }
- }
-
- // Return statement.
- return Q(10), g(Q(11)).c
-}
diff --git a/tools/go_generics/generics_tests/all_types/input.go b/tools/go_generics/generics_tests/all_types/input.go
deleted file mode 100644
index 3575d02ec..000000000
--- a/tools/go_generics/generics_tests/all_types/input.go
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tests
-
-import "./lib"
-
-type T int
-
-type newType struct {
- a T
- b lib.T
- c *T
- d (T)
- e chan T
- f <-chan T
- g chan<- T
- h []T
- i [10]T
- j map[T]T
- k func(T, T) (T, T)
- l interface {
- f(T)
- }
- m struct {
- T
- a T
- }
-}
-
-func f(...T) {
-}
diff --git a/tools/go_generics/generics_tests/all_types/lib/lib.go b/tools/go_generics/generics_tests/all_types/lib/lib.go
deleted file mode 100644
index 988786496..000000000
--- a/tools/go_generics/generics_tests/all_types/lib/lib.go
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package lib
-
-type T int32
diff --git a/tools/go_generics/generics_tests/all_types/opts.txt b/tools/go_generics/generics_tests/all_types/opts.txt
deleted file mode 100644
index c9d0e09bf..000000000
--- a/tools/go_generics/generics_tests/all_types/opts.txt
+++ /dev/null
@@ -1 +0,0 @@
--t=T=Q
diff --git a/tools/go_generics/generics_tests/all_types/output/output.go b/tools/go_generics/generics_tests/all_types/output/output.go
deleted file mode 100644
index 41fd147a1..000000000
--- a/tools/go_generics/generics_tests/all_types/output/output.go
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package main
-
-import "./lib"
-
-type newType struct {
- a Q
- b lib.T
- c *Q
- d (Q)
- e chan Q
- f <-chan Q
- g chan<- Q
- h []Q
- i [10]Q
- j map[Q]Q
- k func(Q, Q) (Q, Q)
- l interface {
- f(Q)
- }
- m struct {
- Q
- a Q
- }
-}
-
-func f(...Q) {
-}
diff --git a/tools/go_generics/generics_tests/anon/input.go b/tools/go_generics/generics_tests/anon/input.go
deleted file mode 100644
index 44086d522..000000000
--- a/tools/go_generics/generics_tests/anon/input.go
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tests
-
-type T interface {
- Apply(T) T
-}
-
-type Foo struct {
- T
- Bar map[string]T `json:"bar,omitempty"`
-}
-
-type Baz struct {
- T someTypeNotT
-}
-
-func (f Foo) GetBar(name string) T {
- b, ok := f.Bar[name]
- if ok {
- b = f.Apply(b)
- } else {
- b = f.T
- }
- return b
-}
-
-func foobar() {
- a := Baz{}
- a.T = 0 // should not be renamed, this is a limitation
-
- b := otherpkg.UnrelatedType{}
- b.T = 0 // should not be renamed, this is a limitation
-}
diff --git a/tools/go_generics/generics_tests/anon/opts.txt b/tools/go_generics/generics_tests/anon/opts.txt
deleted file mode 100644
index a5e9d26de..000000000
--- a/tools/go_generics/generics_tests/anon/opts.txt
+++ /dev/null
@@ -1 +0,0 @@
--t=T=Q -suffix=New -anon
diff --git a/tools/go_generics/generics_tests/anon/output/output.go b/tools/go_generics/generics_tests/anon/output/output.go
deleted file mode 100644
index 160cddf79..000000000
--- a/tools/go_generics/generics_tests/anon/output/output.go
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package main
-
-type FooNew struct {
- Q
- Bar map[string]Q `json:"bar,omitempty"`
-}
-
-type BazNew struct {
- T someTypeNotT
-}
-
-func (f FooNew) GetBar(name string) Q {
- b, ok := f.Bar[name]
- if ok {
- b = f.Apply(b)
- } else {
- b = f.Q
- }
- return b
-}
-
-func foobarNew() {
- a := BazNew{}
- a.Q = 0 // should not be renamed, this is a limitation
-
- b := otherpkg.UnrelatedType{}
- b.Q = 0 // should not be renamed, this is a limitation
-}
diff --git a/tools/go_generics/generics_tests/consts/input.go b/tools/go_generics/generics_tests/consts/input.go
deleted file mode 100644
index 04b95fcc6..000000000
--- a/tools/go_generics/generics_tests/consts/input.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tests
-
-const c1 = 10
-const x, y, z = 100, 200, 300
-const v float32 = 1.0 + 2.0
-const s = "abc"
-const (
- A = 10
- B, C, D = 10, 20, 30
- S = "abc"
- T, U, V string = "abc", "def", "ghi"
-)
diff --git a/tools/go_generics/generics_tests/consts/opts.txt b/tools/go_generics/generics_tests/consts/opts.txt
deleted file mode 100644
index 4fb59dce8..000000000
--- a/tools/go_generics/generics_tests/consts/opts.txt
+++ /dev/null
@@ -1 +0,0 @@
--c=c1=20 -c=z=600 -c=v=3.3 -c=s="def" -c=A=20 -c=C=100 -c=S="def" -c=T="ABC"
diff --git a/tools/go_generics/generics_tests/consts/output/output.go b/tools/go_generics/generics_tests/consts/output/output.go
deleted file mode 100644
index 18d316cc9..000000000
--- a/tools/go_generics/generics_tests/consts/output/output.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package main
-
-const c1 = 20
-const x, y, z = 100, 200, 600
-const v float32 = 3.3
-const s = "def"
-const (
- A = 20
- B, C, D = 10, 100, 30
- S = "def"
- T, U, V string = "ABC", "def", "ghi"
-)
diff --git a/tools/go_generics/generics_tests/imports/input.go b/tools/go_generics/generics_tests/imports/input.go
deleted file mode 100644
index 0f032c2a1..000000000
--- a/tools/go_generics/generics_tests/imports/input.go
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tests
-
-type T int
-
-var global T
-
-const (
- m = 0
- n = 0
-)
diff --git a/tools/go_generics/generics_tests/imports/opts.txt b/tools/go_generics/generics_tests/imports/opts.txt
deleted file mode 100644
index 87324be79..000000000
--- a/tools/go_generics/generics_tests/imports/opts.txt
+++ /dev/null
@@ -1 +0,0 @@
--t=T=sync.Mutex -c=n=math.Uint32 -c=m=math.Uint64 -import=sync=sync -import=math=mymathpath
diff --git a/tools/go_generics/generics_tests/imports/output/output.go b/tools/go_generics/generics_tests/imports/output/output.go
deleted file mode 100644
index 2488ca58c..000000000
--- a/tools/go_generics/generics_tests/imports/output/output.go
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package main
-
-import (
- __generics_imported1 "mymathpath"
- __generics_imported0 "sync"
-)
-
-var global __generics_imported0.Mutex
-
-const (
- m = __generics_imported1.Uint64
- n = __generics_imported1.Uint32
-)
diff --git a/tools/go_generics/generics_tests/remove_typedef/input.go b/tools/go_generics/generics_tests/remove_typedef/input.go
deleted file mode 100644
index cf632bae7..000000000
--- a/tools/go_generics/generics_tests/remove_typedef/input.go
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tests
-
-func f(T) Q {
- return Q{}
-}
-
-type T struct{}
-
-type Q struct{}
-
-func (*T) f() {
-}
-
-func (T) g() {
-}
-
-func (*Q) f(T) T {
- return T{}
-}
-
-func (*Q) g(T) *T {
- return nil
-}
diff --git a/tools/go_generics/generics_tests/remove_typedef/opts.txt b/tools/go_generics/generics_tests/remove_typedef/opts.txt
deleted file mode 100644
index 9c8ecaada..000000000
--- a/tools/go_generics/generics_tests/remove_typedef/opts.txt
+++ /dev/null
@@ -1 +0,0 @@
--t=T=U
diff --git a/tools/go_generics/generics_tests/remove_typedef/output/output.go b/tools/go_generics/generics_tests/remove_typedef/output/output.go
deleted file mode 100644
index d44fd8e1c..000000000
--- a/tools/go_generics/generics_tests/remove_typedef/output/output.go
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package main
-
-func f(U) Q {
- return Q{}
-}
-
-type Q struct{}
-
-func (*Q) f(U) U {
- return U{}
-}
-
-func (*Q) g(U) *U {
- return nil
-}
diff --git a/tools/go_generics/generics_tests/simple/input.go b/tools/go_generics/generics_tests/simple/input.go
deleted file mode 100644
index 2a917f16c..000000000
--- a/tools/go_generics/generics_tests/simple/input.go
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tests
-
-type T int
-
-var global T
-
-func f(_ T, a int) {
-}
-
-func g(a T, b int) {
- var c T
- _ = c
-
- d := (*T)(nil)
- _ = d
-}
-
-type R struct {
- T
- a *T
-}
-
-var (
- Z *T = (*T)(nil)
-)
-
-const (
- X T = (T)(0)
-)
-
-type Y T
diff --git a/tools/go_generics/generics_tests/simple/opts.txt b/tools/go_generics/generics_tests/simple/opts.txt
deleted file mode 100644
index 7832ef66f..000000000
--- a/tools/go_generics/generics_tests/simple/opts.txt
+++ /dev/null
@@ -1 +0,0 @@
--t=T=Q -suffix=New
diff --git a/tools/go_generics/generics_tests/simple/output/output.go b/tools/go_generics/generics_tests/simple/output/output.go
deleted file mode 100644
index 6bfa0b25b..000000000
--- a/tools/go_generics/generics_tests/simple/output/output.go
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package main
-
-var globalNew Q
-
-func fNew(_ Q, a int) {
-}
-
-func gNew(a Q, b int) {
- var c Q
- _ = c
-
- d := (*Q)(nil)
- _ = d
-}
-
-type RNew struct {
- Q
- a *Q
-}
-
-var (
- ZNew *Q = (*Q)(nil)
-)
-
-const (
- XNew Q = (Q)(0)
-)
-
-type YNew Q
diff --git a/tools/go_generics/globals/BUILD b/tools/go_generics/globals/BUILD
deleted file mode 100644
index 74853c7d2..000000000
--- a/tools/go_generics/globals/BUILD
+++ /dev/null
@@ -1,13 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "globals",
- srcs = [
- "globals_visitor.go",
- "scope.go",
- ],
- importpath = "gvisor.dev/gvisor/tools/go_generics/globals",
- visibility = ["//tools/go_generics:__pkg__"],
-)
diff --git a/tools/go_generics/globals/globals_visitor.go b/tools/go_generics/globals/globals_visitor.go
deleted file mode 100644
index 883f21ebe..000000000
--- a/tools/go_generics/globals/globals_visitor.go
+++ /dev/null
@@ -1,597 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package globals provides an AST visitor that calls the visit function for all
-// global identifiers.
-package globals
-
-import (
- "fmt"
-
- "go/ast"
- "go/token"
- "path/filepath"
- "strconv"
-)
-
-// globalsVisitor holds the state used while traversing the nodes of a file in
-// search of globals.
-//
-// The visitor does two passes on the global declarations: the first one adds
-// all globals to the global scope (since Go allows references to globals that
-// haven't been declared yet), and the second one calls f() for the definition
-// and uses of globals found in the first pass.
-//
-// The implementation correctly handles cases when globals are aliased by
-// locals; in such cases, f() is not called.
-type globalsVisitor struct {
- // file is the file whose nodes are being visited.
- file *ast.File
-
- // fset is the file set the file being visited belongs to.
- fset *token.FileSet
-
- // f is the visit function to be called when a global symbol is reached.
- f func(*ast.Ident, SymKind)
-
- // scope is the current scope as nodes are visited.
- scope *scope
-
- // processAnon indicates whether we should process anonymous struct fields.
- // It does not perform strict checking on parameter types that share the same name
- // as the global type and therefore will rename them as well.
- processAnon bool
-}
-
-// unexpected is called when an unexpected node appears in the AST. It dumps
-// the location of the associated token and panics because this should only
-// happen when there is a bug in the traversal code.
-func (v *globalsVisitor) unexpected(p token.Pos) {
- panic(fmt.Sprintf("Unable to parse at %v", v.fset.Position(p)))
-}
-
-// pushScope creates a new scope and pushes it to the top of the scope stack.
-func (v *globalsVisitor) pushScope() {
- v.scope = newScope(v.scope)
-}
-
-// popScope removes the scope created by the last call to pushScope.
-func (v *globalsVisitor) popScope() {
- v.scope = v.scope.outer
-}
-
-// visitType is called when an expression is known to be a type, for example,
-// on the first argument of make(). It visits all children nodes and reports
-// any globals.
-func (v *globalsVisitor) visitType(ge ast.Expr) {
- switch e := ge.(type) {
- case *ast.Ident:
- if s := v.scope.deepLookup(e.Name); s != nil && s.scope.isGlobal() {
- v.f(e, s.kind)
- }
-
- case *ast.SelectorExpr:
- id := GetIdent(e.X)
- if id == nil {
- v.unexpected(e.X.Pos())
- }
-
- case *ast.StarExpr:
- v.visitType(e.X)
- case *ast.ParenExpr:
- v.visitType(e.X)
- case *ast.ChanType:
- v.visitType(e.Value)
- case *ast.Ellipsis:
- v.visitType(e.Elt)
- case *ast.ArrayType:
- v.visitExpr(e.Len)
- v.visitType(e.Elt)
- case *ast.MapType:
- v.visitType(e.Key)
- v.visitType(e.Value)
- case *ast.StructType:
- v.visitFields(e.Fields, KindUnknown)
- case *ast.FuncType:
- v.visitFields(e.Params, KindUnknown)
- v.visitFields(e.Results, KindUnknown)
- case *ast.InterfaceType:
- v.visitFields(e.Methods, KindUnknown)
- default:
- v.unexpected(ge.Pos())
- }
-}
-
-// visitFields visits all fields, and add symbols if kind isn't KindUnknown.
-func (v *globalsVisitor) visitFields(l *ast.FieldList, kind SymKind) {
- if l == nil {
- return
- }
-
- for _, f := range l.List {
- if kind != KindUnknown {
- for _, n := range f.Names {
- v.scope.add(n.Name, kind, n.Pos())
- }
- }
- v.visitType(f.Type)
- if f.Tag != nil {
- tag := ast.NewIdent(f.Tag.Value)
- v.f(tag, KindTag)
- // Replace the tag if updated.
- if tag.Name != f.Tag.Value {
- f.Tag.Value = tag.Name
- }
- }
- }
-}
-
-// visitGenDecl is called when a generic declaration is encountered, for example,
-// on variable, constant and type declarations. It adds all newly defined
-// symbols to the current scope and reports them if the current scope is the
-// global one.
-func (v *globalsVisitor) visitGenDecl(d *ast.GenDecl) {
- switch d.Tok {
- case token.IMPORT:
- case token.TYPE:
- for _, gs := range d.Specs {
- s := gs.(*ast.TypeSpec)
- v.scope.add(s.Name.Name, KindType, s.Name.Pos())
- if v.scope.isGlobal() {
- v.f(s.Name, KindType)
- }
- v.visitType(s.Type)
- }
- case token.CONST, token.VAR:
- kind := KindConst
- if d.Tok == token.VAR {
- kind = KindVar
- }
-
- for _, gs := range d.Specs {
- s := gs.(*ast.ValueSpec)
- if s.Type != nil {
- v.visitType(s.Type)
- }
-
- for _, e := range s.Values {
- v.visitExpr(e)
- }
-
- for _, n := range s.Names {
- if v.scope.isGlobal() {
- v.f(n, kind)
- }
- v.scope.add(n.Name, kind, n.Pos())
- }
- }
- default:
- v.unexpected(d.Pos())
- }
-}
-
-// isViableType determines if the given expression is a viable type expression,
-// that is, if it could be interpreted as a type, for example, sync.Mutex,
-// myType, func(int)int, as opposed to -1, 2 * 2, a + b, etc.
-func (v *globalsVisitor) isViableType(expr ast.Expr) bool {
- switch e := expr.(type) {
- case *ast.Ident:
- // This covers the plain identifier case. When we see it, we
- // have to check if it resolves to a type; if the symbol is not
- // known, we'll claim it's viable as a type.
- s := v.scope.deepLookup(e.Name)
- return s == nil || s.kind == KindType
-
- case *ast.ChanType, *ast.ArrayType, *ast.MapType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.Ellipsis:
- // This covers the following cases:
- // 1. ChanType:
- // chan T
- // <-chan T
- // chan<- T
- // 2. ArrayType:
- // [Expr]T
- // 3. MapType:
- // map[T]U
- // 4. StructType:
- // struct { Fields }
- // 5. FuncType:
- // func(Fields)Returns
- // 6. Interface:
- // interface { Fields }
- // 7. Ellipsis:
- // ...T
- return true
-
- case *ast.SelectorExpr:
- // The only case in which an expression involving a selector can
- // be a type is if it has the following form X.T, where X is an
- // import, and T is a type exported by X.
- //
- // There's no way to know whether T is a type because we don't
- // parse imports. So we just claim that this is a viable type;
- // it doesn't affect the general result because we don't visit
- // imported symbols.
- id := GetIdent(e.X)
- if id == nil {
- return false
- }
-
- s := v.scope.deepLookup(id.Name)
- return s != nil && s.kind == KindImport
-
- case *ast.StarExpr:
- // This covers the *T case. The expression is a viable type if
- // T is.
- return v.isViableType(e.X)
-
- case *ast.ParenExpr:
- // This covers the (T) case. The expression is a viable type if
- // T is.
- return v.isViableType(e.X)
-
- default:
- return false
- }
-}
-
-// visitCallExpr visits a "call expression" which can be either a
-// function/method call (e.g., f(), pkg.f(), obj.f(), etc.) call or a type
-// conversion (e.g., int32(1), (*sync.Mutex)(ptr), etc.).
-func (v *globalsVisitor) visitCallExpr(e *ast.CallExpr) {
- if v.isViableType(e.Fun) {
- v.visitType(e.Fun)
- } else {
- v.visitExpr(e.Fun)
- }
-
- // If the function being called is new or make, the first argument is
- // a type, so it needs to be visited as such.
- first := 0
- if id := GetIdent(e.Fun); id != nil && (id.Name == "make" || id.Name == "new") {
- if len(e.Args) > 0 {
- v.visitType(e.Args[0])
- }
- first = 1
- }
-
- for i := first; i < len(e.Args); i++ {
- v.visitExpr(e.Args[i])
- }
-}
-
-// visitExpr visits all nodes of an expression, and reports any globals that it
-// finds.
-func (v *globalsVisitor) visitExpr(ge ast.Expr) {
- switch e := ge.(type) {
- case nil:
- case *ast.Ident:
- if s := v.scope.deepLookup(e.Name); s != nil && s.scope.isGlobal() {
- v.f(e, s.kind)
- }
-
- case *ast.BasicLit:
- case *ast.CompositeLit:
- v.visitType(e.Type)
- for _, ne := range e.Elts {
- v.visitExpr(ne)
- }
- case *ast.FuncLit:
- v.pushScope()
- v.visitFields(e.Type.Params, KindParameter)
- v.visitFields(e.Type.Results, KindResult)
- v.visitBlockStmt(e.Body)
- v.popScope()
-
- case *ast.BinaryExpr:
- v.visitExpr(e.X)
- v.visitExpr(e.Y)
-
- case *ast.CallExpr:
- v.visitCallExpr(e)
-
- case *ast.IndexExpr:
- v.visitExpr(e.X)
- v.visitExpr(e.Index)
-
- case *ast.KeyValueExpr:
- v.visitExpr(e.Value)
-
- case *ast.ParenExpr:
- v.visitExpr(e.X)
-
- case *ast.SelectorExpr:
- v.visitExpr(e.X)
- if v.processAnon {
- v.visitExpr(e.Sel)
- }
-
- case *ast.SliceExpr:
- v.visitExpr(e.X)
- v.visitExpr(e.Low)
- v.visitExpr(e.High)
- v.visitExpr(e.Max)
-
- case *ast.StarExpr:
- v.visitExpr(e.X)
-
- case *ast.TypeAssertExpr:
- v.visitExpr(e.X)
- if e.Type != nil {
- v.visitType(e.Type)
- }
-
- case *ast.UnaryExpr:
- v.visitExpr(e.X)
-
- default:
- v.unexpected(ge.Pos())
- }
-}
-
-// GetIdent returns the identifier associated with the given expression by
-// removing parentheses if needed.
-func GetIdent(expr ast.Expr) *ast.Ident {
- switch e := expr.(type) {
- case *ast.Ident:
- return e
- case *ast.ParenExpr:
- return GetIdent(e.X)
- default:
- return nil
- }
-}
-
-// visitStmt visits all nodes of a statement, and reports any globals that it
-// finds. It also adds to the current scope new symbols defined/declared.
-func (v *globalsVisitor) visitStmt(gs ast.Stmt) {
- switch s := gs.(type) {
- case nil, *ast.BranchStmt, *ast.EmptyStmt:
- case *ast.AssignStmt:
- for _, e := range s.Rhs {
- v.visitExpr(e)
- }
-
- // We visit the LHS after the RHS because the symbols we'll
- // potentially add to the table aren't meant to be visible to
- // the RHS.
- for _, e := range s.Lhs {
- if s.Tok == token.DEFINE {
- if n := GetIdent(e); n != nil {
- v.scope.add(n.Name, KindVar, n.Pos())
- }
- }
- v.visitExpr(e)
- }
-
- case *ast.BlockStmt:
- v.visitBlockStmt(s)
-
- case *ast.DeclStmt:
- v.visitGenDecl(s.Decl.(*ast.GenDecl))
-
- case *ast.DeferStmt:
- v.visitCallExpr(s.Call)
-
- case *ast.ExprStmt:
- v.visitExpr(s.X)
-
- case *ast.ForStmt:
- v.pushScope()
- v.visitStmt(s.Init)
- v.visitExpr(s.Cond)
- v.visitStmt(s.Post)
- v.visitBlockStmt(s.Body)
- v.popScope()
-
- case *ast.GoStmt:
- v.visitCallExpr(s.Call)
-
- case *ast.IfStmt:
- v.pushScope()
- v.visitStmt(s.Init)
- v.visitExpr(s.Cond)
- v.visitBlockStmt(s.Body)
- v.visitStmt(s.Else)
- v.popScope()
-
- case *ast.IncDecStmt:
- v.visitExpr(s.X)
-
- case *ast.LabeledStmt:
- v.visitStmt(s.Stmt)
-
- case *ast.RangeStmt:
- v.pushScope()
- v.visitExpr(s.X)
- if s.Tok == token.DEFINE {
- if n := GetIdent(s.Key); n != nil {
- v.scope.add(n.Name, KindVar, n.Pos())
- }
-
- if n := GetIdent(s.Value); n != nil {
- v.scope.add(n.Name, KindVar, n.Pos())
- }
- }
- v.visitExpr(s.Key)
- v.visitExpr(s.Value)
- v.visitBlockStmt(s.Body)
- v.popScope()
-
- case *ast.ReturnStmt:
- for _, r := range s.Results {
- v.visitExpr(r)
- }
-
- case *ast.SelectStmt:
- for _, ns := range s.Body.List {
- c := ns.(*ast.CommClause)
-
- v.pushScope()
- v.visitStmt(c.Comm)
- for _, bs := range c.Body {
- v.visitStmt(bs)
- }
- v.popScope()
- }
-
- case *ast.SendStmt:
- v.visitExpr(s.Chan)
- v.visitExpr(s.Value)
-
- case *ast.SwitchStmt:
- v.pushScope()
- v.visitStmt(s.Init)
- v.visitExpr(s.Tag)
- for _, ns := range s.Body.List {
- c := ns.(*ast.CaseClause)
- v.pushScope()
- for _, ce := range c.List {
- v.visitExpr(ce)
- }
- for _, bs := range c.Body {
- v.visitStmt(bs)
- }
- v.popScope()
- }
- v.popScope()
-
- case *ast.TypeSwitchStmt:
- v.pushScope()
- v.visitStmt(s.Init)
- v.visitStmt(s.Assign)
- for _, ns := range s.Body.List {
- c := ns.(*ast.CaseClause)
- v.pushScope()
- for _, ce := range c.List {
- v.visitType(ce)
- }
- for _, bs := range c.Body {
- v.visitStmt(bs)
- }
- v.popScope()
- }
- v.popScope()
-
- default:
- v.unexpected(gs.Pos())
- }
-}
-
-// visitBlockStmt visits all statements in the block, adding symbols to a newly
-// created scope.
-func (v *globalsVisitor) visitBlockStmt(s *ast.BlockStmt) {
- v.pushScope()
- for _, c := range s.List {
- v.visitStmt(c)
- }
- v.popScope()
-}
-
-// visitFuncDecl is called when a function or method declaration is encountered.
-// it creates a new scope for the function [optional] receiver, parameters and
-// results, and visits all children nodes.
-func (v *globalsVisitor) visitFuncDecl(d *ast.FuncDecl) {
- // We don't report methods.
- if d.Recv == nil {
- v.f(d.Name, KindFunction)
- }
-
- v.pushScope()
- v.visitFields(d.Recv, KindReceiver)
- v.visitFields(d.Type.Params, KindParameter)
- v.visitFields(d.Type.Results, KindResult)
- if d.Body != nil {
- v.visitBlockStmt(d.Body)
- }
- v.popScope()
-}
-
-// globalsFromDecl is called in the first, and adds symbols to global scope.
-func (v *globalsVisitor) globalsFromGenDecl(d *ast.GenDecl) {
- switch d.Tok {
- case token.IMPORT:
- for _, gs := range d.Specs {
- s := gs.(*ast.ImportSpec)
- if s.Name == nil {
- str, _ := strconv.Unquote(s.Path.Value)
- v.scope.add(filepath.Base(str), KindImport, s.Path.Pos())
- } else if s.Name.Name != "_" {
- v.scope.add(s.Name.Name, KindImport, s.Name.Pos())
- }
- }
- case token.TYPE:
- for _, gs := range d.Specs {
- s := gs.(*ast.TypeSpec)
- v.scope.add(s.Name.Name, KindType, s.Name.Pos())
- }
- case token.CONST, token.VAR:
- kind := KindConst
- if d.Tok == token.VAR {
- kind = KindVar
- }
-
- for _, s := range d.Specs {
- for _, n := range s.(*ast.ValueSpec).Names {
- v.scope.add(n.Name, kind, n.Pos())
- }
- }
- default:
- v.unexpected(d.Pos())
- }
-}
-
-// visit implements the visiting of globals. It does performs the two passes
-// described in the description of the globalsVisitor struct.
-func (v *globalsVisitor) visit() {
- // Gather all symbols in the global scope. This excludes methods.
- v.pushScope()
- for _, gd := range v.file.Decls {
- switch d := gd.(type) {
- case *ast.GenDecl:
- v.globalsFromGenDecl(d)
- case *ast.FuncDecl:
- if d.Recv == nil {
- v.scope.add(d.Name.Name, KindFunction, d.Name.Pos())
- }
- default:
- v.unexpected(gd.Pos())
- }
- }
-
- // Go through the contents of the declarations.
- for _, gd := range v.file.Decls {
- switch d := gd.(type) {
- case *ast.GenDecl:
- v.visitGenDecl(d)
- case *ast.FuncDecl:
- v.visitFuncDecl(d)
- }
- }
-}
-
-// Visit traverses the provided AST and calls f() for each identifier that
-// refers to global names. The global name must be defined in the file itself.
-//
-// The function f() is allowed to modify the identifier, for example, to rename
-// uses of global references.
-func Visit(fset *token.FileSet, file *ast.File, f func(*ast.Ident, SymKind), processAnon bool) {
- v := globalsVisitor{
- fset: fset,
- file: file,
- f: f,
- processAnon: processAnon,
- }
-
- v.visit()
-}
diff --git a/tools/go_generics/globals/scope.go b/tools/go_generics/globals/scope.go
deleted file mode 100644
index 96c965ea2..000000000
--- a/tools/go_generics/globals/scope.go
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package globals
-
-import (
- "go/token"
-)
-
-// SymKind specifies the kind of a global symbol. For example, a variable, const
-// function, etc.
-type SymKind int
-
-// Constants for different kinds of symbols.
-const (
- KindUnknown SymKind = iota
- KindImport
- KindType
- KindVar
- KindConst
- KindFunction
- KindReceiver
- KindParameter
- KindResult
- KindTag
-)
-
-type symbol struct {
- kind SymKind
- pos token.Pos
- scope *scope
-}
-
-type scope struct {
- outer *scope
- syms map[string]*symbol
-}
-
-func newScope(outer *scope) *scope {
- return &scope{
- outer: outer,
- syms: make(map[string]*symbol),
- }
-}
-
-func (s *scope) isGlobal() bool {
- return s.outer == nil
-}
-
-func (s *scope) lookup(n string) *symbol {
- return s.syms[n]
-}
-
-func (s *scope) deepLookup(n string) *symbol {
- for x := s; x != nil; x = x.outer {
- if sym := x.lookup(n); sym != nil {
- return sym
- }
- }
- return nil
-}
-
-func (s *scope) add(name string, kind SymKind, pos token.Pos) {
- s.syms[name] = &symbol{
- kind: kind,
- pos: pos,
- scope: s,
- }
-}
diff --git a/tools/go_generics/go_generics_unittest.sh b/tools/go_generics/go_generics_unittest.sh
deleted file mode 100755
index 44b22db91..000000000
--- a/tools/go_generics/go_generics_unittest.sh
+++ /dev/null
@@ -1,70 +0,0 @@
-#!/bin/bash
-
-# Copyright 2018 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Bash "safe-mode": Treat command failures as fatal (even those that occur in
-# pipes), and treat unset variables as errors.
-set -eu -o pipefail
-
-# This file will be generated as a self-extracting shell script in order to
-# eliminate the need for any runtime dependencies. The tarball at the end will
-# include the go_generics binary, as well as a subdirectory named
-# generics_tests. See the BUILD file for more information.
-declare -r temp=$(mktemp -d)
-function cleanup() {
- rm -rf "${temp}"
-}
-# trap cleanup EXIT
-
-# Print message in "$1" then exit with status 1.
-function die () {
- echo "$1" 1>&2
- exit 1
-}
-
-# This prints the line number of __BUNDLE__ below, that should be the last line
-# of this script. After that point, the concatenated archive will be the
-# contents.
-declare -r tgz=`awk '/^__BUNDLE__/ {print NR + 1; exit 0; }' $0`
-tail -n+"${tgz}" $0 | tar -xzv -C "${temp}"
-
-# The target for the test.
-declare -r binary="$(find ${temp} -type f -a -name go_generics)"
-declare -r input_dirs="$(find ${temp} -type d -a -name generics_tests)/*"
-
-# Go through all test cases.
-for f in ${input_dirs}; do
- base=$(basename "${f}")
-
- # Run go_generics on the input file.
- opts=$(head -n 1 ${f}/opts.txt)
- out="${f}/output/generated.go"
- expected="${f}/output/output.go"
- ${binary} ${opts} "-i=${f}/input.go" "-o=${out}" || die "go_generics failed for test case \"${base}\""
-
- # Compare the outputs.
- diff ${expected} ${out}
- if [ $? -ne 0 ]; then
- echo "Expected:"
- cat ${expected}
- echo "Actual:"
- cat ${out}
- die "Actual output is different from expected for test \"${base}\""
- fi
-done
-
-echo "PASS"
-exit 0
-__BUNDLE__
diff --git a/tools/go_generics/go_merge/BUILD b/tools/go_generics/go_merge/BUILD
deleted file mode 100644
index 02b09120e..000000000
--- a/tools/go_generics/go_merge/BUILD
+++ /dev/null
@@ -1,9 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary")
-
-package(licenses = ["notice"])
-
-go_binary(
- name = "go_merge",
- srcs = ["main.go"],
- visibility = ["//visibility:public"],
-)
diff --git a/tools/go_generics/go_merge/main.go b/tools/go_generics/go_merge/main.go
deleted file mode 100644
index f6a331123..000000000
--- a/tools/go_generics/go_merge/main.go
+++ /dev/null
@@ -1,139 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package main
-
-import (
- "bytes"
- "flag"
- "fmt"
- "go/ast"
- "go/format"
- "go/parser"
- "go/token"
- "io/ioutil"
- "os"
- "path/filepath"
- "strconv"
-)
-
-var (
- output = flag.String("o", "", "output `file`")
-)
-
-func fatalf(s string, args ...interface{}) {
- fmt.Fprintf(os.Stderr, s, args...)
- os.Exit(1)
-}
-
-func main() {
- flag.Usage = func() {
- fmt.Fprintf(os.Stderr, "Usage: %s [options] <input1> [<input2> ...]\n", os.Args[0])
- flag.PrintDefaults()
- }
-
- flag.Parse()
- if *output == "" || len(flag.Args()) == 0 {
- flag.Usage()
- os.Exit(1)
- }
-
- // Load all files.
- files := make(map[string]*ast.File)
- fset := token.NewFileSet()
- var name string
- for _, fname := range flag.Args() {
- f, err := parser.ParseFile(fset, fname, nil, parser.ParseComments|parser.DeclarationErrors|parser.SpuriousErrors)
- if err != nil {
- fatalf("%v\n", err)
- }
-
- files[fname] = f
- if name == "" {
- name = f.Name.Name
- } else if name != f.Name.Name {
- fatalf("Expected '%s' for package name instead of '%s'.\n", name, f.Name.Name)
- }
- }
-
- // Merge all files into one.
- pkg := &ast.Package{
- Name: name,
- Files: files,
- }
- f := ast.MergePackageFiles(pkg, ast.FilterUnassociatedComments|ast.FilterFuncDuplicates|ast.FilterImportDuplicates)
-
- // Create a new declaration slice with all imports at the top, merging any
- // redundant imports.
- imports := make(map[string]*ast.ImportSpec)
- var anonImports []*ast.ImportSpec
- for _, d := range f.Decls {
- if g, ok := d.(*ast.GenDecl); ok && g.Tok == token.IMPORT {
- for _, s := range g.Specs {
- i := s.(*ast.ImportSpec)
- p, _ := strconv.Unquote(i.Path.Value)
- var n string
- if i.Name == nil {
- n = filepath.Base(p)
- } else {
- n = i.Name.Name
- }
- if n == "_" {
- anonImports = append(anonImports, i)
- } else {
- if i2, ok := imports[n]; ok {
- if first, second := i.Path.Value, i2.Path.Value; first != second {
- fatalf("Conflicting paths for import name '%s': '%s' vs. '%s'\n", n, first, second)
- }
- } else {
- imports[n] = i
- }
- }
- }
- }
- }
- newDecls := make([]ast.Decl, 0, len(f.Decls))
- if l := len(imports) + len(anonImports); l > 0 {
- // Non-NoPos Lparen is needed for Go to recognize more than one spec in
- // ast.GenDecl.Specs.
- d := &ast.GenDecl{
- Tok: token.IMPORT,
- Lparen: token.NoPos + 1,
- Specs: make([]ast.Spec, 0, l),
- }
- for _, i := range imports {
- d.Specs = append(d.Specs, i)
- }
- for _, i := range anonImports {
- d.Specs = append(d.Specs, i)
- }
- newDecls = append(newDecls, d)
- }
- for _, d := range f.Decls {
- if g, ok := d.(*ast.GenDecl); !ok || g.Tok != token.IMPORT {
- newDecls = append(newDecls, d)
- }
- }
- f.Decls = newDecls
-
- // Write the output file.
- var buf bytes.Buffer
- if err := format.Node(&buf, fset, f); err != nil {
- fatalf("%v\n", err)
- }
-
- if err := ioutil.WriteFile(*output, buf.Bytes(), 0644); err != nil {
- fatalf("%v\n", err)
- }
-}
diff --git a/tools/go_generics/imports.go b/tools/go_generics/imports.go
deleted file mode 100644
index 148dc7216..000000000
--- a/tools/go_generics/imports.go
+++ /dev/null
@@ -1,150 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package main
-
-import (
- "bytes"
- "fmt"
- "go/ast"
- "go/format"
- "go/parser"
- "go/token"
- "strconv"
-
- "gvisor.dev/gvisor/tools/go_generics/globals"
-)
-
-type importedPackage struct {
- newName string
- path string
-}
-
-// updateImportIdent modifies the given import identifier with the new name
-// stored in the used map. If the identifier doesn't exist in the used map yet,
-// a new name is generated and inserted into the map.
-func updateImportIdent(orig string, imports mapValue, id *ast.Ident, used map[string]*importedPackage) error {
- importName := id.Name
-
- // If the name is already in the table, just use the new name.
- m := used[importName]
- if m != nil {
- id.Name = m.newName
- return nil
- }
-
- // Create a new entry in the used map.
- path := imports[importName]
- if path == "" {
- return fmt.Errorf("Unknown path to package '%s', used in '%s'", importName, orig)
- }
-
- m = &importedPackage{
- newName: fmt.Sprintf("__generics_imported%d", len(used)),
- path: strconv.Quote(path),
- }
- used[importName] = m
-
- id.Name = m.newName
-
- return nil
-}
-
-// convertExpression creates a new string that is a copy of the input one with
-// all imports references renamed to the names in the "used" map. If the
-// referenced import isn't in "used" yet, a new one is created based on the path
-// in "imports" and stored in "used". For example, if string s is
-// "math.MaxUint32-math.MaxUint16+10", it would be converted to
-// "x.MaxUint32-x.MathUint16+10", where x is a generated name.
-func convertExpression(s string, imports mapValue, used map[string]*importedPackage) (string, error) {
- // Parse the expression in the input string.
- expr, err := parser.ParseExpr(s)
- if err != nil {
- return "", fmt.Errorf("Unable to parse \"%s\": %v", s, err)
- }
-
- // Go through the AST and update references.
- var retErr error
- ast.Inspect(expr, func(n ast.Node) bool {
- switch x := n.(type) {
- case *ast.SelectorExpr:
- if id := globals.GetIdent(x.X); id != nil {
- if err := updateImportIdent(s, imports, id, used); err != nil {
- retErr = err
- }
- return false
- }
- }
- return true
- })
- if retErr != nil {
- return "", retErr
- }
-
- // Convert the modified AST back to a string.
- fset := token.NewFileSet()
- var buf bytes.Buffer
- if err := format.Node(&buf, fset, expr); err != nil {
- return "", err
- }
-
- return string(buf.Bytes()), nil
-}
-
-// updateImports replaces all maps in the input slice with copies where the
-// mapped values have had all references to imported packages renamed to
-// generated names. It also returns an import declaration for all the renamed
-// import packages.
-//
-// For example, if the input maps contains A=math.B and C=math.D, the updated
-// maps will instead contain A=__generics_imported0.B and
-// C=__generics_imported0.C, and the 'import __generics_imported0 "math"' would
-// be returned as the import declaration.
-func updateImports(maps []mapValue, imports mapValue) (ast.Decl, error) {
- importsUsed := make(map[string]*importedPackage)
-
- // Update all maps.
- for i, m := range maps {
- newMap := make(mapValue)
- for n, e := range m {
- updated, err := convertExpression(e, imports, importsUsed)
- if err != nil {
- return nil, err
- }
-
- newMap[n] = updated
- }
- maps[i] = newMap
- }
-
- // Nothing else to do if no imports are used in the expressions.
- if len(importsUsed) == 0 {
- return nil, nil
- }
-
- // Create spec array for each new import.
- specs := make([]ast.Spec, 0, len(importsUsed))
- for _, i := range importsUsed {
- specs = append(specs, &ast.ImportSpec{
- Name: &ast.Ident{Name: i.newName},
- Path: &ast.BasicLit{Value: i.path},
- })
- }
-
- return &ast.GenDecl{
- Tok: token.IMPORT,
- Specs: specs,
- Lparen: token.NoPos + 1,
- }, nil
-}
diff --git a/tools/go_generics/remove.go b/tools/go_generics/remove.go
deleted file mode 100644
index 568a6bbd3..000000000
--- a/tools/go_generics/remove.go
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package main
-
-import (
- "go/ast"
- "go/token"
-)
-
-type typeSet map[string]struct{}
-
-// isTypeOrPointerToType determines if the given AST expression represents a
-// type or a pointer to a type that exists in the provided type set.
-func isTypeOrPointerToType(set typeSet, expr ast.Expr, starCount int) bool {
- switch e := expr.(type) {
- case *ast.Ident:
- _, ok := set[e.Name]
- return ok
- case *ast.StarExpr:
- if starCount > 1 {
- return false
- }
- return isTypeOrPointerToType(set, e.X, starCount+1)
- case *ast.ParenExpr:
- return isTypeOrPointerToType(set, e.X, starCount)
- default:
- return false
- }
-}
-
-// isMethodOf determines if the given function declaration is a method of one
-// of the types in the provided type set. To do that, it checks if the function
-// has a receiver and that its type is either T or *T, where T is a type that
-// exists in the set. This is per the spec:
-//
-// That parameter section must declare a single parameter, the receiver. Its
-// type must be of the form T or *T (possibly using parentheses) where T is a
-// type name. The type denoted by T is called the receiver base type; it must
-// not be a pointer or interface type and it must be declared in the same
-// package as the method.
-func isMethodOf(set typeSet, f *ast.FuncDecl) bool {
- // If the function doesn't have exactly one receiver, then it's
- // definitely not a method.
- if f.Recv == nil || len(f.Recv.List) != 1 {
- return false
- }
-
- return isTypeOrPointerToType(set, f.Recv.List[0].Type, 0)
-}
-
-// removeTypeDefinitions removes the definition of all types contained in the
-// provided type set.
-func removeTypeDefinitions(set typeSet, d *ast.GenDecl) {
- if d.Tok != token.TYPE {
- return
- }
-
- i := 0
- for _, gs := range d.Specs {
- s := gs.(*ast.TypeSpec)
- if _, ok := set[s.Name.Name]; !ok {
- d.Specs[i] = gs
- i++
- }
- }
-
- d.Specs = d.Specs[:i]
-}
-
-// removeTypes removes from the AST the definition of all types and their
-// method sets that are contained in the provided type set.
-func removeTypes(set typeSet, f *ast.File) {
- // Go through the top-level declarations.
- i := 0
- for _, decl := range f.Decls {
- keep := true
- switch d := decl.(type) {
- case *ast.GenDecl:
- countBefore := len(d.Specs)
- removeTypeDefinitions(set, d)
- keep = countBefore == 0 || len(d.Specs) > 0
- case *ast.FuncDecl:
- keep = !isMethodOf(set, d)
- }
-
- if keep {
- f.Decls[i] = decl
- i++
- }
- }
-
- f.Decls = f.Decls[:i]
-}
diff --git a/tools/go_generics/rules_tests/BUILD b/tools/go_generics/rules_tests/BUILD
deleted file mode 100644
index a6f8cdd3c..000000000
--- a/tools/go_generics/rules_tests/BUILD
+++ /dev/null
@@ -1,44 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_generics:defs.bzl", "go_template", "go_template_instance")
-
-go_template_instance(
- name = "instance",
- out = "instance_test.go",
- consts = {
- "n": "20",
- "m": "\"test\"",
- "o": "math.MaxUint64",
- },
- imports = {
- "math": "math",
- },
- package = "template_test",
- template = ":test_template",
- types = {
- "t": "int",
- },
-)
-
-go_template(
- name = "test_template",
- srcs = [
- "template.go",
- ],
- opt_consts = [
- "n",
- "m",
- "o",
- ],
- opt_types = ["t"],
-)
-
-go_test(
- name = "template_test",
- srcs = [
- "instance_test.go",
- "template_test.go",
- ],
-)
diff --git a/tools/go_generics/rules_tests/template.go b/tools/go_generics/rules_tests/template.go
deleted file mode 100644
index aace61da1..000000000
--- a/tools/go_generics/rules_tests/template.go
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package template
-
-type t float
-
-const (
- n t = 10.1
- m = "abc"
- o = 0
-)
-
-func max(a, b t) t {
- if a > b {
- return a
- }
- return b
-}
-
-func add(a t) t {
- return a + n
-}
-
-func getName() string {
- return m
-}
-
-func getMax() uint64 {
- return o
-}
diff --git a/tools/go_generics/rules_tests/template_test.go b/tools/go_generics/rules_tests/template_test.go
deleted file mode 100644
index b2a3446ef..000000000
--- a/tools/go_generics/rules_tests/template_test.go
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package template_test
-
-import (
- "math"
- "testing"
-)
-
-func TestMax(t *testing.T) {
- var a int = max(10, 20)
- if a != 20 {
- t.Errorf("Bad result of max, got %v, want %v", a, 20)
- }
-}
-
-func TestIntConst(t *testing.T) {
- var a int = add(10)
- if a != 30 {
- t.Errorf("Bad result of add, got %v, want %v", a, 30)
- }
-}
-
-func TestStrConst(t *testing.T) {
- v := getName()
- if v != "test" {
- t.Errorf("Bad name, got %v, want %v", v, "test")
- }
-}
-
-func TestImport(t *testing.T) {
- v := getMax()
- if v != math.MaxUint64 {
- t.Errorf("Bad max value, got %v, want %v", v, uint64(math.MaxUint64))
- }
-}
diff --git a/tools/go_marshal/BUILD b/tools/go_marshal/BUILD
deleted file mode 100644
index c862b277c..000000000
--- a/tools/go_marshal/BUILD
+++ /dev/null
@@ -1,14 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary")
-
-package(licenses = ["notice"])
-
-go_binary(
- name = "go_marshal",
- srcs = ["main.go"],
- visibility = [
- "//:sandbox",
- ],
- deps = [
- "//tools/go_marshal/gomarshal",
- ],
-)
diff --git a/tools/go_marshal/README.md b/tools/go_marshal/README.md
deleted file mode 100644
index 481575bd3..000000000
--- a/tools/go_marshal/README.md
+++ /dev/null
@@ -1,164 +0,0 @@
-This package implements the go_marshal utility.
-
-# Overview
-
-`go_marshal` is a code generation utility similar to `go_stateify` for
-automatically generating code to marshal go data structures to memory.
-
-`go_marshal` attempts to improve on `binary.Write` and the sentry's
-`binary.Marshal` by moving the go runtime reflection necessary to marshal a
-struct to compile-time.
-
-`go_marshal` automatically generates implementations for `abi.Marshallable` and
-`safemem.{Reader,Writer}`. Call-sites for serialization (typically syscall
-implementations) can directly invoke `safemem.Reader.ReadToBlocks` and
-`safemem.Writer.WriteFromBlocks`. Data structures that require custom
-serialization will have manual implementations for these interfaces.
-
-Data structures can be flagged for code generation by adding a struct-level
-comment `// +marshal`.
-
-# Usage
-
-See `defs.bzl`: two new rules are provided, `go_marshal` and `go_library`.
-
-The recommended way to generate a go library with marshalling is to use the
-`go_library` with mostly identical configuration as the native go_library rule.
-
-```
-load("<PKGPATH>/gvisor/tools/go_marshal:defs.bzl", "go_library")
-
-go_library(
- name = "foo",
- srcs = ["foo.go"],
-)
-```
-
-Under the hood, the `go_marshal` rule is used to generate a file that will
-appear in a Go target; the output file should appear explicitly in a srcs list.
-For example (note that the above is the preferred method):
-
-```
-load("<PKGPATH>/gvisor/tools/go_marshal:defs.bzl", "go_marshal")
-
-go_marshal(
- name = "foo_abi",
- srcs = ["foo.go"],
- out = "foo_abi.go",
- package = "foo",
-)
-
-go_library(
- name = "foo",
- srcs = [
- "foo.go",
- "foo_abi.go",
- ],
- deps = [
- "<PKGPATH>/gvisor/pkg/abi",
- "<PKGPATH>/gvisor/pkg/sentry/safemem/safemem",
- "<PKGPATH>/gvisor/pkg/sentry/usermem/usermem",
- ],
-)
-```
-
-As part of the interface generation, `go_marshal` also generates some tests for
-sanity checking the struct definitions for potential alignment issues, and a
-simple round-trip test through Marshal/Unmarshal to verify the implementation.
-These tests use reflection to verify properties of the ABI struct, and should be
-considered part of the generated interfaces (but are too expensive to execute at
-runtime). Ensure these tests run at some point.
-
-```
-$ cat BUILD
-load("<PKGPATH>/gvisor/tools/go_marshal:defs.bzl", "go_library")
-
-go_library(
- name = "foo",
- srcs = ["foo.go"],
-)
-$ blaze build :foo
-$ blaze query ...
-<path-to-dir>:foo_abi_autogen
-<path-to-dir>:foo_abi_autogen_test
-$ blaze test :foo_abi_autogen_test
-<test-output>
-```
-
-# Restrictions
-
-Not all valid go type definitions can be used with `go_marshal`. `go_marshal` is
-intended for ABI structs, which have these additional restrictions:
-
-- At the moment, `go_marshal` only supports struct declarations.
-
-- Structs are marshalled as packed types. This means no implicit padding is
- inserted between fields shorter than the platform register size. For
- alignment, manually insert padding fields.
-
-- Structs used with `go_marshal` must have a compile-time static size. This
- means no dynamically sizes fields like slices or strings. Use statically
- sized array (byte arrays for strings) instead.
-
-- No pointers, channel, map or function pointer fields, and no fields that are
- arrays of these types. These don't make sense in an ABI data structure.
-
-- We could support opaque pointers as `uintptr`, but this is currently not
- implemented. Implementing this would require handling the architecture
- dependent native pointer size.
-
-- Fields must either be a primitive integer type (`byte`,
- `[u]int{8,16,32,64}`), or of a type that implements abi.Marshallable.
-
-- `int` and `uint` fields are not allowed. Use an explicitly-sized numeric
- type.
-
-- `float*` fields are currently not supported, but could be if necessary.
-
-# Appendix
-
-## Working with Non-Packed Structs
-
-ABI structs must generally be packed types, meaning they should have no implicit
-padding between short fields. However, if a field is tagged
-`marshal:"unaligned"`, `go_marshal` will fall back to a safer but slower
-mechanism to deal with potentially unaligned fields.
-
-Note that the non-packed property is inheritted by any other struct that embeds
-this struct, since the `go_marshal` tool currently can't reason about alignments
-for embedded structs that are not aligned.
-
-Because of this, it's generally best to avoid using `marshal:"unaligned"` and
-insert explicit padding fields instead.
-
-## Debugging go_marshal
-
-To enable debugging output from the go marshal tool, pass the `-debug` flag to
-the tool. When using the build rules from above, add a `debug = True` field to
-the build rule like this:
-
-```
-load("<PKGPATH>/gvisor/tools/go_marshal:defs.bzl", "go_library")
-
-go_library(
- name = "foo",
- srcs = ["foo.go"],
- debug = True,
-)
-```
-
-## Modifying the `go_marshal` Tool
-
-The following are some guidelines for modifying the `go_marshal` tool:
-
-- The `go_marshal` tool currently does a single pass over all types requesting
- code generation, in arbitrary order. This means the generated code can't
- directly obtain information about embedded marshallable types at
- compile-time. One way to work around this restriction is to add a new
- Marshallable interface method providing this piece of information, and
- calling it from the generated code. Use this sparingly, as we want to rely
- on compile-time information as much as possible for performance.
-
-- No runtime reflection in the code generated for the marshallable interface.
- The entire point of the tool is to avoid runtime reflection. The generated
- tests may use reflection.
diff --git a/tools/go_marshal/analysis/BUILD b/tools/go_marshal/analysis/BUILD
deleted file mode 100644
index c859ced77..000000000
--- a/tools/go_marshal/analysis/BUILD
+++ /dev/null
@@ -1,13 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "analysis",
- testonly = 1,
- srcs = ["analysis_unsafe.go"],
- importpath = "gvisor.dev/gvisor/tools/go_marshal/analysis",
- visibility = [
- "//:sandbox",
- ],
-)
diff --git a/tools/go_marshal/analysis/analysis_unsafe.go b/tools/go_marshal/analysis/analysis_unsafe.go
deleted file mode 100644
index 9a9a4f298..000000000
--- a/tools/go_marshal/analysis/analysis_unsafe.go
+++ /dev/null
@@ -1,175 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package analysis implements common functionality used by generated
-// go_marshal tests.
-package analysis
-
-// All functions in this package are unsafe and are not intended for general
-// consumption. They contain sharp edge cases and the caller is responsible for
-// ensuring none of them are hit. Callers must be carefully to pass in only sane
-// arguments. Failure to do so may cause panics at best and arbitrary memory
-// corruption at worst.
-//
-// Never use outside of tests.
-
-import (
- "fmt"
- "math/rand"
- "reflect"
- "testing"
- "unsafe"
-)
-
-// RandomizeValue assigns random value(s) to an abitrary type. This is intended
-// for used with ABI structs from go_marshal, meaning the typical restrictions
-// apply (fixed-size types, no pointers, maps, channels, etc), and should only
-// be used on zeroed values to avoid overwriting pointers to active go objects.
-//
-// Internally, we populate the type with random data by doing an unsafe cast to
-// access the underlying memory of the type and filling it as if it were a byte
-// slice. This almost gets us what we want, but padding fields named "_" are
-// normally not accessible, so we walk the type and recursively zero all "_"
-// fields.
-//
-// Precondition: x must be a pointer. x must not contain any valid
-// pointers to active go objects (pointer fields aren't allowed in ABI
-// structs anyways), or we'd be violating the go runtime contract and
-// the GC may malfunction.
-func RandomizeValue(x interface{}) {
- v := reflect.Indirect(reflect.ValueOf(x))
- if !v.CanSet() {
- panic("RandomizeType() called with an unaddressable value. You probably need to pass a pointer to the argument")
- }
-
- // Cast the underlying memory for the type into a byte slice.
- var b []byte
- hdr := (*reflect.SliceHeader)(unsafe.Pointer(&b))
- // Note: v.UnsafeAddr panics if x is passed by value. x should be a pointer.
- hdr.Data = v.UnsafeAddr()
- hdr.Len = int(v.Type().Size())
- hdr.Cap = hdr.Len
-
- // Fill the byte slice with random data, which in effect fills the type with
- // random values.
- n, err := rand.Read(b)
- if err != nil || n != len(b) {
- panic("unreachable")
- }
-
- // Normally, padding fields are not accessible, so zero them out.
- reflectZeroPaddingFields(v.Type(), b, false)
-}
-
-// reflectZeroPaddingFields assigns zero values to padding fields for the value
-// of type r, represented by the memory in data. Padding fields are defined as
-// fields with the name "_". If zero is true, the immediate value itself is
-// zeroed. In addition, the type is recursively scanned for padding fields in
-// inner types.
-//
-// This is used for zeroing padding fields after calling RandomizeValue.
-func reflectZeroPaddingFields(r reflect.Type, data []byte, zero bool) {
- if zero {
- for i, _ := range data {
- data[i] = 0
- }
- }
- switch r.Kind() {
- case reflect.Int8, reflect.Uint8, reflect.Int16, reflect.Uint16, reflect.Int32, reflect.Uint32, reflect.Int64, reflect.Uint64:
- // These types are explicitly allowed in an ABI type, but we don't need
- // to recurse further as they're scalar types.
- case reflect.Struct:
- for i, numFields := 0, r.NumField(); i < numFields; i++ {
- f := r.Field(i)
- off := f.Offset
- len := f.Type.Size()
- window := data[off : off+len]
- reflectZeroPaddingFields(f.Type, window, f.Name == "_")
- }
- case reflect.Array:
- eLen := int(r.Elem().Size())
- if int(r.Size()) != eLen*r.Len() {
- panic("Array has unexpected size?")
- }
- for i, n := 0, r.Len(); i < n; i++ {
- reflectZeroPaddingFields(r.Elem(), data[i*eLen:(i+1)*eLen], false)
- }
- default:
- panic(fmt.Sprintf("Type %v not allowed in ABI struct", r.Kind()))
-
- }
-}
-
-// AlignmentCheck ensures the definition of the type represented by typ doesn't
-// cause the go compiler to emit implicit padding between elements of the type
-// (i.e. fields in a struct).
-//
-// AlignmentCheck doesn't explicitly recurse for embedded structs because any
-// struct present in an ABI struct must also be Marshallable, and therefore
-// they're aligned by definition (or their alignment check would have failed).
-func AlignmentCheck(t *testing.T, typ reflect.Type) (ok bool, delta uint64) {
- switch typ.Kind() {
- case reflect.Int8, reflect.Uint8, reflect.Int16, reflect.Uint16, reflect.Int32, reflect.Uint32, reflect.Int64, reflect.Uint64:
- // Primitive types are always considered well aligned. Primitive types
- // that are fields in structs are checked independently, this branch
- // exists to handle recursive calls to alignmentCheck.
- case reflect.Struct:
- xOff := 0
- nextXOff := 0
- skipNext := false
- for i, numFields := 0, typ.NumField(); i < numFields; i++ {
- xOff = nextXOff
- f := typ.Field(i)
- fmt.Printf("Checking alignment of %s.%s @ %d [+%d]...\n", typ.Name(), f.Name, f.Offset, f.Type.Size())
- nextXOff = int(f.Offset + f.Type.Size())
-
- if f.Name == "_" {
- // Padding fields need not be aligned.
- fmt.Printf("Padding field of type %v\n", f.Type)
- continue
- }
-
- if tag, ok := f.Tag.Lookup("marshal"); ok && tag == "unaligned" {
- skipNext = true
- continue
- }
-
- if skipNext {
- skipNext = false
- fmt.Printf("Skipping alignment check for field %s.%s explicitly marked as unaligned.\n", typ.Name(), f.Name)
- continue
- }
-
- if xOff != int(f.Offset) {
- implicitPad := int(f.Offset) - xOff
- t.Fatalf("Suspect offset for field %s.%s, detected an implicit %d byte padding from offset %d to %d; either add %d bytes of explicit padding before this field or tag it as `marshal:\"unaligned\"`.", typ.Name(), f.Name, implicitPad, xOff, f.Offset, implicitPad)
- }
- }
-
- // Ensure structs end on a byte explicitly defined by the type.
- if typ.NumField() > 0 && nextXOff != int(typ.Size()) {
- implicitPad := int(typ.Size()) - nextXOff
- f := typ.Field(typ.NumField() - 1) // Final field
- t.Fatalf("Suspect offset for field %s.%s at the end of %s, detected an implicit %d byte padding from offset %d to %d at the end of the struct; either add %d bytes of explict padding at end of the struct or tag the final field %s as `marshal:\"unaligned\"`.",
- typ.Name(), f.Name, typ.Name(), implicitPad, nextXOff, typ.Size(), implicitPad, f.Name)
- }
- case reflect.Array:
- // Independent arrays are also always considered well aligned. We only
- // need to worry about their alignment when they're embedded in structs,
- // which we handle above.
- default:
- t.Fatalf("Unsupported type in ABI struct while checking for field alignment for type: %v", typ.Kind())
- }
- return true, uint64(typ.Size())
-}
diff --git a/tools/go_marshal/defs.bzl b/tools/go_marshal/defs.bzl
deleted file mode 100644
index c32eb559f..000000000
--- a/tools/go_marshal/defs.bzl
+++ /dev/null
@@ -1,152 +0,0 @@
-"""Marshal is a tool for generating marshalling interfaces for Go types.
-
-The recommended way is to use the go_library rule defined below with mostly
-identical configuration as the native go_library rule.
-
-load("//tools/go_marshal:defs.bzl", "go_library")
-
-go_library(
- name = "foo",
- srcs = ["foo.go"],
-)
-
-Under the hood, the go_marshal rule is used to generate a file that will
-appear in a Go target; the output file should appear explicitly in a srcs list.
-For example (the above is still the preferred way):
-
-load("//tools/go_marshal:defs.bzl", "go_marshal")
-
-go_marshal(
- name = "foo_abi",
- srcs = ["foo.go"],
- out = "foo_abi.go",
- package = "foo",
-)
-
-go_library(
- name = "foo",
- srcs = [
- "foo.go",
- "foo_abi.go",
- ],
- deps = [
- "//tools/go_marshal:marshal",
- "//pkg/sentry/platform/safecopy",
- "//pkg/sentry/usermem",
- ],
-)
-"""
-
-load("@io_bazel_rules_go//go:def.bzl", _go_library = "go_library", _go_test = "go_test")
-
-def _go_marshal_impl(ctx):
- """Execute the go_marshal tool."""
- output = ctx.outputs.lib
- output_test = ctx.outputs.test
- (build_dir, _, _) = ctx.build_file_path.rpartition("/BUILD")
-
- decl = "/".join(["gvisor.dev/gvisor", build_dir])
-
- # Run the marshal command.
- args = ["-output=%s" % output.path]
- args += ["-pkg=%s" % ctx.attr.package]
- args += ["-output_test=%s" % output_test.path]
- args += ["-declarationPkg=%s" % decl]
-
- if ctx.attr.debug:
- args += ["-debug"]
-
- args += ["--"]
- for src in ctx.attr.srcs:
- args += [f.path for f in src.files.to_list()]
- ctx.actions.run(
- inputs = ctx.files.srcs,
- outputs = [output, output_test],
- mnemonic = "GoMarshal",
- progress_message = "go_marshal: %s" % ctx.label,
- arguments = args,
- executable = ctx.executable._tool,
- )
-
-# Generates save and restore logic from a set of Go files.
-#
-# Args:
-# name: the name of the rule.
-# srcs: the input source files. These files should include all structs in the
-# package that need to be saved.
-# imports: an optional list of extra, non-aliased, Go-style absolute import
-# paths.
-# out: the name of the generated file output. This must not conflict with any
-# other files and must be added to the srcs of the relevant go_library.
-# package: the package name for the input sources.
-go_marshal = rule(
- implementation = _go_marshal_impl,
- attrs = {
- "srcs": attr.label_list(mandatory = True, allow_files = True),
- "libname": attr.string(mandatory = True),
- "imports": attr.string_list(mandatory = False),
- "package": attr.string(mandatory = True),
- "debug": attr.bool(doc = "enable debugging output from the go_marshal tool"),
- "_tool": attr.label(executable = True, cfg = "host", default = Label("//tools/go_marshal:go_marshal")),
- },
- outputs = {
- "lib": "%{name}_unsafe.go",
- "test": "%{name}_test.go",
- },
-)
-
-def go_library(name, srcs, deps = [], imports = [], debug = False, **kwargs):
- """wraps the standard go_library and does mashalling interface generation.
-
- Args:
- name: Same as native go_library.
- srcs: Same as native go_library.
- deps: Same as native go_library.
- imports: Extra import paths to pass to the go_marshal tool.
- debug: Enables debugging output from the go_marshal tool.
- **kwargs: Remaining args to pass to the native go_library rule unmodified.
- """
- go_marshal(
- name = name + "_abi_autogen",
- libname = name,
- srcs = [src for src in srcs if src.endswith(".go")],
- debug = debug,
- imports = imports,
- package = name,
- )
-
- extra_deps = [
- "//tools/go_marshal/marshal",
- "//pkg/sentry/platform/safecopy",
- "//pkg/sentry/usermem",
- ]
-
- all_srcs = srcs + [name + "_abi_autogen_unsafe.go"]
- all_deps = deps + [] # + extra_deps
-
- for extra in extra_deps:
- if extra not in deps:
- all_deps.append(extra)
-
- _go_library(
- name = name,
- srcs = all_srcs,
- deps = all_deps,
- **kwargs
- )
-
- # Don't pass importpath arg to go_test.
- kwargs.pop("importpath", "")
-
- _go_test(
- name = name + "_abi_autogen_test",
- srcs = [name + "_abi_autogen_test.go"],
- # Generated test has a fixed set of dependencies since we generate these
- # tests. They should only depend on the library generated above, and the
- # Marshallable interface.
- deps = [
- ":" + name,
- "//tools/go_marshal/analysis",
- ],
- **kwargs
- )
diff --git a/tools/go_marshal/gomarshal/BUILD b/tools/go_marshal/gomarshal/BUILD
deleted file mode 100644
index a0eae6492..000000000
--- a/tools/go_marshal/gomarshal/BUILD
+++ /dev/null
@@ -1,17 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "gomarshal",
- srcs = [
- "generator.go",
- "generator_interfaces.go",
- "generator_tests.go",
- "util.go",
- ],
- importpath = "gvisor.dev/gvisor/tools/go_marshal/gomarshal",
- visibility = [
- "//:sandbox",
- ],
-)
diff --git a/tools/go_marshal/gomarshal/generator.go b/tools/go_marshal/gomarshal/generator.go
deleted file mode 100644
index 641ccd938..000000000
--- a/tools/go_marshal/gomarshal/generator.go
+++ /dev/null
@@ -1,382 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package gomarshal implements the go_marshal code generator. See README.md.
-package gomarshal
-
-import (
- "bytes"
- "fmt"
- "go/ast"
- "go/parser"
- "go/token"
- "os"
- "sort"
-)
-
-const (
- marshalImport = "gvisor.dev/gvisor/tools/go_marshal/marshal"
- usermemImport = "gvisor.dev/gvisor/pkg/sentry/usermem"
- safecopyImport = "gvisor.dev/gvisor/pkg/sentry/platform/safecopy"
-)
-
-// List of identifiers we use in generated code, that may conflict a
-// similarly-named source identifier. Avoid problems by refusing the generate
-// code when we see these.
-//
-// This only applies to import aliases at the moment. All other identifiers
-// are qualified by a receiver argument, since they're struct fields.
-//
-// All recievers are single letters, so we don't allow import aliases to be a
-// single letter.
-var badIdents = []string{
- "src", "srcs", "dst", "dsts", "blk", "buf", "err",
- // All single-letter identifiers.
-}
-
-// Generator drives code generation for a single invocation of the go_marshal
-// utility.
-//
-// The Generator holds arguments passed to the tool, and drives parsing,
-// processing and code Generator for all types marked with +marshal declared in
-// the input files.
-//
-// See Generator.run() as the entry point.
-type Generator struct {
- // Paths to input go source files.
- inputs []string
- // Output file to write generated go source.
- output *os.File
- // Output file to write generated tests.
- outputTest *os.File
- // Package name for the generated file.
- pkg string
- // Go import path for package we're processing. This package should directly
- // declare the type we're generating code for.
- declaration string
- // Set of extra packages to import in the generated file.
- imports *importTable
-}
-
-// NewGenerator creates a new code Generator.
-func NewGenerator(srcs []string, out, outTest, pkg, declaration string, imports []string) (*Generator, error) {
- f, err := os.OpenFile(out, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
- if err != nil {
- return nil, fmt.Errorf("Couldn't open output file %q: %v", out, err)
- }
- fTest, err := os.OpenFile(outTest, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
- if err != nil {
- return nil, fmt.Errorf("Couldn't open test output file %q: %v", out, err)
- }
- g := Generator{
- inputs: srcs,
- output: f,
- outputTest: fTest,
- pkg: pkg,
- declaration: declaration,
- imports: newImportTable(),
- }
- for _, i := range imports {
- // All imports on the extra imports list are unconditionally marked as
- // used, so they're always added to the generated code.
- g.imports.add(i).markUsed()
- }
- g.imports.add(marshalImport).markUsed()
- // The follow imports may or may not be used by the generated
- // code, depending what's required for the target types. Don't
- // mark these imports as used by default.
- g.imports.add(usermemImport)
- g.imports.add(safecopyImport)
- g.imports.add("unsafe")
-
- return &g, nil
-}
-
-// writeHeader writes the header for the generated source file. The header
-// includes the package name, package level comments and import statements.
-func (g *Generator) writeHeader() error {
- var b sourceBuffer
- b.emit("// Automatically generated marshal implementation. See tools/go_marshal.\n\n")
- b.emit("package %s\n\n", g.pkg)
- if err := b.write(g.output); err != nil {
- return err
- }
-
- return g.imports.write(g.output)
-}
-
-// writeTypeChecks writes a statement to force the compiler to perform a type
-// check for all Marshallable types referenced by the generated code.
-func (g *Generator) writeTypeChecks(ms map[string]struct{}) error {
- if len(ms) == 0 {
- return nil
- }
-
- msl := make([]string, 0, len(ms))
- for m, _ := range ms {
- msl = append(msl, m)
- }
- sort.Strings(msl)
-
- var buf bytes.Buffer
- fmt.Fprint(&buf, "// Marshallable types used by this file.\n")
-
- for _, m := range msl {
- fmt.Fprintf(&buf, "var _ marshal.Marshallable = (*%s)(nil)\n", m)
- }
- fmt.Fprint(&buf, "\n")
-
- _, err := fmt.Fprint(g.output, buf.String())
- return err
-}
-
-// parse processes all input files passed this generator and produces a set of
-// parsed go ASTs.
-func (g *Generator) parse() ([]*ast.File, []*token.FileSet, error) {
- debugf("go_marshal invoked with %d input files:\n", len(g.inputs))
- for _, path := range g.inputs {
- debugf(" %s\n", path)
- }
-
- files := make([]*ast.File, 0, len(g.inputs))
- fsets := make([]*token.FileSet, 0, len(g.inputs))
-
- for _, path := range g.inputs {
- fset := token.NewFileSet()
- f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
- if err != nil {
- // Not a valid input file?
- return nil, nil, fmt.Errorf("Input %q can't be parsed: %v", path, err)
- }
-
- if debugEnabled() {
- debugf("AST for %q:\n", path)
- ast.Print(fset, f)
- }
-
- files = append(files, f)
- fsets = append(fsets, fset)
- }
-
- return files, fsets, nil
-}
-
-// collectMarshallabeTypes walks the parsed AST and collects a list of type
-// declarations for which we need to generate the Marshallable interface.
-func (g *Generator) collectMarshallabeTypes(a *ast.File, f *token.FileSet) []*ast.TypeSpec {
- var types []*ast.TypeSpec
- for _, decl := range a.Decls {
- gdecl, ok := decl.(*ast.GenDecl)
- // Type declaration?
- if !ok || gdecl.Tok != token.TYPE {
- debugfAt(f.Position(decl.Pos()), "Skipping declaration since it's not a type declaration.\n")
- continue
- }
- // Does it have a comment?
- if gdecl.Doc == nil {
- debugfAt(f.Position(gdecl.Pos()), "Skipping declaration since it doesn't have a comment.\n")
- continue
- }
- // Does the comment contain a "+marshal" line?
- marked := false
- for _, c := range gdecl.Doc.List {
- if c.Text == "// +marshal" {
- marked = true
- break
- }
- }
- if !marked {
- debugfAt(f.Position(gdecl.Pos()), "Skipping declaration since it doesn't have a comment containing +marshal line.\n")
- continue
- }
- for _, spec := range gdecl.Specs {
- // We already confirmed we're in a type declaration earlier.
- t := spec.(*ast.TypeSpec)
- if _, ok := t.Type.(*ast.StructType); ok {
- debugfAt(f.Position(t.Pos()), "Collected marshallable type %s.\n", t.Name.Name)
- types = append(types, t)
- continue
- }
- debugf("Skipping declaration %v since it's not a struct declaration.\n", gdecl)
- }
- }
- return types
-}
-
-// collectImports collects all imports from all input source files. Some of
-// these imports are copied to the generated output, if they're referenced by
-// the generated code.
-//
-// collectImports de-duplicates imports while building the list, and ensures
-// identifiers in the generated code don't conflict with any imported package
-// names.
-func (g *Generator) collectImports(a *ast.File, f *token.FileSet) map[string]importStmt {
- badImportNames := make(map[string]bool)
- for _, i := range badIdents {
- badImportNames[i] = true
- }
-
- is := make(map[string]importStmt)
- for _, decl := range a.Decls {
- gdecl, ok := decl.(*ast.GenDecl)
- // Import statement?
- if !ok || gdecl.Tok != token.IMPORT {
- continue
- }
- for _, spec := range gdecl.Specs {
- i := g.imports.addFromSpec(spec.(*ast.ImportSpec), f)
- debugf("Collected import '%s' as '%s'\n", i.path, i.name)
-
- // Make sure we have an import that doesn't use any local names that
- // would conflict with identifiers in the generated code.
- if len(i.name) == 1 {
- abortAt(f.Position(spec.Pos()), fmt.Sprintf("Import has a single character local name '%s'; this may conflict with code generated by go_marshal, use a multi-character import alias", i.name))
- }
- if badImportNames[i.name] {
- abortAt(f.Position(spec.Pos()), fmt.Sprintf("Import name '%s' is likely to conflict with code generated by go_marshal, use a different import alias", i.name))
- }
- }
- }
- return is
-
-}
-
-func (g *Generator) generateOne(t *ast.TypeSpec, fset *token.FileSet) *interfaceGenerator {
- // We're guaranteed to have only struct type specs by now. See
- // Generator.collectMarshallabeTypes.
- i := newInterfaceGenerator(t, fset)
- i.validate()
- i.emitMarshallable()
- return i
-}
-
-// generateOneTestSuite generates a test suite for the automatically generated
-// implementations type t.
-func (g *Generator) generateOneTestSuite(t *ast.TypeSpec) *testGenerator {
- i := newTestGenerator(t, g.declaration)
- i.emitTests()
- return i
-}
-
-// Run is the entry point to code generation using g.
-//
-// Run parses all input source files specified in g and emits generated code.
-func (g *Generator) Run() error {
- // Parse our input source files into ASTs and token sets.
- asts, fsets, err := g.parse()
- if err != nil {
- return err
- }
-
- if len(asts) != len(fsets) {
- panic("ASTs and FileSets don't match")
- }
-
- // Map of imports in source files; key = local package name, value = import
- // path.
- is := make(map[string]importStmt)
- for i, a := range asts {
- // Collect all imports from the source files. We may need to copy some
- // of these to the generated code if they're referenced. This has to be
- // done before the loop below because we need to process all ASTs before
- // we start requesting imports to be copied one by one as we encounter
- // them in each generated source.
- for name, i := range g.collectImports(a, fsets[i]) {
- is[name] = i
- }
- }
-
- var impls []*interfaceGenerator
- var ts []*testGenerator
- // Set of Marshallable types referenced by generated code.
- ms := make(map[string]struct{})
- for i, a := range asts {
- // Collect type declarations marked for code generation and generate
- // Marshallable interfaces.
- for _, t := range g.collectMarshallabeTypes(a, fsets[i]) {
- impl := g.generateOne(t, fsets[i])
- // Collect Marshallable types referenced by the generated code.
- for ref, _ := range impl.ms {
- ms[ref] = struct{}{}
- }
- impls = append(impls, impl)
- // Collect imports referenced by the generated code and add them to
- // the list of imports we need to copy to the generated code.
- for name, _ := range impl.is {
- if !g.imports.markUsed(name) {
- panic(fmt.Sprintf("Generated code for '%s' referenced a non-existent import with local name '%s'", impl.typeName(), name))
- }
- }
- ts = append(ts, g.generateOneTestSuite(t))
- }
- }
-
- // Tool was invoked with input files with no data structures marked for code
- // generation. This is probably not what the user intended.
- if len(impls) == 0 {
- var buf bytes.Buffer
- fmt.Fprintf(&buf, "go_marshal invoked on these files, but they don't contain any types requiring code generation. Perhaps mark some with \"// +marshal\"?:\n")
- for _, i := range g.inputs {
- fmt.Fprintf(&buf, " %s\n", i)
- }
- abort(buf.String())
- }
-
- // Write output file header. These include things like package name and
- // import statements.
- if err := g.writeHeader(); err != nil {
- return err
- }
-
- // Write type checks for referenced marshallable types to output file.
- if err := g.writeTypeChecks(ms); err != nil {
- return err
- }
-
- // Write generated interfaces to output file.
- for _, i := range impls {
- if err := i.write(g.output); err != nil {
- return err
- }
- }
-
- // Write generated tests to test file.
- return g.writeTests(ts)
-}
-
-// writeTests outputs tests for the generated interface implementations to a go
-// source file.
-func (g *Generator) writeTests(ts []*testGenerator) error {
- var b sourceBuffer
- b.emit("package %s_test\n\n", g.pkg)
- if err := b.write(g.outputTest); err != nil {
- return err
- }
-
- imports := newImportTable()
- for _, t := range ts {
- imports.merge(t.imports)
- }
-
- if err := imports.write(g.outputTest); err != nil {
- return err
- }
-
- for _, t := range ts {
- if err := t.write(g.outputTest); err != nil {
- return err
- }
- }
- return nil
-}
diff --git a/tools/go_marshal/gomarshal/generator_interfaces.go b/tools/go_marshal/gomarshal/generator_interfaces.go
deleted file mode 100644
index a712c14dc..000000000
--- a/tools/go_marshal/gomarshal/generator_interfaces.go
+++ /dev/null
@@ -1,507 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package gomarshal
-
-import (
- "fmt"
- "go/ast"
- "go/token"
- "strings"
-)
-
-// interfaceGenerator generates marshalling interfaces for a single type.
-//
-// getState is not thread-safe.
-type interfaceGenerator struct {
- sourceBuffer
-
- // The type we're serializing.
- t *ast.TypeSpec
-
- // Receiver argument for generated methods.
- r string
-
- // FileSet containing the tokens for the type we're processing.
- f *token.FileSet
-
- // is records external packages referenced by the generated implementation.
- is map[string]struct{}
-
- // ms records Marshallable types referenced by the generated implementation
- // of t's interfaces.
- ms map[string]struct{}
-
- // as records embedded fields in t that are potentially not packed. The key
- // is the accessor for the field.
- as map[string]struct{}
-}
-
-// typeName returns the name of the type this g represents.
-func (g *interfaceGenerator) typeName() string {
- return g.t.Name.Name
-}
-
-// newinterfaceGenerator creates a new interface generator.
-func newInterfaceGenerator(t *ast.TypeSpec, fset *token.FileSet) *interfaceGenerator {
- if _, ok := t.Type.(*ast.StructType); !ok {
- panic(fmt.Sprintf("Attempting to generate code for a not struct type %v", t))
- }
- g := &interfaceGenerator{
- t: t,
- r: receiverName(t),
- f: fset,
- is: make(map[string]struct{}),
- ms: make(map[string]struct{}),
- as: make(map[string]struct{}),
- }
- g.recordUsedMarshallable(g.typeName())
- return g
-}
-
-func (g *interfaceGenerator) recordUsedMarshallable(m string) {
- g.ms[m] = struct{}{}
-
-}
-
-func (g *interfaceGenerator) recordUsedImport(i string) {
- g.is[i] = struct{}{}
-
-}
-
-func (g *interfaceGenerator) recordPotentiallyNonPackedField(fieldName string) {
- g.as[fieldName] = struct{}{}
-}
-
-func (g *interfaceGenerator) forEachField(fn func(f *ast.Field)) {
- // This is guaranteed to succeed because g.t is always a struct.
- st := g.t.Type.(*ast.StructType)
- for _, field := range st.Fields.List {
- fn(field)
- }
-}
-
-func (g *interfaceGenerator) fieldAccessor(n *ast.Ident) string {
- return fmt.Sprintf("%s.%s", g.r, n.Name)
-}
-
-// abortAt aborts the go_marshal tool with the given error message, with a
-// reference position to the input source. Same as abortAt, but uses g to
-// resolve p to position.
-func (g *interfaceGenerator) abortAt(p token.Pos, msg string) {
- abortAt(g.f.Position(p), msg)
-}
-
-// validate ensures the type we're working with can be marshalled. These checks
-// are done ahead of time and in one place so we can make assumptions later.
-func (g *interfaceGenerator) validate() {
- g.forEachField(func(f *ast.Field) {
- if len(f.Names) == 0 {
- g.abortAt(f.Pos(), "Cannot marshal structs with embedded fields, give the field a name; use '_' for anonymous fields such as padding fields")
- }
- })
-
- g.forEachField(func(f *ast.Field) {
- fieldDispatcher{
- primitive: func(_, t *ast.Ident) {
- switch t.Name {
- case "int8", "uint8", "byte", "int16", "uint16", "int32", "uint32", "int64", "uint64":
- // These are the only primitive types we're allow. Below, we
- // provide suggestions for some disallowed types and reject
- // them, then attempt to marshal any remaining types by
- // invoking the marshal.Marshallable interface on them. If
- // these types don't actually implement
- // marshal.Marshallable, compilation of the generated code
- // will fail with an appropriate error message.
- return
- case "int":
- g.abortAt(f.Pos(), "Type 'int' has ambiguous width, use int32 or int64")
- case "uint":
- g.abortAt(f.Pos(), "Type 'uint' has ambiguous width, use uint32 or uint64")
- case "string":
- g.abortAt(f.Pos(), "Type 'string' is dynamically-sized and cannot be marshalled, use a fixed size byte array '[...]byte' instead")
- default:
- debugfAt(g.f.Position(f.Pos()), fmt.Sprintf("Found derived type '%s', will attempt dispatch via marshal.Marshallable.\n", t.Name))
- }
- },
- selector: func(_, _, _ *ast.Ident) {
- // No validation to perform on selector fields. However this
- // callback must still be provided.
- },
- array: func(n, _ *ast.Ident, len int) {
- a := f.Type.(*ast.ArrayType)
- if a.Len == nil {
- g.abortAt(f.Pos(), fmt.Sprintf("Dynamically sized slice '%s' cannot be marshalled, arrays must be statically sized", n.Name))
- }
-
- if _, ok := a.Len.(*ast.BasicLit); !ok {
- g.abortAt(a.Len.Pos(), fmt.Sprintf("Array size must be a literal, don's use consts or expressions"))
- }
-
- if _, ok := a.Elt.(*ast.Ident); !ok {
- g.abortAt(a.Elt.Pos(), fmt.Sprintf("Marshalling not supported for arrays with %s elements, array elements must be primitive types", kindString(a.Elt)))
- }
-
- if len <= 0 {
- g.abortAt(a.Len.Pos(), fmt.Sprintf("Marshalling not supported for zero length arrays, why does an ABI struct have one?"))
- }
- },
- unhandled: func(_ *ast.Ident) {
- g.abortAt(f.Pos(), fmt.Sprintf("Marshalling not supported for %s fields", kindString(f.Type)))
- },
- }.dispatch(f)
- })
-}
-
-// scalarSize returns the size of type identified by t. If t isn't a primitive
-// type, the size isn't known at code generation time, and must be resolved via
-// the marshal.Marshallable interface.
-func (g *interfaceGenerator) scalarSize(t *ast.Ident) (size int, unknownSize bool) {
- switch t.Name {
- case "int8", "uint8", "byte":
- return 1, false
- case "int16", "uint16":
- return 2, false
- case "int32", "uint32":
- return 4, false
- case "int64", "uint64":
- return 8, false
- default:
- return 0, true
- }
-}
-
-func (g *interfaceGenerator) shift(bufVar string, n int) {
- g.emit("%s = %s[%d:]\n", bufVar, bufVar, n)
-}
-
-func (g *interfaceGenerator) shiftDynamic(bufVar, name string) {
- g.emit("%s = %s[%s.SizeBytes():]\n", bufVar, bufVar, name)
-}
-
-func (g *interfaceGenerator) marshalScalar(accessor, typ string, bufVar string) {
- switch typ {
- case "int8", "uint8", "byte":
- g.emit("%s[0] = byte(%s)\n", bufVar, accessor)
- g.shift(bufVar, 1)
- case "int16", "uint16":
- g.recordUsedImport("usermem")
- g.emit("usermem.ByteOrder.PutUint16(%s[:2], uint16(%s))\n", bufVar, accessor)
- g.shift(bufVar, 2)
- case "int32", "uint32":
- g.recordUsedImport("usermem")
- g.emit("usermem.ByteOrder.PutUint32(%s[:4], uint32(%s))\n", bufVar, accessor)
- g.shift(bufVar, 4)
- case "int64", "uint64":
- g.recordUsedImport("usermem")
- g.emit("usermem.ByteOrder.PutUint64(%s[:8], uint64(%s))\n", bufVar, accessor)
- g.shift(bufVar, 8)
- default:
- g.emit("%s.MarshalBytes(%s[:%s.SizeBytes()])\n", accessor, bufVar, accessor)
- g.shiftDynamic(bufVar, accessor)
- }
-}
-
-func (g *interfaceGenerator) unmarshalScalar(accessor, typ string, bufVar string) {
- switch typ {
- case "int8":
- g.emit("%s = int8(%s[0])\n", accessor, bufVar)
- g.shift(bufVar, 1)
- case "uint8":
- g.emit("%s = uint8(%s[0])\n", accessor, bufVar)
- g.shift(bufVar, 1)
- case "byte":
- g.emit("%s = %s[0]\n", accessor, bufVar)
- g.shift(bufVar, 1)
-
- case "int16":
- g.recordUsedImport("usermem")
- g.emit("%s = int16(usermem.ByteOrder.Uint16(%s[:2]))\n", accessor, bufVar)
- g.shift(bufVar, 2)
- case "uint16":
- g.recordUsedImport("usermem")
- g.emit("%s = usermem.ByteOrder.Uint16(%s[:2])\n", accessor, bufVar)
- g.shift(bufVar, 2)
-
- case "int32":
- g.recordUsedImport("usermem")
- g.emit("%s = int32(usermem.ByteOrder.Uint32(%s[:4]))\n", accessor, bufVar)
- g.shift(bufVar, 4)
- case "uint32":
- g.recordUsedImport("usermem")
- g.emit("%s = usermem.ByteOrder.Uint32(%s[:4])\n", accessor, bufVar)
- g.shift(bufVar, 4)
-
- case "int64":
- g.recordUsedImport("usermem")
- g.emit("%s = int64(usermem.ByteOrder.Uint64(%s[:8]))\n", accessor, bufVar)
- g.shift(bufVar, 8)
- case "uint64":
- g.recordUsedImport("usermem")
- g.emit("%s = usermem.ByteOrder.Uint64(%s[:8])\n", accessor, bufVar)
- g.shift(bufVar, 8)
- default:
- g.emit("%s.UnmarshalBytes(%s[:%s.SizeBytes()])\n", accessor, bufVar, accessor)
- g.shiftDynamic(bufVar, accessor)
- g.recordPotentiallyNonPackedField(accessor)
- }
-}
-
-// areFieldsPackedExpression returns a go expression checking whether g.t's fields are
-// packed. Returns "", false if g.t has no fields that may be potentially
-// packed, otherwise returns <clause>, true, where <clause> is an expression
-// like "t.a.Packed() && t.b.Packed() && t.c.Packed()".
-func (g *interfaceGenerator) areFieldsPackedExpression() (string, bool) {
- if len(g.as) == 0 {
- return "", false
- }
-
- cs := make([]string, 0, len(g.as))
- for accessor, _ := range g.as {
- cs = append(cs, fmt.Sprintf("%s.Packed()", accessor))
- }
- return strings.Join(cs, " && "), true
-}
-
-func (g *interfaceGenerator) emitMarshallable() {
- // Is g.t a packed struct without consideing field types?
- thisPacked := true
- g.forEachField(func(f *ast.Field) {
- if f.Tag != nil {
- if f.Tag.Value == "`marshal:\"unaligned\"`" {
- if thisPacked {
- debugfAt(g.f.Position(g.t.Pos()),
- fmt.Sprintf("Marking type '%s' as not packed due to tag `marshal:\"unaligned\"`.\n", g.t.Name))
- thisPacked = false
- }
- }
- }
- })
-
- g.emit("// SizeBytes implements marshal.Marshallable.SizeBytes.\n")
- g.emit("func (%s *%s) SizeBytes() int {\n", g.r, g.typeName())
- g.inIndent(func() {
- primitiveSize := 0
- var dynamicSizeTerms []string
-
- g.forEachField(fieldDispatcher{
- primitive: func(n, t *ast.Ident) {
- if size, dynamic := g.scalarSize(t); !dynamic {
- primitiveSize += size
- } else {
- g.recordUsedMarshallable(t.Name)
- dynamicSizeTerms = append(dynamicSizeTerms, fmt.Sprintf("%s.SizeBytes()", g.fieldAccessor(n)))
- }
- },
- selector: func(n, tX, tSel *ast.Ident) {
- tName := fmt.Sprintf("%s.%s", tX.Name, tSel.Name)
- g.recordUsedImport(tX.Name)
- g.recordUsedMarshallable(tName)
- dynamicSizeTerms = append(dynamicSizeTerms, fmt.Sprintf("(*%s)(nil).SizeBytes()", tName))
- },
- array: func(n, t *ast.Ident, len int) {
- if len < 1 {
- // Zero-length arrays should've been rejected by validate().
- panic("unreachable")
- }
- if size, dynamic := g.scalarSize(t); !dynamic {
- primitiveSize += size * len
- } else {
- g.recordUsedMarshallable(t.Name)
- dynamicSizeTerms = append(dynamicSizeTerms, fmt.Sprintf("(*%s)(nil).SizeBytes()*%d", t.Name, len))
- }
- },
- }.dispatch)
- g.emit("return %d", primitiveSize)
- if len(dynamicSizeTerms) > 0 {
- g.incIndent()
- }
- {
- for _, d := range dynamicSizeTerms {
- g.emitNoIndent(" +\n")
- g.emit(d)
- }
- }
- if len(dynamicSizeTerms) > 0 {
- g.decIndent()
- }
- })
- g.emit("\n}\n\n")
-
- g.emit("// MarshalBytes implements marshal.Marshallable.MarshalBytes.\n")
- g.emit("func (%s *%s) MarshalBytes(dst []byte) {\n", g.r, g.typeName())
- g.inIndent(func() {
- g.forEachField(fieldDispatcher{
- primitive: func(n, t *ast.Ident) {
- if n.Name == "_" {
- g.emit("// Padding: dst[:sizeof(%s)] ~= %s(0)\n", t.Name, t.Name)
- if len, dynamic := g.scalarSize(t); !dynamic {
- g.shift("dst", len)
- } else {
- // We can't use shiftDynamic here because we don't have
- // an instance of the dynamic type we can referece here
- // (since the version in this struct is anonymous). Use
- // a typed nil pointer to call SizeBytes() instead.
- g.emit("dst = dst[(*%s)(nil).SizeBytes():]\n", t.Name)
- }
- return
- }
- g.marshalScalar(g.fieldAccessor(n), t.Name, "dst")
- },
- selector: func(n, tX, tSel *ast.Ident) {
- g.marshalScalar(g.fieldAccessor(n), fmt.Sprintf("%s.%s", tX.Name, tSel.Name), "dst")
- },
- array: func(n, t *ast.Ident, size int) {
- if n.Name == "_" {
- g.emit("// Padding: dst[:sizeof(%s)*%d] ~= [%d]%s{0}\n", t.Name, size, size, t.Name)
- if len, dynamic := g.scalarSize(t); !dynamic {
- g.shift("dst", len*size)
- } else {
- // We can't use shiftDynamic here because we don't have
- // an instance of the dynamic type we can reference here
- // (since the version in this struct is anonymous). Use
- // a typed nil pointer to call SizeBytes() instead.
- g.emit("dst = dst[(*%s)(nil).SizeBytes()*%d:]\n", t.Name, size)
- }
- return
- }
-
- g.emit("for i := 0; i < %d; i++ {\n", size)
- g.inIndent(func() {
- g.marshalScalar(fmt.Sprintf("%s[i]", g.fieldAccessor(n)), t.Name, "dst")
- })
- g.emit("}\n")
- },
- }.dispatch)
- })
- g.emit("}\n\n")
-
- g.emit("// UnmarshalBytes implements marshal.Marshallable.UnmarshalBytes.\n")
- g.emit("func (%s *%s) UnmarshalBytes(src []byte) {\n", g.r, g.typeName())
- g.inIndent(func() {
- g.forEachField(fieldDispatcher{
- primitive: func(n, t *ast.Ident) {
- if n.Name == "_" {
- g.emit("// Padding: var _ %s ~= src[:sizeof(%s)]\n", t.Name, t.Name)
- if len, dynamic := g.scalarSize(t); !dynamic {
- g.shift("src", len)
- } else {
- // We can't use shiftDynamic here because we don't have
- // an instance of the dynamic type we can reference here
- // (since the version in this struct is anonymous). Use
- // a typed nil pointer to call SizeBytes() instead.
- g.emit("src = src[(*%s)(nil).SizeBytes():]\n", t.Name)
- g.recordPotentiallyNonPackedField(fmt.Sprintf("(*%s)(nil)", t.Name))
- }
- return
- }
- g.unmarshalScalar(g.fieldAccessor(n), t.Name, "src")
- },
- selector: func(n, tX, tSel *ast.Ident) {
- g.unmarshalScalar(g.fieldAccessor(n), fmt.Sprintf("%s.%s", tX.Name, tSel.Name), "src")
- },
- array: func(n, t *ast.Ident, size int) {
- if n.Name == "_" {
- g.emit("// Padding: ~ copy([%d]%s(%s), src[:sizeof(%s)*%d])\n", size, t.Name, g.fieldAccessor(n), t.Name, size)
- if len, dynamic := g.scalarSize(t); !dynamic {
- g.shift("src", len*size)
- } else {
- // We can't use shiftDynamic here because we don't have
- // an instance of the dynamic type we can referece here
- // (since the version in this struct is anonymous). Use
- // a typed nil pointer to call SizeBytes() instead.
- g.emit("src = src[(*%s)(nil).SizeBytes()*%d:]\n", t.Name, size)
- }
- return
- }
-
- g.emit("for i := 0; i < %d; i++ {\n", size)
- g.inIndent(func() {
- g.unmarshalScalar(fmt.Sprintf("%s[i]", g.fieldAccessor(n)), t.Name, "src")
- })
- g.emit("}\n")
- },
- }.dispatch)
- })
- g.emit("}\n\n")
-
- g.emit("// Packed implements marshal.Marshallable.Packed.\n")
- g.emit("func (%s *%s) Packed() bool {\n", g.r, g.typeName())
- g.inIndent(func() {
- expr, fieldsMaybePacked := g.areFieldsPackedExpression()
- switch {
- case !thisPacked:
- g.emit("return false\n")
- case fieldsMaybePacked:
- g.emit("return %s\n", expr)
- default:
- g.emit("return true\n")
-
- }
- })
- g.emit("}\n\n")
-
- g.emit("// MarshalUnsafe implements marshal.Marshallable.MarshalUnsafe.\n")
- g.emit("func (%s *%s) MarshalUnsafe(dst []byte) {\n", g.r, g.typeName())
- g.inIndent(func() {
- if thisPacked {
- g.recordUsedImport("safecopy")
- g.recordUsedImport("unsafe")
- if cond, ok := g.areFieldsPackedExpression(); ok {
- g.emit("if %s {\n", cond)
- g.inIndent(func() {
- g.emit("safecopy.CopyIn(dst, unsafe.Pointer(%s))\n", g.r)
- })
- g.emit("} else {\n")
- g.inIndent(func() {
- g.emit("%s.MarshalBytes(dst)\n", g.r)
- })
- g.emit("}\n")
- } else {
- g.emit("safecopy.CopyIn(dst, unsafe.Pointer(%s))\n", g.r)
- }
- } else {
- g.emit("// Type %s doesn't have a packed layout in memory, fallback to MarshalBytes.\n", g.typeName())
- g.emit("%s.MarshalBytes(dst)\n", g.r)
- }
- })
- g.emit("}\n\n")
-
- g.emit("// UnmarshalUnsafe implements marshal.Marshallable.UnmarshalUnsafe.\n")
- g.emit("func (%s *%s) UnmarshalUnsafe(src []byte) {\n", g.r, g.typeName())
- g.inIndent(func() {
- if thisPacked {
- g.recordUsedImport("safecopy")
- g.recordUsedImport("unsafe")
- if cond, ok := g.areFieldsPackedExpression(); ok {
- g.emit("if %s {\n", cond)
- g.inIndent(func() {
- g.emit("safecopy.CopyOut(unsafe.Pointer(%s), src)\n", g.r)
- })
- g.emit("} else {\n")
- g.inIndent(func() {
- g.emit("%s.UnmarshalBytes(src)\n", g.r)
- })
- g.emit("}\n")
- } else {
- g.emit("safecopy.CopyOut(unsafe.Pointer(%s), src)\n", g.r)
- }
- } else {
- g.emit("// Type %s doesn't have a packed layout in memory, fall back to UnmarshalBytes.\n", g.typeName())
- g.emit("%s.UnmarshalBytes(src)\n", g.r)
- }
- })
- g.emit("}\n\n")
-
-}
diff --git a/tools/go_marshal/gomarshal/generator_tests.go b/tools/go_marshal/gomarshal/generator_tests.go
deleted file mode 100644
index df25cb5b2..000000000
--- a/tools/go_marshal/gomarshal/generator_tests.go
+++ /dev/null
@@ -1,154 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package gomarshal
-
-import (
- "fmt"
- "go/ast"
- "io"
- "strings"
-)
-
-var standardImports = []string{
- "fmt",
- "reflect",
- "testing",
- "gvisor.dev/gvisor/tools/go_marshal/analysis",
-}
-
-type testGenerator struct {
- sourceBuffer
-
- // The type we're serializing.
- t *ast.TypeSpec
-
- // Receiver argument for generated methods.
- r string
-
- // Imports used by generated code.
- imports *importTable
-
- // Import statement for the package declaring the type we generated code
- // for. We need this to construct test instances for the type, since the
- // tests aren't written in the same package.
- decl *importStmt
-}
-
-func newTestGenerator(t *ast.TypeSpec, declaration string) *testGenerator {
- if _, ok := t.Type.(*ast.StructType); !ok {
- panic(fmt.Sprintf("Attempting to generate code for a not struct type %v", t))
- }
- g := &testGenerator{
- t: t,
- r: receiverName(t),
- imports: newImportTable(),
- }
-
- for _, i := range standardImports {
- g.imports.add(i).markUsed()
- }
- g.decl = g.imports.add(declaration)
- g.decl.markUsed()
-
- return g
-}
-
-func (g *testGenerator) typeName() string {
- return fmt.Sprintf("%s.%s", g.decl.name, g.t.Name.Name)
-}
-
-func (g *testGenerator) forEachField(fn func(f *ast.Field)) {
- // This is guaranteed to succeed because g.t is always a struct.
- st := g.t.Type.(*ast.StructType)
- for _, field := range st.Fields.List {
- fn(field)
- }
-}
-
-func (g *testGenerator) testFuncName(base string) string {
- return fmt.Sprintf("%s%s", base, strings.Title(g.t.Name.Name))
-}
-
-func (g *testGenerator) inTestFunction(name string, body func()) {
- g.emit("func %s(t *testing.T) {\n", g.testFuncName(name))
- g.inIndent(body)
- g.emit("}\n\n")
-}
-
-func (g *testGenerator) emitTestNonZeroSize() {
- g.inTestFunction("TestSizeNonZero", func() {
- g.emit("x := &%s{}\n", g.typeName())
- g.emit("if x.SizeBytes() == 0 {\n")
- g.inIndent(func() {
- g.emit("t.Fatal(\"Marshallable.Size() should not return zero\")\n")
- })
- g.emit("}\n")
- })
-}
-
-func (g *testGenerator) emitTestSuspectAlignment() {
- g.inTestFunction("TestSuspectAlignment", func() {
- g.emit("x := %s{}\n", g.typeName())
- g.emit("analysis.AlignmentCheck(t, reflect.TypeOf(x))\n")
- })
-}
-
-func (g *testGenerator) emitTestMarshalUnmarshalPreservesData() {
- g.inTestFunction("TestSafeMarshalUnmarshalPreservesData", func() {
- g.emit("var x, y, z, yUnsafe, zUnsafe %s\n", g.typeName())
- g.emit("analysis.RandomizeValue(&x)\n\n")
-
- g.emit("buf := make([]byte, x.SizeBytes())\n")
- g.emit("x.MarshalBytes(buf)\n")
- g.emit("bufUnsafe := make([]byte, x.SizeBytes())\n")
- g.emit("x.MarshalUnsafe(bufUnsafe)\n\n")
-
- g.emit("y.UnmarshalBytes(buf)\n")
- g.emit("if !reflect.DeepEqual(x, y) {\n")
- g.inIndent(func() {
- g.emit("t.Fatal(fmt.Sprintf(\"Data corrupted across Marshal/Unmarshal cycle:\\nBefore: %%+v\\nAfter: %%+v\\n\", x, y))\n")
- })
- g.emit("}\n")
- g.emit("yUnsafe.UnmarshalBytes(bufUnsafe)\n")
- g.emit("if !reflect.DeepEqual(x, yUnsafe) {\n")
- g.inIndent(func() {
- g.emit("t.Fatal(fmt.Sprintf(\"Data corrupted across MarshalUnsafe/Unmarshal cycle:\\nBefore: %%+v\\nAfter: %%+v\\n\", x, yUnsafe))\n")
- })
- g.emit("}\n\n")
-
- g.emit("z.UnmarshalUnsafe(buf)\n")
- g.emit("if !reflect.DeepEqual(x, z) {\n")
- g.inIndent(func() {
- g.emit("t.Fatal(fmt.Sprintf(\"Data corrupted across Marshal/UnmarshalUnsafe cycle:\\nBefore: %%+v\\nAfter: %%+v\\n\", x, z))\n")
- })
- g.emit("}\n")
- g.emit("zUnsafe.UnmarshalUnsafe(bufUnsafe)\n")
- g.emit("if !reflect.DeepEqual(x, zUnsafe) {\n")
- g.inIndent(func() {
- g.emit("t.Fatal(fmt.Sprintf(\"Data corrupted across MarshalUnsafe/UnmarshalUnsafe cycle:\\nBefore: %%+v\\nAfter: %%+v\\n\", x, zUnsafe))\n")
- })
- g.emit("}\n")
- })
-}
-
-func (g *testGenerator) emitTests() {
- g.emitTestNonZeroSize()
- g.emitTestSuspectAlignment()
- g.emitTestMarshalUnmarshalPreservesData()
-}
-
-func (g *testGenerator) write(out io.Writer) error {
- return g.sourceBuffer.write(out)
-}
diff --git a/tools/go_marshal/gomarshal/util.go b/tools/go_marshal/gomarshal/util.go
deleted file mode 100644
index 967537abf..000000000
--- a/tools/go_marshal/gomarshal/util.go
+++ /dev/null
@@ -1,387 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package gomarshal
-
-import (
- "bytes"
- "flag"
- "fmt"
- "go/ast"
- "go/token"
- "io"
- "os"
- "path"
- "reflect"
- "sort"
- "strconv"
- "strings"
-)
-
-var debug = flag.Bool("debug", false, "enables debugging output")
-
-// receiverName returns an appropriate receiver name given a type spec.
-func receiverName(t *ast.TypeSpec) string {
- if len(t.Name.Name) < 1 {
- // Zero length type name?
- panic("unreachable")
- }
- return strings.ToLower(t.Name.Name[:1])
-}
-
-// kindString returns a user-friendly representation of an AST expr type.
-func kindString(e ast.Expr) string {
- switch e.(type) {
- case *ast.Ident:
- return "scalar"
- case *ast.ArrayType:
- return "array"
- case *ast.StructType:
- return "struct"
- case *ast.StarExpr:
- return "pointer"
- case *ast.FuncType:
- return "function"
- case *ast.InterfaceType:
- return "interface"
- case *ast.MapType:
- return "map"
- case *ast.ChanType:
- return "channel"
- default:
- return reflect.TypeOf(e).String()
- }
-}
-
-// fieldDispatcher is a collection of callbacks for handling different types of
-// fields in a struct declaration.
-type fieldDispatcher struct {
- primitive func(n, t *ast.Ident)
- selector func(n, tX, tSel *ast.Ident)
- array func(n, t *ast.Ident, size int)
- unhandled func(n *ast.Ident)
-}
-
-// Precondition: All dispatch callbacks that will be invoked must be
-// provided. Embedded fields are not allowed, len(f.Names) >= 1.
-func (fd fieldDispatcher) dispatch(f *ast.Field) {
- // Each field declaration may actually be multiple declarations of the same
- // type. For example, consider:
- //
- // type Point struct {
- // x, y, z int
- // }
- //
- // We invoke the call-backs once per such instance. Embedded fields are not
- // allowed, and results in a panic.
- if len(f.Names) < 1 {
- panic("Precondition not met: attempted to dispatch on embedded field")
- }
-
- for _, name := range f.Names {
- switch v := f.Type.(type) {
- case *ast.Ident:
- fd.primitive(name, v)
- case *ast.SelectorExpr:
- fd.selector(name, v.X.(*ast.Ident), v.Sel)
- case *ast.ArrayType:
- len := 0
- if v.Len != nil {
- // Non-literal array length is handled by generatorInterfaces.validate().
- if lenLit, ok := v.Len.(*ast.BasicLit); ok {
- var err error
- len, err = strconv.Atoi(lenLit.Value)
- if err != nil {
- panic(err)
- }
- }
- }
- switch t := v.Elt.(type) {
- case *ast.Ident:
- fd.array(name, t, len)
- default:
- fd.array(name, nil, len)
- }
- default:
- fd.unhandled(name)
- }
- }
-}
-
-// debugEnabled indicates whether debugging is enabled for gomarshal.
-func debugEnabled() bool {
- return *debug
-}
-
-// abort aborts the go_marshal tool with the given error message.
-func abort(msg string) {
- if !strings.HasSuffix(msg, "\n") {
- msg += "\n"
- }
- fmt.Print(msg)
- os.Exit(1)
-}
-
-// abortAt aborts the go_marshal tool with the given error message, with
-// a reference position to the input source.
-func abortAt(p token.Position, msg string) {
- abort(fmt.Sprintf("%v:\n %s\n", p, msg))
-}
-
-// debugf conditionally prints a debug message.
-func debugf(f string, a ...interface{}) {
- if debugEnabled() {
- fmt.Printf(f, a...)
- }
-}
-
-// debugfAt conditionally prints a debug message with a reference to a position
-// in the input source.
-func debugfAt(p token.Position, f string, a ...interface{}) {
- if debugEnabled() {
- fmt.Printf("%s:\n %s", p, fmt.Sprintf(f, a...))
- }
-}
-
-// emit generates a line of code in the output file.
-//
-// emit is a wrapper around writing a formatted string to the output
-// buffer. emit can be invoked in one of two ways:
-//
-// (1) emit("some string")
-// When emit is called with a single string argument, it is simply copied to
-// the output buffer without any further formatting.
-// (2) emit(fmtString, args...)
-// emit can also be invoked in a similar fashion to *Printf() functions,
-// where the first argument is a format string.
-//
-// Calling emit with a single argument that is not a string will result in a
-// panic, as the caller's intent is ambiguous.
-func emit(out io.Writer, indent int, a ...interface{}) {
- const spacesPerIndentLevel = 4
-
- if len(a) < 1 {
- panic("emit() called with no arguments")
- }
-
- if indent > 0 {
- if _, err := fmt.Fprint(out, strings.Repeat(" ", indent*spacesPerIndentLevel)); err != nil {
- // Writing to the emit output should not fail. Typically the output
- // is a byte.Buffer; writes to these never fail.
- panic(err)
- }
- }
-
- first, ok := a[0].(string)
- if !ok {
- // First argument must be either the string to emit (case 1 from
- // function-level comment), or a format string (case 2).
- panic(fmt.Sprintf("First argument to emit() is not a string: %+v", a[0]))
- }
-
- if len(a) == 1 {
- // Single string argument. Assume no formatting requested.
- if _, err := fmt.Fprint(out, first); err != nil {
- // Writing to out should not fail.
- panic(err)
- }
- return
-
- }
-
- // Formatting requested.
- if _, err := fmt.Fprintf(out, first, a[1:]...); err != nil {
- // Writing to out should not fail.
- panic(err)
- }
-}
-
-// sourceBuffer represents fragments of generated go source code.
-//
-// sourceBuffer provides a convenient way to build up go souce fragments in
-// memory. May be safely zero-value initialized. Not thread-safe.
-type sourceBuffer struct {
- // Current indentation level.
- indent int
-
- // Memory buffer containing contents while they're being generated.
- b bytes.Buffer
-}
-
-func (b *sourceBuffer) incIndent() {
- b.indent++
-}
-
-func (b *sourceBuffer) decIndent() {
- if b.indent <= 0 {
- panic("decIndent() without matching incIndent()")
- }
- b.indent--
-}
-
-func (b *sourceBuffer) emit(a ...interface{}) {
- emit(&b.b, b.indent, a...)
-}
-
-func (b *sourceBuffer) emitNoIndent(a ...interface{}) {
- emit(&b.b, 0 /*indent*/, a...)
-}
-
-func (b *sourceBuffer) inIndent(body func()) {
- b.incIndent()
- body()
- b.decIndent()
-}
-
-func (b *sourceBuffer) write(out io.Writer) error {
- _, err := fmt.Fprint(out, b.b.String())
- return err
-}
-
-// Write implements io.Writer.Write.
-func (b *sourceBuffer) Write(buf []byte) (int, error) {
- return (b.b.Write(buf))
-}
-
-// importStmt represents a single import statement.
-type importStmt struct {
- // Local name of the imported package.
- name string
- // Import path.
- path string
- // Indicates whether the local name is an alias, or simply the final
- // component of the path.
- aliased bool
- // Indicates whether this import was referenced by generated code.
- used bool
-}
-
-func newImport(p string) *importStmt {
- name := path.Base(p)
- return &importStmt{
- name: name,
- path: p,
- aliased: false,
- }
-}
-
-func newImportFromSpec(spec *ast.ImportSpec, f *token.FileSet) *importStmt {
- p := spec.Path.Value[1 : len(spec.Path.Value)-1] // Strip the " quotes around path.
- name := path.Base(p)
- if name == "" || name == "/" || name == "." {
- panic(fmt.Sprintf("Couldn't process local package name for import at %s, (processed as %s)",
- f.Position(spec.Path.Pos()), name))
- }
- if spec.Name != nil {
- name = spec.Name.Name
- }
- return &importStmt{
- name: name,
- path: p,
- aliased: spec.Name != nil,
- }
-}
-
-func (i *importStmt) String() string {
- if i.aliased {
- return fmt.Sprintf("%s \"%s\"", i.name, i.path)
- }
- return fmt.Sprintf("\"%s\"", i.path)
-}
-
-func (i *importStmt) markUsed() {
- i.used = true
-}
-
-func (i *importStmt) equivalent(other *importStmt) bool {
- return i == other
-}
-
-// importTable represents a collection of importStmts.
-type importTable struct {
- // Map of imports and whether they should be copied to the output.
- is map[string]*importStmt
-}
-
-func newImportTable() *importTable {
- return &importTable{
- is: make(map[string]*importStmt),
- }
-}
-
-// Merges import statements from other into i. Collisions in import statements
-// result in a panic.
-func (i *importTable) merge(other *importTable) {
- for name, im := range other.is {
- if dup, ok := i.is[name]; ok && dup.equivalent(im) {
- panic(fmt.Sprintf("Found colliding import statements: ours: %+v, other's: %+v", dup, im))
- }
-
- i.is[name] = im
- }
-}
-
-func (i *importTable) add(s string) *importStmt {
- n := newImport(s)
- i.is[n.name] = n
- return n
-}
-
-func (i *importTable) addFromSpec(spec *ast.ImportSpec, f *token.FileSet) *importStmt {
- n := newImportFromSpec(spec, f)
- i.is[n.name] = n
- return n
-}
-
-// Marks the import named n as used. If no such import is in the table, returns
-// false.
-func (i *importTable) markUsed(n string) bool {
- if n, ok := i.is[n]; ok {
- n.markUsed()
- return true
- }
- return false
-}
-
-func (i *importTable) clear() {
- for _, i := range i.is {
- i.used = false
- }
-}
-
-func (i *importTable) write(out io.Writer) error {
- if len(i.is) == 0 {
- // Nothing to import, we're done.
- return nil
- }
-
- imports := make([]string, 0, len(i.is))
- for _, i := range i.is {
- if i.used {
- imports = append(imports, i.String())
- }
- }
- sort.Strings(imports)
-
- var b sourceBuffer
- b.emit("import (\n")
- b.incIndent()
- for _, i := range imports {
- b.emit("%s\n", i)
- }
- b.decIndent()
- b.emit(")\n\n")
-
- return b.write(out)
-}
diff --git a/tools/go_marshal/main.go b/tools/go_marshal/main.go
deleted file mode 100644
index 3d12eb93c..000000000
--- a/tools/go_marshal/main.go
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// go_marshal is a code generation utility for automatically generating code to
-// marshal go data structures to memory.
-//
-// This binary is typically run as part of the build process, and is invoked by
-// the go_marshal bazel rule defined in defs.bzl.
-//
-// See README.md.
-package main
-
-import (
- "flag"
- "fmt"
- "os"
- "strings"
-
- "gvisor.dev/gvisor/tools/go_marshal/gomarshal"
-)
-
-var (
- pkg = flag.String("pkg", "", "output package")
- output = flag.String("output", "", "output file")
- outputTest = flag.String("output_test", "", "output file for tests")
- imports = flag.String("imports", "", "comma-separated list of extra packages to import in generated code")
- declarationPkg = flag.String("declarationPkg", "", "import path of target declaring the types we're generating on")
-)
-
-func main() {
- flag.Usage = func() {
- fmt.Fprintf(os.Stderr, "Usage: %s <input go src files>\n", os.Args[0])
- flag.PrintDefaults()
- }
- flag.Parse()
- if len(flag.Args()) == 0 {
- flag.Usage()
- os.Exit(1)
- }
-
- if *pkg == "" {
- flag.Usage()
- fmt.Fprint(os.Stderr, "Flag -pkg must be provided.\n")
- os.Exit(1)
- }
-
- var extraImports []string
- if len(*imports) > 0 {
- // Note: strings.Split(s, sep) returns s if sep doesn't exist in s. Thus
- // we check for an empty imports list to avoid emitting an empty string
- // as an import.
- extraImports = strings.Split(*imports, ",")
- }
- g, err := gomarshal.NewGenerator(flag.Args(), *output, *outputTest, *pkg, *declarationPkg, extraImports)
- if err != nil {
- panic(err)
- }
-
- if err := g.Run(); err != nil {
- panic(err)
- }
-}
diff --git a/tools/go_marshal/marshal/BUILD b/tools/go_marshal/marshal/BUILD
deleted file mode 100644
index 47dda97a1..000000000
--- a/tools/go_marshal/marshal/BUILD
+++ /dev/null
@@ -1,14 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "marshal",
- srcs = [
- "marshal.go",
- ],
- importpath = "gvisor.dev/gvisor/tools/go_marshal/marshal",
- visibility = [
- "//:sandbox",
- ],
-)
diff --git a/tools/go_marshal/marshal/marshal.go b/tools/go_marshal/marshal/marshal.go
deleted file mode 100644
index a313a27ed..000000000
--- a/tools/go_marshal/marshal/marshal.go
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package marshal defines the Marshallable interface for
-// serialize/deserializing go data structures to/from memory, according to the
-// Linux ABI.
-//
-// Implementations of this interface are typically automatically generated by
-// tools/go_marshal. See the go_marshal README for details.
-package marshal
-
-// Marshallable represents a type that can be marshalled to and from memory.
-type Marshallable interface {
- // SizeBytes is the size of the memory representation of a type in
- // marshalled form.
- SizeBytes() int
-
- // MarshalBytes serializes a copy of a type to dst. dst must be at least
- // SizeBytes() long.
- MarshalBytes(dst []byte)
-
- // UnmarshalBytes deserializes a type from src. src must be at least
- // SizeBytes() long.
- UnmarshalBytes(src []byte)
-
- // Packed returns true if the marshalled size of the type is the same as the
- // size it occupies in memory. This happens when the type has no fields
- // starting at unaligned addresses (should always be true by default for ABI
- // structs, verified by automatically generated tests when using
- // go_marshal), and has no fields marked `marshal:"unaligned"`.
- Packed() bool
-
- // MarshalUnsafe serializes a type by bulk copying its in-memory
- // representation to the dst buffer. This is only safe to do when the type
- // has no implicit padding, see Marshallable.Packed. When Packed would
- // return false, MarshalUnsafe should fall back to the safer but slower
- // MarshalBytes.
- MarshalUnsafe(dst []byte)
-
- // UnmarshalUnsafe deserializes a type directly to the underlying memory
- // allocated for the object by the runtime.
- //
- // This allows much faster unmarshalling of types which have no implicit
- // padding, see Marshallable.Packed. When Packed would return false,
- // UnmarshalUnsafe should fall back to the safer but slower unmarshal
- // mechanism implemented in UnmarshalBytes (usually by calling
- // UnmarshalBytes directly).
- UnmarshalUnsafe(src []byte)
-}
diff --git a/tools/go_marshal/test/BUILD b/tools/go_marshal/test/BUILD
deleted file mode 100644
index fa82f8e9b..000000000
--- a/tools/go_marshal/test/BUILD
+++ /dev/null
@@ -1,31 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-load("//tools/go_marshal:defs.bzl", "go_library")
-
-package_group(
- name = "gomarshal_test",
- packages = [
- "//tools/go_marshal/test/...",
- ],
-)
-
-go_test(
- name = "benchmark_test",
- srcs = ["benchmark_test.go"],
- deps = [
- ":test",
- "//pkg/binary",
- "//pkg/sentry/usermem",
- "//tools/go_marshal/analysis",
- ],
-)
-
-go_library(
- name = "test",
- testonly = 1,
- srcs = ["test.go"],
- importpath = "gvisor.dev/gvisor/tools/go_marshal/test",
- deps = ["//tools/go_marshal/test/external"],
-)
diff --git a/tools/go_marshal/test/benchmark_test.go b/tools/go_marshal/test/benchmark_test.go
deleted file mode 100644
index e70db06d8..000000000
--- a/tools/go_marshal/test/benchmark_test.go
+++ /dev/null
@@ -1,178 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package benchmark_test
-
-import (
- "bytes"
- encbin "encoding/binary"
- "fmt"
- "reflect"
- "testing"
-
- "gvisor.dev/gvisor/pkg/binary"
- "gvisor.dev/gvisor/pkg/sentry/usermem"
- "gvisor.dev/gvisor/tools/go_marshal/analysis"
- test "gvisor.dev/gvisor/tools/go_marshal/test"
-)
-
-// Marshalling using the standard encoding/binary package.
-func BenchmarkEncodingBinary(b *testing.B) {
- var s1, s2 test.Stat
- analysis.RandomizeValue(&s1)
-
- size := encbin.Size(&s1)
-
- b.ResetTimer()
-
- for n := 0; n < b.N; n++ {
- buf := bytes.NewBuffer(make([]byte, size))
- buf.Reset()
- if err := encbin.Write(buf, usermem.ByteOrder, &s1); err != nil {
- b.Error("Write:", err)
- }
- if err := encbin.Read(buf, usermem.ByteOrder, &s2); err != nil {
- b.Error("Read:", err)
- }
- }
-
- b.StopTimer()
-
- // Sanity check, make sure the values were preserved.
- if !reflect.DeepEqual(s1, s2) {
- panic(fmt.Sprintf("Data corruption across marshal/unmarshal cycle:\nBefore: %+v\nAfter: %+v\n", s1, s2))
- }
-}
-
-// Marshalling using the sentry's binary.Marshal.
-func BenchmarkBinary(b *testing.B) {
- var s1, s2 test.Stat
- analysis.RandomizeValue(&s1)
-
- size := binary.Size(s1)
-
- b.ResetTimer()
-
- for n := 0; n < b.N; n++ {
- buf := make([]byte, 0, size)
- buf = binary.Marshal(buf, usermem.ByteOrder, &s1)
- binary.Unmarshal(buf, usermem.ByteOrder, &s2)
- }
-
- b.StopTimer()
-
- // Sanity check, make sure the values were preserved.
- if !reflect.DeepEqual(s1, s2) {
- panic(fmt.Sprintf("Data corruption across marshal/unmarshal cycle:\nBefore: %+v\nAfter: %+v\n", s1, s2))
- }
-}
-
-// Marshalling field-by-field with manually-written code.
-func BenchmarkMarshalManual(b *testing.B) {
- var s1, s2 test.Stat
- analysis.RandomizeValue(&s1)
-
- b.ResetTimer()
-
- for n := 0; n < b.N; n++ {
- buf := make([]byte, 0, s1.SizeBytes())
-
- // Marshal
- buf = binary.AppendUint64(buf, usermem.ByteOrder, s1.Dev)
- buf = binary.AppendUint64(buf, usermem.ByteOrder, s1.Ino)
- buf = binary.AppendUint64(buf, usermem.ByteOrder, s1.Nlink)
- buf = binary.AppendUint32(buf, usermem.ByteOrder, s1.Mode)
- buf = binary.AppendUint32(buf, usermem.ByteOrder, s1.UID)
- buf = binary.AppendUint32(buf, usermem.ByteOrder, s1.GID)
- buf = binary.AppendUint32(buf, usermem.ByteOrder, 0)
- buf = binary.AppendUint64(buf, usermem.ByteOrder, s1.Rdev)
- buf = binary.AppendUint64(buf, usermem.ByteOrder, uint64(s1.Size))
- buf = binary.AppendUint64(buf, usermem.ByteOrder, uint64(s1.Blksize))
- buf = binary.AppendUint64(buf, usermem.ByteOrder, uint64(s1.Blocks))
- buf = binary.AppendUint64(buf, usermem.ByteOrder, uint64(s1.ATime.Sec))
- buf = binary.AppendUint64(buf, usermem.ByteOrder, uint64(s1.ATime.Nsec))
- buf = binary.AppendUint64(buf, usermem.ByteOrder, uint64(s1.MTime.Sec))
- buf = binary.AppendUint64(buf, usermem.ByteOrder, uint64(s1.MTime.Nsec))
- buf = binary.AppendUint64(buf, usermem.ByteOrder, uint64(s1.CTime.Sec))
- buf = binary.AppendUint64(buf, usermem.ByteOrder, uint64(s1.CTime.Nsec))
-
- // Unmarshal
- s2.Dev = usermem.ByteOrder.Uint64(buf[0:8])
- s2.Ino = usermem.ByteOrder.Uint64(buf[8:16])
- s2.Nlink = usermem.ByteOrder.Uint64(buf[16:24])
- s2.Mode = usermem.ByteOrder.Uint32(buf[24:28])
- s2.UID = usermem.ByteOrder.Uint32(buf[28:32])
- s2.GID = usermem.ByteOrder.Uint32(buf[32:36])
- // Padding: buf[36:40]
- s2.Rdev = usermem.ByteOrder.Uint64(buf[40:48])
- s2.Size = int64(usermem.ByteOrder.Uint64(buf[48:56]))
- s2.Blksize = int64(usermem.ByteOrder.Uint64(buf[56:64]))
- s2.Blocks = int64(usermem.ByteOrder.Uint64(buf[64:72]))
- s2.ATime.Sec = int64(usermem.ByteOrder.Uint64(buf[72:80]))
- s2.ATime.Nsec = int64(usermem.ByteOrder.Uint64(buf[80:88]))
- s2.MTime.Sec = int64(usermem.ByteOrder.Uint64(buf[88:96]))
- s2.MTime.Nsec = int64(usermem.ByteOrder.Uint64(buf[96:104]))
- s2.CTime.Sec = int64(usermem.ByteOrder.Uint64(buf[104:112]))
- s2.CTime.Nsec = int64(usermem.ByteOrder.Uint64(buf[112:120]))
- }
-
- b.StopTimer()
-
- // Sanity check, make sure the values were preserved.
- if !reflect.DeepEqual(s1, s2) {
- panic(fmt.Sprintf("Data corruption across marshal/unmarshal cycle:\nBefore: %+v\nAfter: %+v\n", s1, s2))
- }
-}
-
-// Marshalling with the go_marshal safe API.
-func BenchmarkGoMarshalSafe(b *testing.B) {
- var s1, s2 test.Stat
- analysis.RandomizeValue(&s1)
-
- b.ResetTimer()
-
- for n := 0; n < b.N; n++ {
- buf := make([]byte, s1.SizeBytes())
- s1.MarshalBytes(buf)
- s2.UnmarshalBytes(buf)
- }
-
- b.StopTimer()
-
- // Sanity check, make sure the values were preserved.
- if !reflect.DeepEqual(s1, s2) {
- panic(fmt.Sprintf("Data corruption across marshal/unmarshal cycle:\nBefore: %+v\nAfter: %+v\n", s1, s2))
- }
-}
-
-// Marshalling with the go_marshal unsafe API.
-func BenchmarkGoMarshalUnsafe(b *testing.B) {
- var s1, s2 test.Stat
- analysis.RandomizeValue(&s1)
-
- b.ResetTimer()
-
- for n := 0; n < b.N; n++ {
- buf := make([]byte, s1.SizeBytes())
- s1.MarshalUnsafe(buf)
- s2.UnmarshalUnsafe(buf)
- }
-
- b.StopTimer()
-
- // Sanity check, make sure the values were preserved.
- if !reflect.DeepEqual(s1, s2) {
- panic(fmt.Sprintf("Data corruption across marshal/unmarshal cycle:\nBefore: %+v\nAfter: %+v\n", s1, s2))
- }
-}
diff --git a/tools/go_marshal/test/external/BUILD b/tools/go_marshal/test/external/BUILD
deleted file mode 100644
index 8fb43179b..000000000
--- a/tools/go_marshal/test/external/BUILD
+++ /dev/null
@@ -1,11 +0,0 @@
-package(licenses = ["notice"])
-
-load("//tools/go_marshal:defs.bzl", "go_library")
-
-go_library(
- name = "external",
- testonly = 1,
- srcs = ["external.go"],
- importpath = "gvisor.dev/gvisor/tools/go_marshal/test/external",
- visibility = ["//tools/go_marshal/test:gomarshal_test"],
-)
diff --git a/tools/go_marshal/test/external/external.go b/tools/go_marshal/test/external/external.go
deleted file mode 100644
index 4be3722f3..000000000
--- a/tools/go_marshal/test/external/external.go
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package external defines types we can import for testing.
-package external
-
-// External is a public Marshallable type for use in testing.
-//
-// +marshal
-type External struct {
- j int64
-}
diff --git a/tools/go_marshal/test/test.go b/tools/go_marshal/test/test.go
deleted file mode 100644
index 8de02d707..000000000
--- a/tools/go_marshal/test/test.go
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package test contains data structures for testing the go_marshal tool.
-package test
-
-import (
- // We're intentionally using a package name alias here even though it's not
- // necessary to test the code generator's ability to handle package aliases.
- ex "gvisor.dev/gvisor/tools/go_marshal/test/external"
-)
-
-// Type1 is a test data type.
-//
-// +marshal
-type Type1 struct {
- a Type2
- x, y int64 // Multiple field names.
- b byte `marshal:"unaligned"` // Short field.
- c uint64
- _ uint32 // Unnamed scalar field.
- _ [6]byte // Unnamed vector field, typical padding.
- _ [2]byte
- xs [8]int32
- as [10]Type2 `marshal:"unaligned"` // Array of Marshallable objects.
- ss Type3
-}
-
-// Type2 is a test data type.
-//
-// +marshal
-type Type2 struct {
- n int64
- c byte
- _ [7]byte
- m int64
- a int64
-}
-
-// Type3 is a test data type.
-//
-// +marshal
-type Type3 struct {
- s int64
- x ex.External // Type defined in another package.
-}
-
-// Type4 is a test data type.
-//
-// +marshal
-type Type4 struct {
- c byte
- x int64 `marshal:"unaligned"`
- d byte
- _ [7]byte
-}
-
-// Type5 is a test data type.
-//
-// +marshal
-type Type5 struct {
- n int64
- t Type4
- m int64
-}
-
-// Timespec represents struct timespec in <time.h>.
-//
-// +marshal
-type Timespec struct {
- Sec int64
- Nsec int64
-}
-
-// Stat represents struct stat.
-//
-// +marshal
-type Stat struct {
- Dev uint64
- Ino uint64
- Nlink uint64
- Mode uint32
- UID uint32
- GID uint32
- _ int32
- Rdev uint64
- Size int64
- Blksize int64
- Blocks int64
- ATime Timespec
- MTime Timespec
- CTime Timespec
- _ [3]int64
-}
diff --git a/tools/go_stateify/BUILD b/tools/go_stateify/BUILD
deleted file mode 100644
index bb53f8ae9..000000000
--- a/tools/go_stateify/BUILD
+++ /dev/null
@@ -1,9 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary")
-
-package(licenses = ["notice"])
-
-go_binary(
- name = "stateify",
- srcs = ["main.go"],
- visibility = ["//visibility:public"],
-)
diff --git a/tools/go_stateify/defs.bzl b/tools/go_stateify/defs.bzl
deleted file mode 100644
index 3ce36c1c8..000000000
--- a/tools/go_stateify/defs.bzl
+++ /dev/null
@@ -1,136 +0,0 @@
-"""Stateify is a tool for generating state wrappers for Go types.
-
-The recommended way is to use the go_library rule defined below with mostly
-identical configuration as the native go_library rule.
-
-load("//tools/go_stateify:defs.bzl", "go_library")
-
-go_library(
- name = "foo",
- srcs = ["foo.go"],
-)
-
-Under the hood, the go_stateify rule is used to generate a file that will
-appear in a Go target; the output file should appear explicitly in a srcs list.
-For example (the above is still the preferred way):
-
-load("//tools/go_stateify:defs.bzl", "go_stateify")
-
-go_stateify(
- name = "foo_state",
- srcs = ["foo.go"],
- out = "foo_state.go",
- package = "foo",
-)
-
-go_library(
- name = "foo",
- srcs = [
- "foo.go",
- "foo_state.go",
- ],
- deps = [
- "//pkg/state",
- ],
-)
-"""
-
-load("@io_bazel_rules_go//go:def.bzl", _go_library = "go_library")
-
-def _go_stateify_impl(ctx):
- """Implementation for the stateify tool."""
- output = ctx.outputs.out
-
- # Run the stateify command.
- args = ["-output=%s" % output.path]
- args += ["-pkg=%s" % ctx.attr.package]
- if ctx.attr._statepkg:
- args += ["-statepkg=%s" % ctx.attr._statepkg]
- if ctx.attr.imports:
- args += ["-imports=%s" % ",".join(ctx.attr.imports)]
- args += ["--"]
- for src in ctx.attr.srcs:
- args += [f.path for f in src.files.to_list()]
- ctx.actions.run(
- inputs = ctx.files.srcs,
- outputs = [output],
- mnemonic = "GoStateify",
- progress_message = "Generating state library %s" % ctx.label,
- arguments = args,
- executable = ctx.executable._tool,
- )
-
-go_stateify = rule(
- implementation = _go_stateify_impl,
- doc = "Generates save and restore logic from a set of Go files.",
- attrs = {
- "srcs": attr.label_list(
- doc = """
-The input source files. These files should include all structs in the package
-that need to be saved.
-""",
- mandatory = True,
- allow_files = True,
- ),
- "imports": attr.string_list(
- doc = """
-An optional list of extra non-aliased, Go-style absolute import paths required
-for statified types.
-""",
- mandatory = False,
- ),
- "package": attr.string(
- doc = "The package name for the input sources.",
- mandatory = True,
- ),
- "out": attr.output(
- doc = """
-The name of the generated file output. This must not conflict with any other
-files and must be added to the srcs of the relevant go_library.
-""",
- mandatory = True,
- ),
- "_tool": attr.label(
- executable = True,
- cfg = "host",
- default = Label("//tools/go_stateify:stateify"),
- ),
- "_statepkg": attr.string(default = "gvisor.dev/gvisor/pkg/state"),
- },
-)
-
-def go_library(name, srcs, deps = [], imports = [], **kwargs):
- """Standard go_library wrapped which generates state source files.
-
- Args:
- name: the name of the go_library rule.
- srcs: sources of the go_library. Each will be processed for stateify
- annotations.
- deps: dependencies for the go_library.
- imports: an optional list of extra non-aliased, Go-style absolute import
- paths required for stateified types.
- **kwargs: passed to go_library.
- """
- if "encode_unsafe.go" not in srcs and (name + "_state_autogen.go") not in srcs:
- # Only do stateification for non-state packages without manual autogen.
- go_stateify(
- name = name + "_state_autogen",
- srcs = [src for src in srcs if src.endswith(".go")],
- imports = imports,
- package = name,
- out = name + "_state_autogen.go",
- )
- all_srcs = srcs + [name + "_state_autogen.go"]
- if "//pkg/state" not in deps:
- all_deps = deps + ["//pkg/state"]
- else:
- all_deps = deps
- else:
- all_deps = deps
- all_srcs = srcs
- _go_library(
- name = name,
- srcs = all_srcs,
- deps = all_deps,
- **kwargs
- )
diff --git a/tools/go_stateify/main.go b/tools/go_stateify/main.go
deleted file mode 100644
index db7a7107b..000000000
--- a/tools/go_stateify/main.go
+++ /dev/null
@@ -1,418 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Stateify provides a simple way to generate Load/Save methods based on
-// existing types and struct tags.
-package main
-
-import (
- "flag"
- "fmt"
- "go/ast"
- "go/parser"
- "go/token"
- "os"
- "reflect"
- "strings"
- "sync"
-)
-
-var (
- pkg = flag.String("pkg", "", "output package")
- imports = flag.String("imports", "", "extra imports for the output file")
- output = flag.String("output", "", "output file")
- statePkg = flag.String("statepkg", "", "state import package; defaults to empty")
-)
-
-// resolveTypeName returns a qualified type name.
-func resolveTypeName(name string, typ ast.Expr) (field string, qualified string) {
- for done := false; !done; {
- // Resolve star expressions.
- switch rs := typ.(type) {
- case *ast.StarExpr:
- qualified += "*"
- typ = rs.X
- case *ast.ArrayType:
- if rs.Len == nil {
- // Slice type declaration.
- qualified += "[]"
- } else {
- // Array type declaration.
- qualified += "[" + rs.Len.(*ast.BasicLit).Value + "]"
- }
- typ = rs.Elt
- default:
- // No more descent.
- done = true
- }
- }
-
- // Resolve a package selector.
- sel, ok := typ.(*ast.SelectorExpr)
- if ok {
- qualified = qualified + sel.X.(*ast.Ident).Name + "."
- typ = sel.Sel
- }
-
- // Figure out actual type name.
- ident, ok := typ.(*ast.Ident)
- if !ok {
- panic(fmt.Sprintf("type not supported: %s (involves anonymous types?)", name))
- }
- field = ident.Name
- qualified = qualified + field
- return
-}
-
-// extractStateTag pulls the relevant state tag.
-func extractStateTag(tag *ast.BasicLit) string {
- if tag == nil {
- return ""
- }
- if len(tag.Value) < 2 {
- return ""
- }
- return reflect.StructTag(tag.Value[1 : len(tag.Value)-1]).Get("state")
-}
-
-// scanFunctions is a set of functions passed to scanFields.
-type scanFunctions struct {
- zerovalue func(name string)
- normal func(name string)
- wait func(name string)
- value func(name, typName string)
-}
-
-// scanFields scans the fields of a struct.
-//
-// Each provided function will be applied to appropriately tagged fields, or
-// skipped if nil.
-//
-// Fields tagged nosave are skipped.
-func scanFields(ss *ast.StructType, fn scanFunctions) {
- if ss.Fields.List == nil {
- // No fields.
- return
- }
-
- // Scan all fields.
- for _, field := range ss.Fields.List {
- // Calculate the name.
- name := ""
- if field.Names != nil {
- // It's a named field; override.
- name = field.Names[0].Name
- } else {
- // Anonymous types can't be embedded, so we don't need
- // to worry about providing a useful name here.
- name, _ = resolveTypeName("", field.Type)
- }
-
- // Skip _ fields.
- if name == "_" {
- continue
- }
-
- switch tag := extractStateTag(field.Tag); tag {
- case "zerovalue":
- if fn.zerovalue != nil {
- fn.zerovalue(name)
- }
-
- case "":
- if fn.normal != nil {
- fn.normal(name)
- }
-
- case "wait":
- if fn.wait != nil {
- fn.wait(name)
- }
-
- case "manual", "nosave", "ignore":
- // Do nothing.
-
- default:
- if strings.HasPrefix(tag, ".(") && strings.HasSuffix(tag, ")") {
- if fn.value != nil {
- fn.value(name, tag[2:len(tag)-1])
- }
- }
- }
- }
-}
-
-func camelCased(name string) string {
- return strings.ToUpper(name[:1]) + name[1:]
-}
-
-func main() {
- // Parse flags.
- flag.Usage = func() {
- fmt.Fprintf(os.Stderr, "Usage: %s [options]\n", os.Args[0])
- flag.PrintDefaults()
- }
- flag.Parse()
- if len(flag.Args()) == 0 {
- flag.Usage()
- os.Exit(1)
- }
- if *pkg == "" {
- fmt.Fprintf(os.Stderr, "Error: package required.")
- os.Exit(1)
- }
-
- // Open the output file.
- var (
- outputFile *os.File
- err error
- )
- if *output == "" || *output == "-" {
- outputFile = os.Stdout
- } else {
- outputFile, err = os.OpenFile(*output, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
- if err != nil {
- fmt.Fprintf(os.Stderr, "Error opening output %q: %v", *output, err)
- }
- defer outputFile.Close()
- }
-
- // Set the statePrefix for below, depending on the import.
- statePrefix := ""
- if *statePkg != "" {
- parts := strings.Split(*statePkg, "/")
- statePrefix = parts[len(parts)-1] + "."
- }
-
- // initCalls is dumped at the end.
- var initCalls []string
-
- // Declare our emission closures.
- emitRegister := func(name string) {
- initCalls = append(initCalls, fmt.Sprintf("%sRegister(\"%s.%s\", (*%s)(nil), state.Fns{Save: (*%s).save, Load: (*%s).load})", statePrefix, *pkg, name, name, name, name))
- }
- emitZeroCheck := func(name string) {
- fmt.Fprintf(outputFile, " if !%sIsZeroValue(x.%s) { m.Failf(\"%s is %%v, expected zero\", x.%s) }\n", statePrefix, name, name, name)
- }
- emitLoadValue := func(name, typName string) {
- fmt.Fprintf(outputFile, " m.LoadValue(\"%s\", new(%s), func(y interface{}) { x.load%s(y.(%s)) })\n", name, typName, camelCased(name), typName)
- }
- emitLoad := func(name string) {
- fmt.Fprintf(outputFile, " m.Load(\"%s\", &x.%s)\n", name, name)
- }
- emitLoadWait := func(name string) {
- fmt.Fprintf(outputFile, " m.LoadWait(\"%s\", &x.%s)\n", name, name)
- }
- emitSaveValue := func(name, typName string) {
- fmt.Fprintf(outputFile, " var %s %s = x.save%s()\n", name, typName, camelCased(name))
- fmt.Fprintf(outputFile, " m.SaveValue(\"%s\", %s)\n", name, name)
- }
- emitSave := func(name string) {
- fmt.Fprintf(outputFile, " m.Save(\"%s\", &x.%s)\n", name, name)
- }
-
- // Emit the package name.
- fmt.Fprint(outputFile, "// automatically generated by stateify.\n\n")
- fmt.Fprintf(outputFile, "package %s\n\n", *pkg)
-
- // Emit the imports lazily.
- var once sync.Once
- maybeEmitImports := func() {
- once.Do(func() {
- // Emit the imports.
- fmt.Fprint(outputFile, "import (\n")
- if *statePkg != "" {
- fmt.Fprintf(outputFile, " \"%s\"\n", *statePkg)
- }
- if *imports != "" {
- for _, i := range strings.Split(*imports, ",") {
- fmt.Fprintf(outputFile, " \"%s\"\n", i)
- }
- }
- fmt.Fprint(outputFile, ")\n\n")
- })
- }
-
- files := make([]*ast.File, 0, len(flag.Args()))
-
- // Parse the input files.
- for _, filename := range flag.Args() {
- // Parse the file.
- fset := token.NewFileSet()
- f, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
- if err != nil {
- // Not a valid input file?
- fmt.Fprintf(os.Stderr, "Input %q can't be parsed: %v\n", filename, err)
- os.Exit(1)
- }
- files = append(files, f)
- }
-
- type method struct {
- receiver string
- name string
- }
-
- // Search for and add all methods with a pointer receiver and no other
- // arguments to a set. We support auto-detecting the existence of
- // several different methods with this signature.
- simpleMethods := map[method]struct{}{}
- for _, f := range files {
-
- // Go over all functions.
- for _, decl := range f.Decls {
- d, ok := decl.(*ast.FuncDecl)
- if !ok {
- continue
- }
- if d.Name == nil || d.Recv == nil || d.Type == nil {
- // Not a named method.
- continue
- }
- if len(d.Recv.List) != 1 {
- // Wrong number of receivers?
- continue
- }
- if d.Type.Params != nil && len(d.Type.Params.List) != 0 {
- // Has argument(s).
- continue
- }
- if d.Type.Results != nil && len(d.Type.Results.List) != 0 {
- // Has return(s).
- continue
- }
-
- pt, ok := d.Recv.List[0].Type.(*ast.StarExpr)
- if !ok {
- // Not a pointer receiver.
- continue
- }
-
- t, ok := pt.X.(*ast.Ident)
- if !ok {
- // This shouldn't happen with valid Go.
- continue
- }
-
- simpleMethods[method{t.Name, d.Name.Name}] = struct{}{}
- }
- }
-
- for _, f := range files {
- // Go over all named types.
- for _, decl := range f.Decls {
- d, ok := decl.(*ast.GenDecl)
- if !ok || d.Tok != token.TYPE {
- continue
- }
-
- // Only generate code for types marked
- // "// +stateify savable" in one of the proceeding
- // comment lines.
- if d.Doc == nil {
- continue
- }
- savable := false
- for _, l := range d.Doc.List {
- if l.Text == "// +stateify savable" {
- savable = true
- break
- }
- }
- if !savable {
- continue
- }
-
- for _, gs := range d.Specs {
- ts := gs.(*ast.TypeSpec)
- switch ts.Type.(type) {
- case *ast.InterfaceType, *ast.ChanType, *ast.FuncType, *ast.ParenExpr, *ast.StarExpr:
- // Don't register.
- break
- case *ast.StructType:
- maybeEmitImports()
-
- ss := ts.Type.(*ast.StructType)
-
- // Define beforeSave if a definition was not found. This
- // prevents the code from compiling if a custom beforeSave
- // was defined in a file not provided to this binary and
- // prevents inherited methods from being called multiple times
- // by overriding them.
- if _, ok := simpleMethods[method{ts.Name.Name, "beforeSave"}]; !ok {
- fmt.Fprintf(outputFile, "func (x *%s) beforeSave() {}\n", ts.Name.Name)
- }
-
- // Generate the save method.
- fmt.Fprintf(outputFile, "func (x *%s) save(m %sMap) {\n", ts.Name.Name, statePrefix)
- fmt.Fprintf(outputFile, " x.beforeSave()\n")
- scanFields(ss, scanFunctions{zerovalue: emitZeroCheck})
- scanFields(ss, scanFunctions{value: emitSaveValue})
- scanFields(ss, scanFunctions{normal: emitSave, wait: emitSave})
- fmt.Fprintf(outputFile, "}\n\n")
-
- // Define afterLoad if a definition was not found. We do this
- // for the same reason that we do it for beforeSave.
- _, hasAfterLoad := simpleMethods[method{ts.Name.Name, "afterLoad"}]
- if !hasAfterLoad {
- fmt.Fprintf(outputFile, "func (x *%s) afterLoad() {}\n", ts.Name.Name)
- }
-
- // Generate the load method.
- //
- // Note that the manual loads always follow the
- // automated loads.
- fmt.Fprintf(outputFile, "func (x *%s) load(m %sMap) {\n", ts.Name.Name, statePrefix)
- scanFields(ss, scanFunctions{normal: emitLoad, wait: emitLoadWait})
- scanFields(ss, scanFunctions{value: emitLoadValue})
- if hasAfterLoad {
- // The call to afterLoad is made conditionally, because when
- // AfterLoad is called, the object encodes a dependency on
- // referred objects (i.e. fields). This means that afterLoad
- // will not be called until the other afterLoads are called.
- fmt.Fprintf(outputFile, " m.AfterLoad(x.afterLoad)\n")
- }
- fmt.Fprintf(outputFile, "}\n\n")
-
- // Add to our registration.
- emitRegister(ts.Name.Name)
- case *ast.Ident, *ast.SelectorExpr, *ast.ArrayType:
- maybeEmitImports()
-
- _, val := resolveTypeName(ts.Name.Name, ts.Type)
-
- // Dispatch directly.
- fmt.Fprintf(outputFile, "func (x *%s) save(m %sMap) {\n", ts.Name.Name, statePrefix)
- fmt.Fprintf(outputFile, " m.SaveValue(\"\", (%s)(*x))\n", val)
- fmt.Fprintf(outputFile, "}\n\n")
- fmt.Fprintf(outputFile, "func (x *%s) load(m %sMap) {\n", ts.Name.Name, statePrefix)
- fmt.Fprintf(outputFile, " m.LoadValue(\"\", new(%s), func(y interface{}) { *x = (%s)(y.(%s)) })\n", val, ts.Name.Name, val)
- fmt.Fprintf(outputFile, "}\n\n")
-
- // See above.
- emitRegister(ts.Name.Name)
- }
- }
- }
- }
-
- if len(initCalls) > 0 {
- // Emit the init() function.
- fmt.Fprintf(outputFile, "func init() {\n")
- for _, ic := range initCalls {
- fmt.Fprintf(outputFile, " %s\n", ic)
- }
- fmt.Fprintf(outputFile, "}\n")
- }
-}
diff --git a/tools/image_build.sh b/tools/image_build.sh
deleted file mode 100755
index 9b20a740d..000000000
--- a/tools/image_build.sh
+++ /dev/null
@@ -1,98 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# This script is responsible for building a new GCP image that: 1) has nested
-# virtualization enabled, and 2) has been completely set up with the
-# image_setup.sh script. This script should be idempotent, as we memoize the
-# setup script with a hash and check for that name.
-#
-# The GCP project name should be defined via a gcloud config.
-
-set -xeo pipefail
-
-# Parameters.
-declare -r ZONE=${ZONE:-us-central1-f}
-declare -r USERNAME=${USERNAME:-test}
-declare -r IMAGE_PROJECT=${IMAGE_PROJECT:-ubuntu-os-cloud}
-declare -r IMAGE_FAMILY=${IMAGE_FAMILY:-ubuntu-1604-lts}
-
-# Random names.
-declare -r DISK_NAME=$(mktemp -u disk-XXXXXX | tr A-Z a-z)
-declare -r SNAPSHOT_NAME=$(mktemp -u snapshot-XXXXXX | tr A-Z a-z)
-declare -r INSTANCE_NAME=$(mktemp -u build-XXXXXX | tr A-Z a-z)
-
-# Hashes inputs.
-declare -r SETUP_BLOB=$(echo ${ZONE} ${USERNAME} ${IMAGE_PROJECT} ${IMAGE_FAMILY} && sha256sum "$@")
-declare -r SETUP_HASH=$(echo ${SETUP_BLOB} | sha256sum - | cut -d' ' -f1 | cut -c 1-16)
-declare -r IMAGE_NAME=${IMAGE_NAME:-image-}${SETUP_HASH}
-
-# Does the image already exist? Skip the build.
-declare -r existing=$(gcloud compute images list --filter="name=(${IMAGE_NAME})" --format="value(name)")
-if ! [[ -z "${existing}" ]]; then
- echo "${existing}"
- exit 0
-fi
-
-# Set the zone for all actions.
-gcloud config set compute/zone "${ZONE}"
-
-# Start a unique instance. Note that this instance will have a unique persistent
-# disk as it's boot disk with the same name as the instance.
-gcloud compute instances create \
- --quiet \
- --image-project "${IMAGE_PROJECT}" \
- --image-family "${IMAGE_FAMILY}" \
- --boot-disk-size "200GB" \
- "${INSTANCE_NAME}"
-function cleanup {
- gcloud compute instances delete --quiet "${INSTANCE_NAME}"
-}
-trap cleanup EXIT
-
-# Wait for the instance to become available.
-declare attempts=0
-while [[ "${attempts}" -lt 30 ]]; do
- attempts=$((${attempts}+1))
- if gcloud compute ssh "${USERNAME}"@"${INSTANCE_NAME}" -- true; then
- break
- fi
-done
-if [[ "${attempts}" -ge 30 ]]; then
- echo "too many attempts: failed"
- exit 1
-fi
-
-# Run the install scripts provided.
-for arg; do
- gcloud compute ssh "${USERNAME}"@"${INSTANCE_NAME}" -- sudo bash - <"${arg}"
-done
-
-# Stop the instance; required before creating an image.
-gcloud compute instances stop --quiet "${INSTANCE_NAME}"
-
-# Create a snapshot of the instance disk.
-gcloud compute disks snapshot \
- --quiet \
- --zone="${ZONE}" \
- --snapshot-names="${SNAPSHOT_NAME}" \
- "${INSTANCE_NAME}"
-
-# Create the disk image.
-gcloud compute images create \
- --quiet \
- --source-snapshot="${SNAPSHOT_NAME}" \
- --licenses="https://www.googleapis.com/compute/v1/projects/vm-options/global/licenses/enable-vmx" \
- "${IMAGE_NAME}"
diff --git a/tools/make_repository.sh b/tools/make_repository.sh
deleted file mode 100755
index b16ac6311..000000000
--- a/tools/make_repository.sh
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/bin/bash
-
-# Copyright 2018 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Parse arguments. We require more than two arguments, which are the private
-# keyring, the e-mail associated with the signer, and the list of packages.
-if [ "$#" -le 2 ]; then
- echo "usage: $0 <private-key> <signer-email> <packages...>"
- exit 1
-fi
-declare -r private_key=$(readlink -e "$1")
-declare -r signer="$2"
-shift; shift
-
-# Verbose from this point.
-set -xeo pipefail
-
-# Create a temporary working directory. We don't remove this, as we ultimately
-# print this result and allow the caller to copy wherever they would like.
-declare -r tmpdir=$(mktemp -d /tmp/repoXXXXXX)
-
-# Create a temporary keyring, and ensure it is cleaned up.
-declare -r keyring=$(mktemp /tmp/keyringXXXXXX.gpg)
-cleanup() {
- rm -f "${keyring}"
-}
-trap cleanup EXIT
-gpg --no-default-keyring --keyring "${keyring}" --import "${private_key}" >&2
-
-# Copy the packages, and ensure permissions are correct.
-for pkg in "$@"; do
- name=$(basename "${pkg}" .deb)
- name=$(basename "${name}" .changes)
- arch=${name##*_}
- if [[ "${name}" == "${arch}" ]]; then
- continue # Not a regular package.
- fi
- mkdir -p "${tmpdir}"/binary-"${arch}"
- cp -a "${pkg}" "${tmpdir}"/binary-"${arch}"
-done
-find "${tmpdir}" -type f -exec chmod 0644 {} \;
-
-# Ensure there are no symlinks hanging around; these may be remnants of the
-# build process. They may be useful for other things, but we are going to build
-# an index of the actual packages here.
-find "${tmpdir}" -type l -exec rm -f {} \;
-
-# Sign all packages.
-for file in "${tmpdir}"/binary-*/*.deb; do
- dpkg-sig -g "--no-default-keyring --keyring ${keyring}" --sign builder "${file}" >&2
-done
-
-# Build the package list.
-for dir in "${tmpdir}"/binary-*; do
- (cd "${dir}" && apt-ftparchive packages . | gzip > Packages.gz)
-done
-
-# Build the release list.
-(cd "${tmpdir}" && apt-ftparchive release . > Release)
-
-# Sign the release.
-(cd "${tmpdir}" && gpg --no-default-keyring --keyring "${keyring}" --clearsign -o InRelease Release >&2)
-(cd "${tmpdir}" && gpg --no-default-keyring --keyring "${keyring}" -abs -o Release.gpg Release >&2)
-
-# Show the results.
-echo "${tmpdir}"
diff --git a/tools/nogo.js b/tools/nogo.js
deleted file mode 100644
index fc0a4d1f0..000000000
--- a/tools/nogo.js
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "checkunsafe": {
- "exclude_files": {
- "/external/": "not subject to constraint"
- }
- }
-}
diff --git a/tools/tag_release.sh b/tools/tag_release.sh
deleted file mode 100755
index 9d5a60583..000000000
--- a/tools/tag_release.sh
+++ /dev/null
@@ -1,68 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# This script will optionally map a PiperOrigin-RevId to a given commit,
-# validate a provided release name, create a tag and push it. It must be
-# run manually when a release is created.
-
-set -xeu
-
-# Check arguments.
-if [ "$#" -ne 2 ]; then
- echo "usage: $0 <commit|revid> <release.rc>"
- exit 1
-fi
-
-declare -r target_commit="$1"
-declare -r release="$2"
-
-closest_commit() {
- while read line; do
- if [[ "$line" =~ "commit " ]]; then
- current_commit="${line#commit }"
- continue
- elif [[ "$line" =~ "PiperOrigin-RevId: " ]]; then
- revid="${line#PiperOrigin-RevId: }"
- [[ "${revid}" -le "$1" ]] && break
- fi
- done
- echo "${current_commit}"
-}
-
-# Is the passed identifier a sha commit?
-if ! git show "${target_commit}" &> /dev/null; then
- # Extract the commit given a piper ID.
- declare -r commit="$(git log | closest_commit "${target_commit}")"
-else
- declare -r commit="${target_commit}"
-fi
-if ! git show "${commit}" &> /dev/null; then
- echo "unknown commit: ${target_commit}"
- exit 1
-fi
-
-# Is the release name sane? Must be a date with patch/rc.
-if ! [[ "${release}" =~ ^20[0-9]{6}\.[0-9]+$ ]]; then
- declare -r expected="$(date +%Y%m%d.0)" # Use today's date.
- echo "unexpected release format: ${release}"
- echo " ... expected like ${expected}"
- exit 1
-fi
-
-# Tag the given commit (annotated, to record the committer).
-declare -r tag="release-${release}"
-(git tag -a "${tag}" "${commit}" && git push origin tag "${tag}") || \
- (git tag -d "${tag}" && false)
diff --git a/tools/workspace_status.sh b/tools/workspace_status.sh
deleted file mode 100755
index fb09ff331..000000000
--- a/tools/workspace_status.sh
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-
-# Copyright 2018 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# The STABLE_ prefix will trigger a re-link if it changes.
-echo STABLE_VERSION $(git describe --always --tags --abbrev=12 --dirty)
diff --git a/vdso/BUILD b/vdso/BUILD
deleted file mode 100644
index 7ceed349e..000000000
--- a/vdso/BUILD
+++ /dev/null
@@ -1,92 +0,0 @@
-# Description:
-# This VDSO is a shared library that provides the same interfaces as the
-# normal system VDSO (time, gettimeofday, clock_gettimeofday) but which uses
-# timekeeping parameters managed by the sandbox kernel.
-
-load("@bazel_tools//tools/cpp:cc_flags_supplier.bzl", "cc_flags_supplier")
-
-package(licenses = ["notice"])
-
-config_setting(
- name = "x86_64",
- constraint_values = ["@bazel_tools//platforms:x86_64"],
-)
-
-config_setting(
- name = "aarch64",
- constraint_values = ["@bazel_tools//platforms:aarch64"],
-)
-
-genrule(
- name = "vdso",
- srcs = [
- "barrier.h",
- "compiler.h",
- "cycle_clock.h",
- "seqlock.h",
- "syscalls.h",
- "vdso.cc",
- "vdso_amd64.lds",
- "vdso_arm64.lds",
- "vdso_time.h",
- "vdso_time.cc",
- ],
- outs = [
- "vdso.so",
- ],
- cmd = "$(CC) $(CC_FLAGS) " +
- "-I. " +
- "-O2 " +
- "-std=c++11 " +
- "-fPIC " +
- # Some toolchains enable stack protector by default. Disable it, the
- # VDSO has no hooks to handle failures.
- "-fno-stack-protector " +
- "-fuse-ld=gold " +
- select({
- ":x86_64": "-m64 ",
- "//conditions:default": "",
- }) +
- "-shared " +
- "-nostdlib " +
- "-Wl,-soname=linux-vdso.so.1 " +
- "-Wl,--hash-style=sysv " +
- "-Wl,--no-undefined " +
- "-Wl,-Bsymbolic " +
- "-Wl,-z,max-page-size=4096 " +
- "-Wl,-z,common-page-size=4096 " +
- select(
- {
- ":x86_64": "-Wl,-T$(location vdso_amd64.lds) ",
- ":aarch64": "-Wl,-T$(location vdso_arm64.lds) ",
- },
- no_match_error = "Unsupported architecture",
- ) +
- "-o $(location vdso.so) " +
- "$(location vdso.cc) " +
- "$(location vdso_time.cc) " +
- "&& $(location :check_vdso) " +
- "--check-data " +
- "--vdso $(location vdso.so) ",
- features = ["-pie"],
- toolchains = [
- "@bazel_tools//tools/cpp:current_cc_toolchain",
- ":no_pie_cc_flags",
- ],
- tools = [
- ":check_vdso",
- ],
- visibility = ["//:sandbox"],
-)
-
-cc_flags_supplier(
- name = "no_pie_cc_flags",
- features = ["-pie"],
-)
-
-py_binary(
- name = "check_vdso",
- srcs = ["check_vdso.py"],
- python_version = "PY2",
- visibility = ["//:sandbox"],
-)
diff --git a/vdso/barrier.h b/vdso/barrier.h
deleted file mode 100644
index edba4afb5..000000000
--- a/vdso/barrier.h
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef VDSO_BARRIER_H_
-#define VDSO_BARRIER_H_
-
-namespace vdso {
-
-// Compiler Optimization barrier.
-inline void barrier(void) { __asm__ __volatile__("" ::: "memory"); }
-
-#if __x86_64__
-
-inline void memory_barrier(void) {
- __asm__ __volatile__("mfence" ::: "memory");
-}
-inline void read_barrier(void) { barrier(); }
-inline void write_barrier(void) { barrier(); }
-
-#elif __aarch64__
-
-inline void memory_barrier(void) {
- __asm__ __volatile__("dmb ish" ::: "memory");
-}
-inline void read_barrier(void) {
- __asm__ __volatile__("dmb ishld" ::: "memory");
-}
-inline void write_barrier(void) {
- __asm__ __volatile__("dmb ishst" ::: "memory");
-}
-
-#else
-#error "unsupported architecture"
-#endif
-
-} // namespace vdso
-
-#endif // VDSO_BARRIER_H_
diff --git a/vdso/check_vdso.py b/vdso/check_vdso.py
deleted file mode 100644
index b3ee574f3..000000000
--- a/vdso/check_vdso.py
+++ /dev/null
@@ -1,204 +0,0 @@
-# Copyright 2018 The gVisor Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Verify VDSO ELF does not contain any relocations and is directly mmappable.
-"""
-
-import argparse
-import logging
-import re
-import subprocess
-
-PAGE_SIZE = 4096
-
-
-def PageRoundDown(addr):
- """Rounds down to the nearest page.
-
- Args:
- addr: An address.
-
- Returns:
- The address rounded down to the nearest page.
- """
- return addr & ~(PAGE_SIZE - 1)
-
-
-def Fatal(*args, **kwargs):
- """Logs a critical message and exits with code 1.
-
- Args:
- *args: Args to pass to logging.critical.
- **kwargs: Keyword args to pass to logging.critical.
- """
- logging.critical(*args, **kwargs)
- exit(1)
-
-
-def CheckSegments(vdso_path):
- """Verifies layout of PT_LOAD segments.
-
- PT_LOAD segments must be laid out such that the ELF is directly mmappable.
-
- Specifically, check that:
- * PT_LOAD file offsets are equivalent to the memory offset from the first
- segment.
- * No extra zeroed space (memsz) is required.
- * PT_LOAD segments are in order (required for any ELF).
- * No two PT_LOAD segments share part of the same page.
-
- The readelf line format looks like:
- Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
- LOAD 0x000000 0xffffffffff700000 0xffffffffff700000 0x000e68 0x000e68 R E 0x1000
-
- Args:
- vdso_path: Path to VDSO binary.
- """
- output = subprocess.check_output(["readelf", "-lW", vdso_path]).decode()
- lines = output.split("\n")
-
- segments = []
- for line in lines:
- if not line.startswith(" LOAD"):
- continue
-
- components = line.split()
-
- segments.append({
- "offset": int(components[1], 16),
- "addr": int(components[2], 16),
- "filesz": int(components[4], 16),
- "memsz": int(components[5], 16),
- })
-
- if not segments:
- Fatal("No PT_LOAD segments in VDSO")
-
- first = segments[0]
- if first["offset"] != 0:
- Fatal("First PT_LOAD segment has non-zero file offset: %s", first)
-
- for i, segment in enumerate(segments):
- memoff = segment["addr"] - first["addr"]
- if memoff != segment["offset"]:
- Fatal("PT_LOAD segment has different memory and file offsets: %s",
- segments)
-
- if segment["memsz"] != segment["filesz"]:
- Fatal("PT_LOAD segment memsz != filesz: %s", segment)
-
- if i > 0:
- last_end = segments[i-1]["addr"] + segments[i-1]["memsz"]
- if segment["addr"] < last_end:
- Fatal("PT_LOAD segments out of order")
-
- last_page = PageRoundDown(last_end)
- start_page = PageRoundDown(segment["addr"])
- if last_page >= start_page:
- Fatal("PT_LOAD segments share a page: %s and %s", segment,
- segments[i - 1])
-
-
-# Matches the section name in readelf -SW output.
-_SECTION_NAME_RE = re.compile(r"""^\s+\[\ ?\d+\]\s+
- (?P<name>\.\S+)\s+
- (?P<type>\S+)\s+
- (?P<addr>[0-9a-f]+)\s+
- (?P<off>[0-9a-f]+)\s+
- (?P<size>[0-9a-f]+)""", re.VERBOSE)
-
-
-def CheckData(vdso_path):
- """Verifies the VDSO contains no .data or .bss sections.
-
- The readelf line format looks like:
-
- There are 15 section headers, starting at offset 0x15f0:
-
- Section Headers:
- [Nr] Name Type Address Off Size ES Flg Lk Inf Al
- [ 0] NULL 0000000000000000 000000 000000 00 0 0 0
- [ 1] .hash HASH ffffffffff700120 000120 000040 04 A 2 0 8
- [ 2] .dynsym DYNSYM ffffffffff700160 000160 000108 18 A 3 1 8
- ...
- [13] .strtab STRTAB 0000000000000000 001448 000123 00 0 0 1
- [14] .shstrtab STRTAB 0000000000000000 00156b 000083 00 0 0 1
- Key to Flags:
- W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
- L (link order), O (extra OS processing required), G (group), T (TLS),
- C (compressed), x (unknown), o (OS specific), E (exclude),
- l (large), p (processor specific)
-
- Args:
- vdso_path: Path to VDSO binary.
- """
- output = subprocess.check_output(["readelf", "-SW", vdso_path]).decode()
- lines = output.split("\n")
-
- found_text = False
- for line in lines:
- m = re.search(_SECTION_NAME_RE, line)
- if not m:
- continue
-
- if not line.startswith(" ["):
- continue
-
- name = m.group("name")
- size = int(m.group("size"), 16)
-
- if name == ".text" and size != 0:
- found_text = True
-
- # Clang will typically omit these sections entirely; gcc will include them
- # but with size 0.
- if name.startswith(".data") and size != 0:
- Fatal("VDSO contains non-empty .data section:\n%s" % output)
-
- if name.startswith(".bss") and size != 0:
- Fatal("VDSO contains non-empty .bss section:\n%s" % output)
-
- if not found_text:
- Fatal("VDSO contains no/empty .text section? Bad parsing?:\n%s" % output)
-
-
-def CheckRelocs(vdso_path):
- """Verifies that the VDSO includes no relocations.
-
- Args:
- vdso_path: Path to VDSO binary.
- """
- output = subprocess.check_output(["readelf", "-r", vdso_path]).decode()
- if output.strip() != "There are no relocations in this file.":
- Fatal("VDSO contains relocations: %s", output)
-
-
-def main():
- parser = argparse.ArgumentParser(description="Verify VDSO ELF.")
- parser.add_argument("--vdso", required=True, help="Path to VDSO ELF")
- parser.add_argument(
- "--check-data",
- action="store_true",
- help="Check that the ELF contains no .data or .bss sections")
- args = parser.parse_args()
-
- CheckSegments(args.vdso)
- CheckRelocs(args.vdso)
-
- if args.check_data:
- CheckData(args.vdso)
-
-
-if __name__ == "__main__":
- main()
diff --git a/vdso/compiler.h b/vdso/compiler.h
deleted file mode 100644
index 54a510000..000000000
--- a/vdso/compiler.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef VDSO_COMPILER_H_
-#define VDSO_COMPILER_H_
-
-#define likely(x) __builtin_expect(!!(x), 1)
-#define unlikely(x) __builtin_expect(!!(x), 0)
-
-#ifndef __section
-#define __section(S) __attribute__((__section__(#S)))
-#endif
-
-#ifndef __aligned
-#define __aligned(N) __attribute__((__aligned__(N)))
-#endif
-
-#endif // VDSO_COMPILER_H_
diff --git a/vdso/cycle_clock.h b/vdso/cycle_clock.h
deleted file mode 100644
index 5d3fbb257..000000000
--- a/vdso/cycle_clock.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef VDSO_CYCLE_CLOCK_H_
-#define VDSO_CYCLE_CLOCK_H_
-
-#include <stdint.h>
-
-#include "vdso/barrier.h"
-
-namespace vdso {
-
-#if __x86_64__
-
-// TODO(b/74613497): The appropriate barrier instruction to use with rdtsc on
-// x86_64 depends on the vendor. Intel processors can use lfence but AMD may
-// need mfence, depending on MSR_F10H_DECFG_LFENCE_SERIALIZE_BIT.
-
-static inline uint64_t cycle_clock(void) {
- uint32_t lo, hi;
- asm volatile("lfence" : : : "memory");
- asm volatile("rdtsc" : "=a"(lo), "=d"(hi));
- return ((uint64_t)hi << 32) | lo;
-}
-
-#elif __aarch64__
-
-static inline uint64_t cycle_clock(void) {
- uint64_t val;
- asm volatile("mrs %0, CNTVCT_EL0" : "=r"(val)::"memory");
- return val;
-}
-
-#else
-#error "unsupported architecture"
-#endif
-
-} // namespace vdso
-
-#endif // VDSO_CYCLE_CLOCK_H_
diff --git a/vdso/seqlock.h b/vdso/seqlock.h
deleted file mode 100644
index 7a173174b..000000000
--- a/vdso/seqlock.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Low level raw interfaces to the sequence counter used by the VDSO.
-#ifndef VDSO_SEQLOCK_H_
-#define VDSO_SEQLOCK_H_
-
-#include <stdint.h>
-
-#include "vdso/barrier.h"
-#include "vdso/compiler.h"
-
-namespace vdso {
-
-inline int32_t read_seqcount_begin(const uint64_t* s) {
- uint64_t seq = *s;
- read_barrier();
- return seq & ~1;
-}
-
-inline int read_seqcount_retry(const uint64_t* s, uint64_t seq) {
- read_barrier();
- return unlikely(*s != seq);
-}
-
-} // namespace vdso
-
-#endif // VDSO_SEQLOCK_H_
diff --git a/vdso/syscalls.h b/vdso/syscalls.h
deleted file mode 100644
index f5865bb72..000000000
--- a/vdso/syscalls.h
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// System call support for the VDSO.
-//
-// Provides fallback system call interfaces for getcpu()
-// and clock_gettime().
-
-#ifndef VDSO_SYSCALLS_H_
-#define VDSO_SYSCALLS_H_
-
-#include <asm/unistd.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <stddef.h>
-#include <sys/types.h>
-
-namespace vdso {
-
-#if __x86_64__
-
-struct getcpu_cache;
-
-static inline int sys_clock_gettime(clockid_t clock, struct timespec* ts) {
- int num = __NR_clock_gettime;
- asm volatile("syscall\n"
- : "+a"(num)
- : "D"(clock), "S"(ts)
- : "rcx", "r11", "memory");
- return num;
-}
-
-static inline int sys_getcpu(unsigned* cpu, unsigned* node,
- struct getcpu_cache* cache) {
- int num = __NR_getcpu;
- asm volatile("syscall\n"
- : "+a"(num)
- : "D"(cpu), "S"(node), "d"(cache)
- : "rcx", "r11", "memory");
- return num;
-}
-
-#elif __aarch64__
-
-static inline int sys_rt_sigreturn(void) {
- int num = __NR_rt_sigreturn;
-
- asm volatile(
- "mov x8, %0\n"
- "svc #0 \n"
- : "+r"(num)
- :
- :);
- return num;
-}
-
-static inline int sys_clock_gettime(clockid_t _clkid, struct timespec *_ts) {
- register struct timespec *ts asm("x1") = _ts;
- register clockid_t clkid asm("x0") = _clkid;
- register long ret asm("x0");
- register long nr asm("x8") = __NR_clock_gettime;
-
- asm volatile("svc #0\n"
- : "=r"(ret)
- : "r"(clkid), "r"(ts), "r"(nr)
- : "memory");
- return ret;
-}
-
-static inline int sys_clock_getres(clockid_t _clkid, struct timespec *_ts) {
- register struct timespec *ts asm("x1") = _ts;
- register clockid_t clkid asm("x0") = _clkid;
- register long ret asm("x0");
- register long nr asm("x8") = __NR_clock_getres;
-
- asm volatile("svc #0\n"
- : "=r"(ret)
- : "r"(clkid), "r"(ts), "r"(nr)
- : "memory");
- return ret;
-}
-
-#else
-#error "unsupported architecture"
-#endif
-} // namespace vdso
-
-#endif // VDSO_SYSCALLS_H_
diff --git a/vdso/vdso.cc b/vdso/vdso.cc
deleted file mode 100644
index 8bb80a7a4..000000000
--- a/vdso/vdso.cc
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// This is the VDSO for sandboxed binaries. This file just contains the entry
-// points to the VDSO. All of the real work is done in vdso_time.cc
-
-#define _DEFAULT_SOURCE // ensure glibc provides struct timezone.
-#include <sys/time.h>
-#include <time.h>
-
-#include "vdso/syscalls.h"
-#include "vdso/vdso_time.h"
-
-namespace vdso {
-namespace {
-
-int __common_clock_gettime(clockid_t clock, struct timespec* ts) {
- int ret;
-
- switch (clock) {
- case CLOCK_REALTIME:
- ret = ClockRealtime(ts);
- break;
-
- case CLOCK_BOOTTIME:
- // Fallthrough, CLOCK_BOOTTIME is an alias for CLOCK_MONOTONIC
- case CLOCK_MONOTONIC:
- ret = ClockMonotonic(ts);
- break;
-
- default:
- ret = sys_clock_gettime(clock, ts);
- break;
- }
-
- return ret;
-}
-
-int __common_gettimeofday(struct timeval* tv, struct timezone* tz) {
- if (tv) {
- struct timespec ts;
- int ret = ClockRealtime(&ts);
- if (ret) {
- return ret;
- }
- tv->tv_sec = ts.tv_sec;
- tv->tv_usec = ts.tv_nsec / 1000;
- }
-
- // Nobody should be calling gettimeofday() with a non-NULL
- // timezone pointer. If they do then they will get zeros.
- if (tz) {
- tz->tz_minuteswest = 0;
- tz->tz_dsttime = 0;
- }
-
- return 0;
-}
-} // namespace
-
-#if __x86_64__
-
-// __vdso_clock_gettime() implements clock_gettime()
-extern "C" int __vdso_clock_gettime(clockid_t clock, struct timespec* ts) {
- return __common_clock_gettime(clock, ts);
-}
-extern "C" int clock_gettime(clockid_t clock, struct timespec* ts)
- __attribute__((weak, alias("__vdso_clock_gettime")));
-
-// __vdso_gettimeofday() implements gettimeofday()
-extern "C" int __vdso_gettimeofday(struct timeval* tv, struct timezone* tz) {
- return __common_gettimeofday(tv, tz);
-}
-extern "C" int gettimeofday(struct timeval* tv, struct timezone* tz)
- __attribute__((weak, alias("__vdso_gettimeofday")));
-
-// __vdso_time() implements time()
-extern "C" time_t __vdso_time(time_t* t) {
- struct timespec ts;
- ClockRealtime(&ts);
- if (t) {
- *t = ts.tv_sec;
- }
- return ts.tv_sec;
-}
-extern "C" time_t time(time_t* t) __attribute__((weak, alias("__vdso_time")));
-
-// __vdso_getcpu() implements getcpu()
-extern "C" long __vdso_getcpu(unsigned* cpu, unsigned* node,
- struct getcpu_cache* cache) {
- // No optimizations yet, just make the real system call.
- return sys_getcpu(cpu, node, cache);
-}
-extern "C" long getcpu(unsigned* cpu, unsigned* node,
- struct getcpu_cache* cache)
- __attribute__((weak, alias("__vdso_getcpu")));
-
-#elif __aarch64__
-
-// __kernel_clock_gettime() implements clock_gettime()
-extern "C" int __kernel_clock_gettime(clockid_t clock, struct timespec* ts) {
- return __common_clock_gettime(clock, ts);
-}
-
-// __kernel_gettimeofday() implements gettimeofday()
-extern "C" int __kernel_gettimeofday(struct timeval* tv, struct timezone* tz) {
- return __common_gettimeofday(tv, tz);
-}
-
-// __kernel_clock_getres() implements clock_getres()
-extern "C" int __kernel_clock_getres(clockid_t clock, struct timespec* res) {
- int ret = 0;
-
- switch (clock) {
- case CLOCK_REALTIME:
- case CLOCK_MONOTONIC:
- case CLOCK_BOOTTIME: {
- res->tv_sec = 0;
- res->tv_nsec = 1;
- break;
- }
-
- default:
- ret = sys_clock_getres(clock, res);
- break;
- }
-
- return ret;
-}
-
-// __kernel_rt_sigreturn() implements gettimeofday()
-extern "C" int __kernel_rt_sigreturn(unsigned long unused) {
- // No optimizations yet, just make the real system call.
- return sys_rt_sigreturn();
-}
-
-#else
-#error "unsupported architecture"
-#endif
-} // namespace vdso
diff --git a/vdso/vdso_amd64.lds b/vdso/vdso_amd64.lds
deleted file mode 100644
index e2615ae9e..000000000
--- a/vdso/vdso_amd64.lds
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Linker script for the VDSO.
- *
- * The VDSO is essentially a normal ELF shared library that is mapped into the
- * address space of the process that is going to use it. The address of the
- * VDSO is passed to the runtime linker in the AT_SYSINFO_EHDR entry of the aux
- * vector.
- *
- * There are, however, three ways in which the VDSO differs from a normal
- * shared library:
- *
- * - The runtime linker does not attempt to process any relocations for the
- * VDSO so it is the responsibility of whoever loads the VDSO into the
- * address space to do this if necessary. Because of this restriction we are
- * careful to ensure that the VDSO does not need to have any relocations
- * applied to it.
- *
- * - Although the VDSO is position independent and would normally be linked at
- * virtual address 0, the Linux kernel VDSO is actually linked at a non zero
- * virtual address and the code in the system runtime linker that handles the
- * VDSO expects this to be the case so we have to explicitly link this VDSO
- * at a non zero address. The actual address is arbitrary, but we use the
- * same one as the Linux kernel VDSO.
- *
- * - The VDSO will be directly mmapped by the sentry, rather than going through
- * a normal ELF loading process. The VDSO must be carefully constructed such
- * that the layout in the ELF file is identical to the layout in memory.
- */
-
-VDSO_PRELINK = 0xffffffffff700000;
-
-SECTIONS {
- /* The parameter page is mapped just before the VDSO. */
- _params = VDSO_PRELINK - 0x1000;
-
- . = VDSO_PRELINK + SIZEOF_HEADERS;
-
- .hash : { *(.hash) } :text
- .gnu.hash : { *(.gnu.hash) }
- .dynsym : { *(.dynsym) }
- .dynstr : { *(.dynstr) }
- .gnu.version : { *(.gnu.version) }
- .gnu.version_d : { *(.gnu.version_d) }
- .gnu.version_r : { *(.gnu.version_r) }
-
- .note : { *(.note.*) } :text :note
-
- .eh_frame_hdr : { *(.eh_frame_hdr) } :text :eh_frame_hdr
- .eh_frame : { KEEP (*(.eh_frame)) } :text
-
- .dynamic : { *(.dynamic) } :text :dynamic
-
- .rodata : { *(.rodata*) } :text
-
- .altinstructions : { *(.altinstructions) }
- .altinstr_replacement : { *(.altinstr_replacement) }
-
- /*
- * TODO(gvisor.dev/issue/157): Remove this alignment? Then the VDSO would fit
- * in a single page.
- */
- . = ALIGN(0x1000);
- .text : { *(.text*) } :text =0x90909090
-
- /*
- * N.B. There is no data/bss section. This VDSO neither needs nor uses a data
- * section. We omit it entirely because some gcc/clang and gold/bfd version
- * combinations struggle to handle an empty data PHDR segment (internal
- * linker assertion failures result).
- *
- * If the VDSO does incorrectly include a data section, the linker will
- * include it in the text segment. check_vdso.py looks for this degenerate
- * case.
- */
-}
-
-PHDRS {
- text PT_LOAD FLAGS(5) FILEHDR PHDRS; /* PF_R | PF_X */
- dynamic PT_DYNAMIC FLAGS(4); /* PF_R */
- note PT_NOTE FLAGS(4); /* PF_R */
- eh_frame_hdr PT_GNU_EH_FRAME;
-}
-
-/*
- * Define the symbols that are to be exported.
- */
-VERSION {
- LINUX_2.6 {
- global:
- clock_gettime;
- __vdso_clock_gettime;
- gettimeofday;
- __vdso_gettimeofday;
- getcpu;
- __vdso_getcpu;
- time;
- __vdso_time;
-
- local: *;
- };
-}
diff --git a/vdso/vdso_arm64.lds b/vdso/vdso_arm64.lds
deleted file mode 100644
index 469185468..000000000
--- a/vdso/vdso_arm64.lds
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Linker script for the VDSO.
- *
- * The VDSO is essentially a normal ELF shared library that is mapped into the
- * address space of the process that is going to use it. The address of the
- * VDSO is passed to the runtime linker in the AT_SYSINFO_EHDR entry of the aux
- * vector.
- *
- * There are, however, three ways in which the VDSO differs from a normal
- * shared library:
- *
- * - The runtime linker does not attempt to process any relocations for the
- * VDSO so it is the responsibility of whoever loads the VDSO into the
- * address space to do this if necessary. Because of this restriction we are
- * careful to ensure that the VDSO does not need to have any relocations
- * applied to it.
- *
- * - Although the VDSO is position independent and would normally be linked at
- * virtual address 0, the Linux kernel VDSO is actually linked at a non zero
- * virtual address and the code in the system runtime linker that handles the
- * VDSO expects this to be the case so we have to explicitly link this VDSO
- * at a non zero address. The actual address is arbitrary, but we use the
- * same one as the Linux kernel VDSO.
- *
- * - The VDSO will be directly mmapped by the sentry, rather than going through
- * a normal ELF loading process. The VDSO must be carefully constructed such
- * that the layout in the ELF file is identical to the layout in memory.
- */
-
-VDSO_PRELINK = 0xffffffffff700000;
-
-OUTPUT_FORMAT("elf64-littleaarch64", "elf64-bigaarch64", "elf64-littleaarch64")
-OUTPUT_ARCH(aarch64)
-
-SECTIONS {
- /* The parameter page is mapped just before the VDSO. */
- _params = VDSO_PRELINK - 0x1000;
-
- . = VDSO_PRELINK + SIZEOF_HEADERS;
-
- .hash : { *(.hash) } :text
- .gnu.hash : { *(.gnu.hash) }
- .dynsym : { *(.dynsym) }
- .dynstr : { *(.dynstr) }
- .gnu.version : { *(.gnu.version) }
- .gnu.version_d : { *(.gnu.version_d) }
- .gnu.version_r : { *(.gnu.version_r) }
-
- .note : { *(.note.*) } :text :note
-
- .eh_frame_hdr : { *(.eh_frame_hdr) } :text :eh_frame_hdr
- .eh_frame : { KEEP (*(.eh_frame)) } :text
-
- .dynamic : { *(.dynamic) } :text :dynamic
-
- .rodata : { *(.rodata*) } :text
-
- .altinstructions : { *(.altinstructions) }
- .altinstr_replacement : { *(.altinstr_replacement) }
-
- /*
- * TODO(gvisor.dev/issue/157): Remove this alignment? Then the VDSO would fit
- * in a single page.
- */
- . = ALIGN(0x1000);
- .text : { *(.text*) } :text =0xd503201f
-
- /*
- * N.B. There is no data/bss section. This VDSO neither needs nor uses a data
- * section. We omit it entirely because some gcc/clang and gold/bfd version
- * combinations struggle to handle an empty data PHDR segment (internal
- * linker assertion failures result).
- *
- * If the VDSO does incorrectly include a data section, the linker will
- * include it in the text segment. check_vdso.py looks for this degenerate
- * case.
- */
-}
-
-PHDRS {
- text PT_LOAD FLAGS(5) FILEHDR PHDRS; /* PF_R | PF_X */
- dynamic PT_DYNAMIC FLAGS(4); /* PF_R */
- note PT_NOTE FLAGS(4); /* PF_R */
- eh_frame_hdr PT_GNU_EH_FRAME;
-}
-
-/*
- * Define the symbols that are to be exported.
- */
-VERSION {
- LINUX_2.6.39 {
- global:
- __kernel_clock_getres;
- __kernel_clock_gettime;
- __kernel_gettimeofday;
- __kernel_rt_sigreturn;
- local: *;
- };
-}
diff --git a/vdso/vdso_time.cc b/vdso/vdso_time.cc
deleted file mode 100644
index 1bb4bb86b..000000000
--- a/vdso/vdso_time.cc
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "vdso/vdso_time.h"
-
-#include <stdint.h>
-#include <sys/time.h>
-#include <time.h>
-
-#include "vdso/cycle_clock.h"
-#include "vdso/seqlock.h"
-#include "vdso/syscalls.h"
-
-// struct params defines the layout of the parameter page maintained by the
-// kernel (i.e., sentry).
-//
-// This is similar to the VVAR page maintained by the normal Linux kernel for
-// its VDSO, but it has a different layout.
-//
-// It must be kept in sync with VDSOParamPage in pkg/sentry/kernel/vdso.go.
-struct params {
- uint64_t seq_count;
-
- uint64_t monotonic_ready;
- int64_t monotonic_base_cycles;
- int64_t monotonic_base_ref;
- uint64_t monotonic_frequency;
-
- uint64_t realtime_ready;
- int64_t realtime_base_cycles;
- int64_t realtime_base_ref;
- uint64_t realtime_frequency;
-};
-
-// Returns a pointer to the global parameter page.
-//
-// This page lives in the page just before the VDSO binary itself. The linker
-// defines _params as the page before the VDSO.
-//
-// Ideally, we'd simply declare _params as an extern struct params.
-// Unfortunately various combinations of old/new versions of gcc/clang and
-// gold/bfd struggle to generate references to such a global without generating
-// relocations.
-//
-// So instead, we use inline assembly with a construct that seems to have wide
-// compatibility across many toolchains.
-#if __x86_64__
-
-inline struct params* get_params() {
- struct params* p = nullptr;
- asm("leaq _params(%%rip), %0" : "=r"(p) : :);
- return p;
-}
-
-#elif __aarch64__
-
-inline struct params* get_params() {
- struct params* p = nullptr;
- asm("adr %0, _params" : "=r"(p) : :);
- return p;
-}
-
-#else
-#error "unsupported architecture"
-#endif
-
-namespace vdso {
-
-const uint64_t kNsecsPerSec = 1000000000UL;
-
-inline struct timespec ns_to_timespec(uint64_t ns) {
- struct timespec ts;
- ts.tv_sec = ns / kNsecsPerSec;
- ts.tv_nsec = ns % kNsecsPerSec;
- return ts;
-}
-
-inline uint64_t cycles_to_ns(uint64_t frequency, uint64_t cycles) {
- uint64_t mult = (kNsecsPerSec << 32) / frequency;
- return ((unsigned __int128)cycles * mult) >> 32;
-}
-
-// ClockRealtime() is the VDSO implementation of clock_gettime(CLOCK_REALTIME).
-int ClockRealtime(struct timespec* ts) {
- struct params* params = get_params();
- uint64_t seq;
- uint64_t ready;
- int64_t base_ref;
- int64_t base_cycles;
- uint64_t frequency;
- int64_t now_cycles;
-
- do {
- seq = read_seqcount_begin(&params->seq_count);
- ready = params->realtime_ready;
- base_ref = params->realtime_base_ref;
- base_cycles = params->realtime_base_cycles;
- frequency = params->realtime_frequency;
- now_cycles = cycle_clock();
- } while (read_seqcount_retry(&params->seq_count, seq));
-
- if (!ready) {
- // The sandbox kernel ensures that we won't compute a time later than this
- // once the params are ready.
- return sys_clock_gettime(CLOCK_REALTIME, ts);
- }
-
- int64_t delta_cycles =
- (now_cycles < base_cycles) ? 0 : now_cycles - base_cycles;
- int64_t now_ns = base_ref + cycles_to_ns(frequency, delta_cycles);
- *ts = ns_to_timespec(now_ns);
- return 0;
-}
-
-// ClockMonotonic() is the VDSO implementation of
-// clock_gettime(CLOCK_MONOTONIC).
-int ClockMonotonic(struct timespec* ts) {
- struct params* params = get_params();
- uint64_t seq;
- uint64_t ready;
- int64_t base_ref;
- int64_t base_cycles;
- uint64_t frequency;
- int64_t now_cycles;
-
- do {
- seq = read_seqcount_begin(&params->seq_count);
- ready = params->monotonic_ready;
- base_ref = params->monotonic_base_ref;
- base_cycles = params->monotonic_base_cycles;
- frequency = params->monotonic_frequency;
- now_cycles = cycle_clock();
- } while (read_seqcount_retry(&params->seq_count, seq));
-
- if (!ready) {
- // The sandbox kernel ensures that we won't compute a time later than this
- // once the params are ready.
- return sys_clock_gettime(CLOCK_MONOTONIC, ts);
- }
-
- int64_t delta_cycles =
- (now_cycles < base_cycles) ? 0 : now_cycles - base_cycles;
- int64_t now_ns = base_ref + cycles_to_ns(frequency, delta_cycles);
- *ts = ns_to_timespec(now_ns);
- return 0;
-}
-
-} // namespace vdso
diff --git a/vdso/vdso_time.h b/vdso/vdso_time.h
deleted file mode 100644
index 70d079efc..000000000
--- a/vdso/vdso_time.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef VDSO_VDSO_TIME_H_
-#define VDSO_VDSO_TIME_H_
-
-#include <time.h>
-
-namespace vdso {
-
-int ClockRealtime(struct timespec* ts);
-int ClockMonotonic(struct timespec* ts);
-
-} // namespace vdso
-
-#endif // VDSO_VDSO_TIME_H_