"Fossies" - the Fresh Open Source Software Archive

Member "buildah-1.27.2/add.go" (20 Sep 2022, 22993 Bytes) of package /linux/misc/buildah-1.27.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Go source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the last Fossies "Diffs" side-by-side code changes report for "add.go": 1.26.4_vs_1.27.0.

    1 package buildah
    2 
    3 import (
    4     "archive/tar"
    5     "errors"
    6     "fmt"
    7     "io"
    8     "io/ioutil"
    9     "net/http"
   10     "net/url"
   11     "os"
   12     "path"
   13     "path/filepath"
   14     "strconv"
   15     "strings"
   16     "sync"
   17     "syscall"
   18     "time"
   19 
   20     "github.com/containers/buildah/copier"
   21     "github.com/containers/buildah/define"
   22     "github.com/containers/buildah/pkg/chrootuser"
   23     "github.com/containers/storage/pkg/fileutils"
   24     "github.com/containers/storage/pkg/idtools"
   25     "github.com/hashicorp/go-multierror"
   26     "github.com/opencontainers/runc/libcontainer/userns"
   27     "github.com/opencontainers/runtime-spec/specs-go"
   28     "github.com/sirupsen/logrus"
   29 )
   30 
   31 // AddAndCopyOptions holds options for add and copy commands.
   32 type AddAndCopyOptions struct {
   33     //Chmod sets the access permissions of the destination content.
   34     Chmod string
   35     // Chown is a spec for the user who should be given ownership over the
   36     // newly-added content, potentially overriding permissions which would
   37     // otherwise be set to 0:0.
   38     Chown string
   39     // PreserveOwnership, if Chown is not set, tells us to avoid setting
   40     // ownership of copied items to 0:0, instead using whatever ownership
   41     // information is already set.  Not meaningful for remote sources or
   42     // local archives that we extract.
   43     PreserveOwnership bool
   44     // All of the data being copied will pass through Hasher, if set.
   45     // If the sources are URLs or files, their contents will be passed to
   46     // Hasher.
   47     // If the sources include directory trees, Hasher will be passed
   48     // tar-format archives of the directory trees.
   49     Hasher io.Writer
   50     // Excludes is the contents of the .containerignore file.
   51     Excludes []string
   52     // IgnoreFile is the path to the .containerignore file.
   53     IgnoreFile string
   54     // ContextDir is the base directory for content being copied and
   55     // Excludes patterns.
   56     ContextDir string
   57     // ID mapping options to use when contents to be copied are part of
   58     // another container, and need ownerships to be mapped from the host to
   59     // that container's values before copying them into the container.
   60     IDMappingOptions *define.IDMappingOptions
   61     // DryRun indicates that the content should be digested, but not actually
   62     // copied into the container.
   63     DryRun bool
   64     // Clear the setuid bit on items being copied.  Has no effect on
   65     // archives being extracted, where the bit is always preserved.
   66     StripSetuidBit bool
   67     // Clear the setgid bit on items being copied.  Has no effect on
   68     // archives being extracted, where the bit is always preserved.
   69     StripSetgidBit bool
   70     // Clear the sticky bit on items being copied.  Has no effect on
   71     // archives being extracted, where the bit is always preserved.
   72     StripStickyBit bool
   73 }
   74 
   75 // sourceIsRemote returns true if "source" is a remote location.
   76 func sourceIsRemote(source string) bool {
   77     return strings.HasPrefix(source, "http://") || strings.HasPrefix(source, "https://")
   78 }
   79 
   80 // getURL writes a tar archive containing the named content
   81 func getURL(src string, chown *idtools.IDPair, mountpoint, renameTarget string, writer io.Writer, chmod *os.FileMode) error {
   82     url, err := url.Parse(src)
   83     if err != nil {
   84         return err
   85     }
   86     response, err := http.Get(src)
   87     if err != nil {
   88         return err
   89     }
   90     defer response.Body.Close()
   91 
   92     if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusBadRequest {
   93         return fmt.Errorf("invalid response status %d", response.StatusCode)
   94     }
   95 
   96     // Figure out what to name the new content.
   97     name := renameTarget
   98     if name == "" {
   99         name = path.Base(url.Path)
  100     }
  101     // If there's a date on the content, use it.  If not, use the Unix epoch
  102     // for compatibility.
  103     date := time.Unix(0, 0).UTC()
  104     lastModified := response.Header.Get("Last-Modified")
  105     if lastModified != "" {
  106         d, err := time.Parse(time.RFC1123, lastModified)
  107         if err != nil {
  108             return fmt.Errorf("error parsing last-modified time: %w", err)
  109         }
  110         date = d
  111     }
  112     // Figure out the size of the content.
  113     size := response.ContentLength
  114     responseBody := response.Body
  115     if size < 0 {
  116         // Create a temporary file and copy the content to it, so that
  117         // we can figure out how much content there is.
  118         f, err := ioutil.TempFile(mountpoint, "download")
  119         if err != nil {
  120             return fmt.Errorf("error creating temporary file to hold %q: %w", src, err)
  121         }
  122         defer os.Remove(f.Name())
  123         defer f.Close()
  124         size, err = io.Copy(f, response.Body)
  125         if err != nil {
  126             return fmt.Errorf("error writing %q to temporary file %q: %w", src, f.Name(), err)
  127         }
  128         _, err = f.Seek(0, io.SeekStart)
  129         if err != nil {
  130             return fmt.Errorf("error setting up to read %q from temporary file %q: %w", src, f.Name(), err)
  131         }
  132         responseBody = f
  133     }
  134     // Write the output archive.  Set permissions for compatibility.
  135     tw := tar.NewWriter(writer)
  136     defer tw.Close()
  137     uid := 0
  138     gid := 0
  139     if chown != nil {
  140         uid = chown.UID
  141         gid = chown.GID
  142     }
  143     var mode int64 = 0600
  144     if chmod != nil {
  145         mode = int64(*chmod)
  146     }
  147     hdr := tar.Header{
  148         Typeflag: tar.TypeReg,
  149         Name:     name,
  150         Size:     size,
  151         Uid:      uid,
  152         Gid:      gid,
  153         Mode:     mode,
  154         ModTime:  date,
  155     }
  156     err = tw.WriteHeader(&hdr)
  157     if err != nil {
  158         return fmt.Errorf("error writing header: %w", err)
  159     }
  160 
  161     if _, err := io.Copy(tw, responseBody); err != nil {
  162         return fmt.Errorf("error writing content from %q to tar stream: %w", src, err)
  163     }
  164 
  165     return nil
  166 }
  167 
  168 // includeDirectoryAnyway returns true if "path" is a prefix for an exception
  169 // known to "pm".  If "path" is a directory that "pm" claims matches its list
  170 // of patterns, but "pm"'s list of exclusions contains a pattern for which
  171 // "path" is a prefix, then IncludeDirectoryAnyway() will return true.
  172 // This is not always correct, because it relies on the directory part of any
  173 // exception paths to be specified without wildcards.
  174 func includeDirectoryAnyway(path string, pm *fileutils.PatternMatcher) bool {
  175     if !pm.Exclusions() {
  176         return false
  177     }
  178     prefix := strings.TrimPrefix(path, string(os.PathSeparator)) + string(os.PathSeparator)
  179     for _, pattern := range pm.Patterns() {
  180         if !pattern.Exclusion() {
  181             continue
  182         }
  183         spec := strings.TrimPrefix(pattern.String(), string(os.PathSeparator))
  184         if strings.HasPrefix(spec, prefix) {
  185             return true
  186         }
  187     }
  188     return false
  189 }
  190 
  191 // Add copies the contents of the specified sources into the container's root
  192 // filesystem, optionally extracting contents of local files that look like
  193 // non-empty archives.
  194 func (b *Builder) Add(destination string, extract bool, options AddAndCopyOptions, sources ...string) error {
  195     mountPoint, err := b.Mount(b.MountLabel)
  196     if err != nil {
  197         return err
  198     }
  199     defer func() {
  200         if err2 := b.Unmount(); err2 != nil {
  201             logrus.Errorf("error unmounting container: %v", err2)
  202         }
  203     }()
  204 
  205     contextDir := options.ContextDir
  206     currentDir := options.ContextDir
  207     if options.ContextDir == "" {
  208         contextDir = string(os.PathSeparator)
  209         currentDir, err = os.Getwd()
  210         if err != nil {
  211             return fmt.Errorf("error determining current working directory: %w", err)
  212         }
  213     } else {
  214         if !filepath.IsAbs(options.ContextDir) {
  215             contextDir, err = filepath.Abs(options.ContextDir)
  216             if err != nil {
  217                 return fmt.Errorf("error converting context directory path %q to an absolute path: %w", options.ContextDir, err)
  218             }
  219         }
  220     }
  221 
  222     // Figure out what sorts of sources we have.
  223     var localSources, remoteSources []string
  224     for i, src := range sources {
  225         if sourceIsRemote(src) {
  226             remoteSources = append(remoteSources, src)
  227             continue
  228         }
  229         if !filepath.IsAbs(src) && options.ContextDir == "" {
  230             sources[i] = filepath.Join(currentDir, src)
  231         }
  232         localSources = append(localSources, sources[i])
  233     }
  234 
  235     // Check how many items our local source specs matched.  Each spec
  236     // should have matched at least one item, otherwise we consider it an
  237     // error.
  238     var localSourceStats []*copier.StatsForGlob
  239     if len(localSources) > 0 {
  240         statOptions := copier.StatOptions{
  241             CheckForArchives: extract,
  242         }
  243         localSourceStats, err = copier.Stat(contextDir, contextDir, statOptions, localSources)
  244         if err != nil {
  245             return fmt.Errorf("checking on sources under %q: %w", contextDir, err)
  246         }
  247     }
  248     numLocalSourceItems := 0
  249     for _, localSourceStat := range localSourceStats {
  250         if localSourceStat.Error != "" {
  251             errorText := localSourceStat.Error
  252             rel, err := filepath.Rel(contextDir, localSourceStat.Glob)
  253             if err != nil {
  254                 errorText = fmt.Sprintf("%v; %s", err, errorText)
  255             }
  256             if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
  257                 errorText = fmt.Sprintf("possible escaping context directory error: %s", errorText)
  258             }
  259             return fmt.Errorf("checking on sources under %q: %v", contextDir, errorText)
  260         }
  261         if len(localSourceStat.Globbed) == 0 {
  262             return fmt.Errorf("checking source under %q: no glob matches: %w", contextDir, syscall.ENOENT)
  263         }
  264         numLocalSourceItems += len(localSourceStat.Globbed)
  265     }
  266     if numLocalSourceItems+len(remoteSources) == 0 {
  267         return fmt.Errorf("no sources %v found: %w", sources, syscall.ENOENT)
  268     }
  269 
  270     // Find out which user (and group) the destination should belong to.
  271     var chownDirs, chownFiles *idtools.IDPair
  272     var userUID, userGID uint32
  273     if options.Chown != "" {
  274         userUID, userGID, err = b.userForCopy(mountPoint, options.Chown)
  275         if err != nil {
  276             return fmt.Errorf("error looking up UID/GID for %q: %w", options.Chown, err)
  277         }
  278     }
  279     var chmodDirsFiles *os.FileMode
  280     if options.Chmod != "" {
  281         p, err := strconv.ParseUint(options.Chmod, 8, 32)
  282         if err != nil {
  283             return fmt.Errorf("error parsing chmod %q: %w", options.Chmod, err)
  284         }
  285         perm := os.FileMode(p)
  286         chmodDirsFiles = &perm
  287     }
  288 
  289     chownDirs = &idtools.IDPair{UID: int(userUID), GID: int(userGID)}
  290     chownFiles = &idtools.IDPair{UID: int(userUID), GID: int(userGID)}
  291     if options.Chown == "" && options.PreserveOwnership {
  292         chownDirs = nil
  293         chownFiles = nil
  294     }
  295 
  296     // If we have a single source archive to extract, or more than one
  297     // source item, or the destination has a path separator at the end of
  298     // it, and it's not a remote URL, the destination needs to be a
  299     // directory.
  300     if destination == "" || !filepath.IsAbs(destination) {
  301         tmpDestination := filepath.Join(string(os.PathSeparator)+b.WorkDir(), destination)
  302         if destination == "" || strings.HasSuffix(destination, string(os.PathSeparator)) {
  303             destination = tmpDestination + string(os.PathSeparator)
  304         } else {
  305             destination = tmpDestination
  306         }
  307     }
  308     destMustBeDirectory := (len(sources) > 1) || strings.HasSuffix(destination, string(os.PathSeparator)) || destination == b.WorkDir()
  309     destCanBeFile := false
  310     if len(sources) == 1 {
  311         if len(remoteSources) == 1 {
  312             destCanBeFile = sourceIsRemote(sources[0])
  313         }
  314         if len(localSources) == 1 {
  315             item := localSourceStats[0].Results[localSourceStats[0].Globbed[0]]
  316             if item.IsDir || (item.IsArchive && extract) {
  317                 destMustBeDirectory = true
  318             }
  319             if item.IsRegular {
  320                 destCanBeFile = true
  321             }
  322         }
  323     }
  324 
  325     // We care if the destination either doesn't exist, or exists and is a
  326     // file.  If the source can be a single file, for those cases we treat
  327     // the destination as a file rather than as a directory tree.
  328     renameTarget := ""
  329     extractDirectory := filepath.Join(mountPoint, destination)
  330     statOptions := copier.StatOptions{
  331         CheckForArchives: extract,
  332     }
  333     destStats, err := copier.Stat(mountPoint, filepath.Join(mountPoint, b.WorkDir()), statOptions, []string{extractDirectory})
  334     if err != nil {
  335         return fmt.Errorf("error checking on destination %v: %w", extractDirectory, err)
  336     }
  337     if (len(destStats) == 0 || len(destStats[0].Globbed) == 0) && !destMustBeDirectory && destCanBeFile {
  338         // destination doesn't exist - extract to parent and rename the incoming file to the destination's name
  339         renameTarget = filepath.Base(extractDirectory)
  340         extractDirectory = filepath.Dir(extractDirectory)
  341     }
  342 
  343     // if the destination is a directory that doesn't yet exist, let's copy it.
  344     newDestDirFound := false
  345     if (len(destStats) == 1 || len(destStats[0].Globbed) == 0) && destMustBeDirectory && !destCanBeFile {
  346         newDestDirFound = true
  347     }
  348 
  349     if len(destStats) == 1 && len(destStats[0].Globbed) == 1 && destStats[0].Results[destStats[0].Globbed[0]].IsRegular {
  350         if destMustBeDirectory {
  351             return fmt.Errorf("destination %v already exists but is not a directory", destination)
  352         }
  353         // destination exists - it's a file, we need to extract to parent and rename the incoming file to the destination's name
  354         renameTarget = filepath.Base(extractDirectory)
  355         extractDirectory = filepath.Dir(extractDirectory)
  356     }
  357 
  358     pm, err := fileutils.NewPatternMatcher(options.Excludes)
  359     if err != nil {
  360         return fmt.Errorf("error processing excludes list %v: %w", options.Excludes, err)
  361     }
  362 
  363     // Make sure that, if it's a symlink, we'll chroot to the target of the link;
  364     // knowing that target requires that we resolve it within the chroot.
  365     evalOptions := copier.EvalOptions{}
  366     evaluated, err := copier.Eval(mountPoint, extractDirectory, evalOptions)
  367     if err != nil {
  368         return fmt.Errorf("error checking on destination %v: %w", extractDirectory, err)
  369     }
  370     extractDirectory = evaluated
  371 
  372     // Set up ID maps.
  373     var srcUIDMap, srcGIDMap []idtools.IDMap
  374     if options.IDMappingOptions != nil {
  375         srcUIDMap, srcGIDMap = convertRuntimeIDMaps(options.IDMappingOptions.UIDMap, options.IDMappingOptions.GIDMap)
  376     }
  377     destUIDMap, destGIDMap := convertRuntimeIDMaps(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap)
  378 
  379     // Create the target directory if it doesn't exist yet.
  380     mkdirOptions := copier.MkdirOptions{
  381         UIDMap:   destUIDMap,
  382         GIDMap:   destGIDMap,
  383         ChownNew: chownDirs,
  384     }
  385     if err := copier.Mkdir(mountPoint, extractDirectory, mkdirOptions); err != nil {
  386         return fmt.Errorf("error ensuring target directory exists: %w", err)
  387     }
  388 
  389     // Copy each source in turn.
  390     for _, src := range sources {
  391         var multiErr *multierror.Error
  392         var getErr, closeErr, renameErr, putErr error
  393         var wg sync.WaitGroup
  394         if sourceIsRemote(src) {
  395             pipeReader, pipeWriter := io.Pipe()
  396             wg.Add(1)
  397             go func() {
  398                 getErr = getURL(src, chownFiles, mountPoint, renameTarget, pipeWriter, chmodDirsFiles)
  399                 pipeWriter.Close()
  400                 wg.Done()
  401             }()
  402             wg.Add(1)
  403             go func() {
  404                 b.ContentDigester.Start("")
  405                 hashCloser := b.ContentDigester.Hash()
  406                 hasher := io.Writer(hashCloser)
  407                 if options.Hasher != nil {
  408                     hasher = io.MultiWriter(hasher, options.Hasher)
  409                 }
  410                 if options.DryRun {
  411                     _, putErr = io.Copy(hasher, pipeReader)
  412                 } else {
  413                     putOptions := copier.PutOptions{
  414                         UIDMap:        destUIDMap,
  415                         GIDMap:        destGIDMap,
  416                         ChownDirs:     nil,
  417                         ChmodDirs:     nil,
  418                         ChownFiles:    nil,
  419                         ChmodFiles:    nil,
  420                         IgnoreDevices: userns.RunningInUserNS(),
  421                     }
  422                     putErr = copier.Put(extractDirectory, extractDirectory, putOptions, io.TeeReader(pipeReader, hasher))
  423                 }
  424                 hashCloser.Close()
  425                 pipeReader.Close()
  426                 wg.Done()
  427             }()
  428             wg.Wait()
  429             if getErr != nil {
  430                 getErr = fmt.Errorf("error reading %q: %w", src, getErr)
  431             }
  432             if putErr != nil {
  433                 putErr = fmt.Errorf("error storing %q: %w", src, putErr)
  434             }
  435             multiErr = multierror.Append(getErr, putErr)
  436             if multiErr != nil && multiErr.ErrorOrNil() != nil {
  437                 if len(multiErr.Errors) > 1 {
  438                     return multiErr.ErrorOrNil()
  439                 }
  440                 return multiErr.Errors[0]
  441             }
  442             continue
  443         }
  444 
  445         // Dig out the result of running glob+stat on this source spec.
  446         var localSourceStat *copier.StatsForGlob
  447         for _, st := range localSourceStats {
  448             if st.Glob == src {
  449                 localSourceStat = st
  450                 break
  451             }
  452         }
  453         if localSourceStat == nil {
  454             continue
  455         }
  456 
  457         // Iterate through every item that matched the glob.
  458         itemsCopied := 0
  459         for _, glob := range localSourceStat.Globbed {
  460             rel, err := filepath.Rel(contextDir, glob)
  461             if err != nil {
  462                 return fmt.Errorf("error computing path of %q relative to %q: %w", glob, contextDir, err)
  463             }
  464             if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
  465                 return fmt.Errorf("possible escaping context directory error: %q is outside of %q", glob, contextDir)
  466             }
  467             // Check for dockerignore-style exclusion of this item.
  468             if rel != "." {
  469                 excluded, err := pm.Matches(filepath.ToSlash(rel)) // nolint:staticcheck
  470                 if err != nil {
  471                     return fmt.Errorf("error checking if %q(%q) is excluded: %w", glob, rel, err)
  472                 }
  473                 if excluded {
  474                     // non-directories that are excluded are excluded, no question, but
  475                     // directories can only be skipped if we don't have to allow for the
  476                     // possibility of finding things to include under them
  477                     globInfo := localSourceStat.Results[glob]
  478                     if !globInfo.IsDir || !includeDirectoryAnyway(rel, pm) {
  479                         continue
  480                     }
  481                 } else {
  482                     // if the destination is a directory that doesn't yet exist, and is not excluded, let's copy it.
  483                     if newDestDirFound {
  484                         itemsCopied++
  485                     }
  486                 }
  487             } else {
  488                 // Make sure we don't trigger a "copied nothing" error for an empty context
  489                 // directory if we were told to copy the context directory itself.  We won't
  490                 // actually copy it, but we need to make sure that we don't produce an error
  491                 // due to potentially not having anything in the tarstream that we passed.
  492                 itemsCopied++
  493             }
  494             st := localSourceStat.Results[glob]
  495             pipeReader, pipeWriter := io.Pipe()
  496             wg.Add(1)
  497             go func() {
  498                 renamedItems := 0
  499                 writer := io.WriteCloser(pipeWriter)
  500                 if renameTarget != "" {
  501                     writer = newTarFilterer(writer, func(hdr *tar.Header) (bool, bool, io.Reader) {
  502                         hdr.Name = renameTarget
  503                         renamedItems++
  504                         return false, false, nil
  505                     })
  506                 }
  507                 writer = newTarFilterer(writer, func(hdr *tar.Header) (bool, bool, io.Reader) {
  508                     itemsCopied++
  509                     return false, false, nil
  510                 })
  511                 getOptions := copier.GetOptions{
  512                     UIDMap:         srcUIDMap,
  513                     GIDMap:         srcGIDMap,
  514                     Excludes:       options.Excludes,
  515                     ExpandArchives: extract,
  516                     ChownDirs:      chownDirs,
  517                     ChmodDirs:      chmodDirsFiles,
  518                     ChownFiles:     chownFiles,
  519                     ChmodFiles:     chmodDirsFiles,
  520                     StripSetuidBit: options.StripSetuidBit,
  521                     StripSetgidBit: options.StripSetgidBit,
  522                     StripStickyBit: options.StripStickyBit,
  523                 }
  524                 getErr = copier.Get(contextDir, contextDir, getOptions, []string{glob}, writer)
  525                 closeErr = writer.Close()
  526                 if renameTarget != "" && renamedItems > 1 {
  527                     renameErr = fmt.Errorf("internal error: renamed %d items when we expected to only rename 1", renamedItems)
  528                 }
  529                 wg.Done()
  530             }()
  531             wg.Add(1)
  532             go func() {
  533                 if st.IsDir {
  534                     b.ContentDigester.Start("dir")
  535                 } else {
  536                     b.ContentDigester.Start("file")
  537                 }
  538                 hashCloser := b.ContentDigester.Hash()
  539                 hasher := io.Writer(hashCloser)
  540                 if options.Hasher != nil {
  541                     hasher = io.MultiWriter(hasher, options.Hasher)
  542                 }
  543                 if options.DryRun {
  544                     _, putErr = io.Copy(hasher, pipeReader)
  545                 } else {
  546                     putOptions := copier.PutOptions{
  547                         UIDMap:          destUIDMap,
  548                         GIDMap:          destGIDMap,
  549                         DefaultDirOwner: chownDirs,
  550                         DefaultDirMode:  nil,
  551                         ChownDirs:       nil,
  552                         ChmodDirs:       nil,
  553                         ChownFiles:      nil,
  554                         ChmodFiles:      nil,
  555                         IgnoreDevices:   userns.RunningInUserNS(),
  556                     }
  557                     putErr = copier.Put(extractDirectory, extractDirectory, putOptions, io.TeeReader(pipeReader, hasher))
  558                 }
  559                 hashCloser.Close()
  560                 pipeReader.Close()
  561                 wg.Done()
  562             }()
  563             wg.Wait()
  564             if getErr != nil {
  565                 getErr = fmt.Errorf("error reading %q: %w", src, getErr)
  566             }
  567             if closeErr != nil {
  568                 closeErr = fmt.Errorf("error closing %q: %w", src, closeErr)
  569             }
  570             if renameErr != nil {
  571                 renameErr = fmt.Errorf("error renaming %q: %w", src, renameErr)
  572             }
  573             if putErr != nil {
  574                 putErr = fmt.Errorf("error storing %q: %w", src, putErr)
  575             }
  576             multiErr = multierror.Append(getErr, closeErr, renameErr, putErr)
  577             if multiErr != nil && multiErr.ErrorOrNil() != nil {
  578                 if len(multiErr.Errors) > 1 {
  579                     return multiErr.ErrorOrNil()
  580                 }
  581                 return multiErr.Errors[0]
  582             }
  583         }
  584         if itemsCopied == 0 {
  585             excludesFile := ""
  586             if options.IgnoreFile != "" {
  587                 excludesFile = " using " + options.IgnoreFile
  588             }
  589             return fmt.Errorf("no items matching glob %q copied (%d filtered out%s): %w", localSourceStat.Glob, len(localSourceStat.Globbed), excludesFile, syscall.ENOENT)
  590         }
  591     }
  592     return nil
  593 }
  594 
  595 // userForRun returns the user (and group) information which we should use for
  596 // running commands
  597 func (b *Builder) userForRun(mountPoint string, userspec string) (specs.User, string, error) {
  598     if userspec == "" {
  599         userspec = b.User()
  600     }
  601 
  602     uid, gid, homeDir, err := chrootuser.GetUser(mountPoint, userspec)
  603     u := specs.User{
  604         UID:      uid,
  605         GID:      gid,
  606         Username: userspec,
  607     }
  608     if !strings.Contains(userspec, ":") {
  609         groups, err2 := chrootuser.GetAdditionalGroupsForUser(mountPoint, uint64(u.UID))
  610         if err2 != nil {
  611             if !errors.Is(err2, chrootuser.ErrNoSuchUser) && err == nil {
  612                 err = err2
  613             }
  614         } else {
  615             u.AdditionalGids = groups
  616         }
  617 
  618     }
  619     return u, homeDir, err
  620 }
  621 
  622 // userForCopy returns the user (and group) information which we should use for
  623 // setting ownership of contents being copied.  It's just like what
  624 // userForRun() does, except for the case where we're passed a single numeric
  625 // value, where we need to use that value for both the UID and the GID.
  626 func (b *Builder) userForCopy(mountPoint string, userspec string) (uint32, uint32, error) {
  627     var (
  628         user, group string
  629         uid, gid    uint64
  630         err         error
  631     )
  632 
  633     split := strings.SplitN(userspec, ":", 2)
  634     user = split[0]
  635     if len(split) > 1 {
  636         group = split[1]
  637     }
  638 
  639     // If userspec did not specify any values for user or group, then fail
  640     if user == "" && group == "" {
  641         return 0, 0, fmt.Errorf("can't find uid for user %s", userspec)
  642     }
  643 
  644     // If userspec specifies values for user or group, check for numeric values
  645     // and return early.  If not, then translate username/groupname
  646     if user != "" {
  647         uid, err = strconv.ParseUint(user, 10, 32)
  648     }
  649     if err == nil {
  650         // default gid to uid
  651         gid = uid
  652         if group != "" {
  653             gid, err = strconv.ParseUint(group, 10, 32)
  654         }
  655     }
  656     // If err != nil, then user or group not numeric, check filesystem
  657     if err == nil {
  658         return uint32(uid), uint32(gid), nil
  659     }
  660 
  661     owner, _, err := b.userForRun(mountPoint, userspec)
  662     if err != nil {
  663         return 0xffffffff, 0xffffffff, err
  664     }
  665     return owner.UID, owner.GID, nil
  666 }
  667 
  668 // EnsureContainerPathAs creates the specified directory owned by USER
  669 // with the file mode set to MODE.
  670 func (b *Builder) EnsureContainerPathAs(path, user string, mode *os.FileMode) error {
  671     mountPoint, err := b.Mount(b.MountLabel)
  672     if err != nil {
  673         return err
  674     }
  675     defer func() {
  676         if err2 := b.Unmount(); err2 != nil {
  677             logrus.Errorf("error unmounting container: %v", err2)
  678         }
  679     }()
  680 
  681     uid, gid := uint32(0), uint32(0)
  682     if user != "" {
  683         if uidForCopy, gidForCopy, err := b.userForCopy(mountPoint, user); err == nil {
  684             uid = uidForCopy
  685             gid = gidForCopy
  686         }
  687     }
  688 
  689     destUIDMap, destGIDMap := convertRuntimeIDMaps(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap)
  690 
  691     idPair := &idtools.IDPair{UID: int(uid), GID: int(gid)}
  692     opts := copier.MkdirOptions{
  693         ChmodNew: mode,
  694         ChownNew: idPair,
  695         UIDMap:   destUIDMap,
  696         GIDMap:   destGIDMap,
  697     }
  698     return copier.Mkdir(mountPoint, filepath.Join(mountPoint, path), opts)
  699 
  700 }