spawnChildForTLS spawns a child worker process to handle TLS handshake and the rest of the connection. This is called after the parent has received an SSL request and sent 'S'. The TCP connection file descriptor is passed to the child via ExtraFiles. Returns the spawned child process or an error.
(conn net.Conn)
| 130 | // The TCP connection file descriptor is passed to the child via ExtraFiles. |
| 131 | // Returns the spawned child process or an error. |
| 132 | func (s *Server) spawnChildForTLS(conn net.Conn) (*ChildProcess, error) { |
| 133 | tcpConn, ok := conn.(*net.TCPConn) |
| 134 | if !ok { |
| 135 | return nil, fmt.Errorf("connection is not TCP") |
| 136 | } |
| 137 | |
| 138 | // Get a file descriptor for the TCP connection |
| 139 | file, err := tcpConn.File() |
| 140 | if err != nil { |
| 141 | return nil, fmt.Errorf("failed to get file descriptor: %w", err) |
| 142 | } |
| 143 | // Note: tcpConn.File() duplicates the FD, so we need to close the original |
| 144 | // after starting the child. The file will be passed to the child. |
| 145 | |
| 146 | remoteAddr := conn.RemoteAddr().String() |
| 147 | |
| 148 | // Generate backend key for this connection (used for cancel requests) |
| 149 | backendSecretKey := generateSecretKey() |
| 150 | |
| 151 | // Prepare child configuration |
| 152 | // Note: Username is not known yet - child will read it from the startup message after TLS |
| 153 | childCfg := ChildConfig{ |
| 154 | RemoteAddr: remoteAddr, |
| 155 | DataDir: s.cfg.DataDir, |
| 156 | Extensions: s.cfg.Extensions, |
| 157 | IdleTimeout: int64(s.cfg.IdleTimeout), |
| 158 | ClientIdleTimeoutMax: int64(s.cfg.ClientIdleTimeoutMax), |
| 159 | TLSCertFile: s.cfg.TLSCertFile, |
| 160 | TLSKeyFile: s.cfg.TLSKeyFile, |
| 161 | DuckLake: s.cfg.DuckLake, |
| 162 | Users: s.cfg.Users, // Pass all users - child will look up after getting username |
| 163 | BackendSecretKey: backendSecretKey, |
| 164 | ServerStartTime: processStartTime.UnixNano(), |
| 165 | ServerVersion: processVersion, |
| 166 | } |
| 167 | |
| 168 | configJSON, err := json.Marshal(childCfg) |
| 169 | if err != nil { |
| 170 | _ = file.Close() |
| 171 | return nil, fmt.Errorf("failed to marshal child config: %w", err) |
| 172 | } |
| 173 | |
| 174 | // Spawn child process |
| 175 | cmd := exec.Command(os.Args[0]) |
| 176 | cmd.Env = append(os.Environ(), "DUCKGRES_CHILD_MODE=1") |
| 177 | cmd.ExtraFiles = []*os.File{file} // Will be FD 3 in child |
| 178 | |
| 179 | // Pass config via stdin (more secure than env var for passwords) |
| 180 | // Create a pipe for stdin |
| 181 | stdinPipe, err := cmd.StdinPipe() |
| 182 | if err != nil { |
| 183 | _ = file.Close() |
| 184 | return nil, fmt.Errorf("failed to create stdin pipe: %w", err) |
| 185 | } |
| 186 | |
| 187 | // Inherit stdout/stderr for logging |
| 188 | cmd.Stdout = os.Stdout |
| 189 | cmd.Stderr = os.Stderr |
no test coverage detected