Skip to content

Commit 9f12e5b

Browse files
committed
overhaul to properly support terminals
1 parent 3a47cc3 commit 9f12e5b

8 files changed

Lines changed: 191 additions & 241 deletions

File tree

elevate.go

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ package main
22

33
import (
44
"fmt"
5-
"os"
6-
"os/exec"
75
"syscall"
86
"unsafe"
97

@@ -83,15 +81,3 @@ func elevate(app, params, cwd string) error {
8381

8482
return nil
8583
}
86-
87-
func regular(app, params, cwd string) error {
88-
cmd := exec.Command(app, params)
89-
90-
cmd.Dir = cwd
91-
92-
cmd.Stdout = os.Stdout
93-
cmd.Stderr = os.Stderr
94-
cmd.Stdin = os.Stdin
95-
96-
return cmd.Run()
97-
}

go.mod

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
module sudo
22

3-
go 1.23.4
3+
go 1.24.2
44

5-
toolchain go1.23.5
5+
toolchain go1.24.4
66

77
require (
8-
github.com/coalaura/logger v1.2.3
9-
golang.org/x/sys v0.29.0
10-
golang.org/x/term v0.28.0
8+
github.com/coalaura/logger v1.4.5
9+
github.com/shirou/gopsutil v3.21.11+incompatible
10+
golang.org/x/sys v0.34.0
1111
)
1212

1313
require (
14+
github.com/go-ole/go-ole v1.3.0 // indirect
1415
github.com/gookit/color v1.5.4 // indirect
16+
github.com/stretchr/testify v1.9.0 // indirect
17+
github.com/tklauser/go-sysconf v0.3.15 // indirect
18+
github.com/tklauser/numcpus v0.10.0 // indirect
1519
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
20+
github.com/yusufpapurcu/wmi v1.2.4 // indirect
21+
golang.org/x/term v0.33.0 // indirect
1622
)

go.sum

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,44 @@
11
github.com/coalaura/logger v1.2.3 h1:uy6XjA+NjQKddjsY9FbwQplM4Rv9gJ43mVcE3YTyT9E=
22
github.com/coalaura/logger v1.2.3/go.mod h1:4lGnkI2dT7d0GSO3l1ci6dhxAJy3SWqjOi03k+Ugwxc=
3+
github.com/coalaura/logger v1.4.5 h1:xXazOab4qXaltUbD4TrQdSs2TtLB+k6t0t6y/M8LR3Q=
4+
github.com/coalaura/logger v1.4.5/go.mod h1:3HCYCWmsWmYW175e2/fZL9BWjJutr2W+7adeh1BPHkg=
35
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
46
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7+
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
8+
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
9+
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
10+
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
511
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
612
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
713
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
814
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
9-
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
10-
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
15+
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
16+
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
17+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
18+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
19+
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
20+
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
21+
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
22+
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
23+
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
24+
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
25+
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
26+
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
1127
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
1228
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
29+
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
30+
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
1331
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
1432
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
33+
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
34+
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
35+
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
36+
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1537
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
1638
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
17-
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
18-
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
39+
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
40+
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
41+
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
42+
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
1943
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
2044
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

main.go

Lines changed: 23 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package main
22

33
import (
4-
"fmt"
54
"os"
6-
"regexp"
75
"strings"
86

97
"github.com/coalaura/logger"
@@ -16,7 +14,7 @@ var log = logger.New().WithOptions(logger.Options{
1614
func main() {
1715
isAdmin, err := isElevated()
1816
if err != nil {
19-
log.FatalF("Unable to resolve elevation status: %v", err)
17+
log.Fatalf("Unable to resolve elevation status: %v\n", err)
2018

2119
os.Exit(1)
2220
}
@@ -33,69 +31,38 @@ func main() {
3331
log.Note("Already elevated.")
3432
}
3533

36-
cwd, _ := os.Getwd()
37-
38-
var (
39-
app string
40-
keep bool
41-
)
42-
43-
if args[0] == "su" {
44-
// Don't need to do anything here if we are already elevated
45-
if isAdmin {
46-
return
47-
}
48-
49-
// --keep or -k to not close current window
50-
for _, arg := range args[0:] {
51-
if arg == "-k" || arg == "--keep" {
52-
keep = true
53-
54-
break
55-
}
56-
}
57-
} else {
58-
app = strings.Join(args, " ")
59-
}
60-
61-
cmd, arg := build(app, cwd, isAdmin)
34+
terminal, shell, err := GetCurrentTerminalAndShell()
35+
if err != nil {
36+
log.Fatalf("Unable to resolve terminal/shell: %v\n", err)
6237

63-
if isAdmin {
64-
regular(cmd, arg, cwd)
65-
} else {
66-
elevate(cmd, arg, cwd)
38+
os.Exit(1)
6739
}
6840

69-
if app == "" && !keep {
70-
terminateParent()
71-
}
72-
}
41+
command := args[0]
42+
args = args[1:]
7343

74-
func build(app, cwd string, isAdmin bool) (string, string) {
75-
var cmd string
44+
isSu := command == "su"
45+
isWho := command == "who"
7646

77-
if isAdmin {
78-
cmd = app
79-
} else {
80-
if app == "" {
81-
cmd = fmt.Sprintf("%s /K \"cd /d %s\"", shellWithWT(), cwd)
82-
} else {
83-
rgx := regexp.MustCompile(`\s*;\s*`)
84-
app = rgx.ReplaceAllString(app, " && ")
47+
// special "sudo su" handling
48+
if isSu || isWho {
49+
command, args = terminal.Build(shell, "", nil)
50+
if len(command) == 0 {
51+
log.Fatalf("Unable to resolve terminal/shell: %v\n", err)
8552

86-
var sh string
53+
os.Exit(1)
54+
}
8755

88-
if isShell(app) {
89-
sh = shell()
90-
} else {
91-
sh = shellWithWT()
92-
}
56+
if isWho {
57+
log.Infof("%s %s\n", command, strings.Join(args, " "))
9358

94-
cmd = fmt.Sprintf("%s /K \"cd /d %s && %s && exit\"", sh, cwd, app)
59+
return
9560
}
61+
} else {
62+
command, args = terminal.Build(shell, command, args)
9663
}
9764

98-
parts := strings.Split(cmd, " ")
65+
cwd, _ := os.Getwd()
9966

100-
return parts[0], strings.Join(parts[1:], " ")
67+
elevate(command, strings.Join(args, " "), cwd)
10168
}

run.cmd

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
@echo off
22

3+
echo Building...
34
go build -o temp.exe
4-
temp.exe su --keep
5-
rm temp.exe
5+
6+
echo Running...
7+
temp.exe who
8+
9+
echo Done.
10+
del temp.exe

shell.go

Lines changed: 19 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -2,100 +2,39 @@ package main
22

33
import (
44
"fmt"
5-
"os"
6-
"os/exec"
75
"path/filepath"
86
"strings"
9-
"syscall"
10-
11-
"golang.org/x/sys/windows"
127
)
138

14-
const PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
15-
16-
func getParentProcessName() (string, error) {
17-
ppid := uint32(os.Getppid())
18-
19-
hProcess, err := windows.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, ppid)
20-
if err != nil {
21-
return "", err
22-
}
23-
24-
defer windows.CloseHandle(hProcess)
25-
26-
buffer := make([]uint16, windows.MAX_PATH)
27-
size := uint32(len(buffer))
28-
29-
err = windows.QueryFullProcessImageName(hProcess, 0, &buffer[0], &size)
30-
if err != nil {
31-
return "", err
32-
}
33-
34-
processName := syscall.UTF16ToString(buffer)
35-
36-
return filepath.Base(processName), nil
9+
type Shell struct {
10+
Path string
3711
}
3812

39-
func shell() string {
40-
parent, err := getParentProcessName()
41-
if err != nil {
42-
return "cmd.exe"
13+
func (s *Shell) Build(cmd string, args []string) []string {
14+
if cmd == "" {
15+
return []string{s.Path}
4316
}
4417

45-
path, err := exec.LookPath(parent)
46-
if err != nil {
47-
return "cmd.exe"
48-
}
49-
50-
return path
51-
}
52-
53-
func shellWithWT() string {
54-
sh := shell()
55-
56-
if wt, err := exec.LookPath("wt"); err == nil {
57-
if exists, rect := processExists("WindowsTerminal.exe"); exists {
58-
var (
59-
size string
60-
position string
61-
)
62-
63-
if w, h := terminalSize(); w != 0 {
64-
size = fmt.Sprintf(" --size %d,%d ", w, h)
65-
}
66-
67-
if rect != nil {
68-
position = fmt.Sprintf(" --pos %d,%d", rect.Left+10, rect.Top)
69-
}
70-
71-
sh = fmt.Sprintf("%s%s%s %s", wt, size, position, sh)
72-
}
73-
}
74-
75-
return sh
76-
}
18+
var command []string
7719

78-
func isShell(app string) bool {
79-
test := filepath.Base(app)
20+
exe := strings.ToLower(filepath.Base(s.Path))
8021

81-
if !strings.HasSuffix(app, ".exe") {
82-
test += ".exe"
22+
switch exe {
23+
case "cmd.exe":
24+
command = []string{s.Path, "/c"}
25+
case "powershell.exe", "pwsh.exe":
26+
command = []string{s.Path, "-NoProfile", "-Command"}
27+
case "bash.exe", "zsh.exe", "fish.exe", "git-bash.exe", "nu.exe":
28+
command = []string{s.Path, "-c"}
8329
}
8430

85-
shells := []string{
86-
"cmd.exe",
87-
"powershell.exe",
88-
"pwsh.exe",
89-
"wsl.exe",
90-
"bash.exe",
91-
"zsh.exe",
31+
if len(command) == 0 {
32+
return append([]string{cmd}, args...)
9233
}
9334

94-
for _, sh := range shells {
95-
if strings.EqualFold(sh, test) {
96-
return true
97-
}
35+
if len(args) > 0 {
36+
cmd = fmt.Sprintf("%s %s", cmd, strings.Join(args, " "))
9837
}
9938

100-
return false
39+
return append(command, cmd)
10140
}

0 commit comments

Comments
 (0)