| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- package main
- import (
- "bufio"
- "flag"
- "fmt"
- "io"
- "os"
- "os/exec"
- "strings"
- "syscall"
- )
- /*
- NOTES:
- * apt detected it wasn't connected to a TTY, so it said on stderr,
- WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
- * All packages are up to date. <- This isn't seen when using `apt-get update`, only apt.
- * We don't see the warning about "apt does not have a stable CLI interface." when using apt-get.
- * I can't seem to reproduce the output error (where apt is reading the database and showing
- the percentage increasing). See the output directory for am example program that has that type
- of output. (Which works fine. Why doesn't apt or apt-get work correctly?!)
- */
- type CheckResult struct {
- Lines int // Number of lines read.
- Upgradable bool // 'apt list --upgradable'
- // UpToDate bool // All packages are up to date.
- AutoRemove bool // Use 'apt autoremove' to remove it.
- }
- // Check the output for CheckResult conditions.
- func check_lines(r io.Reader, output chan CheckResult) {
- scanner := bufio.NewScanner(r)
- var err error
- var result CheckResult
- var line string
- for scanner.Scan() {
- line = scanner.Text()
- /*
- if line == "All packages are up to date." {
- result.UpToDate = true
- }
- */
- if strings.Contains(line, "apt list --upgradable") {
- result.Upgradable = true
- }
- if line == "Use 'apt autoremove' to remove it." || line == "Use 'sudo apt autoremove' to remove it" {
- result.AutoRemove = true
- }
- result.Lines += 1
- }
- err = scanner.Err()
- if err != nil {
- panic(err)
- }
- output <- result
- }
- // Run the given command, showing output, checking output.
- func run_check_command(command []string, debug_output bool) (CheckResult, error) {
- var result CheckResult
- cmd := exec.Command(command[0], command[1:]...)
- r, w, err := os.Pipe()
- if err != nil {
- fmt.Printf("Failed to create pipe: %s", err)
- return result, err
- }
- // Start output parser.
- output := make(chan CheckResult)
- go check_lines(r, output)
- // Connections
- // Connect output with stdout and parser.
- if debug_output {
- filename := fmt.Sprintf("debug-%s.txt", command[1])
- fp, err := os.Create(filename)
- if err != nil {
- fmt.Printf("Failed to create file [%s]: %s", filename, err)
- return result, err
- }
- defer fp.Close()
- cmd.Stdout = io.MultiWriter(os.Stdout, w, fp)
- fmt.Printf("See: %s for output.\n", filename)
- } else {
- cmd.Stdout = io.MultiWriter(os.Stdout, w)
- }
- cmd.Stderr = os.Stderr
- cmd.Stdin = os.Stdin
- // cmd.Env = append(os.Environ(), "DEBIAN_FRONTEND=noninteractive")
- // Run the process as root (We should be SETUID).
- cmd.SysProcAttr = &syscall.SysProcAttr{}
- cmd.SysProcAttr.Credential = &syscall.Credential{Uid: 0, Gid: 0}
- fmt.Println(strings.Repeat("=", 64))
- fmt.Println("Running", command)
- err = cmd.Run()
- fmt.Println(strings.Repeat("=", 50))
- if err != nil {
- fmt.Println("Did you forget to:")
- fmt.Println(" sudo chown root:root aptgrade")
- fmt.Println(" sudo chmod a+s aptgrade")
- fmt.Println("Command failed:", err)
- }
- // If I want check_lines to end, it needs the reader closed.
- // So, close the write end of the pipe.
- w.Close()
- // fmt.Println("Reading results from go routine:")
- value := <-output
- if !cmd.ProcessState.Success() {
- // Process exited with non-success code.
- fmt.Printf("Process %d exited with status code %d\n", cmd.ProcessState.Pid(), cmd.ProcessState.ExitCode())
- }
- // fmt.Printf("Result: %+v\n", value)
- return value, err
- }
- // const APT = "/usr/bin/apt"
- const APT = "/usr/bin/apt-get"
- func main() {
- // const APT = APT_CMD
- var show bool
- flag.BoolVar(&show, "show", false, "Show run results.")
- /*
- // Testing
- var APT string
- flag.StringVar(&APT, "apt", APT_CMD, "Apt command to use.")
- */
- var output bool
- flag.BoolVar(&output, "output", false, "Create debug output files.")
- flag.Parse()
- if os.Geteuid() != 0 {
- fmt.Println("Use `sudo install -g 0 -o 0 -m 4755 aptgrade ~/bin/aptgrade`")
- fmt.Println("This program requires SETUID.")
- os.Exit(2)
- }
- // Check for updates.
- result, err := run_check_command([]string{APT, "update"}, output)
- if err != nil {
- fmt.Printf("Error: %s\n", err)
- os.Exit(2)
- }
- if show {
- fmt.Printf("Result: {%+v}\n", result)
- }
- // Is everything up to date?
- if result.Upgradable {
- // Perform upgrade.
- result, err = run_check_command([]string{APT, "upgrade", "-y"}, output)
- if err != nil {
- fmt.Printf("Error: %s\n", err)
- os.Exit(2)
- }
- if show {
- fmt.Printf("Result: {%+v}\n", result)
- }
- // Is apt suggesting autoremove?
- if result.AutoRemove {
- // Perform autoremove.
- result, err = run_check_command([]string{APT, "autoremove", "-y"}, output)
- if err != nil {
- fmt.Printf("Error: %s\n", err)
- os.Exit(2)
- }
- if show {
- fmt.Printf("Result: {%+v}\n", result)
- }
- }
- }
- fmt.Println("Done")
- }
|