Skip to content

Commit 5102ecb

Browse files
author
mirkobrombin
committed
feat: code optimization
1 parent 1e0db79 commit 5102ecb

9 files changed

Lines changed: 366 additions & 309 deletions

File tree

README.md

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,3 @@ DBus to communicate with the host, introducing 2 big dependencies.
3737

3838
This is not a replacement for `host-spawn`, but a different approach to the same
3939
problem on those systems where Flatpak or DBus are not available.
40-
41-
## What is left to do?
42-
43-
- [x] Add an option to restrict the commands that can be run
44-
- [x] Add support for creating shims for host binaries
45-
- [x] Switch from TCP to Unix sockets
46-
- [ ] Code optimization

cmd/client.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package cmd
2+
3+
import (
4+
"github.com/distrobox/hrun/pkg/client"
5+
)
6+
7+
func StartClient(command []string, socketPath string) {
8+
client.StartClient(command, socketPath)
9+
}

cmd/help.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
)
7+
8+
func ShowHelp() {
9+
fmt.Fprintf(os.Stderr, `Usage: hrun [options] [command] [args...]
10+
11+
Options:
12+
-h, --help Display this help message.
13+
--start Start the server.
14+
--allowed-cmd Specify allowed command (can be used multiple times).
15+
--socket Specify an alternative socket path (default: /tmp/hrun.sock).
16+
17+
If command is "start", it starts the server with specified allowed commands.
18+
Otherwise, it starts the client and sends the command to the server.
19+
If no command is provided, it starts a shell on the host.
20+
`)
21+
os.Exit(0)
22+
}

cmd/server.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package cmd
2+
3+
import (
4+
"github.com/distrobox/hrun/pkg/server"
5+
)
6+
7+
func StartServer(allowedCmds []string, socketPath string) {
8+
server.StartServer(allowedCmds, socketPath)
9+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module github.com/mirkobrombin/hrun
1+
module github.com/distrobox/hrun
22

33
go 1.21.6
44

main.go

Lines changed: 4 additions & 301 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,14 @@
11
package main
22

33
import (
4-
"bufio"
5-
"encoding/json"
64
"flag"
75
"fmt"
8-
"io"
9-
"log"
10-
"net"
116
"os"
12-
"os/exec"
13-
"os/signal"
147
"path"
15-
"strconv"
16-
"strings"
17-
"syscall"
188

19-
"github.com/creack/pty"
20-
"golang.org/x/term"
9+
"github.com/distrobox/hrun/cmd"
2110
)
2211

23-
type Command struct {
24-
Command []string
25-
Width uint16
26-
Height uint16
27-
}
28-
2912
func main() {
3013
helpFlag := flag.Bool("h", false, "Display help")
3114
helpFlagLong := flag.Bool("help", false, "Display help")
@@ -56,13 +39,13 @@ If no command is provided, it starts a shell on the host.
5639

5740
// Help message
5841
if *helpFlag || *helpFlagLong {
59-
flag.Usage()
42+
cmd.ShowHelp()
6043
return
6144
}
6245

6346
// Server mode
6447
if *startFlag {
65-
startServer(allowedCmds, socketFlag)
48+
cmd.StartServer(allowedCmds, *socketFlag)
6649
return
6750
}
6851

@@ -74,285 +57,5 @@ If no command is provided, it starts a shell on the host.
7457
command = flag.Args()
7558
}
7659

77-
startClient(command, socketFlag)
78-
}
79-
80-
func startServer(allowedCmds []string, socketFlag *string) {
81-
// Create a listener for the server
82-
listener, err := net.Listen("unix", *socketFlag)
83-
if err != nil {
84-
panic(err)
85-
}
86-
defer listener.Close()
87-
log.Printf("Server is running on %s\n", listener.Addr())
88-
89-
// Set up a signal handler to shut down the server
90-
doneCh := make(chan struct{})
91-
go func() {
92-
sigCh := make(chan os.Signal, 1)
93-
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
94-
95-
<-sigCh
96-
log.Println("Shutdown signal received, closing server...")
97-
close(doneCh)
98-
}()
99-
100-
// Accept connections and handle them
101-
for {
102-
select {
103-
case <-doneCh:
104-
log.Println("Shutting down server...")
105-
return
106-
case conn, ok := <-acceptConn(listener):
107-
if !ok {
108-
log.Println("Listener closed, shutting down server...")
109-
return
110-
}
111-
go handleConnection(conn, allowedCmds)
112-
}
113-
}
114-
}
115-
116-
func acceptConn(listener net.Listener) <-chan net.Conn {
117-
ch := make(chan net.Conn)
118-
go func() {
119-
defer close(ch)
120-
conn, err := listener.Accept()
121-
if err != nil {
122-
log.Println("Error accepting connection:", err)
123-
return
124-
}
125-
ch <- conn
126-
}()
127-
return ch
128-
}
129-
130-
func handleConnection(conn net.Conn, allowedCmds []string) {
131-
defer conn.Close()
132-
133-
// Read the command from the client
134-
reader := bufio.NewReader(conn)
135-
rawCommand, err := reader.ReadString('\n')
136-
if err != nil {
137-
log.Println("Failed to read command: ", err)
138-
return
139-
}
140-
log.Printf("Received command: %s", rawCommand)
141-
142-
// Decode the command into the Command struct
143-
var cmdStruct Command
144-
if err := json.Unmarshal([]byte(rawCommand), &cmdStruct); err != nil {
145-
log.Printf("Error decoding command: %v", err)
146-
conn.Close()
147-
return
148-
}
149-
if len(cmdStruct.Command) == 0 {
150-
log.Println("No command provided")
151-
return
152-
}
153-
154-
// Check if the command is allowed
155-
if len(allowedCmds) > 0 {
156-
allowed := false
157-
for _, allowedCmd := range allowedCmds {
158-
if cmdStruct.Command[0] == allowedCmd {
159-
allowed = true
160-
break
161-
}
162-
}
163-
if !allowed {
164-
log.Printf("Command %s is not allowed", cmdStruct.Command[0])
165-
conn.Close()
166-
return
167-
}
168-
}
169-
170-
// Prepare a pty
171-
var ptyMaster, ptySlave *os.File
172-
ptyMaster, ptySlave, err = pty.Open()
173-
if err != nil {
174-
log.Println("Error creating PTY:", err)
175-
conn.Close()
176-
return
177-
}
178-
defer ptySlave.Close()
179-
log.Println("PTY created")
180-
181-
// Set initial terminal size
182-
ws := &pty.Winsize{
183-
Cols: cmdStruct.Width,
184-
Rows: cmdStruct.Height,
185-
}
186-
if err := pty.Setsize(ptyMaster, ws); err != nil {
187-
log.Printf("Error setting initial terminal size: %v", err)
188-
} else {
189-
log.Printf("Terminal initialized to %dx%d", cmdStruct.Width, cmdStruct.Height)
190-
}
191-
192-
// Set up the channels to communicate with the host
193-
go func() {
194-
io.Copy(conn, ptyMaster)
195-
ptyMaster.Close()
196-
conn.Close()
197-
}()
198-
go func() {
199-
io.Copy(ptyMaster, conn)
200-
ptyMaster.Close()
201-
conn.Close()
202-
}()
203-
204-
// Set the terminal size on resize request
205-
go func() {
206-
for {
207-
message, err := reader.ReadString('\n')
208-
if err != nil {
209-
break
210-
}
211-
212-
if strings.HasPrefix(message, "resize:") {
213-
log.Println("Resize request received")
214-
trimmedMessage := strings.TrimSpace(message)
215-
parts := strings.Split(trimmedMessage, ":")
216-
if len(parts) == 3 {
217-
width, errWidth := strconv.Atoi(parts[1])
218-
height, errHeight := strconv.Atoi(parts[2])
219-
if errWidth != nil || errHeight != nil {
220-
log.Printf("Error converting dimensions to integers: width error %v, height error %v", errWidth, errHeight)
221-
continue
222-
}
223-
ws := &pty.Winsize{
224-
Cols: uint16(width),
225-
Rows: uint16(height),
226-
}
227-
if err := pty.Setsize(ptyMaster, ws); err != nil {
228-
log.Printf("Error resizing PTY: %v", err)
229-
} else {
230-
log.Printf("Terminal resized to %dx%d", width, height)
231-
}
232-
} else {
233-
log.Println("Invalid resize message format")
234-
}
235-
}
236-
}
237-
}()
238-
239-
// Execute the command
240-
cmd := exec.Command(cmdStruct.Command[0], cmdStruct.Command[1:]...)
241-
cmd.Stdin = ptySlave
242-
cmd.Stdout = ptySlave
243-
cmd.Stderr = ptySlave
244-
245-
// Set the process attributes
246-
cmd.SysProcAttr = &syscall.SysProcAttr{
247-
Setctty: true,
248-
Setsid: true,
249-
Pdeathsig: syscall.SIGTERM,
250-
}
251-
252-
// Start the shell process
253-
if err = cmd.Start(); err != nil {
254-
log.Println("Error starting shell:", err)
255-
return
256-
}
257-
log.Println("Shell started")
258-
259-
sigCh := make(chan os.Signal, 1)
260-
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
261-
262-
// Handle the termination signal
263-
go func() {
264-
<-sigCh
265-
conn.Close()
266-
syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
267-
}()
268-
269-
// Wait for the shell process to exit
270-
cmd.Wait()
271-
log.Println("Shell process exited")
272-
log.Printf("Connection closed\n\n")
273-
}
274-
275-
func startClient(command []string, socketFlag *string) {
276-
// Connect to the server
277-
conn, err := net.Dial("unix", *socketFlag)
278-
if err != nil {
279-
log.Println("Error connecting to the host:", err)
280-
return
281-
}
282-
defer conn.Close()
283-
284-
// Get the initial terminal size
285-
initialWidth, initialHeight, err := term.GetSize(int(os.Stdin.Fd()))
286-
if err != nil {
287-
log.Println("Error getting initial terminal size:", err)
288-
return
289-
}
290-
291-
// Send the command to the server
292-
cmd := Command{
293-
Command: command,
294-
Width: uint16(initialWidth),
295-
Height: uint16(initialHeight),
296-
}
297-
cmdBytes, err := json.Marshal(cmd)
298-
if err != nil {
299-
log.Println("Error encoding command:", err)
300-
return
301-
}
302-
303-
_, err = conn.Write(append(cmdBytes, '\n'))
304-
if err != nil {
305-
log.Println("Error sending command to the server:", err)
306-
return
307-
}
308-
309-
// Set up handling for SIGWINCH (window change) signal to detect terminal resize events
310-
sendTerminalSize := func() {
311-
width, height, err := term.GetSize(int(os.Stdin.Fd()))
312-
if err != nil {
313-
log.Println("Error getting terminal size:", err)
314-
return
315-
}
316-
317-
resizeCommand := fmt.Sprintf("resize:%d:%d\n", width, height)
318-
_, err = conn.Write([]byte(resizeCommand))
319-
if err != nil {
320-
log.Println("Error sending terminal size to the server:", err)
321-
}
322-
}
323-
324-
sigwinchChan := make(chan os.Signal, 1)
325-
signal.Notify(sigwinchChan, syscall.SIGWINCH)
326-
go func() {
327-
for range sigwinchChan {
328-
sendTerminalSize()
329-
}
330-
}()
331-
332-
// Set the terminal to raw mode
333-
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
334-
if err != nil {
335-
log.Println("Error setting terminal to raw mode:", err)
336-
return
337-
}
338-
defer func() { _ = term.Restore(int(os.Stdin.Fd()), oldState) }()
339-
340-
// Create a channel to communicate with the pty
341-
doneCh := make(chan struct{})
342-
go func() {
343-
_, err := io.Copy(conn, os.Stdin)
344-
if err != nil {
345-
log.Println("Error copying data to the server:", err)
346-
}
347-
close(doneCh)
348-
}()
349-
go func() {
350-
_, err := io.Copy(os.Stdout, conn)
351-
if err != nil {
352-
log.Println("Error copying data from the server:", err)
353-
}
354-
close(doneCh)
355-
}()
356-
357-
<-doneCh
60+
cmd.StartClient(command, *socketFlag)
35861
}

0 commit comments

Comments
 (0)