-
Notifications
You must be signed in to change notification settings - Fork 395
sif: initial sif transport implementation #1402
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
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 | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,238 @@ | ||||||||||||
| // +build linux | ||||||||||||
|
|
||||||||||||
| package internal | ||||||||||||
|
|
||||||||||||
| import ( | ||||||||||||
| "bufio" | ||||||||||||
| "bytes" | ||||||||||||
| "fmt" | ||||||||||||
| "io" | ||||||||||||
| "io/ioutil" | ||||||||||||
| "os" | ||||||||||||
| "os/exec" | ||||||||||||
| "path/filepath" | ||||||||||||
| "strings" | ||||||||||||
|
|
||||||||||||
| "github.com/containers/image/v5/sif/sif" | ||||||||||||
| "github.com/pkg/errors" | ||||||||||||
|
|
||||||||||||
| imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" | ||||||||||||
| ) | ||||||||||||
|
|
||||||||||||
| type SifImage struct { | ||||||||||||
| fimg sif.FileImage | ||||||||||||
| rootfs *sif.Descriptor | ||||||||||||
| deffile *sif.Descriptor | ||||||||||||
| defReader *io.SectionReader | ||||||||||||
| cmdlist []string | ||||||||||||
| runscript *bytes.Buffer | ||||||||||||
| env *sif.Descriptor | ||||||||||||
| envReader *io.SectionReader | ||||||||||||
| envlist []string | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| func LoadSIFImage(path string) (image SifImage, err error) { | ||||||||||||
| // open up the SIF file and get its header | ||||||||||||
| image.fimg, err = sif.LoadContainer(path, true) | ||||||||||||
| if err != nil { | ||||||||||||
| return | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // check for a system partition and save it | ||||||||||||
| image.rootfs, _, err = image.fimg.GetPartPrimSys() | ||||||||||||
| if err != nil { | ||||||||||||
| return SifImage{}, errors.Wrap(err, "looking up rootfs from SIF file") | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // look for a definition file object | ||||||||||||
| searchDesc := sif.Descriptor{Datatype: sif.DataDeffile} | ||||||||||||
| resultDescs, _, err := image.fimg.GetFromDescr(searchDesc) | ||||||||||||
| if err == nil && resultDescs != nil { | ||||||||||||
| // we assume in practice that typical SIF files don't hold multiple deffiles | ||||||||||||
|
Collaborator
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. Should there be a warning, or at least a debug log, in that case? |
||||||||||||
| image.deffile = resultDescs[0] | ||||||||||||
| image.defReader = io.NewSectionReader(image.fimg.Fp, image.deffile.Fileoff, image.deffile.Filelen) | ||||||||||||
|
Collaborator
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.
|
||||||||||||
| } | ||||||||||||
| if err = image.generateConfig(); err != nil { | ||||||||||||
| return SifImage{}, err | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // look for an environment variable set object | ||||||||||||
| searchDesc = sif.Descriptor{Datatype: sif.DataEnvVar} | ||||||||||||
| resultDescs, _, err = image.fimg.GetFromDescr(searchDesc) | ||||||||||||
| if err == nil && resultDescs != nil { | ||||||||||||
| // we assume in practice that typical SIF files don't hold multiple EnvVar sets | ||||||||||||
|
Collaborator
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. Should there be a warning, or at least a debug log, in that case? |
||||||||||||
| image.env = resultDescs[0] | ||||||||||||
| image.envReader = io.NewSectionReader(image.fimg.Fp, image.env.Fileoff, image.env.Filelen) | ||||||||||||
| } | ||||||||||||
|
Comment on lines
+59
to
+66
Collaborator
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. I can’t see any use of |
||||||||||||
|
|
||||||||||||
| return image, nil | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| func (image *SifImage) parseEnvironment(scanner *bufio.Scanner) error { | ||||||||||||
| for scanner.Scan() { | ||||||||||||
| s := strings.TrimSpace(scanner.Text()) | ||||||||||||
| if s == "" || strings.HasPrefix(s, "#") { | ||||||||||||
| continue | ||||||||||||
| } | ||||||||||||
| if strings.HasPrefix(s, "%") { | ||||||||||||
| return nil | ||||||||||||
| } | ||||||||||||
| image.envlist = append(image.envlist, s) | ||||||||||||
| } | ||||||||||||
| if err := scanner.Err(); err != nil { | ||||||||||||
| return errors.Wrap(err, "parsing environment from SIF definition file object") | ||||||||||||
| } | ||||||||||||
| return nil | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| func (image *SifImage) parseRunscript(scanner *bufio.Scanner) error { | ||||||||||||
| for scanner.Scan() { | ||||||||||||
| s := strings.TrimSpace(scanner.Text()) | ||||||||||||
| if strings.HasPrefix(s, "%") { | ||||||||||||
| return nil | ||||||||||||
| } | ||||||||||||
| image.cmdlist = append(image.cmdlist, s) | ||||||||||||
| } | ||||||||||||
| if err := scanner.Err(); err != nil { | ||||||||||||
| return errors.Wrap(err, "parsing runscript from SIF definition file object") | ||||||||||||
| } | ||||||||||||
| return nil | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| func (image *SifImage) generateRunscript() error { | ||||||||||||
| base := `#!/bin/bash | ||||||||||||
| ` | ||||||||||||
| image.runscript = bytes.NewBufferString(base) | ||||||||||||
| for _, s := range image.envlist { | ||||||||||||
| _, err := image.runscript.WriteString(fmt.Sprintln(s)) | ||||||||||||
| if err != nil { | ||||||||||||
| return errors.Wrap(err, "writing to runscript buffer") | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| for _, s := range image.cmdlist { | ||||||||||||
| _, err := image.runscript.WriteString(fmt.Sprintln(s)) | ||||||||||||
|
Comment on lines
+107
to
+113
Collaborator
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.
Contributor
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. how so?
Collaborator
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. What if cmdlist contains Luckily, with Go, it’s not a trivially-exploited arbitrary-code-execution vulnerability, but it’s both useless and it exposes the format string processing code to arbitrary potentially-malicious input, something it’s not really intended for.
Contributor
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. After two months I need to confess that I need to sit down and get back into the context, and I will do that sometime soon. Thanks for the thorough review BTW, but are we not talking about an Sprint(a) vs an Sprintf(format, a) construct here, where Sprintln(a) does not interpret anything? Or are you trying to make another point?
Collaborator
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. You’re right, my mistake; this is not interpreting as a format string, so it is safe. (It doesn’t quite make sense to me to do it this way — either performance and memory use is critical, in which case Assuming the performance of this not that much of a concern, that can be deferred for some indeterminate future.) |
||||||||||||
| if err != nil { | ||||||||||||
| return errors.Wrap(err, "writing to runscript buffer") | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| return nil | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| func (image *SifImage) generateConfig() error { | ||||||||||||
| if image.deffile == nil { | ||||||||||||
| image.cmdlist = append(image.cmdlist, "bash") | ||||||||||||
| return nil | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // extract %environment/%runscript from definition file | ||||||||||||
| var err error | ||||||||||||
| scanner := bufio.NewScanner(image.defReader) | ||||||||||||
| for scanner.Scan() { | ||||||||||||
| s := strings.TrimSpace(scanner.Text()) | ||||||||||||
| again: | ||||||||||||
| if s == `%environment` { | ||||||||||||
| if err = image.parseEnvironment(scanner); err != nil { | ||||||||||||
| return err | ||||||||||||
| } | ||||||||||||
| } else if s == `%runscript` { | ||||||||||||
| if err = image.parseRunscript(scanner); err != nil { | ||||||||||||
| return err | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| s = strings.TrimSpace(scanner.Text()) | ||||||||||||
|
Collaborator
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. This seems to be redundant with the line at the top of the loop at a first glance, and is very non-obvious. At the very least for scanner.Scan() {
again:
// parseEnvironment and parseRunscript, will, on success, scan one line past its section, so we need to
// return here without going through scanner.Scan()
s := strings.TrimSpace(scanner.Text())
if s == `%environment` {
if err = image.parseEnvironment(scanner); err != nil {
return err
}
goto again
} else if s == `%runscript` {
if err = image.parseRunscript(scanner); err != nil {
return err
}
goto again
}
}including the comment, but that AFAICS doesn’t handle EOF in So, how about something vaguely like // enum state = parsingEnvironment/parsingRunscript/parsingOther
for scanner.Scan() {
s := strings.TrimSpace(scanner.Text())
switch {
case s == "%environment": state = parsingEnvironment
case s == "%runscript": state = parsingRunscript
case strings.HasPrefix(s, "%"): state = parsingOther // Warn on unrecognized?
case state == parsingEnvironment: image.parseEnvironmentEntry(s)
case state == parsingRunscript: image.parseRunscriptEntry(s)
default: // ignore s, or warn?
}
}?
Contributor
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. have you tested it?
Collaborator
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. No. I’m not claiming that the code as is doesn’t work (well, apart from a trailing directive?), just that it looks very non-obvious. If there are even more non-obvious gotchas that I didn’t notice by reading the code as is, those gotchas should be commented in detail and/or covered by unit tests.
Collaborator
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. So I’m not sure which “it” I was supposed to test, but looking at https://github.com/mtrmac/image/tree/sif-wip :
|
||||||||||||
| if s == `%environment` || s == `%runscript` { | ||||||||||||
| goto again | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| if err := scanner.Err(); err != nil { | ||||||||||||
| return errors.Wrap(err, "reading lines from SIF definition file object") | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| if len(image.cmdlist) == 0 && len(image.envlist) == 0 { | ||||||||||||
| image.cmdlist = append(image.cmdlist, "bash") | ||||||||||||
| } else { | ||||||||||||
| if err = image.generateRunscript(); err != nil { | ||||||||||||
| return errors.Wrap(err, "generating runscript") | ||||||||||||
| } | ||||||||||||
| image.cmdlist = []string{"/podman/runscript"} | ||||||||||||
|
Collaborator
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.
|
||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| return nil | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| func (image SifImage) GetConfig(config *imgspecv1.Image) error { | ||||||||||||
|
Collaborator
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. The name of this function doesn’t match what it does. Maybe a |
||||||||||||
| config.Config.Cmd = append(config.Config.Cmd, image.cmdlist...) | ||||||||||||
| return nil | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| func (image SifImage) UnloadSIFImage() (err error) { | ||||||||||||
| err = image.fimg.UnloadContainer() | ||||||||||||
| return | ||||||||||||
|
Comment on lines
+168
to
+170
Collaborator
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.
Suggested change
|
||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| func (image SifImage) GetSIFID() string { | ||||||||||||
| return image.fimg.Header.ID.String() | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| func (image SifImage) GetSIFArch() string { | ||||||||||||
| return sif.GetGoArch(string(image.fimg.Header.Arch[:sif.HdrArchLen-1])) | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| const squashFilename = "rootfs.squashfs" | ||||||||||||
|
Collaborator
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. (Absolutely non-blocking: Below is not too much code that it would be unmanageable, still, the way every function gets a A top-level |
||||||||||||
| const tarFilename = "rootfs.tar" | ||||||||||||
|
|
||||||||||||
| func runUnSquashFSTar(tempdir string) (err error) { | ||||||||||||
| script := ` | ||||||||||||
| #!/bin/sh | ||||||||||||
| unsquashfs -f ` + squashFilename + ` && tar --acls --xattrs -C ./squashfs-root -cpf ` + tarFilename + ` ./ | ||||||||||||
|
Collaborator
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. Shouldn’t |
||||||||||||
| ` | ||||||||||||
|
|
||||||||||||
| if err = ioutil.WriteFile(filepath.Join(tempdir, "script"), []byte(script), 0755); err != nil { | ||||||||||||
| return err | ||||||||||||
| } | ||||||||||||
| cmd := []string{"fakeroot", "--", "./script"} | ||||||||||||
|
Collaborator
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. Non-blocking: I don’t think we need this single-use variable; this can be just constants passed to And then |
||||||||||||
|
|
||||||||||||
| xcmd := exec.Command(cmd[0], cmd[1:]...) | ||||||||||||
| xcmd.Stderr = os.Stderr | ||||||||||||
|
Collaborator
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. All of the output should be captured and provided to the caller (on error only, I guess); a library like c/image shouldn’t just write unexpected data to the standard streams. Probably using |
||||||||||||
| xcmd.Dir = tempdir | ||||||||||||
| err = xcmd.Run() | ||||||||||||
| return | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| func (image *SifImage) writeRunscript(tempdir string) (err error) { | ||||||||||||
| if image.runscript == nil { | ||||||||||||
| return nil | ||||||||||||
| } | ||||||||||||
| rsPath := filepath.Join(tempdir, "squashfs-root", "podman") | ||||||||||||
|
Collaborator
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. If |
||||||||||||
| if err = os.MkdirAll(rsPath, 0755); err != nil { | ||||||||||||
| return | ||||||||||||
| } | ||||||||||||
| if err = ioutil.WriteFile(filepath.Join(rsPath, "runscript"), image.runscript.Bytes(), 0755); err != nil { | ||||||||||||
| return errors.Wrap(err, "writing /podman/runscript") | ||||||||||||
| } | ||||||||||||
| return nil | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| func (image SifImage) SquashFSToTarLayer(tempdir string) (tarpath string, err error) { | ||||||||||||
| if _, err = image.fimg.Fp.Seek(image.rootfs.Fileoff, 0); err != nil { | ||||||||||||
| return | ||||||||||||
| } | ||||||||||||
| f, err := os.Create(filepath.Join(tempdir, squashFilename)) | ||||||||||||
|
Collaborator
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. Shouldn’t the file be removed by the time this function returns, so that we don’t unnecessarily use up disk space for another copy of the data? (Do we even need a copy?
Collaborator
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.
This does work, but sadly isn’t available in RHEL 8. |
||||||||||||
| if err != nil { | ||||||||||||
| return | ||||||||||||
| } | ||||||||||||
| defer f.Close() | ||||||||||||
| if _, err = io.CopyN(f, image.fimg.Fp, image.rootfs.Filelen); err != nil { | ||||||||||||
| return | ||||||||||||
| } | ||||||||||||
| if err = f.Sync(); err != nil { | ||||||||||||
|
Collaborator
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. Why is this necessary? |
||||||||||||
| return | ||||||||||||
| } | ||||||||||||
| if err = image.writeRunscript(tempdir); err != nil { | ||||||||||||
|
Collaborator
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. (Non-blocking? It’s non-obvious that |
||||||||||||
| return | ||||||||||||
| } | ||||||||||||
| if err = runUnSquashFSTar(tempdir); err != nil { | ||||||||||||
| return | ||||||||||||
| } | ||||||||||||
| return filepath.Join(tempdir, tarFilename), nil | ||||||||||||
| } | ||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| Copyright (c) 2018, Sylabs Inc. All rights reserved. | ||
|
|
||
| Redistribution and use in source and binary forms, with or without | ||
| modification, are permitted provided that the following conditions are met: | ||
|
|
||
| 1. Redistributions of source code must retain the above copyright notice, | ||
| this list of conditions and the following disclaimer. | ||
|
|
||
| 2. Redistributions in binary form must reproduce the above copyright notice, | ||
| this list of conditions and the following disclaimer in the documentation | ||
| and/or other materials provided with the distribution. | ||
|
|
||
| 3. Neither the name of the copyright holder nor the names of its | ||
| contributors may be used to endorse or promote products derived from this | ||
| software without specific prior written permission. | ||
|
|
||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
| AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
| IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
| ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | ||
| LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
| CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
| SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | ||
| INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||
| CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||
| ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||
| POSSIBILITY OF SUCH DAMAGE. |
Uh oh!
There was an error while loading. Please reload this page.