summaryrefslogtreecommitdiff
path: root/udev/lib/libudev-monitor.c
diff options
context:
space:
mode:
Diffstat (limited to 'udev/lib/libudev-monitor.c')
-rw-r--r--udev/lib/libudev-monitor.c216
1 files changed, 216 insertions, 0 deletions
diff --git a/udev/lib/libudev-monitor.c b/udev/lib/libudev-monitor.c
new file mode 100644
index 0000000000..fbb21d04ea
--- /dev/null
+++ b/udev/lib/libudev-monitor.c
@@ -0,0 +1,216 @@
+/*
+ * libudev - interface to udev device information
+ *
+ * Copyright (C) 2008 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "libudev.h"
+#include "libudev-private.h"
+#include "../udev.h"
+
+struct udev_monitor {
+ struct udev *udev;
+ int refcount;
+ int socket;
+};
+
+struct udev_monitor *udev_monitor_new_from_socket(struct udev *udev, const char *socket_path)
+{
+ struct udev_monitor *udev_monitor;
+ struct sockaddr_un saddr;
+ socklen_t addrlen;
+ const int on = 1;
+
+ if (udev == NULL)
+ return NULL;
+ if (socket_path == NULL)
+ return NULL;
+ udev_monitor = malloc(sizeof(struct udev_monitor));
+ if (udev_monitor == NULL)
+ return NULL;
+ memset(udev_monitor, 0x00, sizeof(struct udev_monitor));
+ udev_monitor->refcount = 1;
+ udev_monitor->udev = udev;
+
+ memset(&saddr, 0x00, sizeof(saddr));
+ saddr.sun_family = AF_LOCAL;
+ strcpy(saddr.sun_path, socket_path);
+ addrlen = offsetof(struct sockaddr_un, sun_path) + strlen(saddr.sun_path);
+
+ /* translate leading '@' to abstract namespace */
+ if (saddr.sun_path[0] == '@')
+ saddr.sun_path[0] = '\0';
+
+ udev_monitor->socket = socket(AF_LOCAL, SOCK_DGRAM, 0);
+ if (udev_monitor->socket == -1) {
+ log_err(udev, "error getting socket: %s\n", strerror(errno));
+ free(udev_monitor);
+ return NULL;
+ }
+
+ if (bind(udev_monitor->socket, (struct sockaddr *) &saddr, addrlen) < 0) {
+ log_err(udev, "bind failed: %s\n", strerror(errno));
+ close(udev_monitor->socket);
+ free(udev_monitor);
+ return NULL;
+ }
+
+ /* enable receiving of the sender credentials */
+ setsockopt(udev_monitor->socket, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
+ log_info(udev_monitor->udev, "udev_monitor: %p created\n", udev_monitor);
+
+ return udev_monitor;
+}
+
+struct udev_monitor *udev_monitor_ref(struct udev_monitor *udev_monitor)
+{
+ if (udev_monitor == NULL)
+ return NULL;
+ udev_monitor->refcount++;
+ return udev_monitor;
+}
+
+void udev_monitor_unref(struct udev_monitor *udev_monitor)
+{
+ if (udev_monitor == NULL)
+ return;
+ udev_monitor->refcount--;
+ if (udev_monitor->refcount > 0)
+ return;
+ close(udev_monitor->socket);
+ log_info(udev_monitor->udev, "udev_monitor: %p released\n", udev_monitor);
+ free(udev_monitor);
+}
+
+struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor)
+{
+ if (udev_monitor == NULL)
+ return NULL;
+ return udev_monitor->udev;
+}
+
+int udev_monitor_get_fd(struct udev_monitor *udev_monitor)
+{
+ if (udev_monitor == NULL)
+ return -1;
+ return udev_monitor->socket;
+}
+
+struct udev_device *udev_monitor_get_device(struct udev_monitor *udev_monitor)
+{
+ struct udev_device *udev_device;
+ struct msghdr smsg;
+ struct cmsghdr *cmsg;
+ struct iovec iov;
+ struct ucred *cred;
+ char cred_msg[CMSG_SPACE(sizeof(struct ucred))];
+ char buf[4096];
+ size_t bufpos;
+
+ if (udev_monitor == NULL)
+ return NULL;
+ memset(buf, 0x00, sizeof(buf));
+ iov.iov_base = &buf;
+ iov.iov_len = sizeof(buf);
+ memset (&smsg, 0x00, sizeof(struct msghdr));
+ smsg.msg_iov = &iov;
+ smsg.msg_iovlen = 1;
+ smsg.msg_control = cred_msg;
+ smsg.msg_controllen = sizeof(cred_msg);
+
+ if (recvmsg(udev_monitor->socket, &smsg, 0) < 0) {
+ if (errno != EINTR)
+ log_info(udev_monitor->udev, "unable to receive message");
+ return NULL;
+ }
+ cmsg = CMSG_FIRSTHDR(&smsg);
+ cred = (struct ucred *)CMSG_DATA (cmsg);
+
+ if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) {
+ log_info(udev_monitor->udev, "no sender credentials received, message ignored");
+ return NULL;
+ }
+
+ if (cred->uid != 0) {
+ log_info(udev_monitor->udev, "sender uid=%d, message ignored", cred->uid);
+ return NULL;
+ }
+
+ /* skip header */
+ bufpos = strlen(buf) + 1;
+ if (bufpos < sizeof("a@/d") || bufpos >= sizeof(buf)) {
+ log_info(udev_monitor->udev, "invalid message length");
+ return NULL;
+ }
+
+ /* check message header */
+ if (strstr(buf, "@/") == NULL) {
+ log_info(udev_monitor->udev, "unrecognized message header");
+ return NULL;
+ }
+
+ udev_device = device_init(udev_monitor->udev);
+ if (udev_device == NULL) {
+ return NULL;
+ }
+
+ while (bufpos < sizeof(buf)) {
+ char *key;
+ size_t keylen;
+
+ key = &buf[bufpos];
+ keylen = strlen(key);
+ if (keylen == 0)
+ break;
+ bufpos += keylen + 1;
+
+ if (strncmp(key, "DEVPATH=", 8) == 0) {
+ udev_device->devpath = strdup(&key[8]);
+ } else if (strncmp(key, "SUBSYSTEM=", 10) == 0) {
+ udev_device->subsystem = strdup(&key[10]);
+ } else if (strncmp(key, "DEVNAME=", 8) == 0) {
+ udev_device->devname = strdup(&key[8]);
+ } else if (strncmp(key, "DEVLINKS=", 9) == 0) {
+ char *slink = &key[9];
+ char *next = strchr(slink, ' ');
+
+ while (next != NULL) {
+ next[0] = '\0';
+ name_list_add(&udev_device->link_list, slink, 0);
+ slink = &next[1];
+ next = strchr(slink, ' ');
+ }
+ if (slink[0] != '\0')
+ name_list_add(&udev_device->link_list, slink, 0);
+ }
+ name_list_add(&udev_device->env_list, key, 0);
+ }
+
+ return udev_device;
+}