//go:build linux package dbreads import ( "errors" "fmt" "log" "net" "os" "os/user" "strconv" "golang.org/x/sys/unix" ) var errNotSocket = errors.New("stdin is not a connected unix stream socket") // RunWorker is `carlos system db-worker`, which carlos-dbread@.service runs // once per connection to carlos-dbread.socket (spec ยง8.3). func RunWorker(args []string) { if len(args) != 0 { fmt.Fprintln(os.Stderr, "db-worker: takes no arguments") os.Exit(2) } RunWorkerWith(WorkerOptions{Scratch: "/var/tmp", MaxRestored: fileSizeLimit()}) } // RunWorkerWith is RunWorker with its options supplied: the seam the root // runner's acceptance tests use to make a restore slow (Task 15). func RunWorkerWith(opt WorkerOptions) { conn, err := StdinConn(os.Stdin, CarlosUID) if err != nil { fmt.Fprintf(os.Stderr, "db-worker: %v\n", err) os.Exit(2) } if err := ServeWorker(conn, opt); err != nil { log.Printf("db-worker: %v", err) os.Exit(1) } } // CarlosUID is the agent's user, the only peer the worker serves. func CarlosUID() (uint32, error) { u, err := user.Lookup("carlos") if err != nil { return 0, err } uid, err := strconv.ParseUint(u.Uid, 10, 32) return uint32(uid), err } // StdinConn checks f is a connected unix stream socket (SO_TYPE, the socket // name's family, SO_PEERCRED) whose peer is wantUID, and returns it as a // connection. func StdinConn(f *os.File, wantUID func() (uint32, error)) (net.Conn, error) { fd := int(f.Fd()) typ, err := unix.GetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_TYPE) if err != nil || typ != unix.SOCK_STREAM { return nil, errNotSocket } if sa, err := unix.Getsockname(fd); err != nil { return nil, errNotSocket } else if _, ok := sa.(*unix.SockaddrUnix); !ok { return nil, errNotSocket } cred, err := unix.GetsockoptUcred(fd, unix.SOL_SOCKET, unix.SO_PEERCRED) if err != nil || cred.Pid == 0 { return nil, errNotSocket } want, err := wantUID() if err != nil { return nil, err } if cred.Uid != want { return nil, fmt.Errorf("the peer is uid %d, not the agent's uid %d", cred.Uid, want) } c, err := net.FileConn(f) if err != nil { return nil, errNotSocket } return c, nil } // fileSizeLimit is the unit's LimitFSIZE, the restored-size cap the box set // for its workers, or DefaultMaxRestored when the process has no limit. func fileSizeLimit() int64 { var r unix.Rlimit if err := unix.Getrlimit(unix.RLIMIT_FSIZE, &r); err != nil || r.Cur == unix.RLIM_INFINITY || r.Cur > 1<<62 { return DefaultMaxRestored } return int64(r.Cur) }