├── .github └── workflows │ └── check.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── ipc.c ├── ipc.h ├── ldm.c ├── ldm.pod ├── ldm.service.in ├── ldmc.c ├── ldmc.pod └── umount.ldm /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Check 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Install dependencies 17 | run: sudo apt install -y libglib2.0-dev libudev-dev libmount-dev libblkid-dev 18 | - name: Build 19 | run: make all 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.swp 3 | ldm 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 The Lemon Man 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION=v0.7 2 | VERSION_GIT=$(shell test -d .git && git describe 2> /dev/null) 3 | 4 | ifneq "$(VERSION_GIT)" "" 5 | VERSION=$(VERSION_GIT) 6 | endif 7 | 8 | CC ?= gcc 9 | CFLAGS += -std=c99 \ 10 | -D_GNU_SOURCE \ 11 | -Wall -Wunused-parameter -O2 \ 12 | -DVERSION_STR="\"$(VERSION)\"" 13 | CFDEBUG = -g3 -pedantic -Wlong-long 14 | CFDEBUG += -Wsign-conversion -Wconversion -Wimplicit-function-declaration 15 | 16 | LIBS = libudev mount glib-2.0 17 | LDFLAGS := `pkg-config --libs $(LIBS)` $(LDFLAGS) 18 | 19 | BINDIR ?= /usr/bin 20 | SBINDIR ?= /sbin 21 | SYSTEMDDIR ?= /usr/lib/systemd 22 | 23 | all: ldm ldmc doc 24 | 25 | .c.o: 26 | $(CC) $(CFLAGS) `pkg-config --cflags $(LIBS)` -o $@ -c $< 27 | 28 | ldm: ipc.o ldm.o 29 | $(CC) -o ldm ipc.o ldm.o $(LDFLAGS) 30 | 31 | ldmc: ipc.o ldmc.o 32 | $(CC) -o ldmc ipc.o ldmc.o $(LDFLAGS) 33 | 34 | debug: ldm ldmc 35 | debug: CC += $(CFDEBUG) 36 | 37 | service: 38 | @sed "s|@@BINDIR@@|$(BINDIR)|" ldm.service.in > ldm.service 39 | 40 | doc: ldm.pod ldmc.pod 41 | @pod2man --section=1 --center="ldm Manual" --name "ldm" --release="$(VERSION)" ldm.pod > ldm.1 42 | @pod2man --section=1 --center="ldmc Manual" --name "ldmc" --release="$(VERSION)" ldmc.pod > ldmc.1 43 | 44 | readme: ldm.pod 45 | @pod2markdown -u ldm.pod README.md 46 | 47 | clean: 48 | $(RM) *.o *.1 ldm ldmc ldm.service 49 | 50 | mrproper: clean 51 | $(RM) ldm ldmc 52 | 53 | install-main: ldm doc 54 | install -D -m 755 ldm $(DESTDIR)$(BINDIR)/ldm 55 | install -D -m 755 ldmc $(DESTDIR)$(BINDIR)/ldmc 56 | install -D -m 755 umount.ldm $(DESTDIR)$(SBINDIR)/umount.ldm 57 | install -D -m 644 ldm.1 $(DESTDIR)/usr/share/man/man1/ldm.1 58 | install -D -m 644 ldmc.1 $(DESTDIR)/usr/share/man/man1/ldmc.1 59 | 60 | install-systemd: service 61 | install -D -m 644 ldm.service $(DESTDIR)$(SYSTEMDDIR)/system/ldm.service 62 | 63 | install: all install-main install-systemd 64 | 65 | uninstall: 66 | $(RM) $(DESTDIR)$(BINDIR)/ldm 67 | $(RM) $(DESTDIR)$(BINDIR)/ldmc 68 | $(RM) $(DESTDIR)$(SBINDIR)/umount.ldm 69 | $(RM) $(DESTDIR)/usr/share/man/man1/ldm.1 70 | $(RM) $(DESTDIR)/usr/share/man/man1/ldmc.1 71 | $(RM) $(DESTDIR)$(SYSTEMDDIR)/system/ldm.service 72 | 73 | .PHONY: all debug clean mrproper install install-main install-systemd uninstall service readme 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NAME 2 | 3 | ldm - Lightweight Device Mounter 4 | 5 | # SYNOPSIS 6 | 7 | _ldm_ \[-d\] \[-u _user_\] \[-p _path_\] \[-c _command_\] \[-m _mask_\] \[-h\] 8 | 9 | # DESCRIPTION 10 | 11 | ldm is a lightweight device mounter following the UNIX philosophy written in C and based on udev and libmount. 12 | The user can use **umount** to unmount the device or **ldmc** with the **-r** switch. 13 | The daemon can be controlled with the **ldmc** tool. 14 | 15 | # OPTIONS 16 | 17 | - **-d** 18 | 19 | Run ldm as a daemon. 20 | 21 | - **-u** _user_ 22 | 23 | Specify the user who owns the mountpoints. 24 | 25 | - **-p** _path_ 26 | 27 | Specify the base folder for the mount points. The default is /mnt. 28 | 29 | - **-m** _fmask_,_dmask_ 30 | 31 | Specify the fmask and dmask for the mounted devices in octal or symbolic format (eg. the octal mask 32 | 0777 is represented as rwxrwxrwx). 33 | 34 | If only the _fmask_ is specified then its used as umask and it's 35 | value is used as dmask too. 36 | 37 | - **-c** _command_ 38 | 39 | Specifies a command that is executed after a successful mount/unmount action. The following environment variables are defined : 40 | 41 | - **LDM\_MOUNTPOINT** 42 | 43 | The complete path to the mountpoint. 44 | 45 | - **LDM\_NODE** 46 | 47 | The path pointing to the device node in /dev 48 | 49 | - **LDM\_FS** 50 | 51 | The filesystem on the mounted device. 52 | 53 | - **LDM\_ACTION** 54 | 55 | The action ldm has just performed, it can either be _mount_, _pre\_unmount_ or _unmount_ 56 | 57 | - **-h** 58 | 59 | Print a brief help and exit. 60 | 61 | # BLACKLISTING 62 | 63 | ldm doesn't offer any blacklisting by itself but it honors the options found in the fstab so it will ignore any device with flag _noauto_. 64 | 65 | # INSTALL 66 | 67 | The included systemd service expects a config file at /etc/ldm.conf similar to this: 68 | 69 |
70 |
71 |     
72 |     MOUNT_OWNER=username
73 |     BASE_MOUNTPOINT=/mnt
74 |     FMASK_DMASK=fmask,dmask
75 |     EXTRA_ARGS=-c <path_to_executable>
76 |     
77 |     
78 |
79 | 80 | The options **FMASK\_DMASK** and **EXTRA\_ARGS** are optional. 81 | The default value for **FMASK\_DMASK** is _0133,0022_. 82 | **EXTRA\_ARGS** will be appended to the _ldm_ executable. 83 | 84 | # SEE ALSO 85 | 86 | ldmc(1), umount(8) 87 | 88 | # WWW 89 | 90 | [git repository](https://github.com/LemonBoy/ldm) 91 | 92 | # AUTHOR 93 | 94 | 2011-2019 (C) The Lemon Man 95 | -------------------------------------------------------------------------------- /ipc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define IPC_SOCKET "/run/ldm.socket" 11 | 12 | void 13 | ipc_deinit (int fd) 14 | { 15 | if (fd >= 0) 16 | close (fd); 17 | unlink (IPC_SOCKET); 18 | } 19 | 20 | int 21 | ipc_init (int as_master) 22 | { 23 | int sock; 24 | int flags; 25 | struct sockaddr_un so; 26 | 27 | sock = socket(AF_UNIX, SOCK_STREAM, 0); 28 | if (sock < 0) { 29 | perror("socket"); 30 | return -1; 31 | } 32 | 33 | memset(&so, 0, sizeof(struct sockaddr_un)); 34 | so.sun_family = AF_UNIX; 35 | strncpy(so.sun_path, IPC_SOCKET, sizeof(so.sun_path)); 36 | 37 | if (as_master) { 38 | // Make sure that there are no leftovers 39 | unlink (IPC_SOCKET); 40 | 41 | // The master waits for the slaves to connect 42 | if (bind(sock, (struct sockaddr *)&so, sizeof(struct sockaddr_un)) < 0) { 43 | perror("bind"); 44 | return -1; 45 | } 46 | 47 | // Make the sock non-blocking 48 | flags = fcntl(sock, F_GETFL, 0); 49 | if (flags < 0) { 50 | perror("fcntl"); 51 | return -1; 52 | } 53 | 54 | if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) { 55 | perror("fcntl"); 56 | return -1; 57 | } 58 | } 59 | else { 60 | // The slave connects to the master 61 | if (connect(sock, (struct sockaddr *)&so, sizeof(struct sockaddr_un)) < 0) { 62 | perror("connect"); 63 | return -1; 64 | } 65 | } 66 | 67 | return sock; 68 | } 69 | 70 | char 71 | ipc_read_one (int fd) 72 | { 73 | char resp; 74 | 75 | if (fd < 0) 76 | return 0; 77 | 78 | if (read(fd, &resp, 1) != 1) { 79 | perror("read"); 80 | return 0; 81 | } 82 | 83 | return resp; 84 | } 85 | 86 | int 87 | ipc_read_line (int fd, char *line, int max_line_length) 88 | { 89 | int p; 90 | 91 | if (fd < 0 || !line) 92 | return 0; 93 | 94 | for (p = 0; p < max_line_length - 1; p++) { 95 | if (read(fd, &line[p], 1) != 1) { 96 | perror("read"); 97 | break; 98 | } 99 | 100 | if (line[p] == '\n') 101 | break; 102 | } 103 | 104 | line[p] = '\0'; 105 | 106 | // Don't take into account the \n 107 | return p? p - 1: 0; 108 | } 109 | 110 | int 111 | ipc_sendf (int fd, const char *fmt, ...) 112 | { 113 | va_list args, args_; 114 | int fmt_length; 115 | char *buf; 116 | 117 | va_start(args, fmt); 118 | 119 | // Make a copy since the first vsnprintf call modifies it 120 | va_copy(args_, args); 121 | 122 | // Obtain the final string length first 123 | fmt_length = vsnprintf(NULL, 0, fmt, args_); 124 | if (fmt_length < 0) { 125 | perror("vsprintf"); 126 | va_end(args); 127 | return 0; 128 | } 129 | 130 | buf = malloc(fmt_length + 1); 131 | if (!buf) { 132 | perror("errno"); 133 | va_end(args); 134 | return 0; 135 | } 136 | 137 | vsnprintf(buf, fmt_length + 1, fmt, args); 138 | 139 | // Don't send the trailing NULL 140 | if (write(fd, buf, fmt_length) != fmt_length) { 141 | perror("write"); 142 | free(buf); 143 | va_end(args); 144 | return 0; 145 | } 146 | 147 | free(buf); 148 | va_end(args); 149 | 150 | return fmt_length; 151 | } 152 | -------------------------------------------------------------------------------- /ipc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void ipc_deinit (int fd); 4 | int ipc_init (int as_master); 5 | char ipc_read_one (int fd); 6 | int ipc_read_line (int fd, char *line, int max_line_length); 7 | int ipc_sendf (int fd, const char *fmt, ...); 8 | -------------------------------------------------------------------------------- /ldm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include "ipc.h" 24 | 25 | typedef enum { 26 | VOLUME, 27 | MAX, 28 | } VolumeType; 29 | 30 | typedef enum { 31 | NONE = 0x00, 32 | OWNER_FIX = 0x01, 33 | UTF8_FLAG = 0x02, 34 | MASK = 0x04, 35 | FLUSH = 0x08, 36 | RO = 0x10, 37 | } Quirk; 38 | 39 | typedef struct { 40 | unsigned long fmask; 41 | unsigned long dmask; 42 | } Mask; 43 | 44 | typedef struct { 45 | VolumeType type; 46 | char *node; 47 | struct udev_device *dev; 48 | char *mp; // The path to the mountpoint 49 | char *fs; // The name of the filesystem 50 | } Device; 51 | 52 | typedef struct { 53 | char *name; 54 | Quirk quirks; 55 | } FsQuirk; 56 | 57 | #define FSTAB_PATH "/etc/fstab" 58 | #define MTAB_PATH "/proc/self/mounts" 59 | #define LOCK_PATH "/run/ldm.pid" 60 | 61 | // Static global structs 62 | 63 | static struct libmnt_table *g_fstab; 64 | static struct libmnt_table *g_mtab; 65 | static int g_running; 66 | static gid_t g_gid; 67 | static uid_t g_uid; 68 | static char *g_mount_path; 69 | static char *g_callback_cmd; 70 | static Mask g_mask; 71 | static GHashTable *g_dev_table; 72 | 73 | // Return the first non-NULL element among {a,b,c} or NULL 74 | // The values are evaluated only once and only if needed 75 | #define first_nonnull(a,b,c) \ 76 | ({ __typeof__ (a) _a = (a); \ 77 | __typeof__ (b) _b = NULL; \ 78 | __typeof__ (c) _c = NULL; \ 79 | if (!_a) _b = (b); \ 80 | if (!_a && !_b) _c = (c); \ 81 | _a ? _a : (_b ? _b : _c); }) 82 | 83 | char * 84 | udev_get_prop (struct udev_device *dev, const char *key) 85 | { 86 | const char *value = udev_device_get_property_value(dev, key); 87 | return (char *)value; 88 | } 89 | 90 | int 91 | udev_prop_true (struct udev_device *dev, const char *key) 92 | { 93 | const char *value = udev_device_get_property_value(dev, key); 94 | return value && !strcmp(value, "1"); 95 | } 96 | 97 | int 98 | udev_attr_true (struct udev_device *dev, const char *key) 99 | { 100 | const char *value = udev_device_get_sysattr_value(dev, key); 101 | return value && !strcmp(value, "1"); 102 | } 103 | 104 | // Locking functions 105 | 106 | int 107 | lock_create (int pid) 108 | { 109 | FILE *fp; 110 | 111 | fp = fopen(LOCK_PATH, "w+"); 112 | if (!fp) 113 | return 0; 114 | fprintf(fp, "%d", pid); 115 | fclose(fp); 116 | 117 | return 1; 118 | } 119 | 120 | // Spawn helper 121 | 122 | int 123 | spawn_callback (char *action, Device *dev) 124 | { 125 | int ret; 126 | pid_t child_pid; 127 | char **env; 128 | unsigned env_count; 129 | 130 | // No callback registered, we're done 131 | if (!g_callback_cmd) 132 | return 0; 133 | 134 | child_pid = fork(); 135 | 136 | if (child_pid < 0) 137 | return 1; 138 | 139 | if (child_pid > 0) { 140 | // Wait for the process to return 141 | wait(&ret); 142 | 143 | // Return the exit code or EXIT_FAILURE if something went wrong 144 | return WIFEXITED(ret) ? WEXITSTATUS(ret) : EXIT_FAILURE; 145 | } 146 | 147 | env_count = g_strv_length(environ); 148 | env = malloc((env_count + 5) * sizeof(char *)); 149 | 150 | if (!env) 151 | return 0; 152 | 153 | // Copy the parent's environment 154 | for (int i = 0; i < env_count; i++) 155 | env[i] = environ[i]; 156 | 157 | // Inject the ldm-specific variables 158 | env[env_count] = g_strdup_printf("LDM_ACTION=%s", action); 159 | env[env_count+1] = g_strdup_printf("LDM_NODE=%s", dev->node); 160 | env[env_count+2] = g_strdup_printf("LDM_MOUNTPOINT=%s", dev->mp); 161 | env[env_count+3] = g_strdup_printf("LDM_FS=%s", dev->fs); 162 | env[env_count+4] = NULL; 163 | 164 | // Drop the root priviledges. Oh and the bass too. 165 | if (setgid(g_gid) < 0 || setuid(g_uid) < 0) { 166 | _Exit(EXIT_FAILURE); 167 | } 168 | 169 | close(STDIN_FILENO); 170 | close(STDOUT_FILENO); 171 | close(STDERR_FILENO); 172 | 173 | char * const cmdline[] = { 174 | "/bin/sh", 175 | "-c", g_callback_cmd, 176 | NULL 177 | }; 178 | execve(cmdline[0], cmdline, env); 179 | 180 | // Should never reach this 181 | syslog(LOG_ERR, "Could not execute \"%s\"", g_callback_cmd); 182 | // Die 183 | _Exit(EXIT_FAILURE); 184 | } 185 | 186 | // Convenience function for fstab handling 187 | 188 | enum { 189 | NODE, 190 | UUID, 191 | LABEL, 192 | }; 193 | 194 | struct libmnt_fs * 195 | table_search_by_str (struct libmnt_table *tbl, int type, char *str) 196 | { 197 | struct libmnt_fs *fs; 198 | 199 | if (!tbl || !str) 200 | return NULL; 201 | 202 | switch (type) { 203 | case NODE: 204 | fs = mnt_table_find_source(tbl, str, MNT_ITER_FORWARD); 205 | break; 206 | case UUID: 207 | fs = mnt_table_find_tag(tbl, "UUID", str, MNT_ITER_FORWARD); 208 | break; 209 | case LABEL: 210 | fs = mnt_table_find_tag(tbl, "LABEL", str, MNT_ITER_FORWARD); 211 | break; 212 | default: 213 | return NULL; 214 | } 215 | 216 | return fs; 217 | } 218 | 219 | struct libmnt_fs * 220 | table_search_by_dev (struct libmnt_table *tbl, Device *dev) 221 | { 222 | // Try to find a match against the device node name, the uuid and the 223 | // label in this order 224 | return first_nonnull( 225 | table_search_by_str(tbl, NODE, dev->node), 226 | table_search_by_str(tbl, UUID, udev_get_prop(dev->dev, "ID_FS_UUID")), 227 | table_search_by_str(tbl, LABEL, udev_get_prop(dev->dev, "ID_FS_LABEL")) 228 | ); 229 | } 230 | 231 | struct libmnt_fs * 232 | table_search_by_udev (struct libmnt_table *tbl, struct udev_device *udev) 233 | { 234 | struct libmnt_fs *fs; 235 | char *resolved; 236 | 237 | resolved = mnt_resolve_path(udev_device_get_devnode(udev), NULL); 238 | 239 | fs = first_nonnull( 240 | table_search_by_str(tbl, NODE, resolved), 241 | table_search_by_str(tbl, UUID, udev_get_prop(udev, "ID_FS_UUID")), 242 | table_search_by_str(tbl, LABEL, udev_get_prop(udev, "ID_FS_LABEL")) 243 | ); 244 | 245 | free(resolved); 246 | 247 | return fs; 248 | } 249 | 250 | int 251 | fstab_has_option (struct udev_device *udev, const char *option) 252 | { 253 | struct libmnt_fs *fs; 254 | 255 | if (!udev || !option) 256 | return 0; 257 | 258 | fs = table_search_by_udev(g_fstab, udev); 259 | if (!fs) 260 | return 0; 261 | 262 | return mnt_fs_match_options(fs, option); 263 | } 264 | 265 | unsigned int 266 | fs_get_quirks (char *fs) 267 | { 268 | static const FsQuirk fs_table [] = { 269 | { "msdos" , OWNER_FIX | UTF8_FLAG }, 270 | { "umsdos", OWNER_FIX | UTF8_FLAG }, 271 | { "vfat", OWNER_FIX | UTF8_FLAG | MASK | FLUSH }, 272 | { "exfat", OWNER_FIX }, 273 | { "ntfs", OWNER_FIX | UTF8_FLAG | MASK }, 274 | { "iso9660",OWNER_FIX | UTF8_FLAG | RO }, 275 | { "udf", OWNER_FIX | RO }, 276 | }; 277 | 278 | for (int i = 0; i < sizeof(fs_table)/sizeof(FsQuirk); i++) { 279 | if (!strcmp(fs_table[i].name, fs)) 280 | return fs_table[i].quirks; 281 | } 282 | 283 | return NONE; 284 | } 285 | 286 | int 287 | mnt_context_rc_value (struct libmnt_context *ctx, int rc) 288 | { 289 | // Return the /sbin/umount. helper return code. 290 | if (mnt_context_helper_executed(ctx)) { 291 | int helper_rc = mnt_context_get_helper_status(ctx); 292 | syslog(LOG_INFO, "Mount helper returned code %d", helper_rc); 293 | return (helper_rc != 0); 294 | } 295 | 296 | // The library and the syscall succeeded just fine. 297 | if (!rc && mnt_context_get_status(ctx) == 1) 298 | return 0; 299 | 300 | if (!mnt_context_syscall_called(ctx)) 301 | syslog(LOG_ERR, "Error in libmount (rc = %d)", rc); 302 | else { 303 | int syscall_errno = mnt_context_get_syscall_errno(ctx); 304 | syslog(LOG_ERR, "Error in syscall (%s)", syscall_errno? 305 | strerror(syscall_errno): "Generic error"); 306 | } 307 | 308 | return 1; 309 | } 310 | 311 | int 312 | device_find_predicate (char *key, Device *value, char *what) 313 | { 314 | (void)key; 315 | 316 | // Try to match the resolved node or the mountpoint name 317 | if (!strcmp(value->node, what)) 318 | return 1; 319 | if (value->type == VOLUME && !strcmp(value->mp, what)) 320 | return 1; 321 | 322 | return 0; 323 | } 324 | 325 | // Path is either the /dev/ node or the mountpoint 326 | Device * 327 | device_search (const char *path) 328 | { 329 | Device *dev; 330 | 331 | if (!path || !path[0]) 332 | return NULL; 333 | 334 | // This is the fast path, let's just hope it's a /dev/ node 335 | dev = g_hash_table_lookup(g_dev_table, path); 336 | 337 | if (!dev) 338 | dev = g_hash_table_find(g_dev_table, (GHRFunc)device_find_predicate, (gpointer)path); 339 | 340 | return dev; 341 | } 342 | 343 | int 344 | device_has_media (struct udev_device *udev) 345 | { 346 | const char *dev_node; 347 | int fd; 348 | 349 | if (!udev) 350 | return 0; 351 | 352 | // Fast path, the device always have something in it 353 | if (!udev_attr_true(udev, "removable")) 354 | return 1; 355 | 356 | if (udev_get_prop(udev, "ID_CDROM")) 357 | return udev_prop_true(udev, "ID_CDROM_MEDIA"); 358 | 359 | dev_node = udev_device_get_devnode(udev); 360 | fd = open(dev_node, O_RDONLY); 361 | if (fd < 0) 362 | return 0; 363 | close(fd); 364 | 365 | return 1; 366 | } 367 | 368 | void 369 | device_free (Device *dev) 370 | { 371 | if (!dev) 372 | return; 373 | 374 | free(dev->node); 375 | udev_device_unref(dev->dev); 376 | 377 | switch (dev->type) { 378 | case VOLUME: 379 | free(dev->mp); 380 | free(dev->fs); 381 | break; 382 | 383 | default: 384 | break; 385 | } 386 | 387 | free(dev); 388 | } 389 | 390 | Device * 391 | device_new (struct udev_device *udev) 392 | { 393 | const char *dev_node, *dev_fs, *dev_fs_usage; 394 | Device *dev; 395 | 396 | if (!udev) 397 | return NULL; 398 | 399 | if (!device_has_media(udev)) 400 | return NULL; 401 | 402 | dev_node = udev_device_get_devnode(udev); 403 | dev_fs = udev_get_prop(udev, "ID_FS_TYPE"); 404 | dev_fs_usage = udev_get_prop(udev, "ID_FS_USAGE"); 405 | 406 | if (!dev_fs || !dev_fs_usage) 407 | return NULL; 408 | 409 | if (strcmp(dev_fs_usage, "filesystem")) 410 | return NULL; 411 | 412 | dev = calloc(1, sizeof(Device)); 413 | 414 | dev->type = VOLUME; 415 | dev->dev = udev; 416 | dev->node = mnt_resolve_path(dev_node, NULL); 417 | dev->fs = strdup(dev_fs); 418 | 419 | udev_device_ref(udev); 420 | 421 | return dev; 422 | } 423 | 424 | char * 425 | device_get_mp (Device *dev, const char *base) 426 | { 427 | char *unique; 428 | char mp[4096]; 429 | GDir *dir; 430 | 431 | if (!dev || !base) 432 | return NULL; 433 | 434 | // Use the first non-null field 435 | unique = first_nonnull(udev_get_prop(dev->dev, "ID_FS_LABEL"), 436 | udev_get_prop(dev->dev, "ID_FS_UUID"), 437 | udev_get_prop(dev->dev, "ID_SERIAL")); 438 | 439 | if (!unique) 440 | return NULL; 441 | 442 | if (snprintf(mp, sizeof(mp), "%s/%s", base, unique) < 0) 443 | return NULL; 444 | 445 | // If the mountpoint we've come up with already exists try to find a good one by appending '_' 446 | while (g_file_test(mp, G_FILE_TEST_EXISTS)) { 447 | // We tried hard and failed 448 | if (strlen(mp) == sizeof(mp) - 2) 449 | return NULL; 450 | 451 | // Reuse the directory only if it's empty 452 | dir = g_dir_open(mp, 0, NULL); 453 | if (dir) { 454 | // The directory is empty! 455 | // Note for the reader : 'g_dir_read_name' omits '.' and '..' 456 | if (!g_dir_read_name(dir)) { 457 | g_dir_close(dir); 458 | break; 459 | } 460 | 461 | g_dir_close(dir); 462 | } 463 | 464 | // Directory not empty, append a '_' 465 | strcat(mp, "_"); 466 | } 467 | 468 | return strdup(mp); 469 | } 470 | 471 | int 472 | device_mount (Device *dev) 473 | { 474 | char *mp; 475 | unsigned int fs_quirks; 476 | char opt_fmt[256] = {0}; 477 | struct libmnt_context *ctx; 478 | struct libmnt_fs *fstab; 479 | int rc; 480 | 481 | if (!dev) 482 | return 0; 483 | 484 | fstab = table_search_by_dev(g_fstab, dev); 485 | 486 | if (fstab && mnt_fs_get_target(fstab)) 487 | mp = strdup(mnt_fs_get_target(fstab)); 488 | else 489 | mp = device_get_mp(dev, g_mount_path); 490 | 491 | if (!mp) 492 | return 0; 493 | 494 | if (!g_file_test(mp, G_FILE_TEST_EXISTS)) { 495 | // Create the mountpoint folder only if it's not already present 496 | if (mkdir(mp, 775) < 0) { 497 | syslog(LOG_ERR, "Could not mkdir() the folder at %s (%s)", mp, strerror(errno)); 498 | return 0; 499 | } 500 | } 501 | 502 | // Set 'mp' as the mountpoint for the device 503 | dev->mp = mp; 504 | 505 | fs_quirks = fs_get_quirks(dev->fs); 506 | 507 | // Apply the needed quirks 508 | if (fs_quirks != NONE) { 509 | char *p = opt_fmt; 510 | // Microsoft filesystems and filesystems used on optical 511 | // discs require the gid and uid to be passed as mount 512 | // arguments to allow the user to read and write, while 513 | // posix filesystems just need a chown after being mounted 514 | if (fs_quirks & OWNER_FIX) 515 | p += sprintf(p, "uid=%i,gid=%i,", g_uid, g_gid); 516 | if (fs_quirks & UTF8_FLAG) 517 | p += sprintf(p, "utf8,"); 518 | if (fs_quirks & FLUSH) 519 | p += sprintf(p, "flush,"); 520 | if (fs_quirks & MASK) 521 | p += sprintf(p, "dmask=%04lo,fmask=%04lo,", g_mask.dmask, g_mask.fmask); 522 | 523 | *p = '\0'; 524 | } 525 | 526 | // Take a deep breath and don't panic! 527 | // The buffer is big enough to accomodate the content. 528 | strcat(opt_fmt, "uhelper=ldm"); 529 | 530 | ctx = mnt_new_context(); 531 | 532 | mnt_context_set_fstype(ctx, dev->fs); 533 | mnt_context_set_source(ctx, dev->node); 534 | mnt_context_set_target(ctx, dev->mp); 535 | mnt_context_set_options(ctx, opt_fmt); 536 | 537 | if (fs_quirks & RO) 538 | mnt_context_set_mflags(ctx, MS_RDONLY); 539 | 540 | rc = mnt_context_mount(ctx); 541 | rc = mnt_context_rc_value(ctx, rc); 542 | 543 | if (rc) { 544 | syslog(LOG_ERR, "Error while mounting %s", dev->node); 545 | 546 | mnt_free_context(ctx); 547 | rmdir(dev->mp); 548 | return 0; 549 | } 550 | 551 | mnt_free_context(ctx); 552 | 553 | if (!(fs_quirks & OWNER_FIX)) { 554 | if (chown(dev->mp, g_uid, g_gid) < 0) { 555 | syslog(LOG_ERR, "Cannot chown %s", dev->mp); 556 | return 0; 557 | } 558 | } 559 | 560 | (void)spawn_callback("mount", dev); 561 | 562 | return 1; 563 | } 564 | 565 | int 566 | device_unmount (Device *dev) 567 | { 568 | struct libmnt_context *ctx; 569 | int rc; 570 | 571 | if (!dev) 572 | return 0; 573 | 574 | // Unmount the device if it is actually mounted 575 | if (!table_search_by_dev(g_mtab, dev)) 576 | return 0; 577 | 578 | (void)spawn_callback("pre_unmount", dev); 579 | 580 | ctx = mnt_new_context(); 581 | mnt_context_set_target(ctx, dev->node); 582 | 583 | rc = mnt_context_umount(ctx); 584 | rc = mnt_context_rc_value(ctx, rc); 585 | 586 | if (rc) { 587 | syslog(LOG_ERR, "Error while unmounting %s", dev->node); 588 | 589 | mnt_free_context(ctx); 590 | return 0; 591 | } 592 | mnt_free_context(ctx); 593 | 594 | rmdir(dev->mp); 595 | 596 | (void)spawn_callback("unmount", dev); 597 | 598 | return 1; 599 | } 600 | 601 | // udev action callbacks 602 | 603 | void 604 | on_udev_add (struct udev_device *udev) 605 | { 606 | Device *dev; 607 | const char *dev_node; 608 | 609 | dev_node = udev_device_get_devnode(udev); 610 | 611 | // libmount < 2.21 doesn't support '+noauto', using 'noauto' instead 612 | const char *noauto_opt = mnt_parse_version_string(LIBMOUNT_VERSION) < 2210 ? "noauto" : "+noauto"; 613 | if (fstab_has_option(udev, noauto_opt)) 614 | return; 615 | 616 | dev = device_new(udev); 617 | if (!dev) { 618 | return; 619 | } 620 | 621 | if (!device_mount(dev)) { 622 | fprintf(stderr, "device_mount()\n"); 623 | return; 624 | } 625 | 626 | g_hash_table_insert(g_dev_table, (char *)dev_node, dev); 627 | } 628 | 629 | void 630 | on_udev_remove (struct udev_device *udev) 631 | { 632 | Device *dev; 633 | const char *dev_node; 634 | 635 | dev_node = udev_device_get_devnode(udev); 636 | 637 | dev = g_hash_table_lookup(g_dev_table, dev_node); 638 | if (!dev) 639 | return; 640 | 641 | if (!device_unmount(dev)) { 642 | fprintf(stderr, "device_unmount()"); 643 | return; 644 | } 645 | 646 | g_hash_table_remove(g_dev_table, dev_node); 647 | } 648 | 649 | void 650 | on_udev_change (struct udev_device *udev) 651 | { 652 | const char *type; 653 | 654 | on_udev_remove(udev); 655 | 656 | type = udev_get_prop(udev, "ID_TYPE"); 657 | 658 | if (!type) 659 | return; 660 | 661 | // Exit if there's no media 662 | if (!device_has_media(udev)) 663 | return; 664 | 665 | on_udev_add(udev); 666 | } 667 | 668 | void 669 | on_mtab_change (void) 670 | { 671 | struct libmnt_table *new_tab; 672 | struct libmnt_tabdiff *diff; 673 | struct libmnt_fs *old, *new; 674 | struct libmnt_iter *it; 675 | Device *dev; 676 | int change_type; 677 | 678 | new_tab = mnt_new_table_from_file(MTAB_PATH); 679 | if (!new_tab) { 680 | fprintf(stderr, "Could not parse %s\n", MTAB_PATH); 681 | return; 682 | } 683 | 684 | diff = mnt_new_tabdiff(); 685 | if (!diff) { 686 | fprintf(stderr, "Could not diff the mtab\n"); 687 | mnt_free_table(new_tab); 688 | return; 689 | } 690 | 691 | if (mnt_diff_tables(diff, g_mtab, new_tab) < 0) { 692 | fprintf(stderr, "Could not diff the mtab\n"); 693 | mnt_free_table(new_tab); 694 | mnt_free_tabdiff(diff); 695 | return; 696 | } 697 | 698 | it = mnt_new_iter(MNT_ITER_BACKWARD); 699 | 700 | while (!mnt_tabdiff_next_change(diff, it, &new, &old, &change_type)) { 701 | switch (change_type) { 702 | case MNT_TABDIFF_UMOUNT: 703 | dev = device_search(mnt_fs_get_source(new)); 704 | 705 | if (dev) { 706 | const char *ht_key = udev_device_get_devnode(dev->dev); 707 | 708 | g_hash_table_remove(g_dev_table, ht_key); 709 | } 710 | 711 | break; 712 | 713 | case MNT_TABDIFF_REMOUNT: 714 | case MNT_TABDIFF_MOVE: 715 | dev = device_search(mnt_fs_get_source(old)); 716 | 717 | // Disown the device if it has been remounted 718 | if (dev) { 719 | const char *ht_key = udev_device_get_devnode(dev->dev); 720 | 721 | g_hash_table_remove(g_dev_table, ht_key); 722 | } 723 | 724 | break; 725 | } 726 | } 727 | 728 | mnt_free_iter(it); 729 | mnt_free_tabdiff(diff); 730 | 731 | // We're done diffing, replace the old table 732 | mnt_free_table(g_mtab); 733 | g_mtab = new_tab; 734 | } 735 | 736 | void 737 | mount_plugged_devices (struct udev *udev) 738 | { 739 | struct udev_enumerate *udev_enum; 740 | struct udev_list_entry *devices; 741 | struct udev_list_entry *entry; 742 | struct udev_device *dev; 743 | const char *path; 744 | 745 | udev_enum = udev_enumerate_new(udev); 746 | udev_enumerate_add_match_subsystem(udev_enum, "block"); 747 | udev_enumerate_scan_devices(udev_enum); 748 | devices = udev_enumerate_get_list_entry(udev_enum); 749 | 750 | udev_list_entry_foreach(entry, devices) { 751 | path = udev_list_entry_get_name(entry); 752 | dev = udev_device_new_from_syspath(udev, path); 753 | 754 | if (!table_search_by_udev(g_mtab, dev)) 755 | on_udev_add(dev); 756 | 757 | udev_device_unref(dev); 758 | } 759 | udev_enumerate_unref(udev_enum); 760 | } 761 | 762 | void 763 | sig_handler (int signal) 764 | { 765 | if (signal == SIGINT || signal == SIGTERM || signal == SIGHUP) 766 | g_running = 0; 767 | } 768 | 769 | int 770 | daemonize (void) 771 | { 772 | pid_t child_pid; 773 | 774 | child_pid = fork(); 775 | 776 | if (child_pid < 0) 777 | return 0; 778 | 779 | if (child_pid > 0) 780 | exit(EXIT_SUCCESS); 781 | 782 | if (chdir("/") < 0) { 783 | perror("chdir"); 784 | return 0; 785 | } 786 | 787 | umask(022); 788 | 789 | if (setsid() < 0) { 790 | perror("setsid"); 791 | return 0; 792 | } 793 | 794 | // Close std* descriptors 795 | close(0); 796 | close(1); 797 | close(2); 798 | 799 | return 1; 800 | } 801 | 802 | int 803 | ipc_serve (int client) 804 | { 805 | char msg_buffer[4096]; 806 | ssize_t r; 807 | 808 | if (client < 0) 809 | return 0; 810 | 811 | r = read(client, msg_buffer, sizeof(msg_buffer)); 812 | if (r < 0) { 813 | perror("read"); 814 | return 0; 815 | } 816 | msg_buffer[r] = '\0'; 817 | 818 | if (r < 1) { 819 | syslog(LOG_WARNING, "Malformed ipc command of length %zu", r); 820 | return 0; 821 | } 822 | 823 | switch (msg_buffer[0]) { 824 | case 'R': { // Remove a mounted device 825 | // Resolve the user-provided path 826 | char *res = realpath(msg_buffer + 1, NULL); 827 | 828 | if (!res) { 829 | perror("realpath"); 830 | ipc_sendf(client, "-"); 831 | return 0; 832 | } 833 | 834 | Device *dev = device_search(res); 835 | free(res); 836 | 837 | int ok = 0; 838 | // We don't have to check whether the device is mounted here since device_unmount takes care 839 | // of stale device entries 840 | if (dev) { 841 | const char *ht_key = udev_device_get_devnode(dev->dev); 842 | 843 | if (device_unmount(dev)) { 844 | g_hash_table_remove(g_dev_table, ht_key); 845 | ok = 1; 846 | } 847 | } 848 | 849 | // Send the response back 850 | ipc_sendf(client, "%c", ok? '+': '-'); 851 | 852 | return 1; 853 | } 854 | 855 | case 'L': { // List the mounted devices 856 | GHashTableIter it; 857 | char *node; 858 | Device *dev; 859 | 860 | g_hash_table_iter_init(&it, g_dev_table); 861 | 862 | while (g_hash_table_iter_next(&it, (gpointer)&node, (gpointer)&dev)) { 863 | // Print the volume information like this 864 | // v 865 | if (dev->type == VOLUME) { 866 | ipc_sendf(client, "v \"%s\" \"%s\" \"%s\"\n", dev->node, dev->fs, dev->mp); 867 | } 868 | } 869 | 870 | // Send an empty line to mark the end of the list 871 | ipc_sendf(client, "\n"); 872 | 873 | return 1; 874 | } 875 | } 876 | 877 | syslog(LOG_WARNING, "Unrecognized ipc command %c", msg_buffer[0]); 878 | 879 | return 0; 880 | } 881 | 882 | void device_clear_list () { 883 | GHashTableIter it; 884 | Device *dev; 885 | 886 | g_hash_table_iter_init(&it, g_dev_table); 887 | while (g_hash_table_iter_next(&it, NULL, (gpointer)&dev)) { 888 | device_unmount(dev); 889 | } 890 | g_hash_table_destroy(g_dev_table); 891 | } 892 | 893 | int 894 | parse_mask (char *args, unsigned long *mask) 895 | { 896 | unsigned long tmp; 897 | 898 | tmp = 0; 899 | 900 | if (args[0] != '0') { 901 | // format : rwxrwxrwx 902 | if (strlen(args) != 9) 903 | return 0; 904 | 905 | for (int i = 0; i < 9; i++) { 906 | if (!strchr("rwx-", args[i])) { 907 | fprintf(stderr, "Stray '%c' character in the mask", args[i]); 908 | return 0; 909 | } 910 | tmp <<= 1; 911 | tmp |= (args[i] != '-')? 1: 0; 912 | } 913 | } 914 | else { 915 | // format : 0000 916 | if (strlen(args) != 4) 917 | return 0; 918 | 919 | errno = 0; 920 | tmp = strtoul(args, NULL, 8); 921 | if (errno) { 922 | perror("strtoul"); 923 | return 0; 924 | } 925 | } 926 | 927 | *mask = (unsigned)tmp; 928 | 929 | return 1; 930 | } 931 | 932 | int 933 | map_user_to_id (char *username) 934 | { 935 | struct passwd *pw; 936 | 937 | errno = 0; 938 | pw = getpwnam(username); 939 | 940 | // The 'Return Value' section states that getpwnam() returns NULL "if the 941 | // matching entry is not found or an error occurs. If an error occurs, errno 942 | // is set appropriately" 943 | if (!pw) { 944 | if (errno) 945 | perror("getpwnam"); 946 | else 947 | fprintf(stderr, "Could not find any information about the user \"%s\"\n", username); 948 | 949 | return 0; 950 | } 951 | 952 | g_gid = pw->pw_gid; 953 | g_uid = pw->pw_uid; 954 | 955 | return 1; 956 | } 957 | 958 | int 959 | main (int argc, char *argv[]) 960 | { 961 | struct udev *udev; 962 | struct udev_monitor *monitor; 963 | struct udev_device *device; 964 | const char *action; 965 | struct pollfd pollfd[3]; // udev / mtab / fifo 966 | char *resolved; 967 | int opt, got_u, daemon; 968 | int ipc_fd, mtab_fd; 969 | 970 | ipc_fd = mtab_fd = -1; 971 | daemon = 0; 972 | got_u = 0; 973 | g_callback_cmd = NULL; 974 | 975 | g_mask.fmask = 0133; 976 | g_mask.dmask = 0022; 977 | 978 | while ((opt = getopt(argc, argv, "hdu:p:c:m:")) != -1) { 979 | switch (opt) { 980 | case 'd': 981 | daemon = 1; 982 | break; 983 | case 'u': 984 | if (!map_user_to_id(optarg)) 985 | return EXIT_FAILURE; 986 | got_u = 1; 987 | break; 988 | case 'm': 989 | { 990 | char *sep = strchr(optarg, ','); 991 | 992 | if (!sep) { 993 | if (!parse_mask(optarg, &g_mask.fmask)) { 994 | fprintf(stderr, "Invalid mask specified!\n"); 995 | return EXIT_FAILURE; 996 | } 997 | // The user specified a single mask, use that as umask 998 | g_mask.dmask = g_mask.fmask; 999 | } 1000 | else { 1001 | *sep++ = '\0'; 1002 | // The user specified two distinct masks 1003 | if (!parse_mask(optarg, &g_mask.fmask) || !parse_mask(sep, &g_mask.dmask)) { 1004 | fprintf(stderr, "Invalid mask specified!\n"); 1005 | return EXIT_FAILURE; 1006 | } 1007 | } 1008 | } 1009 | break; 1010 | case 'p': 1011 | case 'c': 1012 | if (optarg[0] == '\0') { 1013 | fprintf(stderr, "Cannot pass empty argument to -%c\n", opt); 1014 | return EXIT_FAILURE; 1015 | } 1016 | if (opt == 'p') { 1017 | g_mount_path = strdup(optarg); 1018 | } 1019 | else { 1020 | g_callback_cmd = strdup(optarg); 1021 | } 1022 | break; 1023 | case 'h': 1024 | printf("ldm "VERSION_STR"\n"); 1025 | printf("2011-2019 (C) The Lemon Man\n"); 1026 | printf("%s [-d | -r | -u | -p | -c | -m | -h]\n", argv[0]); 1027 | printf("\t-d Run ldm as a daemon\n"); 1028 | printf("\t-u Specify the user\n"); 1029 | printf("\t-m Specify the umask or the fmask/dmask\n"); 1030 | printf("\t-p Specify where to mount the devices\n"); 1031 | printf("\t-c Specify the path to the script executed after mount/unmount events\n"); 1032 | printf("\t-h Show this help\n"); 1033 | // Falltrough 1034 | default: 1035 | return EXIT_SUCCESS; 1036 | } 1037 | } 1038 | 1039 | if (getuid() != 0) { 1040 | fprintf(stderr, "You have to run this program as root!\n"); 1041 | return EXIT_FAILURE; 1042 | } 1043 | 1044 | if (g_file_test(LOCK_PATH, G_FILE_TEST_EXISTS)) { 1045 | fprintf(stderr, "ldm is already running!\n"); 1046 | return EXIT_SUCCESS; 1047 | } 1048 | 1049 | if (!got_u) { 1050 | fprintf(stderr, "You must supply the user with the -u switch!\n"); 1051 | return EXIT_FAILURE; 1052 | } 1053 | 1054 | if (g_callback_cmd && !g_file_test(g_callback_cmd, G_FILE_TEST_IS_EXECUTABLE)) { 1055 | fprintf(stderr, "The callback script isn't executable!\n"); 1056 | 1057 | free(g_callback_cmd); 1058 | g_callback_cmd = NULL; 1059 | } 1060 | 1061 | if (!g_mount_path) 1062 | g_mount_path = strdup("/mnt"); 1063 | 1064 | // Resolve the mount point path before using it 1065 | resolved = realpath(g_mount_path, NULL); 1066 | if (!resolved) { 1067 | // Print a nice warning if the path doesn't exist 1068 | if (errno == ENOENT) 1069 | fprintf(stderr, "The path \"%s\" doesn't name an existing folder!\n", g_mount_path); 1070 | else 1071 | perror("realpath()"); 1072 | return EXIT_FAILURE; 1073 | } 1074 | free(g_mount_path); 1075 | g_mount_path = resolved; 1076 | 1077 | // Check anyways 1078 | if (!g_file_test(g_mount_path, G_FILE_TEST_IS_DIR)) { 1079 | fprintf(stderr, "The path \"%s\" doesn't name a folder or doesn't exist!\n", g_mount_path); 1080 | 1081 | free(g_callback_cmd); 1082 | free(g_mount_path); 1083 | 1084 | return EXIT_FAILURE; 1085 | } 1086 | 1087 | // Create the ipc socket 1088 | umask(0); 1089 | 1090 | if (daemon && !daemonize()) { 1091 | fprintf(stderr, "Could not spawn the daemon!\n"); 1092 | return EXIT_FAILURE; 1093 | } 1094 | 1095 | lock_create(getpid()); 1096 | 1097 | openlog("ldm", LOG_CONS, LOG_DAEMON); 1098 | 1099 | signal(SIGTERM, sig_handler); 1100 | signal(SIGINT , sig_handler); 1101 | signal(SIGHUP , sig_handler); 1102 | 1103 | syslog(LOG_INFO, "ldm "VERSION_STR); 1104 | 1105 | // Create the udev struct/monitor 1106 | udev = udev_new(); 1107 | monitor = udev_monitor_new_from_netlink(udev, "udev"); 1108 | 1109 | if (!monitor) { 1110 | syslog(LOG_ERR, "Cannot create a new monitor"); 1111 | goto cleanup; 1112 | } 1113 | if (udev_monitor_filter_add_match_subsystem_devtype(monitor, "block", NULL)) { 1114 | syslog(LOG_ERR, "Cannot set the filter"); 1115 | goto cleanup; 1116 | } 1117 | 1118 | // Create the hashtable holding the mounted devices 1119 | g_dev_table = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)device_free); 1120 | 1121 | // Load the tables 1122 | g_fstab = mnt_new_table_from_file(FSTAB_PATH); 1123 | g_mtab = mnt_new_table_from_file(MTAB_PATH); 1124 | 1125 | if (!g_fstab || !g_mtab) { 1126 | fprintf(stderr, "Could not parse the fstab/mtab\n"); 1127 | goto cleanup; 1128 | } 1129 | 1130 | mount_plugged_devices(udev); 1131 | 1132 | mnt_free_table(g_mtab); 1133 | g_mtab = mnt_new_table_from_file(MTAB_PATH); 1134 | 1135 | // Setup the fd to poll 1136 | mtab_fd = open(MTAB_PATH, O_RDONLY); 1137 | if (mtab_fd < 0) { 1138 | perror("open"); 1139 | goto cleanup; 1140 | } 1141 | 1142 | ipc_fd = ipc_init(1); 1143 | if (ipc_fd < 0) 1144 | goto cleanup; 1145 | 1146 | if (listen(ipc_fd, 1) < 0) { 1147 | perror("listen"); 1148 | goto cleanup; 1149 | } 1150 | 1151 | // Register all the events 1152 | pollfd[0].fd = udev_monitor_get_fd(monitor); 1153 | pollfd[0].events = POLLIN; 1154 | 1155 | pollfd[1].fd = mtab_fd; 1156 | pollfd[1].events = 0; 1157 | 1158 | pollfd[2].fd = ipc_fd; 1159 | pollfd[2].events = POLLIN; 1160 | 1161 | // Enable receiving now, we're ready to process the incoming events 1162 | if (udev_monitor_enable_receiving(monitor)) { 1163 | syslog(LOG_ERR, "Cannot enable receiving"); 1164 | goto cleanup; 1165 | } 1166 | 1167 | syslog(LOG_INFO, "Entering the main loop"); 1168 | 1169 | g_running = 1; 1170 | 1171 | while (g_running) { 1172 | if (poll(pollfd, 3, -1) < 1) 1173 | continue; 1174 | 1175 | // Incoming message on udev socket 1176 | if (pollfd[0].revents & POLLIN) { 1177 | device = udev_monitor_receive_device(monitor); 1178 | 1179 | if (!device) 1180 | continue; 1181 | 1182 | action = udev_device_get_action(device); 1183 | 1184 | if (!strcmp(action, "add")) { 1185 | on_udev_add(device); 1186 | } 1187 | else if (!strcmp(action, "remove")) { 1188 | on_udev_remove(device); 1189 | } 1190 | else if (!strcmp(action, "change")) { 1191 | on_udev_change(device); 1192 | } 1193 | 1194 | udev_device_unref(device); 1195 | } 1196 | // mtab change 1197 | if (pollfd[1].revents & POLLERR) { 1198 | on_mtab_change(); 1199 | } 1200 | // client connection to the ipc socket 1201 | if (pollfd[2].revents & POLLIN) { 1202 | int client; 1203 | 1204 | client = accept(ipc_fd, NULL, NULL); 1205 | if (client < 0) { 1206 | perror("accept"); 1207 | continue; 1208 | } 1209 | 1210 | if (!ipc_serve(client)) 1211 | syslog(LOG_ERR, "Could not serve a client due to an error"); 1212 | 1213 | close(client); 1214 | } 1215 | } 1216 | 1217 | cleanup: 1218 | 1219 | device_clear_list (); 1220 | 1221 | free(g_callback_cmd); 1222 | free(g_mount_path); 1223 | 1224 | // Do the cleanup 1225 | ipc_deinit (ipc_fd); 1226 | 1227 | close(mtab_fd); 1228 | 1229 | unlink(LOCK_PATH); 1230 | 1231 | udev_monitor_unref(monitor); 1232 | udev_unref(udev); 1233 | 1234 | mnt_free_table(g_fstab); 1235 | mnt_free_table(g_mtab); 1236 | 1237 | syslog(LOG_INFO, "Terminating..."); 1238 | 1239 | return EXIT_SUCCESS; 1240 | } 1241 | -------------------------------------------------------------------------------- /ldm.pod: -------------------------------------------------------------------------------- 1 | =head1 NAME 2 | 3 | ldm - Lightweight Device Mounter 4 | 5 | =head1 SYNOPSIS 6 | 7 | I [-d] [-u I] [-p I] [-c I] [-m I] [-h] 8 | 9 | =head1 DESCRIPTION 10 | 11 | ldm is a lightweight device mounter following the UNIX philosophy written in C and based on udev and libmount. 12 | The user can use B to unmount the device or B with the B<-r> switch. 13 | The daemon can be controlled with the B tool. 14 | 15 | =head1 OPTIONS 16 | 17 | =over 18 | 19 | =item B<-d> 20 | 21 | Run ldm as a daemon. 22 | 23 | =item B<-u> I 24 | 25 | Specify the user who owns the mountpoints. 26 | 27 | =item B<-p> I 28 | 29 | Specify the base folder for the mount points. The default is /mnt. 30 | 31 | =item B<-m> I,I 32 | 33 | Specify the fmask and dmask for the mounted devices in octal or symbolic format (eg. the octal mask 34 | 0777 is represented as rwxrwxrwx). 35 | 36 | If only the I is specified then its used as umask and it's 37 | value is used as dmask too. 38 | 39 | =item B<-c> I 40 | 41 | Specifies a command that is executed after a successful mount/unmount action. The following environment variables are defined : 42 | 43 | =over 44 | 45 | =item B 46 | 47 | The complete path to the mountpoint. 48 | 49 | =item B 50 | 51 | The path pointing to the device node in /dev 52 | 53 | =item B 54 | 55 | The filesystem on the mounted device. 56 | 57 | =item B 58 | 59 | The action ldm has just performed, it can either be I, I or I 60 | 61 | =back 62 | 63 | =item B<-h> 64 | 65 | Print a brief help and exit. 66 | 67 | =back 68 | 69 | =head1 BLACKLISTING 70 | 71 | ldm doesn't offer any blacklisting by itself but it honors the options found in the fstab so it will ignore any device with flag I. 72 | 73 | =head1 INSTALL 74 | 75 | The included systemd service expects a config file at /etc/ldm.conf similar to this: 76 | 77 | =begin man 78 | 79 | .sp 80 | .RS 4 81 | .nf 82 | .BB lightgray 83 | MOUNT_OWNER=\fIusername\fR 84 | BASE_MOUNTPOINT=\fI/mnt\fR 85 | FMASK_DMASK=\fIfmask,dmask\fR 86 | EXTRA_ARGS=\fl-c \fR 87 | .EB lightgray 88 | .fi 89 | .RE 90 | 91 | =end man 92 | 93 | =begin html 94 | 95 |
 96 | 
 97 | MOUNT_OWNER=username
 98 | BASE_MOUNTPOINT=/mnt
 99 | FMASK_DMASK=fmask,dmask
100 | EXTRA_ARGS=-c <path_to_executable>
101 | 
102 | 
103 | 104 | =end html 105 | 106 | The options B and B are optional. 107 | The default value for B is I<0133,0022>. 108 | B will be appended to the I executable. 109 | 110 | =head1 SEE ALSO 111 | 112 | ldmc(1), umount(8) 113 | 114 | =head1 WWW 115 | 116 | L 117 | 118 | =head1 AUTHOR 119 | 120 | 2011-2019 (C) The Lemon Man 121 | -------------------------------------------------------------------------------- /ldm.service.in: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=lightweight device mounter 3 | 4 | [Service] 5 | Environment=FMASK_DMASK=0133,0022 6 | EnvironmentFile=/etc/ldm.conf 7 | ExecStart=@@BINDIR@@/ldm -u ${MOUNT_OWNER} -p ${BASE_MOUNTPOINT} -m ${FMASK_DMASK} ${EXTRA_ARGS} 8 | KillMode=process 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | -------------------------------------------------------------------------------- /ldmc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "ipc.h" 6 | 7 | int 8 | ipc_send (int fd, char type, char *arg) 9 | { 10 | char *fmt; 11 | char *opt; 12 | char tmp[4096]; 13 | int r; 14 | 15 | if (fd < 0) 16 | return 0; 17 | 18 | fmt = opt = NULL; 19 | 20 | switch (type) { 21 | case 'r': 22 | fmt = "R%s"; 23 | opt = arg; 24 | break; 25 | 26 | case 'l': 27 | fmt = "L"; 28 | opt = NULL; 29 | break; 30 | 31 | default: 32 | fprintf(stderr, "Unknown ipc command %c\n", type); 33 | return 0; 34 | } 35 | 36 | if (!ipc_sendf(fd, fmt, opt)) { 37 | fprintf(stderr, "Could not communicate with the daemon! Is ldm running?\n"); 38 | return 0; 39 | } 40 | 41 | switch (type) { 42 | case 'r': 43 | // Receive the result code 44 | r = ipc_read_one(fd); 45 | 46 | if (r == '+') 47 | printf("Operation completed successfully\n"); 48 | else 49 | printf("The operation didn't complete successfully\n"); 50 | 51 | return (r == '+'); 52 | 53 | case 'l': 54 | // Dump all the received lines to stdout 55 | while (1) { 56 | r = ipc_read_line(fd, tmp, sizeof(tmp)); 57 | if (!r) 58 | break; 59 | printf("%s\n", tmp); 60 | } 61 | 62 | return 1; 63 | } 64 | 65 | return 0; 66 | } 67 | 68 | void 69 | usage () 70 | { 71 | printf("ldmc "VERSION_STR"\n"); 72 | printf("2015-2019 (C) The Lemon Man\n"); 73 | printf("\t-r Remove a mounted device\n"); 74 | printf("\t-l List the mounted devices\n"); 75 | printf("\t-h Show this help\n"); 76 | } 77 | 78 | int 79 | main (int argc, char **argv) 80 | { 81 | int opt; 82 | int ipc_fd; 83 | int ret; 84 | 85 | if (argc == 1) { 86 | usage (); 87 | return EXIT_FAILURE; 88 | } 89 | 90 | ret = EXIT_SUCCESS; 91 | 92 | while ((opt = getopt(argc, argv, "hlr:")) != -1) { 93 | switch (opt) { 94 | case 'l': 95 | case 'r': 96 | ipc_fd = ipc_init(0); 97 | 98 | // Could not open the ipc socket 99 | if (ipc_fd < 0) 100 | return EXIT_FAILURE; 101 | 102 | // Propagate the error code to the exit status 103 | if (!ipc_send(ipc_fd, (char)opt, optarg)) 104 | ret = EXIT_FAILURE; 105 | 106 | close(ipc_fd); 107 | break; 108 | 109 | default: 110 | case 'h': 111 | usage (); 112 | } 113 | } 114 | 115 | return ret; 116 | } 117 | -------------------------------------------------------------------------------- /ldmc.pod: -------------------------------------------------------------------------------- 1 | =head1 NAME 2 | 3 | ldmc - Send commands to a running ldm daemon instance 4 | 5 | =head1 SYNOPSIS 6 | 7 | I [-l] [-r I] [-h] 8 | 9 | =head1 DESCRIPTION 10 | 11 | Send commands to the running ldm daemon. See I for a list of the supported commands and 12 | their arguments 13 | 14 | =head1 OPTIONS 15 | 16 | =over 17 | 18 | =item B<-r> I 19 | 20 | Ask the running daemon to unmount the device I, where I is either the device node or the 21 | mountpoint. 22 | 23 | =item B<-l> 24 | 25 | List the mounted devices. Every entry is in the form 26 | 27 | C 28 | 29 | =item B<-h> 30 | 31 | Print a brief help and exit. 32 | 33 | =back 34 | 35 | =head1 SEE ALSO 36 | 37 | ldm(1) 38 | 39 | =head1 WWW 40 | 41 | L 42 | 43 | =head1 AUTHOR 44 | 45 | 2011-2016 (C) The Lemon Man 46 | -------------------------------------------------------------------------------- /umount.ldm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Little script to provide compatibility with umount, it currently doesn't 3 | # handle any flag but that's fine. 4 | 5 | if [ $# -lt 1 ]; then 6 | exit 1 7 | fi 8 | 9 | # Use ldmc to talk with the daemon 10 | ldmc -r "$1" 11 | 12 | # vim: ft=sh: 13 | --------------------------------------------------------------------------------