package main import ( "errors" "fmt" "io" "os" "os/exec" "path/filepath" "strings" "syscall" "time" ) func initialize_paths(paths Paths) error { if err := os.MkdirAll(paths.ConfigDir, 0700); err != nil { return fmt.Errorf("create configuration directory: %w", err) } if err := os.MkdirAll(paths.CacheDir, 0700); err != nil { return fmt.Errorf("create cache directory: %w", err) } logHandle, err := os.OpenFile(paths.LogFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) if err != nil { return fmt.Errorf("initialize launcher log: %w", err) } if err := logHandle.Chmod(0600); err != nil { logHandle.Close() return fmt.Errorf("protect launcher log: %w", err) } if err := logHandle.Close(); err != nil { return fmt.Errorf("initialize launcher log: %w", err) } return nil } func write_private_file(path string, writeContent func(io.Writer) error) error { directory := filepath.Dir(path) if err := os.MkdirAll(directory, 0700); err != nil { return err } temporary, err := os.CreateTemp(directory, filepath.Base(path)+".tmp-") if err != nil { return err } temporaryPath := temporary.Name() defer os.Remove(temporaryPath) if err := temporary.Chmod(0600); err != nil { temporary.Close() return err } if err := writeContent(temporary); err != nil { temporary.Close() return err } if err := temporary.Sync(); err != nil { temporary.Close() return err } if err := temporary.Close(); err != nil { return err } if err := os.Rename(temporaryPath, path); err != nil { return err } directoryHandle, err := os.Open(directory) if err == nil { _ = directoryHandle.Sync() _ = directoryHandle.Close() } return nil } func write_password_file(configDir, password string) (string, error) { if strings.ContainsAny(password, "\r\n") { return "", fmt.Errorf("password cannot contain line breaks") } if err := os.MkdirAll(configDir, 0700); err != nil { return "", err } file, err := os.CreateTemp(configDir, ".password-") if err != nil { return "", err } path := file.Name() failed := true defer func() { if failed { _ = os.Remove(path) } }() if err := file.Chmod(0600); err != nil { file.Close() return "", err } if _, err := io.WriteString(file, password); err != nil { file.Close() return "", err } if err := file.Sync(); err != nil { file.Close() return "", err } if err := file.Close(); err != nil { return "", err } failed = false return path, nil } func expand_user_path(path string) (string, error) { if path != "~" && !strings.HasPrefix(path, "~/") { return path, nil } homeDir, err := os.UserHomeDir() if err != nil { return "", err } if path == "~" { return homeDir, nil } return filepath.Join(homeDir, strings.TrimPrefix(path, "~/")), nil } func run_external(ui *TerminalUI, command *exec.Cmd) error { if err := command.Start(); err != nil { return err } done := make(chan error, 1) go func() { done <- command.Wait() }() select { case err := <-done: if receivedSignal := ui.take_termination(); receivedSignal != nil { return &terminalSignalError{signal: receivedSignal} } var exitError *exec.ExitError if errors.As(err, &exitError) { if status, ok := exitError.Sys().(syscall.WaitStatus); ok && status.Signaled() { return &terminalSignalError{signal: status.Signal()} } } return err case receivedSignal := <-ui.termination: _ = command.Process.Signal(receivedSignal) timer := time.NewTimer(3 * time.Second) defer timer.Stop() select { case <-done: case <-timer.C: _ = command.Process.Kill() <-done } return &terminalSignalError{signal: receivedSignal} } }