aquaproj / aqua

Declarative CLI Version manager written in Go. Support Lazy Install, Registry, and continuous update with Renovate. CLI version is switched seamlessly
https://aquaproj.github.io
879 stars 39 forks source link

Change `$0` when executing tools #3130

Closed suzuki-shunsuke closed 1 month ago

suzuki-shunsuke commented 1 month ago

Feature Overview

Enable us to change $0 when executing tools like exec's -a option.

exec -a assumego granted "$@" # Execute granted as assumego

Why is the feature needed?

Some tools change their behavior by $0.

For example, granted changes the behavior based on args[0].

https://github.com/common-fate/granted/blob/e8de3ec7d62d543062d8be802b27abb3d8fac429/cmd/granted/main.go#L37-L44

    // Use a single binary to keep keychain ACLs simple, swapping behavior via argv[0]
    var app *cli.App
    switch filepath.Base(os.Args[0]) {
    case "assumego", "assumego.exe", "dassumego", "dassumego.exe":
        app = assume.GetCliApp()
    default:
        app = granted.GetCliApp()
    }

Workaround

Create shell scripts like this.

https://github.com/aquaproj/aqua-registry/issues/27177#issuecomment-2378009669

~/bin/assumego

#!/usr/bin/env bash

set -eu

granted=$(aqua which granted) # Get the absolute path of granted
exec -a assumego "$granted" "$@" # Execute granted changing args[0] to `assumego` by `exec -a assumego`

Example Code

registry.yaml

files:
  - name: assumego
    src: granted
    arg0: assumego

Note

No response

suzuki-shunsuke commented 1 month ago

aqua executes commands by exec.Run and unix.Exec.

https://github.com/aquaproj/aqua/blob/57c7332384f48ee2e56a144bdb9806c7faf8fdc6/pkg/osexec/exec.go#L24

https://github.com/aquaproj/aqua/blob/57c7332384f48ee2e56a144bdb9806c7faf8fdc6/pkg/osexec/xsys.go#L14

I thought we can change arg0 by changing exec.Cmd.Args[0] and unix.Exec's argv[0], but it seems not to work.

#!/usr/bin/env bash

set -eu

echo "0: $0"
echo "@: $@"

echo foo

os/exec

package main

import (
    "log"
    "os"
    "os/exec"
)

func main() {
    if err := core(); err != nil {
        log.Fatal(err)
    }
}

func core() error {
    cmd := exec.Command("./foo", "a", "b")
    cmd.Args[0] = "bar"
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    if err := cmd.Run(); err != nil {
        return err
    }
    return nil
}
$ go run main.go
0: ./foo
@: a b
foo

unix

package main

import (
    "log"
    "os"

    "golang.org/x/sys/unix"
)

func main() {
    if err := core(); err != nil {
        log.Fatal(err)
    }
}

func core() error {
    return unix.Exec("../foo", []string{"bar", "a", "b"}, os.Environ()) //nolint:wrapcheck
}
$ go run main.go
0: ../foo
@: a b
foo
suzuki-shunsuke commented 1 month ago

I asked some help in Stackoverflow.

jpeeler commented 1 month ago

Looks like to me if you set cmd.Path (in addition to cmd.Args[0] to avoid confusion) it works as expected?

suzuki-shunsuke commented 1 month ago

Looks like to me if you set cmd.Path (in addition to cmd.Args[0] to avoid confusion) it works as expected?

Could you show me any example code?

jpeeler commented 1 month ago
package main

import (
    "fmt"
    "log"
    "os"
    "os/exec"
)

func main() {
    cmd := exec.Command("ls", "-l")
    cmd.Path = "/usr/bin/echo"
    cmd.Args[0] = "echo"
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    if err := cmd.Run(); err != nil {
        log.Fatal(err)
    }
    fmt.Printf("DEBUG: %#v\n", cmd.Args)
}
❯ go run main.go
-l
DEBUG: []string{"echo", "-l"}
suzuki-shunsuke commented 1 month ago

It seems ls isn't executed.

$ go run main.go
2024/09/28 07:47:51 fork/exec /usr/bin/echo: no such file or directory
exit status 1

/usr/bin/echo was executed.

jpeeler commented 1 month ago

Change Path to /usr/bin/ls then. Sorry, wasn't sure which direction was necessary here. Or am I still misunderstanding? Ok yeah, I give up.

suzuki-shunsuke commented 1 month ago

We want to change $0 when executing tools, but I'm not sure the way in Go, so I'm writing the simple code to look into it. In the above code, I try to execute the shell script foo changing $0 to bar. The script foo outputs $0, so I expect bar is outputted, but it doesn't work as expected.

BTW, I got an answer in Stackoverflow, and changing Cmd.Args[0] worked if the command is a single binary written in Go, not a bash script. https://stackoverflow.com/questions/79029456/how-can-we-execute-a-command-changing-the-command-name-argv0-in-go?noredirect=1#comment139353340_79029456 https://gist.github.com/suzuki-shunsuke/6429c1f054ae06946e5ca6e923002592

I'm still not sure if we can change $0 of a shell script in Go.

suzuki-shunsuke commented 1 month ago

Maybe we should consider creating links same as granted's official installation guide if it's difficult to change $0 in Go.

e.g.

files:
  - name: granted
  - name: assume
  - name: assumego
    src: granted
    link: assumego # Create a link to `src` in the same directory with `granted` and `aqua exec -- assumego` executes this link
jpeeler commented 1 month ago

Yeah I know this is an edge case, but perhaps supporting symlink functionality could be useful for other scenarios too.

suzuki-shunsuke commented 1 month ago

@jpeeler Could you try aqua v2.36.0-1 on macOS?

mkdir test
cd test
aqua upa v2.36.0-1
echo "
registries:
- type: standard
  ref: 2e36190cfe286029af7a2f9eb29bdd0e74a9f62c
packages:
- name: common-fate/granted@v0.34.1
" > aqua.yaml
aqua rm granted
aqua i
assume

I expect assume would work.

jpeeler commented 1 month ago

Yes, looks good to me!

suzuki-shunsuke commented 1 month ago

Thank you!

aqua v2.36.0 and aqua-registry v4.227.0 is out 🎉

https://github.com/aquaproj/aqua/releases/tag/v2.36.0 https://github.com/aquaproj/aqua-registry/releases/tag/v4.227.0