@@ -305,7 +305,8 @@ type removeResponse struct{}
305
305
306
306
// ensureResponse encodes a response to an Ensure request.
307
307
type ensureResponse struct {
308
- Created []string // paths that were created because they weren't already present
308
+ Created []string // paths that were created because they weren't already present
309
+ Noted []EnsureParentPath // preexisting paths that are parents of created items
309
310
}
310
311
311
312
// conditionalRemoveResponse encodes a response to a conditionalRemove request.
@@ -479,6 +480,7 @@ func Put(root string, directory string, options PutOptions, bulkReader io.Reader
479
480
// MkdirOptions controls parts of Mkdir()'s behavior.
480
481
type MkdirOptions struct {
481
482
UIDMap , GIDMap []idtools.IDMap // map from containerIDs to hostIDs when creating directories
483
+ ModTimeNew * time.Time // set mtime and atime of newly-created directories
482
484
ChownNew * idtools.IDPair // set ownership of newly-created directories
483
485
ChmodNew * os.FileMode // set permissions on newly-created directories
484
486
}
@@ -2199,6 +2201,7 @@ func copierHandlerMkdir(req request, idMappings *idtools.IDMappings) (*response,
2199
2201
}
2200
2202
2201
2203
subdir := ""
2204
+ var created []string
2202
2205
for _ , component := range strings .Split (rel , string (os .PathSeparator )) {
2203
2206
subdir = filepath .Join (subdir , component )
2204
2207
path := filepath .Join (req .Root , subdir )
@@ -2209,6 +2212,7 @@ func copierHandlerMkdir(req request, idMappings *idtools.IDMappings) (*response,
2209
2212
if err = chmod (path , dirMode ); err != nil {
2210
2213
return errorResponse ("copier: mkdir: error setting permissions on %q to 0%o: %v" , path , dirMode )
2211
2214
}
2215
+ created = append (created , path )
2212
2216
} else {
2213
2217
// FreeBSD can return EISDIR for "mkdir /":
2214
2218
// https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=59739.
@@ -2217,6 +2221,17 @@ func copierHandlerMkdir(req request, idMappings *idtools.IDMappings) (*response,
2217
2221
}
2218
2222
}
2219
2223
}
2224
+ // set timestamps last, in case we needed to create some nested directories, which would
2225
+ // update the timestamps on directories that we'd just set timestamps on, if we had done
2226
+ // that immediately
2227
+ if req .MkdirOptions .ModTimeNew != nil {
2228
+ when := * req .MkdirOptions .ModTimeNew
2229
+ for _ , newDirectory := range created {
2230
+ if err = lutimes (false , newDirectory , when , when ); err != nil {
2231
+ return errorResponse ("copier: mkdir: error setting datestamp on %q: %v" , newDirectory , err )
2232
+ }
2233
+ }
2234
+ }
2220
2235
2221
2236
return & response {Error : "" , Mkdir : mkdirResponse {}}, nil , nil
2222
2237
}
@@ -2255,12 +2270,22 @@ type EnsureOptions struct {
2255
2270
Paths []EnsurePath
2256
2271
}
2257
2272
2273
+ // EnsureParentPath is a parent (or grandparent, or...) directory of an item
2274
+ // created by Ensure(), along with information about it, from before the item
2275
+ // in question was created. If the information about this directory hasn't
2276
+ // changed when commit-time rolls around, it's most likely that this directory
2277
+ // is only being considered for inclusion in the layer because it was pulled
2278
+ // up, and it was not actually changed.
2279
+ type EnsureParentPath = ConditionalRemovePath
2280
+
2258
2281
// Ensure ensures that the specified mount point targets exist under the root.
2259
2282
// If the root directory is not specified, the current root directory is used.
2260
2283
// If root is specified and the current OS supports it, and the calling process
2261
2284
// has the necessary privileges, the operation is performed in a chrooted
2262
2285
// context.
2263
- func Ensure (root , directory string , options EnsureOptions ) ([]string , error ) {
2286
+ // Returns a slice with the pathnames of items that needed to be created and a
2287
+ // slice of affected parent directories and information about them.
2288
+ func Ensure (root , directory string , options EnsureOptions ) ([]string , []EnsureParentPath , error ) {
2264
2289
req := request {
2265
2290
Request : requestEnsure ,
2266
2291
Root : root ,
@@ -2269,12 +2294,12 @@ func Ensure(root, directory string, options EnsureOptions) ([]string, error) {
2269
2294
}
2270
2295
resp , err := copier (nil , nil , req )
2271
2296
if err != nil {
2272
- return nil , err
2297
+ return nil , nil , err
2273
2298
}
2274
2299
if resp .Error != "" {
2275
- return nil , errors .New (resp .Error )
2300
+ return nil , nil , errors .New (resp .Error )
2276
2301
}
2277
- return resp .Ensure .Created , nil
2302
+ return resp .Ensure .Created , resp . Ensure . Noted , nil
2278
2303
}
2279
2304
2280
2305
func copierHandlerEnsure (req request , idMappings * idtools.IDMappings ) * response {
@@ -2283,6 +2308,7 @@ func copierHandlerEnsure(req request, idMappings *idtools.IDMappings) *response
2283
2308
}
2284
2309
slices .SortFunc (req .EnsureOptions .Paths , func (a , b EnsurePath ) int { return strings .Compare (a .Path , b .Path ) })
2285
2310
var created []string
2311
+ notedByName := map [string ]EnsureParentPath {}
2286
2312
for _ , item := range req .EnsureOptions .Paths {
2287
2313
uid , gid := 0 , 0
2288
2314
if item .Chown != nil {
@@ -2326,11 +2352,25 @@ func copierHandlerEnsure(req request, idMappings *idtools.IDMappings) *response
2326
2352
if parentPath == "" {
2327
2353
parentPath = "."
2328
2354
}
2329
- leaf := filepath .Join (subdir , component )
2355
+ leaf := filepath .Join (parentPath , component )
2330
2356
parentInfo , err := os .Stat (filepath .Join (req .Root , parentPath ))
2331
2357
if err != nil {
2332
2358
return errorResponse ("copier: ensure: checking datestamps on %q (%d: %v): %v" , parentPath , i , components , err )
2333
2359
}
2360
+ if parentPath != "." {
2361
+ parentModTime := parentInfo .ModTime ().UTC ()
2362
+ parentMode := parentInfo .Mode ()
2363
+ uid , gid , err := owner (parentInfo )
2364
+ if err != nil {
2365
+ return errorResponse ("copier: ensure: error reading owner of %q: %v" , parentPath , err )
2366
+ }
2367
+ notedByName [parentPath ] = EnsureParentPath {
2368
+ Path : parentPath ,
2369
+ ModTime : & parentModTime ,
2370
+ Mode : & parentMode ,
2371
+ Owner : & idtools.IDPair {UID : uid , GID : gid },
2372
+ }
2373
+ }
2334
2374
if i < len (components )- 1 || item .Typeflag == tar .TypeDir {
2335
2375
err = os .Mkdir (filepath .Join (req .Root , leaf ), mode )
2336
2376
subdir = leaf
@@ -2372,7 +2412,15 @@ func copierHandlerEnsure(req request, idMappings *idtools.IDMappings) *response
2372
2412
}
2373
2413
}
2374
2414
slices .Sort (created )
2375
- return & response {Error : "" , Ensure : ensureResponse {Created : created }}
2415
+ noted := make ([]EnsureParentPath , 0 , len (notedByName ))
2416
+ for _ , n := range notedByName {
2417
+ if slices .Contains (created , n .Path ) {
2418
+ continue
2419
+ }
2420
+ noted = append (noted , n )
2421
+ }
2422
+ slices .SortFunc (noted , func (a , b EnsureParentPath ) int { return strings .Compare (a .Path , b .Path ) })
2423
+ return & response {Error : "" , Ensure : ensureResponse {Created : created , Noted : noted }}
2376
2424
}
2377
2425
2378
2426
// ConditionalRemovePath is a single item being passed to an ConditionalRemove() call.
0 commit comments