fmsg: implement suspend in writer
This removes the requirement to call fmsg.Exit on every exit path, and enables direct use of the "log" package. However, fmsg.BeforeExit is still encouraged when possible to catch exit on suspended output. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
parent
33a4ab11c2
commit
e599b5583d
@ -2,10 +2,10 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/dbus"
|
"git.gensokyo.uk/security/fortify/dbus"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/system"
|
"git.gensokyo.uk/security/fortify/internal/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -63,18 +63,18 @@ func loadBundleInfo(name string, beforeFail func()) *bundleInfo {
|
|||||||
bundle := new(bundleInfo)
|
bundle := new(bundleInfo)
|
||||||
if f, err := os.Open(name); err != nil {
|
if f, err := os.Open(name); err != nil {
|
||||||
beforeFail()
|
beforeFail()
|
||||||
fmsg.Fatalf("cannot open bundle: %v", err)
|
log.Fatalf("cannot open bundle: %v", err)
|
||||||
} else if err = json.NewDecoder(f).Decode(&bundle); err != nil {
|
} else if err = json.NewDecoder(f).Decode(&bundle); err != nil {
|
||||||
beforeFail()
|
beforeFail()
|
||||||
fmsg.Fatalf("cannot parse bundle metadata: %v", err)
|
log.Fatalf("cannot parse bundle metadata: %v", err)
|
||||||
} else if err = f.Close(); err != nil {
|
} else if err = f.Close(); err != nil {
|
||||||
fmsg.Printf("cannot close bundle metadata: %v", err)
|
log.Printf("cannot close bundle metadata: %v", err)
|
||||||
// not fatal
|
// not fatal
|
||||||
}
|
}
|
||||||
|
|
||||||
if bundle.ID == "" {
|
if bundle.ID == "" {
|
||||||
beforeFail()
|
beforeFail()
|
||||||
fmsg.Fatal("application identifier must not be empty")
|
log.Fatal("application identifier must not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
return bundle
|
return bundle
|
||||||
@ -82,7 +82,7 @@ func loadBundleInfo(name string, beforeFail func()) *bundleInfo {
|
|||||||
|
|
||||||
func formatHostname(name string) string {
|
func formatHostname(name string) string {
|
||||||
if h, err := os.Hostname(); err != nil {
|
if h, err := os.Hostname(); err != nil {
|
||||||
fmsg.Printf("cannot get hostname: %v", err)
|
log.Printf("cannot get hostname: %v", err)
|
||||||
return "fortify-" + name
|
return "fortify-" + name
|
||||||
} else {
|
} else {
|
||||||
return h + "-" + name
|
return h + "-" + name
|
||||||
|
@ -3,10 +3,12 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -25,12 +27,12 @@ func actionInstall(args []string) {
|
|||||||
args = set.Args()
|
args = set.Args()
|
||||||
|
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
fmsg.Fatal("invalid argument")
|
log.Fatal("invalid argument")
|
||||||
}
|
}
|
||||||
pkgPath := args[0]
|
pkgPath := args[0]
|
||||||
if !path.IsAbs(pkgPath) {
|
if !path.IsAbs(pkgPath) {
|
||||||
if dir, err := os.Getwd(); err != nil {
|
if dir, err := os.Getwd(); err != nil {
|
||||||
fmsg.Fatalf("cannot get current directory: %v", err)
|
log.Fatalf("cannot get current directory: %v", err)
|
||||||
} else {
|
} else {
|
||||||
pkgPath = path.Join(dir, pkgPath)
|
pkgPath = path.Join(dir, pkgPath)
|
||||||
}
|
}
|
||||||
@ -54,7 +56,7 @@ func actionInstall(args []string) {
|
|||||||
|
|
||||||
var workDir string
|
var workDir string
|
||||||
if p, err := os.MkdirTemp("", "fpkg.*"); err != nil {
|
if p, err := os.MkdirTemp("", "fpkg.*"); err != nil {
|
||||||
fmsg.Fatalf("cannot create temporary directory: %v", err)
|
log.Fatalf("cannot create temporary directory: %v", err)
|
||||||
} else {
|
} else {
|
||||||
workDir = p
|
workDir = p
|
||||||
}
|
}
|
||||||
@ -78,19 +80,17 @@ func actionInstall(args []string) {
|
|||||||
if s, err := os.Stat(pathSet.metaPath); err != nil {
|
if s, err := os.Stat(pathSet.metaPath); err != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
cleanup()
|
cleanup()
|
||||||
fmsg.Fatalf("cannot access %q: %v", pathSet.metaPath, err)
|
log.Fatalf("cannot access %q: %v", pathSet.metaPath, err)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
// did not modify app, clean installation condition met later
|
// did not modify app, clean installation condition met later
|
||||||
} else if s.IsDir() {
|
} else if s.IsDir() {
|
||||||
cleanup()
|
cleanup()
|
||||||
fmsg.Fatalf("metadata path %q is not a file", pathSet.metaPath)
|
log.Fatalf("metadata path %q is not a file", pathSet.metaPath)
|
||||||
panic("unreachable")
|
|
||||||
} else {
|
} else {
|
||||||
app = loadBundleInfo(pathSet.metaPath, cleanup)
|
app = loadBundleInfo(pathSet.metaPath, cleanup)
|
||||||
if app.ID != bundle.ID {
|
if app.ID != bundle.ID {
|
||||||
cleanup()
|
cleanup()
|
||||||
fmsg.Fatalf("app %q claims to have identifier %q", bundle.ID, app.ID)
|
log.Fatalf("app %q claims to have identifier %q", bundle.ID, app.ID)
|
||||||
}
|
}
|
||||||
// sec: should verify credentials
|
// sec: should verify credentials
|
||||||
}
|
}
|
||||||
@ -102,21 +102,20 @@ func actionInstall(args []string) {
|
|||||||
app.Launcher == bundle.Launcher &&
|
app.Launcher == bundle.Launcher &&
|
||||||
app.ActivationPackage == bundle.ActivationPackage {
|
app.ActivationPackage == bundle.ActivationPackage {
|
||||||
cleanup()
|
cleanup()
|
||||||
fmsg.Printf("package %q is identical to local application %q", pkgPath, app.ID)
|
log.Printf("package %q is identical to local application %q", pkgPath, app.ID)
|
||||||
fmsg.Exit(0)
|
internal.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppID determines uid
|
// AppID determines uid
|
||||||
if app.AppID != bundle.AppID {
|
if app.AppID != bundle.AppID {
|
||||||
cleanup()
|
cleanup()
|
||||||
fmsg.Fatalf("package %q app id %d differs from installed %d", pkgPath, bundle.AppID, app.AppID)
|
log.Fatalf("package %q app id %d differs from installed %d", pkgPath, bundle.AppID, app.AppID)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sec: should compare version string
|
// sec: should compare version string
|
||||||
fmsg.VPrintf("installing application %q version %q over local %q", bundle.ID, bundle.Version, app.Version)
|
fmsg.Verbosef("installing application %q version %q over local %q", bundle.ID, bundle.Version, app.Version)
|
||||||
} else {
|
} else {
|
||||||
fmsg.VPrintf("application %q clean installation", bundle.ID)
|
fmsg.Verbosef("application %q clean installation", bundle.ID)
|
||||||
// sec: should install credentials
|
// sec: should install credentials
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,21 +173,18 @@ func actionInstall(args []string) {
|
|||||||
// serialise metadata to ensure consistency
|
// serialise metadata to ensure consistency
|
||||||
if f, err := os.OpenFile(pathSet.metaPath+"~", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err != nil {
|
if f, err := os.OpenFile(pathSet.metaPath+"~", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err != nil {
|
||||||
cleanup()
|
cleanup()
|
||||||
fmsg.Fatalf("cannot create metadata file: %v", err)
|
log.Fatalf("cannot create metadata file: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
} else if err = json.NewEncoder(f).Encode(bundle); err != nil {
|
} else if err = json.NewEncoder(f).Encode(bundle); err != nil {
|
||||||
cleanup()
|
cleanup()
|
||||||
fmsg.Fatalf("cannot write metadata: %v", err)
|
log.Fatalf("cannot write metadata: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
} else if err = f.Close(); err != nil {
|
} else if err = f.Close(); err != nil {
|
||||||
fmsg.Printf("cannot close metadata file: %v", err)
|
log.Printf("cannot close metadata file: %v", err)
|
||||||
// not fatal
|
// not fatal
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Rename(pathSet.metaPath+"~", pathSet.metaPath); err != nil {
|
if err := os.Rename(pathSet.metaPath+"~", pathSet.metaPath); err != nil {
|
||||||
cleanup()
|
cleanup()
|
||||||
fmsg.Fatalf("cannot rename metadata file: %v", err)
|
log.Fatalf("cannot rename metadata file: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup()
|
cleanup()
|
||||||
|
@ -2,8 +2,10 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -11,7 +13,7 @@ const shell = "/run/current-system/sw/bin/bash"
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if err := os.Setenv("SHELL", shell); err != nil {
|
if err := os.Setenv("SHELL", shell); err != nil {
|
||||||
fmsg.Fatalf("cannot set $SHELL: %v", err)
|
log.Fatalf("cannot set $SHELL: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,14 +26,14 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmsg.SetPrefix("fpkg")
|
fmsg.Prepare("fpkg")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
fmsg.SetVerbose(flagVerbose)
|
fmsg.Store(flagVerbose)
|
||||||
|
|
||||||
args := flag.Args()
|
args := flag.Args()
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
fmsg.Fatal("invalid argument")
|
log.Fatal("invalid argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch args[0] {
|
switch args[0] {
|
||||||
@ -41,8 +43,8 @@ func main() {
|
|||||||
actionStart(args[1:])
|
actionStart(args[1:])
|
||||||
|
|
||||||
default:
|
default:
|
||||||
fmsg.Fatal("invalid argument")
|
log.Fatal("invalid argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmsg.Exit(0)
|
internal.Exit(0)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
@ -25,8 +26,8 @@ func init() {
|
|||||||
|
|
||||||
func lookPath(file string) string {
|
func lookPath(file string) string {
|
||||||
if p, err := exec.LookPath(file); err != nil {
|
if p, err := exec.LookPath(file); err != nil {
|
||||||
fmsg.Fatalf("%s: command not found", file)
|
log.Fatalf("%s: command not found", file)
|
||||||
panic("unreachable")
|
return ""
|
||||||
} else {
|
} else {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
@ -35,15 +36,14 @@ func lookPath(file string) string {
|
|||||||
var beforeRunFail = new(atomic.Pointer[func()])
|
var beforeRunFail = new(atomic.Pointer[func()])
|
||||||
|
|
||||||
func mustRun(name string, arg ...string) {
|
func mustRun(name string, arg ...string) {
|
||||||
fmsg.VPrintf("spawning process: %q %q", name, arg)
|
fmsg.Verbosef("spawning process: %q %q", name, arg)
|
||||||
cmd := exec.Command(name, arg...)
|
cmd := exec.Command(name, arg...)
|
||||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
if f := beforeRunFail.Swap(nil); f != nil {
|
if f := beforeRunFail.Swap(nil); f != nil {
|
||||||
(*f)()
|
(*f)()
|
||||||
}
|
}
|
||||||
fmsg.Fatalf("%s: %v", name, err)
|
log.Fatalf("%s: %v", name, err)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
@ -25,14 +26,12 @@ func fortifyApp(config *fst.Config, beforeFail func()) {
|
|||||||
)
|
)
|
||||||
if p, ok := internal.Path(Fmain); !ok {
|
if p, ok := internal.Path(Fmain); !ok {
|
||||||
beforeFail()
|
beforeFail()
|
||||||
fmsg.Fatal("invalid fortify path, this copy of fpkg is not compiled correctly")
|
log.Fatal("invalid fortify path, this copy of fpkg is not compiled correctly")
|
||||||
panic("unreachable")
|
|
||||||
} else if r, w, err := os.Pipe(); err != nil {
|
} else if r, w, err := os.Pipe(); err != nil {
|
||||||
beforeFail()
|
beforeFail()
|
||||||
fmsg.Fatalf("cannot pipe: %v", err)
|
log.Fatalf("cannot pipe: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
} else {
|
} else {
|
||||||
if fmsg.Verbose() {
|
if fmsg.Load() {
|
||||||
cmd = exec.Command(p, "-v", "app", "3")
|
cmd = exec.Command(p, "-v", "app", "3")
|
||||||
} else {
|
} else {
|
||||||
cmd = exec.Command(p, "app", "3")
|
cmd = exec.Command(p, "app", "3")
|
||||||
@ -45,26 +44,22 @@ func fortifyApp(config *fst.Config, beforeFail func()) {
|
|||||||
go func() {
|
go func() {
|
||||||
if err := json.NewEncoder(st).Encode(config); err != nil {
|
if err := json.NewEncoder(st).Encode(config); err != nil {
|
||||||
beforeFail()
|
beforeFail()
|
||||||
fmsg.Fatalf("cannot send configuration: %v", err)
|
log.Fatalf("cannot send configuration: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
beforeFail()
|
beforeFail()
|
||||||
fmsg.Fatalf("cannot start fortify: %v", err)
|
log.Fatalf("cannot start fortify: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
if err := cmd.Wait(); err != nil {
|
if err := cmd.Wait(); err != nil {
|
||||||
var exitError *exec.ExitError
|
var exitError *exec.ExitError
|
||||||
if errors.As(err, &exitError) {
|
if errors.As(err, &exitError) {
|
||||||
beforeFail()
|
beforeFail()
|
||||||
fmsg.Exit(exitError.ExitCode())
|
internal.Exit(exitError.ExitCode())
|
||||||
panic("unreachable")
|
|
||||||
} else {
|
} else {
|
||||||
beforeFail()
|
beforeFail()
|
||||||
fmsg.Fatalf("cannot wait: %v", err)
|
log.Fatalf("cannot wait: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,12 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
"log"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
func actionStart(args []string) {
|
func actionStart(args []string) {
|
||||||
@ -26,7 +27,7 @@ func actionStart(args []string) {
|
|||||||
args = set.Args()
|
args = set.Args()
|
||||||
|
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
fmsg.Fatal("invalid argument")
|
log.Fatal("invalid argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -37,7 +38,7 @@ func actionStart(args []string) {
|
|||||||
pathSet := pathSetByApp(id)
|
pathSet := pathSetByApp(id)
|
||||||
app := loadBundleInfo(pathSet.metaPath, func() {})
|
app := loadBundleInfo(pathSet.metaPath, func() {})
|
||||||
if app.ID != id {
|
if app.ID != id {
|
||||||
fmsg.Fatalf("app %q claims to have identifier %q", id, app.ID)
|
log.Fatalf("app %q claims to have identifier %q", id, app.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -144,7 +145,7 @@ func actionStart(args []string) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
fortifyApp(config, func() {})
|
fortifyApp(config, func() {})
|
||||||
fmsg.Exit(0)
|
internal.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendGPUFilesystem(config *fst.Config) {
|
func appendGPUFilesystem(config *fst.Config) {
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
func withNixDaemon(
|
func withNixDaemon(
|
||||||
@ -95,7 +95,7 @@ func fortifyAppDropShell(config *fst.Config, dropShell bool, beforeFail func())
|
|||||||
config.Command = []string{shell, "-l"}
|
config.Command = []string{shell, "-l"}
|
||||||
fortifyApp(config, beforeFail)
|
fortifyApp(config, beforeFail)
|
||||||
beforeFail()
|
beforeFail()
|
||||||
fmsg.Exit(0)
|
internal.Exit(0)
|
||||||
}
|
}
|
||||||
fortifyApp(config, beforeFail)
|
fortifyApp(config, beforeFail)
|
||||||
}
|
}
|
||||||
|
15
error.go
15
error.go
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"log"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/app"
|
"git.gensokyo.uk/security/fortify/internal/app"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
@ -10,13 +11,13 @@ import (
|
|||||||
func logWaitError(err error) {
|
func logWaitError(err error) {
|
||||||
var e *fmsg.BaseError
|
var e *fmsg.BaseError
|
||||||
if !fmsg.AsBaseError(err, &e) {
|
if !fmsg.AsBaseError(err, &e) {
|
||||||
fmsg.Println("wait failed:", err)
|
log.Println("wait failed:", err)
|
||||||
} else {
|
} else {
|
||||||
// Wait only returns either *app.ProcessError or *app.StateStoreError wrapped in a *app.BaseError
|
// Wait only returns either *app.ProcessError or *app.StateStoreError wrapped in a *app.BaseError
|
||||||
var se *app.StateStoreError
|
var se *app.StateStoreError
|
||||||
if !errors.As(err, &se) {
|
if !errors.As(err, &se) {
|
||||||
// does not need special handling
|
// does not need special handling
|
||||||
fmsg.Print(e.Message())
|
log.Print(e.Message())
|
||||||
} else {
|
} else {
|
||||||
// inner error are either unwrapped store errors
|
// inner error are either unwrapped store errors
|
||||||
// or joined errors returned by *appSealTx revert
|
// or joined errors returned by *appSealTx revert
|
||||||
@ -24,7 +25,7 @@ func logWaitError(err error) {
|
|||||||
var ej app.RevertCompoundError
|
var ej app.RevertCompoundError
|
||||||
if !errors.As(se.InnerErr, &ej) {
|
if !errors.As(se.InnerErr, &ej) {
|
||||||
// does not require special handling
|
// does not require special handling
|
||||||
fmsg.Print(e.Message())
|
log.Print(e.Message())
|
||||||
} else {
|
} else {
|
||||||
errs := ej.Unwrap()
|
errs := ej.Unwrap()
|
||||||
|
|
||||||
@ -33,10 +34,10 @@ func logWaitError(err error) {
|
|||||||
var eb *fmsg.BaseError
|
var eb *fmsg.BaseError
|
||||||
if !errors.As(ei, &eb) {
|
if !errors.As(ei, &eb) {
|
||||||
// unreachable
|
// unreachable
|
||||||
fmsg.Println("invalid error type returned by revert:", ei)
|
log.Println("invalid error type returned by revert:", ei)
|
||||||
} else {
|
} else {
|
||||||
// print inner *app.BaseError message
|
// print inner *app.BaseError message
|
||||||
fmsg.Print(eb.Message())
|
log.Print(eb.Message())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,8 +49,8 @@ func logBaseError(err error, message string) {
|
|||||||
var e *fmsg.BaseError
|
var e *fmsg.BaseError
|
||||||
|
|
||||||
if fmsg.AsBaseError(err, &e) {
|
if fmsg.AsBaseError(err, &e) {
|
||||||
fmsg.Print(e.Message())
|
log.Print(e.Message())
|
||||||
} else {
|
} else {
|
||||||
fmsg.Println(message, err)
|
log.Println(message, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if s.Syscall == nil {
|
if s.Syscall == nil {
|
||||||
fmsg.VPrintln("syscall filter not configured, PROCEED WITH CAUTION")
|
fmsg.Verbose("syscall filter not configured, PROCEED WITH CAUTION")
|
||||||
}
|
}
|
||||||
|
|
||||||
var uid int
|
var uid int
|
||||||
@ -121,11 +121,11 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) {
|
|||||||
// get parent dir of socket
|
// get parent dir of socket
|
||||||
dir := path.Dir(pair[1])
|
dir := path.Dir(pair[1])
|
||||||
if dir == "." || dir == "/" {
|
if dir == "." || dir == "/" {
|
||||||
fmsg.VPrintf("dbus socket %q is in an unusual location", pair[1])
|
fmsg.Verbosef("dbus socket %q is in an unusual location", pair[1])
|
||||||
}
|
}
|
||||||
hidePaths = append(hidePaths, dir)
|
hidePaths = append(hidePaths, dir)
|
||||||
} else {
|
} else {
|
||||||
fmsg.VPrintf("dbus socket %q is not absolute", pair[1])
|
fmsg.Verbosef("dbus socket %q is not absolute", pair[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -169,7 +169,7 @@ func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
} else if ok {
|
} else if ok {
|
||||||
hidePathMatch[i] = true
|
hidePathMatch[i] = true
|
||||||
fmsg.VPrintf("hiding paths from %q", c.Src)
|
fmsg.Verbosef("hiding paths from %q", c.Src)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,7 +221,7 @@ func evalSymlinks(os linux.System, v *string) error {
|
|||||||
if !errors.Is(err, fs.ErrNotExist) {
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmsg.VPrintf("path %q does not yet exist", *v)
|
fmsg.Verbosef("path %q does not yet exist", *v)
|
||||||
} else {
|
} else {
|
||||||
*v = p
|
*v = p
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package bwrap_test
|
package bwrap_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
@ -8,11 +9,10 @@ import (
|
|||||||
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
||||||
"git.gensokyo.uk/security/fortify/helper/proc"
|
"git.gensokyo.uk/security/fortify/helper/proc"
|
||||||
"git.gensokyo.uk/security/fortify/helper/seccomp"
|
"git.gensokyo.uk/security/fortify/helper/seccomp"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConfig_Args(t *testing.T) {
|
func TestConfig_Args(t *testing.T) {
|
||||||
seccomp.CPrintln = fmsg.Println
|
seccomp.CPrintln = log.Println
|
||||||
t.Cleanup(func() { seccomp.CPrintln = nil })
|
t.Cleanup(func() { seccomp.CPrintln = nil })
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
|
@ -4,12 +4,12 @@ import (
|
|||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"slices"
|
"slices"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/helper/seccomp"
|
"git.gensokyo.uk/security/fortify/helper/seccomp"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestExport(t *testing.T) {
|
func TestExport(t *testing.T) {
|
||||||
@ -79,7 +79,7 @@ func TestExport(t *testing.T) {
|
|||||||
buf := make([]byte, 8)
|
buf := make([]byte, 8)
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
seccomp.CPrintln = fmsg.Println
|
seccomp.CPrintln = log.Println
|
||||||
t.Cleanup(func() { seccomp.CPrintln = nil })
|
t.Cleanup(func() { seccomp.CPrintln = nil })
|
||||||
|
|
||||||
e := seccomp.New(tc.opts)
|
e := seccomp.New(tc.opts)
|
||||||
|
@ -14,7 +14,7 @@ import (
|
|||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
||||||
"git.gensokyo.uk/security/fortify/helper/proc"
|
"git.gensokyo.uk/security/fortify/helper/proc"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InternalChildStub is an internal function but exported because it is cross-package;
|
// InternalChildStub is an internal function but exported because it is cross-package;
|
||||||
@ -40,7 +40,7 @@ func InternalChildStub() {
|
|||||||
genericStub(flagRestoreFiles(4, ap, sp))
|
genericStub(flagRestoreFiles(4, ap, sp))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmsg.Exit(0)
|
internal.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// InternalReplaceExecCommand is an internal function but exported because it is cross-package;
|
// InternalReplaceExecCommand is an internal function but exported because it is cross-package;
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
// used by the parent process
|
// used by the parent process
|
||||||
@ -13,6 +13,6 @@ import (
|
|||||||
func TryArgv0() {
|
func TryArgv0() {
|
||||||
if len(os.Args) > 0 && path.Base(os.Args[0]) == "init" {
|
if len(os.Args) > 0 && path.Base(os.Args[0]) == "init" {
|
||||||
Main()
|
Main()
|
||||||
fmsg.Exit(0)
|
internal.Exit(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package init0
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@ -24,17 +25,15 @@ const (
|
|||||||
func Main() {
|
func Main() {
|
||||||
// sharing stdout with shim
|
// sharing stdout with shim
|
||||||
// USE WITH CAUTION
|
// USE WITH CAUTION
|
||||||
fmsg.SetPrefix("init")
|
fmsg.Prepare("init")
|
||||||
|
|
||||||
// setting this prevents ptrace
|
// setting this prevents ptrace
|
||||||
if err := internal.PR_SET_DUMPABLE__SUID_DUMP_DISABLE(); err != nil {
|
if err := internal.PR_SET_DUMPABLE__SUID_DUMP_DISABLE(); err != nil {
|
||||||
fmsg.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
|
log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.Getpid() != 1 {
|
if os.Getpid() != 1 {
|
||||||
fmsg.Fatal("this process must run as pid 1")
|
log.Fatal("this process must run as pid 1")
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// receive setup payload
|
// receive setup payload
|
||||||
@ -44,30 +43,29 @@ func Main() {
|
|||||||
)
|
)
|
||||||
if f, err := proc.Receive(Env, &payload); err != nil {
|
if f, err := proc.Receive(Env, &payload); err != nil {
|
||||||
if errors.Is(err, proc.ErrInvalid) {
|
if errors.Is(err, proc.ErrInvalid) {
|
||||||
fmsg.Fatal("invalid config descriptor")
|
log.Fatal("invalid config descriptor")
|
||||||
}
|
}
|
||||||
if errors.Is(err, proc.ErrNotSet) {
|
if errors.Is(err, proc.ErrNotSet) {
|
||||||
fmsg.Fatal("FORTIFY_INIT not set")
|
log.Fatal("FORTIFY_INIT not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmsg.Fatalf("cannot decode init setup payload: %v", err)
|
log.Fatalf("cannot decode init setup payload: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
} else {
|
} else {
|
||||||
fmsg.SetVerbose(payload.Verbose)
|
fmsg.Store(payload.Verbose)
|
||||||
closeSetup = f
|
closeSetup = f
|
||||||
|
|
||||||
// child does not need to see this
|
// child does not need to see this
|
||||||
if err = os.Unsetenv(Env); err != nil {
|
if err = os.Unsetenv(Env); err != nil {
|
||||||
fmsg.Printf("cannot unset %s: %v", Env, err)
|
log.Printf("cannot unset %s: %v", Env, err)
|
||||||
// not fatal
|
// not fatal
|
||||||
} else {
|
} else {
|
||||||
fmsg.VPrintln("received configuration")
|
fmsg.Verbose("received configuration")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// die with parent
|
// die with parent
|
||||||
if err := internal.PR_SET_PDEATHSIG__SIGKILL(); err != nil {
|
if err := internal.PR_SET_PDEATHSIG__SIGKILL(); err != nil {
|
||||||
fmsg.Fatalf("prctl(PR_SET_PDEATHSIG, SIGKILL): %v", err)
|
log.Fatalf("prctl(PR_SET_PDEATHSIG, SIGKILL): %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(payload.Argv0)
|
cmd := exec.Command(payload.Argv0)
|
||||||
@ -76,13 +74,13 @@ func Main() {
|
|||||||
cmd.Env = os.Environ()
|
cmd.Env = os.Environ()
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
fmsg.Fatalf("cannot start %q: %v", payload.Argv0, err)
|
log.Fatalf("cannot start %q: %v", payload.Argv0, err)
|
||||||
}
|
}
|
||||||
fmsg.Suspend()
|
fmsg.Suspend()
|
||||||
|
|
||||||
// close setup pipe as setup is now complete
|
// close setup pipe as setup is now complete
|
||||||
if err := closeSetup(); err != nil {
|
if err := closeSetup(); err != nil {
|
||||||
fmsg.Println("cannot close setup pipe:", err)
|
log.Println("cannot close setup pipe:", err)
|
||||||
// not fatal
|
// not fatal
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +117,7 @@ func Main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !errors.Is(err, syscall.ECHILD) {
|
if !errors.Is(err, syscall.ECHILD) {
|
||||||
fmsg.Println("unexpected wait4 response:", err)
|
log.Println("unexpected wait4 response:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
close(done)
|
close(done)
|
||||||
@ -132,9 +130,12 @@ func Main() {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case s := <-sig:
|
case s := <-sig:
|
||||||
fmsg.VPrintln("received", s.String())
|
if fmsg.Resume() {
|
||||||
fmsg.Resume() // output could still be withheld at this point, so resume is called
|
fmsg.Verbosef("terminating on %s after process start", s.String())
|
||||||
fmsg.Exit(0)
|
} else {
|
||||||
|
fmsg.Verbosef("terminating on %s", s.String())
|
||||||
|
}
|
||||||
|
internal.Exit(0)
|
||||||
case w := <-info:
|
case w := <-info:
|
||||||
if w.wpid == cmd.Process.Pid {
|
if w.wpid == cmd.Process.Pid {
|
||||||
// initial process exited, output is most likely available again
|
// initial process exited, output is most likely available again
|
||||||
@ -155,10 +156,10 @@ func Main() {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
case <-done:
|
case <-done:
|
||||||
fmsg.Exit(r)
|
internal.Exit(r)
|
||||||
case <-timeout:
|
case <-timeout:
|
||||||
fmsg.Println("timeout exceeded waiting for lingering processes")
|
log.Println("timeout exceeded waiting for lingering processes")
|
||||||
fmsg.Exit(r)
|
internal.Exit(r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -191,7 +191,7 @@ func (a *app) Seal(config *fst.Config) error {
|
|||||||
|
|
||||||
// map sandbox config to bwrap
|
// map sandbox config to bwrap
|
||||||
if config.Confinement.Sandbox == nil {
|
if config.Confinement.Sandbox == nil {
|
||||||
fmsg.VPrintln("sandbox configuration not supplied, PROCEED WITH CAUTION")
|
fmsg.Verbose("sandbox configuration not supplied, PROCEED WITH CAUTION")
|
||||||
|
|
||||||
// permissive defaults
|
// permissive defaults
|
||||||
conf := &fst.SandboxConfig{
|
conf := &fst.SandboxConfig{
|
||||||
@ -264,7 +264,7 @@ func (a *app) Seal(config *fst.Config) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// verbose log seal information
|
// verbose log seal information
|
||||||
fmsg.VPrintf("created application seal for uid %s (%s) groups: %v, command: %s",
|
fmsg.Verbosef("created application seal for uid %s (%s) groups: %v, command: %s",
|
||||||
seal.sys.user.us, seal.sys.user.username, config.Confinement.Groups, config.Command)
|
seal.sys.user.us, seal.sys.user.username, config.Confinement.Groups, config.Command)
|
||||||
|
|
||||||
// seal app and release lock
|
// seal app and release lock
|
||||||
|
@ -143,7 +143,7 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error {
|
|||||||
if seal.et.Has(system.EWayland) {
|
if seal.et.Has(system.EWayland) {
|
||||||
var socketPath string
|
var socketPath string
|
||||||
if name, ok := os.LookupEnv(wl.WaylandDisplay); !ok {
|
if name, ok := os.LookupEnv(wl.WaylandDisplay); !ok {
|
||||||
fmsg.VPrintln(wl.WaylandDisplay + " is not set, assuming " + wl.FallbackName)
|
fmsg.Verbose(wl.WaylandDisplay + " is not set, assuming " + wl.FallbackName)
|
||||||
socketPath = path.Join(seal.RuntimePath, wl.FallbackName)
|
socketPath = path.Join(seal.RuntimePath, wl.FallbackName)
|
||||||
} else if !path.IsAbs(name) {
|
} else if !path.IsAbs(name) {
|
||||||
socketPath = path.Join(seal.RuntimePath, name)
|
socketPath = path.Join(seal.RuntimePath, name)
|
||||||
@ -166,7 +166,7 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error {
|
|||||||
seal.sys.Wayland(outerPath, socketPath, appID, seal.id)
|
seal.sys.Wayland(outerPath, socketPath, appID, seal.id)
|
||||||
seal.sys.bwrap.Bind(outerPath, innerPath)
|
seal.sys.bwrap.Bind(outerPath, innerPath)
|
||||||
} else { // bind mount wayland socket (insecure)
|
} else { // bind mount wayland socket (insecure)
|
||||||
fmsg.VPrintln("direct wayland access, PROCEED WITH CAUTION")
|
fmsg.Verbose("direct wayland access, PROCEED WITH CAUTION")
|
||||||
seal.sys.bwrap.Bind(socketPath, innerPath)
|
seal.sys.bwrap.Bind(socketPath, innerPath)
|
||||||
|
|
||||||
// ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`)
|
// ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`)
|
||||||
@ -229,7 +229,7 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error {
|
|||||||
// publish current user's pulse cookie for target user
|
// publish current user's pulse cookie for target user
|
||||||
if src, err := discoverPulseCookie(os); err != nil {
|
if src, err := discoverPulseCookie(os); err != nil {
|
||||||
// not fatal
|
// not fatal
|
||||||
fmsg.VPrintln(strings.TrimSpace(err.(*fmsg.BaseError).Message()))
|
fmsg.Verbose(strings.TrimSpace(err.(*fmsg.BaseError).Message()))
|
||||||
} else {
|
} else {
|
||||||
dst := path.Join(seal.share, "pulse-cookie")
|
dst := path.Join(seal.share, "pulse-cookie")
|
||||||
innerDst := fst.Tmp + "/pulse-cookie"
|
innerDst := fst.Tmp + "/pulse-cookie"
|
||||||
|
@ -3,6 +3,7 @@ package shim
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@ -25,12 +26,11 @@ import (
|
|||||||
func Main() {
|
func Main() {
|
||||||
// sharing stdout with fortify
|
// sharing stdout with fortify
|
||||||
// USE WITH CAUTION
|
// USE WITH CAUTION
|
||||||
fmsg.SetPrefix("shim")
|
fmsg.Prepare("shim")
|
||||||
|
|
||||||
// setting this prevents ptrace
|
// setting this prevents ptrace
|
||||||
if err := internal.PR_SET_DUMPABLE__SUID_DUMP_DISABLE(); err != nil {
|
if err := internal.PR_SET_DUMPABLE__SUID_DUMP_DISABLE(); err != nil {
|
||||||
fmsg.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
|
log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// receive setup payload
|
// receive setup payload
|
||||||
@ -40,21 +40,20 @@ func Main() {
|
|||||||
)
|
)
|
||||||
if f, err := proc.Receive(Env, &payload); err != nil {
|
if f, err := proc.Receive(Env, &payload); err != nil {
|
||||||
if errors.Is(err, proc.ErrInvalid) {
|
if errors.Is(err, proc.ErrInvalid) {
|
||||||
fmsg.Fatal("invalid config descriptor")
|
log.Fatal("invalid config descriptor")
|
||||||
}
|
}
|
||||||
if errors.Is(err, proc.ErrNotSet) {
|
if errors.Is(err, proc.ErrNotSet) {
|
||||||
fmsg.Fatal("FORTIFY_SHIM not set")
|
log.Fatal("FORTIFY_SHIM not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmsg.Fatalf("cannot decode shim setup payload: %v", err)
|
log.Fatalf("cannot decode shim setup payload: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
} else {
|
} else {
|
||||||
fmsg.SetVerbose(payload.Verbose)
|
fmsg.Store(payload.Verbose)
|
||||||
closeSetup = f
|
closeSetup = f
|
||||||
}
|
}
|
||||||
|
|
||||||
if payload.Bwrap == nil {
|
if payload.Bwrap == nil {
|
||||||
fmsg.Fatal("bwrap config not supplied")
|
log.Fatal("bwrap config not supplied")
|
||||||
}
|
}
|
||||||
|
|
||||||
// restore bwrap sync fd
|
// restore bwrap sync fd
|
||||||
@ -65,7 +64,7 @@ func Main() {
|
|||||||
|
|
||||||
// close setup socket
|
// close setup socket
|
||||||
if err := closeSetup(); err != nil {
|
if err := closeSetup(); err != nil {
|
||||||
fmsg.Println("cannot close setup pipe:", err)
|
log.Println("cannot close setup pipe:", err)
|
||||||
// not fatal
|
// not fatal
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,15 +72,15 @@ func Main() {
|
|||||||
if s, err := os.Stat(payload.Home); err != nil {
|
if s, err := os.Stat(payload.Home); err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
if err = os.Mkdir(payload.Home, 0700); err != nil {
|
if err = os.Mkdir(payload.Home, 0700); err != nil {
|
||||||
fmsg.Fatalf("cannot create home directory: %v", err)
|
log.Fatalf("cannot create home directory: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmsg.Fatalf("cannot access home directory: %v", err)
|
log.Fatalf("cannot access home directory: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// home directory is created, proceed
|
// home directory is created, proceed
|
||||||
} else if !s.IsDir() {
|
} else if !s.IsDir() {
|
||||||
fmsg.Fatalf("data path %q is not a directory", payload.Home)
|
log.Fatalf("data path %q is not a directory", payload.Home)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ic init0.Payload
|
var ic init0.Payload
|
||||||
@ -95,10 +94,10 @@ func Main() {
|
|||||||
// no argv, look up shell instead
|
// no argv, look up shell instead
|
||||||
var ok bool
|
var ok bool
|
||||||
if payload.Bwrap.SetEnv == nil {
|
if payload.Bwrap.SetEnv == nil {
|
||||||
fmsg.Fatal("no command was specified and environment is unset")
|
log.Fatal("no command was specified and environment is unset")
|
||||||
}
|
}
|
||||||
if ic.Argv0, ok = payload.Bwrap.SetEnv["SHELL"]; !ok {
|
if ic.Argv0, ok = payload.Bwrap.SetEnv["SHELL"]; !ok {
|
||||||
fmsg.Fatal("no command was specified and $SHELL was unset")
|
log.Fatal("no command was specified and $SHELL was unset")
|
||||||
}
|
}
|
||||||
|
|
||||||
ic.Argv = []string{ic.Argv0}
|
ic.Argv = []string{ic.Argv0}
|
||||||
@ -110,20 +109,20 @@ func Main() {
|
|||||||
|
|
||||||
// serve setup payload
|
// serve setup payload
|
||||||
if fd, encoder, err := proc.Setup(&extraFiles); err != nil {
|
if fd, encoder, err := proc.Setup(&extraFiles); err != nil {
|
||||||
fmsg.Fatalf("cannot pipe: %v", err)
|
log.Fatalf("cannot pipe: %v", err)
|
||||||
} else {
|
} else {
|
||||||
conf.SetEnv[init0.Env] = strconv.Itoa(fd)
|
conf.SetEnv[init0.Env] = strconv.Itoa(fd)
|
||||||
go func() {
|
go func() {
|
||||||
fmsg.VPrintln("transmitting config to init")
|
fmsg.Verbose("transmitting config to init")
|
||||||
if err = encoder.Encode(&ic); err != nil {
|
if err = encoder.Encode(&ic); err != nil {
|
||||||
fmsg.Fatalf("cannot transmit init config: %v", err)
|
log.Fatalf("cannot transmit init config: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
helper.BubblewrapName = payload.Exec[0] // resolved bwrap path by parent
|
helper.BubblewrapName = payload.Exec[0] // resolved bwrap path by parent
|
||||||
if fmsg.Verbose() {
|
if fmsg.Load() {
|
||||||
seccomp.CPrintln = fmsg.Println
|
seccomp.CPrintln = log.Println
|
||||||
}
|
}
|
||||||
if b, err := helper.NewBwrap(
|
if b, err := helper.NewBwrap(
|
||||||
conf, path.Join(fst.Tmp, "sbin/init"),
|
conf, path.Join(fst.Tmp, "sbin/init"),
|
||||||
@ -131,7 +130,7 @@ func Main() {
|
|||||||
extraFiles,
|
extraFiles,
|
||||||
syncFd,
|
syncFd,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
fmsg.Fatalf("malformed sandbox config: %v", err)
|
log.Fatalf("malformed sandbox config: %v", err)
|
||||||
} else {
|
} else {
|
||||||
b.Stdin(os.Stdin).Stdout(os.Stdout).Stderr(os.Stderr)
|
b.Stdin(os.Stdin).Stdout(os.Stdout).Stderr(os.Stderr)
|
||||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||||
@ -139,15 +138,15 @@ func Main() {
|
|||||||
|
|
||||||
// run and pass through exit code
|
// run and pass through exit code
|
||||||
if err = b.Start(ctx, false); err != nil {
|
if err = b.Start(ctx, false); err != nil {
|
||||||
fmsg.Fatalf("cannot start target process: %v", err)
|
log.Fatalf("cannot start target process: %v", err)
|
||||||
} else if err = b.Wait(); err != nil {
|
} else if err = b.Wait(); err != nil {
|
||||||
var exitError *exec.ExitError
|
var exitError *exec.ExitError
|
||||||
if !errors.As(err, &exitError) {
|
if !errors.As(err, &exitError) {
|
||||||
fmsg.Println("wait:", err)
|
log.Printf("wait: %v", err)
|
||||||
fmsg.Exit(127)
|
internal.Exit(127)
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
fmsg.Exit(exitError.ExitCode())
|
internal.Exit(exitError.ExitCode())
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"errors"
|
"errors"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -54,8 +55,7 @@ func (s *Shim) Start(
|
|||||||
// prepare user switcher invocation
|
// prepare user switcher invocation
|
||||||
var fsu string
|
var fsu string
|
||||||
if p, ok := internal.Path(internal.Fsu); !ok {
|
if p, ok := internal.Path(internal.Fsu); !ok {
|
||||||
fmsg.Fatal("invalid fsu path, this copy of fortify is not compiled correctly")
|
log.Fatal("invalid fsu path, this copy of fortify is not compiled correctly")
|
||||||
panic("unreachable")
|
|
||||||
} else {
|
} else {
|
||||||
fsu = p
|
fsu = p
|
||||||
}
|
}
|
||||||
@ -75,7 +75,7 @@ func (s *Shim) Start(
|
|||||||
|
|
||||||
// format fsu supplementary groups
|
// format fsu supplementary groups
|
||||||
if len(supp) > 0 {
|
if len(supp) > 0 {
|
||||||
fmsg.VPrintf("attaching supplementary group ids %s", supp)
|
fmsg.Verbosef("attaching supplementary group ids %s", supp)
|
||||||
s.cmd.Env = append(s.cmd.Env, "FORTIFY_GROUPS="+strings.Join(supp, " "))
|
s.cmd.Env = append(s.cmd.Env, "FORTIFY_GROUPS="+strings.Join(supp, " "))
|
||||||
}
|
}
|
||||||
s.cmd.Stdin, s.cmd.Stdout, s.cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
s.cmd.Stdin, s.cmd.Stdout, s.cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||||
@ -87,7 +87,7 @@ func (s *Shim) Start(
|
|||||||
s.sync = &fd
|
s.sync = &fd
|
||||||
}
|
}
|
||||||
|
|
||||||
fmsg.VPrintln("starting shim via fsu:", s.cmd)
|
fmsg.Verbose("starting shim via fsu:", s.cmd)
|
||||||
// withhold messages to stderr
|
// withhold messages to stderr
|
||||||
fmsg.Suspend()
|
fmsg.Suspend()
|
||||||
if err := s.cmd.Start(); err != nil {
|
if err := s.cmd.Start(); err != nil {
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@ -81,7 +82,7 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
|
|||||||
Bwrap: a.seal.sys.bwrap,
|
Bwrap: a.seal.sys.bwrap,
|
||||||
Home: a.seal.sys.user.data,
|
Home: a.seal.sys.user.data,
|
||||||
|
|
||||||
Verbose: fmsg.Verbose(),
|
Verbose: fmsg.Load(),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -119,8 +120,8 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
|
|||||||
} else {
|
} else {
|
||||||
rs.ExitCode = a.shim.Unwrap().ProcessState.ExitCode()
|
rs.ExitCode = a.shim.Unwrap().ProcessState.ExitCode()
|
||||||
}
|
}
|
||||||
if fmsg.Verbose() {
|
if fmsg.Load() {
|
||||||
fmsg.VPrintf("process %d exited with exit code %d", a.shim.Unwrap().Process.Pid, rs.ExitCode)
|
fmsg.Verbosef("process %d exited with exit code %d", a.shim.Unwrap().Process.Pid, rs.ExitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is reached when a fault makes an already running shim impossible to continue execution
|
// this is reached when a fault makes an already running shim impossible to continue execution
|
||||||
@ -128,11 +129,11 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
|
|||||||
// the effects of this is similar to the alternative exit path and ensures shim death
|
// the effects of this is similar to the alternative exit path and ensures shim death
|
||||||
case err := <-a.shim.WaitFallback():
|
case err := <-a.shim.WaitFallback():
|
||||||
rs.ExitCode = 255
|
rs.ExitCode = 255
|
||||||
fmsg.Printf("cannot terminate shim on faulted setup: %v", err)
|
log.Printf("cannot terminate shim on faulted setup: %v", err)
|
||||||
|
|
||||||
// alternative exit path relying on shim behaviour on monitor process exit
|
// alternative exit path relying on shim behaviour on monitor process exit
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
fmsg.VPrintln("alternative exit path selected")
|
fmsg.Verbose("alternative exit path selected")
|
||||||
}
|
}
|
||||||
|
|
||||||
// child process exited, resume output
|
// child process exited, resume output
|
||||||
@ -163,10 +164,10 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
|
|||||||
} else {
|
} else {
|
||||||
if l := len(states); l == 0 {
|
if l := len(states); l == 0 {
|
||||||
// cleanup globals as the final launcher
|
// cleanup globals as the final launcher
|
||||||
fmsg.VPrintln("no other launchers active, will clean up globals")
|
fmsg.Verbose("no other launchers active, will clean up globals")
|
||||||
ec.Set(system.User)
|
ec.Set(system.User)
|
||||||
} else {
|
} else {
|
||||||
fmsg.VPrintf("found %d active launchers, cleaning up without globals", l)
|
fmsg.Verbosef("found %d active launchers, cleaning up without globals", l)
|
||||||
}
|
}
|
||||||
|
|
||||||
// accumulate capabilities of other launchers
|
// accumulate capabilities of other launchers
|
||||||
@ -174,7 +175,7 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
|
|||||||
if s.Config != nil {
|
if s.Config != nil {
|
||||||
*rt |= s.Config.Confinement.Enablements
|
*rt |= s.Config.Confinement.Enablements
|
||||||
} else {
|
} else {
|
||||||
fmsg.Printf("state entry %d does not contain config", i)
|
log.Printf("state entry %d does not contain config", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -184,7 +185,7 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
|
|||||||
ec.Set(i)
|
ec.Set(i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if fmsg.Verbose() {
|
if fmsg.Load() {
|
||||||
labels := make([]string, 0, system.ELen+1)
|
labels := make([]string, 0, system.ELen+1)
|
||||||
for i := system.Enablement(0); i < system.Enablement(system.ELen+2); i++ {
|
for i := system.Enablement(0); i < system.Enablement(system.ELen+2); i++ {
|
||||||
if ec.Has(i) {
|
if ec.Has(i) {
|
||||||
@ -192,7 +193,7 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(labels) > 0 {
|
if len(labels) > 0 {
|
||||||
fmsg.VPrintln("reverting operations labelled", strings.Join(labels, ", "))
|
fmsg.Verbose("reverting operations labelled", strings.Join(labels, ", "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
package proc
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -14,7 +13,7 @@ var (
|
|||||||
|
|
||||||
func copyExecutable() {
|
func copyExecutable() {
|
||||||
if name, err := os.Executable(); err != nil {
|
if name, err := os.Executable(); err != nil {
|
||||||
fmsg.Fatalf("cannot read executable path: %v", err)
|
log.Fatalf("cannot read executable path: %v", err)
|
||||||
} else {
|
} else {
|
||||||
executable = name
|
executable = name
|
||||||
}
|
}
|
9
internal/exit.go
Normal file
9
internal/exit.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Exit(code int) { fmsg.BeforeExit(); os.Exit(code) }
|
@ -1,98 +0,0 @@
|
|||||||
package fmsg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
wstate atomic.Bool
|
|
||||||
dropped atomic.Uint64
|
|
||||||
withhold = make(chan struct{}, 1)
|
|
||||||
msgbuf = make(chan dOp, 64) // these ops are tiny so a large buffer is allocated for withholding output
|
|
||||||
|
|
||||||
dequeueOnce sync.Once
|
|
||||||
queueSync sync.WaitGroup
|
|
||||||
)
|
|
||||||
|
|
||||||
func dequeue() {
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case op := <-msgbuf:
|
|
||||||
op.Do()
|
|
||||||
queueSync.Done()
|
|
||||||
case <-withhold:
|
|
||||||
<-withhold
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// queue submits ops to msgbuf but drops messages
|
|
||||||
// when the buffer is full and dequeue is withholding
|
|
||||||
func queue(op dOp) {
|
|
||||||
queueSync.Add(1)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case msgbuf <- op:
|
|
||||||
default:
|
|
||||||
// send the op anyway if not withholding
|
|
||||||
// as dequeue will get to it eventually
|
|
||||||
if !wstate.Load() {
|
|
||||||
msgbuf <- op
|
|
||||||
} else {
|
|
||||||
queueSync.Done()
|
|
||||||
// increment dropped message count
|
|
||||||
dropped.Add(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type dOp interface{ Do() }
|
|
||||||
|
|
||||||
func Exit(code int) {
|
|
||||||
Resume() // resume here to avoid deadlock
|
|
||||||
queueSync.Wait()
|
|
||||||
os.Exit(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Suspend() {
|
|
||||||
dequeueOnce.Do(dequeue)
|
|
||||||
if wstate.CompareAndSwap(false, true) {
|
|
||||||
queueSync.Wait()
|
|
||||||
withhold <- struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Resume() {
|
|
||||||
dequeueOnce.Do(dequeue)
|
|
||||||
if wstate.CompareAndSwap(true, false) {
|
|
||||||
withhold <- struct{}{}
|
|
||||||
if d := dropped.Swap(0); d != 0 {
|
|
||||||
Printf("dropped %d messages during withhold", d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type dPrint []any
|
|
||||||
|
|
||||||
func (v dPrint) Do() {
|
|
||||||
std.Print(v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
type dPrintf struct {
|
|
||||||
format string
|
|
||||||
v []any
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dPrintf) Do() {
|
|
||||||
std.Printf(d.format, d.v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
type dPrintln []any
|
|
||||||
|
|
||||||
func (v dPrintln) Do() {
|
|
||||||
std.Println(v...)
|
|
||||||
}
|
|
@ -2,39 +2,85 @@
|
|||||||
package fmsg
|
package fmsg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
var std = log.New(os.Stderr, "fortify: ", 0)
|
const (
|
||||||
|
bufSize = 4 * 1024
|
||||||
|
bufSizeMax = 16 * 1024 * 1024
|
||||||
|
)
|
||||||
|
|
||||||
func SetPrefix(prefix string) {
|
var o = &suspendable{w: os.Stderr}
|
||||||
prefix += ": "
|
|
||||||
std.SetPrefix(prefix)
|
// Prepare configures the system logger for [Suspend] and [Resume] to take effect.
|
||||||
std.SetPrefix(prefix)
|
func Prepare(prefix string) { log.SetPrefix(prefix + ": "); log.SetFlags(0); log.SetOutput(o) }
|
||||||
|
|
||||||
|
type suspendable struct {
|
||||||
|
w io.Writer
|
||||||
|
s atomic.Bool
|
||||||
|
|
||||||
|
buf bytes.Buffer
|
||||||
|
bufOnce sync.Once
|
||||||
|
bufMu sync.Mutex
|
||||||
|
dropped int
|
||||||
}
|
}
|
||||||
|
|
||||||
func Print(v ...any) {
|
func (s *suspendable) Write(p []byte) (n int, err error) {
|
||||||
dequeueOnce.Do(dequeue)
|
if !s.s.Load() {
|
||||||
queue(dPrint(v))
|
return s.w.Write(p)
|
||||||
|
}
|
||||||
|
s.bufOnce.Do(func() { s.prepareBuf() })
|
||||||
|
|
||||||
|
s.bufMu.Lock()
|
||||||
|
defer s.bufMu.Unlock()
|
||||||
|
|
||||||
|
if l := len(p); s.buf.Len()+l > bufSizeMax {
|
||||||
|
s.dropped += l
|
||||||
|
return 0, syscall.ENOMEM
|
||||||
|
}
|
||||||
|
return s.buf.Write(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Printf(format string, v ...any) {
|
func (s *suspendable) prepareBuf() { s.buf.Grow(bufSize) }
|
||||||
dequeueOnce.Do(dequeue)
|
func (s *suspendable) Suspend() bool { return o.s.CompareAndSwap(false, true) }
|
||||||
queue(&dPrintf{format, v})
|
func (s *suspendable) Resume() (resumed bool, dropped uintptr, n int64, err error) {
|
||||||
|
if o.s.CompareAndSwap(true, false) {
|
||||||
|
o.bufMu.Lock()
|
||||||
|
defer o.bufMu.Unlock()
|
||||||
|
|
||||||
|
resumed = true
|
||||||
|
dropped = uintptr(o.dropped)
|
||||||
|
|
||||||
|
o.dropped = 0
|
||||||
|
n, err = io.Copy(s.w, &s.buf)
|
||||||
|
s.buf = bytes.Buffer{}
|
||||||
|
s.prepareBuf()
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func Println(v ...any) {
|
func Suspend() bool { return o.Suspend() }
|
||||||
dequeueOnce.Do(dequeue)
|
func Resume() bool {
|
||||||
queue(dPrintln(v))
|
resumed, dropped, _, err := o.Resume()
|
||||||
|
if err != nil {
|
||||||
|
// probably going to result in an error as well,
|
||||||
|
// so this call is as good as unreachable
|
||||||
|
log.Printf("cannot dump buffer on resume: %v", err)
|
||||||
|
}
|
||||||
|
if resumed && dropped > 0 {
|
||||||
|
log.Fatalf("dropped %d bytes while output is suspended", dropped)
|
||||||
|
}
|
||||||
|
return resumed
|
||||||
}
|
}
|
||||||
|
|
||||||
func Fatal(v ...any) {
|
func BeforeExit() {
|
||||||
Print(v...)
|
if Resume() {
|
||||||
Exit(1)
|
log.Printf("beforeExit reached on suspended output")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Fatalf(format string, v ...any) {
|
|
||||||
Printf(format, v...)
|
|
||||||
Exit(1)
|
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,23 @@
|
|||||||
package fmsg
|
package fmsg
|
||||||
|
|
||||||
import "sync/atomic"
|
import (
|
||||||
|
"log"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
var verbose = new(atomic.Bool)
|
var verbose = new(atomic.Bool)
|
||||||
|
|
||||||
func Verbose() bool {
|
func Load() bool { return verbose.Load() }
|
||||||
return verbose.Load()
|
func Store(v bool) { verbose.Store(v) }
|
||||||
}
|
|
||||||
|
|
||||||
func SetVerbose(v bool) {
|
func Verbosef(format string, v ...any) {
|
||||||
verbose.Store(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func VPrintf(format string, v ...any) {
|
|
||||||
if verbose.Load() {
|
if verbose.Load() {
|
||||||
Printf(format, v...)
|
log.Printf(format, v...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func VPrintln(v ...any) {
|
func Verbose(v ...any) {
|
||||||
if verbose.Load() {
|
if verbose.Load() {
|
||||||
Println(v...)
|
log.Println(v...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ type Paths struct {
|
|||||||
func CopyPaths(os System, v *Paths) {
|
func CopyPaths(os System, v *Paths) {
|
||||||
v.SharePath = path.Join(os.TempDir(), "fortify."+strconv.Itoa(os.Geteuid()))
|
v.SharePath = path.Join(os.TempDir(), "fortify."+strconv.Itoa(os.Geteuid()))
|
||||||
|
|
||||||
fmsg.VPrintf("process share directory at %q", v.SharePath)
|
fmsg.Verbosef("process share directory at %q", v.SharePath)
|
||||||
|
|
||||||
if r, ok := os.LookupEnv(xdgRuntimeDir); !ok || r == "" || !path.IsAbs(r) {
|
if r, ok := os.LookupEnv(xdgRuntimeDir); !ok || r == "" || !path.IsAbs(r) {
|
||||||
// fall back to path in share since fortify has no hard XDG dependency
|
// fall back to path in share since fortify has no hard XDG dependency
|
||||||
@ -65,5 +65,5 @@ func CopyPaths(os System, v *Paths) {
|
|||||||
v.RunDirPath = path.Join(v.RuntimePath, "fortify")
|
v.RunDirPath = path.Join(v.RuntimePath, "fortify")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmsg.VPrintf("runtime directory at %q", v.RunDirPath)
|
fmsg.Verbosef("runtime directory at %q", v.RunDirPath)
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package linux
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
@ -11,9 +12,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/helper/proc"
|
|
||||||
"git.gensokyo.uk/security/fortify/internal"
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Std implements System using the standard library.
|
// Std implements System using the standard library.
|
||||||
@ -33,13 +32,13 @@ func (s *Std) Geteuid() int { return os.Geteuid(
|
|||||||
func (s *Std) LookupEnv(key string) (string, bool) { return os.LookupEnv(key) }
|
func (s *Std) LookupEnv(key string) (string, bool) { return os.LookupEnv(key) }
|
||||||
func (s *Std) TempDir() string { return os.TempDir() }
|
func (s *Std) TempDir() string { return os.TempDir() }
|
||||||
func (s *Std) LookPath(file string) (string, error) { return exec.LookPath(file) }
|
func (s *Std) LookPath(file string) (string, error) { return exec.LookPath(file) }
|
||||||
func (s *Std) MustExecutable() string { return proc.MustExecutable() }
|
func (s *Std) MustExecutable() string { return internal.MustExecutable() }
|
||||||
func (s *Std) LookupGroup(name string) (*user.Group, error) { return user.LookupGroup(name) }
|
func (s *Std) LookupGroup(name string) (*user.Group, error) { return user.LookupGroup(name) }
|
||||||
func (s *Std) ReadDir(name string) ([]os.DirEntry, error) { return os.ReadDir(name) }
|
func (s *Std) ReadDir(name string) ([]os.DirEntry, error) { return os.ReadDir(name) }
|
||||||
func (s *Std) Stat(name string) (fs.FileInfo, error) { return os.Stat(name) }
|
func (s *Std) Stat(name string) (fs.FileInfo, error) { return os.Stat(name) }
|
||||||
func (s *Std) Open(name string) (fs.File, error) { return os.Open(name) }
|
func (s *Std) Open(name string) (fs.File, error) { return os.Open(name) }
|
||||||
func (s *Std) EvalSymlinks(path string) (string, error) { return filepath.EvalSymlinks(path) }
|
func (s *Std) EvalSymlinks(path string) (string, error) { return filepath.EvalSymlinks(path) }
|
||||||
func (s *Std) Exit(code int) { fmsg.Exit(code) }
|
func (s *Std) Exit(code int) { internal.Exit(code) }
|
||||||
|
|
||||||
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
||||||
|
|
||||||
@ -74,8 +73,9 @@ func (s *Std) Uid(aid int) (int, error) {
|
|||||||
|
|
||||||
u.uid = -1
|
u.uid = -1
|
||||||
if fsu, ok := internal.Check(internal.Fsu); !ok {
|
if fsu, ok := internal.Check(internal.Fsu); !ok {
|
||||||
fmsg.Fatal("invalid fsu path, this copy of fortify is not compiled correctly")
|
log.Fatal("invalid fsu path, this copy of fortify is not compiled correctly")
|
||||||
panic("unreachable")
|
// unreachable
|
||||||
|
return 0, syscall.EBADE
|
||||||
} else {
|
} else {
|
||||||
cmd := exec.Command(fsu)
|
cmd := exec.Command(fsu)
|
||||||
cmd.Path = fsu
|
cmd.Path = fsu
|
||||||
|
@ -85,17 +85,17 @@ func (s *multiStore) List() ([]int, error) {
|
|||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
// skip non-directories
|
// skip non-directories
|
||||||
if !e.IsDir() {
|
if !e.IsDir() {
|
||||||
fmsg.VPrintf("skipped non-directory entry %q", e.Name())
|
fmsg.Verbosef("skipped non-directory entry %q", e.Name())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip non-numerical names
|
// skip non-numerical names
|
||||||
if v, err := strconv.Atoi(e.Name()); err != nil {
|
if v, err := strconv.Atoi(e.Name()); err != nil {
|
||||||
fmsg.VPrintf("skipped non-aid entry %q", e.Name())
|
fmsg.Verbosef("skipped non-aid entry %q", e.Name())
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
if v < 0 || v > 9999 {
|
if v < 0 || v > 9999 {
|
||||||
fmsg.VPrintf("skipped out of bounds entry %q", e.Name())
|
fmsg.Verbosef("skipped out of bounds entry %q", e.Name())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,18 +36,18 @@ func (a *ACL) Type() Enablement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACL) apply(sys *I) error {
|
func (a *ACL) apply(sys *I) error {
|
||||||
fmsg.VPrintln("applying ACL", a)
|
fmsg.Verbose("applying ACL", a)
|
||||||
return fmsg.WrapErrorSuffix(acl.UpdatePerm(a.path, sys.uid, a.perms...),
|
return fmsg.WrapErrorSuffix(acl.UpdatePerm(a.path, sys.uid, a.perms...),
|
||||||
fmt.Sprintf("cannot apply ACL entry to %q:", a.path))
|
fmt.Sprintf("cannot apply ACL entry to %q:", a.path))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACL) revert(sys *I, ec *Criteria) error {
|
func (a *ACL) revert(sys *I, ec *Criteria) error {
|
||||||
if ec.hasType(a) {
|
if ec.hasType(a) {
|
||||||
fmsg.VPrintln("stripping ACL", a)
|
fmsg.Verbose("stripping ACL", a)
|
||||||
return fmsg.WrapErrorSuffix(acl.UpdatePerm(a.path, sys.uid),
|
return fmsg.WrapErrorSuffix(acl.UpdatePerm(a.path, sys.uid),
|
||||||
fmt.Sprintf("cannot strip ACL entry from %q:", a.path))
|
fmt.Sprintf("cannot strip ACL entry from %q:", a.path))
|
||||||
} else {
|
} else {
|
||||||
fmsg.VPrintln("skipping ACL", a)
|
fmsg.Verbose("skipping ACL", a)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package system
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -47,12 +48,12 @@ func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath st
|
|||||||
d.proxy = dbus.New(sessionBus, systemBus)
|
d.proxy = dbus.New(sessionBus, systemBus)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if fmsg.Verbose() && d.proxy.Sealed() {
|
if fmsg.Load() && d.proxy.Sealed() {
|
||||||
fmsg.VPrintln("sealed session proxy", session.Args(sessionBus))
|
fmsg.Verbose("sealed session proxy", session.Args(sessionBus))
|
||||||
if system != nil {
|
if system != nil {
|
||||||
fmsg.VPrintln("sealed system proxy", system.Args(systemBus))
|
fmsg.Verbose("sealed system proxy", system.Args(systemBus))
|
||||||
}
|
}
|
||||||
fmsg.VPrintln("message bus proxy final args:", d.proxy)
|
fmsg.Verbose("message bus proxy final args:", d.proxy)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -78,9 +79,9 @@ func (d *DBus) Type() Enablement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DBus) apply(sys *I) error {
|
func (d *DBus) apply(sys *I) error {
|
||||||
fmsg.VPrintf("session bus proxy on %q for upstream %q", d.proxy.Session()[1], d.proxy.Session()[0])
|
fmsg.Verbosef("session bus proxy on %q for upstream %q", d.proxy.Session()[1], d.proxy.Session()[0])
|
||||||
if d.system {
|
if d.system {
|
||||||
fmsg.VPrintf("system bus proxy on %q for upstream %q", d.proxy.System()[1], d.proxy.System()[0])
|
fmsg.Verbosef("system bus proxy on %q for upstream %q", d.proxy.System()[1], d.proxy.System()[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// this starts the process and blocks until ready
|
// this starts the process and blocks until ready
|
||||||
@ -89,15 +90,15 @@ func (d *DBus) apply(sys *I) error {
|
|||||||
return fmsg.WrapErrorSuffix(err,
|
return fmsg.WrapErrorSuffix(err,
|
||||||
"cannot start message bus proxy:")
|
"cannot start message bus proxy:")
|
||||||
}
|
}
|
||||||
fmsg.VPrintln("starting message bus proxy:", d.proxy)
|
fmsg.Verbose("starting message bus proxy:", d.proxy)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DBus) revert(_ *I, _ *Criteria) error {
|
func (d *DBus) revert(_ *I, _ *Criteria) error {
|
||||||
// criteria ignored here since dbus is always process-scoped
|
// criteria ignored here since dbus is always process-scoped
|
||||||
fmsg.VPrintln("terminating message bus proxy")
|
fmsg.Verbose("terminating message bus proxy")
|
||||||
d.proxy.Close()
|
d.proxy.Close()
|
||||||
defer fmsg.VPrintln("message bus proxy exit")
|
defer fmsg.Verbose("message bus proxy exit")
|
||||||
return fmsg.WrapErrorSuffix(d.proxy.Wait(), "message bus proxy error:")
|
return fmsg.WrapErrorSuffix(d.proxy.Wait(), "message bus proxy error:")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,7 +145,7 @@ func (s *scanToFmsg) write(p []byte, a int) (int, error) {
|
|||||||
func (s *scanToFmsg) Dump() {
|
func (s *scanToFmsg) Dump() {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
for _, msg := range s.msgbuf {
|
for _, msg := range s.msgbuf {
|
||||||
fmsg.Println(msg)
|
log.Println(msg)
|
||||||
}
|
}
|
||||||
s.mu.RUnlock()
|
s.mu.RUnlock()
|
||||||
}
|
}
|
||||||
|
@ -28,18 +28,18 @@ type Hardlink struct {
|
|||||||
func (l *Hardlink) Type() Enablement { return l.et }
|
func (l *Hardlink) Type() Enablement { return l.et }
|
||||||
|
|
||||||
func (l *Hardlink) apply(_ *I) error {
|
func (l *Hardlink) apply(_ *I) error {
|
||||||
fmsg.VPrintln("linking ", l)
|
fmsg.Verbose("linking ", l)
|
||||||
return fmsg.WrapErrorSuffix(os.Link(l.src, l.dst),
|
return fmsg.WrapErrorSuffix(os.Link(l.src, l.dst),
|
||||||
fmt.Sprintf("cannot link %q:", l.dst))
|
fmt.Sprintf("cannot link %q:", l.dst))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Hardlink) revert(_ *I, ec *Criteria) error {
|
func (l *Hardlink) revert(_ *I, ec *Criteria) error {
|
||||||
if ec.hasType(l) {
|
if ec.hasType(l) {
|
||||||
fmsg.VPrintf("removing hard link %q", l.dst)
|
fmsg.Verbosef("removing hard link %q", l.dst)
|
||||||
return fmsg.WrapErrorSuffix(os.Remove(l.dst),
|
return fmsg.WrapErrorSuffix(os.Remove(l.dst),
|
||||||
fmt.Sprintf("cannot remove hard link %q:", l.dst))
|
fmt.Sprintf("cannot remove hard link %q:", l.dst))
|
||||||
} else {
|
} else {
|
||||||
fmsg.VPrintf("skipping hard link %q", l.dst)
|
fmsg.Verbosef("skipping hard link %q", l.dst)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ func (m *Mkdir) Type() Enablement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Mkdir) apply(_ *I) error {
|
func (m *Mkdir) apply(_ *I) error {
|
||||||
fmsg.VPrintln("ensuring directory", m)
|
fmsg.Verbose("ensuring directory", m)
|
||||||
|
|
||||||
// create directory
|
// create directory
|
||||||
err := os.Mkdir(m.path, m.perm)
|
err := os.Mkdir(m.path, m.perm)
|
||||||
@ -61,11 +61,11 @@ func (m *Mkdir) revert(_ *I, ec *Criteria) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ec.hasType(m) {
|
if ec.hasType(m) {
|
||||||
fmsg.VPrintln("destroying ephemeral directory", m)
|
fmsg.Verbose("destroying ephemeral directory", m)
|
||||||
return fmsg.WrapErrorSuffix(os.Remove(m.path),
|
return fmsg.WrapErrorSuffix(os.Remove(m.path),
|
||||||
fmt.Sprintf("cannot remove ephemeral directory %q:", m.path))
|
fmt.Sprintf("cannot remove ephemeral directory %q:", m.path))
|
||||||
} else {
|
} else {
|
||||||
fmsg.VPrintln("skipping ephemeral directory", m)
|
fmsg.Verbose("skipping ephemeral directory", m)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package system
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -105,9 +106,9 @@ func (sys *I) Commit(ctx context.Context) error {
|
|||||||
// sp is set to nil when all ops are applied
|
// sp is set to nil when all ops are applied
|
||||||
if sp != nil {
|
if sp != nil {
|
||||||
// rollback partial commit
|
// rollback partial commit
|
||||||
fmsg.VPrintf("commit faulted after %d ops, rolling back partial commit", len(sp.ops))
|
fmsg.Verbosef("commit faulted after %d ops, rolling back partial commit", len(sp.ops))
|
||||||
if err := sp.Revert(&Criteria{nil}); err != nil {
|
if err := sp.Revert(&Criteria{nil}); err != nil {
|
||||||
fmsg.Println("errors returned reverting partial commit:", err)
|
log.Println("errors returned reverting partial commit:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -44,7 +44,7 @@ func (t *Tmpfile) Type() Enablement {
|
|||||||
func (t *Tmpfile) apply(_ *I) error {
|
func (t *Tmpfile) apply(_ *I) error {
|
||||||
switch t.method {
|
switch t.method {
|
||||||
case tmpfileCopy:
|
case tmpfileCopy:
|
||||||
fmsg.VPrintln("publishing tmpfile", t)
|
fmsg.Verbose("publishing tmpfile", t)
|
||||||
return fmsg.WrapErrorSuffix(copyFile(t.dst, t.src),
|
return fmsg.WrapErrorSuffix(copyFile(t.dst, t.src),
|
||||||
fmt.Sprintf("cannot copy tmpfile %q:", t.dst))
|
fmt.Sprintf("cannot copy tmpfile %q:", t.dst))
|
||||||
default:
|
default:
|
||||||
@ -54,11 +54,11 @@ func (t *Tmpfile) apply(_ *I) error {
|
|||||||
|
|
||||||
func (t *Tmpfile) revert(_ *I, ec *Criteria) error {
|
func (t *Tmpfile) revert(_ *I, ec *Criteria) error {
|
||||||
if ec.hasType(t) {
|
if ec.hasType(t) {
|
||||||
fmsg.VPrintf("removing tmpfile %q", t.dst)
|
fmsg.Verbosef("removing tmpfile %q", t.dst)
|
||||||
return fmsg.WrapErrorSuffix(os.Remove(t.dst),
|
return fmsg.WrapErrorSuffix(os.Remove(t.dst),
|
||||||
fmt.Sprintf("cannot remove tmpfile %q:", t.dst))
|
fmt.Sprintf("cannot remove tmpfile %q:", t.dst))
|
||||||
} else {
|
} else {
|
||||||
fmsg.VPrintf("skipping tmpfile %q", t.dst)
|
fmsg.Verbosef("skipping tmpfile %q", t.dst)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ func (w Wayland) apply(sys *I) error {
|
|||||||
return fmsg.WrapErrorSuffix(err,
|
return fmsg.WrapErrorSuffix(err,
|
||||||
fmt.Sprintf("cannot attach to wayland on %q:", w.pair[1]))
|
fmt.Sprintf("cannot attach to wayland on %q:", w.pair[1]))
|
||||||
} else {
|
} else {
|
||||||
fmsg.VPrintf("wayland attached on %q", w.pair[1])
|
fmsg.Verbosef("wayland attached on %q", w.pair[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
if sp, err := w.conn.Bind(w.pair[0], w.appID, w.instanceID); err != nil {
|
if sp, err := w.conn.Bind(w.pair[0], w.appID, w.instanceID); err != nil {
|
||||||
@ -53,7 +53,7 @@ func (w Wayland) apply(sys *I) error {
|
|||||||
fmt.Sprintf("cannot bind to socket on %q:", w.pair[0]))
|
fmt.Sprintf("cannot bind to socket on %q:", w.pair[0]))
|
||||||
} else {
|
} else {
|
||||||
sys.sp = sp
|
sys.sp = sp
|
||||||
fmsg.VPrintf("wayland listening on %q", w.pair[0])
|
fmsg.Verbosef("wayland listening on %q", w.pair[0])
|
||||||
return fmsg.WrapErrorSuffix(errors.Join(os.Chmod(w.pair[0], 0), acl.UpdatePerm(w.pair[0], sys.uid, acl.Read, acl.Write, acl.Execute)),
|
return fmsg.WrapErrorSuffix(errors.Join(os.Chmod(w.pair[0], 0), acl.UpdatePerm(w.pair[0], sys.uid, acl.Read, acl.Write, acl.Execute)),
|
||||||
fmt.Sprintf("cannot chmod socket on %q:", w.pair[0]))
|
fmt.Sprintf("cannot chmod socket on %q:", w.pair[0]))
|
||||||
}
|
}
|
||||||
@ -61,16 +61,16 @@ func (w Wayland) apply(sys *I) error {
|
|||||||
|
|
||||||
func (w Wayland) revert(_ *I, ec *Criteria) error {
|
func (w Wayland) revert(_ *I, ec *Criteria) error {
|
||||||
if ec.hasType(w) {
|
if ec.hasType(w) {
|
||||||
fmsg.VPrintf("removing wayland socket on %q", w.pair[0])
|
fmsg.Verbosef("removing wayland socket on %q", w.pair[0])
|
||||||
if err := os.Remove(w.pair[0]); err != nil && !errors.Is(err, os.ErrNotExist) {
|
if err := os.Remove(w.pair[0]); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmsg.VPrintf("detaching from wayland on %q", w.pair[1])
|
fmsg.Verbosef("detaching from wayland on %q", w.pair[1])
|
||||||
return fmsg.WrapErrorSuffix(w.conn.Close(),
|
return fmsg.WrapErrorSuffix(w.conn.Close(),
|
||||||
fmt.Sprintf("cannot detach from wayland on %q:", w.pair[1]))
|
fmt.Sprintf("cannot detach from wayland on %q:", w.pair[1]))
|
||||||
} else {
|
} else {
|
||||||
fmsg.VPrintf("skipping wayland cleanup on %q", w.pair[0])
|
fmsg.Verbosef("skipping wayland cleanup on %q", w.pair[0])
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,18 +24,18 @@ func (x XHost) Type() Enablement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (x XHost) apply(_ *I) error {
|
func (x XHost) apply(_ *I) error {
|
||||||
fmsg.VPrintf("inserting entry %s to X11", x)
|
fmsg.Verbosef("inserting entry %s to X11", x)
|
||||||
return fmsg.WrapErrorSuffix(xcb.ChangeHosts(xcb.HostModeInsert, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)),
|
return fmsg.WrapErrorSuffix(xcb.ChangeHosts(xcb.HostModeInsert, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)),
|
||||||
fmt.Sprintf("cannot insert entry %s to X11:", x))
|
fmt.Sprintf("cannot insert entry %s to X11:", x))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x XHost) revert(_ *I, ec *Criteria) error {
|
func (x XHost) revert(_ *I, ec *Criteria) error {
|
||||||
if ec.hasType(x) {
|
if ec.hasType(x) {
|
||||||
fmsg.VPrintf("deleting entry %s from X11", x)
|
fmsg.Verbosef("deleting entry %s from X11", x)
|
||||||
return fmsg.WrapErrorSuffix(xcb.ChangeHosts(xcb.HostModeDelete, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)),
|
return fmsg.WrapErrorSuffix(xcb.ChangeHosts(xcb.HostModeDelete, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)),
|
||||||
fmt.Sprintf("cannot delete entry %s from X11:", x))
|
fmt.Sprintf("cannot delete entry %s from X11:", x))
|
||||||
} else {
|
} else {
|
||||||
fmsg.VPrintf("skipping entry %s in X11", x)
|
fmsg.Verbosef("skipping entry %s in X11", x)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
71
main.go
71
main.go
@ -5,6 +5,7 @@ import (
|
|||||||
_ "embed"
|
_ "embed"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"os/user"
|
"os/user"
|
||||||
@ -37,6 +38,8 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
fmsg.Prepare("fortify")
|
||||||
|
|
||||||
flag.BoolVar(&flagVerbose, "v", false, "Verbose output")
|
flag.BoolVar(&flagVerbose, "v", false, "Verbose output")
|
||||||
flag.BoolVar(&flagJSON, "json", false, "Format output in JSON when applicable")
|
flag.BoolVar(&flagJSON, "json", false, "Format output in JSON when applicable")
|
||||||
}
|
}
|
||||||
@ -62,13 +65,12 @@ func main() {
|
|||||||
init0.TryArgv0()
|
init0.TryArgv0()
|
||||||
|
|
||||||
if err := internal.PR_SET_DUMPABLE__SUID_DUMP_DISABLE(); err != nil {
|
if err := internal.PR_SET_DUMPABLE__SUID_DUMP_DISABLE(); err != nil {
|
||||||
fmsg.Printf("cannot set SUID_DUMP_DISABLE: %s", err)
|
log.Printf("cannot set SUID_DUMP_DISABLE: %s", err)
|
||||||
// not fatal: this program runs as the privileged user
|
// not fatal: this program runs as the privileged user
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.Geteuid() == 0 {
|
if os.Geteuid() == 0 {
|
||||||
fmsg.Fatal("this program must not run as root")
|
log.Fatal("this program must not run as root")
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
flag.CommandLine.Usage = func() {
|
flag.CommandLine.Usage = func() {
|
||||||
@ -96,12 +98,12 @@ func main() {
|
|||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
fmsg.SetVerbose(flagVerbose)
|
fmsg.Store(flagVerbose)
|
||||||
|
|
||||||
args := flag.Args()
|
args := flag.Args()
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
flag.CommandLine.Usage()
|
flag.CommandLine.Usage()
|
||||||
fmsg.Exit(0)
|
internal.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch args[0] {
|
switch args[0] {
|
||||||
@ -111,16 +113,20 @@ func main() {
|
|||||||
} else {
|
} else {
|
||||||
fmt.Println("impure")
|
fmt.Println("impure")
|
||||||
}
|
}
|
||||||
fmsg.Exit(0)
|
internal.Exit(0)
|
||||||
|
|
||||||
case "license": // print embedded license
|
case "license": // print embedded license
|
||||||
fmt.Println(license)
|
fmt.Println(license)
|
||||||
fmsg.Exit(0)
|
internal.Exit(0)
|
||||||
|
|
||||||
case "template": // print full template configuration
|
case "template": // print full template configuration
|
||||||
printJSON(os.Stdout, false, fst.Template())
|
printJSON(os.Stdout, false, fst.Template())
|
||||||
fmsg.Exit(0)
|
internal.Exit(0)
|
||||||
|
|
||||||
case "help": // print help message
|
case "help": // print help message
|
||||||
flag.CommandLine.Usage()
|
flag.CommandLine.Usage()
|
||||||
fmsg.Exit(0)
|
internal.Exit(0)
|
||||||
|
|
||||||
case "ps": // print all state info
|
case "ps": // print all state info
|
||||||
set := flag.NewFlagSet("ps", flag.ExitOnError)
|
set := flag.NewFlagSet("ps", flag.ExitOnError)
|
||||||
var short bool
|
var short bool
|
||||||
@ -130,7 +136,8 @@ func main() {
|
|||||||
_ = set.Parse(args[1:])
|
_ = set.Parse(args[1:])
|
||||||
|
|
||||||
printPs(os.Stdout, time.Now().UTC(), state.NewMulti(sys.Paths().RunDirPath), short)
|
printPs(os.Stdout, time.Now().UTC(), state.NewMulti(sys.Paths().RunDirPath), short)
|
||||||
fmsg.Exit(0)
|
internal.Exit(0)
|
||||||
|
|
||||||
case "show": // pretty-print app info
|
case "show": // pretty-print app info
|
||||||
set := flag.NewFlagSet("show", flag.ExitOnError)
|
set := flag.NewFlagSet("show", flag.ExitOnError)
|
||||||
var short bool
|
var short bool
|
||||||
@ -142,6 +149,7 @@ func main() {
|
|||||||
switch len(set.Args()) {
|
switch len(set.Args()) {
|
||||||
case 0: // system
|
case 0: // system
|
||||||
printShowSystem(os.Stdout, short)
|
printShowSystem(os.Stdout, short)
|
||||||
|
|
||||||
case 1: // instance
|
case 1: // instance
|
||||||
name := set.Args()[0]
|
name := set.Args()[0]
|
||||||
config, instance := tryShort(name)
|
config, instance := tryShort(name)
|
||||||
@ -149,14 +157,15 @@ func main() {
|
|||||||
config = tryPath(name)
|
config = tryPath(name)
|
||||||
}
|
}
|
||||||
printShowInstance(os.Stdout, time.Now().UTC(), instance, config, short)
|
printShowInstance(os.Stdout, time.Now().UTC(), instance, config, short)
|
||||||
default:
|
|
||||||
fmsg.Fatal("show requires 1 argument")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmsg.Exit(0)
|
default:
|
||||||
|
log.Fatal("show requires 1 argument")
|
||||||
|
}
|
||||||
|
internal.Exit(0)
|
||||||
|
|
||||||
case "app": // launch app from configuration file
|
case "app": // launch app from configuration file
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
fmsg.Fatal("app requires at least 1 argument")
|
log.Fatal("app requires at least 1 argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
// config extraArgs...
|
// config extraArgs...
|
||||||
@ -166,6 +175,7 @@ func main() {
|
|||||||
// invoke app
|
// invoke app
|
||||||
runApp(config)
|
runApp(config)
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
|
|
||||||
case "run": // run app in permissive defaults usage pattern
|
case "run": // run app in permissive defaults usage pattern
|
||||||
set := flag.NewFlagSet("run", flag.ExitOnError)
|
set := flag.NewFlagSet("run", flag.ExitOnError)
|
||||||
|
|
||||||
@ -208,8 +218,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if aid < 0 || aid > 9999 {
|
if aid < 0 || aid > 9999 {
|
||||||
fmsg.Fatalf("aid %d out of range", aid)
|
log.Fatalf("aid %d out of range", aid)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolve home/username from os when flag is unset
|
// resolve home/username from os when flag is unset
|
||||||
@ -219,13 +228,13 @@ func main() {
|
|||||||
passwdFunc = func() {
|
passwdFunc = func() {
|
||||||
var us string
|
var us string
|
||||||
if uid, err := sys.Uid(aid); err != nil {
|
if uid, err := sys.Uid(aid); err != nil {
|
||||||
fmsg.Fatalf("cannot obtain uid from fsu: %v", err)
|
log.Fatalf("cannot obtain uid from fsu: %v", err)
|
||||||
} else {
|
} else {
|
||||||
us = strconv.Itoa(uid)
|
us = strconv.Itoa(uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
if u, err := user.LookupId(us); err != nil {
|
if u, err := user.LookupId(us); err != nil {
|
||||||
fmsg.VPrintf("cannot look up uid %s", us)
|
fmsg.Verbosef("cannot look up uid %s", us)
|
||||||
passwd = &user.User{
|
passwd = &user.User{
|
||||||
Uid: us,
|
Uid: us,
|
||||||
Gid: us,
|
Gid: us,
|
||||||
@ -267,7 +276,7 @@ func main() {
|
|||||||
config.Confinement.SessionBus = dbus.NewConfig(fid, true, mpris)
|
config.Confinement.SessionBus = dbus.NewConfig(fid, true, mpris)
|
||||||
} else {
|
} else {
|
||||||
if c, err := dbus.NewConfigFromFile(dbusConfigSession); err != nil {
|
if c, err := dbus.NewConfigFromFile(dbusConfigSession); err != nil {
|
||||||
fmsg.Fatalf("cannot load session bus proxy config from %q: %s", dbusConfigSession, err)
|
log.Fatalf("cannot load session bus proxy config from %q: %s", dbusConfigSession, err)
|
||||||
} else {
|
} else {
|
||||||
config.Confinement.SessionBus = c
|
config.Confinement.SessionBus = c
|
||||||
}
|
}
|
||||||
@ -276,7 +285,7 @@ func main() {
|
|||||||
// system bus proxy is optional
|
// system bus proxy is optional
|
||||||
if dbusConfigSystem != "nil" {
|
if dbusConfigSystem != "nil" {
|
||||||
if c, err := dbus.NewConfigFromFile(dbusConfigSystem); err != nil {
|
if c, err := dbus.NewConfigFromFile(dbusConfigSystem); err != nil {
|
||||||
fmsg.Fatalf("cannot load system bus proxy config from %q: %s", dbusConfigSystem, err)
|
log.Fatalf("cannot load system bus proxy config from %q: %s", dbusConfigSystem, err)
|
||||||
} else {
|
} else {
|
||||||
config.Confinement.SystemBus = c
|
config.Confinement.SystemBus = c
|
||||||
}
|
}
|
||||||
@ -291,17 +300,18 @@ func main() {
|
|||||||
|
|
||||||
// invoke app
|
// invoke app
|
||||||
runApp(config)
|
runApp(config)
|
||||||
|
panic("unreachable")
|
||||||
|
|
||||||
// internal commands
|
// internal commands
|
||||||
case "shim":
|
case "shim":
|
||||||
shim.Main()
|
shim.Main()
|
||||||
fmsg.Exit(0)
|
internal.Exit(0)
|
||||||
case "init":
|
case "init":
|
||||||
init0.Main()
|
init0.Main()
|
||||||
fmsg.Exit(0)
|
internal.Exit(0)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
fmsg.Fatalf("%q is not a valid command", args[0])
|
log.Fatalf("%q is not a valid command", args[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
@ -313,15 +323,15 @@ func runApp(config *fst.Config) {
|
|||||||
syscall.SIGINT, syscall.SIGTERM)
|
syscall.SIGINT, syscall.SIGTERM)
|
||||||
defer stop() // unreachable
|
defer stop() // unreachable
|
||||||
|
|
||||||
if fmsg.Verbose() {
|
if fmsg.Load() {
|
||||||
seccomp.CPrintln = fmsg.Println
|
seccomp.CPrintln = log.Println
|
||||||
}
|
}
|
||||||
|
|
||||||
if a, err := app.New(sys); err != nil {
|
if a, err := app.New(sys); err != nil {
|
||||||
fmsg.Fatalf("cannot create app: %s\n", err)
|
log.Fatalf("cannot create app: %s", err)
|
||||||
} else if err = a.Seal(config); err != nil {
|
} else if err = a.Seal(config); err != nil {
|
||||||
logBaseError(err, "cannot seal app:")
|
logBaseError(err, "cannot seal app:")
|
||||||
fmsg.Exit(1)
|
internal.Exit(1)
|
||||||
} else if err = a.Run(ctx, rs); err != nil {
|
} else if err = a.Run(ctx, rs); err != nil {
|
||||||
if !rs.Start {
|
if !rs.Start {
|
||||||
logBaseError(err, "cannot start app:")
|
logBaseError(err, "cannot start app:")
|
||||||
@ -334,8 +344,7 @@ func runApp(config *fst.Config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if rs.WaitErr != nil {
|
if rs.WaitErr != nil {
|
||||||
fmsg.Println("inner wait failed:", rs.WaitErr)
|
log.Println("inner wait failed:", rs.WaitErr)
|
||||||
}
|
}
|
||||||
fmsg.Exit(rs.ExitCode)
|
internal.Exit(rs.ExitCode)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
|
21
parse.go
21
parse.go
@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -21,11 +22,10 @@ func tryPath(name string) (config *fst.Config) {
|
|||||||
if name != "-" {
|
if name != "-" {
|
||||||
r = tryFd(name)
|
r = tryFd(name)
|
||||||
if r == nil {
|
if r == nil {
|
||||||
fmsg.VPrintln("load configuration from file")
|
fmsg.Verbose("load configuration from file")
|
||||||
|
|
||||||
if f, err := os.Open(name); err != nil {
|
if f, err := os.Open(name); err != nil {
|
||||||
fmsg.Fatalf("cannot access configuration file %q: %s", name, err)
|
log.Fatalf("cannot access configuration file %q: %s", name, err)
|
||||||
panic("unreachable")
|
|
||||||
} else {
|
} else {
|
||||||
// finalizer closes f
|
// finalizer closes f
|
||||||
r = f
|
r = f
|
||||||
@ -33,7 +33,7 @@ func tryPath(name string) (config *fst.Config) {
|
|||||||
} else {
|
} else {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := r.(io.ReadCloser).Close(); err != nil {
|
if err := r.(io.ReadCloser).Close(); err != nil {
|
||||||
fmsg.Printf("cannot close config fd: %v", err)
|
log.Printf("cannot close config fd: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@ -42,8 +42,7 @@ func tryPath(name string) (config *fst.Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := json.NewDecoder(r).Decode(&config); err != nil {
|
if err := json.NewDecoder(r).Decode(&config); err != nil {
|
||||||
fmsg.Fatalf("cannot load configuration: %v", err)
|
log.Fatalf("cannot load configuration: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -51,7 +50,7 @@ func tryPath(name string) (config *fst.Config) {
|
|||||||
|
|
||||||
func tryFd(name string) io.ReadCloser {
|
func tryFd(name string) io.ReadCloser {
|
||||||
if v, err := strconv.Atoi(name); err != nil {
|
if v, err := strconv.Atoi(name); err != nil {
|
||||||
fmsg.VPrintf("name cannot be interpreted as int64: %v", err)
|
fmsg.Verbosef("name cannot be interpreted as int64: %v", err)
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
fd := uintptr(v)
|
fd := uintptr(v)
|
||||||
@ -59,7 +58,7 @@ func tryFd(name string) io.ReadCloser {
|
|||||||
if errors.Is(errno, syscall.EBADF) {
|
if errors.Is(errno, syscall.EBADF) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
fmsg.Fatalf("cannot get fd %d: %v", fd, errno)
|
log.Fatalf("cannot get fd %d: %v", fd, errno)
|
||||||
}
|
}
|
||||||
return os.NewFile(fd, strconv.Itoa(v))
|
return os.NewFile(fd, strconv.Itoa(v))
|
||||||
}
|
}
|
||||||
@ -83,11 +82,11 @@ func tryShort(name string) (config *fst.Config, instance *state.State) {
|
|||||||
|
|
||||||
// try to match from state store
|
// try to match from state store
|
||||||
if likePrefix && len(name) >= 8 {
|
if likePrefix && len(name) >= 8 {
|
||||||
fmsg.VPrintln("argument looks like prefix")
|
fmsg.Verbose("argument looks like prefix")
|
||||||
|
|
||||||
s := state.NewMulti(sys.Paths().RunDirPath)
|
s := state.NewMulti(sys.Paths().RunDirPath)
|
||||||
if entries, err := state.Join(s); err != nil {
|
if entries, err := state.Join(s); err != nil {
|
||||||
fmsg.Printf("cannot join store: %v", err)
|
log.Printf("cannot join store: %v", err)
|
||||||
// drop to fetch from file
|
// drop to fetch from file
|
||||||
} else {
|
} else {
|
||||||
for id := range entries {
|
for id := range entries {
|
||||||
@ -99,7 +98,7 @@ func tryShort(name string) (config *fst.Config, instance *state.State) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
fmsg.VPrintf("instance %s skipped", v)
|
fmsg.Verbosef("instance %s skipped", v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
30
print.go
30
print.go
@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -12,7 +13,6 @@ import (
|
|||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/dbus"
|
"git.gensokyo.uk/security/fortify/dbus"
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/state"
|
"git.gensokyo.uk/security/fortify/internal/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ func printShowSystem(output io.Writer, short bool) {
|
|||||||
|
|
||||||
// get fid by querying uid of aid 0
|
// get fid by querying uid of aid 0
|
||||||
if uid, err := sys.Uid(0); err != nil {
|
if uid, err := sys.Uid(0); err != nil {
|
||||||
fmsg.Fatalf("cannot obtain uid from fsu: %v", err)
|
log.Fatalf("cannot obtain uid from fsu: %v", err)
|
||||||
} else {
|
} else {
|
||||||
info.User = (uid / 10000) - 100
|
info.User = (uid / 10000) - 100
|
||||||
}
|
}
|
||||||
@ -190,12 +190,12 @@ func printShowInstance(
|
|||||||
func printPs(output io.Writer, now time.Time, s state.Store, short bool) {
|
func printPs(output io.Writer, now time.Time, s state.Store, short bool) {
|
||||||
var entries state.Entries
|
var entries state.Entries
|
||||||
if e, err := state.Join(s); err != nil {
|
if e, err := state.Join(s); err != nil {
|
||||||
fmsg.Fatalf("cannot join store: %v", err)
|
log.Fatalf("cannot join store: %v", err)
|
||||||
} else {
|
} else {
|
||||||
entries = e
|
entries = e
|
||||||
}
|
}
|
||||||
if err := s.Close(); err != nil {
|
if err := s.Close(); err != nil {
|
||||||
fmsg.Printf("cannot close store: %v", err)
|
log.Printf("cannot close store: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !short && flagJSON {
|
if !short && flagJSON {
|
||||||
@ -212,13 +212,13 @@ func printPs(output io.Writer, now time.Time, s state.Store, short bool) {
|
|||||||
for id, instance := range entries {
|
for id, instance := range entries {
|
||||||
// gracefully skip nil states
|
// gracefully skip nil states
|
||||||
if instance == nil {
|
if instance == nil {
|
||||||
fmsg.Printf("got invalid state entry %s", id.String())
|
log.Printf("got invalid state entry %s", id.String())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// gracefully skip inconsistent states
|
// gracefully skip inconsistent states
|
||||||
if id != instance.ID {
|
if id != instance.ID {
|
||||||
fmsg.Printf("possible store corruption: entry %s has id %s",
|
log.Printf("possible store corruption: entry %s has id %s",
|
||||||
id.String(), instance.ID.String())
|
id.String(), instance.ID.String())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -273,8 +273,7 @@ func printJSON(output io.Writer, short bool, v any) {
|
|||||||
encoder.SetIndent("", " ")
|
encoder.SetIndent("", " ")
|
||||||
}
|
}
|
||||||
if err := encoder.Encode(v); err != nil {
|
if err := encoder.Encode(v); err != nil {
|
||||||
fmsg.Fatalf("cannot serialise: %v", err)
|
log.Fatalf("cannot serialise: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,31 +283,26 @@ type tp struct{ *tabwriter.Writer }
|
|||||||
|
|
||||||
func (p *tp) Printf(format string, a ...any) {
|
func (p *tp) Printf(format string, a ...any) {
|
||||||
if _, err := fmt.Fprintf(p, format, a...); err != nil {
|
if _, err := fmt.Fprintf(p, format, a...); err != nil {
|
||||||
fmsg.Fatalf("cannot write to tabwriter: %v", err)
|
log.Fatalf("cannot write to tabwriter: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (p *tp) Println(a ...any) {
|
func (p *tp) Println(a ...any) {
|
||||||
if _, err := fmt.Fprintln(p, a...); err != nil {
|
if _, err := fmt.Fprintln(p, a...); err != nil {
|
||||||
fmsg.Fatalf("cannot write to tabwriter: %v", err)
|
log.Fatalf("cannot write to tabwriter: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (p *tp) MustFlush() {
|
func (p *tp) MustFlush() {
|
||||||
if err := p.Writer.Flush(); err != nil {
|
if err := p.Writer.Flush(); err != nil {
|
||||||
fmsg.Fatalf("cannot flush tabwriter: %v", err)
|
log.Fatalf("cannot flush tabwriter: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func mustPrint(output io.Writer, a ...any) {
|
func mustPrint(output io.Writer, a ...any) {
|
||||||
if _, err := fmt.Fprint(output, a...); err != nil {
|
if _, err := fmt.Fprint(output, a...); err != nil {
|
||||||
fmsg.Fatalf("cannot print: %v", err)
|
log.Fatalf("cannot print: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func mustPrintln(output io.Writer, a ...any) {
|
func mustPrintln(output io.Writer, a ...any) {
|
||||||
if _, err := fmt.Fprintln(output, a...); err != nil {
|
if _, err := fmt.Fprintln(output, a...); err != nil {
|
||||||
fmsg.Fatalf("cannot print: %v", err)
|
log.Fatalf("cannot print: %v", err)
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user