╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ serve_common.zig ─────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯
5 // the outcome of resolving a requested repo path to its on-disk directory. 6 // the http and ssh paths each map the cases to their own error responses. 7 pub const RepoPath = union(enum) { 8 ok: []const u8, // resolved on-disk path; the caller owns and frees it 9 invalid, // a <owner>:<repo> path was expected but not given 10 not_found, // unknown owner, or the repo doesn't exist 11 }; 12 13 // resolve a requested repo path to its on-disk directory, parsed as 14 // <owner>:<repo> through the event store. 15 pub fn resolveRepoPath( 16 io: std.Io, 17 allocator: std.mem.Allocator, 18 users_dir: []const u8, 19 admin_repo_path: []const u8, 20 requested: []const u8, 21 ) !RepoPath { 22 const owner_repo = evt.parseOwnerRepoPath(requested) orelse return .invalid; 23 const location = (try evt.resolveOrCreateRepo( 24 io, 25 allocator, 26 users_dir, 27 admin_repo_path, 28 owner_repo.owner, 29 owner_repo.name, 30 null, 31 )) orelse return .not_found; 32 return .{ .ok = try evt.repoPath(allocator, users_dir, &location.owner_id, &location.repo_id) }; 33 } 34 35 // every connection task shares one error writer, so writing to it takes a 36 // lock. uncancelable, since a log line shouldn't be a cancelation point. 37 var log_mutex: std.Io.Mutex = .init; 38 39 pub fn logError(io: std.Io, err: *std.Io.Writer, comptime fmt: []const u8, args: anytype) void { 40 log_mutex.lockUncancelable(io); 41 defer log_mutex.unlock(io); 42 err.print(fmt, args) catch return; 43 err.flush() catch {}; 44 } 45 46 // accept connections forever, spawning `handleConn(context, stream)` as a task 47 // for each. the handler owns the stream (it closes it). `name` labels accept 48 // errors in the log. shared by the http, ssh, and web ui listeners. 49 pub fn runListener( 50 io: std.Io, 51 net_server: *std.Io.net.Server, 52 tasks: *std.Io.Group, 53 err: *std.Io.Writer, 54 name: []const u8, 55 context: anytype, 56 comptime handleConn: fn (@TypeOf(context), std.Io.net.Stream) void, 57 ) void { 58 const Context = @TypeOf(context); 59 60 const Conn = struct { 61 context: Context, 62 stream: std.Io.net.Stream, 63 64 fn run(c: @This()) void { 65 handleConn(c.context, c.stream); 66 } 67 }; 68 69 const Listener = struct { 70 io: std.Io, 71 net_server: *std.Io.net.Server, 72 tasks: *std.Io.Group, 73 err: *std.Io.Writer, 74 name: []const u8, 75 context: Context, 76 77 fn run(self: @This()) void { 78 while (true) { 79 const stream = self.net_server.accept(self.io) catch |accept_err| { 80 if (accept_err == error.Canceled) return; 81 logError(self.io, self.err, "{s} accept failed: {s}\n", .{ self.name, @errorName(accept_err) }); 82 continue; 83 }; 84 self.tasks.async(self.io, Conn.run, .{Conn{ .context = self.context, .stream = stream }}); 85 } 86 } 87 }; 88 89 tasks.async(io, Listener.run, .{Listener{ 90 .io = io, 91 .net_server = net_server, 92 .tasks = tasks, 93 .err = err, 94 .name = name, 95 .context = context, 96 }}); 97 } 98