def copy_src_file(self, src, dst): """Copy file src, named by COPY either explicitly or with wildcards, to dst. src might be a symlink, but dst is a canonical path. Both must be at the top level of the COPY instruction; i.e., this function must not be called recursively. If dst is a directory, file should go in that directory named src (i.e., the directory creation magic has already happened).""" assert (os.path.isfile(src)) assert (not os.path.exists(dst) or (os.path.isdir(dst) and not os.path.islink(dst)) or (os.path.isfile(dst) and not os.path.islink(dst))) ch.DEBUG("copying named file: %s -> %s" % (src, dst)) ch.copy2(src, dst, follow_symlinks=True)
def execute_(self): # Complain about unsupported stuff. if (self.options.pop("chown", False)): self.unsupported_forever_warn("--chown") # Any remaining options are invalid. self.options_assert_empty() # Find the source directory. if (self.from_ is None): context = cli.context else: if (self.from_ == image_i or self.from_ == image_alias): ch.FATAL("COPY --from: stage %s is the current stage" % self.from_) if (not self.from_ in images): # FIXME: Would be nice to also report if a named stage is below. if (isinstance(self.from_, int) and self.from_ < image_ct): if (self.from_ < 0): ch.FATAL("COPY --from: invalid negative stage index %d" % self.from_) else: ch.FATAL("COPY --from: stage %d does not exist yet" % self.from_) else: ch.FATAL("COPY --from: stage %s does not exist" % self.from_) context = images[self.from_].unpack_path ch.DEBUG("context: " + context) # Do the copy. srcs = list() for src in self.srcs: if (os.path.normpath(src).startswith("..")): ch.FATAL("can't COPY: %s climbs outside context" % src) for i in glob.glob(context + "/" + src): srcs.append(i) if (len(srcs) == 0): ch.FATAL("can't COPY: no sources exist") dst = images[image_i].unpack_path + "/" if (not self.dst.startswith("/")): dst += env.workdir + "/" dst += self.dst if (dst.endswith("/") or len(srcs) > 1 or os.path.isdir(srcs[0])): # Create destination directory. if (dst.endswith("/")): dst = dst[:-1] if (os.path.exists(dst) and not os.path.isdir(dst)): ch.FATAL("can't COPY: %s exists but is not a directory" % dst) ch.mkdirs(dst) for src in srcs: # Check for symlinks to outside context. src_real = os.path.realpath(src) context_real = os.path.realpath(context) if (not os.path.commonpath([src_real, context_real]) \ .startswith(context_real)): ch.FATAL("can't COPY: %s climbs outside context via symlink" % src) # Do the copy. if (os.path.isfile(src)): # or symlink to file ch.DEBUG("COPY via copy2 file %s to %s" % (src, dst)) ch.copy2(src, dst, follow_symlinks=True) elif (os.path.isdir(src)): # or symlink to directory # Copy *contents* of src, not src itself. Note: shutil.copytree() # has a parameter dirs_exist_ok that I think will make this easier # in Python 3.8. ch.DEBUG("COPY dir %s to %s" % (src, dst)) if (not os.path.isdir(dst)): ch.FATAL("can't COPY: destination not a directory: %s to %s" % (src, dst)) for src2_basename in ch.ossafe( os.listdir, "can't list directory: %s" % src, src): src2 = src + "/" + src2_basename if (os.path.islink(src2)): # Symlinks within directories do not get dereferenced. ch.DEBUG("symlink via copy2: %s to %s" % (src2, dst)) ch.copy2(src2, dst, follow_symlinks=False) elif (os.path.isfile(src2)): # not symlink to file ch.DEBUG("file via copy2: %s to %s" % (src2, dst)) ch.copy2(src2, dst) elif (os.path.isdir(src2)): # not symlink to directory dst2 = dst + "/" + src2_basename ch.DEBUG("directory via copytree: %s to %s" % (src2, dst2)) ch.copytree(src2, dst2, symlinks=True, ignore_dangling_symlinks=True) else: ch.FATAL("can't COPY unknown file type: %s" % src2) else: ch.FATAL("can't COPY unknown file type: %s" % src)
def copy_src_dir(self, src, dst): """Copy the contents of directory src, named by COPY, either explicitly or with wildcards, to dst. src might be a symlink, but dst is a canonical path. Both must be at the top level of the COPY instruction; i.e., this function must not be called recursively. dst must exist already and be a directory. Unlike subdirectories, the metadata of dst will not be altered to match src.""" def onerror(x): ch.FATAL("can't scan directory: %s: %s" % (x.filename, x.strerror)) # Use Path objects in this method because the path arithmetic was # getting too hard with strings. src = ch.Path(os.path.realpath(src)) dst = ch.Path(dst) assert (os.path.isdir(src) and not os.path.islink(src)) assert (os.path.isdir(dst) and not os.path.islink(dst)) ch.DEBUG("copying named directory: %s -> %s" % (src, dst)) for (dirpath, dirnames, filenames) in os.walk(src, onerror=onerror): dirpath = ch.Path(dirpath) subdir = dirpath.relative_to(src) dst_dir = dst // subdir # dirnames can contain symlinks, which we handle as files, so we'll # rebuild it; the walk will not descend into those "directories". dirnames2 = dirnames.copy() # shallow copy dirnames[:] = list() # clear in place for d in dirnames2: d = ch.Path(d) src_path = dirpath // d dst_path = dst_dir // d ch.TRACE("dir: %s -> %s" % (src_path, dst_path)) if (os.path.islink(src_path)): filenames.append(d) # symlink, handle as file ch.TRACE("symlink to dir, will handle as file") continue else: dirnames.append(d) # directory, descend into later # If destination exists, but isn't a directory, remove it. if (os.path.exists(dst_path)): if (os.path.isdir(dst_path) and not os.path.islink(dst_path)): ch.TRACE("dst_path exists and is a directory") else: ch.TRACE("dst_path exists, not a directory, removing") ch.unlink(dst_path) # If destination directory doesn't exist, create it. if (not os.path.exists(dst_path)): ch.TRACE("mkdir dst_path") ch.ossafe(os.mkdir, "can't mkdir: %s" % dst_path, dst_path) # Copy metadata, now that we know the destination exists and is a # directory. ch.ossafe(shutil.copystat, "can't copy metadata: %s -> %s" % (src_path, dst_path), src_path, dst_path, follow_symlinks=False) for f in filenames: f = ch.Path(f) src_path = dirpath // f dst_path = dst_dir // f ch.TRACE("file or symlink via copy2: %s -> %s" % (src_path, dst_path)) if (not (os.path.isfile(src_path) or os.path.islink(src_path))): ch.FATAL("can't COPY: unknown file type: %s" % src_path) if (os.path.exists(dst_path)): ch.TRACE("destination exists, removing") if (os.path.isdir(dst_path) and not os.path.islink(dst_path)): ch.rmtree(dst_path) else: ch.unlink(dst_path) ch.copy2(src_path, dst_path, follow_symlinks=False)