chiark / gitweb /
3ff6cabbb01a7ed5e785502be10e14671a2569d0
[elogind.git] / udev / lib / libudev.c
1 /*
2  * libudev - interface to udev device information
3  *
4  * Copyright (C) 2008 Kay Sievers <kay.sievers@vrfy.org>
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <stddef.h>
23 #include <stdarg.h>
24 #include <unistd.h>
25 #include <errno.h>
26 #include <string.h>
27 #include <ctype.h>
28 #ifdef USE_SELINUX
29 #include <selinux/selinux.h>
30 #endif
31
32 #include "libudev.h"
33 #include "libudev-private.h"
34
35 struct udev {
36         int refcount;
37         void (*log_fn)(struct udev *udev,
38                        int priority, const char *file, int line, const char *fn,
39                        const char *format, va_list args);
40         void *userdata;
41         char *sys_path;
42         char *dev_path;
43         char *rules_path;
44         int log_priority;
45 #ifdef USE_SELINUX
46         int selinux_enabled;
47         security_context_t selinux_prev_scontext;
48 #endif
49         int run;
50 };
51
52 void udev_log(struct udev *udev,
53               int priority, const char *file, int line, const char *fn,
54               const char *format, ...)
55 {
56         va_list args;
57
58         if (priority > udev->log_priority)
59                 return;
60
61         va_start(args, format);
62         udev->log_fn(udev, priority, file, line, fn, format, args);
63         va_end(args);
64 }
65
66 static void log_stderr(struct udev *udev,
67                        int priority, const char *file, int line, const char *fn,
68                        const char *format, va_list args)
69 {
70         fprintf(stderr, "libudev: %s: ", fn);
71         vfprintf(stderr, format, args);
72 }
73
74 static void selinux_init(struct udev *udev)
75 {
76 #ifdef USE_SELINUX
77         /*
78          * record the present security context, for file-creation
79          * restoration creation purposes.
80          */
81         udev->selinux_enabled = (is_selinux_enabled() > 0);
82         info(udev, "selinux=%i\n", udev->selinux_enabled);
83         if (udev->selinux_enabled) {
84                 matchpathcon_init_prefix(NULL, udev_get_dev_path(udev));
85                 if (getfscreatecon(&udev->selinux_prev_scontext) < 0) {
86                         err(udev, "getfscreatecon failed\n");
87                         udev->selinux_prev_scontext = NULL;
88                 }
89         }
90 #endif
91 }
92
93 void *udev_get_userdata(struct udev *udev)
94 {
95         if (udev == NULL)
96                 return NULL;
97         return udev->userdata;
98 }
99
100 void udev_set_userdata(struct udev *udev, void *userdata)
101 {
102         if (udev == NULL)
103                 return;
104         udev->userdata = userdata;
105 }
106
107 static void selinux_exit(struct udev *udev)
108 {
109 #ifdef USE_SELINUX
110         if (udev->selinux_enabled) {
111                 freecon(udev->selinux_prev_scontext);
112                 udev->selinux_prev_scontext = NULL;
113         }
114 #endif
115 }
116
117 void udev_selinux_lsetfilecon(struct udev *udev, const char *file, unsigned int mode)
118 {
119 #ifdef USE_SELINUX
120         if (udev->selinux_enabled) {
121                 security_context_t scontext = NULL;
122
123                 if (matchpathcon(file, mode, &scontext) < 0) {
124                         err(udev, "matchpathcon(%s) failed\n", file);
125                         return;
126                 } 
127                 if (lsetfilecon(file, scontext) < 0)
128                         err(udev, "setfilecon %s failed: %s\n", file, strerror(errno));
129                 freecon(scontext);
130         }
131 #endif
132 }
133
134 void udev_selinux_setfscreatecon(struct udev *udev, const char *file, unsigned int mode)
135 {
136 #ifdef USE_SELINUX
137         if (udev->selinux_enabled) {
138                 security_context_t scontext = NULL;
139
140                 if (matchpathcon(file, mode, &scontext) < 0) {
141                         err(udev, "matchpathcon(%s) failed\n", file);
142                         return;
143                 }
144                 if (setfscreatecon(scontext) < 0)
145                         err(udev, "setfscreatecon %s failed: %s\n", file, strerror(errno));
146                 freecon(scontext);
147         }
148 #endif
149 }
150
151 void udev_selinux_resetfscreatecon(struct udev *udev)
152 {
153 #ifdef USE_SELINUX
154         if (udev->selinux_enabled) {
155                 if (setfscreatecon(udev->selinux_prev_scontext) < 0)
156                         err(udev, "setfscreatecon failed: %s\n", strerror(errno));
157         }
158 #endif
159 }
160
161 /**
162  * udev_new:
163  *
164  * Create udev library context.
165  *
166  * The initial refcount is 1, and needs to be decremented to
167  * release the ressources of the udev library context.
168  *
169  * Returns: a new udev library context
170  **/
171 struct udev *udev_new(void)
172 {
173         struct udev *udev;
174         const char *env;
175         char *config_file;
176         FILE *f;
177
178         udev = malloc(sizeof(struct udev));
179         if (udev == NULL)
180                 return NULL;
181         memset(udev, 0x00, (sizeof(struct udev)));
182         udev->refcount = 1;
183         udev->log_fn = log_stderr;
184         udev->log_priority = LOG_ERR;
185         udev->run = 1;
186         udev->dev_path = strdup(UDEV_PREFIX "/dev");
187         udev->sys_path = strdup("/sys");
188         config_file = strdup(SYSCONFDIR "/udev/udev.conf");
189         if (udev->dev_path == NULL ||
190             udev->sys_path == NULL ||
191             config_file == NULL)
192                 goto err;
193
194         /* settings by environment and config file */
195         env = getenv("SYSFS_PATH");
196         if (env != NULL) {
197                 free(udev->sys_path);
198                 udev->sys_path = strdup(env);
199                 util_remove_trailing_chars(udev->sys_path, '/');
200         }
201
202         env = getenv("UDEV_RUN");
203         if (env != NULL && strcmp(env, "0") == 0)
204                 udev->run = 0;
205
206         env = getenv("UDEV_CONFIG_FILE");
207         if (env != NULL) {
208                 free(config_file);
209                 config_file = strdup(env);
210                 util_remove_trailing_chars(config_file, '/');
211         }
212         if (config_file == NULL)
213                 goto err;
214         f = fopen(config_file, "r");
215         if (f != NULL) {
216                 char line[UTIL_LINE_SIZE];
217                 int line_nr = 0;
218
219                 while (fgets(line, sizeof(line), f)) {
220                         size_t len;
221                         char *key;
222                         char *val;
223
224                         line_nr++;
225
226                         /* find key */
227                         key = line;
228                         while (isspace(key[0]))
229                                 key++;
230
231                         /* comment or empty line */
232                         if (key[0] == '#' || key[0] == '\0')
233                                 continue;
234
235                         /* split key/value */
236                         val = strchr(key, '=');
237                         if (val == NULL) {
238                                 err(udev, "missing <key>=<value> in '%s'[%i], skip line\n", config_file, line_nr);
239                                 continue;
240                         }
241                         val[0] = '\0';
242                         val++;
243
244                         /* find value */
245                         while (isspace(val[0]))
246                                 val++;
247
248                         /* terminate key */
249                         len = strlen(key);
250                         if (len == 0)
251                                 continue;
252                         while (isspace(key[len-1]))
253                                 len--;
254                         key[len] = '\0';
255
256                         /* terminate value */
257                         len = strlen(val);
258                         if (len == 0)
259                                 continue;
260                         while (isspace(val[len-1]))
261                                 len--;
262                         val[len] = '\0';
263
264                         if (len == 0)
265                                 continue;
266
267                         /* unquote */
268                         if (val[0] == '"' || val[0] == '\'') {
269                                 if (val[len-1] != val[0]) {
270                                         err(udev, "inconsistent quoting in '%s'[%i], skip line\n", config_file, line_nr);
271                                         continue;
272                                 }
273                                 val[len-1] = '\0';
274                                 val++;
275                         }
276
277                         if (strcasecmp(key, "udev_log") == 0) {
278                                 udev->log_priority = util_log_priority(val);
279                                 continue;
280                         }
281                         if (strcasecmp(key, "udev_root") == 0) {
282                                 free(udev->dev_path);
283                                 udev->dev_path = strdup(val);
284                                 util_remove_trailing_chars(udev->dev_path, '/');
285                                 continue;
286                         }
287                         if (strcasecmp(key, "udev_rules") == 0) {
288                                 free(udev->rules_path);
289                                 udev->rules_path = strdup(val);
290                                 util_remove_trailing_chars(udev->rules_path, '/');
291                                 continue;
292                         }
293                 }
294                 fclose(f);
295         }
296
297         env = getenv("UDEV_ROOT");
298         if (env != NULL) {
299                 free(udev->dev_path);
300                 udev->dev_path = strdup(env);
301                 util_remove_trailing_chars(udev->dev_path, '/');
302         }
303
304         env = getenv("UDEV_LOG");
305         if (env != NULL)
306                 udev->log_priority = util_log_priority(env);
307
308         if (udev->dev_path == NULL || udev->sys_path == NULL)
309                 goto err;
310         selinux_init(udev);
311         info(udev, "context %p created\n", udev);
312         info(udev, "log_priority=%d\n", udev->log_priority);
313         info(udev, "config_file='%s'\n", config_file);
314         info(udev, "dev_path='%s'\n", udev->dev_path);
315         info(udev, "sys_path='%s'\n", udev->sys_path);
316         if (udev->rules_path != NULL)
317                 info(udev, "rules_path='%s'\n", udev->rules_path);
318         free(config_file);
319         return udev;
320 err:
321         free(config_file);
322         err(udev, "context creation failed\n");
323         udev_unref(udev);
324         return NULL;
325 }
326
327 /**
328  * udev_ref:
329  * @udev: udev library context
330  *
331  * Take a reference of the udev library context.
332  *
333  * Returns: the passed udev library context
334  **/
335 struct udev *udev_ref(struct udev *udev)
336 {
337         if (udev == NULL)
338                 return NULL;
339         udev->refcount++;
340         return udev;
341 }
342
343 /**
344  * udev_unref:
345  * @udev: udev library context
346  *
347  * Drop a reference of the udev library context. If the refcount
348  * reaches zero, the ressources of the context will be released.
349  *
350  **/
351 void udev_unref(struct udev *udev)
352 {
353         if (udev == NULL)
354                 return;
355         udev->refcount--;
356         if (udev->refcount > 0)
357                 return;
358         selinux_exit(udev);
359         free(udev->dev_path);
360         free(udev->sys_path);
361         free(udev->rules_path);
362         info(udev, "context %p released\n", udev);
363         free(udev);
364 }
365
366 /**
367  * udev_set_log_fn:
368  * @udev: udev library context
369  * @log_fn: function to be called for logging messages
370  *
371  * The built-in logging, which writes to stderr if the
372  * LIBUDEV_DEBUG environment variable is set, can be
373  * overridden by a custom function, to plug log messages
374  * into the users logging functionality.
375  *
376  **/
377 void udev_set_log_fn(struct udev *udev,
378                      void (*log_fn)(struct udev *udev,
379                                     int priority, const char *file, int line, const char *fn,
380                                     const char *format, va_list args))
381 {
382         udev->log_fn = log_fn;
383         info(udev, "custom logging function %p registered\n", udev);
384 }
385
386 int udev_get_log_priority(struct udev *udev)
387 {
388         return udev->log_priority;
389 }
390
391 void udev_set_log_priority(struct udev *udev, int priority)
392 {
393         udev->log_priority = priority;
394 }
395
396 const char *udev_get_rules_path(struct udev *udev)
397 {
398         return udev->rules_path;
399 }
400
401 int udev_get_run(struct udev *udev)
402 {
403         return udev->run;
404 }
405
406 /**
407  * udev_get_sys_path:
408  * @udev: udev library context
409  *
410  * Retrieve the sysfs mount point. The default is "/sys". For
411  * testing purposes, it can be overridden with the environment
412  * variable SYSFS_PATH.
413  *
414  * Returns: the sys mount point
415  **/
416 const char *udev_get_sys_path(struct udev *udev)
417 {
418         if (udev == NULL)
419                 return NULL;
420         return udev->sys_path;
421 }
422
423 /**
424  * udev_get_dev_path:
425  * @udev: udev library context
426  *
427  * Retrieve the device directory path. The default value is "/dev",
428  * the actual value may be overridden in the udev configuration
429  * file.
430  *
431  * Returns: the device directory path
432  **/
433 const char *udev_get_dev_path(struct udev *udev)
434 {
435         if (udev == NULL)
436                 return NULL;
437         return udev->dev_path;
438 }