Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package killport for openSUSE:Factory checked in at 2026-08-09 21:45:02 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/killport (Old) and /work/SRC/openSUSE:Factory/.killport.new.16738 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "killport" Sun Aug 9 21:45:02 2026 rev:5 rq:1370403 version:2.0.2 Changes: -------- --- /work/SRC/openSUSE:Factory/killport/killport.changes 2026-07-12 18:36:27.445417106 +0200 +++ /work/SRC/openSUSE:Factory/.killport.new.16738/killport.changes 2026-08-09 21:47:02.998234118 +0200 @@ -1,0 +2,16 @@ +Sun Aug 9 14:47:49 UTC 2026 - Andrea Manzini <[email protected]> + +- Update to 2.0.2: + * bump deps + * Minimum supported Rust version corrected from 1.74 to 1.86 + * Killing multiple ports no longer stops at the first port that errors + * port lookup scans /proc once for all matching socket inodes instead of once + per inode, and no longer returns duplicate entries when several fds match the port + * processes with unreadable command lines fall back to the short process name + instead of being skipped + * Container runtime detection and kills honor DOCKER_HOST (colima, podman, + remote docker) + * Container kills reuse the shared tokio runtime instead of building a new + one per container + +------------------------------------------------------------------- Old: ---- killport-2.0.1.tar.gz New: ---- killport-2.0.2.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ killport.spec ++++++ --- /var/tmp/diff_new_pack.V64jDM/_old 2026-08-09 21:47:04.134272857 +0200 +++ /var/tmp/diff_new_pack.V64jDM/_new 2026-08-09 21:47:04.138272993 +0200 @@ -17,7 +17,7 @@ Name: killport -Version: 2.0.1 +Version: 2.0.2 Release: 0 Summary: A tool to easily kill processes running on a specified port License: MIT ++++++ _service ++++++ --- /var/tmp/diff_new_pack.V64jDM/_old 2026-08-09 21:47:04.174274221 +0200 +++ /var/tmp/diff_new_pack.V64jDM/_new 2026-08-09 21:47:04.178274357 +0200 @@ -3,7 +3,7 @@ <param name="url">https://github.com/jkfran/killport</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v2.0.1</param> + <param name="revision">v2.0.2</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> </service> ++++++ killport-2.0.1.tar.gz -> killport-2.0.2.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/killport-2.0.1/CHANGELOG.md new/killport-2.0.2/CHANGELOG.md --- old/killport-2.0.1/CHANGELOG.md 2026-07-06 19:46:23.000000000 +0200 +++ new/killport-2.0.2/CHANGELOG.md 2026-08-01 10:04:31.000000000 +0200 @@ -7,6 +7,68 @@ ## [Unreleased] +## [2.0.2] - 2026-08-01 + +### Dependencies + +- build(deps): bump the cargo-minor-patch group with 3 updates (#52) +- build(deps): bump the cargo-minor-patch group with 3 updates (#51) +- build(deps): bump regex in the cargo-minor-patch group (#50) +- Updating bitflags v2.13.0 -> v2.13.1 +- Updating bstr v1.12.3 -> v1.13.0 +- Updating bytes v1.12.0 -> v1.12.1 +- Updating cc v1.2.66 -> v1.4.0 +- Updating cfg_aliases v0.2.1 -> v0.2.2 +- Updating clang-sys v1.8.1 -> v1.9.1 +- Updating clap v4.6.4 -> v4.6.5 +- Updating clap_builder v4.6.2 -> v4.6.5 +- Updating clap_complete v4.6.7 -> v4.6.8 +- Updating displaydoc v0.2.6 -> v0.2.7 +- Updating either v1.16.0 -> v1.17.0 +- Updating fastrand v2.4.1 -> v2.5.0 +- Updating futures-channel v0.3.32 -> v0.3.33 +- Updating futures-core v0.3.32 -> v0.3.33 +- Updating futures-macro v0.3.32 -> v0.3.33 +- Updating futures-sink v0.3.32 -> v0.3.33 +- Updating futures-task v0.3.32 -> v0.3.33 +- Updating futures-util v0.3.32 -> v0.3.33 +- Updating glob v0.3.3 -> v0.3.4 +- Updating http v1.4.2 -> v1.5.0 +- Updating http-body v1.0.1 -> v1.1.0 +- Updating http-body-util v0.1.3 -> v0.1.4 +- Updating hyper v1.10.1 -> v1.11.0 +- Updating hyper-named-pipe v0.1.0 -> v0.1.1 +- Updating jiff v0.2.31 -> v0.2.35 +- Adding jiff-core v0.1.0 +- Updating jiff-static v0.2.31 -> v0.2.35 +- Updating memchr v2.8.2 -> v2.8.3 +- Updating mio v1.2.1 -> v1.2.2 +- Updating portable-atomic v1.13.1 -> v1.14.0 +- Updating proc-macro2 v1.0.106 -> v1.0.107 +- Updating quote v1.0.46 -> v1.0.47 +- Updating rustversion v1.0.22 -> v1.0.23 +- Updating serde v1.0.228 -> v1.0.229 +- Updating serde_core v1.0.228 -> v1.0.229 +- Updating serde_derive v1.0.228 -> v1.0.229 +- Updating serde_json v1.0.150 -> v1.0.151 +- Updating serde_repr v0.1.20 -> v0.1.21 +- Updating simd-adler32 v0.3.9 -> v0.3.10 +- Updating socket2 v0.6.4 -> v0.6.5 +- ...and 5 more transitive updates + +### Changed +- Minimum supported Rust version corrected from 1.74 to 1.86 — the actual floor of the current dependency tree — and now enforced by a dedicated CI job + +### Fixed +- Killing multiple ports no longer stops at the first port that errors; all ports are processed and the exit code reflects the worst outcome (error > not found > success) +- Windows: no longer attempts to kill PID 0 (`[System Process]`, owner of TIME_WAIT entries) or PID 4 (System), which failed with error 0x57 +- macOS: processes holding more than 1024 file descriptors could have their listening sockets missed; the fd list is now sized to the process's actual fd count +- macOS: a process exiting mid-scan no longer aborts the whole port lookup +- Linux: port lookup scans `/proc` once for all matching socket inodes instead of once per inode, and no longer returns duplicate entries when several fds match the port +- Linux: processes with unreadable command lines fall back to the short process name instead of being skipped +- Container runtime detection and kills honor `DOCKER_HOST` (colima, podman, remote docker) +- Container kills reuse the shared tokio runtime instead of building a new one per container + ## [2.0.1] - 2026-07-06 ### Dependencies diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/killport-2.0.1/Cargo.lock new/killport-2.0.2/Cargo.lock --- old/killport-2.0.1/Cargo.lock 2026-07-06 19:46:23.000000000 +0200 +++ new/killport-2.0.2/Cargo.lock 2026-08-01 10:04:31.000000000 +0200 @@ -115,7 +115,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "cexpr", "clang-sys", "itertools", @@ -124,7 +124,7 @@ "regex", "rustc-hash", "shlex 1.3.0", - "syn", + "syn 2.0.119", ] [[package]] @@ -135,9 +135,9 @@ [[package]] name = "bitflags" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "bollard" @@ -184,9 +184,9 @@ [[package]] name = "bstr" -version = "1.12.3" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cee35f73844aa3014bb606320a6c1f010249dbdf43342fe54b5a4f6a8ed4b79" +checksum = "1f7dc094d718f2e1c1559ad110e27eeaae14a5465d3d56dd6dbd793079fbd530" dependencies = [ "memchr", "regex-automata", @@ -201,15 +201,15 @@ [[package]] name = "bytes" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cc" -version = "1.2.66" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", "shlex 2.0.1", @@ -232,9 +232,9 @@ [[package]] name = "cfg_aliases" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" [[package]] name = "chrono" @@ -249,9 +249,9 @@ [[package]] name = "clang-sys" -version = "1.8.1" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" dependencies = [ "glob", "libc", @@ -260,9 +260,9 @@ [[package]] name = "clap" -version = "4.6.1" +version = "4.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +checksum = "301b56658598e48f3648647ac6fc887be7e7108eddfa4e9b63fcf3ec58c0cadf" dependencies = [ "clap_builder", "clap_derive", @@ -280,9 +280,9 @@ [[package]] name = "clap_builder" -version = "4.6.0" +version = "4.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +checksum = "94a65403d1a1bd28f7dc68eb8506e8874808ee5eecb59298de588e2e1407a078" dependencies = [ "anstream", "anstyle", @@ -292,23 +292,23 @@ [[package]] name = "clap_complete" -version = "4.6.7" +version = "4.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8b397918185f0161ff3d6fcaa9e4bfc09b8367caf6e1d4a2848e5477ed027b" +checksum = "b1f84a88507dbd05c695f2cb5e8558e747179134005e9893882dec964190ed89" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.6.1" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -367,7 +367,7 @@ "defmt-parser", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -387,20 +387,20 @@ [[package]] name = "displaydoc" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "either" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "env_filter" @@ -437,9 +437,9 @@ [[package]] name = "fastrand" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "find-msvc-tools" @@ -468,47 +468,47 @@ [[package]] name = "futures-channel" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" [[package]] name = "futures-macro" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "futures-sink" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" dependencies = [ "futures-core", "futures-macro", @@ -530,9 +530,9 @@ [[package]] name = "glob" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] name = "heck" @@ -548,9 +548,9 @@ [[package]] name = "http" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", "itoa", @@ -558,9 +558,9 @@ [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", "http", @@ -568,9 +568,9 @@ [[package]] name = "http-body-util" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" dependencies = [ "bytes", "futures-core", @@ -593,9 +593,9 @@ [[package]] name = "hyper" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" dependencies = [ "atomic-waker", "bytes", @@ -614,9 +614,9 @@ [[package]] name = "hyper-named-pipe" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" +checksum = "fab3637d6b04a8037af8a266fdf6cf92ea957e8c53981a2bf6136572531025bf" dependencies = [ "hex", "hyper", @@ -624,7 +624,6 @@ "pin-project-lite", "tokio", "tower-service", - "winapi", ] [[package]] @@ -812,11 +811,12 @@ [[package]] name = "jiff" -version = "0.2.31" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccfe6121cbe750cf81efa362d85c0bde7ea298ec43092d3a193baca59cdbd634" +checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" dependencies = [ "defmt", + "jiff-core", "jiff-static", "log", "portable-atomic", @@ -825,14 +825,24 @@ ] [[package]] +name = "jiff-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +dependencies = [ + "defmt", +] + +[[package]] name = "jiff-static" -version = "0.2.31" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e165e897f662d428f3cd3828a919dbe067c2d42bb1031eede74ef9d27ecdedd2" +checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" dependencies = [ + "jiff-core", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -848,7 +858,7 @@ [[package]] name = "killport" -version = "2.0.1" +version = "2.0.2" dependencies = [ "assert_cmd", "bollard", @@ -870,9 +880,9 @@ [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libloading" @@ -915,9 +925,9 @@ [[package]] name = "memchr" -version = "2.8.2" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "minimal-lexical" @@ -937,9 +947,9 @@ [[package]] name = "mio" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "wasi", @@ -952,7 +962,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "cfg-if", "cfg_aliases", "libc", @@ -1003,9 +1013,9 @@ [[package]] name = "portable-atomic" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" [[package]] name = "portable-atomic-util" @@ -1054,9 +1064,9 @@ [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] @@ -1067,7 +1077,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25485360a54d6861439d60facef26de713b1e126bf015ec8f98239467a2b82f7" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "chrono", "flate2", "procfs-core", @@ -1080,16 +1090,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6401bf7b6af22f78b563665d15a22e9aef27775b79b149a66ca022468a4e405" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "chrono", "hex", ] [[package]] name = "quote" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -1102,9 +1112,9 @@ [[package]] name = "regex" -version = "1.12.4" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -1114,9 +1124,9 @@ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" dependencies = [ "aho-corasick", "memchr", @@ -1147,7 +1157,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "errno", "libc", "linux-raw-sys", @@ -1156,9 +1166,9 @@ [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "ryu" @@ -1168,9 +1178,9 @@ [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -1178,29 +1188,29 @@ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", @@ -1211,13 +1221,13 @@ [[package]] name = "serde_repr" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -1246,9 +1256,9 @@ [[package]] name = "simd-adler32" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" [[package]] name = "slab" @@ -1264,9 +1274,9 @@ [[package]] name = "socket2" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys", @@ -1286,9 +1296,20 @@ [[package]] name = "syn" -version = "2.0.118" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -1303,7 +1324,7 @@ dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1327,22 +1348,22 @@ [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -1357,9 +1378,9 @@ [[package]] name = "tokio" -version = "1.52.3" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", @@ -1371,13 +1392,14 @@ [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" dependencies = [ "bytes", "futures-core", "futures-sink", + "libc", "pin-project-lite", "tokio", ] @@ -1499,7 +1521,7 @@ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.119", "wasm-bindgen-shared", ] @@ -1555,7 +1577,7 @@ dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1566,7 +1588,7 @@ dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1627,7 +1649,7 @@ dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] @@ -1648,7 +1670,7 @@ dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] @@ -1682,11 +1704,11 @@ dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/killport-2.0.1/Cargo.toml new/killport-2.0.2/Cargo.toml --- old/killport-2.0.1/Cargo.toml 2026-07-06 19:46:23.000000000 +0200 +++ new/killport-2.0.2/Cargo.toml 2026-08-01 10:04:31.000000000 +0200 @@ -1,9 +1,10 @@ [package] name = "killport" -version = "2.0.1" +version = "2.0.2" authors = ["Francisco Jimenez Cabrera <[email protected]>"] edition = "2021" -rust-version = "1.74" +# Actual floor of the dependency tree (icu crates); enforced by the MSRV CI job. +rust-version = "1.86" license = "MIT" description = "A command-line tool to easily kill processes and containers running on a specified port." readme = "README.md" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/killport-2.0.1/src/container.rs new/killport-2.0.2/src/container.rs --- old/killport-2.0.1/src/container.rs 2026-07-06 19:46:23.000000000 +0200 +++ new/killport-2.0.2/src/container.rs 2026-08-01 10:04:31.000000000 +0200 @@ -20,8 +20,10 @@ /// * `signal` - A enum value representing the signal type. pub fn kill(rt: &Runtime, name: &str, signal: KillportSignal) -> Result<(), Error> { rt.block_on(async { + // connect_with_local_defaults honors DOCKER_HOST, so custom + // sockets (colima, podman, rancher desktop) are found too. let docker = - Docker::connect_with_socket_defaults().map_err(|e| Error::other(e.to_string()))?; + Docker::connect_with_local_defaults().map_err(|e| Error::other(e.to_string()))?; let options = KillContainerOptions { signal: signal.to_string(), @@ -37,7 +39,7 @@ /// Finds the containers associated with the specified `port`. pub fn find_target_containers(rt: &Runtime, port: u16) -> Result<Vec<Self>, Error> { rt.block_on(async { - let docker = match Docker::connect_with_socket_defaults() { + let docker = match Docker::connect_with_local_defaults() { Ok(d) => d, Err(e) => { debug!("Container runtime socket not available: {}", e); @@ -75,7 +77,7 @@ pub fn is_available(rt: &Runtime) -> Result<bool, Error> { rt.block_on(async { - let docker = match Docker::connect_with_socket_defaults() { + let docker = match Docker::connect_with_local_defaults() { Ok(d) => d, Err(e) => { debug!("Container runtime socket not available: {}", e); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/killport-2.0.1/src/killable.rs new/killport-2.0.2/src/killable.rs --- old/killport-2.0.1/src/killable.rs 2026-07-06 19:46:23.000000000 +0200 +++ new/killport-2.0.2/src/killable.rs 2026-08-01 10:04:31.000000000 +0200 @@ -29,6 +29,10 @@ } impl Killable for Container { + /// Kills the container on a freshly-built runtime. The orchestrator + /// (`Killport::kill_service_by_port`) routes container kills through + /// `ContainerOps` instead, reusing the shared runtime; this impl remains + /// for standalone `Killable` use. fn kill(&self, signal: KillportSignal) -> Result<bool, Error> { let rt = Builder::new_current_thread().enable_all().build()?; Self::kill(&rt, &self.name, signal)?; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/killport-2.0.1/src/killport.rs new/killport-2.0.2/src/killport.rs --- old/killport-2.0.1/src/killport.rs 2026-07-06 19:46:23.000000000 +0200 +++ new/killport-2.0.2/src/killport.rs 2026-08-01 10:04:31.000000000 +0200 @@ -19,6 +19,7 @@ pub trait ContainerOps { fn is_available(&self) -> Result<bool, Error>; fn find_target_containers(&self, port: u16) -> Result<Vec<Container>, Error>; + fn kill_container(&self, name: &str, signal: KillportSignal) -> Result<bool, Error>; } /// Real implementation of ProcessFinder that calls the platform-specific functions. @@ -55,6 +56,11 @@ fn find_target_containers(&self, port: u16) -> Result<Vec<Container>, Error> { Container::find_target_containers(&self.rt, port) } + + fn kill_container(&self, name: &str, signal: KillportSignal) -> Result<bool, Error> { + Container::kill(&self.rt, name, signal)?; + Ok(true) + } } /// Killport implementation with injectable dependencies for testability. @@ -124,7 +130,16 @@ let target_killables = self.find_target_killables(port, mode)?; for killable in target_killables { - let killed = dry_run || killable.kill(signal.clone())?; + let killed = if dry_run { + true + } else if killable.get_type() == KillableType::Container { + // Containers are killed through ContainerOps so the shared + // tokio runtime is reused instead of building one per kill. + self.container_ops + .kill_container(&killable.get_name(), signal.clone())? + } else { + killable.kill(signal.clone())? + }; if killed { results.push((killable.get_type(), killable.get_name())); } @@ -197,13 +212,18 @@ struct FnContainerOps< P: Fn() -> Result<bool, Error>, C: Fn(u16) -> Result<Vec<Container>, Error>, + K: Fn(&str, KillportSignal) -> Result<bool, Error>, > { is_present: P, find_containers: C, + kill_container: K, } - impl<P: Fn() -> Result<bool, Error>, C: Fn(u16) -> Result<Vec<Container>, Error>> ContainerOps - for FnContainerOps<P, C> + impl< + P: Fn() -> Result<bool, Error>, + C: Fn(u16) -> Result<Vec<Container>, Error>, + K: Fn(&str, KillportSignal) -> Result<bool, Error>, + > ContainerOps for FnContainerOps<P, C, K> { fn is_available(&self) -> Result<bool, Error> { (self.is_present)() @@ -212,16 +232,22 @@ fn find_target_containers(&self, port: u16) -> Result<Vec<Container>, Error> { (self.find_containers)(port) } + + fn kill_container(&self, name: &str, signal: KillportSignal) -> Result<bool, Error> { + (self.kill_container)(name, signal) + } } #[allow(clippy::type_complexity)] fn no_containers() -> FnContainerOps< impl Fn() -> Result<bool, Error>, impl Fn(u16) -> Result<Vec<Container>, Error>, + impl Fn(&str, KillportSignal) -> Result<bool, Error>, > { FnContainerOps { is_present: || Ok(false), find_containers: |_| Ok(vec![]), + kill_container: |_, _| Ok(true), } } @@ -231,6 +257,7 @@ ) -> FnContainerOps< impl Fn() -> Result<bool, Error>, impl Fn(u16) -> Result<Vec<Container>, Error>, + impl Fn(&str, KillportSignal) -> Result<bool, Error>, > { FnContainerOps { is_present: || Ok(true), @@ -240,6 +267,7 @@ .map(|n| Container { name: n.clone() }) .collect()) }, + kill_container: |_, _| Ok(true), } } @@ -340,6 +368,9 @@ let ct = FnContainerOps { is_present: || panic!("Container runtime should not be checked in Process mode"), find_containers: |_| panic!("Container runtime should not be checked in Process mode"), + kill_container: |_, _| { + panic!("Container runtime should not be checked in Process mode") + }, }; let kp = Killport::new(finder, ct); let results = kp.find_target_killables(8080, Mode::Process).unwrap(); @@ -378,6 +409,9 @@ let ct = FnContainerOps { is_present: || panic!("Container runtime should not be checked in Process mode"), find_containers: |_| panic!("Container runtime should not be checked in Process mode"), + kill_container: |_, _| { + panic!("Container runtime should not be checked in Process mode") + }, }; let kp = Killport::new(finder, ct); let results = kp.find_target_killables(8080, Mode::Process).unwrap(); @@ -433,6 +467,7 @@ let ct = FnContainerOps { is_present: || Err(Error::other("container runtime error")), find_containers: |_| Ok(vec![]), + kill_container: |_, _| Ok(true), }; let kp = Killport::new(finder, ct); let result = kp.find_target_killables(8080, Mode::Auto); @@ -447,6 +482,7 @@ let ct = FnContainerOps { is_present: || Ok(true), find_containers: |_| Err(Error::other("container error")), + kill_container: |_, _| Ok(true), }; let kp = Killport::new(finder, ct); let result = kp.find_target_killables(8080, Mode::Auto); @@ -576,4 +612,51 @@ assert_eq!(results[0].0, KillableType::Container); assert_eq!(results[0].1, "nginx"); } + + #[test] + fn test_kill_service_container_killed_via_ops() { + // Container kills must route through ContainerOps (shared runtime), + // not through the standalone Killable impl. + let finder = FnProcessFinder { + finder: |_| Ok(vec![]), + }; + let ct = FnContainerOps { + is_present: || Ok(true), + find_containers: |_| { + Ok(vec![Container { + name: "nginx".to_string(), + }]) + }, + kill_container: |name, _| { + assert_eq!(name, "nginx"); + Ok(true) + }, + }; + let kp = Killport::new(finder, ct); + let results = kp + .kill_service_by_port(8080, signal(), Mode::Container, false) + .unwrap(); + assert_eq!(results.len(), 1); + assert_eq!(results[0].0, KillableType::Container); + assert_eq!(results[0].1, "nginx"); + } + + #[test] + fn test_kill_service_container_kill_error_propagates() { + let finder = FnProcessFinder { + finder: |_| Ok(vec![]), + }; + let ct = FnContainerOps { + is_present: || Ok(true), + find_containers: |_| { + Ok(vec![Container { + name: "nginx".to_string(), + }]) + }, + kill_container: |_, _| Err(Error::other("docker kill failed")), + }; + let kp = Killport::new(finder, ct); + let result = kp.kill_service_by_port(8080, signal(), Mode::Container, false); + assert!(result.is_err()); + } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/killport-2.0.1/src/linux.rs new/killport-2.0.2/src/linux.rs --- old/killport-2.0.1/src/linux.rs 2026-07-06 19:46:23.000000000 +0200 +++ new/killport-2.0.2/src/linux.rs 2026-08-01 10:04:31.000000000 +0200 @@ -8,17 +8,17 @@ /// Finds the inodes associated with the specified `port`. /// -/// Returns a `Vec` of inodes for both IPv4 and IPv6 connections. +/// Returns the set of socket inodes for both IPv4 and IPv6, TCP and UDP. /// /// # Arguments /// /// * `port` - A u16 value representing the port number. -fn find_target_inodes(port: u16) -> Vec<u64> { +fn find_target_inodes(port: u16) -> HashSet<u64> { let tcp = procfs::net::tcp(); let tcp6 = procfs::net::tcp6(); let udp = procfs::net::udp(); let udp6 = procfs::net::udp6(); - let mut target_inodes = Vec::new(); + let mut target_inodes = HashSet::new(); trait NetEntry { fn local_address(&self) -> std::net::SocketAddr; @@ -47,7 +47,7 @@ } fn add_matching_inodes<T: NetEntry>( - target_inodes: &mut Vec<u64>, + target_inodes: &mut HashSet<u64>, net_entries: procfs::ProcResult<Vec<T>>, port: u16, ) { @@ -69,49 +69,62 @@ target_inodes } +/// Returns a display name for the process: the full command line, falling +/// back to the short `comm` name when the command line is empty or +/// unreadable (kernel threads, exec races). `None` means the process is +/// effectively gone and should be skipped. +fn process_name(process: &procfs::process::Process) -> Option<String> { + match process.cmdline() { + Ok(parts) if !parts.is_empty() => Some(parts.join(" ")), + _ => process.stat().ok().map(|stat| stat.comm), + } +} + /// Finds the processes associated with the specified `port`. /// /// Returns a `Vec` of native processes. /// /// # Arguments /// -/// * `inodes` - Target inodes +/// * `port` - Target port number pub fn find_target_processes(port: u16) -> Result<Vec<UnixProcess>, Error> { let mut target_pids: Vec<UnixProcess> = vec![]; - let mut seen_pids: HashSet<i32> = HashSet::new(); - let inodes = find_target_inodes(port); + let target_inodes = find_target_inodes(port); - for inode in inodes { - let processes = procfs::process::all_processes().map_err(std::io::Error::other)?; - for p in processes { - // Processes can vanish between enumeration and inspection (race condition). - // Skip any process that disappears mid-scan. - let process = match p { - Ok(p) => p, - Err(_) => continue, - }; - - if seen_pids.contains(&process.pid) { - continue; - } + if target_inodes.is_empty() { + return Ok(target_pids); + } - if let Ok(fds) = process.fd() { - for fd in fds { - let fd = match fd { - Ok(fd) => fd, - Err(_) => continue, - }; - - if let FDTarget::Socket(sock_inode) = fd.target { - if inode == sock_inode { - let name = match process.cmdline() { - Ok(parts) => parts.join(" "), - Err(_) => continue, - }; - debug!("Found process '{}' with PID {}", name, process.pid()); - seen_pids.insert(process.pid); - target_pids.push(UnixProcess::new(Pid::from_raw(process.pid), name)); - } + // Single pass over /proc: match each process's socket fds against the + // full inode set at once (a port bound on tcp/tcp6/udp/udp6 yields up + // to four inodes; scanning /proc once per inode is wasteful). + let processes = procfs::process::all_processes().map_err(Error::other)?; + 'next_process: for p in processes { + // Processes can vanish between enumeration and inspection (race condition). + // Skip any process that disappears mid-scan. + let process = match p { + Ok(p) => p, + Err(_) => continue, + }; + + if let Ok(fds) = process.fd() { + for fd in fds { + let fd = match fd { + Ok(fd) => fd, + Err(_) => continue, + }; + + if let FDTarget::Socket(sock_inode) = fd.target { + if target_inodes.contains(&sock_inode) { + let name = match process_name(&process) { + Some(name) => name, + None => continue 'next_process, + }; + debug!("Found process '{}' with PID {}", name, process.pid()); + target_pids.push(UnixProcess::new(Pid::from_raw(process.pid), name)); + // One entry per process, even when several fds match + // (dup'd sockets, multiple protocols on one port). + continue 'next_process; } } } @@ -120,3 +133,71 @@ Ok(target_pids) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::killable::Killable; + use std::net::{TcpListener, UdpSocket}; + + #[test] + fn test_find_target_processes_no_listeners() { + // Use a port that is very unlikely to be in use + let result = find_target_processes(19876); + assert!(result.is_ok()); + assert!(result.unwrap().is_empty()); + } + + #[test] + fn test_find_target_processes_tcp_listener() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let port = listener.local_addr().unwrap().port(); + + let processes = find_target_processes(port).unwrap(); + assert!( + !processes.is_empty(), + "Expected to find at least one process on port {}", + port + ); + for process in &processes { + assert!(!process.get_name().is_empty()); + } + + drop(listener); + } + + #[test] + fn test_find_target_processes_udp_listener() { + let socket = UdpSocket::bind("127.0.0.1:0").unwrap(); + let port = socket.local_addr().unwrap().port(); + + let processes = find_target_processes(port).unwrap(); + assert!( + !processes.is_empty(), + "Expected to find process with UDP socket on port {}", + port + ); + + drop(socket); + } + + #[test] + fn test_find_target_processes_dedup() { + // Bind IPv4 and IPv6 on the same port — two distinct socket inodes, + // one owning process. The single-pass scan must return one entry. + let listener4 = TcpListener::bind("127.0.0.1:0").unwrap(); + let port = listener4.local_addr().unwrap().port(); + let listener6 = TcpListener::bind(format!("[::1]:{}", port)).unwrap(); + + let processes = find_target_processes(port).unwrap(); + assert_eq!( + processes.len(), + 1, + "Expected exactly 1 deduplicated process entry, got {}", + processes.len() + ); + + drop(listener4); + drop(listener6); + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/killport-2.0.1/src/macos.rs new/killport-2.0.2/src/macos.rs --- old/killport-2.0.1/src/macos.rs 2026-07-06 19:46:23.000000000 +0200 +++ new/killport-2.0.2/src/macos.rs 2026-08-01 10:04:31.000000000 +0200 @@ -1,9 +1,10 @@ use crate::unix::UnixProcess; +use libproc::libproc::bsd_info::BSDInfo; use libproc::libproc::file_info::pidfdinfo; use libproc::libproc::file_info::{ListFDs, ProcFDType}; use libproc::libproc::net_info::{SocketFDInfo, SocketInfoKind}; -use libproc::libproc::proc_pid::{listpidinfo, name}; +use libproc::libproc::proc_pid::{listpidinfo, name, pidinfo}; use libproc::processes::{pids_by_type, ProcFilter}; use log::debug; use nix::unistd::Pid; @@ -27,7 +28,14 @@ if seen_pids.contains(&pid) { continue; } - let fds = listpidinfo::<ListFDs>(pid, 1024); + // Size the fd list to the process's actual fd count — a fixed cap + // silently misses sockets in fd-heavy processes. Small headroom + // covers fds opened between the two calls; fall back to a generous + // default when the count is unavailable. + let max_fds = pidinfo::<BSDInfo>(pid, 0) + .map(|info| info.pbi_nfiles as usize + 32) + .unwrap_or(4096); + let fds = listpidinfo::<ListFDs>(pid, max_fds); if let Ok(fds) = fds { for fd in fds { if let ProcFDType::Socket = fd.proc_fdtype.into() { @@ -48,7 +56,19 @@ } }; if u16::from_be(local_port) == port { - let process_name = name(pid).map_err(io::Error::other)?; + // The process can exit between socket + // enumeration and the name lookup; a + // vanished process must not fail the scan. + let process_name = match name(pid) { + Ok(process_name) => process_name, + Err(e) => { + debug!( + "Skipping PID {}: name lookup failed ({})", + pid, e + ); + continue 'next_process; + } + }; debug!( "Found process '{}' with PID {} listening on port {}", process_name, pid, port @@ -234,4 +254,61 @@ drop(socket); } + + #[test] + fn test_find_target_processes_beyond_1024_fds() { + // Regression test: the fd list used to be capped at 1024 entries, so a + // socket whose fd index landed past that was silently missed. + const TARGET_FDS: libc::rlim_t = 2200; + + unsafe { + let mut lim = libc::rlimit { + rlim_cur: 0, + rlim_max: 0, + }; + if libc::getrlimit(libc::RLIMIT_NOFILE, &mut lim) != 0 { + eprintln!("skipping: getrlimit failed"); + return; + } + if lim.rlim_cur < TARGET_FDS { + let raised = libc::rlimit { + rlim_cur: TARGET_FDS.min(lim.rlim_max), + rlim_max: lim.rlim_max, + }; + if libc::setrlimit(libc::RLIMIT_NOFILE, &raised) != 0 + || raised.rlim_cur < TARGET_FDS + { + eprintln!("skipping: cannot raise RLIMIT_NOFILE to {}", TARGET_FDS); + return; + } + } + } + + // Hold enough fds open that the listener bound afterwards gets an fd + // index well past the old 1024 cap. + let mut hoard = Vec::new(); + for _ in 0..1500 { + match std::fs::File::open("/dev/null") { + Ok(f) => hoard.push(f), + Err(_) => break, + } + } + if hoard.len() < 1200 { + eprintln!("skipping: could only open {} fds", hoard.len()); + return; + } + + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let port = listener.local_addr().unwrap().port(); + + let processes = find_target_processes(port).unwrap(); + assert!( + !processes.is_empty(), + "Expected to find listener on port {} even with fd index > 1024", + port + ); + + drop(listener); + drop(hoard); + } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/killport-2.0.1/src/main.rs new/killport-2.0.2/src/main.rs --- old/killport-2.0.1/src/main.rs 2026-07-06 19:46:23.000000000 +0200 +++ new/killport-2.0.2/src/main.rs 2026-08-01 10:04:31.000000000 +0200 @@ -60,8 +60,10 @@ exit(1); }); - // Attempt to kill processes listening on specified ports + // Attempt to kill processes listening on specified ports. A failing + // port must not prevent the remaining ports from being processed. let mut any_not_found = false; + let mut any_error = false; for port in args.ports { match killport.kill_service_by_port(port, args.signal.clone(), args.mode, args.dry_run) { @@ -84,12 +86,17 @@ } } Err(err) => { - error!("{}", err); - exit(1); + error!("Port {}: {}", port, err); + any_error = true; } } } + // Exit with the worst outcome across all ports: + // error (1) > nothing found (2, unless --no-fail) > success (0). + if any_error { + exit(1); + } if any_not_found && !args.no_fail { exit(2); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/killport-2.0.1/src/windows.rs new/killport-2.0.2/src/windows.rs --- old/killport-2.0.1/src/windows.rs 2026-07-06 19:46:23.000000000 +0200 +++ new/killport-2.0.2/src/windows.rs 2026-08-01 10:04:31.000000000 +0200 @@ -413,8 +413,12 @@ .for_each(|element| { // Convert the port value let local_port: u16 = (element.dwLocalPort as u16).to_be(); - if local_port == port { - pids.insert(element.dwOwningPid); + // PID 0 ([System Process]) owns TIME_WAIT rows and PID 4 + // (System) owns kernel sockets; neither can nor should be + // terminated, and opening PID 0 fails with 0x57. + let pid = element.dwOwningPid; + if local_port == port && pid != 0 && pid != 4 { + pids.insert(pid); } }); } ++++++ vendor.tar.xz ++++++ /work/SRC/openSUSE:Factory/killport/vendor.tar.xz /work/SRC/openSUSE:Factory/.killport.new.16738/vendor.tar.xz differ: char 15, line 1
