@@ -30,6 +30,8 @@ type Bundle struct {
3030
3131 RootfsPath string `json:"rootfsPath"` // where actual fs to chroot will appear
3232 TmpDir string `json:"tmpPath"` // where temp files required during build will appear
33+
34+ parentPath string // parent directory for RootfsPath
3335}
3436
3537// Options defines build time behavior to be executed on the bundle.
@@ -73,13 +75,13 @@ type Options struct {
7375}
7476
7577// NewEncryptedBundle creates an Encrypted Bundle environment.
76- func NewEncryptedBundle (rootfs , tempDir string , keyInfo * crypt.KeyInfo ) (b * Bundle , err error ) {
77- return newBundle (rootfs , tempDir , keyInfo )
78+ func NewEncryptedBundle (parentPath , tempDir string , keyInfo * crypt.KeyInfo ) (b * Bundle , err error ) {
79+ return newBundle (parentPath , tempDir , keyInfo )
7880}
7981
8082// NewBundle creates a Bundle environment.
81- func NewBundle (rootfs , tempDir string ) (b * Bundle , err error ) {
82- return newBundle (rootfs , tempDir , nil )
83+ func NewBundle (parentPath , tempDir string ) (b * Bundle , err error ) {
84+ return newBundle (parentPath , tempDir , nil )
8385}
8486
8587// RunSection iterates through the sections specified in a bundle
@@ -100,7 +102,7 @@ func (b *Bundle) RunSection(s string) bool {
100102// Remove cleans up any bundle files.
101103func (b * Bundle ) Remove () error {
102104 var errors []string
103- for _ , dir := range []string {b .TmpDir , b .RootfsPath } {
105+ for _ , dir := range []string {b .TmpDir , b .parentPath } {
104106 if err := fs .ForceRemoveAll (dir ); err != nil {
105107 errors = append (errors , fmt .Sprintf ("could not remove %q: %v" , dir , err ))
106108 }
@@ -141,11 +143,19 @@ func cleanupDir(path string) {
141143 }
142144}
143145
144- // newBundle creates a minimum bundle with root filesystem in rootfs .
146+ // newBundle creates a minimum bundle with root filesystem in parentPath .
145147// Any temporary files created during build process will be in tempDir/bundle-temp-*
146148// directory, that will be cleaned up after successful build.
147- func newBundle (rootfs , tempDir string , keyInfo * crypt.KeyInfo ) (* Bundle , error ) {
148- rootfsPath := rootfs
149+
150+ //
151+ // TODO: much of the logic in this func should likely be re-factored to func newBuild in the
152+ // internal/pkg/build package, since it is the sole caller and has conditional logic which depends
153+ // on implementation details of this package. In particular, chown() handling should be done at the
154+ // build level, rather than the bundle level, to avoid repetition during multi-stage builds, and
155+ // clarify responsibility for cleanup of the various directories that are created during the build
156+ // process.
157+ func newBundle (parentPath , tempDir string , keyInfo * crypt.KeyInfo ) (* Bundle , error ) {
158+ rootfsPath := filepath .Join (parentPath , "rootfs" )
149159
150160 tmpPath , err := ioutil .TempDir (tempDir , "bundle-temp-" )
151161 if err != nil {
@@ -155,6 +165,7 @@ func newBundle(rootfs, tempDir string, keyInfo *crypt.KeyInfo) (*Bundle, error)
155165
156166 if err := os .MkdirAll (rootfsPath , 0755 ); err != nil {
157167 cleanupDir (tmpPath )
168+ cleanupDir (parentPath )
158169 return nil , fmt .Errorf ("could not create %q: %v" , rootfsPath , err )
159170 }
160171
@@ -164,14 +175,25 @@ func newBundle(rootfs, tempDir string, keyInfo *crypt.KeyInfo) (*Bundle, error)
164175 if err != nil {
165176 cleanupDir (tmpPath )
166177 cleanupDir (rootfsPath )
178+ cleanupDir (parentPath )
167179 return nil , err
168180 } else if ! can {
169- defer cleanupDir (rootfsPath )
181+ cleanupDir (rootfsPath )
182+ cleanupDir (parentPath )
170183
171- rootfsNewPath := filepath .Join (tempDir , filepath .Base (rootfsPath ))
172- if rootfsNewPath != rootfsPath {
173- if err := os .MkdirAll (rootfsNewPath , 0755 ); err != nil {
184+ // If the supplied rootfs was not inside tempDir (as is the case during a sandbox build),
185+ // try tempDir as a fallback.
186+ if ! strings .HasPrefix (parentPath , tempDir ) {
187+ parentPath , err = ioutil .TempDir (tempDir , "build-temp-" )
188+ if err != nil {
189+ cleanupDir (tmpPath )
190+ return nil , fmt .Errorf ("failed to create rootfs directory: %v" , err )
191+ }
192+ // Create an inner dir, so we don't clobber the secure permissions on the surrounding dir.
193+ rootfsNewPath := filepath .Join (parentPath , "rootfs" )
194+ if err := os .Mkdir (rootfsNewPath , 0755 ); err != nil {
174195 cleanupDir (tmpPath )
196+ cleanupDir (parentPath )
175197 return nil , fmt .Errorf ("could not create rootfs dir in %q: %v" , rootfsNewPath , err )
176198 }
177199 // check that chown works with the underlying filesystem pointed
@@ -180,10 +202,12 @@ func newBundle(rootfs, tempDir string, keyInfo *crypt.KeyInfo) (*Bundle, error)
180202 if err != nil {
181203 cleanupDir (tmpPath )
182204 cleanupDir (rootfsNewPath )
205+ cleanupDir (parentPath )
183206 return nil , err
184207 } else if ! can {
185208 cleanupDir (tmpPath )
186209 cleanupDir (rootfsNewPath )
210+ cleanupDir (parentPath )
187211 sylog .Errorf ("Could not set files/directories ownership, if %s is on a network filesystem, " +
188212 "you must set TMPDIR to a local path (eg: TMPDIR=/var/tmp singularity build ...)" , rootfsNewPath )
189213 return nil , fmt .Errorf ("ownership change not allowed in %s, aborting" , tempDir )
@@ -195,6 +219,7 @@ func newBundle(rootfs, tempDir string, keyInfo *crypt.KeyInfo) (*Bundle, error)
195219 sylog .Debugf ("Created directory %q for the bundle" , rootfsPath )
196220
197221 return & Bundle {
222+ parentPath : parentPath ,
198223 RootfsPath : rootfsPath ,
199224 TmpDir : tmpPath ,
200225 JSONObjects : make (map [string ][]byte ),
0 commit comments