Commit caac091f257a
tarball8 files changed+1255-40
@@ -3,24 +3,443 @@
33
version = 444
55
[[package]]6
+name = "async-broadcast"7
+version = "0.7.2"8
+source = "registry+https://github.com/rust-lang/crates.io-index"9
+checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532"10
+dependencies = [11
+ "event-listener",12
+ "event-listener-strategy",13
+ "futures-core",14
+ "pin-project-lite",15
+]16
+17
+[[package]]18
+name = "async-channel"19
+version = "2.5.0"20
+source = "registry+https://github.com/rust-lang/crates.io-index"21
+checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"22
+dependencies = [23
+ "concurrent-queue",24
+ "event-listener-strategy",25
+ "futures-core",26
+ "pin-project-lite",27
+]28
+29
+[[package]]30
+name = "async-executor"31
+version = "1.14.0"32
+source = "registry+https://github.com/rust-lang/crates.io-index"33
+checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a"34
+dependencies = [35
+ "async-task",36
+ "concurrent-queue",37
+ "fastrand",38
+ "futures-lite",39
+ "pin-project-lite",40
+ "slab",41
+]42
+43
+[[package]]44
+name = "async-io"45
+version = "2.6.0"46
+source = "registry+https://github.com/rust-lang/crates.io-index"47
+checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"48
+dependencies = [49
+ "autocfg",50
+ "cfg-if",51
+ "concurrent-queue",52
+ "futures-io",53
+ "futures-lite",54
+ "parking",55
+ "polling",56
+ "rustix",57
+ "slab",58
+ "windows-sys",59
+]60
+61
+[[package]]62
+name = "async-lock"63
+version = "3.4.2"64
+source = "registry+https://github.com/rust-lang/crates.io-index"65
+checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"66
+dependencies = [67
+ "event-listener",68
+ "event-listener-strategy",69
+ "pin-project-lite",70
+]71
+72
+[[package]]73
+name = "async-process"74
+version = "2.5.0"75
+source = "registry+https://github.com/rust-lang/crates.io-index"76
+checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75"77
+dependencies = [78
+ "async-channel",79
+ "async-io",80
+ "async-lock",81
+ "async-signal",82
+ "async-task",83
+ "blocking",84
+ "cfg-if",85
+ "event-listener",86
+ "futures-lite",87
+ "rustix",88
+]89
+90
+[[package]]91
+name = "async-recursion"92
+version = "1.2.0"93
+source = "registry+https://github.com/rust-lang/crates.io-index"94
+checksum = "4f8abc12baad266b1c8cec146854c195b5864b4221d4b2ca7296a7ae82d9e451"95
+dependencies = [96
+ "proc-macro2",97
+ "quote",98
+ "syn 3.0.6",99
+]100
+101
+[[package]]102
+name = "async-signal"103
+version = "0.2.14"104
+source = "registry+https://github.com/rust-lang/crates.io-index"105
+checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485"106
+dependencies = [107
+ "async-io",108
+ "async-lock",109
+ "atomic-waker",110
+ "cfg-if",111
+ "futures-core",112
+ "futures-io",113
+ "rustix",114
+ "signal-hook-registry",115
+ "slab",116
+ "windows-sys",117
+]118
+119
+[[package]]120
+name = "async-task"121
+version = "4.7.1"122
+source = "registry+https://github.com/rust-lang/crates.io-index"123
+checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"124
+125
+[[package]]126
+name = "async-trait"127
+version = "0.1.92"128
+source = "registry+https://github.com/rust-lang/crates.io-index"129
+checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"130
+dependencies = [131
+ "proc-macro2",132
+ "quote",133
+ "syn 3.0.6",134
+]135
+136
+[[package]]137
+name = "atomic-waker"138
+version = "1.1.2"139
+source = "registry+https://github.com/rust-lang/crates.io-index"140
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"141
+142
+[[package]]143
+name = "autocfg"144
+version = "1.5.1"145
+source = "registry+https://github.com/rust-lang/crates.io-index"146
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"147
+148
+[[package]]149
+name = "bitflags"150
+version = "2.13.2"151
+source = "registry+https://github.com/rust-lang/crates.io-index"152
+checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"153
+154
+[[package]]155
+name = "blocking"156
+version = "1.7.0"157
+source = "registry+https://github.com/rust-lang/crates.io-index"158
+checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa"159
+dependencies = [160
+ "async-channel",161
+ "async-task",162
+ "futures-io",163
+ "futures-lite",164
+ "piper",165
+]166
+167
+[[package]]168
+name = "bumpalo"169
+version = "3.20.3"170
+source = "registry+https://github.com/rust-lang/crates.io-index"171
+checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"172
+173
+[[package]]174
+name = "cfg-if"175
+version = "1.0.5"176
+source = "registry+https://github.com/rust-lang/crates.io-index"177
+checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"178
+179
+[[package]]180
+name = "concurrent-queue"181
+version = "2.5.0"182
+source = "registry+https://github.com/rust-lang/crates.io-index"183
+checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"184
+dependencies = [185
+ "crossbeam-utils",186
+]187
+188
+[[package]]189
+name = "crossbeam-utils"190
+version = "0.8.23"191
+source = "registry+https://github.com/rust-lang/crates.io-index"192
+checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"193
+194
+[[package]]195
+name = "endi"196
+version = "1.1.1"197
+source = "registry+https://github.com/rust-lang/crates.io-index"198
+checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099"199
+200
+[[package]]201
+name = "enumflags2"202
+version = "0.7.12"203
+source = "registry+https://github.com/rust-lang/crates.io-index"204
+checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef"205
+dependencies = [206
+ "enumflags2_derive",207
+ "serde",208
+]209
+210
+[[package]]211
+name = "enumflags2_derive"212
+version = "0.7.12"213
+source = "registry+https://github.com/rust-lang/crates.io-index"214
+checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"215
+dependencies = [216
+ "proc-macro2",217
+ "quote",218
+ "syn 2.0.119",219
+]220
+221
+[[package]]222
+name = "equivalent"223
+version = "1.0.2"224
+source = "registry+https://github.com/rust-lang/crates.io-index"225
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"226
+227
+[[package]]228
+name = "errno"229
+version = "0.3.14"230
+source = "registry+https://github.com/rust-lang/crates.io-index"231
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"232
+dependencies = [233
+ "libc",234
+ "windows-sys",235
+]236
+237
+[[package]]238
+name = "event-listener"239
+version = "5.4.2"240
+source = "registry+https://github.com/rust-lang/crates.io-index"241
+checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2"242
+dependencies = [243
+ "parking",244
+ "pin-project-lite",245
+]246
+247
+[[package]]248
+name = "event-listener-strategy"249
+version = "0.5.4"250
+source = "registry+https://github.com/rust-lang/crates.io-index"251
+checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"252
+dependencies = [253
+ "event-listener",254
+ "pin-project-lite",255
+]256
+257
+[[package]]258
+name = "fastrand"259
+version = "2.5.0"260
+source = "registry+https://github.com/rust-lang/crates.io-index"261
+checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"262
+263
+[[package]]264
+name = "futures-core"265
+version = "0.3.34"266
+source = "registry+https://github.com/rust-lang/crates.io-index"267
+checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"268
+269
+[[package]]270
+name = "futures-io"271
+version = "0.3.34"272
+source = "registry+https://github.com/rust-lang/crates.io-index"273
+checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"274
+275
+[[package]]276
+name = "futures-lite"277
+version = "2.6.1"278
+source = "registry+https://github.com/rust-lang/crates.io-index"279
+checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"280
+dependencies = [281
+ "fastrand",282
+ "futures-core",283
+ "futures-io",284
+ "parking",285
+ "pin-project-lite",286
+]287
+288
+[[package]]289
+name = "futures-task"290
+version = "0.3.34"291
+source = "registry+https://github.com/rust-lang/crates.io-index"292
+checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"293
+294
+[[package]]295
+name = "futures-util"296
+version = "0.3.34"297
+source = "registry+https://github.com/rust-lang/crates.io-index"298
+checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"299
+dependencies = [300
+ "futures-core",301
+ "futures-task",302
+ "pin-project-lite",303
+ "slab",304
+]305
+306
+[[package]]307
+name = "getrandom"308
+version = "0.4.3"309
+source = "registry+https://github.com/rust-lang/crates.io-index"310
+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"311
+dependencies = [312
+ "cfg-if",313
+ "libc",314
+ "r-efi",315
+]316
+317
+[[package]]318
+name = "hashbrown"319
+version = "0.17.1"320
+source = "registry+https://github.com/rust-lang/crates.io-index"321
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"322
+323
+[[package]]324
+name = "hermit-abi"325
+version = "0.5.3"326
+source = "registry+https://github.com/rust-lang/crates.io-index"327
+checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284"328
+329
+[[package]]330
+name = "hex"331
+version = "0.4.3"332
+source = "registry+https://github.com/rust-lang/crates.io-index"333
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"334
+335
+[[package]]336
+name = "indexmap"337
+version = "2.14.2"338
+source = "registry+https://github.com/rust-lang/crates.io-index"339
+checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"340
+dependencies = [341
+ "equivalent",342
+ "hashbrown",343
+]344
+345
+[[package]]6346
name = "itoa"7347
version = "1.0.18"8348
source = "registry+https://github.com/rust-lang/crates.io-index"9349
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"10350
11351
[[package]]352
+name = "js-sys"353
+version = "0.3.106"354
+source = "registry+https://github.com/rust-lang/crates.io-index"355
+checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5"356
+dependencies = [357
+ "cfg-if",358
+ "futures-util",359
+ "wasm-bindgen",360
+]361
+362
+[[package]]12363
name = "libc"13364
version = "0.2.189"14365
source = "registry+https://github.com/rust-lang/crates.io-index"15366
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"16367
17368
[[package]]369
+name = "linux-raw-sys"370
+version = "0.12.1"371
+source = "registry+https://github.com/rust-lang/crates.io-index"372
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"373
+374
+[[package]]18375
name = "memchr"19376
version = "2.8.3"20377
source = "registry+https://github.com/rust-lang/crates.io-index"21378
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"22379
23380
[[package]]381
+name = "memoffset"382
+version = "0.9.1"383
+source = "registry+https://github.com/rust-lang/crates.io-index"384
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"385
+dependencies = [386
+ "autocfg",387
+]388
+389
+[[package]]390
+name = "once_cell"391
+version = "1.21.4"392
+source = "registry+https://github.com/rust-lang/crates.io-index"393
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"394
+395
+[[package]]396
+name = "ordered-stream"397
+version = "0.2.0"398
+source = "registry+https://github.com/rust-lang/crates.io-index"399
+checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"400
+dependencies = [401
+ "futures-core",402
+ "pin-project-lite",403
+]404
+405
+[[package]]406
+name = "parking"407
+version = "2.2.1"408
+source = "registry+https://github.com/rust-lang/crates.io-index"409
+checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"410
+411
+[[package]]412
+name = "pin-project-lite"413
+version = "0.2.17"414
+source = "registry+https://github.com/rust-lang/crates.io-index"415
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"416
+417
+[[package]]418
+name = "piper"419
+version = "0.2.5"420
+source = "registry+https://github.com/rust-lang/crates.io-index"421
+checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1"422
+dependencies = [423
+ "atomic-waker",424
+ "fastrand",425
+ "futures-io",426
+]427
+428
+[[package]]429
+name = "polling"430
+version = "3.11.0"431
+source = "registry+https://github.com/rust-lang/crates.io-index"432
+checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"433
+dependencies = [434
+ "cfg-if",435
+ "concurrent-queue",436
+ "hermit-abi",437
+ "pin-project-lite",438
+ "rustix",439
+ "windows-sys",440
+]441
+442
+[[package]]24443
name = "pound"25444
version = "0.1.8"26445
source = "registry+https://github.com/rust-lang/crates.io-index"@@ -41,6 +460,15 @@ dependencies = [
41460
]42461
43462
[[package]]463
+name = "proc-macro-crate"464
+version = "3.5.0"465
+source = "registry+https://github.com/rust-lang/crates.io-index"466
+checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"467
+dependencies = [468
+ "toml_edit",469
+]470
+471
+[[package]]44472
name = "proc-macro2"45473
version = "1.0.107"46474
source = "registry+https://github.com/rust-lang/crates.io-index"@@ -59,17 +487,45 @@ dependencies = [
59487
]60488
61489
[[package]]490
+name = "r-efi"491
+version = "6.0.0"492
+source = "registry+https://github.com/rust-lang/crates.io-index"493
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"494
+495
+[[package]]62496
name = "roundabout"63497
version = "0.1.0"64498
dependencies = [499
+ "async-io",500
+ "futures-lite",65501
"libc",66502
"pound",67503
"serde",68504
"serde_json",69505
"toml",506
+ "zbus",507
+]508
+509
+[[package]]510
+name = "rustix"511
+version = "1.1.5"512
+source = "registry+https://github.com/rust-lang/crates.io-index"513
+checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d"514
+dependencies = [515
+ "bitflags",516
+ "errno",517
+ "libc",518
+ "linux-raw-sys",519
+ "windows-sys",70520
]71521
72522
[[package]]523
+name = "rustversion"524
+version = "1.0.23"525
+source = "registry+https://github.com/rust-lang/crates.io-index"526
+checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"527
+528
+[[package]]73529
name = "serde"74530
version = "1.0.229"75531
source = "registry+https://github.com/rust-lang/crates.io-index"@@ -96,7 +552,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
96552
dependencies = [97553
"proc-macro2",98554
"quote",99
- "syn",555
+ "syn 3.0.6",100556
]101557
102558
[[package]]@@ -113,6 +569,17 @@ dependencies = [
113569
]114570
115571
[[package]]572
+name = "serde_repr"573
+version = "0.1.21"574
+source = "registry+https://github.com/rust-lang/crates.io-index"575
+checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906"576
+dependencies = [577
+ "proc-macro2",578
+ "quote",579
+ "syn 3.0.6",580
+]581
+582
+[[package]]116583
name = "serde_spanned"117584
version = "1.1.1"118585
source = "registry+https://github.com/rust-lang/crates.io-index"@@ -122,6 +589,33 @@ dependencies = [
122589
]123590
124591
[[package]]592
+name = "signal-hook-registry"593
+version = "1.4.8"594
+source = "registry+https://github.com/rust-lang/crates.io-index"595
+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"596
+dependencies = [597
+ "errno",598
+ "libc",599
+]600
+601
+[[package]]602
+name = "slab"603
+version = "0.4.12"604
+source = "registry+https://github.com/rust-lang/crates.io-index"605
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"606
+607
+[[package]]608
+name = "syn"609
+version = "2.0.119"610
+source = "registry+https://github.com/rust-lang/crates.io-index"611
+checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"612
+dependencies = [613
+ "proc-macro2",614
+ "quote",615
+ "unicode-ident",616
+]617
+618
+[[package]]125619
name = "syn"126620
version = "3.0.6"127621
source = "registry+https://github.com/rust-lang/crates.io-index"@@ -133,6 +627,19 @@ dependencies = [
133627
]134628
135629
[[package]]630
+name = "tempfile"631
+version = "3.27.0"632
+source = "registry+https://github.com/rust-lang/crates.io-index"633
+checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"634
+dependencies = [635
+ "fastrand",636
+ "getrandom",637
+ "once_cell",638
+ "rustix",639
+ "windows-sys",640
+]641
+642
+[[package]]136643
name = "toml"137644
version = "0.9.12+spec-1.1.0"138645
source = "registry+https://github.com/rust-lang/crates.io-index"@@ -140,7 +647,7 @@ checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
140647
dependencies = [141648
"serde_core",142649
"serde_spanned",143
- "toml_datetime",650
+ "toml_datetime 0.7.5+spec-1.1.0",144651
"toml_parser",145652
"winnow 0.7.15",146653
]@@ -155,6 +662,27 @@ dependencies = [
155662
]156663
157664
[[package]]665
+name = "toml_datetime"666
+version = "1.1.1+spec-1.1.0"667
+source = "registry+https://github.com/rust-lang/crates.io-index"668
+checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"669
+dependencies = [670
+ "serde_core",671
+]672
+673
+[[package]]674
+name = "toml_edit"675
+version = "0.25.15+spec-1.1.0"676
+source = "registry+https://github.com/rust-lang/crates.io-index"677
+checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614"678
+dependencies = [679
+ "indexmap",680
+ "toml_datetime 1.1.1+spec-1.1.0",681
+ "toml_parser",682
+ "winnow 1.0.4",683
+]684
+685
+[[package]]158686
name = "toml_parser"159687
version = "1.1.3+spec-1.1.0"160688
source = "registry+https://github.com/rust-lang/crates.io-index"@@ -164,12 +692,65 @@ dependencies = [
164692
]165693
166694
[[package]]695
+name = "tracing"696
+version = "0.1.44"697
+source = "registry+https://github.com/rust-lang/crates.io-index"698
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"699
+dependencies = [700
+ "pin-project-lite",701
+ "tracing-attributes",702
+ "tracing-core",703
+]704
+705
+[[package]]706
+name = "tracing-attributes"707
+version = "0.1.31"708
+source = "registry+https://github.com/rust-lang/crates.io-index"709
+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"710
+dependencies = [711
+ "proc-macro2",712
+ "quote",713
+ "syn 2.0.119",714
+]715
+716
+[[package]]717
+name = "tracing-core"718
+version = "0.1.36"719
+source = "registry+https://github.com/rust-lang/crates.io-index"720
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"721
+dependencies = [722
+ "once_cell",723
+]724
+725
+[[package]]726
+name = "uds_windows"727
+version = "1.2.1"728
+source = "registry+https://github.com/rust-lang/crates.io-index"729
+checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"730
+dependencies = [731
+ "memoffset",732
+ "tempfile",733
+ "windows-sys",734
+]735
+736
+[[package]]167737
name = "unicode-ident"168738
version = "1.0.26"169739
source = "registry+https://github.com/rust-lang/crates.io-index"170740
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"171741
172742
[[package]]743
+name = "uuid"744
+version = "1.27.0"745
+source = "registry+https://github.com/rust-lang/crates.io-index"746
+checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a"747
+dependencies = [748
+ "js-sys",749
+ "serde_core",750
+ "wasm-bindgen",751
+]752
+753
+[[package]]173754
name = "venial"174755
version = "0.6.1"175756
source = "registry+https://github.com/rust-lang/crates.io-index"@@ -180,6 +761,66 @@ dependencies = [
180761
]181762
182763
[[package]]764
+name = "wasm-bindgen"765
+version = "0.2.129"766
+source = "registry+https://github.com/rust-lang/crates.io-index"767
+checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409"768
+dependencies = [769
+ "cfg-if",770
+ "once_cell",771
+ "rustversion",772
+ "wasm-bindgen-macro",773
+ "wasm-bindgen-shared",774
+]775
+776
+[[package]]777
+name = "wasm-bindgen-macro"778
+version = "0.2.129"779
+source = "registry+https://github.com/rust-lang/crates.io-index"780
+checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535"781
+dependencies = [782
+ "quote",783
+ "wasm-bindgen-macro-support",784
+]785
+786
+[[package]]787
+name = "wasm-bindgen-macro-support"788
+version = "0.2.129"789
+source = "registry+https://github.com/rust-lang/crates.io-index"790
+checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7"791
+dependencies = [792
+ "bumpalo",793
+ "proc-macro2",794
+ "quote",795
+ "syn 3.0.6",796
+ "wasm-bindgen-shared",797
+]798
+799
+[[package]]800
+name = "wasm-bindgen-shared"801
+version = "0.2.129"802
+source = "registry+https://github.com/rust-lang/crates.io-index"803
+checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6"804
+dependencies = [805
+ "unicode-ident",806
+]807
+808
+[[package]]809
+name = "windows-link"810
+version = "0.2.1"811
+source = "registry+https://github.com/rust-lang/crates.io-index"812
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"813
+814
+[[package]]815
+name = "windows-sys"816
+version = "0.61.2"817
+source = "registry+https://github.com/rust-lang/crates.io-index"818
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"819
+dependencies = [820
+ "windows-link",821
+]822
+823
+[[package]]183824
name = "winnow"184825
version = "0.7.15"185826
source = "registry+https://github.com/rust-lang/crates.io-index"@@ -190,9 +831,123 @@ name = "winnow"
190831
version = "1.0.4"191832
source = "registry+https://github.com/rust-lang/crates.io-index"192833
checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"834
+dependencies = [835
+ "memchr",836
+]837
+838
+[[package]]839
+name = "zbus"840
+version = "5.19.0"841
+source = "registry+https://github.com/rust-lang/crates.io-index"842
+checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907"843
+dependencies = [844
+ "async-broadcast",845
+ "async-executor",846
+ "async-io",847
+ "async-lock",848
+ "async-process",849
+ "async-recursion",850
+ "async-task",851
+ "async-trait",852
+ "blocking",853
+ "enumflags2",854
+ "event-listener",855
+ "futures-core",856
+ "futures-lite",857
+ "hex",858
+ "libc",859
+ "ordered-stream",860
+ "rustix",861
+ "serde",862
+ "serde_repr",863
+ "tracing",864
+ "uds_windows",865
+ "uuid",866
+ "windows-sys",867
+ "winnow 1.0.4",868
+ "zbus_macros",869
+ "zbus_names",870
+ "zvariant",871
+]872
+873
+[[package]]874
+name = "zbus_macros"875
+version = "5.19.0"876
+source = "registry+https://github.com/rust-lang/crates.io-index"877
+checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40"878
+dependencies = [879
+ "proc-macro-crate",880
+ "proc-macro2",881
+ "quote",882
+ "syn 3.0.6",883
+ "zbus_names",884
+ "zvariant",885
+ "zvariant_utils",886
+]887
+888
+[[package]]889
+name = "zbus_names"890
+version = "4.3.4"891
+source = "registry+https://github.com/rust-lang/crates.io-index"892
+checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e"893
+dependencies = [894
+ "serde",895
+ "winnow 1.0.4",896
+ "zvariant",897
+]898
+899
+[[package]]900
+name = "zcheapstr"901
+version = "1.1.0"902
+source = "registry+https://github.com/rust-lang/crates.io-index"903
+checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473"904
+dependencies = [905
+ "serde",906
+]193907
194908
[[package]]195909
name = "zmij"196910
version = "1.0.23"197911
source = "registry+https://github.com/rust-lang/crates.io-index"198912
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"913
+914
+[[package]]915
+name = "zvariant"916
+version = "5.15.0"917
+source = "registry+https://github.com/rust-lang/crates.io-index"918
+checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147"919
+dependencies = [920
+ "endi",921
+ "enumflags2",922
+ "serde",923
+ "winnow 1.0.4",924
+ "zcheapstr",925
+ "zvariant_derive",926
+ "zvariant_utils",927
+]928
+929
+[[package]]930
+name = "zvariant_derive"931
+version = "5.15.0"932
+source = "registry+https://github.com/rust-lang/crates.io-index"933
+checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497"934
+dependencies = [935
+ "proc-macro-crate",936
+ "proc-macro2",937
+ "quote",938
+ "syn 3.0.6",939
+ "zvariant_utils",940
+]941
+942
+[[package]]943
+name = "zvariant_utils"944
+version = "4.2.0"945
+source = "registry+https://github.com/rust-lang/crates.io-index"946
+checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3"947
+dependencies = [948
+ "proc-macro2",949
+ "quote",950
+ "serde",951
+ "syn 3.0.6",952
+ "winnow 1.0.4",953
+]@@ -18,6 +18,11 @@ serde = { version = "1", features = [ "derive" ] }
1818
serde_json = "1"1919
toml = { version = "0.9", default-features = false, features = [ "parse", "serde" ] }2020
21
+[target.'cfg(target_os = "linux")'.dependencies]22
+async-io = "2"23
+futures-lite = "2"24
+zbus = { version = "5", default-features = false, features = [ "async-io", "blocking-api" ] }25
+2126
[lints.clippy]2227
nursery = { level = "warn", priority = -1 }2328
pedantic = { level = "warn", priority = -1 }@@ -22,7 +22,9 @@ rabt artifacts check | list | gc [--cap] | pin <path>
2222
2323
`run` exits with the build's code, 124 if `--deadline` killed it, 75 if `--max-wait` ran out.2424
25
-a granted build gets `CARGO_BUILD_JOBS`, `MAKEFLAGS`, `CMAKE_BUILD_PARALLEL_LEVEL` and a `NIX_CONFIG` with `cores`/`max-jobs` to match its grant, so it actually uses the cores it got and no more.25
+a granted build gets `CARGO_BUILD_JOBS`, `MAKEFLAGS`, `CMAKE_BUILD_PARALLEL_LEVEL` and a `NIX_CONFIG` with `cores`/`max-jobs` to match its grant.26
+27
+on linux, `rabt` creates a transient scope through the systemd user manager's D-Bus `StartTransientUnit` method. the command waits until its scope is active, then runs with its original environment and terminal I/O. `CPUQuotaPerSecUSec` caps CPU time at the granted core count, and `MemoryMax` caps the whole build at its granted RAM multiplied by `overrun.ram_slack`, with at least the granted RAM available. these limits cover descendants too, even when a tool ignores the job-count environment variables. linux requires a running systemd user manager and user bus, a failed scope refuses the build rather than running it without limits. other platforms retain the job-count environment variables without cgroup enforcement.2628
2729
the grant can be bigger than the ask. idle cores go to whoever's building, minus what other sessions have said they'll want via `rabt expect`. spare cores never hold up admission, oversubscribing cpu only slows things down, but each one is charged ram at the build's own ram per core since more jobs eat more memory. a session's build counts as an `expect` too, so agents that forget still leave a mark. `--exact` opts out.2830
@@ -99,7 +101,7 @@ elastic = true # hand idle cores to whoever's building
99101
intent_ttl = "20m" # how long a `rabt expect` or a build keeps its claim100102
101103
[overrun]102
-ram = "warn" # or "throttle", systemd-run MemoryHigh on linux104
+ram = "warn" # or "throttle", MemoryHigh at granted RAM on linux103105
ram_slack = 1.25104106
time_grace = 1.5 # overdue at this multiple of --time105107
remind_every = "5m"@@ -10,6 +10,7 @@ rustPlatform.buildRustPackage {
1010
../Cargo.toml1111
../Cargo.lock1212
../src13
+ ../tests/systemd.rs1314
];1415
};1516
@@ -11,11 +11,17 @@ mod run;
1111
mod sched;1212
mod store;1313
mod sys;14
+#[cfg(target_os = "linux")]15
+mod systemd;1416
mod units;1517
1618
use pound::Parse;1719
1820
fn main() {21
+ #[cfg(target_os = "linux")]22
+ if let Some(code) = systemd::exec() {23
+ std::process::exit(code);24
+ }1925
// a typo must not exit 2, that's a blocking pretooluse verdict and would2026
// wall off every bash call in every session2127
let cfg = config::Config::load().unwrap_or_else(|e| {@@ -13,7 +13,7 @@ use std::{
1313
1414
use crate::{1515
artifacts, client,16
- config::{Config, OverCap, RamOverrun},16
+ config::{Config, OverCap},1717
history, paths,1818
proto::{self, Acquire, Event, Reader, Request},1919
sched::Need,@@ -39,8 +39,12 @@ pub struct Args {
3939
}4040
4141
static PGID: AtomicI32 = AtomicI32::new(0);42
+#[cfg(target_os = "linux")]43
+static FORWARDED: AtomicI32 = AtomicI32::new(0);4244
4345
extern "C" fn forward(sig: libc::c_int) {46
+ #[cfg(target_os = "linux")]47
+ FORWARDED.store(sig, Ordering::Relaxed);4448
let pgid = PGID.load(Ordering::Relaxed);4549
if pgid > 1 {4650
unsafe { libc::kill(-pgid, sig) };@@ -149,34 +153,10 @@ fn acquire(req: &Acquire, max_wait: Option<Dur>) -> Result<Grant, i32> {
149153
Err(1)150154
}151155
152
-fn has_systemd_run() -> bool {153
- env::var_os("PATH")154
- .is_some_and(|p| env::split_paths(&p).any(|d| d.join("systemd-run").exists()))155
-}156
-157
-fn command(cfg: &Config, a: &Args, need: &Need, grant: &Grant) -> Command {158
- let mut argv = a.cmd.clone();159
- if cfg.overrun.ram == RamOverrun::Throttle && cfg!(target_os = "linux") && has_systemd_run() {160
- let high = Bytes((need.ram.0 as f64 * cfg.overrun.ram_slack) as u64);161
- let mut wrapped: Vec<String> = [162
- "systemd-run",163
- "--user",164
- "--scope",165
- "--quiet",166
- "--collect",167
- "-p",168
- ]169
- .map(Into::into)170
- .into();171
- wrapped.push(format!("MemoryHigh={}", high.0));172
- wrapped.push("--".into());173
- wrapped.append(&mut argv);174
- argv = wrapped;175
- }176
-156
+fn command(cfg: &Config, a: &Args, grant: &Grant) -> Command {177157
let n = grant.cores.to_string();178
- let mut cmd = Command::new(&argv[0]);179
- cmd.args(&argv[1..])158
+ let mut cmd = Command::new(&a.cmd[0]);159
+ cmd.args(&a.cmd[1..])180160
.process_group(0)181161
.env("ROUNDABOUT_LEASE", grant.lease.to_string())182162
.env("CARGO_BUILD_JOBS", &n)@@ -196,13 +176,6 @@ fn command(cfg: &Config, a: &Args, need: &Need, grant: &Grant) -> Command {
196176
cmd.env("NIX_CONFIG", nix);197177
}198178
199
- #[cfg(target_os = "linux")]200
- unsafe {201
- cmd.pre_exec(|| {202
- libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGTERM);203
- Ok(())204
- });205
- }206179
cmd207180
}208181
@@ -278,7 +251,20 @@ pub fn run(cfg: &Config, a: &Args) -> i32 {
278251
};279252
need.ram = need.ram.max(grant.ram);280253
281
- let mut child = match command(cfg, a, &need, &grant).spawn() {254
+ let command = command(cfg, a, &grant);255
+ #[cfg(target_os = "linux")]256
+ let (mut child, scope) =257
+ match crate::systemd::spawn(&command, grant.lease, grant.cores, grant.ram, &cfg.overrun) {258
+ Ok(spawned) => spawned,259
+ Err(error) => {260
+ say(&format!("couldn't start {}: {error}", a.cmd[0]));261
+ return 127;262
+ }263
+ };264
+ #[cfg(not(target_os = "linux"))]265
+ let mut command = command;266
+ #[cfg(not(target_os = "linux"))]267
+ let mut child = match command.spawn() {282268
Ok(c) => c,283269
Err(e) => {284270
say(&format!("couldn't start {}: {e}", a.cmd[0]));@@ -319,6 +305,10 @@ pub fn run(cfg: &Config, a: &Args) -> i32 {
319305
let mut warned_time = false;320306
321307
let status = loop {308
+ #[cfg(target_os = "linux")]309
+ if let signal @ 1.. = FORWARDED.swap(0, Ordering::Relaxed) {310
+ let _ = scope.signal(signal);311
+ }322312
match child.try_wait() {323313
Ok(Some(status)) => break status,324314
Ok(None) => {}@@ -339,9 +329,13 @@ pub fn run(cfg: &Config, a: &Args) -> i32 {
339329
term_sent = Some(Instant::now());340330
}341331
if term_sent.is_some() {332
+ #[cfg(target_os = "linux")]333
+ let _ = scope.signal(libc::SIGTERM);342334
unsafe { libc::kill(-pgid, libc::SIGTERM) };343335
}344336
} else if term_sent.is_some_and(|t| t.elapsed() > Duration::from_secs(10)) {337
+ #[cfg(target_os = "linux")]338
+ let _ = scope.signal(libc::SIGKILL);345339
unsafe { libc::kill(-pgid, libc::SIGKILL) };346340
}347341
@@ -368,6 +362,9 @@ pub fn run(cfg: &Config, a: &Args) -> i32 {
368362
thread::sleep(Duration::from_millis(250));369363
};370364
365
+ PGID.store(0, Ordering::Relaxed);366
+ #[cfg(target_os = "linux")]367
+ drop(scope);371368
let wall = started.elapsed();372369
if let Some(s) = &mut out {373370
let _ = proto::send(s, &Request::Done);@@ -0,0 +1,302 @@
1
+use std::{2
+ env, io,3
+ os::fd::{AsRawFd, FromRawFd, OwnedFd},4
+ os::unix::process::CommandExt,5
+ process::{Child, Command},6
+ time::Duration,7
+};8
+9
+use futures_lite::StreamExt;10
+use zbus::{11
+ blocking::{Connection, Proxy},12
+ proxy::SignalStream,13
+ zvariant::{OwnedObjectPath, Value},14
+};15
+16
+use crate::{17
+ config::{Overrun, RamOverrun},18
+ units::Bytes,19
+};20
+21
+const EXEC: &str = "__roundabout_exec";22
+const MANAGER: &str = "org.freedesktop.systemd1.Manager";23
+const BUS_NAME: &str = "org.freedesktop.systemd1";24
+const BUS_PATH: &str = "/org/freedesktop/systemd1";25
+26
+pub struct Scope {27
+ connection: Connection,28
+ name: String,29
+}30
+31
+fn properties(32
+ pid: u32,33
+ cores: u32,34
+ ram: Bytes,35
+ overrun: &Overrun,36
+) -> zbus::Result<Vec<(&'static str, Value<'static>)>> {37
+ let max = ram.0 as f64 * overrun.ram_slack.max(1.0);38
+ if !overrun.ram_slack.is_finite() || max >= u64::MAX as f64 {39
+ return Err(zbus::Error::Failure("RAM limit must be finite".into()));40
+ }41
+ let mut properties = vec![42
+ ("Description", Value::from("roundabout build")),43
+ ("PIDs", Value::from(vec![pid])),44
+ ("CollectMode", Value::from("inactive-or-failed")),45
+ ("CPUAccounting", Value::from(true)),46
+ ("MemoryAccounting", Value::from(true)),47
+ (48
+ "CPUQuotaPerSecUSec",49
+ Value::from(u64::from(cores.max(1)) * 1_000_000),50
+ ),51
+ ("MemoryMax", Value::from((max as u64).max(1))),52
+ ("OOMPolicy", Value::from("kill")),53
+ ("TimeoutStopUSec", Value::from(10_000_000_u64)),54
+ ];55
+ if overrun.ram == RamOverrun::Throttle {56
+ properties.push(("MemoryHigh", Value::from(ram.0.max(1))));57
+ }58
+ Ok(properties)59
+}60
+61
+fn wait_job(mut signals: SignalStream<'_>, job: &OwnedObjectPath) -> zbus::Result<()> {62
+ async_io::block_on(futures_lite::future::race(63
+ async {64
+ while let Some(signal) = signals.next().await {65
+ let (_, path, _, result): (u32, OwnedObjectPath, String, String) =66
+ signal.body().deserialize()?;67
+ if path == *job {68
+ return if result == "done" {69
+ Ok(())70
+ } else {71
+ Err(zbus::Error::Failure(format!("systemd job {result}")))72
+ };73
+ }74
+ }75
+ Err(zbus::Error::Failure(76
+ "systemd disconnected before the job finished".into(),77
+ ))78
+ },79
+ async {80
+ async_io::Timer::after(Duration::from_secs(15)).await;81
+ Err(zbus::Error::Failure("systemd job timed out".into()))82
+ },83
+ ))84
+}85
+86
+impl Scope {87
+ fn manager(&self) -> zbus::Result<Proxy<'_>> {88
+ Proxy::new(&self.connection, BUS_NAME, BUS_PATH, MANAGER)89
+ }90
+91
+ fn start(92
+ connection: Connection,93
+ pid: u32,94
+ lease: u64,95
+ cores: u32,96
+ ram: Bytes,97
+ overrun: &Overrun,98
+ ) -> zbus::Result<Self> {99
+ let scope = Self {100
+ connection,101
+ name: format!("roundabout-{}-{lease}-{pid}.scope", std::process::id()),102
+ };103
+ let manager = scope.manager()?;104
+ let signals = async_io::block_on(105
+ manager106
+ .inner()107
+ .receive_signal_with_args("JobRemoved", &[(2, &scope.name)]),108
+ )?;109
+ let _: () = manager.call("Subscribe", &())?;110
+ let auxiliary: Vec<(&str, Vec<(&str, Value<'_>)>)> = Vec::new();111
+ let job: OwnedObjectPath = manager.call(112
+ "StartTransientUnit",113
+ &(114
+ &scope.name,115
+ "fail",116
+ properties(pid, cores, ram, overrun)?,117
+ auxiliary,118
+ ),119
+ )?;120
+ wait_job(signals, &job)?;121
+ drop(manager);122
+ Ok(scope)123
+ }124
+125
+ pub fn signal(&self, signal: i32) -> zbus::Result<()> {126
+ self.manager()?127
+ .call("KillUnit", &(&self.name, "all", signal))128
+ }129
+130
+ fn stop(&self) -> zbus::Result<()> {131
+ let manager = self.manager()?;132
+ let signals = async_io::block_on(133
+ manager134
+ .inner()135
+ .receive_signal_with_args("JobRemoved", &[(2, &self.name)]),136
+ )?;137
+ let job: OwnedObjectPath = manager.call("StopUnit", &(&self.name, "replace"))?;138
+ wait_job(signals, &job)139
+ }140
+}141
+142
+impl Drop for Scope {143
+ fn drop(&mut self) {144
+ let _ = self.stop();145
+ }146
+}147
+148
+fn gate() -> io::Result<(OwnedFd, OwnedFd)> {149
+ let mut descriptors = [-1; 2];150
+ if unsafe { libc::pipe2(descriptors.as_mut_ptr(), libc::O_CLOEXEC) } != 0 {151
+ return Err(io::Error::last_os_error());152
+ }153
+ Ok(unsafe {154
+ (155
+ OwnedFd::from_raw_fd(descriptors[0]),156
+ OwnedFd::from_raw_fd(descriptors[1]),157
+ )158
+ })159
+}160
+161
+pub fn spawn(162
+ command: &Command,163
+ lease: u64,164
+ cores: u32,165
+ ram: Bytes,166
+ overrun: &Overrun,167
+) -> Result<(Child, Scope), String> {168
+ let connection =169
+ Connection::session().map_err(|error| format!("can't connect to the user bus, {error}"))?;170
+ let (read, write) = gate().map_err(|error| error.to_string())?;171
+ let descriptor = read.as_raw_fd();172
+ let mut runner = Command::new(env::current_exe().map_err(|error| error.to_string())?);173
+ runner174
+ .arg(EXEC)175
+ .arg(descriptor.to_string())176
+ .arg(command.get_program())177
+ .args(command.get_args())178
+ .process_group(0);179
+ for (name, value) in command.get_envs() {180
+ if let Some(value) = value {181
+ runner.env(name, value);182
+ } else {183
+ runner.env_remove(name);184
+ }185
+ }186
+ let parent = unsafe { libc::getpid() };187
+ unsafe {188
+ runner.pre_exec(move || {189
+ if libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGTERM) != 0190
+ || libc::fcntl(descriptor, libc::F_SETFD, 0) != 0191
+ {192
+ return Err(io::Error::last_os_error());193
+ }194
+ if libc::getppid() != parent {195
+ return Err(io::Error::other("parent exited before launch"));196
+ }197
+ Ok(())198
+ });199
+ }200
+ let mut child = runner.spawn().map_err(|error| error.to_string())?;201
+ drop(read);202
+ let result = Scope::start(connection, child.id(), lease, cores, ram, overrun)203
+ .map_err(|error| format!("can't start the transient scope, {error}"))204
+ .and_then(|scope| {205
+ if unsafe { libc::write(write.as_raw_fd(), b"1".as_ptr().cast(), 1) } != 1 {206
+ return Err(io::Error::last_os_error().to_string());207
+ }208
+ Ok(scope)209
+ });210
+ drop(write);211
+ match result {212
+ Ok(scope) => Ok((child, scope)),213
+ Err(error) => {214
+ let _ = child.kill();215
+ let _ = child.wait();216
+ Err(error)217
+ }218
+ }219
+}220
+221
+pub fn exec() -> Option<i32> {222
+ let mut arguments = env::args_os().skip(1);223
+ if arguments.next()?.as_encoded_bytes() != EXEC.as_bytes() {224
+ return None;225
+ }226
+ let Some(descriptor) = arguments227
+ .next()228
+ .and_then(|arg| arg.to_str()?.parse::<i32>().ok())229
+ .filter(|fd| *fd >= 3)230
+ else {231
+ return Some(1);232
+ };233
+ let descriptor = unsafe { OwnedFd::from_raw_fd(descriptor) };234
+ let mut byte = [0_u8];235
+ loop {236
+ let count = unsafe { libc::read(descriptor.as_raw_fd(), byte.as_mut_ptr().cast(), 1) };237
+ if count == 1 && byte[0] == b'1' {238
+ break;239
+ }240
+ if count != -1 || io::Error::last_os_error().kind() != io::ErrorKind::Interrupted {241
+ return Some(1);242
+ }243
+ }244
+ drop(descriptor);245
+ let Some(program) = arguments.next() else {246
+ return Some(1);247
+ };248
+ let error = Command::new(&program).args(arguments).exec();249
+ eprintln!(250
+ "rabt: couldn't start {}: {error}",251
+ program.to_string_lossy()252
+ );253
+ Some(127)254
+}255
+256
+#[cfg(test)]257
+mod tests {258
+ use super::*;259
+260
+ #[test]261
+ fn limits_follow_the_grant_with_memory_headroom() {262
+ let overrun = Overrun::default();263
+ let properties = properties(123, 8, Bytes::gib(4), &overrun).unwrap();264
+ assert!(properties.contains(&("PIDs", Value::from(vec![123_u32]))));265
+ assert!(properties.contains(&("CPUQuotaPerSecUSec", Value::from(8_000_000_u64))));266
+ assert!(properties.contains(&("MemoryMax", Value::from(Bytes::gib(5).0))));267
+ assert!(!properties.iter().any(|(name, _)| *name == "MemoryHigh"));268
+ }269
+270
+ #[test]271
+ fn throttle_starts_at_the_granted_memory() {272
+ let overrun = Overrun {273
+ ram: RamOverrun::Throttle,274
+ ..Overrun::default()275
+ };276
+ let properties = properties(123, 2, Bytes::gib(2), &overrun).unwrap();277
+ assert!(properties.contains(&("MemoryHigh", Value::from(Bytes::gib(2).0))));278
+ assert!(properties.contains(&("MemoryMax", Value::from(Bytes::mib(2560).0))));279
+ }280
+281
+ #[test]282
+ fn small_grants_never_become_unlimited() {283
+ let overrun = Overrun {284
+ ram_slack: 0.5,285
+ ..Overrun::default()286
+ };287
+ let properties = properties(123, 0, Bytes::ZERO, &overrun).unwrap();288
+ assert!(properties.contains(&("CPUQuotaPerSecUSec", Value::from(1_000_000_u64))));289
+ assert!(properties.contains(&("MemoryMax", Value::from(1_u64))));290
+ }291
+292
+ #[test]293
+ fn nonfinite_limits_are_rejected() {294
+ for ram_slack in [f64::INFINITY, f64::NEG_INFINITY, f64::NAN, f64::MAX] {295
+ let overrun = Overrun {296
+ ram_slack,297
+ ..Overrun::default()298
+ };299
+ assert!(properties(123, 1, Bytes::gib(2), &overrun).is_err());300
+ }301
+ }302
+}@@ -0,0 +1,147 @@
1
+#![cfg(target_os = "linux")]2
+3
+use std::{4
+ fs,5
+ io::Write,6
+ os::fd::{AsRawFd, FromRawFd, OwnedFd},7
+ os::unix::process::CommandExt,8
+ process::{Child, Command, Stdio},9
+ time::Duration,10
+};11
+12
+fn gated_command(program: &str, arguments: &[&str]) -> (Child, OwnedFd) {13
+ let mut descriptors = [-1; 2];14
+ assert_eq!(15
+ unsafe { libc::pipe2(descriptors.as_mut_ptr(), libc::O_CLOEXEC) },16
+ 017
+ );18
+ let read = unsafe { OwnedFd::from_raw_fd(descriptors[0]) };19
+ let write = unsafe { OwnedFd::from_raw_fd(descriptors[1]) };20
+ let descriptor = read.as_raw_fd();21
+ let mut command = Command::new(env!("CARGO_BIN_EXE_rabt"));22
+ command23
+ .args(["__roundabout_exec", &descriptor.to_string(), program])24
+ .args(arguments)25
+ .stdout(Stdio::piped())26
+ .stderr(Stdio::piped());27
+ unsafe {28
+ command.pre_exec(move || {29
+ if libc::fcntl(descriptor, libc::F_SETFD, 0) == -1 {30
+ return Err(std::io::Error::last_os_error());31
+ }32
+ Ok(())33
+ });34
+ }35
+ let child = command.spawn().unwrap();36
+ drop(read);37
+ (child, write)38
+}39
+40
+#[test]41
+fn command_waits_for_scope_setup() {42
+ let (mut child, gate) = gated_command(env!("CARGO_BIN_EXE_rabt"), &["--version"]);43
+ let mut output = libc::pollfd {44
+ fd: child.stdout.as_ref().unwrap().as_raw_fd(),45
+ events: libc::POLLIN,46
+ revents: 0,47
+ };48
+ assert_eq!(unsafe { libc::poll(&raw mut output, 1, 100) }, 0);49
+ assert!(child.try_wait().unwrap().is_none());50
+ fs::File::from(gate).write_all(b"1").unwrap();51
+ let output = child.wait_with_output().unwrap();52
+ assert!(output.status.success());53
+ assert!(54
+ String::from_utf8(output.stdout)55
+ .unwrap()56
+ .starts_with("rabt ")57
+ );58
+}59
+60
+#[test]61
+fn failed_scope_setup_never_executes_the_command() {62
+ let (child, gate) = gated_command(env!("CARGO_BIN_EXE_rabt"), &["--version"]);63
+ drop(gate);64
+ let output = child.wait_with_output().unwrap();65
+ assert_eq!(output.status.code(), Some(1));66
+ assert!(output.stdout.is_empty());67
+}68
+69
+#[test]70
+fn missing_commands_keep_exit_127() {71
+ let (child, gate) = gated_command("/roundabout-missing-command", &[]);72
+ fs::File::from(gate).write_all(b"1").unwrap();73
+ assert_eq!(child.wait_with_output().unwrap().status.code(), Some(127));74
+}75
+76
+#[test]77
+#[ignore = "requires a systemd user manager and cgroup v2"]78
+fn transient_scope_enforces_the_grant() {79
+ if std::env::var_os("ROUNDABOUT_SCOPE_PROBE").is_some() {80
+ let membership = fs::read_to_string("/proc/self/cgroup").unwrap();81
+ let path = membership82
+ .lines()83
+ .find_map(|line| line.strip_prefix("0::"))84
+ .unwrap();85
+ assert!(path.contains("roundabout-"));86
+ let cgroup = std::path::Path::new("/sys/fs/cgroup").join(path.trim_start_matches('/'));87
+ let cpu = fs::read_to_string(cgroup.join("cpu.max")).unwrap();88
+ let limits: Vec<_> = cpu.split_whitespace().collect();89
+ assert_eq!(limits[0], limits[1]);90
+ assert_eq!(91
+ fs::read_to_string(cgroup.join("memory.max"))92
+ .unwrap()93
+ .trim(),94
+ (80 * 1024 * 1024).to_string()95
+ );96
+ assert_eq!(97
+ fs::read_to_string(cgroup.join("memory.high"))98
+ .unwrap()99
+ .trim(),100
+ (64 * 1024 * 1024).to_string()101
+ );102
+ assert_eq!(103
+ fs::read_to_string(cgroup.join("memory.oom.group"))104
+ .unwrap()105
+ .trim(),106
+ "1"107
+ );108
+ assert_eq!(std::env::var("CARGO_BUILD_JOBS").unwrap(), "1");109
+ assert!(std::env::var("ROUNDABOUT_LEASE").is_ok());110
+ return;111
+ }112
+ let directory =113
+ std::env::temp_dir().join(format!("roundabout-scope-test-{}", std::process::id()));114
+ fs::create_dir_all(&directory).unwrap();115
+ let config = directory.join("config.toml");116
+ fs::write(117
+ &config,118
+ "[overrun]\nram = \"throttle\"\n[queue]\nlinger = \"0s\"\n",119
+ )120
+ .unwrap();121
+ let output = Command::new(env!("CARGO_BIN_EXE_rabt"))122
+ .args([123
+ "run", "--ram", "64M", "--disk", "0", "--cores", "1", "--time", "5s", "--exact", "--",124
+ ])125
+ .arg(std::env::current_exe().unwrap())126
+ .args([127
+ "--exact",128
+ "transient_scope_enforces_the_grant",129
+ "--ignored",130
+ "--nocapture",131
+ ])132
+ .current_dir(&directory)133
+ .env("ROUNDABOUT_CONFIG", &config)134
+ .env("ROUNDABOUT_RUNTIME_DIR", directory.join("runtime"))135
+ .env("ROUNDABOUT_STATE_DIR", directory.join("state"))136
+ .env("ROUNDABOUT_SCOPE_PROBE", "1")137
+ .output()138
+ .unwrap();139
+ std::thread::sleep(Duration::from_secs(2));140
+ fs::remove_dir_all(directory).unwrap();141
+ assert!(142
+ output.status.success(),143
+ "{}\n{}",144
+ String::from_utf8_lossy(&output.stdout),145
+ String::from_utf8_lossy(&output.stderr)146
+ );147
+}