Skip to content

Instantly share code, notes, and snippets.

@jschwinger233
Last active March 2, 2020 17:09
Show Gist options
  • Select an option

  • Save jschwinger233/d8486dac6cef0d4b88be40f5409515eb to your computer and use it in GitHub Desktop.

Select an option

Save jschwinger233/d8486dac6cef0d4b88be40f5409515eb to your computer and use it in GitHub Desktop.

发现 docker cp 的实现跟屎一样.

一开始发现的问题是 docker-ce/components/client 里的 CopyToContainer 这个 interface, 签名是这样的:

CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error

我注意到的问题是里面 path 必须是 dirname, 比如说你想 docker cp ./a.txt $CONTAINER_ID:/etc/b.txt, 那么 path 必须是 /etc, 否则直接报错.

那么问题来了, destination filename 都没传过去, dockerd 是怎么知道文件名的? 就比如上面的复制到 /etc/b.txt, 我都没把 b.txt 告诉 dockerd 它怎么可能做到的?

懒, 不想看代码, 先抓包, docker -H tcp://10.143.201.210:2376 cp /tmp/a.txt 4ea389f2119e:/etc/b.txt, 发现是这样的:

PUT /v1.39/containers/4ea389f2119e/archive?noOverwriteDirNonDir=true&path=%2Fetc HTTP/1.1
Host: 10.143.201.210:2376
User-Agent: Docker-Client/19.03.5 (darwin)
Transfer-Encoding: chunked
Content-Type: text/plain

PaxHeaders.0/b.txt..................................................................................0000000.0000000.0000000.00000000021.00000000000.011174. x....................................................................................................ustar.00.......................................................................................................................................................................................................................................................

那么一看到 PaxHeaders 就了然了, body 是一个 tarball, 所以 dest filename 也蕴藏其中.

那么问题来了, CopyToContainer interface 里的类型签名只说是 container io.Reader, 我直接 bytes.NewBuffer() 生成一个 reader 它岂不是要仆街?

看了一下源码, 结果在接口实现里注释写着:

// CopyToContainer copies content into the container filesystem.
// Note that `content` must be a Reader for a TAR archive
func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath string, content io.Reader, options types.CopyToContainerOptions) error {

我服了... 垃圾 API, 居然会在客户端创建一个临时文件.tar 再 open, 匪夷所思, 给我三个大脑我都想不出这种实现, 内存里不能压缩 bytes buffer 吗?

更垃圾在后面.

我发现 docker cp 连 process substitution 都不认, 具体的命令是这样的:

docker cp /tmp/a.txt 4ea389f2119e:/etc/b.txt // ok
docker cp <(cat /tmp/a.txt) 4ea389f2119e:/etc/b.txt // not ok

后者的命令不会出错, 然而如果你去看 cp 进去是啥文件的时候就:

# docker exec -it f21996c9dab3 ls -l /etc/b.txt
lrwxrwxrwx    1 root     root            15 Mar  2 15:12 /etc/b.txt -> pipe:[38941509]

这垃圾居然直接把 process substitution 的 pipe 打到 tarball 里了, 就不能认认真真去 read 到内存里吗?

然后它还有一个 followLink 的 option, 这次直接报错了:

# docker -H tcp://localhost:2376 cp --follow-link <(cat /tmp/a.txt) f21996c9dab3:/etc/b.txt
lstat /proc/300038/fd/pipe:[38956641]: no such file or directory

这次直接报错了, 看了一下代码, 因为在 follow 的情况下它非要去 os.Lstat 一下 link 的 dest, 然而 pipe 在文件系统上是不存在的, 就挂了.

我 真 的 服 了.

做俯卧撑的时间突然明白了, 这一切的设计都是为了能够复制目录.

不想 cp(1) 那样需要 cp -r 来区分复制目录和文件, docker cp 由于 tar 打包的设计用一套接口就可以处理好文件和目录.

不, 我还是不接受, 怎么可以连 process sub 都处理不好呢, 我个人认为一切接受文件名的地方, 必须能够接受 process substitution, 唉.

@timfeirg
Copy link
Copy Markdown

timfeirg commented Mar 2, 2020

你的重定向的写法是不是要用 -?

Use '-' as the source to read a tar archive from stdin
and extract it to a directory destination in a container.
Use '-' as the destination to stream a tar archive of a
container source to stdout.

@jschwinger233
Copy link
Copy Markdown
Author

你的重定向的写法是不是要用 -?

Use '-' as the source to read a tar archive from stdin
and extract it to a directory destination in a container.
Use '-' as the destination to stream a tar archive of a
container source to stdout.

我知道, 不过这不叫重定向.

重定向的定义很简单, 就是要调用 dup(2) 系列的 syscall 让不同的 fd 指向相同的 file description;

- 的 convention 本质上只是指代 /dev/stdin 或者 /proc/self/fd/0, 理论上来说, 能接受 - 的地方, 应该可能等价替换为 /dev/stdin, /proc/self/fd/0, <(cat /dev/stdin), 然而我吐槽的就是 docker cp 不可以.

快加入我们的 tg discussion (拉你了

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment