|
22 | 22 | SOFTWARE.
|
23 | 23 | */
|
24 | 24 |
|
25 |
| -#ifndef _LIBUE_H |
26 |
| -#define _LIBUE_H |
| 25 | +#ifndef __LIBUE_H |
| 26 | +#define __LIBUE_H |
27 | 27 |
|
| 28 | +#include <stdio.h> |
| 29 | +#include <stdlib.h> |
| 30 | +#include <string.h> |
28 | 31 | #include <sys/poll.h>
|
29 | 32 | #include <sys/socket.h>
|
30 |
| -#include <string.h> |
| 33 | +#include <sys/types.h> |
| 34 | +#include <syslog.h> |
| 35 | +#include <unistd.h> |
| 36 | + |
31 | 37 | #include <linux/netlink.h>
|
32 |
| -#include <stdio.h> |
33 | 38 |
|
34 |
| -#define LIBUE_VERSION_MAJOR "0" |
35 |
| -#define LIBUE_VERSION_MINOR "1.0" |
36 |
| -#define LIBUE_VERSION LIBUE_VERSION_MAJOR "." LIBUE_VERSION_MINOR |
| 39 | +#define LIBUE_VERSION_MAJOR "0" |
| 40 | +#define LIBUE_VERSION_MINOR "3.0" |
| 41 | +#define LIBUE_VERSION LIBUE_VERSION_MAJOR "." LIBUE_VERSION_MINOR |
37 | 42 | #define LIBUE_VERSION_NUMBER 10000
|
38 |
| -#ifndef DEBUG |
39 |
| - #define DEBUG 0 |
| 43 | + |
| 44 | +// Enable debug logging in Debug builds |
| 45 | +#ifdef DEBUG |
| 46 | +# define UE_DEBUG 1 |
| 47 | +#else |
| 48 | +# define UE_DEBUG 0 |
40 | 49 | #endif
|
41 |
| -#define UE_DEBUG(...) \ |
42 |
| - do { if (DEBUG) fprintf(stderr, __VA_ARGS__); } while(0) |
43 | 50 |
|
44 |
| -struct uevent_listener { |
45 |
| - struct pollfd pfd; |
46 |
| - struct sockaddr_nl nls; |
| 51 | +// We don't log to syslog |
| 52 | +#define UE_SYSLOG 0 |
| 53 | + |
| 54 | +// Logging helpers |
| 55 | +#define UE_LOG(prio, fmt, ...) \ |
| 56 | + ({ \ |
| 57 | + if (UE_SYSLOG) { \ |
| 58 | + syslog(prio, fmt, ##__VA_ARGS__); \ |
| 59 | + } else { \ |
| 60 | + fprintf(stderr, fmt "\n", ##__VA_ARGS__); \ |
| 61 | + } \ |
| 62 | + }) |
| 63 | + |
| 64 | +// Same, but with __PRETTY_FUNCTION__:__LINE__ right before fmt |
| 65 | +#define UE_PFLOG(prio, fmt, ...) \ |
| 66 | + ({ \ |
| 67 | + if ((prio != LOG_DEBUG) || (prio == LOG_DEBUG && UE_DEBUG)) { \ |
| 68 | + UE_LOG(prio, "[%s:%d] " fmt, __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__); \ |
| 69 | + } \ |
| 70 | + }) |
| 71 | + |
| 72 | +struct uevent_listener |
| 73 | +{ |
| 74 | + struct pollfd pfd; |
| 75 | + struct sockaddr_nl nls; |
47 | 76 | };
|
48 | 77 |
|
49 |
| -#define ERR_LISTENER_NOT_ROOT -1 |
50 |
| -#define ERR_LISTENER_BIND -2 |
51 |
| -#define ERR_LISTENER_POLL -3 |
52 |
| -#define ERR_LISTENER_RECV -4 |
53 |
| -#define ERR_PARSE_UDEV -1 |
54 |
| -#define ERR_PARSE_INVALID_HDR -2 |
55 |
| -#define UE_STR_EQ(str, const_str) (strncmp((str), (const_str), sizeof(const_str)-1) == 0) |
56 |
| - |
57 |
| -enum uevent_action { |
58 |
| - UEVENT_ACTION_INVALID = 0, |
59 |
| - UEVENT_ACTION_ADD, |
60 |
| - UEVENT_ACTION_REMOVE, |
61 |
| - UEVENT_ACTION_CHANGE, |
62 |
| - UEVENT_ACTION_MOVE, |
63 |
| - UEVENT_ACTION_ONLINE, |
64 |
| - UEVENT_ACTION_OFFLINE, |
| 78 | +#define ERR_LISTENER_NOT_ROOT -1 |
| 79 | +#define ERR_LISTENER_BIND -2 |
| 80 | +#define ERR_LISTENER_POLL -3 |
| 81 | +#define ERR_LISTENER_RECV -4 |
| 82 | +#define ERR_PARSE_UDEV -1 |
| 83 | +#define ERR_PARSE_INVALID_HDR -2 |
| 84 | +#define UE_STR_EQ(str, const_str) (strncmp((str), (const_str), sizeof(const_str) - 1U) == 0) |
| 85 | + |
| 86 | +enum uevent_action |
| 87 | +{ |
| 88 | + UEVENT_ACTION_INVALID = 0, |
| 89 | + UEVENT_ACTION_ADD, |
| 90 | + UEVENT_ACTION_REMOVE, |
| 91 | + UEVENT_ACTION_CHANGE, |
| 92 | + UEVENT_ACTION_MOVE, |
| 93 | + UEVENT_ACTION_ONLINE, |
| 94 | + UEVENT_ACTION_OFFLINE, |
65 | 95 | };
|
66 | 96 |
|
67 |
| -struct uevent { |
68 |
| - enum uevent_action action; |
69 |
| - char *devpath; |
70 |
| - char buf[4096]; |
71 |
| - size_t buflen; |
72 |
| -}; |
| 97 | +static const char* uev_action_str[] = { "invalid", "add", "remove", "change", "move", "online", "offline" }; |
73 | 98 |
|
74 |
| -const char* uev_action_str[] = { "invalid", "add", "remove", "change", "move", "online", "offline" }; |
| 99 | +struct uevent |
| 100 | +{ |
| 101 | + enum uevent_action action; |
| 102 | + char* devpath; |
| 103 | + char buf[PIPE_BUF]; |
| 104 | + size_t buflen; |
| 105 | +}; |
75 | 106 |
|
76 | 107 | /*
|
77 | 108 | * Reference for uevent format:
|
78 | 109 | * https://www.kernel.org/doc/pending/hotplug.txt
|
| 110 | + * https://stackoverflow.com/a/22813783 |
79 | 111 | */
|
80 |
| -int ue_parse_event_msg(struct uevent *uevp, size_t buflen) { |
81 |
| - /* skip udev events */ |
82 |
| - if (memcmp(uevp->buf, "libudev", 8) == 0) return ERR_PARSE_UDEV; |
83 |
| - |
84 |
| - /* validate message header */ |
85 |
| - int body_start = strlen(uevp->buf) + 1; |
86 |
| - if ((size_t)body_start < sizeof("a@/d") |
87 |
| - || body_start >= buflen |
88 |
| - || (strstr(uevp->buf, "@/") == NULL)) { |
89 |
| - return ERR_PARSE_INVALID_HDR; |
90 |
| - } |
91 |
| - |
92 |
| - int i = body_start; |
93 |
| - char *cur_line; |
94 |
| - uevp->buflen = buflen; |
95 |
| - |
96 |
| - while (i < buflen) { |
97 |
| - cur_line = uevp->buf + i; |
98 |
| - UE_DEBUG("line: '%s'\n", cur_line); |
99 |
| - if (UE_STR_EQ(cur_line, "ACTION")) { |
100 |
| - cur_line += sizeof("ACTION"); |
101 |
| - if (UE_STR_EQ(cur_line, "add")) { |
102 |
| - uevp->action = UEVENT_ACTION_ADD; |
103 |
| - } else if (UE_STR_EQ(cur_line, "change")) { |
104 |
| - uevp->action = UEVENT_ACTION_CHANGE; |
105 |
| - } else if (UE_STR_EQ(cur_line, "remove")) { |
106 |
| - uevp->action = UEVENT_ACTION_REMOVE; |
107 |
| - } else if (UE_STR_EQ(cur_line, "move")) { |
108 |
| - uevp->action = UEVENT_ACTION_MOVE; |
109 |
| - } else if (UE_STR_EQ(cur_line, "online")) { |
110 |
| - uevp->action = UEVENT_ACTION_ONLINE; |
111 |
| - } else if (UE_STR_EQ(cur_line, "offline")) { |
112 |
| - uevp->action = UEVENT_ACTION_OFFLINE; |
113 |
| - } |
114 |
| - } else if (UE_STR_EQ(cur_line, "DEVPATH")) { |
115 |
| - uevp->devpath = cur_line + sizeof("DEVPATH"); |
116 |
| - } |
117 |
| - /* proceed to next line */ |
118 |
| - i += strlen(cur_line) + 1; |
119 |
| - } |
120 |
| - return 0; |
| 112 | +static int |
| 113 | + ue_parse_event_msg(struct uevent* uevp, size_t buflen) |
| 114 | +{ |
| 115 | + /* skip udev events */ |
| 116 | + if (memcmp(uevp->buf, "libudev", 7) == 0 || memcmp(uevp->buf, "udev", 4) == 0) { |
| 117 | + return ERR_PARSE_UDEV; |
| 118 | + } |
| 119 | + |
| 120 | + /* validate message header */ |
| 121 | + size_t body_start = strlen(uevp->buf) + 1U; |
| 122 | + if (body_start < sizeof("a@/d") || body_start >= buflen || (strstr(uevp->buf, "@/") == NULL)) { |
| 123 | + return ERR_PARSE_INVALID_HDR; |
| 124 | + } |
| 125 | + |
| 126 | + size_t i = body_start; |
| 127 | + char* cur_line; |
| 128 | + uevp->buflen = buflen; |
| 129 | + |
| 130 | + while (i < buflen) { |
| 131 | + cur_line = uevp->buf + i; |
| 132 | + UE_PFLOG(LOG_DEBUG, "line: `%s`", cur_line); |
| 133 | + char* p = cur_line; |
| 134 | + if (UE_STR_EQ(p, "ACTION")) { |
| 135 | + p += sizeof("ACTION"); |
| 136 | + if (UE_STR_EQ(p, "add")) { |
| 137 | + uevp->action = UEVENT_ACTION_ADD; |
| 138 | + } else if (UE_STR_EQ(p, "change")) { |
| 139 | + uevp->action = UEVENT_ACTION_CHANGE; |
| 140 | + } else if (UE_STR_EQ(p, "remove")) { |
| 141 | + uevp->action = UEVENT_ACTION_REMOVE; |
| 142 | + } else if (UE_STR_EQ(p, "move")) { |
| 143 | + uevp->action = UEVENT_ACTION_MOVE; |
| 144 | + } else if (UE_STR_EQ(p, "online")) { |
| 145 | + uevp->action = UEVENT_ACTION_ONLINE; |
| 146 | + } else if (UE_STR_EQ(p, "offline")) { |
| 147 | + uevp->action = UEVENT_ACTION_OFFLINE; |
| 148 | + } |
| 149 | + } else if (UE_STR_EQ(p, "DEVPATH")) { |
| 150 | + uevp->devpath = p + sizeof("DEVPATH"); |
| 151 | + } |
| 152 | + /* proceed to next line */ |
| 153 | + i += strlen(cur_line) + 1U; |
| 154 | + } |
| 155 | + return EXIT_SUCCESS; |
121 | 156 | }
|
122 | 157 |
|
123 |
| -inline void ue_dump_event(struct uevent *uevp) { |
124 |
| - printf("%s %s\n", uev_action_str[uevp->action], uevp->devpath); |
| 158 | +static inline void |
| 159 | + ue_dump_event(struct uevent* uevp) |
| 160 | +{ |
| 161 | + printf("%s %s\n", uev_action_str[uevp->action], uevp->devpath); |
125 | 162 | }
|
126 | 163 |
|
127 |
| -inline void ue_reset_event(struct uevent *uevp) { |
128 |
| - uevp->action = UEVENT_ACTION_INVALID; |
129 |
| - uevp->buflen = 0; |
130 |
| - uevp->devpath = NULL; |
| 164 | +static inline void |
| 165 | + ue_reset_event(struct uevent* uevp) |
| 166 | +{ |
| 167 | + uevp->action = UEVENT_ACTION_INVALID; |
| 168 | + uevp->devpath = NULL; |
| 169 | + uevp->buflen = 0U; |
131 | 170 | }
|
132 | 171 |
|
133 |
| -int ue_init_listener(struct uevent_listener *l) { |
134 |
| - memset(&l->nls, 0, sizeof(struct sockaddr_nl)); |
135 |
| - l->nls.nl_family = AF_NETLINK; |
136 |
| - l->nls.nl_pid = getpid(); |
137 |
| - l->nls.nl_groups = -1; |
138 |
| - |
139 |
| - l->pfd.events = POLLIN; |
140 |
| - l->pfd.fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); |
141 |
| - if (l->pfd.fd == -1) return ERR_LISTENER_NOT_ROOT; |
142 |
| - |
143 |
| - if (bind(l->pfd.fd, (void *)&(l->nls), sizeof(struct sockaddr_nl))) { |
144 |
| - return ERR_LISTENER_BIND; |
145 |
| - } |
| 172 | +static int |
| 173 | + ue_init_listener(struct uevent_listener* l) |
| 174 | +{ |
| 175 | + memset(&l->nls, 0, sizeof(struct sockaddr_nl)); |
| 176 | + l->nls.nl_family = AF_NETLINK; |
| 177 | + // NOTE: It's actually a pid_t in non-braindead kernels... |
| 178 | +#pragma GCC diagnostic push |
| 179 | +#pragma GCC diagnostic ignored "-Wsign-conversion" |
| 180 | + l->nls.nl_pid = getpid(); |
| 181 | +#pragma GCC diagnostic pop |
| 182 | + l->nls.nl_groups = -1U; |
| 183 | + |
| 184 | + l->pfd.events = POLLIN; |
| 185 | + l->pfd.fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); |
| 186 | + if (l->pfd.fd == -1) { |
| 187 | + UE_PFLOG(LOG_CRIT, "socket: %m"); |
| 188 | + return ERR_LISTENER_NOT_ROOT; |
| 189 | + } |
| 190 | + |
| 191 | + if (bind(l->pfd.fd, (struct sockaddr*) &(l->nls), sizeof(struct sockaddr_nl))) { |
| 192 | + UE_PFLOG(LOG_CRIT, "bind: %m"); |
| 193 | + return ERR_LISTENER_BIND; |
| 194 | + } |
| 195 | + |
| 196 | + return EXIT_SUCCESS; |
| 197 | +} |
146 | 198 |
|
147 |
| - return 0; |
| 199 | +static int |
| 200 | + ue_wait_for_event(struct uevent_listener* l, struct uevent* uevp) |
| 201 | +{ |
| 202 | + while (poll(&(l->pfd), 1, -1) != -1) { |
| 203 | + ue_reset_event(uevp); |
| 204 | + ssize_t len = recv(l->pfd.fd, uevp->buf, sizeof(uevp->buf), MSG_DONTWAIT); |
| 205 | + if (len == -1) { |
| 206 | + UE_PFLOG(LOG_CRIT, "recv: %m"); |
| 207 | + return ERR_LISTENER_RECV; |
| 208 | + } |
| 209 | + int rc = ue_parse_event_msg(uevp, (size_t) len); |
| 210 | + if (rc == EXIT_SUCCESS) { |
| 211 | + UE_PFLOG(LOG_DEBUG, "uevent successfully parsed"); |
| 212 | + return EXIT_SUCCESS; |
| 213 | + } else if (rc == ERR_PARSE_UDEV) { |
| 214 | + UE_PFLOG(LOG_DEBUG, "skipped udev uevent: `%s`", uevp->buf); |
| 215 | + } else if (rc == ERR_PARSE_INVALID_HDR) { |
| 216 | + UE_PFLOG(LOG_DEBUG, "skipped malformed uevent: `%s`", uevp->buf); |
| 217 | + } else { |
| 218 | + UE_PFLOG(LOG_DEBUG, "skipped unsupported uevent: `%s`", uevp->buf); |
| 219 | + } |
| 220 | + } |
| 221 | + UE_PFLOG(LOG_CRIT, "poll: %m"); |
| 222 | + return ERR_LISTENER_POLL; |
148 | 223 | }
|
149 | 224 |
|
150 |
| -int ue_wait_for_event(struct uevent_listener *l, struct uevent *uevp) { |
151 |
| - ue_reset_event(uevp); |
152 |
| - while (poll(&(l->pfd), 1, -1) != -1) { |
153 |
| - int i, len = recv(l->pfd.fd, uevp->buf, sizeof(uevp->buf), MSG_DONTWAIT); |
154 |
| - if (len == -1) return ERR_LISTENER_RECV; |
155 |
| - if (ue_parse_event_msg(uevp, len) == 0) { |
156 |
| - UE_DEBUG("uevent successfully parsed\n"); |
157 |
| - return 0; |
158 |
| - } else { |
159 |
| - UE_DEBUG("skipped unsupported uevent:\n%s\n", uevp->buf); |
160 |
| - } |
161 |
| - } |
162 |
| - return ERR_LISTENER_POLL; |
| 225 | +static int |
| 226 | + ue_destroy_listener(struct uevent_listener* l) |
| 227 | +{ |
| 228 | + if (l->pfd.fd != -1) { |
| 229 | + return close(l->pfd.fd); |
| 230 | + } else { |
| 231 | + return EXIT_SUCCESS; |
| 232 | + } |
163 | 233 | }
|
164 | 234 |
|
165 |
| -#endif |
| 235 | +#endif // __LIBUE_H |
0 commit comments