As a snap author you will have some new syntax you can put at the top-level of your snapcraft.yaml
or snap.yaml
. Let me start with a simple example that we shall discuss below:
snap.yaml and snapcraft.yaml syntax
name: my-fun-snap
version: 1.0
apps:
my-fun-snap:
command: ...
...
layout:
/usr:
bind: $SNAP/usr
/mytmp:
type: tmpfs
user: nobody
group: nobody
mode: 1777
/mylink:
symlink: /link/target
As you can see layout is a top-level construct that describes filesystem entries. Each entry can be of one of the three inferred types: a bind mount, a tmpfs mount or a symlink.
Bind mount
Bind mounts are perhaps the most familiar and the first to arrive soon. The declaration above will hide anything that is usually present in /usr
and replace it with what the snap ships in its $SNAP/usr/
directory. The variable $SNAP
is expanded to a path such as /snap/my-fun-snap/42/
internally.
If you never played with bind mounts you can experiment on your own system using the mount --bind /source /target
command (you can unmount /target
later). Keep in mind that bind mounts are not copies or symlinks. Anything that is visible from /source
becomes visible from /target
. Notably both /source
and /target
must exist (even if they are just empty directories) as mount needs to attach to an existing file or directory.
If there are other things mounted in /source
, for example if /source/data
is a mounted USB stick the way the mount --bind
command is issued affects what will show up in /target/data
. If the bind mount is recursive then all of the things mount that were mounted in /source
at the time the command was issued will also show up in /target
. On command line this can be done by using mount --rbind
which stands for recursive bind.
After the bind is done subsequent mount operations can be done both inside /source
and /target
and the behavior of those changes depending on the type of sharing that is set up. In simple terms when we mount or unmount /source/data
this event can propagate to peer groups (such as /target
). The event can propagate both ways (shared) from master to slaves only (master and slave) or not at all (private). This allows complex arrangements where events only propagate in certain directions, or even to things that are not visible from the point of view of the process inhabiting a given mount namespace. You don’t necessarily have to use all of those features but it is worth remembering they exist.
For the curious readers this is described in detail in the Linux kernel documentation https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt (the keyword is “shared subtree”).
The bind layout element will allow only a subset of this complexity, namely recursive, shared mounts. This is the most intuitive behavior that fits most use cases.
Interestingly, the source directory may refer to places other than $SNAP. In fact, most arbitrary paths can be used. There will be some limitations (described below), like /media
but in general unless you are trying to be malicious the system should not get in your way.
The target directory can already exist but will be created automatically if necessary. The user
, group
and mode
attributes can be used to define fine-grained details about the created parent directories. The technical approach to creating new entires on top of a read only substrate, such as a squashfs filesystem, will be described in the implementation notes in a subsequent post. From the developer’s and users point of view it will just work.
TempFS
The tmpfs element are also very familiar. They will allow snap developers to put a new, ephemeral, temporary file system at the desired location. The same semantics regarding variable expansion (e.g. $SNAP
, $SNAP_COMMON
or $SNAP_DATA
), creation of parent directories, application of user, group and mode (permissions).
Use of tmpfs can be beneficial to explicitly create empty spaces, e.g. for small amounts of ephemeral data, lock files, pid files or other typical things. Keep in mind that tmpfs is backed by RAM and can be very constrained. At this time there is no syntax to describe the size limits on mounted tmpfs but such keyword may be added later.
Symlink
The symlink element can be used to create a symlink rooted at the designated location and pointing at the specified target. The target doen’t have to exist (the symlink can be broken). The same semantics regarding creation of parent directories, user and group ownership and permissions mentioned earlier applies.
Syminks or bind mounts? What is appropriate for me?
Symlinks and bind-mounts have similar “feeling” but they behave differently. Symlinks are very common and many applications can detect them. In comparison bind mounts are not as easy to detect so they have higher chance of being transparent to the application. The downside is that a mount point (/target
) cannot be removed before it is unmounted while many applications can simply unlink a symbolic link easily.
Bind mount gotchas
Lastly bind mounts have some, say, unusual semantics when the source is deleted. Let’s look at a simple example
root@fyke:~# mkdir experiment
root@fyke:~# cd experiment/
root@fyke:~/experiment# mkdir source
root@fyke:~/experiment# mkdir target
root@fyke:~/experiment# mount --bind source target
root@fyke:~/experiment# ls -l
total 8
drwxr-xr-x 2 root root 4096 lip 28 15:44 source
drwxr-xr-x 2 root root 4096 lip 28 15:44 target
root@fyke:~/experiment# cat /proc/self/mountinfo | egrep 'source|target'
139 25 8:2 /home/zyga/experiment/source /home/zyga/experiment/target rw,relatime shared:1 - ext4 /dev/sda2 rw,errors=remount-ro,data=ordered
root@fyke:~/experiment# rmdir target
rmdir: failed to remove 'target': Device or resource busy
root@fyke:~/experiment# rmdir source
root@fyke:~/experiment# cat /proc/self/mountinfo | egrep 'source|target'
139 25 8:2 /home/zyga/experiment/source//deleted /home/zyga/experiment/target rw,relatime shared:1 - ext4 /dev/sda2 rw,errors=remount-ro,data=ordered
root@fyke:~/experiment# touch target/file
touch: cannot touch 'target/file': No such file or directory
root@fyke:~/experiment# umount target
root@fyke:~/experiment# ls -l
total 4
drwxr-xr-x 2 root root 4096 lip 28 15:44 target
root@fyke:~/experiment# rmdir target
When the target is mounted it cannot be removed. This restriction does not apply to source but once removed the target is, well, broken. It’s still a mount point but it no longer exists so we cannot create anything inside it anymore (anything that used to be there would have to be removed before the source would be possible to remove anyway so it is always empty).