Skip to content

Commit 295b26b

Browse files
authored
patchpkg: add packageFS to handle store paths (#2246)
Add a `packageFS` type that extends `fs.FS` to translate to/from operating system paths. This makes it a little easier to get real file paths when writing or passing files to external commands (like `patchelf`).
1 parent 38b8144 commit 295b26b

File tree

1 file changed

+62
-22
lines changed

1 file changed

+62
-22
lines changed

internal/patchpkg/builder.go

Lines changed: 62 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,29 @@ func (d *DerivationBuilder) init() error {
4848
// Build applies patches to a package store path and puts the result in the
4949
// d.Out directory.
5050
func (d *DerivationBuilder) Build(ctx context.Context, pkgStorePath string) error {
51-
slog.DebugContext(ctx, "starting build of patched package", "pkg", pkgStorePath, "out", d.Out)
51+
if err := d.init(); err != nil {
52+
return err
53+
}
5254

55+
slog.DebugContext(ctx, "starting build to patch package",
56+
"pkg", pkgStorePath, "out", d.Out)
57+
return d.build(ctx, newPackageFS(pkgStorePath), newPackageFS(d.Out))
58+
}
59+
60+
func (d *DerivationBuilder) build(ctx context.Context, pkg, out *packageFS) error {
5361
var err error
54-
pkgFS := os.DirFS(pkgStorePath)
55-
for path, entry := range allFiles(pkgFS, ".") {
62+
for path, entry := range allFiles(pkg, ".") {
63+
if ctx.Err() != nil {
64+
return err
65+
}
66+
5667
switch {
5768
case entry.IsDir():
58-
err = d.copyDir(path)
69+
err = d.copyDir(out, path)
5970
case isSymlink(entry.Type()):
60-
err = d.copySymlink(pkgStorePath, path)
71+
err = d.copySymlink(pkg, out, path)
6172
default:
62-
err = d.copyFile(pkgFS, path)
73+
err = d.copyFile(pkg, out, path)
6374
}
6475

6576
if err != nil {
@@ -74,16 +85,16 @@ func (d *DerivationBuilder) Build(ctx context.Context, pkgStorePath string) erro
7485
return cmd.Run()
7586
}
7687

77-
func (d *DerivationBuilder) copyDir(path string) error {
78-
osPath, err := filepath.Localize(path)
88+
func (d *DerivationBuilder) copyDir(out *packageFS, path string) error {
89+
path, err := out.OSPath(path)
7990
if err != nil {
8091
return err
8192
}
82-
return os.Mkdir(filepath.Join(d.Out, osPath), 0o777)
93+
return os.Mkdir(path, 0o777)
8394
}
8495

85-
func (d *DerivationBuilder) copyFile(pkgFS fs.FS, path string) error {
86-
src, err := pkgFS.Open(path)
96+
func (d *DerivationBuilder) copyFile(pkg, out *packageFS, path string) error {
97+
src, err := pkg.Open(path)
8798
if err != nil {
8899
return err
89100
}
@@ -102,12 +113,10 @@ func (d *DerivationBuilder) copyFile(pkgFS fs.FS, path string) error {
102113
perm = fs.FileMode(0o777)
103114
}
104115

105-
osPath, err := filepath.Localize(path)
116+
dstPath, err := out.OSPath(path)
106117
if err != nil {
107118
return err
108119
}
109-
dstPath := filepath.Join(d.Out, osPath)
110-
111120
dst, err := os.OpenFile(dstPath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, perm)
112121
if err != nil {
113122
return err
@@ -121,22 +130,53 @@ func (d *DerivationBuilder) copyFile(pkgFS fs.FS, path string) error {
121130
return dst.Close()
122131
}
123132

124-
func (d *DerivationBuilder) copySymlink(pkgStorePath, path string) error {
125-
// The fs package doesn't support symlinks, so we need to convert the
126-
// path back to an OS path to see what it points to.
127-
osPath, err := filepath.Localize(path)
133+
func (d *DerivationBuilder) copySymlink(pkg, out *packageFS, path string) error {
134+
link, err := out.OSPath(path)
128135
if err != nil {
129136
return err
130137
}
131-
target, err := os.Readlink(filepath.Join(pkgStorePath, osPath))
138+
target, err := pkg.Readlink(path)
132139
if err != nil {
133140
return err
134141
}
135-
// TODO(gcurtis): translate absolute symlink targets to relative paths.
136-
return os.Symlink(target, filepath.Join(d.Out, osPath))
142+
return os.Symlink(target, link)
143+
}
144+
145+
// packageFS is the tree of files for a package in the Nix store.
146+
type packageFS struct {
147+
fs.FS
148+
storePath string
149+
}
150+
151+
// newPackageFS returns a packageFS for the given store path.
152+
func newPackageFS(storePath string) *packageFS {
153+
return &packageFS{
154+
FS: os.DirFS(storePath),
155+
storePath: storePath,
156+
}
157+
}
158+
159+
// Readlink returns the destination of a symlink.
160+
func (p *packageFS) Readlink(path string) (string, error) {
161+
osPath, err := p.OSPath(path)
162+
if err != nil {
163+
return "", err
164+
}
165+
// TODO(gcurtis): check that the symlink isn't absolute or points
166+
// outside the Nix store.
167+
return os.Readlink(osPath)
168+
}
169+
170+
// OSPath translates a package-relative path to an operating system path.
171+
func (p *packageFS) OSPath(path string) (string, error) {
172+
local, err := filepath.Localize(path)
173+
if err != nil {
174+
return "", err
175+
}
176+
return filepath.Join(p.storePath, local), nil
137177
}
138178

139-
// RegularFiles iterates over all files in fsys starting at root. It silently
179+
// allFiles iterates over all files in fsys starting at root. It silently
140180
// ignores errors.
141181
func allFiles(fsys fs.FS, root string) iter.Seq2[string, fs.DirEntry] {
142182
return func(yield func(string, fs.DirEntry) bool) {

0 commit comments

Comments
 (0)