summaryrefslogtreecommitdiff
path: root/tmpoverlay
blob: 313309dae0080c32a967faf5d340f4451e6c16db (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#!/bin/sh

usage() {
    cat << EOF
usage: tmpoverlay [OPTIONS] SOURCE [DESTINATION]

Create a tmpfs-backed overlayfs for SOURCE, which is one or more directories
separated by colons. If DESTINATION is specified, mount the result at
DESTINATION. Otherwise, mount it on top of the last SOURCE.

To free the memory, simply unmount the DESTINATION.

tmpoverlay is not fully race-safe. It tries to match the ownership and
permissions of the tmpfs to the final SOURCE, but this is done in a step before
mounting. Therefore, any permission changes immediately before mounting may be
hidden by the mount.

options:
  -h        print this help
  -k        don't clean up temporary directory
  -o        additional overlayfs mount options
  -n        pass -n to mount (don't write to /etc/mtab)
  -t        additional tmpfs mount options
  -T        temporary directory
  -v        verbose mode

examples:
  tmpoverlay / /tmp/my_root    # make a thin copy of root
  tmpoverlay /etc              # make read-only /etc writable
EOF
}

log() {
    fmt=$1
    shift
    printf "tmpoverlay: $fmt\n" "$@" >&2
}

verb() {
    if [ -n "$verbose" ]; then
        log "$@"
    fi
}

die() {
    log "$@"
    exit 1
}

args=$(getopt -n tmpoverlay hko:nt:T:v "$@") || { usage >&2; exit 1; }
eval set -- "$args"
unset args

unset no_mtab extra_overlayfs_opts tmpfs_opts tmpdir verbose
while true; do
    case "$1" in
        '-h') usage; exit 0;;
        '-o')
            [ -n "$2" ] && extra_overlayfs_opts="$extra_overlayfs_opts,$2"
            shift 2
            ;;
        '-n') no_mtab=1; shift;;
        '-t')
            [ -n "$2" ] && tmpfs_opts="${tmpfs_opts:+$tmpfs_opts,}$2"
            shift 2
            ;;
        '-T')
            [ -d "$2" ] || die 'tmpdir "%s" is not a directory' "$2"
            tmpdir=$2
            shift 2
            ;;
        '-v') verbose=1; shift;;
        '--') shift; break;;
        *) die "getopt failure"
    esac
done

case "$#" in
    1) lowerdir=$1 dir=${1##*:};;
    2) lowerdir=$1 dir=$2;;
    *) usage >&2; exit 1;;
esac

ld=
set -f
savedIFS=$IFS IFS=:
for d in $lowerdir; do
    d=$(realpath -e "$d")
    [ -d "$d" ] || die 'lowerdir "%s" is not a directory' "$d"
    ld=$ld:$d
done
IFS=$savedIFS
set +f
ld=${ld#:}
[ -d "$dir" ] || die 'destination "%s" is not a directory' "$dir"

err() {
    if [ -n "$mounted_tmpdir" ]; then
        umount "$tmpdir" || exit 1
    fi
    if [ -n "$tmpdir" ] && [ -n "$created_tmpdir" ]; then
        rm -r "$tmpdir"
    fi
    exit 1
}

if [ -z "$tmpdir" ]; then
    tmpdir=$(umask 077; mktemp -d --tmpdir tmpoverlay.XXXXXXXXXX) || exit 1
    created_tmpdir=1
    verb 'created tmpdir: %s' "$tmpdir"
fi
mount -t tmpfs ${tmpfs_opts:+-o "$tmpfs_opts"} ${verbose:+-v} tmpfs "$tmpdir" || err
mounted_tmpdir=1
upperdir="$tmpdir/upper"
workdir="$tmpdir/work"
mkdir "$upperdir" "$workdir" || err
chown --reference="${ld##*:}" "$upperdir" "$workdir"
chmod --reference="${ld##*:}" "$upperdir" "$workdir"

overlayfs_opts="lowerdir=$ld,upperdir=$upperdir,workdir=$workdir$extra_overlayfs_opts"
mount -t overlay -o "$overlayfs_opts" ${no_mtab:+-n} ${verbose:+-v} overlay "$dir" || err

umount "$tmpdir" || err
rm -r "$tmpdir" || err