├── .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 |
--------------------------------------------------------------------------------