-
Notifications
You must be signed in to change notification settings - Fork 49
user: add filesystem-aware mkdir/chown helpers #214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| module github.com/moby/sys/user | ||
|
|
||
| go 1.18 | ||
| go 1.24 | ||
|
|
||
| require golang.org/x/sys v0.1.0 | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,6 +3,21 @@ package user | |||||||||||||
| import ( | ||||||||||||||
| "fmt" | ||||||||||||||
| "os" | ||||||||||||||
| "path/filepath" | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| // FS is the filesystem contract used by the Mkdir*AndChownFS helpers. | ||||||||||||||
| type FS interface { | ||||||||||||||
| Stat(name string) (os.FileInfo, error) | ||||||||||||||
| Mkdir(name string, perm os.FileMode) error | ||||||||||||||
| MkdirAll(name string, perm os.FileMode) error | ||||||||||||||
| Chmod(name string, mode os.FileMode) error | ||||||||||||||
| Chown(name string, uid, gid int) error | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| var ( | ||||||||||||||
| _ FS = &os.Root{} | ||||||||||||||
| _ FS = &hostFS{} | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| // MkdirOpt is a type for options to pass to Mkdir calls | ||||||||||||||
|
|
@@ -23,12 +38,24 @@ func WithOnlyNew(o *mkdirOptions) { | |||||||||||||
| // function will still change ownership and permissions. If WithOnlyNew is passed as an | ||||||||||||||
| // option, then only the newly created directories will have ownership and permissions changed. | ||||||||||||||
| func MkdirAllAndChown(path string, mode os.FileMode, uid, gid int, opts ...MkdirOpt) error { | ||||||||||||||
| var options mkdirOptions | ||||||||||||||
| for _, opt := range opts { | ||||||||||||||
| opt(&options) | ||||||||||||||
| return MkdirAllAndChownFS(nil, path, mode, uid, gid, opts...) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // MkdirAllAndChownFS creates a directory (including any along the path) on the | ||||||||||||||
| // provided filesystem and then modifies ownership to the requested uid/gid. If | ||||||||||||||
| // fsys is nil, the host filesystem is used. | ||||||||||||||
| func MkdirAllAndChownFS(fsys FS, path string, mode os.FileMode, uid, gid int, opts ...MkdirOpt) error { | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at the signature; I see the variadic options at the end; did we add those later (but kept the positional options because they already were there?) If that's the case, and we want to make more of these "optional", then now is the opportunity to change the signature for the new functions we added. (also looking at windows <--> linux w.r.t. "uid", "gid" etc)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given that |
||||||||||||||
| if fsys == nil { | ||||||||||||||
| absPath, err := filepath.Abs(path) | ||||||||||||||
| if err != nil { | ||||||||||||||
| return err | ||||||||||||||
| } | ||||||||||||||
| fsys = &hostFS{} | ||||||||||||||
| path = absPath | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| return mkdirAs(path, mode, uid, gid, true, options.onlyNew) | ||||||||||||||
| options := mkdirOpts(opts) | ||||||||||||||
| return mkdirAs(fsys, path, mode, uid, gid, true, options.onlyNew) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // MkdirAndChown creates a directory and then modifies ownership to the requested uid/gid. | ||||||||||||||
|
|
@@ -38,11 +65,32 @@ func MkdirAllAndChown(path string, mode os.FileMode, uid, gid int, opts ...Mkdir | |||||||||||||
| // Note that unlike os.Mkdir(), this function does not return IsExist error | ||||||||||||||
| // in case path already exists. | ||||||||||||||
| func MkdirAndChown(path string, mode os.FileMode, uid, gid int, opts ...MkdirOpt) error { | ||||||||||||||
|
Comment on lines
66
to
67
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same for this one;
Suggested change
|
||||||||||||||
| return MkdirAndChownFS(nil, path, mode, uid, gid, opts...) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // MkdirAndChownFS creates a directory on the provided filesystem and then | ||||||||||||||
| // modifies ownership to the requested uid/gid. If fsys is nil, the host | ||||||||||||||
| // filesystem is used. | ||||||||||||||
| func MkdirAndChownFS(fsys FS, path string, mode os.FileMode, uid, gid int, opts ...MkdirOpt) error { | ||||||||||||||
| if fsys == nil { | ||||||||||||||
| absPath, err := filepath.Abs(path) | ||||||||||||||
| if err != nil { | ||||||||||||||
| return err | ||||||||||||||
| } | ||||||||||||||
| fsys = &hostFS{} | ||||||||||||||
| path = absPath | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| options := mkdirOpts(opts) | ||||||||||||||
| return mkdirAs(fsys, path, mode, uid, gid, false, options.onlyNew) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| func mkdirOpts(opts []MkdirOpt) mkdirOptions { | ||||||||||||||
| var options mkdirOptions | ||||||||||||||
| for _, opt := range opts { | ||||||||||||||
| opt(&options) | ||||||||||||||
| } | ||||||||||||||
| return mkdirAs(path, mode, uid, gid, false, options.onlyNew) | ||||||||||||||
| return options | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // getRootUIDGID retrieves the remapped root uid/gid pair from the set of maps. | ||||||||||||||
|
|
@@ -139,3 +187,25 @@ func (i IdentityMapping) ToContainer(uid, gid int) (int, int, error) { | |||||||||||||
| func (i IdentityMapping) Empty() bool { | ||||||||||||||
| return len(i.UIDMaps) == 0 && len(i.GIDMaps) == 0 | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| type hostFS struct{} | ||||||||||||||
|
|
||||||||||||||
| func (*hostFS) Stat(name string) (os.FileInfo, error) { | ||||||||||||||
| return os.Stat(name) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| func (*hostFS) Mkdir(name string, perm os.FileMode) error { | ||||||||||||||
| return os.Mkdir(name, perm) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| func (*hostFS) MkdirAll(name string, perm os.FileMode) error { | ||||||||||||||
| return os.MkdirAll(name, perm) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| func (*hostFS) Chmod(name string, mode os.FileMode) error { | ||||||||||||||
| return os.Chmod(name, mode) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| func (*hostFS) Chown(name string, uid, gid int) error { | ||||||||||||||
| return os.Chown(name, uid, gid) | ||||||||||||||
| } | ||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we want users of this module to migrate to the new functions, we can add a
//go:fix inline, thengo fixwill automatically migrate them 🎉There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case (if you want everyone to migrate) it also makes sense to mark old ones as deprecated.
OTOH if you want the new functions signatures to be simpler as you're suggesting above, the go:fix won't be possible (or easy).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, deprecating is an option for sure; didn't want to make that a strict requiremnt for this PR (good as a follow-up, also for visibility).
I'd be fine with keeping the signature the same, but at least wanted to mention the option, because now we have the chance to do so.
Specifically in these cases where Golang tries to abstract away platform differences, and on Windows making (e.g.)
ChownandChmod(for permissions) a no-op, silently discarding options that the user may have expected to secure things.Arguments that are required should probably still be positional though (which is the fun thing here if those are required on Linux, but won't take effect on Windows I guess); happy for input on that though!
Yeah,
//go:fixis not a MUST, but can help. It can do a pretty decent job; erm, but of course when I was about to write an example, I found yet another corner-case 😂 - adding it to my list of reports; golang/go#78432