diff --git a/Makefile b/Makefile index c0de866..29fbea3 100644 --- a/Makefile +++ b/Makefile @@ -1,40 +1,53 @@ +VERSION=v0.6 +VERSION_GIT=$(shell test -d .git && git describe 2> /dev/null) + +ifneq "$(VERSION_GIT)" "" +VERSION=$(VERSION_GIT) +endif + CC ?= gcc -CFLAGS := -O2 $(CFLAGS) -LDFLAGS := -ludev -lmount $(LDFLAGS) -CFDEBUG = -g3 -pedantic -Wall -Wunused-parameter -Wlong-long +CFLAGS += -std=c99 \ + -D_GNU_SOURCE \ + -Wall -Wunused-parameter -O2 \ + -DVERSION_STR="\"$(VERSION)\"" +CFDEBUG = -g3 -pedantic -Wlong-long CFDEBUG += -Wsign-conversion -Wconversion -Wimplicit-function-declaration -PREFIX ?= /usr/local -BINDIR ?= $(PREFIX)/bin -SYSTEMDDIR ?= $(PREFIX)/lib/systemd +LIBS = libudev mount glib-2.0 +LDFLAGS := `pkg-config --libs $(LIBS)` $(LDFLAGS) -EXEC = ldm -SRCS = ldm.c -OBJS = $(SRCS:.c=.o) +BINDIR ?= /usr/bin +SYSTEMDDIR ?= /usr/lib/systemd -all: $(EXEC) doc +all: ldm ldmc doc .c.o: - $(CC) $(CFLAGS) -o $@ -c $< + $(CC) $(CFLAGS) `pkg-config --cflags $(LIBS)` -o $@ -c $< + +ldm: ipc.o ldm.o + $(CC) $(LDFLAGS) -o ldm ipc.o ldm.o -$(EXEC): $(OBJS) - $(CC) $(LDFLAGS) -o $(EXEC) $(OBJS) +ldmc: ipc.o ldmc.o + $(CC) $(LDFLAGS) -o ldmc ipc.o ldmc.o -debug: $(EXEC) +debug: ldm ldmc debug: CC += $(CFDEBUG) doc: README.pod - @pod2man --section=1 --center="ldm Manual" --name "ldm" --release="ldm $(shell git describe)" README.pod > ldm.1 + @pod2man --section=1 --center="ldm Manual" --name "ldm" --release="$(VERSION)" README.pod > ldm.1 + @pod2man --section=1 --center="ldmc Manual" --name "ldmc" --release="$(VERSION)" ldmc.pod > ldmc.1 clean: - $(RM) *.o *.1 ldm + $(RM) *.o *.1 ldm ldmc mrproper: clean - $(RM) $(EXEC) + $(RM) ldm ldmc install-main: ldm doc install -D -m 755 ldm $(DESTDIR)$(BINDIR)/ldm - install -D -m 644 ldm.1 $(DESTDIR)$(PREFIX)/share/man/man1/ldm.1 + install -D -m 755 ldmc $(DESTDIR)$(BINDIR)/ldmc + install -D -m 644 ldm.1 $(DESTDIR)/usr/share/man/man1/ldm.1 + install -D -m 644 ldmc.1 $(DESTDIR)/usr/share/man/man1/ldmc.1 install-systemd: ldm.service install -D -m 644 ldm.service $(DESTDIR)$(SYSTEMDDIR)/system/ldm.service @@ -43,7 +56,9 @@ install: all install-main install-systemd uninstall: $(RM) $(DESTDIR)$(BINDIR)/ldm - $(RM) $(DESTDIR)$(PREFIX)/share/man/man1/ldm.1 + $(RM) $(DESTDIR)$(BINDIR)/ldmc + $(RM) $(DESTDIR)/usr/share/man/man1/ldm.1 + $(RM) $(DESTDIR)/usr/share/man/man1/ldmc.1 $(RM) $(DESTDIR)$(SYSTEMDDIR)/system/ldm.service .PHONY: all debug clean mrproper install install-main install-systemd uninstall diff --git a/README.pod b/README.pod index 95fba20..aabd647 100644 --- a/README.pod +++ b/README.pod @@ -4,24 +4,21 @@ ldm - Lightweight Device Mounter =head1 SYNOPSIS -I [-d] [-r I] [-g I] [-u I] [-p I] [-c I] [-h] +I [-d] [-g I] [-u I] [-p I] [-c I] [-h] =head1 DESCRIPTION ldm is a lightweight device mounter following the UNIX philosophy written in C and based on udev and libmount. +The daemon can be controlled with B. =head1 OPTIONS =over -=item B<-d> +=item B<-d> Run ldm as a daemon. -=item B<-r> I - -Ask the ldm daemon to unmount the selected device. - =item B<-g> I Specify the gid of the user owning the mount points. @@ -48,6 +45,10 @@ The complete path to the mountpoint. The path pointing to the device node in /dev +=item B + +The filesystem on the mounted device. + =item B The action ldm has just performed, it can either be I or I @@ -92,10 +93,14 @@ USER_UID = uid
=end html +=head1 SEE ALSO + +ldmc(1) + =head1 WWW L =head1 AUTHOR -2011-2014 (C) The Lemon Man +2011-2015 (C) The Lemon Man diff --git a/ipc.c b/ipc.c new file mode 100644 index 0000000..af475d8 --- /dev/null +++ b/ipc.c @@ -0,0 +1,150 @@ +#include +#include +#include +#include +#include +#include +#include + +#define IPC_SOCKET "/run/ldm.fifo" + +void +ipc_deinit (int fd) +{ + if (fd >= 0) + close (fd); + unlink (IPC_SOCKET); +} + +int +ipc_init (int as_master) +{ + int sock; + int flags; + struct sockaddr_un so; + + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + perror("socket"); + return -1; + } + + // Make sure that there are no leftovers + unlink (IPC_SOCKET); + + memset(&so, 0, sizeof(struct sockaddr_un)); + so.sun_family = AF_UNIX; + strncpy(so.sun_path, IPC_SOCKET, sizeof(so.sun_path)); + + if (as_master) { + // The master waits for the slaves to connect + if (bind(sock, (struct sockaddr *)&so, sizeof(struct sockaddr_un)) < 0) { + perror("bind"); + return -1; + } + + // Make the sock non-blocking + flags = fcntl(sock, F_GETFL, 0); + if (flags < 0) { + perror("fcntl"); + return -1; + } + + if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) { + perror("fcntl"); + return -1; + } + } + else { + // The slave connects to the master + if (connect(sock, (struct sockaddr *)&so, sizeof(struct sockaddr_un)) < 0) { + perror("connect"); + return -1; + } + } + + return sock; +} + +char +ipc_read_one (int fd) +{ + char resp; + + if (fd < 0) + return 0; + + if (read(fd, &resp, 1) != 1) { + perror("read"); + return 0; + } + + return resp; +} + +int +ipc_read_line (int fd, char *line, int max_line_length) +{ + int p; + + if (fd < 0 || !line) + return 0; + + for (p = 0; p < max_line_length - 1; p++) { + if (read(fd, &line[p], 1) != 1) { + perror("read"); + break; + } + + if (line[p] == '\n') + break; + } + + line[p] = '\0'; + + // Don't take into account the \n + return p? p - 1: 0; +} + +int +ipc_sendf (int fd, const char *fmt, ...) +{ + va_list args, args_; + int fmt_length; + char *buf; + + va_start(args, fmt); + + // Make a copy since the first vsnprintf call modifies it + va_copy(args_, args); + + // Obtain the final string length first + fmt_length = vsnprintf(NULL, 0, fmt, args_); + if (fmt_length < 0) { + perror("vsprintf"); + va_end(args); + return 0; + } + + buf = malloc(fmt_length + 1); + if (!buf) { + perror("errno"); + va_end(args); + return 0; + } + + vsnprintf(buf, fmt_length + 1, fmt, args); + + // Don't send the trailing NULL + if (write(fd, buf, fmt_length) != fmt_length) { + perror("write"); + free(buf); + va_end(args); + return 0; + } + + free(buf); + va_end(args); + + return fmt_length; +} diff --git a/ipc.h b/ipc.h new file mode 100644 index 0000000..e719b1b --- /dev/null +++ b/ipc.h @@ -0,0 +1,7 @@ +#pragma once + +void ipc_deinit (int fd); +int ipc_init (int as_master); +char ipc_read_one (int fd); +int ipc_read_line (int fd, char *line, int max_line_length); +int ipc_sendf (int fd, const char *fmt, ...); diff --git a/ldm.c b/ldm.c index 837392e..3d98b8a 100644 --- a/ldm.c +++ b/ldm.c @@ -1,91 +1,83 @@ -#include +#include +#include +#include #include -#include -#include +#include +#include +#include +#include +#include #include -#include -#include -#include -#include #include -#include +#include +#include +#include +#include #include -#include #include -#include -#include -#include - -#define VERSION_STR "0.5.1" - -enum { - DEVICE_VOLUME, - DEVICE_CD, - DEVICE_UNK -}; - -enum { - QUIRK_NONE = 0, - QUIRK_OWNER_FIX = (1<<0), - QUIRK_UTF8_FLAG = (1<<1), - QUIRK_MASK = (1<<2), - QUIRK_FLUSH = (1<<3), - QUIRK_RO = (1<<4) -}; +#include +#include +#include +#include +#include +#include "ipc.h" + +typedef enum { + VOLUME, + MAX, +} VolumeType; + +typedef enum { + NONE = 0x00, + OWNER_FIX = 0x01, + UTF8_FLAG = 0x02, + MASK = 0x04, + FLUSH = 0x08, + RO = 0x10, +} Quirk; typedef struct device_t { - int kind; + VolumeType type; char *node; - char *rnode; // Pointer to the resolved /dev node - char *filesystem; - char *mountpoint; - struct udev_device *udev; + struct udev_device *dev; + char *mp; // The path to the mountpoint + char *fs; // The name of the filesystem } device_t; typedef struct fs_quirk_t { char *name; - int quirks; + Quirk quirks; } fs_quirk_t; -#define OPT_FMT "uid=%i,gid=%i" -#define MAX_DEVICES 20 -#define FSTAB_PATH "/etc/fstab" -#define MTAB_PATH "/proc/self/mounts" -#define LOCK_PATH "/run/ldm.pid" -#define FIFO_PATH "/run/ldm.fifo" +#define FSTAB_PATH "/etc/fstab" +#define MTAB_PATH "/proc/self/mounts" +#define LOCK_PATH "/run/ldm.pid" // Static global structs static struct libmnt_table *g_fstab; static struct libmnt_table *g_mtab; -static struct device_t *g_devices[MAX_DEVICES]; static FILE *g_lockfd; static int g_running; -static int g_verbose; static int g_gid, g_uid; static char *g_mount_path; -static char *g_callback_path; - -#define LOG(...) do { if (g_verbose) fprintf(stderr, __VA_ARGS__); } while(0); +static char *g_callback_cmd; +static GHashTable *g_dev_table; -// A less stupid strdup +#define first_nonnull(a,b,c) ((a) ? (a) : ((b) ? (b) : ((c) ? (c) : NULL))) char * -xstrdup(const char *str) +udev_get_prop (struct udev_device *dev, char *key) { - if (!str) - return NULL; - return (char *)strdup(str); + const char *value = udev_device_get_property_value(dev, key); + return value? (char *)value: NULL; } -// A less stupid strcmp - int -xstrcmp (const char *s1, const char *s2) +udev_prop_true (struct udev_device *dev, char *key) { - if (!s1 || !s2) - return 1; - return strcmp(s1, s2); + const char *value = udev_device_get_property_value(dev, key); + return value && !strcmp(value, "1"); } // Locking functions @@ -104,549 +96,598 @@ lock_create (int pid) // Spawn helper int -spawn_helper (char *command, char *node, char *action, char *mountpoint) +spawn_callback (char *action, device_t *dev) { + int ret; pid_t child_pid; - int ret, l, env_len; - char *env_buf, *envp[4]; - char * const cmd[] = { "/bin/sh", "-c", command, NULL }; - - if (!command) - return 0; + char *env[5]; - env_len = 38 + strlen(node) + strlen(action) + strlen(mountpoint); - env_buf = alloca(env_len + 1); - if (!env_buf) + // No callback registered, we're done + if (!g_callback_cmd) return 0; - envp[0] = env_buf; - l = snprintf(env_buf, env_len, "LDM_MOUNTPOINT=%s", mountpoint); - env_len -= l + 1; - env_buf += l + 1; + env[0] = g_strdup_printf("LDM_ACTION=%s", action); + env[1] = g_strdup_printf("LDM_NODE=%s", dev->node); + env[2] = g_strdup_printf("LDM_MOUNTPOINT=%s", dev->mp); + env[3] = g_strdup_printf("LDM_FS=%s", dev->fs); + env[4] = NULL; - envp[1] = env_buf; - l = snprintf(env_buf, env_len, "LDM_NODE=%s", node); - env_len -= l + 1; - env_buf += l + 1; + if (!env[0] || !env[1] || !env[2] || !env[3]) { + free(env[0]); + free(env[1]); + free(env[2]); + free(env[3]); - envp[2] = env_buf; - l = snprintf(env_buf, env_len, "LDM_ACTION=%s", action); - env_len -= l + 1; - env_buf += l; - - envp[3] = NULL; - - assert(env_len == 0 && *env_buf == '\0'); + return 1; + } child_pid = fork(); if (child_pid < 0) - return 0; + return 1; if (child_pid > 0) { + // Wait for the process to return wait(&ret); - // Return the exit code or 0 if something went wrong - return WIFEXITED(ret) ? WEXITSTATUS(ret) : 0; + + free(env[0]); + free(env[1]); + free(env[2]); + free(env[3]); + + // Return the exit code or EXIT_FAILURE if something went wrong + return WIFEXITED(ret) ? WEXITSTATUS(ret) : EXIT_FAILURE; } // Drop the root priviledges. Oh and the bass too. - if (setgid(g_gid) < 0) - return 0; - if (setuid(g_uid) < 0) - return 0; + if (setgid((__gid_t)g_gid) < 0 || setuid((__uid_t)g_uid) < 0) { + free(env[0]); + free(env[1]); + free(env[2]); + free(env[3]); + + _Exit(EXIT_FAILURE); + } close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); - execve(cmd[0], cmd, (char * const *)envp); + char * const cmdline[] = { + "/bin/sh", + "-c", g_callback_cmd, + NULL + }; + execve(cmdline[0], cmdline, env); + // Should never reach this - syslog(LOG_ERR, "Could not execute \"%s\"", command); + syslog(LOG_ERR, "Could not execute \"%s\"", g_callback_cmd); // Die - _Exit(1); + _Exit(EXIT_FAILURE); } // Convenience function for fstab handling +enum { + NODE, + UUID, + LABEL, +}; + struct libmnt_fs * -fstab_search (struct libmnt_table *tab, struct udev_device *udev) +table_search_by_str (struct libmnt_table *tbl, int type, char *str) { - struct libmnt_fs *ret; - char *tmp; - char keyword[128]; - - // Try matching the /dev/ node - tmp = (char *)udev_device_get_devnode(udev); - ret = mnt_table_find_source(tab, tmp, MNT_ITER_FORWARD); - if (ret) - return ret; - - // Try to resolve the /dev node and retry, this expands the dm-n to the full path - tmp = mnt_resolve_path(tmp, NULL); - if (!tmp) - return NULL; - ret = mnt_table_find_source(tab, tmp, MNT_ITER_FORWARD); - free(tmp); - if (ret) - return ret; - - // Try matching the uuid - tmp = (char *)udev_device_get_property_value(udev, "ID_FS_UUID"); - if (!tmp) - return NULL; - snprintf(keyword, sizeof(keyword), "UUID=%s", tmp); - ret = mnt_table_find_source(tab, keyword, MNT_ITER_FORWARD); - if (ret) - return ret; - - // Try matching the label - tmp = (char *)udev_device_get_property_value(udev, "ID_FS_LABEL"); - if (!tmp) - return NULL; - snprintf(keyword, sizeof(keyword), "LABEL=%s", tmp); - ret = mnt_table_find_source(tab, keyword, MNT_ITER_FORWARD); - if (ret) - return ret; + struct libmnt_fs *fs; - return NULL; -} - -int -fstab_has_option (struct libmnt_table *tab, struct udev_device *udev, const char *option) -{ - struct libmnt_fs *ret; + if (!tbl || !str) + return NULL; - ret = fstab_search(tab, udev); - if (!ret) - return 0; + switch (type) { + case NODE: + fs = mnt_table_find_source(tbl, str, MNT_ITER_FORWARD); + break; + case UUID: + fs = mnt_table_find_tag(tbl, "UUID", str, MNT_ITER_FORWARD); + break; + case LABEL: + fs = mnt_table_find_tag(tbl, "LABEL", str, MNT_ITER_FORWARD); + break; + default: + fprintf(stderr, "table_search_by_str()\n"); + return NULL; + } - return mnt_fs_match_options(ret, option); + return fs; } -int -device_has_media (struct udev_device *udev, const int dev_kind) +struct libmnt_fs * +table_search_by_dev (struct libmnt_table *tbl, device_t *dev) { - if (!udev) - return 0; + struct libmnt_fs *fs; - switch (dev_kind) { - case DEVICE_VOLUME: - return (udev_device_get_property_value(udev, "ID_FS_USAGE") != NULL); - case DEVICE_CD: - return (udev_device_get_property_value(udev, "ID_CDROM_MEDIA") != NULL); - default: - return 0; - } -} + // Try to match the dev node + fs = table_search_by_str(tbl, NODE, dev->node); + if (fs) + return fs; -int -filesystem_quirks (char *fs) -{ - int i; - static const fs_quirk_t fs_table [] = { - { "msdos" , QUIRK_OWNER_FIX | QUIRK_UTF8_FLAG }, - { "umsdos", QUIRK_OWNER_FIX | QUIRK_UTF8_FLAG }, - { "vfat", QUIRK_OWNER_FIX | QUIRK_UTF8_FLAG | QUIRK_MASK | QUIRK_FLUSH }, - { "exfat", QUIRK_OWNER_FIX }, - { "ntfs", QUIRK_OWNER_FIX | QUIRK_UTF8_FLAG | QUIRK_MASK }, - { "iso9660",QUIRK_OWNER_FIX | QUIRK_UTF8_FLAG | QUIRK_RO }, - { "udf", QUIRK_OWNER_FIX }, - }; + // Try to match the uuid + fs = table_search_by_str(tbl, UUID, udev_get_prop(dev->dev, "ID_FS_UUID")); + if (fs) + return fs; - for (i = 0; i < sizeof(fs_table)/sizeof(fs_quirk_t); i++) { - if (!xstrcmp(fs_table[i].name, fs)) - return fs_table[i].quirks; - } - return QUIRK_NONE; + // Try to match the label + fs = table_search_by_str(tbl, LABEL, udev_get_prop(dev->dev, "ID_FS_LABEL")); + if (fs) + return fs; + + return NULL; } -char * -device_create_mountpoint (struct device_t *device) +struct libmnt_fs * +table_search_by_udev (struct libmnt_table *tbl, struct udev_device *udev) { - char tmp[PATH_MAX]; - char *c; - const char *label, *uuid, *serial; - int file_count; - DIR *dir; - - label = udev_device_get_property_value(device->udev, "ID_FS_LABEL"); - uuid = udev_device_get_property_value(device->udev, "ID_FS_UUID"); - serial = udev_device_get_property_value(device->udev, "ID_SERIAL"); - - if (label) - snprintf(tmp, sizeof(tmp), "%s/%s", g_mount_path, label); - else if (uuid) - snprintf(tmp, sizeof(tmp), "%s/%s", g_mount_path, uuid); - else if (serial) - snprintf(tmp, sizeof(tmp), "%s/%s", g_mount_path, serial); - else - return NULL; - - // Replace the whitespaces - for (c = tmp; *c; c++) { - if (*c == ' ') - *c = '_'; - } + struct libmnt_fs *fs; + char *resolved; - // Check if there's another folder with the same name - while (access(tmp, F_OK) != -1) { - // We tried hard and failed - if (strlen(tmp) == sizeof(tmp) - 2) - return NULL; + resolved = mnt_resolve_path(udev_device_get_devnode(udev), NULL); + // Try to match the resolved dev node + fs = table_search_by_str(tbl, NODE, resolved); + free(resolved); - // Reuse the directory if it's empty - if ((dir = opendir(tmp)) != NULL) { - file_count = 0; - while (readdir(dir) != NULL) { - if (++file_count > 2) - break; - } - closedir(dir); + if (fs) + return fs; - // Directory is empty, reuse for mounting - if (file_count <= 2) - break; - } + // Try to match the uuid + fs = table_search_by_str(tbl, UUID, udev_get_prop(udev, "ID_FS_UUID")); + if (fs) + return fs; - // Directory not empty, append a trailing _ - strcat(tmp, "_"); - } + // Try to match the label + fs = table_search_by_str(tbl, LABEL, udev_get_prop(udev, "ID_FS_LABEL")); + if (fs) + return fs; - return xstrdup(tmp); + return NULL; } int -device_register (struct device_t *dev) +fstab_has_option (struct udev_device *udev, const char *option) { - int j; + struct libmnt_fs *fs; - for (j = 0; j < MAX_DEVICES; j++) { - if (g_devices[j] == NULL) { - g_devices[j] = dev; - return 1; - } - } + if (!udev || !option) + return 0; - return 0; + fs = table_search_by_udev(g_fstab, udev); + if (!fs) + return 0; + + return mnt_fs_match_options(fs, option); } -void -device_destroy (struct device_t *dev) +unsigned int +fs_get_quirks (char *fs) { - int j; + int i; + static const fs_quirk_t fs_table [] = { + { "msdos" , OWNER_FIX | UTF8_FLAG }, + { "umsdos", OWNER_FIX | UTF8_FLAG }, + { "vfat", OWNER_FIX | UTF8_FLAG | MASK | FLUSH }, + { "exfat", OWNER_FIX }, + { "ntfs", OWNER_FIX | UTF8_FLAG | MASK }, + { "iso9660",OWNER_FIX | UTF8_FLAG | RO }, + { "udf", OWNER_FIX }, + }; - for (j = 0; j < MAX_DEVICES; j++) { - if (g_devices[j] == dev) - break; + for (i = 0; i < sizeof(fs_table)/sizeof(fs_quirk_t); i++) { + if (!strcmp(fs_table[i].name, fs)) + return fs_table[i].quirks; } + return NONE; +} - free(dev->node); - free(dev->rnode); - free(dev->filesystem); - free(dev->mountpoint); - udev_device_unref(dev->udev); +int +device_find_predicate (char *key, device_t *value, char *what) +{ + (void)key; - free(dev); + // Try to match the resolved node or the mountpoint name + if (!strcmp(value->node, what)) + return 1; + if (value->type == VOLUME && !strcmp(value->mp, what)) + return 1; - // Might happen that we have to destroy a device not yet - // registered. Just free it - if (j < MAX_DEVICES) - g_devices[j] = NULL; + return 0; } - // Path is either the /dev/ node or the mountpoint struct device_t * device_search (const char *path) { - int j; device_t *dev; - if (!path) + if (!path || !path[0]) return NULL; - for (j = 0; j < MAX_DEVICES; j++) { - dev = g_devices[j]; + // This is the fast path, let's just hope it's a /dev/ node + dev = g_hash_table_lookup (g_dev_table, path); - if (dev) { - if (!xstrcmp(dev->node, path) || !xstrcmp(dev->rnode, path) || !xstrcmp(dev->mountpoint, path)) - return g_devices[j]; - } - } + if (!dev) + dev = g_hash_table_find (g_dev_table, (GHRFunc)device_find_predicate, (gpointer)path); - return NULL; + return dev; } -int -device_is_mounted (struct udev_device *dev) -{ - // Use fstab_search to resolve lvm names - return (fstab_search(g_mtab, dev) != NULL); -} - -struct device_t * -device_new (struct udev_device *dev) +void +device_free (device_t *dev) { - struct device_t *device; - struct libmnt_fs *fstab_entry; + if (!dev) + return; - const char *dev_node; - const char *dev_type; - const char *dev_idtype; - const char *dev_fs; - int dev_kind; + free(dev->node); + udev_device_unref(dev->dev); - // libmount < 2.21 doesn't support '+noauto', using 'noauto' instead - const char *noauto_opt = mnt_parse_version_string(LIBMOUNT_VERSION) < 2210 ? "noauto" : "+noauto"; - // First of all check wether we're dealing with a noauto device - if (fstab_has_option(g_fstab, dev, noauto_opt)) - return NULL; + switch (dev->type) { + case VOLUME: + free(dev->mp); + free(dev->fs); + break; - dev_node = udev_device_get_devnode(dev); - dev_fs = udev_device_get_property_value(dev, "ID_FS_TYPE"); + default: + break; + } - LOG("%s\n\tdev_fs : %s\n", dev_node, dev_fs); + free(dev); +} - // Avoid mounting swap partitions because we're not intrested in those and LVM/LUKS - // containers as udev issues another event for each single partition contained in them - if (!dev_fs || !xstrcmp(dev_fs, "swap") || !xstrcmp(dev_fs, "LVM2_member") || !xstrcmp(dev_fs, "crypto_LUKS")) - return NULL; +device_t * +device_new (struct udev_device *udev) +{ + const char *dev_node, *dev_fs, *dev_fs_usage; + device_t *dev; - dev_type = udev_device_get_devtype(dev); - dev_idtype = udev_device_get_property_value(dev, "ID_TYPE"); - - LOG("\tdev_type : %s\n\tdev_idtype : %s\n", dev_type, dev_idtype); - - if (!xstrcmp(dev_type, "partition")) - dev_kind = DEVICE_VOLUME; - // Partition-less devices - else if (dev_fs && !xstrcmp(dev_type, "disk")) - dev_kind = DEVICE_VOLUME; - // LVM partitions - else if (udev_device_get_property_value(dev, "DM_NAME") && !xstrcmp(dev_type, "disk")) - dev_kind = DEVICE_VOLUME; - else if (!xstrcmp(dev_idtype, "floppy")) - dev_kind = DEVICE_VOLUME; - else if (!xstrcmp(dev_idtype, "cd")) - dev_kind = DEVICE_CD; - else + if (!udev) return NULL; - if (!device_has_media(dev, dev_kind)) - return NULL; + dev_node = udev_device_get_devnode(udev); + dev_fs = udev_get_prop(udev, "ID_FS_TYPE"); + dev_fs_usage = udev_get_prop(udev, "ID_FS_USAGE"); - device = calloc(1, sizeof(struct device_t)); + fprintf(stderr, "%s (FS_USAGE : %s FS_TYPE : %s)\n", dev_node, dev_fs, dev_fs_usage); - if (!device) + if (!dev_fs && !dev_fs_usage) return NULL; - device->udev = dev; - device->kind = dev_kind; - device->node = xstrdup(dev_node); - device->rnode = mnt_resolve_path(dev_node, NULL); - device->filesystem = xstrdup(dev_fs); - - // Increment the refcount - udev_device_ref(device->udev); + // Avoid empty cd/dvd drives + if (udev_get_prop(udev, "ID_CDROM") && !udev_prop_true(udev, "ID_CDROM_MEDIA")) + return NULL; - fstab_entry = fstab_search(g_fstab, device->udev); + if (!strcmp(dev_fs_usage, "filesystem")) { + dev = calloc(1, sizeof(device_t)); - device->mountpoint = (fstab_entry) ? - xstrdup(mnt_fs_get_target(fstab_entry)) : - device_create_mountpoint(device); + dev->type = VOLUME; + dev->dev = udev; + dev->node = mnt_resolve_path(dev_node, NULL); + dev->fs = strdup(dev_fs); - if (!device->mountpoint) { - syslog(LOG_ERR, "Couldn't make up a mountpoint name. Please report this bug."); - device_destroy(device); - return NULL; + udev_device_ref(udev); } - - if (!device_register(device)) { - device_destroy(device); - return NULL; + else { + dev = NULL; + fprintf(stderr, "Skipping %s (ID_FS_USAGE : %s)\n", dev_node, dev_fs_usage); } - return device; + return dev; } -int -device_unmount (struct udev_device *dev) +char * +device_get_mp (device_t *dev, const char *base) { - struct device_t *device; - struct libmnt_context *ctx; + char *unique; + char mp[4096]; + GDir *dir; - device = device_search((char *)udev_device_get_devnode(dev)); + if (!dev || !base) + return NULL; - if (!device) - return 0; + // Use the first non-null field + unique = first_nonnull(udev_get_prop(dev->dev, "ID_FS_LABEL"), + udev_get_prop(dev->dev, "ID_FS_UUID"), + udev_get_prop(dev->dev, "ID_SERIAL")); - if (device_is_mounted(dev)) { - ctx = mnt_new_context(); - mnt_context_set_target(ctx, device->rnode); - if (mnt_context_umount(ctx)) { - syslog(LOG_ERR, "Error while unmounting %s (%s)", device->rnode, strerror(errno)); - mnt_free_context(ctx); - return 0; - } - mnt_free_context(ctx); - } + if (!unique) + return NULL; - rmdir(device->mountpoint); + if (snprintf(mp, sizeof(mp), "%s/%s", base, unique) < 0) + return NULL; - spawn_helper(g_callback_path, device->rnode, "unmount", device->mountpoint); + // If the mountpoint we've come up with already exists try to find a good one by appending '_' + while (g_file_test(mp, G_FILE_TEST_EXISTS)) { + // We tried hard and failed + if (strlen(mp) == sizeof(mp) - 2) + return NULL; + + // Reuse the directory only if it's empty + dir = g_dir_open(mp, 0, NULL); + if (dir) { + // The directory is empty! + // Note for the reader : 'g_dir_read_name' omits '.' and '..' + if (!g_dir_read_name(dir)) + break; - device_destroy(device); + g_dir_close(dir); + } - return 1; + // Directory not empty, append a '_' + strcat(mp, "_"); + } + + return strdup(mp);; } int -device_mount (struct udev_device *dev) +device_mount (device_t *dev) { - struct device_t *device; + char *mp; + unsigned int fs_quirks; + char opt_fmt[256] = {0}; struct libmnt_context *ctx; - char opt_fmt[256]; - char *p; - int quirks; + struct libmnt_fs *fstab; - device = device_new(dev); + if (!dev) + return 0; - if (!device) + fstab = table_search_by_dev(g_fstab, dev); + + if (fstab && mnt_fs_get_target(fstab)) + mp = strdup(mnt_fs_get_target(fstab)); + else + mp = device_get_mp(dev, g_mount_path); + + if (!mp) return 0; - mkdir(device->mountpoint, 755); + if (mkdir(mp, 775) < 0) { + syslog(LOG_ERR, "Could not mkdir() the folder at %s (%s)", mp, strerror(errno)); + return 0; + } - p = opt_fmt; + // Set 'mp' as the mountpoint for the device + dev->mp = mp; - // Some filesystems just want to watch the world burn - quirks = filesystem_quirks(device->filesystem); + fs_quirks = fs_get_quirks(dev->fs); - if (quirks != QUIRK_NONE) { + // Apply the needed quirks + if (fs_quirks != NONE) { + char *p = opt_fmt; // Microsoft filesystems and filesystems used on optical // discs require the gid and uid to be passed as mount // arguments to allow the user to read and write, while // posix filesystems just need a chown after being mounted - if (quirks & QUIRK_OWNER_FIX) - p += sprintf(p, OPT_FMT",", g_uid, g_gid); - if (quirks & QUIRK_UTF8_FLAG) + if (fs_quirks & OWNER_FIX) + p += sprintf(p, "uid=%i,gid=%i,", g_uid, g_gid); + if (fs_quirks & UTF8_FLAG) p += sprintf(p, "utf8,"); - if (quirks & QUIRK_FLUSH) + if (fs_quirks & FLUSH) p += sprintf(p, "flush,"); - if (quirks & QUIRK_MASK) + if (fs_quirks & MASK) p += sprintf(p, "dmask=022,fmask=133,"); + + *p = '\0'; } - *p = 0; ctx = mnt_new_context(); - mnt_context_set_fstype(ctx, device->filesystem); - mnt_context_set_source(ctx, device->rnode); - mnt_context_set_target(ctx, device->mountpoint); +#if 0 + fprintf(stderr, "fs : %s\n", dev->fs); + fprintf(stderr, "node : %s\n", dev->node); + fprintf(stderr, "mp : %s\n", dev->mp); + fprintf(stderr, "opt : %s\n", opt_fmt); +#endif + + mnt_context_set_fstype(ctx, dev->fs); + mnt_context_set_source(ctx, dev->node); + mnt_context_set_target(ctx, dev->mp); mnt_context_set_options(ctx, opt_fmt); - if (device->kind == DEVICE_CD || quirks & QUIRK_RO) + if (fs_quirks & RO) mnt_context_set_mflags(ctx, MS_RDONLY); if (mnt_context_mount(ctx)) { - syslog(LOG_ERR, "Error while mounting %s (%s)", device->rnode, strerror(errno)); + syslog(LOG_ERR, "Error while mounting %s (%s)", dev->node, strerror(errno)); mnt_free_context(ctx); - device_unmount(dev); + rmdir(dev->mp); return 0; } mnt_free_context(ctx); - if (!(quirks & QUIRK_OWNER_FIX)) { - if (chown(device->mountpoint, (__uid_t)g_uid, (__gid_t)g_gid)) { - syslog(LOG_ERR, "Cannot chown %s", device->mountpoint); - device_unmount(dev); + if (!(fs_quirks & OWNER_FIX)) { + if (chown(dev->mp, (__uid_t)g_uid, (__gid_t)g_gid) < 0) { + syslog(LOG_ERR, "Cannot chown %s", dev->mp); return 0; } } - spawn_helper(g_callback_path, device->rnode, "mount", device->mountpoint); + (void)spawn_callback ("mount", dev); return 1; } int -device_change (struct udev_device *dev) +device_unmount (device_t *dev) { - struct device_t *device; - - device = device_search((char *)udev_device_get_devnode(dev)); + struct libmnt_context *ctx; - // Handle change events for CD drives only - if (device && device->kind != DEVICE_CD) + if (!dev) return 0; - if (device) { - // Unmount the old media - if (device_is_mounted(dev) && !device_unmount(dev)) + // Unmount the device if it is actually mounted + if (table_search_by_dev(g_mtab, dev)) { + ctx = mnt_new_context(); + mnt_context_set_target(ctx, dev->node); + if (mnt_context_umount(ctx)) { + syslog(LOG_ERR, "Error while unmounting %s (%s)", dev->node, strerror(errno)); + mnt_free_context(ctx); return 0; + } + mnt_free_context(ctx); } - // ...and mount the new one if present - if (!device_mount(dev)) - return 0; + rmdir(dev->mp); + + (void)spawn_callback ("unmount", dev); return 1; } -// Strip the trailing slash. Brutally. -#define strip_slash(s) do { size_t l = strlen(s); if (l && s[l-1] == '/') s[l-1] = '\0'; } while(0) +// udev action callbacks void -handle_ipc_event (char *msg) +on_udev_add (struct udev_device *udev) { - struct device_t *device; + device_t *dev; + const char *dev_node; - // Keep it simple - switch (msg[0]) { - case 'R': // R for Remove - strip_slash(msg); + dev_node = udev_device_get_devnode(udev); - device = device_search(msg + 1); + // libmount < 2.21 doesn't support '+noauto', using 'noauto' instead + const char *noauto_opt = mnt_parse_version_string(LIBMOUNT_VERSION) < 2210 ? "noauto" : "+noauto"; + if (fstab_has_option(udev, noauto_opt)) + return; - if (device && device_is_mounted(device->udev)) - device_unmount(device->udev); + dev = device_new(udev); + if (!dev) { + fprintf(stderr, "device_new()\n"); + return; + } - break; + if (!device_mount(dev)) { + fprintf(stderr, "device_mount()\n"); + return; } + + g_hash_table_insert(g_dev_table, (char *)dev_node, dev); } void -check_registered_devices (void) +on_udev_remove (struct udev_device *udev) { - int j; + device_t *dev; + const char *dev_node; + + dev_node = udev_device_get_devnode(udev); - // Drop all the devices in the table that aren't mounted anymore - for (j = 0; j < MAX_DEVICES; j++) { - if (g_devices[j] && !device_is_mounted(g_devices[j]->udev)) - device_unmount(g_devices[j]->udev); + dev = g_hash_table_lookup(g_dev_table, dev_node); + if (!dev) + return; + + if (!device_unmount(dev)) { + fprintf(stderr, "device_unmount()"); + return; } + g_hash_table_remove(g_dev_table, dev_node); } void -device_list_clear (void) +on_udev_change (struct udev_device *udev) { - int j; + device_t *dev; + const char *dev_node; + + dev_node = udev_device_get_devnode(udev); + + dev = g_hash_table_lookup(g_dev_table, dev_node); + // Unmount the old media + if (dev && table_search_by_dev(g_mtab, dev)) { + if (device_unmount(dev)) + g_hash_table_remove(g_dev_table, dev_node); + } - for (j = 0; j < MAX_DEVICES; j++) { - if (g_devices[j]) - device_unmount(g_devices[j]->udev); - g_devices[j] = NULL; + if (!strcmp (udev_get_prop(udev, "ID_TYPE"), "cd") && + NULL != udev_get_prop(udev, "ID_CDROM_MEDIA")) { + device_mount(dev); } } +void +on_mtab_change (void) +{ + struct libmnt_table *new_tab; + struct libmnt_tabdiff *diff; + struct libmnt_fs *old, *new; + struct libmnt_iter *it; + device_t *dev; + int change_type; + + new_tab = mnt_new_table_from_file(MTAB_PATH); + if (!new_tab) { + fprintf(stderr, "Could not parse %s\n", MTAB_PATH); + return; + } + + diff = mnt_new_tabdiff(); + if (!diff) { + fprintf(stderr, "Could not diff the mtab\n"); + mnt_free_table(new_tab); + return; + } + + if (mnt_diff_tables(diff, g_mtab, new_tab) < 0) { + fprintf(stderr, "Could not diff the mtab\n"); + mnt_free_table(new_tab); + mnt_free_tabdiff(diff); + return; + } + + it = mnt_new_iter(MNT_ITER_BACKWARD); + + while (!mnt_tabdiff_next_change(diff, it, &new, &old, &change_type)) { + switch (change_type) { + case MNT_TABDIFF_UMOUNT: + fprintf(stderr, "UMOUNT : %s\n", mnt_fs_get_source(new)); + dev = device_search(mnt_fs_get_source(new)); + + if (dev) { + const char *ht_key = udev_device_get_devnode(dev->dev); + + g_hash_table_remove(g_dev_table, ht_key); + } + + break; + + case MNT_TABDIFF_REMOUNT: + case MNT_TABDIFF_MOVE: + fprintf(stderr, "REMOUNT/MOVE : %s\n", mnt_fs_get_source(old)); + dev = device_search(mnt_fs_get_source(old)); + + // Disown the device if it has been remounted + if (dev) { + const char *ht_key = udev_device_get_devnode(dev->dev); + + g_hash_table_remove(g_dev_table, ht_key); + } + + break; + } + } + + mnt_free_iter(it); + mnt_free_tabdiff(diff); + + // We're done diffing, replace the old table + mnt_free_table(g_mtab); + g_mtab = new_tab; +} + void mount_plugged_devices (struct udev *udev) { - const char *path; struct udev_enumerate *udev_enum; struct udev_list_entry *devices; struct udev_list_entry *entry; struct udev_device *dev; + const char *path; udev_enum = udev_enumerate_new(udev); udev_enumerate_add_match_subsystem(udev_enum, "block"); @@ -657,21 +698,14 @@ mount_plugged_devices (struct udev *udev) path = udev_list_entry_get_name(entry); dev = udev_device_new_from_syspath(udev, path); - if (!device_is_mounted(dev)) - device_mount(dev); + if (!table_search_by_udev(g_mtab, dev)) + on_udev_add(dev); + udev_device_unref(dev); } udev_enumerate_unref(udev_enum); } -struct libmnt_table * -update_mnt_table (const char *path, struct libmnt_table *old) -{ - if (old) - mnt_free_table(old); - return mnt_new_table_from_file(path); -} - void sig_handler (int signal) { @@ -713,69 +747,118 @@ daemonize (void) } int -isdir (const char *path) +ipc_serve (int client) { - struct stat st; + char msg_buffer[4096]; + ssize_t r; - if (stat(path, &st) < 0) + if (client < 0) return 0; - return S_ISDIR(st.st_mode); -} + r = read(client, msg_buffer, sizeof(msg_buffer)); + if (r < 0) { + perror("read"); + return 0; + } + msg_buffer[r] = '\0'; -int -fifo_open (int oldfd, const int mode) -{ - int fd; + if (r < 1) { + syslog(LOG_WARNING, "Malformed ipc command of length %zu", r); + return 0; + } - if (oldfd > 0) - close(oldfd); + switch (msg_buffer[0]) { + case 'R': { // Remove a mounted device + // Resolve the user-provided path + char *res = realpath(msg_buffer + 1, NULL); - if ((fd = open(FIFO_PATH, mode)) < 0) { - perror("open"); - return -1; + if (!res) { + perror("realpath"); + ipc_sendf(client, "-"); + return 0; + } + + device_t *dev = device_search(res); + free(res); + + int ok = 0; + // We don't have to check whether the device is mounted here since device_unmount takes care + // of stale device entries + if (dev) { + const char *ht_key = udev_device_get_devnode(dev->dev); + + if (device_unmount(dev)) { + g_hash_table_remove(g_dev_table, ht_key); + ok = 1; + } + } + + // Send the response back + ipc_sendf(client, "%c", ok? '+': '-'); + + return 1; + } + + case 'L': { // List the mounted devices + GHashTableIter it; + char *node; + device_t *dev; + + g_hash_table_iter_init(&it, g_dev_table); + + while (g_hash_table_iter_next(&it, (gpointer)&node, (gpointer)&dev)) { + // Print the volume information like this + // v + if (dev->type == VOLUME) { + ipc_sendf(client, "v \"%s\" \"%s\" \"%s\"\n", dev->node, dev->fs, dev->mp); + } + } + + // Send an empty line to mark the end of the list + ipc_sendf(client, "\n"); + + return 1; + } } - return fd; + syslog(LOG_WARNING, "Unrecognized ipc command %c", msg_buffer[0]); + + return 0; +} + +void device_clear_list () { + GHashTableIter it; + device_t *dev; + + g_hash_table_iter_init(&it, g_dev_table); + while (g_hash_table_iter_next(&it, NULL, (gpointer)&dev)) { + device_unmount(dev); + } + g_hash_table_destroy(g_dev_table); } int main (int argc, char *argv[]) { - struct udev *udev; + struct udev *udev; struct udev_monitor *monitor; - struct udev_device *device; - const char *action; - struct pollfd pollfd[4]; // udev / inotify watch / mtab / fifo - char rpath[PATH_MAX]; - int opt; - int daemon; - int notifyfd; - int watchd; - int ipcfd; + struct udev_device *device; + const char *action; + struct pollfd pollfd[4]; // udev / inotify watch / mtab / fifo + char *resolved; + int opt; + int daemon; + int ino_fd, ipc_fd, fstab_fd, mtab_fd; struct inotify_event event; - daemon = 0; + ino_fd = ipc_fd = fstab_fd = mtab_fd = -1; + daemon = 0; g_uid = -1; g_gid = -1; - watchd = -1; + g_callback_cmd = NULL; - while ((opt = getopt(argc, argv, "hdg:u:r:p:c:V")) != -1) { + while ((opt = getopt(argc, argv, "hdg:u:p:c:")) != -1) { switch (opt) { - case 'r': - ipcfd = fifo_open(-1, O_WRONLY); - // Could not open the pipe - if (ipcfd < 0) - return EXIT_FAILURE; - - if (!realpath(optarg, rpath)) - return EXIT_FAILURE; - - write(ipcfd, "R", 1); - write(ipcfd, rpath, strlen(rpath)); - close(ipcfd); - - return EXIT_SUCCESS; case 'd': daemon = 1; break; @@ -786,17 +869,14 @@ main (int argc, char *argv[]) g_uid = (int)strtoul(optarg, NULL, 10); break; case 'p': - g_mount_path = xstrdup(optarg); + g_mount_path = strdup(optarg); break; case 'c': - g_callback_path = xstrdup(optarg); - break; - case 'V': - g_verbose = 1; + g_callback_cmd = strdup(optarg); break; case 'h': printf("ldm "VERSION_STR"\n"); - printf("2011-2014 (C) The Lemon Man\n"); + printf("2011-2015 (C) The Lemon Man\n"); printf("%s [-d | -r | -g | -u | -p | -c | -h]\n", argv[0]); printf("\t-d Run ldm as a daemon\n"); printf("\t-r Removes a mounted device\n"); @@ -811,59 +891,60 @@ main (int argc, char *argv[]) } } + if (getuid() != 0) { + fprintf(stderr, "You have to run this program as root!\n"); + return EXIT_FAILURE; + } + + if (g_file_test(LOCK_PATH, G_FILE_TEST_EXISTS)) { + fprintf(stderr, "ldm is already running!\n"); + return EXIT_SUCCESS; + } + if (g_uid < 0 || g_gid < 0) { fprintf(stderr, "You must supply your gid/uid!\n"); return EXIT_FAILURE; } - // A not-so-safe default + if (g_callback_cmd && !g_file_test(g_callback_cmd, G_FILE_TEST_IS_EXECUTABLE)) { + fprintf(stderr, "The callback script isn't executable!\n"); + + free(g_callback_cmd); + g_callback_cmd = NULL; + } + if (!g_mount_path) - g_mount_path = xstrdup("/mnt"); + g_mount_path = strdup("/mnt"); + + // Resolve the mount point path before using it + resolved = realpath(g_mount_path, NULL); + free(g_mount_path); + g_mount_path = resolved; // Check anyways - if (!isdir(g_mount_path)) { + if (!g_file_test(g_mount_path, G_FILE_TEST_IS_DIR)) { fprintf(stderr, "The path %s doesn't name a folder or doesn't exist!\n", g_mount_path); - free(g_callback_path); + free(g_callback_cmd); free(g_mount_path); return EXIT_FAILURE; } - // Sanitize before use - strip_slash(g_mount_path); + ino_fd = inotify_init(); - if (getuid() != 0) { - fprintf(stderr, "You have to run this program as root!\n"); - return EXIT_FAILURE; - } - - if (access(LOCK_PATH, F_OK) != -1) { - fprintf(stderr, "ldm is already running!\n"); - return EXIT_SUCCESS; - } + if (ino_fd < 0) { + perror("inotify_init"); - notifyfd = inotify_init(); + free(g_callback_cmd); + free(g_mount_path); - if (notifyfd < 0) { - perror("inotify_init"); return EXIT_FAILURE; } // Create the ipc socket - unlink(FIFO_PATH); umask(0); - if (mkfifo(FIFO_PATH, 0666) < 0) { - perror("mkfifo"); - return EXIT_FAILURE; - } - - ipcfd = fifo_open(-1, O_RDONLY | O_NONBLOCK); - - if (ipcfd < 0) - return EXIT_FAILURE; - if (daemon && !daemonize()) { fprintf(stderr, "Could not spawn the daemon!\n"); return EXIT_FAILURE; @@ -878,7 +959,6 @@ main (int argc, char *argv[]) signal(SIGHUP , sig_handler); syslog(LOG_INFO, "ldm "VERSION_STR); - syslog(LOG_INFO, "Starting up..."); // Create the udev struct/monitor udev = udev_new(); @@ -897,37 +977,56 @@ main (int argc, char *argv[]) goto cleanup; } - // Clear the devices array - device_list_clear(); - - g_fstab = NULL; - g_mtab = NULL; + // Create the hashtable holding the mounted devices + g_dev_table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)device_free); - // The loop isn't active at this time so just do it by hand - if (!(g_fstab = update_mnt_table(FSTAB_PATH, g_fstab))) - goto cleanup; + // Load the tables + g_fstab = mnt_new_table_from_file(FSTAB_PATH); + g_mtab = mnt_new_table_from_file(MTAB_PATH); - if (!(g_mtab = update_mnt_table(MTAB_PATH, g_mtab))) + if (!g_fstab || !g_mtab) { + fprintf(stderr, "Could not parse the fstab/mtab\n"); goto cleanup; + } mount_plugged_devices(udev); - if (!(g_fstab = update_mnt_table(FSTAB_PATH, g_fstab))) + mnt_free_table(g_mtab); + g_mtab = mnt_new_table_from_file(MTAB_PATH); + + // Setup the fd to poll + fstab_fd = inotify_add_watch(ino_fd, FSTAB_PATH, IN_CLOSE_WRITE); + if (fstab_fd < 0) { + perror("inotify_add_watch"); + goto cleanup; + } + + mtab_fd = open(MTAB_PATH, O_RDONLY | O_NONBLOCK); + if (mtab_fd < 0) { + perror("open"); goto cleanup; + } - if (!(g_mtab = update_mnt_table(MTAB_PATH, g_mtab))) + ipc_fd = ipc_init(1); + if (ipc_fd < 0) goto cleanup; - watchd = inotify_add_watch(notifyfd, FSTAB_PATH, IN_CLOSE_WRITE); + if (listen(ipc_fd, 1) < 0) { + perror("listen"); + goto cleanup; + } // Register all the events pollfd[0].fd = udev_monitor_get_fd(monitor); pollfd[0].events = POLLIN; - pollfd[1].fd = notifyfd; + + pollfd[1].fd = ino_fd; pollfd[1].events = POLLIN; - pollfd[2].fd = open(MTAB_PATH, O_RDONLY | O_NONBLOCK); - pollfd[2].events = POLLERR; - pollfd[3].fd = ipcfd; + + pollfd[2].fd = mtab_fd; + pollfd[2].events = 0; + + pollfd[3].fd = ipc_fd; pollfd[3].events = POLLIN; syslog(LOG_INFO, "Entering the main loop"); @@ -947,68 +1046,63 @@ main (int argc, char *argv[]) action = udev_device_get_action(device); - if (!xstrcmp(action, "add")) - device_mount(device); - else if (!xstrcmp(action, "remove")) - device_unmount(device); - else if (!xstrcmp(action, "change")) - device_change(device); + if (!strcmp(action, "add")) { + on_udev_add(device); + } + else if (!strcmp(action, "remove")) { + on_udev_remove(device); + } + else if (!strcmp(action, "change")) { + on_udev_change(device); + } udev_device_unref(device); } // Incoming message on inotify socket if (pollfd[1].revents & POLLIN) { - read(pollfd[1].fd, &event, sizeof(struct inotify_event)); + read(ino_fd, &event, sizeof(struct inotify_event)); - if (mnt_table_parse_fstab(g_fstab, NULL) < 0) - break; + mnt_free_table(g_fstab); + g_fstab = mnt_new_table_from_file(MTAB_PATH); } // mtab change if (pollfd[2].revents & POLLERR) { - read(pollfd[2].fd, &event, sizeof(struct inotify_event)); + read(mtab_fd, &event, sizeof(struct inotify_event)); - if (!(g_mtab = update_mnt_table(MTAB_PATH, g_mtab))) - break; - - check_registered_devices(); + on_mtab_change(); } - // ipc message on the fifo + // client connection to the ipc socket if (pollfd[3].revents & POLLIN) { - int msg_len; - // Get the exact message length - if (ioctl(ipcfd, FIONREAD, &msg_len) < 0) { - syslog(LOG_ERR, "ipc:ioctl(FIONREAD) failed!"); - break; - } + int client; - char msg[msg_len]; - if (read(ipcfd, msg, msg_len) != msg_len) { - syslog(LOG_ERR, "ipc:read() failed!"); - break; + client = accept(ipc_fd, NULL, NULL); + if (client < 0) { + perror("accept"); + continue; } - msg[msg_len] = '\0'; - handle_ipc_event(msg); + if (!ipc_serve(client)) + syslog(LOG_ERR, "Could not serve a client due to an error"); - // The fifo is closed once the other end finishes sending the data so just reopen it. - pollfd[3].fd = ipcfd = fifo_open(ipcfd, O_RDONLY | O_NONBLOCK); + close(client); } } cleanup: - device_list_clear(); - free(g_callback_path); + device_clear_list (); + + free(g_callback_cmd); free(g_mount_path); // Do the cleanup - inotify_rm_watch(notifyfd, watchd); + inotify_rm_watch(ino_fd, fstab_fd); + + ipc_deinit (ipc_fd); - close(ipcfd); - close(notifyfd); - close(pollfd[2].fd); + close(ino_fd); + close(mtab_fd); - unlink(FIFO_PATH); unlink(LOCK_PATH); udev_monitor_unref(monitor); diff --git a/ldm.service b/ldm.service index fbff49a..6b0303c 100644 --- a/ldm.service +++ b/ldm.service @@ -3,7 +3,7 @@ Description=lightweight device mounter [Service] EnvironmentFile=/etc/ldm.conf -ExecStart=/usr/local/bin/ldm -u ${USER_UID} -g ${USER_GID} -p ${BASE_MOUNTPOINT} +ExecStart=/usr/bin/ldm -u ${USER_UID} -g ${USER_GID} -p ${BASE_MOUNTPOINT} KillMode=process [Install] diff --git a/ldmc.c b/ldmc.c new file mode 100644 index 0000000..8edfd8d --- /dev/null +++ b/ldmc.c @@ -0,0 +1,111 @@ +#include +#include +#include +#include +#include "ipc.h" + +int +ipc_send (int fd, char type, char *arg) +{ + char *fmt; + char *opt; + char tmp[4096]; + int r; + + if (fd < 0) + return 0; + + fmt = opt = NULL; + + switch (type) { + case 'r': + fmt = "R%s"; + opt = arg; + break; + + case 'l': + fmt = "L"; + opt = NULL; + break; + + default: + fprintf(stderr, "Unknown ipc command %c\n", type); + return 0; + } + + if (!ipc_sendf(fd, fmt, opt)) { + fprintf(stderr, "Could not communicate with the daemon! Is ldm running?\n"); + return EXIT_FAILURE; + } + + switch (type) { + case 'r': + // Receive the result code + r = ipc_read_one(fd); + + if (r == '+') + printf("Operation completed successfully\n"); + else + printf("The operation didn't complete successfully\n"); + + return (r == '+'); + + case 'l': + // Dump all the received lines to stdout + while (1) { + r = ipc_read_line(fd, tmp, sizeof(tmp)); + if (!r) + break; + printf("%s\n", tmp); + } + + return 1; + } + + return 0; +} + +void +usage () +{ + printf("ldmc %s\n", VERSION_STR); + printf("2015 (C) The Lemon Man\n"); + printf("\t-r Remove a mounted device\n"); + printf("\t-l List the mounted devices\n"); + printf("\t-h Show this help\n"); +} + +int +main (int argc, char **argv) +{ + int opt; + int ipc_fd; + + if (argc == 1) { + usage (); + return EXIT_FAILURE; + } + + while ((opt = getopt(argc, argv, "hlr:")) != -1) { + switch (opt) { + case 'l': + case 'r': + ipc_fd = ipc_init(0); + + // Could not open the ipc socket + if (ipc_fd < 0) + return EXIT_FAILURE; + + ipc_send(ipc_fd, (char)opt, optarg); + + close(ipc_fd); + break; + + default: + case 'h': + usage (); + } + } + + return EXIT_SUCCESS; +} diff --git a/ldmc.pod b/ldmc.pod new file mode 100644 index 0000000..c5d184e --- /dev/null +++ b/ldmc.pod @@ -0,0 +1,45 @@ +=head1 NAME + +ldmc - Send commands to a running ldm daemon instance + +=head1 SYNOPSIS + +I [-l] [-r I] [-h] + +=head1 DESCRIPTION + +Send commands to the running ldm daemon. See I for a list of the supported commands and +their arguments + +=head1 OPTIONS + +=over + +=item B<-r> I + +Ask the running daemon to unmount the device I, where I is either the device node or the +mountpoint. + +=item B<-l> + +List the mounted devices. Every entry is in the form + +C + +=item B<-h> + +Print a brief help and exit. + +=back + +=head1 SEE ALSO + +ldm(1) + +=head1 WWW + +L + +=head1 AUTHOR + +2011-2015 (C) The Lemon Man