Skip to content

Commit 263d3d3

Browse files
authored
Merge pull request #455 from tjwoodall/unique-inodes
Unique inodes
2 parents 7dfaae2 + e58fb12 commit 263d3d3

File tree

9 files changed

+512
-34
lines changed

9 files changed

+512
-34
lines changed

direct.c

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,80 @@ direct_readlinkat (struct ovl_layer *l, const char *path, char *buf, size_t bufs
165165
return TEMP_FAILURE_RETRY (readlinkat (l->fd, path, buf, bufsiz));
166166
}
167167

168+
static ino_t
169+
direct_get_nfs_filehandle (const struct ovl_layer *l, const char *path)
170+
{
171+
int mount_id;
172+
int ret = name_to_handle_at (l->fd, path, l->fh, &mount_id, 0);
173+
if (ret == -1)
174+
return 0;
175+
176+
ino_t h = 0xcbf29ce484222325ULL;
177+
for (size_t i = 0; i < l->fh->handle_bytes; i++)
178+
{
179+
h ^= l->fh->f_handle[i];
180+
h *= 0x100000001b3ULL;
181+
}
182+
183+
return h;
184+
}
185+
186+
/* Returns:
187+
-1 - Error
188+
0 - NFS filehandles not supported
189+
1 - Can use NFS filehandles */
190+
static int
191+
has_nfs_filehandles (struct ovl_layer *l, const char *path)
192+
{
193+
struct file_handle *tmp_fh;
194+
int mount_id;
195+
int ret;
196+
197+
tmp_fh = malloc (sizeof (*tmp_fh));
198+
if (tmp_fh == NULL)
199+
return -1;
200+
201+
tmp_fh->handle_bytes = 0;
202+
203+
ret = name_to_handle_at (AT_FDCWD, path, tmp_fh, &mount_id, 0);
204+
if (ret == -1 && errno == ENOTSUP)
205+
{
206+
free (tmp_fh);
207+
return 0;
208+
}
209+
/* previous call should fail with EOVERFLOW and handle_bytes replaced with
210+
* the size of the handle. EOVERFLOW can also occur if no filehandle is
211+
* available in a system that does support file-handle lookup.
212+
*/
213+
if (ret != -1 || errno != EOVERFLOW || tmp_fh->handle_bytes == 0)
214+
{
215+
free (tmp_fh);
216+
return -1;
217+
}
218+
219+
l->fh = realloc (tmp_fh, tmp_fh->handle_bytes + sizeof (*l->fh));
220+
if (! l->fh)
221+
{
222+
free (tmp_fh);
223+
return -1;
224+
}
225+
226+
ret = name_to_handle_at (AT_FDCWD, path, l->fh, &mount_id, 0);
227+
228+
if (ret == -1)
229+
{
230+
free (l->fh);
231+
l->fh = NULL;
232+
return 0;
233+
}
234+
return 1;
235+
}
236+
168237
static int
169238
direct_load_data_source (struct ovl_layer *l, const char *opaque, const char *path, int n_layer)
170239
{
171240
char tmp[64];
241+
struct stat st;
172242
l->path = realpath (path, NULL);
173243
if (l->path == NULL)
174244
{
@@ -184,6 +254,18 @@ direct_load_data_source (struct ovl_layer *l, const char *opaque, const char *pa
184254
return l->fd;
185255
}
186256

257+
if (fstat (l->fd, &st) == -1)
258+
{
259+
close (l->fd);
260+
free (l->path);
261+
l->path = NULL;
262+
return -1;
263+
}
264+
else
265+
l->st_dev = st.st_dev;
266+
267+
l->nfs_filehandles = has_nfs_filehandles (l, l->path);
268+
187269
if (fgetxattr (l->fd, XATTR_PRIVILEGED_OVERRIDE_STAT, tmp, sizeof (tmp)) >= 0)
188270
l->stat_override_mode = STAT_OVERRIDE_PRIVILEGED;
189271
else if (fgetxattr (l->fd, XATTR_OVERRIDE_CONTAINERS_STAT, tmp, sizeof (tmp)) >= 0)
@@ -230,4 +312,5 @@ struct data_source direct_access_ds = {
230312
.listxattr = direct_listxattr,
231313
.readlinkat = direct_readlinkat,
232314
.support_acls = direct_support_acls,
315+
.get_nfs_filehandle = direct_get_nfs_filehandle,
233316
};

fuse-overlayfs.1

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ fuse-overlayfs - overlayfs FUSE implementation
99
.SH SYNOPSIS
1010
.PP
1111
mounting
12-
fuse-overlayfs [-f] [--debug] [-o OPTS] MOUNT_TARGET
12+
fuse-overlayfs [\-f] [\-\-debug] [\-o OPTS] MOUNT_TARGET
1313

1414
.PP
1515
unmounting
16-
fusermount -u mountpoint
16+
fusermount \-u mountpoint
1717

1818

1919
.SH DESCRIPTION
@@ -25,26 +25,26 @@ namespace.
2525

2626
.SH OPTIONS
2727
.PP
28-
\fB--debug\fP
28+
\fB\-\-debug\fP
2929
Enable debugging mode, can be very noisy.
3030

3131
.PP
32-
\fB-o lowerdir=low1[:low2...]\fP
32+
\fB\-o lowerdir=low1[:low2...]\fP
3333
A list of directories separated by \fB\fC:\fR\&. Their content is merged.
3434

3535
.PP
36-
\fB-o upperdir=upperdir\fP
36+
\fB\-o upperdir=upperdir\fP
3737
A directory merged on top of all the lowerdirs where all the changes
3838
done to the file system will be written.
3939

4040
.PP
41-
\fB-o workdir=workdir\fP
41+
\fB\-o workdir=workdir\fP
4242
A directory used internally by fuse-overlays, must be on the same file
4343
system as the upper dir.
4444

4545
.PP
46-
\fB-o uidmapping=UID:MAPPED-UID:LEN[,UID2:MAPPED-UID2:LEN2]\fP
47-
\fB-o gidmapping=GID:MAPPED-GID:LEN[,GID2:MAPPED-GID2:LEN2]\fP
46+
\fB\-o uidmapping=UID:MAPPED-UID:LEN[,UID2:MAPPED-UID2:LEN2]\fP
47+
\fB\-o gidmapping=GID:MAPPED-GID:LEN[,GID2:MAPPED-GID2:LEN2]\fP
4848
Specifies the dynamic UID/GID mapping used by fuse-overlayfs when
4949
reading/writing files to the system.
5050

@@ -62,7 +62,7 @@ without requiring to chown the files.
6262
For example, given on the host two files like:
6363

6464
.PP
65-
$ stat -c %u:%g lower/a lower/b
65+
$ stat \-c %u:%g lower/a lower/b
6666
0:0
6767
1:1
6868

@@ -76,7 +76,7 @@ $ cat /proc/self/uid_map
7676
We would see:
7777

7878
.PP
79-
$ stat -c %u:%g merged/a merged/b
79+
$ stat \-c %u:%g merged/a merged/b
8080
65534:65534
8181
65534:65534
8282

@@ -87,32 +87,32 @@ mapped.
8787

8888
.PP
8989
In the above example, if we mount the fuse-overlayfs file system using:
90-
\fB\fC-ouidmapping=0:1000:1:1:110000:65536,gidmapping=0:1000:1:1:110000:65536\fR,
90+
\fB\fC\-ouidmapping=0:1000:1:1:110000:65536,gidmapping=0:1000:1:1:110000:65536\fR,
9191
which is the namespace configuration specified on a single line, we'd
9292
see from the same user namespace:
9393

9494
.PP
95-
$ stat -c %u:%g merged/a merged/b
95+
$ stat \-c %u:%g merged/a merged/b
9696
0:0
9797
1:1
9898

9999
.PP
100100
Those are the same IDs visible from outside the user namespace.
101101

102102
.PP
103-
\fB-o squash_to_root\fP
103+
\fB\-o squash_to_root\fP
104104
Every file and directory is owned by the root user (0:0).
105105

106106
.PP
107-
\fB-o squash_to_uid=uid\fP
108-
\fB-o squash_to_gid=gid\fP
107+
\fB\-o squash_to_uid=uid\fP
108+
\fB\-o squash_to_gid=gid\fP
109109
Every file and directory is owned by the specified uid or gid.
110110

111111
.PP
112112
It has higher precedence over \fBsquash_to_root\fP\&.
113113

114114
.PP
115-
\fB-o static_nlink\fP
115+
\fB\-o static_nlink\fP
116116
Set st_nlink to the static value 1 for all directories.
117117

118118
.PP
@@ -122,9 +122,51 @@ be a slow operation. With this option enabled, the number of hard
122122
links reported when running stat for any directory is 1.
123123

124124
.PP
125-
\fB-o noacl\fP
125+
\fB\-o noacl\fP
126126
Disable ACL support in the FUSE file system.
127127

128+
.PP
129+
\fB\-o xino=off|auto|on\fP
130+
Controls how \fIst_ino\fP values are generated for files exposed by fuse-overlayfs.
131+
132+
When all lower and upper layers reside on the same underlying device,
133+
fuse-overlayfs exposes the real inode number from the underlying filesystem.
134+
When layers span multiple devices, an opaque inode number is generated; by
135+
default this value is not stable across mounts.
136+
137+
The \fBxino\fP option modifies this behavior:
138+
139+
.PP
140+
\fB\-o xino=off\fP
141+
Disables extended inode generation. This matches the default behavior:
142+
when all layers are on the same device, the underlying inode number is used;
143+
otherwise an opaque, non‑stable inode number is returned.
144+
145+
.PP
146+
\fB\-o xino=auto\fP
147+
Attempts to generate stable inode numbers across mounts by hashing the file
148+
handle returned by \fIname_to_handle_at\fP(2).
149+
This mode is used only if all layers support \fIname_to_handle_at\fP(2); if any
150+
layer does not, behavior falls back to \fBxino=off\fP.
151+
If all layers are on the same device, the underlying inode number is still
152+
used, regardless of this setting.
153+
154+
.PP
155+
\fB\-o xino=on\fP
156+
Requires that all layers support \fIname_to_handle_at\fP(2). If they do, inode
157+
numbers are derived from a hash of the file handle and remain stable across
158+
mounts.
159+
If any layer does not support \fIname_to_handle_at\fP(2), the mount fails.
160+
As with other modes, when all layers are on the same device, the underlying
161+
inode number always takes precedence.
162+
163+
.PP
164+
\fB\-o ino32_t\fP
165+
Forces all returned \fIst_ino\fP values to be truncated to 32 bits.
166+
167+
This option exists solely for compatibility with older 32‑bit userspaces that
168+
cannot correctly handle 64‑bit inode numbers. It has no functional benefit on
169+
modern systems and should not be used unless required for legacy compatibility.
128170

129171
.SH SEE ALSO
130172
.PP

fuse-overlayfs.1.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,44 @@ links reported when running stat for any directory is 1.
100100
**-o noacl**
101101
Disable ACL support in the FUSE file system.
102102

103+
**-o xino=off|auto|on**
104+
Controls how `st_ino` values are generated for files returned by fuse-overlayfs.
105+
106+
When all lower and upper layers reside on the same underlying device,
107+
fuse-overlayfs exposes the real inode number from the underlying filesystem.
108+
When layers span multiple devices, an opaque inode number is generated; by
109+
default this value is not stable across mounts.
110+
111+
The `xino` option modifies this behavior:
112+
113+
**xino=off**
114+
Disables extended inode generation. This matches the default behavior:
115+
when all layers are on the same device, the underlying inode number is used;
116+
otherwise an opaque, non‑stable inode number is returned.
117+
118+
**xino=auto**
119+
Attempts to generate stable inode numbers across mounts by hashing the file
120+
handle returned by `name_to_handle_at(2)`.
121+
This mode is used only if all layers support `name_to_handle_at(2)`; if any
122+
layer does not, behavior falls back to `xino=off`.
123+
If all layers are on the same device, the underlying inode number is still
124+
used, regardless of this setting.
125+
126+
**xino=on**
127+
Requires that all layers support `name_to_handle_at(2)`. If they do, inode
128+
numbers are derived from a hash of the file handle and remain stable across
129+
mounts.
130+
If any layer does not support `name_to_handle_at(2)`, the mount fails.
131+
As with other modes, when all layers are on the same device, the underlying
132+
inode number always takes precedence.
133+
134+
**-o ino32_t**
135+
Forces all returned `st_ino` values to be truncated to 32 bits.
136+
137+
This option exists solely for compatibility with older 32‑bit userspaces that
138+
cannot correctly handle 64‑bit inode numbers. It has no functional benefit on
139+
modern systems and should not be used unless required for legacy compatibility.
140+
103141
# SEE ALSO
104142

105143
**fuse**(8), **mount**(8), **user_namespaces**(7)

fuse-overlayfs.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ struct ovl_data
105105
int squash_to_uid;
106106
int squash_to_gid;
107107
int static_nlink;
108+
int ino_passthrough;
109+
int nfs_filehandles;
110+
int ino_t_32;
108111

109112
int volatile_mode;
110113

@@ -137,6 +140,10 @@ struct ovl_layer
137140

138141
void *data_source_private_data;
139142
int stat_override_mode;
143+
144+
dev_t st_dev;
145+
int nfs_filehandles;
146+
struct file_handle *fh;
140147
};
141148

142149
/* a data_source defines the methods for accessing a lower layer. */
@@ -156,6 +163,7 @@ struct data_source
156163
int (*getxattr) (struct ovl_layer *l, const char *path, const char *name, char *buf, size_t size);
157164
ssize_t (*readlinkat) (struct ovl_layer *l, const char *path, char *buf, size_t bufsiz);
158165
bool (*support_acls) (struct ovl_layer *l);
166+
ino_t (*get_nfs_filehandle) (const struct ovl_layer *l, const char *path);
159167
};
160168

161169
/* passthrough to the file system. */

0 commit comments

Comments
 (0)