-
Notifications
You must be signed in to change notification settings - Fork 658
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Does OCI allow "cross-layer hardlinks"? And how should they be handled? #1204
Comments
For anyone curious, here's the OCI image (linux/amd64) that I got from |
Personally, I think the following 2 rules should be specified in the specification:
|
I can't think of a good solution for this one because there are multiple issues a layered filesystem creates with hard links. First, we can't assume layer order from within the context of a single layer. Each layer may be reused in multiple images, with different preceding layers. That means extracting one image could create a hard link to a file in a preceding layer that doesn't match when another image is extracted, potentially allowing a data leakage across images. The other challenge I have with creating a solution is that hard links are to an inode, but one file that is modified across layers could have multiple inodes, and I don't think there's a way to know with the layered filesystem that's performing a copy-on-write whether that write would have changed the original inode or not. That means a file created in one layer, and either modified in the running container or modified in a later layer, could lose the associated connection with a hard link. My initial inclination is to say that hard links only have limited support within a single layer, and once a hard link attempts to go beyond a layer boundary, either by creating that hard link in a later layer, or by performing a copy-on-write of any file included in a hard link, it may be converted to a regular file and no longer be associated with the other file. |
I ran a small test using overlay2 and it looks like the hard link source is pulled up to the new layer, but not other files also linked to that file: $ cat df.hard-link
FROM alpine
RUN echo hello >foo.txt \
&& ln foo.txt foo-same-layer.txt
RUN ln foo.txt foo-new-layer.txt
$ docker run -it --rm test-hard-link
/ # ls -li foo*.txt
39231816 -rw-r--r-- 2 root root 6 Oct 24 20:24 foo-new-layer.txt
39231807 -rw-r--r-- 2 root root 6 Oct 24 20:24 foo-same-layer.txt
39231816 -rw-r--r-- 2 root root 6 Oct 24 20:24 foo.txt
/ # ln foo.txt foo-container.txt
/ # ls -li foo*.txt
39232186 -rw-r--r-- 2 root root 6 Oct 24 20:24 foo-container.txt
39231816 -rw-r--r-- 2 root root 6 Oct 24 20:24 foo-new-layer.txt
39231807 -rw-r--r-- 2 root root 6 Oct 24 20:24 foo-same-layer.txt
39232186 -rw-r--r-- 2 root root 6 Oct 24 20:24 foo.txt
/ # echo world >foo.txt
/ # ls -li foo*.txt
39232186 -rw-r--r-- 2 root root 6 Oct 24 20:32 foo-container.txt
39231816 -rw-r--r-- 2 root root 6 Oct 24 20:24 foo-new-layer.txt
39231807 -rw-r--r-- 2 root root 6 Oct 24 20:24 foo-same-layer.txt
39232186 -rw-r--r-- 2 root root 6 Oct 24 20:32 foo.txt This aligns with the kernel documentation on overlay filesystems without the index configured:
|
I think most implementations delegate this to the underlying union filesystem implementation. However the link should not go outside of the layer (aka change set). If it does, the link across layers would not be recorded, breaking the link and storing the individual file. Here's the relevant spec text that covers that scenario:
|
Hi, OCI. I was writing an OCI image parser, and quickly realized there's some serious undefined behaviors about hardlinks.
First, let's recall that a hardlink is a filesystem entry that actually points to the same "file" (
inode
) as another filesystem entry. So, modifying a hardlink can lead to implicit and unpredictable changes to other filesystem entries, which actually provides a mean of implicit communication. Treating hardlinks as independent normal files can cause runtime error if the application relies on the implicit communication assumption of hardlinks. Second, to remind all of us, OCI image layers are in thetar
format, e.g. POSIX pax/ustar/cpio standard, which allows hardlinks and duplicate paths.Indeed, there has been some content about hardlinks in current specification. But they are not enough to answer the following questions:
What if a layer contains an invalid hardlink, for example, pointing to an non-existent path? Should we consider the image as invalid or just ignore it?
When creating the filesystem bundle, what should we do if a subsequent layer has an entry that is a hardlink in previous layer (sharing an
inode
with many other filesystem entries)? Should we unlink the filesystem entry with the previousinode
and create a newinode
with the data in the new layer, or to update the existinginode
with the data (so that all hardlinked filesystem entries are affected)?When building the OCI image, how should it be recorded in the image if the user creates hardlinks to files of previous layer? In such case, the layer itself may be an error
tar
file, but can be extracted successfully under the condition that the previous layers are extracted in order.(I believe there are more problems with regard to the
tar
format. Comments are welcomed.)There has been an issue about hardlink and symlink: #857 . But I believe it does not covers all the problems I list above here.
Personally, for question 3, I did an experiment with Docker. I write a simple static-linked C program that creates a copy, a hardlink, a symlink, and print their
inode
id:Then I build an image from scratch with the compiled C program:
When I run the image on the same machine that built it, here's the output:
We can see that
/b
is a hardlink to/a.out
, as expected.However, if I use
docker save
to dump the image into a.tar.gz
file, I find that the/b
entry in layer 3 actually has a type0
, which means it is stored as a normal file, instead of hardlink. To further validate my suspicion, I copy the.tar.gz
file to another machine with Docker, and the result is:This means
/b
is now a regular file, which is not expected, or it is? Anyway, this example indicates that even Docker is confused with such situation.The text was updated successfully, but these errors were encountered: