-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
stepwriter: Add OS that replicates the current logic
- Loading branch information
1 parent
a86389a
commit 1263015
Showing
4 changed files
with
281 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
// SPDX-FileCopyrightText: Fabio Forni <development@redaril.me> | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package stepwriter | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"io/fs" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
|
||
"github.com/livingsilver94/backee/installer" | ||
"github.com/livingsilver94/backee/privilege" | ||
"github.com/livingsilver94/backee/service" | ||
) | ||
|
||
func init() { | ||
privilege.RegisterInterfaceImpl(symlinkWriter{}) | ||
privilege.RegisterInterfaceImpl(fileCopyWriter{}) | ||
privilege.RegisterInterfaceImpl(privilegedPathWriter{}) | ||
} | ||
|
||
type OS struct{} | ||
|
||
func (OS) Setup(script string) error { | ||
return runScript(script) | ||
} | ||
|
||
func (OS) InstallPackages(fullCmd []string) error { | ||
return runProcess(fullCmd[0], fullCmd[1:]...) | ||
} | ||
|
||
func (OS) SymlinkFile(dst service.FilePath, src string) error { | ||
return writePossiblyPrivilegedPath(dst, &symlinkWriter{SrcPath: src}) | ||
} | ||
|
||
func (OS) CopyFile(dst service.FilePath, src installer.FileCopy) error { | ||
return writePossiblyPrivilegedPath(dst, &fileCopyWriter{}) | ||
} | ||
|
||
func (OS) Finalize(script string) error { | ||
return runScript(script) | ||
} | ||
|
||
type fileWriter interface { | ||
writeFile(dst string) error | ||
} | ||
|
||
func writePath(dst service.FilePath, wr fileWriter) error { | ||
err := os.MkdirAll(filepath.Dir(dst.Path), 0755) | ||
if err != nil { | ||
return err | ||
} | ||
err = wr.writeFile(dst.Path) | ||
if err != nil { | ||
return err | ||
} | ||
if dst.Mode != 0 { | ||
return os.Chmod(dst.Path, fs.FileMode(dst.Mode)) | ||
} | ||
return nil | ||
} | ||
|
||
func writePathPrivileged(dst service.FilePath, wr fileWriter) error { | ||
var r privilege.Runner = privilegedPathWriter{Dst: dst, Wr: wr} | ||
return privilege.Run(r) | ||
} | ||
|
||
func writePossiblyPrivilegedPath(dst service.FilePath, wr fileWriter) error { | ||
err := writePath(dst, wr) | ||
if err != nil { | ||
if !errors.Is(err, fs.ErrPermission) { | ||
return err | ||
} | ||
err = writePathPrivileged(dst, wr) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
type symlinkWriter struct { | ||
SrcPath string | ||
} | ||
|
||
func (w symlinkWriter) writeFile(dst string) error { | ||
err := os.Symlink(w.SrcPath, dst) | ||
if err != nil { | ||
if !errors.Is(err, fs.ErrExist) { | ||
return err | ||
} | ||
eq, errEq := w.isSymlinkEqual(dst) | ||
if errEq != nil { | ||
return errEq | ||
} | ||
if !eq { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (w *symlinkWriter) isSymlinkEqual(dst string) (bool, error) { | ||
eq, err := filepath.EvalSymlinks(dst) | ||
if err != nil { | ||
return false, err | ||
} | ||
return eq == w.SrcPath, nil | ||
} | ||
|
||
type fileCopyWriter struct { | ||
FileCopy installer.FileCopy | ||
} | ||
|
||
func (w fileCopyWriter) writeFile(dst string) error { | ||
file, err := os.Create(dst) | ||
if err != nil { | ||
return err | ||
} | ||
defer file.Close() | ||
_, err = w.FileCopy.WriteTo(file) | ||
return err | ||
} | ||
|
||
type privilegedPathWriter struct { | ||
Dst service.FilePath | ||
Wr fileWriter | ||
} | ||
|
||
func (p privilegedPathWriter) RunPrivileged() error { | ||
return writePath(p.Dst, p.Wr) | ||
} | ||
|
||
func runProcess(name string, arg ...string) error { | ||
cmd := exec.Command(name, arg...) | ||
cmd.Stdout = nil | ||
cmd.Stderr = os.Stderr | ||
return cmd.Run() | ||
} | ||
|
||
type UnixID struct { | ||
UID uint32 | ||
GID uint32 | ||
} | ||
|
||
func PathOwner(path string) (UnixID, error) { | ||
return PathOwnerFS(os.DirFS(path), ".") | ||
} | ||
|
||
func parentPathOwner(path string) (UnixID, error) { | ||
for { | ||
if len(path) == 1 { | ||
return UnixID{}, fmt.Errorf("parent directory of %s: %w", path, fs.ErrNotExist) | ||
} | ||
id, err := PathOwner(path) | ||
if err != nil { | ||
if !errors.Is(err, fs.ErrNotExist) { | ||
return UnixID{}, err | ||
} | ||
path = filepath.Dir(path) | ||
continue | ||
} | ||
return id, nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
//go:build unix | ||
|
||
// SPDX-FileCopyrightText: Fabio Forni <development@redaril.me> | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package stepwriter | ||
|
||
import ( | ||
"fmt" | ||
"io/fs" | ||
"syscall" | ||
) | ||
|
||
func runScript(script string) error { | ||
return runProcess( | ||
"sh", | ||
"-e", // Stop script on first error. | ||
"-c", // Run the following script string. | ||
script, | ||
) | ||
} | ||
|
||
func PathOwnerFS(sys fs.FS, path string) (UnixID, error) { | ||
info, err := fs.Stat(sys, path) | ||
if err != nil { | ||
return UnixID{}, err | ||
} | ||
stat := info.Sys().(*syscall.Stat_t) | ||
return UnixID{UID: stat.Uid, GID: stat.Gid}, nil | ||
} | ||
|
||
func RunAsUnixID(f func() error, id UnixID) error { | ||
oldUID := syscall.Geteuid() | ||
oldGID := syscall.Getegid() | ||
err := syscall.Setegid(int(id.GID)) | ||
if err != nil { | ||
return fmt.Errorf("setting GID %d: %w", id.GID, err) | ||
} | ||
defer syscall.Setegid(oldGID) | ||
err = syscall.Seteuid(int(id.UID)) | ||
if err != nil { | ||
return fmt.Errorf("setting UID %d: %w", id.UID, err) | ||
} | ||
defer syscall.Seteuid(oldUID) | ||
return f() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
//go:build unix | ||
|
||
// SPDX-FileCopyrightText: Fabio Forni <development@redaril.me> | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package stepwriter_test | ||
|
||
import ( | ||
"syscall" | ||
"testing" | ||
"testing/fstest" | ||
|
||
"github.com/livingsilver94/backee/installer/stepwriter" | ||
) | ||
|
||
func TestUnixIDsFS(t *testing.T) { | ||
const expUID = 123 | ||
const expGID = 456 | ||
fs := fstest.MapFS{ | ||
"file.txt": &fstest.MapFile{Sys: &syscall.Stat_t{Uid: expUID, Gid: expGID}}, | ||
} | ||
|
||
id, err := stepwriter.PathOwnerFS(fs, "file.txt") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if id.UID != expUID || id.GID != expGID { | ||
t.Fatalf("expected UID %d and GID %d. Got %d and %d", expUID, expGID, id.UID, id.GID) | ||
} | ||
} | ||
|
||
func TestRunAs(t *testing.T) { | ||
f := func() error { return nil } | ||
uid := syscall.Getuid() | ||
gid := syscall.Getgid() | ||
err := stepwriter.RunAsUnixID(f, stepwriter.UnixID{UID: uint32(uid), GID: uint32(gid)}) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
//go:build windows | ||
|
||
// SPDX-FileCopyrightText: Fabio Forni <development@redaril.me> | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package stepwriter | ||
|
||
import ( | ||
"io/fs" | ||
) | ||
|
||
func runScript(script string) error { | ||
return runProcess( | ||
"powershell", | ||
"-NoLogo", // Hide copyright banner. | ||
"-Command", // Run the following script string. | ||
script, | ||
) | ||
} | ||
|
||
func PathOwnerFS(sys fs.FS, path string) (UnixID, error) { | ||
return UnixID{}, nil | ||
} | ||
|
||
func RunAsUnixID(f func() error, id UnixID) error { | ||
return f() | ||
} |