diff --git a/CMakeLists.txt b/CMakeLists.txt index 8741110..a8dce7d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,7 @@ FetchContent_MakeAvailable(abseil) FetchContent_Declare( co GIT_REPOSITORY https://github.com/dallison/co.git - GIT_TAG main + GIT_TAG cf1252b2f5952d7cba83b67dd69288971c0a2b57 # Pass architecture settings to co's CMake build CMAKE_ARGS CMAKE_OSX_ARCHITECTURES="${CMAKE_OSX_ARCHITECTURES}" diff --git a/MODULE.bazel b/MODULE.bazel index 24d82bf..fee65e5 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -6,19 +6,21 @@ http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "ht bazel_dep(name = "bazel_skylib", version = "1.7.1") bazel_dep(name = "platforms", version = "0.0.10") -bazel_dep(name = "abseil-cpp", version = "20240722.0.bcr.1", repo_name = "com_google_absl") +bazel_dep(name = "abseil-cpp", version = "20250814.1") bazel_dep(name = "googletest", version = "1.15.2", repo_name = "com_google_googletest") # Coroutines http_archive( name = "coroutines", - integrity = "sha256-PhOYq1eE8Q8UOhzDHq2+rafTU4VTt9fZ0ZZyNE+hWb4=", - strip_prefix = "co-2.1.0", - urls = ["https://github.com/dallison/co/archive/refs/tags/2.1.0.tar.gz"], + sha256 = "44cfc56924e4aa338fbf6c424a8f5db2e6e7d88e27666fd137aeb6cb67c236e1", + strip_prefix = "co-26f5da14c994276ab038f7ad512ca37ba32f19dc", + urls = ["https://github.com/dallison/co/archive/26f5da14c994276ab038f7ad512ca37ba32f19dc.tar.gz"], ) + # For local debugging of co coroutine library. # bazel_dep(name = "coroutines") # local_path_override( -# module_name = "coroutines", -# path = "../co", +# module_name = "coroutines", +# path = "../co", # ) + diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 7723de7..94eaac9 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -1,5 +1,5 @@ { - "lockFileVersion": 18, + "lockFileVersion": 24, "registryFileHashes": { "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", @@ -10,16 +10,20 @@ "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", "https://bcr.bazel.build/modules/abseil-cpp/20240116.2/MODULE.bazel": "73939767a4686cd9a520d16af5ab440071ed75cec1a876bf2fcfaf1f71987a16", - "https://bcr.bazel.build/modules/abseil-cpp/20240722.0.bcr.1/MODULE.bazel": "c0aa5eaefff1121b40208397f229604c717bd2fdf214ff67586d627118e17720", - "https://bcr.bazel.build/modules/abseil-cpp/20240722.0.bcr.1/source.json": "e067fdd217bacbe74c88a975434be5df0b44315a247be180f0e20f891715210c", + "https://bcr.bazel.build/modules/abseil-cpp/20250127.1/MODULE.bazel": "c4a89e7ceb9bf1e25cf84a9f830ff6b817b72874088bf5141b314726e46a57c1", + "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/MODULE.bazel": "51f2312901470cdab0dbdf3b88c40cd21c62a7ed58a3de45b365ddc5b11bcab2", + "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/source.json": "cea3901d7e299da7320700abbaafe57a65d039f10d0d7ea601c4a66938ea4b0c", "https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85", - "https://bcr.bazel.build/modules/apple_support/1.15.1/source.json": "517f2b77430084c541bc9be2db63fdcbb7102938c5f64c17ee60ffda2e5cf07b", + "https://bcr.bazel.build/modules/apple_support/1.23.1/MODULE.bazel": "53763fed456a968cf919b3240427cf3a9d5481ec5466abc9d5dc51bc70087442", + "https://bcr.bazel.build/modules/apple_support/1.23.1/source.json": "d888b44312eb0ad2c21a91d026753f330caa48a25c9b2102fae75eb2b0dcfdd2", "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", + "https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65", + "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", "https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc", "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", @@ -35,7 +39,8 @@ "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", - "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/source.json": "f121b43eeefc7c29efbd51b83d08631e2347297c95aac9764a701f2a6a2bb953", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/source.json": "7ebaefba0b03efe59cac88ed5bbc67bcf59a3eff33af937345ede2a38b2d368a", "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", @@ -43,19 +48,21 @@ "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", "https://bcr.bazel.build/modules/googletest/1.15.2/MODULE.bazel": "6de1edc1d26cafb0ea1a6ab3f4d4192d91a312fd2d360b63adaa213cd00b2108", - "https://bcr.bazel.build/modules/googletest/1.15.2/source.json": "dbdda654dcb3a0d7a8bc5d0ac5fc7e150b58c2a986025ae5bc634bb2cb61f470", + "https://bcr.bazel.build/modules/googletest/1.17.0/MODULE.bazel": "dbec758171594a705933a29fcf69293d2468c49ec1f2ebca65c36f504d72df46", + "https://bcr.bazel.build/modules/googletest/1.17.0/source.json": "38e4454b25fc30f15439c0378e57909ab1fd0a443158aa35aec685da727cd713", "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", "https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d", "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", - "https://bcr.bazel.build/modules/platforms/0.0.11/source.json": "f7e188b79ebedebfe75e9e1d098b8845226c7992b307e28e1496f23112e8fc29", "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc", + "https://bcr.bazel.build/modules/platforms/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580", + "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96", "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", @@ -67,8 +74,9 @@ "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34", "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/source.json": "6900fdc8a9e95866b8c0d4ad4aba4d4236317b5c1cd04c502df3f0d33afed680", "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", + "https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/MODULE.bazel": "b4963dda9b31080be1905ef085ecd7dd6cd47c05c79b9cdf83ade83ab2ab271a", + "https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/source.json": "2ff292be6ef3340325ce8a045ecc326e92cbfab47c7cbab4bd85d28971b97ac4", "https://bcr.bazel.build/modules/re2/2024-07-02/MODULE.bazel": "0eadc4395959969297cbcf31a249ff457f2f1d456228c67719480205aa306daa", - "https://bcr.bazel.build/modules/re2/2024-07-02/source.json": "547d0111a9d4f362db32196fef805abbf3676e8d6afbe44d395d87816c1130ca", "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", @@ -77,12 +85,14 @@ "https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac", "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", + "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a", "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", - "https://bcr.bazel.build/modules/rules_cc/0.1.1/source.json": "d61627377bd7dd1da4652063e368d9366fc9a73920bfa396798ad92172cf645c", + "https://bcr.bazel.build/modules/rules_cc/0.2.0/MODULE.bazel": "b5c17f90458caae90d2ccd114c81970062946f49f355610ed89bebf954f5783c", + "https://bcr.bazel.build/modules/rules_cc/0.2.0/source.json": "5f7f4e578e950adbf194217d4b607237a8197fc53ba46c367b3d61a86ecf35c2", "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e", @@ -146,40 +156,9 @@ }, "selectedYankedVersions": {}, "moduleExtensions": { - "@@apple_support+//crosstool:setup.bzl%apple_cc_configure_extension": { - "general": { - "bzlTransitiveDigest": "E970FlMbwpgJPdPUQzatKh6BMfeE0ZpWABvwshh7Tmg=", - "usagesDigest": "aYRVMk+1OupIp+5hdBlpzT36qgd6ntgSxYTzMLW5K4U=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "local_config_apple_cc_toolchains": { - "repoRuleId": "@@apple_support+//crosstool:setup.bzl%_apple_cc_autoconf_toolchains", - "attributes": {} - }, - "local_config_apple_cc": { - "repoRuleId": "@@apple_support+//crosstool:setup.bzl%_apple_cc_autoconf", - "attributes": {} - } - }, - "recordedRepoMappingEntries": [ - [ - "apple_support+", - "bazel_tools", - "bazel_tools" - ], - [ - "bazel_tools", - "rules_cc", - "rules_cc+" - ] - ] - } - }, "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { - "bzlTransitiveDigest": "OlvsB0HsvxbR8ZN+J9Vf00X/+WVz/Y/5Xrq2LgcVfdo=", + "bzlTransitiveDigest": "rL/34P1aFDq2GqVC2zCFgQ8nTuOC6ziogocpvG50Qz8=", "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -241,5 +220,6 @@ ] } } - } + }, + "facts": {} } diff --git a/README.md b/README.md index 169d5dc..fda81f4 100644 --- a/README.md +++ b/README.md @@ -1,63 +1,1029 @@ # cpp_toolbelt -Collection of useful C++ classes and utility functions -I find these useful when writing C++ code. +A collection of useful C++ classes and utility functions for systems programming. -The classes are: +## Table of Contents -1. FileDescriptor: a reference counted UNIX file descriptor representing an open file -2. InetAddress: an interet address (IPv4 for now) -3. Socket: a general socket -4. UnixSocket: a UNIX Domain socket -5. TCPSocket: a TCP socket -6. UDPSocket: a UDP socket -7. Logger: a level-aware logger that prints in color on TTYs -8. MutexLock: an RAII class for handling pthread mutexes -9. BitSet: a fixed size set of bits that allows allocation. +- [Installation](#installation) +- [Classes](#classes) + - [FileDescriptor](#filedescriptor) + - [Socket Classes](#socket-classes) + - [InetAddress](#inetaddress) + - [VirtualAddress](#virtualaddress) + - [SocketAddress](#socketaddress) + - [Socket](#socket) + - [UnixSocket](#unixsocket) + - [TCPSocket](#tcpsocket) + - [UDPSocket](#udpsocket) + - [VirtualStreamSocket](#virtualstreamsocket) + - [StreamSocket](#streamsocket) + - [Logger](#logger) + - [MutexLock and RWLock](#mutexlock-and-rwlock) + - [BitSet](#bitset) + - [Pipe](#pipe) + - [Table](#table) + - [PayloadBuffer](#payloadbuffer) + - [TriggerFd](#triggerfd) +- [Utility Functions](#utility-functions) + - [Now](#now) + - [Hexdump](#hexdump) + - [PrintCurrentStack](#printcurrentstack) -In addition, the following functions are provided: +## Installation -1. Hexdump: dump memory in hex -2. Now: get the current nanosecond monotonic time. +### Using Bazel -The Socket classes are coroutine aware and need my -[coroutine library](https://github.com/dallison/cocpp) +Add this to your `MODULE.bazel` or `WORKSPACE` file: -Enjoy! +```python +http_archive( + name = "toolbelt", + urls = ["https://github.com/dallison/cpp_toolbelt/archive/refs/tags/A.B.C.tar.gz"], + strip_prefix = "cpp_toolbelt-A.B.C", +) +``` + +Replace `A.B.C` with the version you want (e.g., `1.0.3`). -# Downloading and using the toolbelt -Add this to your WORKSPACE file to download and use toolbelt: +Add a dependency to your `BUILD.bazel` targets: +```python +deps = [ + # ... + "@toolbelt//toolbelt", +], ``` -http_archive( - name = "toolbelt", - urls = ["https://github.com/dallison/cpp_toolbelt/archive/refs/tags/A.B.C.tar.gz"], - strip_prefix = "cpp_toolbelt-A.B.C", + +### Using CMake + +```cmake +include(FetchContent) +FetchContent_Declare( + toolbelt + GIT_REPOSITORY https://github.com/dallison/cpp_toolbelt.git + GIT_TAG ) +FetchContent_MakeAvailable(toolbelt) + +target_link_libraries(your_target PRIVATE toolbelt) +``` + +## Classes + +### FileDescriptor + +A reference-counted wrapper around UNIX file descriptors. Automatically closes the file descriptor when all references are destroyed. + +**Header:** `toolbelt/fd.h` + +#### API + +```cpp +class FileDescriptor { +public: + FileDescriptor(); + explicit FileDescriptor(int fd, bool owned = true); + FileDescriptor(const FileDescriptor &f); + FileDescriptor(FileDescriptor &&f); + + void Close(); + bool IsOpen() const; + bool IsATTY() const; + int RefCount() const; + struct pollfd GetPollFd(); + bool Valid() const; + int Fd() const; + void SetFd(int fd, bool owned = true); + void Reset(); + void Release(); + void ForceClose(); + bool IsNonBlocking() const; + absl::Status SetNonBlocking(); + absl::Status SetCloseOnExec(); + absl::StatusOr Read(void* buffer, size_t length, + const co::Coroutine* c = nullptr); + absl::StatusOr Write(const void* buffer, size_t length, + const co::Coroutine* c = nullptr); +}; +``` + +#### Example + +```cpp +#include "toolbelt/fd.h" + +// Create from an existing file descriptor +int fd = open("/path/to/file", O_RDONLY); +toolbelt::FileDescriptor file(fd); + +// Copy creates a new reference (cheap, just increments ref count) +toolbelt::FileDescriptor file2 = file; +assert(file.RefCount() == 2); + +// File descriptor is automatically closed when last reference is destroyed +file.Close(); // Still open, file2 still references it +// file2 goes out of scope -> file descriptor is closed + +// Read from file descriptor +char buffer[1024]; +auto result = file.Read(buffer, sizeof(buffer)); +if (result.ok()) { + size_t bytes_read = *result; + // Process data... +} +``` + +### Socket Classes + +The socket classes provide a high-level interface to various socket types. They are coroutine-aware and work with the [co coroutine library](https://github.com/dallison/co). + +**Header:** `toolbelt/sockets.h` + +#### InetAddress + +Represents an IPv4 internet address and port. + +```cpp +class InetAddress { +public: + InetAddress(); + InetAddress(int port); // INADDR_ANY with given port + InetAddress(const in_addr &ip, int port); + InetAddress(const std::string &hostname, int port); + InetAddress(const struct sockaddr_in &addr); + + const sockaddr_in &GetAddress() const; + socklen_t GetLength() const; + bool Valid() const; + in_addr IpAddress() const; // Host byte order + int Port() const; // Host byte order + void SetPort(int port); + std::string ToString() const; + + static InetAddress BroadcastAddress(int port); + static InetAddress AnyAddress(int port); +}; +``` + +#### Example + +```cpp +#include "toolbelt/sockets.h" + +// Create address from hostname and port +toolbelt::InetAddress addr("localhost", 8080); + +// Create address for any interface on port 8080 +auto any_addr = toolbelt::InetAddress::AnyAddress(8080); + +// Get address components +in_addr ip = addr.IpAddress(); +int port = addr.Port(); +std::string str = addr.ToString(); // "127.0.0.1:8080" ``` -Replacing A.B.C with the version you want (e.g. 1.0.3). +#### VirtualAddress -Add a dependency to your BUILD.bazel targets like this: +Represents a virtual socket address (VSOCK) used for communication between VMs and the host. +```cpp +class VirtualAddress { +public: + VirtualAddress(); + VirtualAddress(uint32_t port); + VirtualAddress(uint32_t cid, uint32_t port); + + uint32_t Cid() const; + uint32_t Port() const; + void SetPort(uint32_t port); + std::string ToString() const; + + static VirtualAddress HypervisorAddress(uint32_t port); + static VirtualAddress HostAddress(uint32_t port); + static VirtualAddress AnyAddress(uint32_t port); + #if defined(__linux__) + static VirtualAddress LocalAddress(uint32_t port); + #endif +}; ``` - deps = [ - # ... - "@toolbelt//toolbelt", - ], + +#### SocketAddress + +A variant type that can hold an `InetAddress`, `VirtualAddress`, or Unix socket path (string). + +```cpp +class SocketAddress { +public: + SocketAddress(); + SocketAddress(const InetAddress &addr); + SocketAddress(const VirtualAddress &addr); + SocketAddress(const std::string &addr); // Unix socket path + + const InetAddress &GetInetAddress() const; + const VirtualAddress &GetVirtualAddress() const; + const std::string &GetUnixAddress() const; + std::string ToString() const; + bool Valid() const; + int Type() const; // kAddressInet, kAddressVirtual, or kAddressUnix + int Port() const; +}; ``` +#### Socket -All the header files are in *toolbelt/*, so for example: +Base class for all socket types. Provides common functionality for sending and receiving data. -```c++ -#include "toolbelt/fd.h +```cpp +class Socket { +public: + void Close(); + bool Connected() const; + absl::StatusOr Receive(char *buffer, size_t buflen, + const co::Coroutine *c = nullptr); + absl::StatusOr Send(const char *buffer, size_t length, + const co::Coroutine *c = nullptr); + absl::StatusOr ReceiveMessage(char *buffer, size_t buflen, + const co::Coroutine *c = nullptr); + absl::StatusOr> ReceiveVariableLengthMessage( + const co::Coroutine *c = nullptr); + absl::StatusOr SendMessage(char *buffer, size_t length, + const co::Coroutine *c = nullptr); + absl::Status SetNonBlocking(); + FileDescriptor GetFileDescriptor() const; + absl::Status SetCloseOnExec(); + bool IsNonBlocking() const; + bool IsBlocking() const; +}; ``` -Then you can use it as: +#### UnixSocket -```c++ -toolbelt::FileDescriptor fd_; +A Unix Domain socket for inter-process communication. + +```cpp +class UnixSocket : public Socket { +public: + UnixSocket(); + explicit UnixSocket(int fd, bool connected = false); + + absl::Status Bind(const std::string &pathname, bool listen); + absl::Status Connect(const std::string &pathname); + absl::StatusOr Accept(const co::Coroutine *c = nullptr) const; + absl::Status SendFds(const std::vector &fds, + const co::Coroutine *c = nullptr); + absl::Status ReceiveFds(std::vector &fds, + const co::Coroutine *c = nullptr); + std::string BoundAddress() const; + absl::StatusOr GetPeerName() const; + absl::StatusOr LocalAddress() const; +}; ``` - +#### Example + +```cpp +#include "toolbelt/sockets.h" + +// Server side +toolbelt::UnixSocket server; +server.Bind("/tmp/mysocket", true); // true = listen + +// In a coroutine or event loop +auto client = server.Accept(coroutine); +if (client.ok()) { + char buffer[1024]; + auto result = client->Receive(buffer, sizeof(buffer), coroutine); + // Process received data... +} + +// Client side +toolbelt::UnixSocket client; +client.Connect("/tmp/mysocket"); +client.Send("Hello", 5, coroutine); +``` + +#### TCPSocket + +A TCP socket for network communication. + +```cpp +class TCPSocket : public NetworkSocket { +public: + TCPSocket(); + explicit TCPSocket(int fd, bool connected = false); + + absl::Status Bind(const InetAddress &addr, bool listen); + absl::StatusOr Accept(const co::Coroutine *c = nullptr) const; + absl::StatusOr LocalAddress(int port) const; + absl::StatusOr GetPeerName() const; +}; +``` + +#### Example + +```cpp +#include "toolbelt/sockets.h" + +// Server +toolbelt::TCPSocket server; +toolbelt::InetAddress addr(8080); +server.Bind(addr, true); // Listen on port 8080 + +// Accept connections +auto client = server.Accept(coroutine); +if (client.ok()) { + char buffer[1024]; + auto result = client->Receive(buffer, sizeof(buffer), coroutine); +} + +// Client +toolbelt::TCPSocket client; +toolbelt::InetAddress server_addr("example.com", 8080); +client.Connect(server_addr); +client.Send("Hello", 5, coroutine); +``` + +#### UDPSocket + +A UDP socket for datagram communication. + +```cpp +class UDPSocket : public NetworkSocket { +public: + UDPSocket(); + explicit UDPSocket(int fd, bool connected = false); + + absl::Status Bind(const InetAddress &addr); + absl::Status JoinMulticastGroup(const InetAddress &addr); + absl::Status LeaveMulticastGroup(const InetAddress &addr); + absl::Status SendTo(const InetAddress &addr, const void *buffer, + size_t length, const co::Coroutine *c = nullptr); + absl::StatusOr Receive(void *buffer, size_t buflen, + const co::Coroutine *c = nullptr); + absl::StatusOr ReceiveFrom(InetAddress &sender, void *buffer, + size_t buflen, + const co::Coroutine *c = nullptr); + absl::Status SetBroadcast(); + absl::Status SetMulticastLoop(); +}; +``` + +#### Example + +```cpp +#include "toolbelt/sockets.h" + +// Server +toolbelt::UDPSocket socket; +toolbelt::InetAddress addr(8080); +socket.Bind(addr); + +toolbelt::InetAddress sender; +char buffer[1024]; +auto result = socket.ReceiveFrom(sender, buffer, sizeof(buffer), coroutine); +if (result.ok()) { + // Process datagram from sender +} + +// Client +toolbelt::UDPSocket socket; +toolbelt::InetAddress server("example.com", 8080); +socket.SendTo(server, "Hello", 5, coroutine); +``` + +#### VirtualStreamSocket + +A virtual stream socket for VM-to-host communication using VSOCK. + +```cpp +class VirtualStreamSocket : public Socket { +public: + VirtualStreamSocket(); + explicit VirtualStreamSocket(int fd, bool connected = false); + + absl::Status Connect(const VirtualAddress &addr); + absl::Status Bind(const VirtualAddress &addr, bool listen); + absl::StatusOr Accept( + const co::Coroutine *c = nullptr) const; + absl::StatusOr LocalAddress(uint32_t port) const; + const VirtualAddress &BoundAddress() const; + absl::StatusOr GetPeerName() const; + uint32_t Cid() const; +}; +``` + +#### StreamSocket + +A type-erased wrapper that can hold a `TCPSocket`, `VirtualStreamSocket`, or `UnixSocket`. Useful when you need to work with different socket types uniformly. + +```cpp +class StreamSocket { +public: + StreamSocket(); + + absl::Status Bind(const SocketAddress &addr, bool listen); + absl::Status Connect(const SocketAddress &addr); + absl::StatusOr Accept(const co::Coroutine *c = nullptr) const; + + TCPSocket &GetTCPSocket(); + VirtualStreamSocket &GetVirtualStreamSocket(); + UnixSocket &GetUnixSocket(); + + SocketAddress BoundAddress() const; + void Close(); + bool Connected() const; + absl::StatusOr Receive(char *buffer, size_t buflen, + const co::Coroutine *c = nullptr); + absl::StatusOr Send(const char *buffer, size_t length, + const co::Coroutine *c = nullptr); + // ... other Socket methods +}; +``` + +#### Example + +```cpp +#include "toolbelt/sockets.h" + +// Works with any socket type +toolbelt::StreamSocket socket; + +// Can bind to TCP, Unix, or VSOCK addresses +toolbelt::SocketAddress addr = toolbelt::InetAddress(8080); +// or: toolbelt::SocketAddress addr("/tmp/socket"); +// or: toolbelt::SocketAddress addr(toolbelt::VirtualAddress(1234)); + +socket.Bind(addr, true); +auto client = socket.Accept(coroutine); +``` + +### Logger + +A level-aware logger that supports colored output, multiple output modes, and themes. + +**Header:** `toolbelt/logging.h` + +#### API + +```cpp +enum class LogLevel { + kVerboseDebug, + kDebug, + kInfo, + kWarning, + kError, + kFatal, +}; + +enum class LogDisplayMode { + kPlain, + kColor, + kColumnar, +}; + +enum class LogTheme { + kDefault, + kLight, + kDark, +}; + +class Logger { +public: + Logger(); + Logger(const std::string &subsystem, bool enabled = true, + LogTheme theme = LogTheme::kDefault, + LogDisplayMode mode = LogDisplayMode::kPlain); + Logger(LogLevel min); + + void Enable(); + void Disable(); + absl::Status SetTeeFile(const std::string &filename, bool truncate = true); + void SetTeeStream(FILE *stream); + void Log(LogLevel level, const char *fmt, ...); + void VLog(LogLevel level, const char *fmt, va_list ap); + void Log(LogLevel level, uint64_t timestamp, + const std::string &source, std::string text); + void SetTheme(LogTheme theme); + void SetLogLevel(LogLevel l); + void SetLogLevel(const std::string &s); // "verbose", "debug", "info", etc. + LogLevel GetLogLevel() const; + void SetOutputStream(FILE *stream); +}; +``` + +#### Example + +```cpp +#include "toolbelt/logging.h" + +// Create logger with subsystem name +toolbelt::Logger logger("myapp", true, toolbelt::LogTheme::kDefault, + toolbelt::LogDisplayMode::kColor); + +// Set minimum log level +logger.SetLogLevel(toolbelt::LogLevel::kInfo); +// or +logger.SetLogLevel("info"); + +// Log messages +logger.Log(toolbelt::LogLevel::kInfo, "Application started"); +logger.Log(toolbelt::LogLevel::kDebug, "Debug value: %d", 42); +logger.Log(toolbelt::LogLevel::kWarning, "Warning: %s", "Something happened"); +logger.Log(toolbelt::LogLevel::kError, "Error code: %d", errno); + +// Tee output to a file +logger.SetTeeFile("/var/log/myapp.log"); + +// Disable logging temporarily +logger.Disable(); +// ... do something ... +logger.Enable(); +``` + +### MutexLock and RWLock + +RAII wrappers for pthread mutexes and read-write locks. + +**Header:** `toolbelt/mutex.h` + +#### API + +```cpp +class MutexLock { +public: + MutexLock(pthread_mutex_t *mutex); + ~MutexLock(); +}; + +class RWLock { +public: + RWLock(pthread_rwlock_t *lock, bool read); + ~RWLock(); + + void ReadLock(); + void WriteLock(); + void Unlock(); +}; +``` + +#### Example + +```cpp +#include "toolbelt/mutex.h" +#include + +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +{ + toolbelt::MutexLock lock(&mutex); + // Critical section - mutex is automatically unlocked when lock goes out of scope + // Do work... +} + +pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; + +{ + toolbelt::RWLock lock(&rwlock, true); // true = read lock + // Multiple readers allowed + // Read data... +} + +{ + toolbelt::RWLock lock(&rwlock, false); // false = write lock + // Exclusive write access + // Write data... +} +``` + +### BitSet + +A fixed-size bitset that allows allocation of individual bits. + +**Header:** `toolbelt/bitset.h` + +#### API + +```cpp +template +class BitSet { +public: + BitSet(); + void Init(); + absl::StatusOr Allocate(const std::string &type); + bool IsEmpty() const; + void Set(int b); + void Clear(int b); + bool IsSet(int b) const; +}; +``` + +#### Example + +```cpp +#include "toolbelt/bitset.h" + +// Create a bitset with 256 bits +toolbelt::BitSet<256> bitset; + +// Allocate the first available bit +auto result = bitset.Allocate("resource"); +if (result.ok()) { + int bit_index = *result; + // Use the bit... + + // Check if bit is set + if (bitset.IsSet(bit_index)) { + // ... + } + + // Clear the bit when done + bitset.Clear(bit_index); +} + +// Check if all bits are clear +if (bitset.IsEmpty()) { + // All resources freed +} +``` + +### Pipe + +A pipe for inter-process or inter-coroutine communication. Supports both blocking and non-blocking I/O, and can work with coroutines. + +**Header:** `toolbelt/pipe.h` + +#### API + +```cpp +class Pipe { +public: + static absl::StatusOr Create(); + static absl::StatusOr CreateWithFlags(int flags); + static absl::StatusOr Create(int r, int w); + + Pipe(); + Pipe(int r, int w); + + absl::Status Open(int flags = 0); + FileDescriptor &ReadFd(); + FileDescriptor &WriteFd(); + void SetReadFd(int fd); + void SetWriteFd(int fd); + void Close(); + void ForceClose(); + absl::Status SetNonBlocking(bool read, bool write); + absl::StatusOr GetPipeSize(); + absl::Status SetPipeSize(size_t size); + absl::StatusOr Read(char *buffer, size_t length, + const co::Coroutine *c = nullptr); + absl::StatusOr Write(const char *buffer, size_t length, + const co::Coroutine *c = nullptr); +}; + +template +class SharedPtrPipe : public Pipe { +public: + static absl::StatusOr> Create(); + static absl::StatusOr> Create(int r, int w); + + absl::StatusOr> Read(const co::Coroutine *c = nullptr); + absl::Status Write(std::shared_ptr p, const co::Coroutine *c = nullptr); +}; +``` + +#### Example + +```cpp +#include "toolbelt/pipe.h" + +// Create a pipe +auto pipe_result = toolbelt::Pipe::Create(); +if (!pipe_result.ok()) { + // Handle error + return; +} +toolbelt::Pipe pipe = *pipe_result; + +// Set non-blocking mode +pipe.SetNonBlocking(true, true); + +// Write data (in a coroutine or thread) +char data[] = "Hello, World!"; +auto write_result = pipe.Write(data, strlen(data), coroutine); + +// Read data (in another coroutine or thread) +char buffer[1024]; +auto read_result = pipe.Read(buffer, sizeof(buffer), coroutine); +if (read_result.ok()) { + size_t bytes_read = *read_result; + // Process data... +} + +// SharedPtrPipe for sending shared_ptr objects (same process only) +auto shared_pipe = toolbelt::SharedPtrPipe::Create(); +if (shared_pipe.ok()) { + auto obj = std::make_shared(); + shared_pipe->Write(obj, coroutine); + + auto received = shared_pipe->Read(coroutine); + if (received.ok()) { + std::shared_ptr obj = *received; + // Use object... + } +} +``` + +### Table + +A table formatter for displaying tabular data with colors and sorting. + +**Header:** `toolbelt/table.h` + +#### API + +```cpp +class Table { +public: + struct Cell { + std::string data; + color::Color color; + }; + + Table(const std::vector titles, ssize_t sort_column = 0, + std::function comp = nullptr); + + void AddRow(const std::vector cells); + void AddRow(const std::vector cells, color::Color color); + void AddRowWithColors(const std::vector cells); + void AddRow(); + void SetCell(size_t col, Cell &&cell); + void Print(int width, std::ostream &os); + void Clear(); + void SortBy(size_t column, + std::function comp); + void SortBy(size_t column); + + static Cell MakeCell(std::string data, color::Color color = {...}); +}; +``` + +#### Example + +```cpp +#include "toolbelt/table.h" +#include + +// Create table with column titles +toolbelt::Table table({"Name", "Age", "City"}, 0); // Sort by first column + +// Add rows +table.AddRow({"Alice", "30", "New York"}); +table.AddRow({"Bob", "25", "London"}); +table.AddRow({"Charlie", "35", "Paris"}); + +// Add row with color +auto red = toolbelt::color::Color{.mod = toolbelt::color::kRed}; +table.AddRow({"David", "40", "Tokyo"}, red); + +// Add row with per-cell colors +std::vector cells = { + toolbelt::Table::MakeCell("Eve", toolbelt::color::Color{.mod = toolbelt::color::kGreen}), + toolbelt::Table::MakeCell("28"), + toolbelt::Table::MakeCell("Berlin") +}; +table.AddRowWithColors(cells); + +// Sort by age column (column 1) +table.SortBy(1, [](const std::string &a, const std::string &b) { + return std::stoi(a) < std::stoi(b); +}); + +// Print table +table.Print(80, std::cout); +``` + +### PayloadBuffer + +A memory buffer with built-in allocation and deallocation. Useful for message serialization and wire protocols. Supports both fixed-size and resizable buffers, with optional bitmap-based small block allocator. + +**Header:** `toolbelt/payload_buffer.h` + +#### Key Types + +```cpp +using BufferOffset = uint32_t; + +struct PayloadBuffer { + // Constructors + PayloadBuffer(uint32_t size, bool bitmap_allocator = true); + PayloadBuffer(uint32_t initial_size, Resizer r, bool bitmap_allocator = true); + + // Allocation + static void *Allocate(PayloadBuffer **buffer, uint32_t n, + bool clear = true, bool enable_small_block = true); + void Free(void *p); + static void *Realloc(PayloadBuffer **buffer, void *p, uint32_t n, + bool clear = true, bool enable_small_block = true); + + // String operations + static char *SetString(PayloadBuffer **self, const char *s, size_t len, + BufferOffset header_offset); + std::string GetString(BufferOffset header_offset) const; + std::string_view GetStringView(BufferOffset header_offset) const; + + // Vector operations + template + static void VectorPush(PayloadBuffer **self, VectorHeader *hdr, T v, + bool enable_small_block = true); + template + static void VectorReserve(PayloadBuffer **self, VectorHeader *hdr, size_t n, + bool enable_small_block = true); + template + T VectorGet(const VectorHeader *hdr, size_t index) const; + + // Address conversion + template + T *ToAddress(BufferOffset offset, size_t size = 0); + template + BufferOffset ToOffset(T *addr, size_t size = 0); + + // Utilities + size_t Size() const; + void Dump(std::ostream &os); + bool IsValidMagic() const; + bool IsMoveable() const; +}; +``` + +#### Example + +```cpp +#include "toolbelt/payload_buffer.h" + +// Create a fixed-size buffer +char buffer_memory[4096]; +toolbelt::PayloadBuffer *pb = new (buffer_memory) toolbelt::PayloadBuffer(4096); + +// Allocate memory in the buffer +void *data = toolbelt::PayloadBuffer::Allocate(&pb, 100); +// Use data... + +// Free memory +pb->Free(data); + +// Create a resizable buffer +auto resizer = [](toolbelt::PayloadBuffer **b, size_t old_size, size_t new_size) { + char *new_mem = new char[new_size]; + memcpy(new_mem, *b, old_size); + delete[] reinterpret_cast(*b); + *b = reinterpret_cast(new_mem); +}; +toolbelt::PayloadBuffer *resizable = new toolbelt::PayloadBuffer(1024, resizer); + +// Allocate string +toolbelt::BufferOffset str_offset = 100; +toolbelt::PayloadBuffer::SetString(&resizable, "Hello", 5, str_offset); +std::string str = resizable->GetString(str_offset); + +// Vector operations +toolbelt::VectorHeader vec_hdr = {0, 0}; +toolbelt::PayloadBuffer::VectorPush(&resizable, &vec_hdr, 42); +toolbelt::PayloadBuffer::VectorPush(&resizable, &vec_hdr, 100); +int value = resizable->VectorGet(&vec_hdr, 0); // Returns 42 +``` + +### TriggerFd + +A file descriptor that can be used to trigger events. Implemented as an `eventfd` on Linux or a pipe on other systems. + +**Header:** `toolbelt/triggerfd.h` + +#### API + +```cpp +class TriggerFd { +public: + TriggerFd(); + TriggerFd(const FileDescriptor &poll_fd, const FileDescriptor &trigger_fd); + + absl::Status Open(); + static absl::StatusOr Create(); + static absl::StatusOr Create(const FileDescriptor &poll_fd, + const FileDescriptor &trigger_fd); + + void Close(); + void SetPollFd(FileDescriptor fd); + void SetTriggerFd(FileDescriptor fd); + void Trigger(); + bool Clear(); // Clears trigger and returns true if it was triggered + FileDescriptor &GetPollFd(); + FileDescriptor &GetTriggerFd(); + void AddPollFd(std::vector &fds); +}; +``` + +#### Example + +```cpp +#include "toolbelt/triggerfd.h" +#include + +// Create a trigger file descriptor +auto trigger_result = toolbelt::TriggerFd::Create(); +if (!trigger_result.ok()) { + return; +} +toolbelt::TriggerFd trigger = *trigger_result; + +// Add to poll set +std::vector fds; +trigger.AddPollFd(fds); + +// In event loop +int result = poll(fds.data(), fds.size(), -1); +if (result > 0 && (fds[0].revents & POLLIN)) { + if (trigger.Clear()) { + // Trigger was set, handle event + } +} + +// Trigger from another thread/coroutine +trigger.Trigger(); +``` + +## Utility Functions + +### Now + +Get the current monotonic time in nanoseconds. + +**Header:** `toolbelt/clock.h` + +```cpp +uint64_t Now(); +``` + +#### Example + +```cpp +#include "toolbelt/clock.h" + +uint64_t start = toolbelt::Now(); +// Do work... +uint64_t end = toolbelt::Now(); +uint64_t elapsed_ns = end - start; +double elapsed_ms = elapsed_ns / 1e6; +``` + +### Hexdump + +Dump memory in hexadecimal format. + +**Header:** `toolbelt/hexdump.h` + +```cpp +void Hexdump(const void* addr, size_t length, FILE* out = stdout); +``` + +#### Example + +```cpp +#include "toolbelt/hexdump.h" + +char buffer[64] = "Hello, World!"; +toolbelt::Hexdump(buffer, sizeof(buffer)); +// Output: +// 00000000 48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21 00 00 00 |Hello, World!...| +// ... +``` + +### PrintCurrentStack + +Print the current stack trace. + +**Header:** `toolbelt/stacktrace.h` + +```cpp +void PrintCurrentStack(std::ostream &os); +``` + +#### Example + +```cpp +#include "toolbelt/stacktrace.h" +#include + +void some_function() { + // Print stack trace + toolbelt::PrintCurrentStack(std::cerr); +} +``` + +## Dependencies + +- [Abseil](https://github.com/abseil/abseil-cpp) - Status types, strings, formatting +- [co](https://github.com/dallison/co) - Coroutine library (for socket classes) + +## License + +See LICENSE file for licensing information. diff --git a/toolbelt/BUILD.bazel b/toolbelt/BUILD.bazel index 081ede1..6436531 100644 --- a/toolbelt/BUILD.bazel +++ b/toolbelt/BUILD.bazel @@ -12,6 +12,7 @@ cc_library( "sockets.cc", "table.cc", "triggerfd.cc", + "stacktrace.cc", ], hdrs = [ "bitset.h", @@ -26,13 +27,14 @@ cc_library( "sockets.h", "table.h", "triggerfd.h", + "stacktrace.h", ], deps = [ - "@com_google_absl//absl/status", - "@com_google_absl//absl/status:statusor", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/strings:str_format", - "@com_google_absl//absl/types:span", + "@abseil-cpp//absl/status", + "@abseil-cpp//absl/status:statusor", + "@abseil-cpp//absl/strings", + "@abseil-cpp//absl/strings:str_format", + "@abseil-cpp//absl/types:span", "@coroutines//:co", ], ) @@ -43,9 +45,9 @@ cc_test( srcs = ["fd_test.cc"], deps = [ ":toolbelt", - "@com_google_absl//absl/hash:hash_testing", - "@com_google_absl//absl/status", - "@com_google_absl//absl/status:statusor", + "@abseil-cpp//absl/hash:hash_testing", + "@abseil-cpp//absl/status", + "@abseil-cpp//absl/status:statusor", "@com_google_googletest//:gtest_main", ], ) @@ -56,24 +58,36 @@ cc_test( srcs = ["table_test.cc"], deps = [ ":toolbelt", - "@com_google_absl//absl/hash:hash_testing", - "@com_google_absl//absl/status", - "@com_google_absl//absl/status:statusor", - "@com_google_absl//absl/strings:str_format", + "@abseil-cpp//absl/hash:hash_testing", + "@abseil-cpp//absl/status", + "@abseil-cpp//absl/status:statusor", + "@abseil-cpp//absl/strings:str_format", + "@com_google_googletest//:gtest_main", + ], +) +cc_test( + name = "stacktrace_test", + size = "small", + srcs = ["stacktrace_test.cc"], + deps = [ + ":toolbelt", + "@abseil-cpp//absl/hash:hash_testing", + "@abseil-cpp//absl/status", + "@abseil-cpp//absl/status:statusor", + "@abseil-cpp//absl/strings:str_format", "@com_google_googletest//:gtest_main", ], ) - cc_test( name = "pipe_test", size = "small", srcs = ["pipe_test.cc"], deps = [ ":toolbelt", - "@com_google_absl//absl/hash:hash_testing", - "@com_google_absl//absl/status", - "@com_google_absl//absl/status:status_matchers", - "@com_google_absl//absl/status:statusor", + "@abseil-cpp//absl/hash:hash_testing", + "@abseil-cpp//absl/status", + "@abseil-cpp//absl/status:status_matchers", + "@abseil-cpp//absl/status:statusor", "@com_google_googletest//:gtest_main", ], ) @@ -84,9 +98,10 @@ cc_test( srcs = ["sockets_test.cc"], deps = [ ":toolbelt", - "@com_google_absl//absl/hash:hash_testing", - "@com_google_absl//absl/status", - "@com_google_absl//absl/status:statusor", + "@abseil-cpp//absl/hash:hash_testing", + "@abseil-cpp//absl/status", + "@abseil-cpp//absl/status:status_matchers", + "@abseil-cpp//absl/status:statusor", "@com_google_googletest//:gtest_main", ], ) diff --git a/toolbelt/fd.cc b/toolbelt/fd.cc index e4a8440..a160646 100644 --- a/toolbelt/fd.cc +++ b/toolbelt/fd.cc @@ -16,7 +16,7 @@ void CloseAllFds(std::function predicate) { } absl::StatusOr FileDescriptor::Read(void *buffer, size_t length, - co::Coroutine *c) { + const co::Coroutine *c) { char *buf = reinterpret_cast(buffer); size_t total = 0; while (total < length) { @@ -58,7 +58,7 @@ absl::StatusOr FileDescriptor::Read(void *buffer, size_t length, } absl::StatusOr FileDescriptor::Write(const void *buffer, size_t length, - co::Coroutine *c) { + const co::Coroutine *c) { const char *buf = reinterpret_cast(buffer); size_t total = 0; diff --git a/toolbelt/fd.h b/toolbelt/fd.h index d89dc68..19619d8 100644 --- a/toolbelt/fd.h +++ b/toolbelt/fd.h @@ -18,7 +18,7 @@ #include #include #include -#include "coroutine.h" +#include "co/coroutine.h" namespace toolbelt { @@ -56,7 +56,8 @@ class FileDescriptor { FileDescriptor() = default; // FileDescriptor initialize with an OS fd. Takes ownership // of the fd and will close it when all references go away. - explicit FileDescriptor(int fd) : data_(std::make_shared(fd)) {} + // If owned is false, the fd will not be closed when all references go away. + explicit FileDescriptor(int fd, bool owned = true) : data_(std::make_shared(fd, owned)) {} // Copy constructor, increments reference on shared data. Very cheap. FileDescriptor(const FileDescriptor &f) : data_(f.data_) {} @@ -119,13 +120,13 @@ class FileDescriptor { // Sets the OS fd. If it's the same as the underlying OS fd, there is // no effect (that's not another reference to it). Allocates new // shared data for the fd. - void SetFd(int fd) { + void SetFd(int fd, bool owned = true) { if (Fd() == fd) { // SetFd with same fd. This isn't another reference to the // fd. return; } - data_ = std::make_shared(fd); + data_ = std::make_shared(fd, owned); } void Reset() { Close(); } @@ -187,23 +188,27 @@ class FileDescriptor { return absl::OkStatus(); } - absl::StatusOr Read(void* buffer, size_t length, co::Coroutine* c = nullptr); + absl::StatusOr Read(void* buffer, size_t length, const co::Coroutine* c = nullptr); absl::StatusOr Write(const void* buffer, size_t length, - co::Coroutine* c = nullptr); + const co::Coroutine* c = nullptr); private: // Reference counted OS fd, shared among all FileDescriptors with the // same OS fd, provided you don't create two FileDescriptors with the // same OS fd (that would be a mistake but there's no way to stop it). struct SharedData { SharedData() = default; - SharedData(int f) : fd(f) {} + SharedData(int f, bool o) : fd(f), owned(o) {} ~SharedData() { if (fd != -1) { + if (!owned) { + return; + } ::close(fd); } } int fd = -1; // OS file descriptor. bool nonblocking = false; + bool owned = true; }; // The actual shared data. If nullptr the FileDescriptor is invalid. diff --git a/toolbelt/fd_test.cc b/toolbelt/fd_test.cc index 79a9e01..be1cd26 100644 --- a/toolbelt/fd_test.cc +++ b/toolbelt/fd_test.cc @@ -173,3 +173,26 @@ TEST(FdTest, Reset) { int e = fstat(f, &st); ASSERT_EQ(-1, e); } + +TEST(FdTest, CreateUnowned) { + int f = dup(1); + { + FileDescriptor fd(f, false); + ASSERT_TRUE(fd.Valid()); + ASSERT_EQ(f, fd.Fd()); + ASSERT_EQ(1, fd.RefCount()); + } + // Fd should still be open. + ASSERT_EQ(0, fcntl(f, F_GETFD)); + + // Now take ownership of f. + { + FileDescriptor fd(f, true); + ASSERT_TRUE(fd.Valid()); + ASSERT_EQ(f, fd.Fd()); + ASSERT_EQ(1, fd.RefCount()); + } + // Will be closed. + ASSERT_EQ(-1, fcntl(f, F_GETFD)); + ASSERT_EQ(EBADF, errno); +} diff --git a/toolbelt/logging.cc b/toolbelt/logging.cc index 933d76e..00fcc3e 100644 --- a/toolbelt/logging.cc +++ b/toolbelt/logging.cc @@ -7,6 +7,7 @@ #include "clock.h" #include #include +#include namespace toolbelt { @@ -148,10 +149,19 @@ void Logger::VLog(LogLevel level, const char *fmt, va_list ap) { if (level < min_level_) { return; } +#if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-nonliteral" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +#endif size_t n = vsnprintf(buffer_, sizeof(buffer_), fmt, ap); +#if defined(__clang__) #pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif // Strip final \n if present. Refactoring from printf can leave // this in place. diff --git a/toolbelt/logging.h b/toolbelt/logging.h index ca8f177..50b95a2 100644 --- a/toolbelt/logging.h +++ b/toolbelt/logging.h @@ -10,7 +10,6 @@ #include #include #include -#include #include #include diff --git a/toolbelt/payload_buffer.cc b/toolbelt/payload_buffer.cc index a45b116..21fe3f7 100644 --- a/toolbelt/payload_buffer.cc +++ b/toolbelt/payload_buffer.cc @@ -5,7 +5,7 @@ namespace toolbelt { static constexpr struct BitmapRunInfo { int num; - int size; + uint32_t size; } bitmp_run_infos[kNumBitmapRuns] = { {kRunSize1, kBitmapRunSize1}, {kRunSize2, kBitmapRunSize2}, @@ -162,7 +162,7 @@ void PayloadBuffer::Dump(std::ostream &os) { os << " free_list: " << free_list << " " << ToAddress(free_list) << std::endl; os << " message: " << message << " " << ToAddress(message) << std::endl; - for (int i = 0; i < kNumBitmapRuns; i++) { + for (size_t i = 0; i < kNumBitmapRuns; i++) { os << " bitmaps[" << i << "]: " << bitmaps[i] << " " << ToAddress(bitmaps[i]) << std::endl; } @@ -576,9 +576,9 @@ void *PayloadBuffer::Realloc(PayloadBuffer **buffer, void *p, uint32_t n, (n & kBitmapRunSizeMask); *len_ptr = encoded_size; - if (clear && n > decoded_length) { + if (clear && n > static_cast(decoded_length)) { memset(reinterpret_cast(p) + decoded_length, 0, - n - decoded_length); + n - static_cast(decoded_length)); } return p; } @@ -589,7 +589,7 @@ void *PayloadBuffer::Realloc(PayloadBuffer **buffer, void *p, uint32_t n, return NULL; } memcpy(newp, p, decoded_length); - if (clear && n > decoded_length) { + if (clear && n > static_cast(decoded_length)) { memset(reinterpret_cast(newp) + decoded_length, 0, n - decoded_length); } @@ -629,8 +629,8 @@ void *PayloadBuffer::Realloc(PayloadBuffer **buffer, void *p, uint32_t n, int diff = n - orig_length; if (alloc_addr + orig_length == free_addr) { // There is a free block above. See if has enough space. - if (free_block->length > diff) { - ssize_t freelen = free_block->length - diff; + if (free_block->length > static_cast(diff)) { + uint32_t freelen = free_block->length - static_cast(diff); if (freelen > sizeof(FreeBlockHeader)) { (*buffer)->ExpandIntoFreeBlockAbove(free_block, n, diff, freelen, len_ptr, next_ptr, clear); @@ -642,7 +642,7 @@ void *PayloadBuffer::Realloc(PayloadBuffer **buffer, void *p, uint32_t n, if (prev != NULL) { uintptr_t prev_addr = (uintptr_t)prev; if (prev_addr + prev->length == (uintptr_t)alloc_block && - prev->length >= diff) { + prev->length >= static_cast(diff)) { // Previous free block is adjacent and has enough space in it. // Use start of new block as new address and place FreeBlockHeader // at newly free part. diff --git a/toolbelt/payload_buffer.h b/toolbelt/payload_buffer.h index 5c56cfa..ba8c9f0 100644 --- a/toolbelt/payload_buffer.h +++ b/toolbelt/payload_buffer.h @@ -144,7 +144,7 @@ struct PayloadBuffer { PayloadBuffer(uint32_t size, bool bitmap_allocator = true) : magic(kFixedBufferMagic | (bitmap_allocator ? kBitMapFlag : 0)), message(0), hwm(0), full_size(size), metadata(0) { - for (int i = 0; i < kNumBitmapRuns; i++) { + for (size_t i = 0; i < kNumBitmapRuns; i++) { bitmaps[i] = 0; } InitFreeList(); @@ -163,7 +163,7 @@ struct PayloadBuffer { PayloadBuffer(uint32_t initial_size, Resizer r, bool bitmap_allocator = true) : magic(kMovableBufferMagic | (bitmap_allocator ? kBitMapFlag : 0)), message(0), hwm(0), full_size(initial_size), metadata(0) { - for (int i = 0; i < kNumBitmapRuns; i++) { + for (size_t i = 0; i < kNumBitmapRuns; i++) { bitmaps[i] = 0; } InitFreeList(); diff --git a/toolbelt/payload_buffer_test.cc b/toolbelt/payload_buffer_test.cc index e33e52e..b699966 100644 --- a/toolbelt/payload_buffer_test.cc +++ b/toolbelt/payload_buffer_test.cc @@ -1,6 +1,7 @@ #include "toolbelt/clock.h" #include "toolbelt/hexdump.h" #include "toolbelt/payload_buffer.h" +#include #include #include @@ -152,13 +153,13 @@ TEST(BufferTest, SmallBlockAllocFree) { blocks.push_back(addr); } // Free every 5th block. - for (int i = 0; i < blocks.size(); i++) { + for (size_t i = 0; i < blocks.size(); i++) { if (i % 5 == 0) { pb->Free(blocks[i]); } } // Now allocate every 5th block again. - for (int i = 0; i < blocks.size(); i++) { + for (size_t i = 0; i < blocks.size(); i++) { if (i % 5 == 0) { size_t size = sizes[i % sizes.size()]; void *addr = PayloadBuffer::Allocate(&pb, size); @@ -314,7 +315,7 @@ TEST(BufferTest, TypicalPerformance) { small_blocks.push_back(addr); } // Free some of the blocks. - for (int i = prev_size; i < small_blocks.size(); i++) { + for (size_t i = prev_size; i < small_blocks.size(); i++) { if (i % 8 == 0) { continue; } @@ -341,7 +342,7 @@ TEST(BufferTest, TypicalPerformance) { large_blocks.push_back(addr); } // Free some of the blocks. - for (int i = prev_size; i < large_blocks.size(); i++) { + for (size_t i = prev_size; i < large_blocks.size(); i++) { if (i % 8 == 0) { continue; } diff --git a/toolbelt/pipe.cc b/toolbelt/pipe.cc index 82c89cb..0cb8312 100644 --- a/toolbelt/pipe.cc +++ b/toolbelt/pipe.cc @@ -83,7 +83,7 @@ absl::Status Pipe::SetPipeSize(size_t size) { } absl::StatusOr Pipe::Read(char *buffer, size_t length, - co::Coroutine *c) { + const co::Coroutine *c) { size_t total = 0; ScopedRead sc(*this, c); @@ -123,7 +123,7 @@ absl::StatusOr Pipe::Read(char *buffer, size_t length, } absl::StatusOr Pipe::Write(const char *buffer, size_t length, - co::Coroutine *c) { + const co::Coroutine *c) { size_t total = 0; ScopedWrite sc(*this, c); diff --git a/toolbelt/pipe.h b/toolbelt/pipe.h index 25996db..f05fb20 100644 --- a/toolbelt/pipe.h +++ b/toolbelt/pipe.h @@ -3,7 +3,7 @@ #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_format.h" -#include "coroutine.h" +#include "co/coroutine.h" #include "toolbelt/fd.h" #include @@ -66,9 +66,9 @@ class Pipe { absl::Status SetPipeSize(size_t size); virtual absl::StatusOr Read(char *buffer, size_t length, - co::Coroutine *c = nullptr); + const co::Coroutine *c = nullptr); virtual absl::StatusOr Write(const char *buffer, size_t length, - co::Coroutine *c = nullptr); + const co::Coroutine *c = nullptr); protected: // RAII classes for keeping coroutines from interleaving reads or writes on a @@ -78,7 +78,7 @@ class Pipe { // // Same applies to non-coroutine use except we block with a sleep. struct ScopedRead { - ScopedRead(Pipe &p, co::Coroutine *c) : pipe(p) { + ScopedRead(Pipe &p, const co::Coroutine *c) : pipe(p) { while (pipe.read_in_progress_) { if (c) { c->Yield(); @@ -97,7 +97,7 @@ class Pipe { }; struct ScopedWrite { - ScopedWrite(Pipe &p, co::Coroutine *c) : pipe(p) { + ScopedWrite(Pipe &p, const co::Coroutine *c) : pipe(p) { while (pipe.write_in_progress_) { if (c) { c->Yield(); @@ -151,15 +151,15 @@ template class SharedPtrPipe : public Pipe { // You can't use raw buffers with shared ptr pipes. absl::StatusOr Read(char *buffer, size_t length, - co::Coroutine *c = nullptr) override { + const co::Coroutine *c = nullptr) override { return absl::InternalError("Not supported on SharedPtrPipe"); } absl::StatusOr Write(const char *buffer, size_t length, - co::Coroutine *c = nullptr) override { + const co::Coroutine *c = nullptr) override { return absl::InternalError("Not supported on SharedPtrPipe"); } - absl::StatusOr> Read(co::Coroutine *c = nullptr) { + absl::StatusOr> Read(const co::Coroutine *c = nullptr) { char buffer[sizeof(std::shared_ptr)]; size_t length = sizeof(buffer); size_t total = 0; @@ -205,7 +205,7 @@ template class SharedPtrPipe : public Pipe { } // This makes the pipe an owner of the pointer. - absl::Status Write(std::shared_ptr p, co::Coroutine *c = nullptr) { + absl::Status Write(std::shared_ptr p, const co::Coroutine *c = nullptr) { // On entry, ref count for p = N char buffer[sizeof(std::shared_ptr)]; @@ -272,4 +272,4 @@ template class SharedPtrPipe : public Pipe { } }; -} // namespace toolbelt \ No newline at end of file +} // namespace toolbelt diff --git a/toolbelt/pipe_test.cc b/toolbelt/pipe_test.cc index 097160c..d0d5811 100644 --- a/toolbelt/pipe_test.cc +++ b/toolbelt/pipe_test.cc @@ -3,7 +3,7 @@ // See LICENSE file for licensing information. #include "absl/status/status_matchers.h" -#include "coroutine.h" +#include "co/coroutine.h" #include "pipe.h" #include @@ -387,4 +387,4 @@ TEST(PipeTest, CoroutineOverFullPipeReadAndWriteMultiwriterNonblocking) { } }); scheduler.Run(); -} \ No newline at end of file +} diff --git a/toolbelt/sockets.cc b/toolbelt/sockets.cc index 27905c8..2010e4a 100644 --- a/toolbelt/sockets.cc +++ b/toolbelt/sockets.cc @@ -33,19 +33,19 @@ InetAddress InetAddress::AnyAddress(int port) { return InetAddress(port); } InetAddress::InetAddress(const in_addr &ip, int port) { valid_ = true; addr_ = { -#if defined(_APPLE__) - .sin_len = sizeof(int), +#if defined(__APPLE__) + .sin_len = sizeof(struct sockaddr_in), #endif .sin_family = AF_INET, .sin_port = htons(port), - .sin_addr = {.s_addr = htonl(ip.s_addr)}}; + .sin_addr = {.s_addr = ip.s_addr}}; } InetAddress::InetAddress(int port) { valid_ = true; addr_ = { -#if defined(_APPLE__) - .sin_len = sizeof(int), +#if defined(__APPLE__) + .sin_len = sizeof(struct sockaddr_in), #endif .sin_family = AF_INET, .sin_port = htons(port), @@ -68,8 +68,8 @@ InetAddress::InetAddress(const std::string &hostname, int port) { } valid_ = true; addr_ = { -#if defined(_APPLE__) - .sin_len = sizeof(int), +#if defined(__APPLE__) + .sin_len = sizeof(struct sockaddr_in), #endif .sin_family = AF_INET, .sin_port = htons(port), @@ -88,7 +88,7 @@ VirtualAddress::VirtualAddress(uint32_t cid, uint32_t port) { valid_ = true; memset(&addr_, 0, sizeof(addr_)); addr_ = { -#if defined(_APPLE__) +#if defined(__APPLE__) .svm_len = sizeof(struct sockaddr_vm), #endif .svm_family = AF_VSOCK, @@ -100,7 +100,7 @@ VirtualAddress::VirtualAddress(uint32_t port) { valid_ = true; memset(&addr_, 0, sizeof(addr_)); addr_ = { -#if defined(_APPLE__) +#if defined(__APPLE__) .svm_len = sizeof(struct sockaddr_vm), #endif .svm_family = AF_VSOCK, @@ -131,14 +131,17 @@ std::string VirtualAddress::ToString() const { return absl::StrFormat("%d:%d", addr_.svm_cid, addr_.svm_port); } -static ssize_t ReceiveFully(co::Coroutine *c, int fd, size_t length, +static ssize_t ReceiveFully(const co::Coroutine *c, int fd, size_t length, char *buffer, size_t buflen) { int offset = 0; size_t remaining = length; while (remaining > 0) { size_t readlen = std::min(remaining, buflen); if (c != nullptr) { - c->Wait(fd, POLLIN); + int f = c->Wait(fd, POLLIN); + if (f != fd) { + return -1; + } } ssize_t n = ::recv(fd, buffer + offset, readlen, 0); if (n == -1) { @@ -164,7 +167,7 @@ static ssize_t ReceiveFully(co::Coroutine *c, int fd, size_t length, return length; } -static ssize_t SendFully(co::Coroutine *c, int fd, const char *buffer, +static ssize_t SendFully(const co::Coroutine *c, int fd, const char *buffer, size_t length, bool blocking) { size_t remaining = length; size_t offset = 0; @@ -176,7 +179,10 @@ static ssize_t SendFully(co::Coroutine *c, int fd, const char *buffer, // Yielding before sending to a nonblocking socket will // cause a context switch between coroutines and we want // the write to the network to be as fast as possible. - c->Wait(fd, POLLOUT); + int f = c->Wait(fd, POLLOUT); + if (f != fd) { + return -1; + } } ssize_t n = ::send(fd, buffer + offset, remaining, 0); if (n == -1) { @@ -190,7 +196,10 @@ static ssize_t SendFully(co::Coroutine *c, int fd, const char *buffer, // If we are nonblocking yield the coroutine now. When we // are resumed we can write to the socket again. if (!blocking) { - c->Wait(fd, POLLOUT); + int f = c->Wait(fd, POLLOUT); + if (f != fd) { + return -1; + } } continue; } @@ -207,7 +216,7 @@ static ssize_t SendFully(co::Coroutine *c, int fd, const char *buffer, } absl::StatusOr Socket::Receive(char *buffer, size_t buflen, - co::Coroutine *c) { + const co::Coroutine *c) { if (!Connected()) { return absl::InternalError("Socket is not connected"); } @@ -221,7 +230,7 @@ absl::StatusOr Socket::Receive(char *buffer, size_t buflen, } absl::StatusOr Socket::Send(const char *buffer, size_t length, - co::Coroutine *c) { + const co::Coroutine *c) { if (!Connected()) { return absl::InternalError("Socket is not connected"); } @@ -235,7 +244,7 @@ absl::StatusOr Socket::Send(const char *buffer, size_t length, } absl::StatusOr Socket::ReceiveMessage(char *buffer, size_t buflen, - co::Coroutine *c) { + const co::Coroutine *c) { if (!Connected()) { return absl::InternalError("Socket is not connected"); } @@ -268,7 +277,7 @@ absl::StatusOr Socket::ReceiveMessage(char *buffer, size_t buflen, } absl::StatusOr> -Socket::ReceiveVariableLengthMessage(co::Coroutine *c) { +Socket::ReceiveVariableLengthMessage(const co::Coroutine *c) { if (!Connected()) { return absl::InternalError("Socket is not connected"); } @@ -303,7 +312,7 @@ Socket::ReceiveVariableLengthMessage(co::Coroutine *c) { } absl::StatusOr Socket::SendMessage(char *buffer, size_t length, - co::Coroutine *c) { + const co::Coroutine *c) { if (!Connected()) { return absl::InternalError("Socket is not connected"); } @@ -371,12 +380,15 @@ absl::Status UnixSocket::Bind(const std::string &pathname, bool listen) { return absl::OkStatus(); } -absl::StatusOr UnixSocket::Accept(co::Coroutine *c) const { +absl::StatusOr UnixSocket::Accept(const co::Coroutine *c) const { if (!fd_.Valid()) { return absl::InternalError("UnixSocket is not valid"); } if (c != nullptr) { - c->Wait(fd_.Fd(), POLLIN); + int fd = c->Wait(fd_.Fd(), POLLIN); + if (fd != fd_.Fd()) { + return absl::InternalError("Interrupted"); + } } struct sockaddr_un sender; socklen_t sock_len = sizeof(sender); @@ -419,7 +431,7 @@ absl::Status UnixSocket::Connect(const std::string &pathname) { } absl::Status UnixSocket::SendFds(const std::vector &fds, - co::Coroutine *c) { + const co::Coroutine *c) { if (!Connected()) { return absl::InternalError("Socket is not connected"); } @@ -459,7 +471,10 @@ absl::Status UnixSocket::SendFds(const std::vector &fds, } if (c != nullptr) { - c->Wait(fd_.Fd(), POLLOUT); + int fd = c->Wait(fd_.Fd(), POLLOUT); + if (fd != fd_.Fd()) { + return absl::InternalError("Interrupted"); + } } int e = ::sendmsg(fd_.Fd(), &msg, 0); if (e == -1) { @@ -473,7 +488,7 @@ absl::Status UnixSocket::SendFds(const std::vector &fds, } absl::Status UnixSocket::ReceiveFds(std::vector &fds, - co::Coroutine *c) { + const co::Coroutine *c) { if (!Connected()) { return absl::InternalError("Socket is not connected"); } @@ -498,7 +513,10 @@ absl::Status UnixSocket::ReceiveFds(std::vector &fds, .msg_controllen = sizeof(u.buf)}; if (c != nullptr) { - c->Wait(fd_.Fd(), POLLIN); + int fd = c->Wait(fd_.Fd(), POLLIN); + if (fd != fd_.Fd()) { + return absl::InternalError("Interrupted"); + } } ssize_t n = ::recvmsg(fd_.Fd(), &msg, 0); if (n == -1) { @@ -634,12 +652,15 @@ absl::Status TCPSocket::Bind(const InetAddress &addr, bool listen) { return absl::OkStatus(); } -absl::StatusOr TCPSocket::Accept(co::Coroutine *c) const { +absl::StatusOr TCPSocket::Accept(const co::Coroutine *c) const { if (!fd_.Valid()) { return absl::InternalError("Socket is not valid"); } if (c != nullptr) { - c->Wait(fd_.Fd(), POLLIN); + int fd = c->Wait(fd_.Fd(), POLLIN); + if (fd != fd_.Fd()) { + return absl::InternalError("Interrupted"); + } } struct sockaddr_in sender; socklen_t sock_len = sizeof(sender); @@ -776,9 +797,12 @@ absl::Status UDPSocket::SetMulticastLoop() { } absl::Status UDPSocket::SendTo(const InetAddress &addr, const void *buffer, - size_t length, co::Coroutine *c) { + size_t length, const co::Coroutine *c) { if (c != nullptr) { - c->Wait(fd_.Fd(), POLLOUT); + int fd = c->Wait(fd_.Fd(), POLLOUT); + if (fd != fd_.Fd()) { + return absl::InternalError("Interrupted"); + } } ssize_t n = ::sendto(fd_.Fd(), buffer, length, 0, reinterpret_cast(&addr.GetAddress()), @@ -792,9 +816,12 @@ absl::Status UDPSocket::SendTo(const InetAddress &addr, const void *buffer, } absl::StatusOr UDPSocket::Receive(void *buffer, size_t buflen, - co::Coroutine *c) { + const co::Coroutine *c) { if (c != nullptr) { - c->Wait(fd_.Fd(), POLLIN); + int fd = c->Wait(fd_.Fd(), POLLIN); + if (fd != fd_.Fd()) { + return absl::InternalError("Interrupted"); + } } ssize_t n = recv(fd_.Fd(), buffer, buflen, 0); if (n == -1) { @@ -805,9 +832,12 @@ absl::StatusOr UDPSocket::Receive(void *buffer, size_t buflen, } absl::StatusOr UDPSocket::ReceiveFrom(InetAddress &sender, void *buffer, size_t buflen, - co::Coroutine *c) { + const co::Coroutine *c) { if (c != nullptr) { - c->Wait(fd_.Fd(), POLLIN); + int fd = c->Wait(fd_.Fd(), POLLIN); + if (fd != fd_.Fd()) { + return absl::InternalError("Interrupted"); + } } struct sockaddr_in sender_addr; socklen_t sender_addr_length = sizeof(sender_addr); @@ -819,6 +849,9 @@ absl::StatusOr UDPSocket::ReceiveFrom(InetAddress &sender, return absl::InternalError( absl::StrFormat("Unable to receive UDP datagram: %s", strerror(errno))); } +#if defined(__APPLE__) + sender_addr.sin_len = sender_addr_length; +#endif sender = {sender_addr}; return n; } @@ -859,12 +892,15 @@ absl::Status VirtualStreamSocket::Bind(const VirtualAddress &addr, } absl::StatusOr -VirtualStreamSocket::Accept(co::Coroutine *c) const { +VirtualStreamSocket::Accept(const co::Coroutine *c) const { if (!fd_.Valid()) { return absl::InternalError("Socket is not valid"); } if (c != nullptr) { - c->Wait(fd_.Fd(), POLLIN); + int fd = c->Wait(fd_.Fd(), POLLIN); + if (fd != fd_.Fd()) { + return absl::InternalError("Interrupted"); + } } struct sockaddr_vm sender; socklen_t sock_len = sizeof(sender); diff --git a/toolbelt/sockets.h b/toolbelt/sockets.h index 795c1e3..1127e42 100644 --- a/toolbelt/sockets.h +++ b/toolbelt/sockets.h @@ -6,7 +6,7 @@ #define __TOOLBELT_SOCKETS_H #include "absl/status/status.h" #include "absl/status/statusor.h" -#include "coroutine.h" +#include "co/coroutine.h" #include "fd.h" #include #include @@ -53,6 +53,10 @@ struct sockaddr_vm { #define AF_VSOCK 40 #endif +#if !defined(VMADDR_CID_LOCAL) +#define VMADDR_CID_LOCAL 3 +#endif + #include #include #include @@ -110,7 +114,7 @@ class InetAddress { static InetAddress AnyAddress(int port); private: - struct sockaddr_in addr_; // In network byte order. + struct sockaddr_in addr_ = {}; // In network byte order. bool valid_ = false; }; @@ -348,17 +352,17 @@ class Socket { // Send and receive raw buffers. absl::StatusOr Receive(char *buffer, size_t buflen, - co::Coroutine *c = nullptr); + const co::Coroutine *c = nullptr); absl::StatusOr Send(const char *buffer, size_t length, - co::Coroutine *c = nullptr); + const co::Coroutine *c = nullptr); // Send and receive length-delimited message. The length is a 4-byte // network byte order (big endian) int as the first 4 bytes and // contains the length of the message. absl::StatusOr ReceiveMessage(char *buffer, size_t buflen, - co::Coroutine *c = nullptr); + const co::Coroutine *c = nullptr); absl::StatusOr> - ReceiveVariableLengthMessage(co::Coroutine *c = nullptr); + ReceiveVariableLengthMessage(const co::Coroutine *c = nullptr); // For SendMessage, the buffer pointer must be 4 bytes beyond // the actual buffer start, which must be length+4 bytes @@ -366,7 +370,7 @@ class Socket { // at buffer-4. This is to allow us to do a single send // to the socket rather than splitting it into 2. absl::StatusOr SendMessage(char *buffer, size_t length, - co::Coroutine *c = nullptr); + const co::Coroutine *c = nullptr); absl::Status SetNonBlocking() { if (absl::Status s = fd_.SetNonBlocking(); !s.ok()) { @@ -399,12 +403,12 @@ class UnixSocket : public Socket { absl::Status Bind(const std::string &pathname, bool listen); absl::Status Connect(const std::string &pathname); - absl::StatusOr Accept(co::Coroutine *c = nullptr) const; + absl::StatusOr Accept(const co::Coroutine *c = nullptr) const; absl::Status SendFds(const std::vector &fds, - co::Coroutine *c = nullptr); + const co::Coroutine *c = nullptr); absl::Status ReceiveFds(std::vector &fds, - co::Coroutine *c = nullptr); + const co::Coroutine *c = nullptr); std::string BoundAddress() const { return bound_address_; } absl::StatusOr GetPeerName() const; @@ -449,12 +453,12 @@ class UDPSocket : public NetworkSocket { // NOTE: Read and Write may or may not work on UDP sockets. Use SendTo and // Receive for datagrams. absl::Status SendTo(const InetAddress &addr, const void *buffer, - size_t length, co::Coroutine *c = nullptr); + size_t length, const co::Coroutine *c = nullptr); absl::StatusOr Receive(void *buffer, size_t buflen, - co::Coroutine *c = nullptr); + const co::Coroutine *c = nullptr); absl::StatusOr ReceiveFrom(InetAddress &sender, void *buffer, size_t buflen, - co::Coroutine *c = nullptr); + const co::Coroutine *c = nullptr); absl::Status SetBroadcast(); absl::Status SetMulticastLoop(); }; @@ -468,7 +472,7 @@ class TCPSocket : public NetworkSocket { absl::Status Bind(const InetAddress &addr, bool listen); - absl::StatusOr Accept(co::Coroutine *c = nullptr) const; + absl::StatusOr Accept(const co::Coroutine *c = nullptr) const; absl::StatusOr LocalAddress(int port) const; @@ -485,7 +489,7 @@ class VirtualStreamSocket : public Socket { absl::Status Bind(const VirtualAddress &addr, bool listen); - absl::StatusOr Accept(co::Coroutine *c = nullptr) const; + absl::StatusOr Accept(const co::Coroutine *c = nullptr) const; absl::StatusOr LocalAddress(uint32_t port) const; const VirtualAddress &BoundAddress() const { return bound_address_; } @@ -546,7 +550,7 @@ class StreamSocket { return absl::Status(absl::StatusCode::kInternal, "Invalid socket address"); } - absl::StatusOr Accept(co::Coroutine *c = nullptr) const { + absl::StatusOr Accept(const co::Coroutine *c = nullptr) const { return std::visit( EyeOfNewt{ [&](const TCPSocket &s) mutable -> absl::StatusOr { @@ -612,7 +616,7 @@ class StreamSocket { // Send and receive raw buffers. absl::StatusOr Receive(char *buffer, size_t buflen, - co::Coroutine *c = nullptr) { + const co::Coroutine *c = nullptr) { return std::visit( EyeOfNewt{[&](TCPSocket &s) { return s.Receive(buffer, buflen, c); }, [&](VirtualStreamSocket &s) { @@ -623,7 +627,7 @@ class StreamSocket { } absl::StatusOr Send(const char *buffer, size_t length, - co::Coroutine *c = nullptr) { + const co::Coroutine *c = nullptr) { return std::visit( EyeOfNewt{ [&](TCPSocket &s) { return s.Send(buffer, length, c); }, @@ -636,7 +640,7 @@ class StreamSocket { // network byte order (big endian) int as the first 4 bytes and // contains the length of the message. absl::StatusOr ReceiveMessage(char *buffer, size_t buflen, - co::Coroutine *c = nullptr) { + const co::Coroutine *c = nullptr) { return std::visit( EyeOfNewt{ [&](TCPSocket &s) { return s.ReceiveMessage(buffer, buflen, c); }, @@ -648,7 +652,7 @@ class StreamSocket { } absl::StatusOr> - ReceiveVariableLengthMessage(co::Coroutine *c = nullptr) { + ReceiveVariableLengthMessage(const co::Coroutine *c = nullptr) { return std::visit( EyeOfNewt{ [&](TCPSocket &s) { return s.ReceiveVariableLengthMessage(c); }, @@ -665,7 +669,7 @@ class StreamSocket { // at buffer-4. This is to allow us to do a single send // to the socket rather than splitting it into 2. absl::StatusOr SendMessage(char *buffer, size_t length, - co::Coroutine *c = nullptr) { + const co::Coroutine *c = nullptr) { return std::visit( EyeOfNewt{ [&](TCPSocket &s) { return s.SendMessage(buffer, length, c); }, diff --git a/toolbelt/sockets_test.cc b/toolbelt/sockets_test.cc index 476c89a..08c253c 100644 --- a/toolbelt/sockets_test.cc +++ b/toolbelt/sockets_test.cc @@ -4,13 +4,31 @@ #include #include #include +#include +#include "absl/status/status_matchers.h" +#include "toolbelt/hexdump.h" + +#define VAR(a) a##__COUNTER__ +#define EVAL_AND_ASSERT_OK(expr) EVAL_AND_ASSERT_OK2(VAR(r_), expr) + +#define EVAL_AND_ASSERT_OK2(result, expr) \ + ({ \ + auto result = (expr); \ + if (!result.ok()) { \ + std::cerr << result.status() << std::endl; \ + } \ + ASSERT_OK(result); \ + std::move(*result); \ + }) + +#define ASSERT_OK(e) ASSERT_THAT(e, ::absl_testing::IsOk()) namespace { constexpr std::string_view TEST_DATA = "The quick brown fox jumped over the lazy dog."; const static absl::Duration LOOPBACK_TIMEOUT = absl::Milliseconds(10); // Test class to hold on to a randomly assigned unused port until destruction -// Any tests binding to this unused port will probably need to call NetworkSocket::SetReusePort +// Any tests Binding to this unused port will probably need to call NetworkSocket::SetReusePort class UnusedPort { public: UnusedPort() { @@ -31,42 +49,491 @@ class UnusedPort { }; } +TEST(SocketsTest, InetAddresses) { + toolbelt::InetAddress addr1; + ASSERT_FALSE(addr1.Valid()); + + toolbelt::InetAddress addr2 = toolbelt::InetAddress::BroadcastAddress(1234); + ASSERT_TRUE(addr2.Valid()); + ASSERT_EQ(1234, addr2.Port()); + + toolbelt::InetAddress addr3 = toolbelt::InetAddress::AnyAddress(4321); + ASSERT_EQ(4321, addr3.Port()); + + toolbelt::InetAddress local_ip = toolbelt::InetAddress("127.0.0.1", 1111); + ASSERT_EQ(1111, local_ip.Port()); + ASSERT_EQ("127.0.0.1:1111", local_ip.ToString()); + + toolbelt::InetAddress local_host = toolbelt::InetAddress("localhost", 2222); + ASSERT_EQ(2222, local_host.Port()); + ASSERT_EQ("127.0.0.1:2222", local_host.ToString()); + + toolbelt::InetAddress bad = toolbelt::InetAddress("foobardoesntexist", 2222); + ASSERT_FALSE(bad.Valid()); + + in_addr ipaddr; + ASSERT_EQ(1, inet_pton(AF_INET, "127.0.0.1", &ipaddr.s_addr)); + toolbelt::InetAddress local_in = toolbelt::InetAddress(ipaddr, 3333); + ASSERT_EQ(3333, local_in.Port()); + ASSERT_EQ("127.0.0.1:3333", local_in.ToString()); +} + +TEST(SocketsTest, UnixSocket) { + char tmp[] = "/tmp/socketsXXXXXX"; + int fd = mkstemp(tmp); + ASSERT_NE(-1, fd); + std::string socket_name = tmp; + close(fd); + + unlink(socket_name.c_str()); + co::CoroutineScheduler scheduler; + + toolbelt::UnixSocket listener; + absl::Status status = listener.Bind(socket_name, true); + std::cerr << status << std::endl; + ASSERT_TRUE(status.ok()); + + co::Coroutine incoming(scheduler, [&listener](co::Coroutine* c) { + absl::StatusOr s = listener.Accept(c); + ASSERT_TRUE(s.ok()); + auto socket = s.value(); + + char buffer[256]; + // ReceiveMessage uses the 4 bytes below the buffer for the length. + absl::StatusOr nbytes = socket.ReceiveMessage(buffer + 4, sizeof(buffer) - 4, c); + ASSERT_TRUE(nbytes.ok()); + auto n = nbytes.value(); + ASSERT_EQ(12, n); // "hello world\0" + ASSERT_EQ("hello world", std::string(buffer + 4, n - 1)); + std::vector fds; + + absl::Status s2 = socket.ReceiveFds(fds, c); + ASSERT_TRUE(s2.ok()); + ASSERT_EQ(3, fds.size()); + }); + + co::Coroutine outgoing(scheduler, [&socket_name](co::Coroutine* c) { + toolbelt::UnixSocket socket; + absl::Status s = socket.Connect(socket_name); + ASSERT_TRUE(s.ok()); + char buffer[256]; + // SendMessage uses the 4 bytes below the buffer for the length of the message. + ssize_t n = snprintf(buffer + 4, sizeof(buffer) - 4, "hello world"); + n += 1; // Include NUL at end. + absl::StatusOr nsent = socket.SendMessage(buffer + 4, n, c); + ASSERT_TRUE(nsent.ok()); + ASSERT_EQ(n + 4, nsent.value()); + + std::vector fds; + for (int i = 0; i < 3; i++) { + // We dup the file descriptors to avoid closing stdout. + fds.push_back(toolbelt::FileDescriptor(dup(i))); + } + absl::Status s2 = socket.SendFds(fds, c); + ASSERT_TRUE(s2.ok()); + }); + + scheduler.Run(); + remove(socket_name.c_str()); +} + +TEST(SocketsTest, UnixSocketErrors) { + toolbelt::UnixSocket socket; + // Socket is inValid, all will fail. + ASSERT_FALSE(socket.Accept().ok()); + ASSERT_FALSE(socket.Connect("foobar").ok()); + std::vector fds; + ASSERT_FALSE(socket.SendFds(fds).ok()); + ASSERT_FALSE(socket.ReceiveFds(fds).ok()); +} + +TEST(SocketsTest, TCPSocket) { + toolbelt::InetAddress addr("127.0.0.1", 6502); + + co::CoroutineScheduler scheduler; + + toolbelt::TCPSocket listener; + ASSERT_TRUE(listener.SetReuseAddr().ok()); + absl::Status status = listener.Bind(addr, true); + ASSERT_TRUE(status.ok()); + + co::Coroutine incoming(scheduler, [&listener](co::Coroutine* c) { + absl::StatusOr s = listener.Accept(c); + ASSERT_TRUE(s.ok()); + auto socket = s.value(); + + absl::StatusOr> b = socket.ReceiveVariableLengthMessage(c); + ASSERT_TRUE(b.ok()); + auto buf = b.value(); + ASSERT_EQ(12, buf.size()); // "hello world\0" + ASSERT_EQ("hello world", std::string(buf.data(), 11)); + }); + + co::Coroutine outgoing(scheduler, [&addr](co::Coroutine* c) { + toolbelt::TCPSocket socket; + absl::Status s = socket.Connect(addr); + ASSERT_TRUE(s.ok()); + char buffer[256]; + // SendMessage uses the 4 bytes below the buffer for the length of the message. + ssize_t n = snprintf(buffer + 4, sizeof(buffer) - 4, "hello world"); + n += 1; // Include NUL at end. + absl::StatusOr nsent = socket.SendMessage(buffer + 4, n, c); + ASSERT_TRUE(nsent.ok()); + ASSERT_EQ(n + 4, nsent.value()); + }); + + scheduler.Run(); +} + +TEST(SocketsTest, BigTCPSocketNonblocking) { + toolbelt::InetAddress addr("127.0.0.1", 6502); + + co::CoroutineScheduler scheduler; + + toolbelt::TCPSocket listener; + ASSERT_TRUE(listener.SetReuseAddr().ok()); + absl::Status status = listener.Bind(addr, true); + ASSERT_TRUE(status.ok()); + + constexpr size_t kBufferSize = 10 * 1024 * 1024; + co::Coroutine incoming(scheduler, [&listener, kBufferSize](co::Coroutine* c) { + absl::StatusOr s = listener.Accept(c); + ASSERT_TRUE(s.ok()); + auto socket = s.value(); + ASSERT_OK(socket.SetNonBlocking()); + + absl::StatusOr> b = socket.ReceiveVariableLengthMessage(c); + ASSERT_TRUE(b.ok()); + auto buf = b.value(); + ASSERT_EQ(kBufferSize, buf.size()); + for (size_t i = 0; i < kBufferSize; i++) { + if (size_t(buf[i]) != 'a' + ((i + 4) % 26)) { + std::cerr << "Mismatch at " << i << ": " << buf[i] << " != " << 'a' + (i % 26) + << "\n"; + } + ASSERT_EQ('a' + ((i + 4) % 26), buf[i]); + } + }); + + co::Coroutine outgoing(scheduler, [&addr](co::Coroutine* c) { + toolbelt::TCPSocket socket; + absl::Status s = socket.Connect(addr); + ASSERT_TRUE(s.ok()); + ASSERT_OK(socket.SetNonBlocking()); + std::vector buffer(kBufferSize + 4); + for (size_t i = 4; i < buffer.size(); i++) { + buffer[i] = 'a' + (i % 26); + } + absl::StatusOr nsent = + socket.SendMessage(buffer.data() + 4, buffer.size() - 4, c); + ASSERT_TRUE(nsent.ok()); + ASSERT_EQ(buffer.size(), nsent.value()); + }); + + scheduler.Run(); +} + +TEST(SocketsTest, BigTCPSocketBlocking) { + toolbelt::InetAddress addr("127.0.0.1", 6502); + + co::CoroutineScheduler sendScheduler, ReceiveScheduler; + + toolbelt::TCPSocket listener; + ASSERT_TRUE(listener.SetReuseAddr().ok()); + absl::Status status = listener.Bind(addr, true); + ASSERT_TRUE(status.ok()); + + constexpr size_t kBufferSize = 10 * 1024 * 1024; + co::Coroutine incoming( + sendScheduler, [&listener, kBufferSize](co::Coroutine* c) { + absl::StatusOr s = listener.Accept(c); + ASSERT_TRUE(s.ok()); + auto socket = s.value(); + + absl::StatusOr> b = socket.ReceiveVariableLengthMessage(c); + ASSERT_TRUE(b.ok()); + auto buf = b.value(); + ASSERT_EQ(kBufferSize, buf.size()); + for (size_t i = 0; i < kBufferSize; i++) { + if (size_t(buf[i]) != 'a' + ((i + 4) % 26)) { + std::cerr << "Mismatch at " << i << ": " << buf[i] + << " != " << 'a' + (i % 26) << "\n"; + } + ASSERT_EQ('a' + ((i + 4) % 26), buf[i]); + } + }); + + co::Coroutine outgoing(ReceiveScheduler, [&addr](co::Coroutine* c) { + toolbelt::TCPSocket socket; + absl::Status s = socket.Connect(addr); + ASSERT_TRUE(s.ok()); + std::vector buffer(kBufferSize + 4); + for (size_t i = 4; i < buffer.size(); i++) { + buffer[i] = 'a' + (i % 26); + } + absl::StatusOr nsent = + socket.SendMessage(buffer.data() + 4, buffer.size() - 4, c); + ASSERT_TRUE(nsent.ok()); + ASSERT_EQ(buffer.size(), nsent.value()); + }); + + std::thread sender([&sendScheduler]() { sendScheduler.Run(); }); + std::thread Receiver([&ReceiveScheduler]() { ReceiveScheduler.Run(); }); + sender.join(); + Receiver.join(); +} + +TEST(SocketsTest, TCPSocketInterrupt) { + // TODO(dave.allison): is there a way to pick an unused port? + toolbelt::InetAddress addr("127.0.0.1", 6502); + + co::CoroutineScheduler scheduler; + + toolbelt::TCPSocket listener; + ASSERT_TRUE(listener.SetReuseAddr().ok()); + absl::Status status = listener.Bind(addr, true); + ASSERT_TRUE(status.ok()); + + co::Coroutine incoming( + scheduler, + [&listener](co::Coroutine* c) { + absl::StatusOr s = listener.Accept(c); + ASSERT_FALSE(s.ok()); + }, + co::CoroutineOptions{.name = "foo", .interrupt_fd = scheduler.GetInterruptFd()}); + + co::Coroutine interrupt(scheduler, [](co::Coroutine* c) { + c->Yield(); + c->Scheduler().TriggerInterrupt(); + }); + + scheduler.Run(); +} + +TEST(SocketsTest, TCPSocket2) { + // TODO(dave.allison): is there a way to pick an unused port? + toolbelt::InetAddress addr("127.0.0.1", 6502); + + co::CoroutineScheduler scheduler; + + toolbelt::TCPSocket listener; + ASSERT_TRUE(listener.SetReuseAddr().ok()); + absl::Status status = listener.Bind(addr, true); + ASSERT_TRUE(status.ok()); + + co::Coroutine incoming(scheduler, [&listener](co::Coroutine* c) { + absl::StatusOr s = listener.Accept(c); + ASSERT_TRUE(s.ok()); + auto socket = s.value(); + + char buffer[256]; + absl::StatusOr nbytes = socket.Receive(buffer, 12, c); + ASSERT_TRUE(nbytes.ok()); + auto n = nbytes.value(); + ASSERT_EQ(12, n); // "hello world\0" + ASSERT_EQ("hello world", std::string(buffer, n - 1)); + std::vector fds; + }); + + co::Coroutine outgoing(scheduler, [&addr](co::Coroutine* c) { + toolbelt::TCPSocket socket; + absl::Status s = socket.Connect(addr); + ASSERT_TRUE(s.ok()); + char buffer[256]; + ssize_t n = snprintf(buffer, sizeof(buffer), "hello world"); + n += 1; // Include NUL at end. + absl::StatusOr nsent = socket.Send(buffer, n, c); + ASSERT_TRUE(nsent.ok()); + ASSERT_EQ(n, nsent.value()); + }); + + scheduler.Run(); +} + +TEST(SocketsTest, TCPSocket3) { + toolbelt::InetAddress addr("127.0.0.1", 0); + + co::CoroutineScheduler scheduler; + + toolbelt::TCPSocket listener; + ASSERT_TRUE(listener.SetReuseAddr().ok()); + ASSERT_TRUE(listener.SetReusePort().ok()); + absl::Status status = listener.Bind(addr, true); + ASSERT_TRUE(status.ok()); + toolbelt::InetAddress baddr = listener.BoundAddress(); + + co::Coroutine incoming(scheduler, [&listener](co::Coroutine* c) { + absl::StatusOr s = listener.Accept(c); + ASSERT_TRUE(s.ok()); + auto socket = s.value(); + + char buffer[256]; + absl::StatusOr nbytes = socket.Receive(buffer, 12, c); + ASSERT_TRUE(nbytes.ok()); + auto n = nbytes.value(); + ASSERT_EQ(12, n); // "hello world\0" + ASSERT_EQ("hello world", std::string(buffer, n - 1)); + std::vector fds; + }); + + co::Coroutine outgoing(scheduler, [&baddr](co::Coroutine* c) { + toolbelt::TCPSocket socket; + absl::Status s = socket.Connect(baddr); + ASSERT_TRUE(s.ok()); + char buffer[256]; + ssize_t n = snprintf(buffer, sizeof(buffer), "hello world"); + n += 1; // Include NUL at end. + absl::StatusOr nsent = socket.Send(buffer, n, c); + ASSERT_TRUE(nsent.ok()); + ASSERT_EQ(n, nsent.value()); + }); + + scheduler.Run(); +} + +TEST(SocketsTest, TCPSocketErrors) { + toolbelt::TCPSocket socket; + char buffer[256]; + + // Socket is not Connected. These will fail. + toolbelt::InetAddress goodAddr("localhost", 2222); + ASSERT_FALSE(socket.Connect(goodAddr).ok()); // Valid fd, but nothing is on this port. + ASSERT_FALSE(socket.Send(buffer, 1).ok()); + ASSERT_FALSE(socket.Receive(buffer, 1).ok()); + ASSERT_FALSE(socket.SendMessage(buffer, 1).ok()); + ASSERT_FALSE(socket.ReceiveMessage(buffer, 1).ok()); + + toolbelt::InetAddress badAddr = + toolbelt::InetAddress("foobardoesntexist", 2222); + ASSERT_FALSE(badAddr.Valid()); + ASSERT_FALSE(socket.Connect(badAddr).ok()); + + socket.Close(); + ASSERT_FALSE(socket.Connect(goodAddr).ok()); // InValid fd. +} + +TEST(SocketsTest, UDPSocket) { + // TODO(dave.allison): is there a way to pick an unused port? + toolbelt::InetAddress sender("127.0.0.1", 6502); + toolbelt::InetAddress Receiver("127.0.0.1", 6503); + + co::CoroutineScheduler scheduler; + + co::Coroutine incoming(scheduler, [&Receiver](co::Coroutine* c) { + toolbelt::UDPSocket socket; + absl::Status s1 = socket.Bind(Receiver); + ASSERT_TRUE(s1.ok()); + + char buffer[256]; + absl::StatusOr nbytes = socket.Receive(buffer, sizeof(buffer), c); + ASSERT_TRUE(nbytes.ok()); + auto n = nbytes.value(); + ASSERT_EQ(12, n); // "hello world\0" + ASSERT_EQ("hello world", std::string(buffer, n - 1)); + }); + + co::Coroutine outgoing(scheduler, [&sender, &Receiver](co::Coroutine* c) { + toolbelt::UDPSocket socket; + absl::Status s1 = socket.Bind(sender); + ASSERT_TRUE(s1.ok()); + + char buffer[256]; + ssize_t n = snprintf(buffer, sizeof(buffer), "hello world"); + n += 1; // Include NUL at end. + + absl::Status s2 = socket.SendTo(Receiver, buffer, n, c); + ASSERT_TRUE(s2.ok()); + }); + + scheduler.Run(); +} + +TEST(SocketsTest, UDPSocket2) { + // TODO(dave.allison): is there a way to pick an unused port? + toolbelt::InetAddress sender("127.0.0.1", 6502); + toolbelt::InetAddress receiver("127.0.0.1", 6503); + + co::CoroutineScheduler scheduler; + + co::Coroutine incoming(scheduler, [&receiver, &sender](co::Coroutine* c) { + toolbelt::UDPSocket socket; + absl::Status s1 = socket.Bind(receiver); + ASSERT_TRUE(s1.ok()); + + char buffer[256]; + toolbelt::InetAddress from; + absl::StatusOr nbytes = socket.ReceiveFrom(from, buffer, sizeof(buffer), c); + ASSERT_TRUE(nbytes.ok()); + auto n = nbytes.value(); + ASSERT_EQ(12, n); // "hello world\0" + ASSERT_EQ("hello world", std::string(buffer, n - 1)); + ASSERT_EQ(sender, from); + }); + + co::Coroutine outgoing(scheduler, [&sender, &receiver](co::Coroutine* c) { + toolbelt::UDPSocket socket; + absl::Status s1 = socket.Bind(sender); + ASSERT_TRUE(s1.ok()); + + char buffer[256]; + ssize_t n = snprintf(buffer, sizeof(buffer), "hello world"); + n += 1; // Include NUL at end. + + absl::Status s2 = socket.SendTo(receiver, buffer, n, c); + ASSERT_TRUE(s2.ok()); + }); + + scheduler.Run(); +} + +TEST(SocketsTest, UDPSocketBroadcast) { + toolbelt::UDPSocket socket; + ASSERT_TRUE(socket.SetBroadcast().ok()); +} + +TEST(SocketsTest, InValidAsString) { + toolbelt::InetAddress addr; + ASSERT_FALSE(addr.Valid()); + EXPECT_EQ(addr.ToString(), "0.0.0.0:0"); +} + + TEST(SocketsTest, UDPSocket_SendAndReceiveUnicast) { UnusedPort port; auto sender = toolbelt::UDPSocket(); - auto receiver = toolbelt::UDPSocket(); + auto Receiver = toolbelt::UDPSocket(); - ASSERT_TRUE(receiver.SetReusePort().ok()); - ASSERT_TRUE(receiver.Bind(toolbelt::InetAddress("localhost", port)).ok()); + ASSERT_TRUE(Receiver.SetReusePort().ok()); + ASSERT_TRUE(Receiver.Bind(toolbelt::InetAddress("localhost", port)).ok()); toolbelt::InetAddress sendto_address("localhost", port); ASSERT_TRUE(sender.SendTo(sendto_address, TEST_DATA.data(), TEST_DATA.size()).ok()); - std::vector receive_buffer(TEST_DATA.size()); - ASSERT_EQ(*receiver.Receive(receive_buffer.data(), receive_buffer.size()), TEST_DATA.size()); - ASSERT_EQ(std::string_view(receive_buffer.data(), receive_buffer.size()), TEST_DATA); + std::vector Receive_buffer(TEST_DATA.size()); + ASSERT_EQ(*Receiver.Receive(Receive_buffer.data(), Receive_buffer.size()), TEST_DATA.size()); + ASSERT_EQ(std::string_view(Receive_buffer.data(), Receive_buffer.size()), TEST_DATA); - ASSERT_EQ(0, std::strcmp(receive_buffer.data(), TEST_DATA.data())); + ASSERT_EQ(0, std::strcmp(Receive_buffer.data(), TEST_DATA.data())); } TEST(SocketsTest, UDPSocket_SendAndReceiveBroadcast) { UnusedPort port; auto sender = toolbelt::UDPSocket(); - auto receiver = toolbelt::UDPSocket(); + auto Receiver = toolbelt::UDPSocket(); ASSERT_TRUE(sender.SetBroadcast().ok()); - ASSERT_TRUE(receiver.SetReusePort().ok()); - ASSERT_TRUE(receiver.Bind(toolbelt::InetAddress(toolbelt::InetAddress::AnyAddress(port))).ok()); + ASSERT_TRUE(Receiver.SetReusePort().ok()); + ASSERT_TRUE(Receiver.Bind(toolbelt::InetAddress(toolbelt::InetAddress::AnyAddress(port))).ok()); toolbelt::InetAddress sendto_address(toolbelt::InetAddress::BroadcastAddress(port)); ASSERT_TRUE(sender.SendTo(sendto_address, TEST_DATA.data(), TEST_DATA.size()).ok()); - std::vector receive_buffer(TEST_DATA.size()); - ASSERT_EQ(*receiver.Receive(receive_buffer.data(), receive_buffer.size()), TEST_DATA.size()); - ASSERT_EQ(std::string_view(receive_buffer.data(), receive_buffer.size()), TEST_DATA); + std::vector Receive_buffer(TEST_DATA.size()); + ASSERT_EQ(*Receiver.Receive(Receive_buffer.data(), Receive_buffer.size()), TEST_DATA.size()); + ASSERT_EQ(std::string_view(Receive_buffer.data(), Receive_buffer.size()), TEST_DATA); - ASSERT_EQ(0, std::strcmp(receive_buffer.data(), TEST_DATA.data())); + ASSERT_EQ(0, std::strcmp(Receive_buffer.data(), TEST_DATA.data())); } TEST(SocketsTest, UDPSocket_SendAndReceiveMulticast) { @@ -75,33 +542,33 @@ TEST(SocketsTest, UDPSocket_SendAndReceiveMulticast) { toolbelt::InetAddress multicast_address(multicast_ip, port); auto sender = toolbelt::UDPSocket(); - auto receiver = toolbelt::UDPSocket(); + auto Receiver = toolbelt::UDPSocket(); ASSERT_TRUE(sender.SetMulticastLoop().ok()); - std::vector receive_buffer(TEST_DATA.size()); - ASSERT_TRUE(receiver.SetReusePort().ok()); - ASSERT_TRUE(receiver.SetNonBlocking().ok()); - ASSERT_TRUE(receiver.Bind(toolbelt::InetAddress::AnyAddress(port)).ok()); + std::vector Receive_buffer(TEST_DATA.size()); + ASSERT_TRUE(Receiver.SetReusePort().ok()); + ASSERT_TRUE(Receiver.SetNonBlocking().ok()); + ASSERT_TRUE(Receiver.Bind(toolbelt::InetAddress::AnyAddress(port)).ok()); - ASSERT_TRUE(receiver.JoinMulticastGroup(multicast_address).ok()); + ASSERT_TRUE(Receiver.JoinMulticastGroup(multicast_address).ok()); ASSERT_TRUE(sender.SendTo(multicast_address, TEST_DATA.data(), TEST_DATA.size()).ok()); absl::Time timeout = absl::Now() + LOOPBACK_TIMEOUT; while (absl::Now() < timeout) { - auto status_or_len = receiver.Receive(receive_buffer.data(), receive_buffer.size()); + auto status_or_len = Receiver.Receive(Receive_buffer.data(), Receive_buffer.size()); if (status_or_len.ok()) { ASSERT_EQ(*status_or_len, TEST_DATA.size()); - ASSERT_EQ(std::string_view(receive_buffer.data(), receive_buffer.size()), TEST_DATA); + ASSERT_EQ(std::string_view(Receive_buffer.data(), Receive_buffer.size()), TEST_DATA); break; } } - ASSERT_TRUE(receiver.LeaveMulticastGroup(multicast_address).ok()); + ASSERT_TRUE(Receiver.LeaveMulticastGroup(multicast_address).ok()); ASSERT_TRUE(sender.SendTo(multicast_address, TEST_DATA.data(), TEST_DATA.size()).ok()); timeout = absl::Now() + LOOPBACK_TIMEOUT; while (absl::Now() < timeout) { - auto status_or_len = receiver.Receive(receive_buffer.data(), receive_buffer.size()); + auto status_or_len = Receiver.Receive(Receive_buffer.data(), Receive_buffer.size()); if (status_or_len.ok()) { FAIL() << "Received " << *status_or_len << " bytes but expected nothing"; } diff --git a/toolbelt/stacktrace.cc b/toolbelt/stacktrace.cc new file mode 100644 index 0000000..4557e21 --- /dev/null +++ b/toolbelt/stacktrace.cc @@ -0,0 +1,42 @@ + +#include "absl/debugging/stacktrace.h" +#include "absl/base/optimization.h" +#include "absl/debugging/symbolize.h" +#include +#include + +namespace toolbelt { + +void PrintCurrentStack(std::ostream &os) { + os << "--- Stack Trace Capture (Deepest Function) ---\n"; + + constexpr int kMaxFrames = 50; + + // 1. Capture the raw stack addresses + void *stack[kMaxFrames]; + int depth = absl::GetStackTrace(stack, kMaxFrames, 0); + + // 2. Resolve addresses to human-readable symbol names + os << "Captured " << depth << " stack frames:\n"; + + // Buffer to hold the symbolized name + char symbolized_name[1024]; + + for (int i = 0; i < depth; ++i) { + // Attempt to symbolize the address + if (absl::Symbolize(stack[i], symbolized_name, sizeof(symbolized_name))) { + // Success: Print the frame index, address, and resolved symbol name + os << "#" << std::setw(2) << std::left << i << " [0x" << std::hex + << std::setw(16) << stack[i] << std::dec << "] " << symbolized_name + << "\n"; + } else { + // Failure: Symbolization failed (e.g., address not in a symbol table) + os << "#" << std::setw(2) << std::left << i << " [0x" << std::hex + << std::setw(16) << stack[i] << std::dec << "] " + << "\n"; + } + } + os << "----------------------------------------------\n"; +} + +} // namespace toolbelt diff --git a/toolbelt/stacktrace.h b/toolbelt/stacktrace.h new file mode 100644 index 0000000..be6c332 --- /dev/null +++ b/toolbelt/stacktrace.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +namespace toolbelt { +void PrintCurrentStack(std::ostream &os); +} // namespace toolbelt diff --git a/toolbelt/stacktrace_test.cc b/toolbelt/stacktrace_test.cc new file mode 100644 index 0000000..9ed8394 --- /dev/null +++ b/toolbelt/stacktrace_test.cc @@ -0,0 +1,27 @@ +// Copyright 2025 David Allison +// All Rights Reserved +// See LICENSE file for licensing information. + +#include "absl/strings/str_format.h" +#include "toolbelt/stacktrace.h" +#include + +TEST(StacktraceTest, PrintCurrentStack) { + toolbelt::PrintCurrentStack(std::cout); +} + +void foo() { + toolbelt::PrintCurrentStack(std::cout); +} + +void bar() { + foo(); +} + +void baz() { + bar(); +} + +TEST(StacktraceTest, PrintCurrentStackWithFunction) { + baz(); +} \ No newline at end of file diff --git a/toolbelt/table.cc b/toolbelt/table.cc index 7517b4f..3840688 100644 --- a/toolbelt/table.cc +++ b/toolbelt/table.cc @@ -73,7 +73,7 @@ void Table::Print(int width, std::ostream &os) { // Print titles. for (auto &col : cols_) { std::string title = col.title; - if (title.size() > col.width) { + if (title.size() > static_cast(col.width)) { title = title.substr(0, col.width - 1); } os << std::left << std::setw(col.width) << std::setfill(' ') << title; @@ -83,10 +83,10 @@ void Table::Print(int width, std::ostream &os) { os << std::setw(width) << std::setfill('-') << "" << std::endl; // Print each row. - for (size_t i = 0; i < num_rows_; i++) { + for (int i = 0; i < num_rows_; i++) { for (auto &col : cols_) { std::string data = col.cells[i].data; - if (data.size() > col.width) { + if (data.size() > static_cast(col.width)) { // Truncate if too wide. data = data.substr(0, col.width - 1); } @@ -107,7 +107,7 @@ void Table::Clear() { void Table::Render(int width) { std::vector max_widths(cols_.size()); - for (size_t i = 0; i < num_rows_; i++) { + for (int i = 0; i < num_rows_; i++) { int col_index = 0; for (auto &col : cols_) { if (col.cells[i].data.size() > max_widths[col_index]) { @@ -132,7 +132,7 @@ void Table::Render(int width) { } void Table::Sort() { - if (sort_column_ == -1 || sort_column_ >= cols_.size()) { + if (sort_column_ == -1ULL || sort_column_ >= cols_.size()) { return; } struct Index { @@ -140,8 +140,8 @@ void Table::Sort() { std::string data; }; std::vector index(num_rows_); - for (size_t i = 0; i < num_rows_; i++) { - index[i] = {.row = i, .data = cols_[sort_column_].cells[i].data}; + for (int i = 0; i < num_rows_; i++) { + index[i] = {.row = static_cast(i), .data = cols_[sort_column_].cells[i].data}; } std::sort(index.begin(), index.end(), [this](const Index &a, const Index &b) { return sorter_(a.data, b.data);