mount jetstream as a userspace character device on linux

clippy

Signed-off-by: oppiliappan <me@oppi.li>

oppi.li 6bc1b106 7d0c8d00

verified
Changed files
+47 -27
src
-20
readme.md
··· 1 - # excuse 2 - 3 - mount jetstream as a userspace character device on linux. 4 - 5 - ## usage 6 - 7 - ``` 8 - λ cargo b 9 - λ sudo RUST_LOG=info ./target/debug/excuse 10 - 11 - # -- in another shell -- 12 - λ cat /dev/jetstream | jq 13 - ``` 14 - 15 - ## motivation 16 - 17 - > People who think that userspace filesystems are realistic 18 - > for anything but toys are just misguided 19 - > 20 - > Linus Torvalds
···
+42
readme.txt
···
··· 1 + excuse - mount jetstream as a character device on linux 2 + 3 + CUSE is FUSE's little brother, and it allows mounting a 4 + single character device file in /dev, with the device driver 5 + being implemented in userspace. 6 + 7 + unlike typical files, character devices like /dev/urandom 8 + are not seekable. they only support a streaming interface: 9 + 10 + λ cat /dev/urandom # random numbers 11 + 12 + excuse simply mounts a websocket connection to an atproto 13 + jetstream as a userspace character device: 14 + 15 + λ sudo RUST_LOG=info excuse 16 + [2025-09-21 INFO excuse] Initializing CUSE at /dev/jetstream 17 + [2025-09-21 INFO excuse] /dev/jetstream is now 0644 18 + 19 + in another terminal: 20 + 21 + λ cat /dev/jetstream | jq 22 + { 23 + "did": "did:plc:s6zjj6aw652mmvsrs573j6ti", 24 + "time_us": 1758442606746247, 25 + "kind": "commit", 26 + "commit": { ... } 27 + } 28 + . 29 + . 30 + . 31 + 32 + you will notice a couple of optimizations: 33 + 34 + - the websocket is only opened when the file has readers, 35 + and is closed when all readers are closed 36 + - the same connection is shared by all readers 37 + - the data is buffered and not sent byte by byte 38 + 39 + it is a bit of a gimmick however, you can do this easily 40 + with websocat: 41 + 42 + λ websocat wss://jetstream1.us-east.fire.hose.cam/subscribe
+5 -7
src/main.rs
··· 91 break; 92 } 93 Some(Err(e)) => { 94 - error!("websocket error: {}", e); 95 break; 96 } 97 None => { ··· 105 } 106 } 107 Err(e) => { 108 - error!("failed to connect to websocket: {}", e); 109 } 110 } 111 ··· 182 reply: fuser::ReplyEmpty, 183 ) { 184 info!( 185 - "release(ino: {:#x?}, fh: {}, flags: {:#x?}, lock_owner: {:?}, flush: {})", 186 - ino, fh, flags, lock_owner, flush 187 ); 188 self.end_stream(); 189 reply.ok(); ··· 198 let handle = thread::spawn(|| { 199 fuser::cuse(device).unwrap_or_else(|e| { 200 error!( 201 - "failed to start cuse device: {}. try run this example as privileged user", 202 - e 203 ); 204 std::process::exit(1); 205 }) ··· 207 208 // make the device readable without sudo 209 let output = Command::new("chmod") 210 - .args(&["644", &format!("/dev/{DEV}")]) 211 .output(); 212 213 match output {
··· 91 break; 92 } 93 Some(Err(e)) => { 94 + error!("websocket error: {e}"); 95 break; 96 } 97 None => { ··· 105 } 106 } 107 Err(e) => { 108 + error!("failed to connect to websocket: {e}"); 109 } 110 } 111 ··· 182 reply: fuser::ReplyEmpty, 183 ) { 184 info!( 185 + "release(ino: {ino:#x?}, fh: {fh}, flags: {flags:#x?}, lock_owner: {lock_owner:?}, flush: {flush})" 186 ); 187 self.end_stream(); 188 reply.ok(); ··· 197 let handle = thread::spawn(|| { 198 fuser::cuse(device).unwrap_or_else(|e| { 199 error!( 200 + "failed to start cuse device: {e}. try run this example as privileged user" 201 ); 202 std::process::exit(1); 203 }) ··· 205 206 // make the device readable without sudo 207 let output = Command::new("chmod") 208 + .args(["644", &format!("/dev/{DEV}")]) 209 .output(); 210 211 match output {