├── .gitignore ├── snap ├── hooks │ └── install └── snapcraft.yaml ├── scripts └── bin │ ├── udiskctl.sh │ ├── auto-mount │ └── auto-install └── udisksd.patch /.gitignore: -------------------------------------------------------------------------------- 1 | *.snap 2 | parts/ 3 | prime/ 4 | stage/ 5 | -------------------------------------------------------------------------------- /snap/hooks/install: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #WATCHED is a signal file that informs a USB drive is mounted 4 | touch ${SNAP_COMMON}/watched 5 | -------------------------------------------------------------------------------- /scripts/bin/udiskctl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | if [ "$(id -u)" -ne 0 ]; then 4 | echo "ERROR: You need to run udisksctl as root!" 5 | exit 1 6 | fi 7 | 8 | exec $SNAP/usr/local/bin/udisksctl $@ 9 | -------------------------------------------------------------------------------- /scripts/bin/auto-mount: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | sleep 5 4 | 5 | $SNAP/usr/local/bin/udisksctl monitor | grep --line-buffered " Device:" | while read -r -- device; do 6 | 7 | echo "$SNAP_NAME.auto-mount found ${device}" 8 | if echo $device| grep -q "[0-9]$"; then 9 | DEV="$(echo $device | sed 's/^Device: //')" 10 | fi 11 | if [ -n "$DEV" ]; then 12 | RES=`$SNAP/usr/local/bin/udisksctl mount --no-user-interaction --block-device "$DEV"` 13 | # process returned value into the mount point string 14 | RES2=`echo $RES | cut -f4 -d\ | cut -f1 -d.` 15 | echo $RES2 > $WATCHED 16 | fi 17 | done 18 | -------------------------------------------------------------------------------- /udisksd.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/udisksdaemon.c b/src/udisksdaemon.c 2 | index 9ca899e0..27b6ba17 100644 3 | --- a/src/udisksdaemon.c 4 | +++ b/src/udisksdaemon.c 5 | @@ -264,12 +264,17 @@ udisks_daemon_constructed (GObject *object) 6 | } 7 | } 8 | 9 | - if (!g_file_test (PACKAGE_LOCALSTATE_DIR "/lib/udisks2", G_FILE_TEST_IS_DIR)) 10 | + /* If we're running in a snap environment just keep the state in $SNAP_DATA. 11 | + * otherwise we need to create a traditional state dir */ 12 | + if (g_getenv ("SNAP_DATA") == NULL) 13 | { 14 | - if (g_mkdir_with_parents (PACKAGE_LOCALSTATE_DIR "/lib/udisks2", 0700) != 0) 15 | - { 16 | - udisks_critical ("Error creating directory %s: %m", PACKAGE_LOCALSTATE_DIR "/lib/udisks2"); 17 | - } 18 | + if (!g_file_test (PACKAGE_LOCALSTATE_DIR "/lib/udisks2", G_FILE_TEST_IS_DIR)) 19 | + { 20 | + if (g_mkdir_with_parents (PACKAGE_LOCALSTATE_DIR "/lib/udisks2", 0700) != 0) 21 | + { 22 | + udisks_error ("Error creating directory %s: %m", PACKAGE_LOCALSTATE_DIR "/lib/udisks2"); 23 | + } 24 | + } 25 | } 26 | 27 | 28 | diff --git a/src/udisksstate.c b/src/udisksstate.c 29 | index 4d6e95a9..8c23286c 100644 30 | --- a/src/udisksstate.c 31 | +++ b/src/udisksstate.c 32 | @@ -2236,14 +2236,20 @@ udisks_state_get (UDisksState *state, 33 | * - could also mmap the file 34 | */ 35 | 36 | -#ifdef HAVE_FHS_MEDIA 37 | - /* /media usually isn't on a tmpfs, so we need to make this persistant */ 38 | - if (strcmp (key, "mounted-fs") == 0) 39 | - path = g_strdup_printf (PACKAGE_LOCALSTATE_DIR "/lib/udisks2/%s", key); 40 | + if (g_getenv ("SNAP_DATA") != NULL) 41 | + { 42 | + path = g_strdup_printf ("%s/%s", g_getenv ("SNAP_DATA"), key); 43 | + } 44 | else 45 | + { 46 | +#ifdef HAVE_FHS_MEDIA 47 | + /* /media usually isn't on a tmpfs, so we need to make this persistant */ 48 | + if (strcmp (key, "mounted-fs") == 0) 49 | + path = g_strdup_printf (PACKAGE_LOCALSTATE_DIR "/lib/udisks2/%s", key); 50 | + else 51 | #endif 52 | - path = g_strdup_printf ("/run/udisks2/%s", key); 53 | - 54 | + path = g_strdup_printf ("/run/udisks2/%s", key); 55 | + } 56 | 57 | /* see if it's already in the cache */ 58 | ret = g_hash_table_lookup (state->cache, path); 59 | @@ -2309,13 +2315,20 @@ udisks_state_set (UDisksState *state, 60 | data = g_malloc (size); 61 | g_variant_store (normalized, data); 62 | 63 | + if (g_getenv ("SNAP_DATA") != NULL) 64 | + { 65 | + path = g_strdup_printf ("%s/%s", g_getenv ("SNAP_DATA"), key); 66 | + } 67 | + else 68 | + { 69 | #ifdef HAVE_FHS_MEDIA 70 | /* /media usually isn't on a tmpfs, so we need to make this persistant */ 71 | - if (strcmp (key, "mounted-fs") == 0) 72 | - path = g_strdup_printf (PACKAGE_LOCALSTATE_DIR "/lib/udisks2/%s", key); 73 | - else 74 | + if (strcmp (key, "mounted-fs") == 0) 75 | + path = g_strdup_printf (PACKAGE_LOCALSTATE_DIR "/lib/udisks2/%s", key); 76 | + else 77 | #endif 78 | - path = g_strdup_printf ("/run/udisks2/%s", key); 79 | + path = g_strdup_printf ("/run/udisks2/%s", key); 80 | + } 81 | 82 | g_hash_table_insert (state->cache, g_strdup (path), g_variant_ref (value)); 83 | 84 | -------------------------------------------------------------------------------- /snap/snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: automount-actions 2 | base: core20 3 | version: '0.2' 4 | summary: Automatically mount USB and take actions 5 | description: | 6 | This snap waits for USB plug events of block devices and automatically 7 | mounts such devices under the /media dir when inserted and then installs 8 | asserted snaps found there. 9 | 10 | grade: stable 11 | confinement: strict 12 | 13 | apps: 14 | udisksd: 15 | command: usr/local/libexec/udisks2/udisksd 16 | daemon: simple 17 | slots: 18 | - service 19 | plugs: 20 | - hardware-observe 21 | - mount-observe 22 | environment: 23 | LD_LIBRARY_PATH: $SNAP/usr/local/lib 24 | # Signal file to inform auto-install daemon the mount point of USB 25 | WATCHED: $SNAP_COMMON/watched 26 | 27 | udiskctl: 28 | command: bin/udiskctl.sh 29 | plugs: 30 | - block-devices 31 | - hardware-observe 32 | - mount-observe 33 | environment: 34 | LD_LIBRARY_PATH: $SNAP/usr/local/lib 35 | # Signal file to inform auto-install daemon the mount point of USB 36 | WATCHED: $SNAP_COMMON/watched 37 | 38 | auto-mount: 39 | command: bin/auto-mount 40 | daemon: simple 41 | after: [ udisksd ] 42 | environment: 43 | LD_LIBRARY_PATH: $SNAP/usr/local/lib 44 | # Signal file to inform auto-install daemon the mount point of USB 45 | WATCHED: $SNAP_COMMON/watched 46 | 47 | auto-install: 48 | command: bin/auto-install 49 | daemon: simple 50 | # Install as disabled because most interfaces are not auto-connecting 51 | # You can remove this line if you enable the interfaces to auto-connect 52 | install-mode: disable 53 | plugs: 54 | - snapd-control 55 | environment: 56 | LD_LIBRARY_PATH: $SNAP/usr/local/lib 57 | # Signal file to inform auto-install daemon the mount point of USB 58 | WATCHED: $SNAP_COMMON/watched 59 | 60 | 61 | slots: 62 | service: 63 | interface: udisks2 64 | 65 | plugs: 66 | client: 67 | interface: udisks2 68 | removable-media: 69 | interface: removable-media 70 | 71 | layout: 72 | /var/lib/udisks2: 73 | bind: $SNAP_DATA/var/lib/udisks2 74 | /etc/libblockdev/conf.d: 75 | bind: $SNAP/etc/libblockdev/conf.d 76 | /usr/local/etc/udisks2: 77 | bind: $SNAP/usr/local/etc/udisks2 78 | /etc/crypttab: 79 | bind-file: $SNAP/etc/crypttab 80 | /usr/sbin/dumpe2fs: 81 | bind-file: $SNAP/usr/sbin/dumpe2fs 82 | 83 | parts: 84 | actions: 85 | source: scripts/ 86 | plugin: dump 87 | 88 | debs: 89 | plugin: nil 90 | stage-packages: 91 | - curl 92 | - inotify-tools 93 | 94 | pkgs: 95 | plugin: nil 96 | stage-packages: 97 | - mount 98 | - e2fsprogs 99 | organize: 100 | sbin/dumpe2fs: usr/sbin/dumpe2fs 101 | bin/umount: usr/bin/umount 102 | 103 | udisks: 104 | source: https://github.com/storaged-project/udisks.git 105 | source-tag: udisks-2.8.4 106 | plugin: autotools 107 | autotools-configure-parameters: 108 | - --enable-fhs-media 109 | override-pull: | 110 | snapcraftctl pull 111 | git apply -v $SNAPCRAFT_PROJECT_DIR/udisksd.patch 112 | prime: 113 | - -lib/pkgconfig 114 | - -lib/systemd 115 | - -lib/cgmanager 116 | build-packages: 117 | - gnome-common 118 | - gobject-introspection 119 | - gtk-doc-tools 120 | - intltool 121 | - libacl1-dev 122 | - libatasmart-dev 123 | - libblockdev-btrfs-dev 124 | - libblockdev-crypto-dev 125 | - libblockdev-dev 126 | - libblockdev-dm-dev 127 | - libblockdev-fs-dev 128 | - libblockdev-kbd-dev 129 | - libblockdev-loop-dev 130 | - libblockdev-lvm-dbus-dev 131 | - libblockdev-lvm-dev 132 | - libblockdev-mdraid-dev 133 | - libblockdev-mpath-dev 134 | - libblockdev-part-dev 135 | - libblockdev-part-err-dev 136 | - libblockdev-swap-dev 137 | - libblockdev-utils-dev 138 | - libgirepository1.0-dev 139 | - libglib2.0-dev 140 | - libgudev-1.0-dev 141 | - libmount-dev 142 | - libpolkit-agent-1-dev 143 | - libpolkit-gobject-1-dev 144 | - libsystemd-dev 145 | - pkg-config 146 | - udev 147 | - xsltproc 148 | stage-packages: 149 | - libacl1 150 | - libatasmart4 151 | - libglib2.0-0 152 | - libgudev-1.0-0 153 | - libpam-systemd 154 | - libpolkit-agent-1-0 155 | - libpolkit-gobject-1-0 156 | - parted 157 | - libmount1 158 | - libblockdev2 159 | - libblockdev-swap2 160 | - libblockdev-loop2 161 | - libblockdev-crypto2 162 | - libblockdev-mdraid2 163 | - libblockdev-part2 164 | - libblockdev-fs2 165 | -------------------------------------------------------------------------------- /scripts/bin/auto-install: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | LOG_SNAP_COMMON=${SNAP_COMMON}/auto-install-usb.log 4 | 5 | # log to both SNAP_COMMON and the mounted partition 6 | function log(){ 7 | echo -e "`date -Iseconds` ${1}" >> ${LOG_SNAP_COMMON} 8 | echo -e "`date -Iseconds` ${1}" >> ${LOG_MOUNTED} 9 | } 10 | 11 | # log with echo to both SNAP_COMMON and the mounted partition 12 | function logecho(){ 13 | echo -e "${1}" 14 | log "${1}" 15 | } 16 | 17 | # Outter loop ensures that the process continues after 18 | # insertion of USB with inapplicable content (for example no asserts and snaps) 19 | # causes break on inner loop 20 | while true 21 | do 22 | # create watched file just to be sure 23 | [[ -n ${WATCHED} ]] && touch ${WATCHED} 24 | 25 | while true; 26 | do 27 | sleep 5 28 | #WATCHED is touched on automount and its content is the mount point 29 | ${SNAP}/usr/bin/inotifywait -e open ${WATCHED} 30 | 31 | #MNT is the mountpoint of the USB drive 32 | MNT=`cat ${WATCHED}` 33 | 34 | LOG_MOUNTED=${MNT}/auto-install-usb.log 35 | 36 | echo -e "Mount signal file ${WATCHED} touched. Mountpoint is ${MNT}." 37 | # We only start logging to *files* after the mount device is known to have 38 | # at least one assert file and one snap file, our use case of interest 39 | # so for now store up the mesesage to be logged to file later 40 | MSG="\tMount signal file ${WATCHED} touched.\n\tMountpoint is ${MNT}.\n" 41 | 42 | # find any *.assert files 43 | ASSERTS=`ls "${MNT}" | grep assert$` 44 | 45 | # stop if no assert files found 46 | [[ -z $ASSERTS ]] && break 47 | 48 | #log assert files found 49 | for ANASSERT in ${ASSERTS}; do 50 | #strip whitespace 51 | ANASSERT=`echo ${ANASSERT} | sed 's/\s*$//g'` 52 | echo -e "\tAssert: ${ANASSERT} found." 53 | MSG="${MSG}Assert: ${ANASSERT} found." 54 | done 55 | 56 | # find any *.snap files 57 | SNAPS=`ls "${MNT}" | grep snap$` 58 | 59 | # stop if no snap files found 60 | [[ -z $SNAPS ]] && break 61 | 62 | # We are now in a valid use case, the mount point has *.assert and *.snap 63 | # so write log messages 64 | log "${MSG}" 65 | 66 | for ASNAP in ${SNAPS}; do 67 | #strip whitespace 68 | ASNAP=`echo ${ASNAP} | sed 's/\s*$//g'` 69 | logecho "\tSnap file ${ASNAP} found." 70 | done 71 | 72 | #Make a datetime 73 | DT=`date -Iseconds` 74 | 75 | for ASSERT in ${ASSERTS}; do 76 | ASSERT_RES_FILE="${SNAP_COMMON}/${ASSERT}_result.${DT}" 77 | # Ack the assert: 78 | ${SNAP}/usr/bin/curl -o "${ASSERT_RES_FILE}" -X POST -sS --unix-socket /run/snapd.socket http://localhost/v2/assertions --data-binary "@${MNT}/${ASSERT}" 79 | grep -Iq '"status":"OK"' ${ASSERT_RES_FILE} 80 | if [[ "$?" == "0" ]]; then 81 | logecho "Ack of ${ASSERT} succeeded" 82 | else 83 | logecho "ERROR: Ack of ${ASSERT} failed. This installation iteration is STOPPING." 84 | FAILED=0 85 | fi 86 | done 87 | 88 | # ack of assertion failed, so quit 89 | [[ ${FAILED} ]] && break 90 | 91 | INSTALL_SUCCEEDED=0 92 | for ASNAP in ${SNAPS}; do 93 | CHANNEL_SUCCEEDED=0 94 | SNAP_RES_FILE="${SNAP_COMMON}/${ASNAP}_result.${DT}" 95 | # install the snap 96 | ${SNAP}/usr/bin/curl -o "${SNAP_RES_FILE}" -sS --unix-socket /run/snapd.socket --form snap=@${MNT}/${ASNAP} http://localhost/v2/snaps 97 | grep -Iq '"status":"Accepted"' ${SNAP_RES_FILE} 98 | if [[ "$?" == "0" ]]; then 99 | logecho "Install of ${ASNAP} succeeded" 100 | # set snapd to explicitly follow stable for this snap 101 | SNAPNAME=`echo ${ASNAP} | cut -f1 -d_` 102 | # keep trying to set the channel to stable because it may take some time if a snap change is in process 103 | # give up after 14 loops, where each loop sleeps twice as long as the previous 104 | # where 14 loops takes 19.1 minutes 105 | IDX=0 106 | SLEEP=2 107 | CHANNEL_SUCCEEDED=1 108 | while [ $IDX -lt 14 ]; do 109 | sleep $SLEEP 110 | ${SNAP}/usr/bin/curl -X POST -o "${SNAP_RES_FILE}.channel" --header "Content-Type: application/json" --data '{"action":"switch","channel":"stable"}' -sS --unix-socket /run/snapd.socket http://localhost/v2/snaps/${SNAPNAME} 111 | grep -Iq '"status":"Accepted"' ${SNAP_RES_FILE}.channel 112 | if [[ "$?" == "0" ]]; then 113 | logecho "${SNAPNAME} is now following stable" 114 | CHANNEL_SUCCEEDED=0 115 | break 116 | fi 117 | let IDX=$IDX+1 118 | let SLEEP=${SLEEP}*2 119 | done 120 | [[ ! ${CHANNEL_SUCCEEDED} ]] && logecho "WARNING: Unable to configure ${SNAPNAME} channel" 121 | else 122 | INSTALL_SUCCEEDED=1 123 | logecho "Install of ${ASNAP} failed. This installation iteration is STOPPING." 124 | break 125 | fi 126 | done 127 | if [ $INSTALL_SUCCEEDED -eq 0 ]; then 128 | logecho "AUTO INSTALL RESULT: All snaps installed. Ending" 129 | else 130 | logecho "AUTO INSTALL RESULT: Not all snaps installed. Process incomplete." 131 | fi 132 | done 133 | done 134 | --------------------------------------------------------------------------------