func copyFromContainer(ctx context.Context, dockerCli *client.DockerCli, srcContainer, srcPath, dstPath string, cpParam *cpConfig) (err error) { if dstPath != "-" { // Get an absolute destination path. dstPath, err = resolveLocalPath(dstPath) if err != nil { return err } } // if client requests to follow symbol link, then must decide target file to be copied var rebaseName string if cpParam.followLink { srcStat, err := statContainerPath(ctx, dockerCli, srcContainer, srcPath) // If the destination is a symbolic link, we should follow it. if err == nil && srcStat.Mode&os.ModeSymlink != 0 { linkTarget := srcStat.LinkTarget if !system.IsAbs(linkTarget) { // Join with the parent directory. srcParent, _ := archive.SplitPathDirEntry(srcPath) linkTarget = filepath.Join(srcParent, linkTarget) } linkTarget, rebaseName = archive.GetRebaseName(srcPath, linkTarget) srcPath = linkTarget } } content, stat, err := dockerCli.Client().CopyFromContainer(ctx, srcContainer, srcPath) if err != nil { return err } defer content.Close() if dstPath == "-" { // Send the response to STDOUT. _, err = io.Copy(os.Stdout, content) return err } // Prepare source copy info. srcInfo := archive.CopyInfo{ Path: srcPath, Exists: true, IsDir: stat.Mode.IsDir(), RebaseName: rebaseName, } preArchive := content if len(srcInfo.RebaseName) != 0 { _, srcBase := archive.SplitPathDirEntry(srcInfo.Path) preArchive = archive.RebaseArchiveEntries(content, srcBase, srcInfo.RebaseName) } // See comments in the implementation of `archive.CopyTo` for exactly what // goes into deciding how and whether the source archive needs to be // altered for the correct copy behavior. return archive.CopyTo(preArchive, srcInfo, dstPath) }
func copyToContainer(ctx context.Context, dockerCli *client.DockerCli, srcPath, dstContainer, dstPath string, cpParam *cpConfig) (err error) { if srcPath != "-" { // Get an absolute source path. srcPath, err = resolveLocalPath(srcPath) if err != nil { return err } } // In order to get the copy behavior right, we need to know information // about both the source and destination. The API is a simple tar // archive/extract API but we can use the stat info header about the // destination to be more informed about exactly what the destination is. // Prepare destination copy info by stat-ing the container path. dstInfo := archive.CopyInfo{Path: dstPath} dstStat, err := statContainerPath(ctx, dockerCli, dstContainer, dstPath) // If the destination is a symbolic link, we should evaluate it. if err == nil && dstStat.Mode&os.ModeSymlink != 0 { linkTarget := dstStat.LinkTarget if !system.IsAbs(linkTarget) { // Join with the parent directory. dstParent, _ := archive.SplitPathDirEntry(dstPath) linkTarget = filepath.Join(dstParent, linkTarget) } dstInfo.Path = linkTarget dstStat, err = statContainerPath(ctx, dockerCli, dstContainer, linkTarget) } // Ignore any error and assume that the parent directory of the destination // path exists, in which case the copy may still succeed. If there is any // type of conflict (e.g., non-directory overwriting an existing directory // or vice versa) the extraction will fail. If the destination simply did // not exist, but the parent directory does, the extraction will still // succeed. if err == nil { dstInfo.Exists, dstInfo.IsDir = true, dstStat.Mode.IsDir() } var ( content io.Reader resolvedDstPath string ) if srcPath == "-" { // Use STDIN. content = os.Stdin resolvedDstPath = dstInfo.Path if !dstInfo.IsDir { return fmt.Errorf("destination %q must be a directory", fmt.Sprintf("%s:%s", dstContainer, dstPath)) } } else { // Prepare source copy info. srcInfo, err := archive.CopyInfoSourcePath(srcPath, cpParam.followLink) if err != nil { return err } srcArchive, err := archive.TarResource(srcInfo) if err != nil { return err } defer srcArchive.Close() // With the stat info about the local source as well as the // destination, we have enough information to know whether we need to // alter the archive that we upload so that when the server extracts // it to the specified directory in the container we get the desired // copy behavior. // See comments in the implementation of `archive.PrepareArchiveCopy` // for exactly what goes into deciding how and whether the source // archive needs to be altered for the correct copy behavior when it is // extracted. This function also infers from the source and destination // info which directory to extract to, which may be the parent of the // destination that the user specified. dstDir, preparedArchive, err := archive.PrepareArchiveCopy(srcArchive, srcInfo, dstInfo) if err != nil { return err } defer preparedArchive.Close() resolvedDstPath = dstDir content = preparedArchive } options := types.CopyToContainerOptions{ AllowOverwriteDirWithFile: false, } return dockerCli.Client().CopyToContainer(ctx, dstContainer, resolvedDstPath, content, options) }
func (cli *DockerCli) copyToContainer(srcPath, dstContainer, dstPath string) (err error) { if srcPath != "-" { // Get an absolute source path. srcPath, err = resolveLocalPath(srcPath) if err != nil { return err } } // In order to get the copy behavior right, we need to know information // about both the source and destination. The API is a simple tar // archive/extract API but we can use the stat info header about the // destination to be more informed about exactly what the destination is. // Prepare destination copy info by stat-ing the container path. dstInfo := archive.CopyInfo{Path: dstPath} dstStat, err := cli.statContainerPath(dstContainer, dstPath) // If the destination is a symbolic link, we should evaluate it. if err == nil && dstStat.Mode&os.ModeSymlink != 0 { linkTarget := dstStat.LinkTarget if !system.IsAbs(linkTarget) { // Join with the parent directory. dstParent, _ := archive.SplitPathDirEntry(dstPath) linkTarget = filepath.Join(dstParent, linkTarget) } dstInfo.Path = linkTarget dstStat, err = cli.statContainerPath(dstContainer, linkTarget) } // Ignore any error and assume that the parent directory of the destination // path exists, in which case the copy may still succeed. If there is any // type of conflict (e.g., non-directory overwriting an existing directory // or vice versia) the extraction will fail. If the destination simply did // not exist, but the parent directory does, the extraction will still // succeed. if err == nil { dstInfo.Exists, dstInfo.IsDir = true, dstStat.Mode.IsDir() } var ( content io.Reader resolvedDstPath string ) if srcPath == "-" { // Use STDIN. content = os.Stdin resolvedDstPath = dstInfo.Path if !dstInfo.IsDir { return fmt.Errorf("destination %q must be a directory", fmt.Sprintf("%s:%s", dstContainer, dstPath)) } } else { // Prepare source copy info. srcInfo, err := archive.CopyInfoSourcePath(srcPath) if err != nil { return err } srcArchive, err := archive.TarResource(srcInfo) if err != nil { return err } defer srcArchive.Close() // With the stat info about the local source as well as the // destination, we have enough information to know whether we need to // alter the archive that we upload so that when the server extracts // it to the specified directory in the container we get the disired // copy behavior. // See comments in the implementation of `archive.PrepareArchiveCopy` // for exactly what goes into deciding how and whether the source // archive needs to be altered for the correct copy behavior when it is // extracted. This function also infers from the source and destination // info which directory to extract to, which may be the parent of the // destination that the user specified. dstDir, preparedArchive, err := archive.PrepareArchiveCopy(srcArchive, srcInfo, dstInfo) if err != nil { return err } defer preparedArchive.Close() resolvedDstPath = dstDir content = preparedArchive } query := make(url.Values, 2) query.Set("path", filepath.ToSlash(resolvedDstPath)) // Normalize the paths used in the API. // Do not allow for an existing directory to be overwritten by a non-directory and vice versa. query.Set("noOverwriteDirNonDir", "true") urlStr := fmt.Sprintf("/containers/%s/archive?%s", dstContainer, query.Encode()) response, err := cli.stream("PUT", urlStr, &streamOpts{in: content}) if err != nil { return err } defer response.body.Close() if response.statusCode != http.StatusOK { return fmt.Errorf("unexpected status code from daemon: %d", response.statusCode) } return nil }
func (cli *DockerCli) copyFromContainer(srcContainer, srcPath, dstPath string, cpParam *cpConfig) (err error) { if dstPath != "-" { // Get an absolute destination path. dstPath, err = resolveLocalPath(dstPath) if err != nil { return err } } // if client requests to follow symbol link, then must decide target file to be copied var rebaseName string if cpParam.followLink { srcStat, err := cli.statContainerPath(srcContainer, srcPath) // If the destination is a symbolic link, we should follow it. if err == nil && srcStat.Mode&os.ModeSymlink != 0 { linkTarget := srcStat.LinkTarget if !system.IsAbs(linkTarget) { // Join with the parent directory. srcParent, _ := archive.SplitPathDirEntry(srcPath) linkTarget = filepath.Join(srcParent, linkTarget) } linkTarget, rebaseName = archive.GetRebaseName(srcPath, linkTarget) srcPath = linkTarget } } query := make(url.Values, 1) query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API. urlStr := fmt.Sprintf("/containers/%s/archive?%s", srcContainer, query.Encode()) response, err := cli.call("GET", urlStr, nil, nil) if err != nil { return err } defer response.body.Close() if response.statusCode != http.StatusOK { return fmt.Errorf("unexpected status code from daemon: %d", response.statusCode) } if dstPath == "-" { // Send the response to STDOUT. _, err = io.Copy(os.Stdout, response.body) return err } // In order to get the copy behavior right, we need to know information // about both the source and the destination. The response headers include // stat info about the source that we can use in deciding exactly how to // copy it locally. Along with the stat info about the local destination, // we have everything we need to handle the multiple possibilities there // can be when copying a file/dir from one location to another file/dir. stat, err := getContainerPathStatFromHeader(response.header) if err != nil { return fmt.Errorf("unable to get resource stat from response: %s", err) } // Prepare source copy info. srcInfo := archive.CopyInfo{ Path: srcPath, Exists: true, IsDir: stat.Mode.IsDir(), RebaseName: rebaseName, } preArchive := response.body if len(srcInfo.RebaseName) != 0 { _, srcBase := archive.SplitPathDirEntry(srcInfo.Path) preArchive = archive.RebaseArchiveEntries(response.body, srcBase, srcInfo.RebaseName) } // See comments in the implementation of `archive.CopyTo` for exactly what // goes into deciding how and whether the source archive needs to be // altered for the correct copy behavior. return archive.CopyTo(preArchive, srcInfo, dstPath) }