├── .ci └── build.py ├── README.md ├── circle.yml └── libue.h /.ci/build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | 4 | from __future__ import print_function 5 | 6 | import os 7 | from subprocess import check_call 8 | 9 | 10 | with open('../README.md') as readme: 11 | found_code = False 12 | code_lines = [] 13 | for line in readme: 14 | if '```C\n' == line: 15 | found_code = True 16 | continue 17 | elif '```\n' == line: 18 | break 19 | else: 20 | if found_code: 21 | code_lines.append(line) 22 | sample_code = ''.join(code_lines) 23 | 24 | print('Extracted sample code:') 25 | print(sample_code) 26 | 27 | tmp_file = 'libue-test.c' 28 | 29 | with open(tmp_file, 'w') as fd: 30 | fd.write(sample_code) 31 | 32 | print('Building sample code...') 33 | check_call(['gcc', '-I../', '-o', 'libue-test', tmp_file]) 34 | 35 | os.remove(tmp_file) 36 | os.remove('libue-test') 37 | 38 | print('Done.') 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | libue [![Build Status](https://circleci.com/gh/houqp/libue.svg?style=shield)](https://circleci.com/gh/houqp/libue) 2 | ===== 3 | 4 | Zero dependency minimal library for interacting with Linux hot-plug events. 5 | 6 | 7 | Installation 8 | ------------ 9 | 10 | Just drop the header file into your C project. 11 | 12 | 13 | Usage 14 | ----- 15 | 16 | ```C 17 | #include "libue.h" 18 | 19 | int main() { 20 | struct uevent_listener listener; 21 | struct uevent uev; 22 | int re; 23 | 24 | re = ue_init_listener(&listener); 25 | if (re < 0) { 26 | fprintf(stderr, "Failed to initilize libue listener, err: %d\n", re); 27 | return -1; 28 | } 29 | 30 | /* blocking call */ 31 | while ((re = ue_wait_for_event(&listener, &uev)) == 0) { 32 | switch (uev.action) { 33 | case UEVENT_ACTION_ADD: 34 | printf("Device %s added.", uev.devpath); 35 | break; 36 | case UEVENT_ACTION_REMOVE: 37 | printf("Device %s removed.", uev.devpath); 38 | break; 39 | } 40 | } 41 | } 42 | ``` 43 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | python: 3 | version: 2.7.11 4 | 5 | test: 6 | override: 7 | - cd .ci && python build.py 8 | -------------------------------------------------------------------------------- /libue.h: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) <2016> 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 | of the Software, and to permit persons to whom the Software is furnished to do 11 | so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | #ifndef _LIBUE_H 26 | #define _LIBUE_H 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #define LIBUE_VERSION_MAJOR "0" 37 | #define LIBUE_VERSION_MINOR "2.0" 38 | #define LIBUE_VERSION LIBUE_VERSION_MAJOR "." LIBUE_VERSION_MINOR 39 | #define LIBUE_VERSION_NUMBER 10000 40 | #ifndef DEBUG 41 | #define DEBUG 0 42 | #endif 43 | #define UE_DEBUG(...) \ 44 | do { if (DEBUG) fprintf(stderr, __VA_ARGS__); } while(0) 45 | 46 | struct uevent_listener { 47 | struct pollfd pfd; 48 | struct sockaddr_nl nls; 49 | }; 50 | 51 | #define ERR_LISTENER_NOT_ROOT -1 52 | #define ERR_LISTENER_BIND -2 53 | #define ERR_LISTENER_POLL -3 54 | #define ERR_LISTENER_RECV -4 55 | #define ERR_PARSE_UDEV -1 56 | #define ERR_PARSE_INVALID_HDR -2 57 | #define UE_STR_EQ(str, const_str) (strncmp((str), (const_str), sizeof(const_str)-1) == 0) 58 | 59 | enum uevent_action { 60 | UEVENT_ACTION_INVALID = 0, 61 | UEVENT_ACTION_ADD, 62 | UEVENT_ACTION_REMOVE, 63 | UEVENT_ACTION_CHANGE, 64 | UEVENT_ACTION_MOVE, 65 | UEVENT_ACTION_ONLINE, 66 | UEVENT_ACTION_OFFLINE, 67 | }; 68 | 69 | struct uevent { 70 | enum uevent_action action; 71 | char *devpath; 72 | char buf[4096]; 73 | size_t buflen; 74 | }; 75 | 76 | const char* uev_action_str[] = { "invalid", "add", "remove", "change", "move", "online", "offline" }; 77 | 78 | /* 79 | * Reference for uevent format: 80 | * https://www.kernel.org/doc/pending/hotplug.txt 81 | */ 82 | int ue_parse_event_msg(struct uevent *uevp, size_t buflen) { 83 | /* skip udev events */ 84 | if (memcmp(uevp->buf, "libudev", 8) == 0) return ERR_PARSE_UDEV; 85 | 86 | /* validate message header */ 87 | size_t body_start = strlen(uevp->buf) + 1; 88 | if (body_start < sizeof("a@/d") 89 | || body_start >= buflen 90 | || (strstr(uevp->buf, "@/") == NULL)) { 91 | return ERR_PARSE_INVALID_HDR; 92 | } 93 | 94 | int i = body_start; 95 | char *cur_line; 96 | uevp->buflen = buflen; 97 | 98 | while (i < buflen) { 99 | cur_line = uevp->buf + i; 100 | UE_DEBUG("line: '%s'\n", cur_line); 101 | if (UE_STR_EQ(cur_line, "ACTION")) { 102 | cur_line += sizeof("ACTION"); 103 | if (UE_STR_EQ(cur_line, "add")) { 104 | uevp->action = UEVENT_ACTION_ADD; 105 | } else if (UE_STR_EQ(cur_line, "change")) { 106 | uevp->action = UEVENT_ACTION_CHANGE; 107 | } else if (UE_STR_EQ(cur_line, "remove")) { 108 | uevp->action = UEVENT_ACTION_REMOVE; 109 | } else if (UE_STR_EQ(cur_line, "move")) { 110 | uevp->action = UEVENT_ACTION_MOVE; 111 | } else if (UE_STR_EQ(cur_line, "online")) { 112 | uevp->action = UEVENT_ACTION_ONLINE; 113 | } else if (UE_STR_EQ(cur_line, "offline")) { 114 | uevp->action = UEVENT_ACTION_OFFLINE; 115 | } 116 | } else if (UE_STR_EQ(cur_line, "DEVPATH")) { 117 | uevp->devpath = cur_line + sizeof("DEVPATH"); 118 | } 119 | /* proceed to next line */ 120 | i += strlen(cur_line) + 1; 121 | } 122 | return 0; 123 | } 124 | 125 | static inline void ue_dump_event(struct uevent *uevp) { 126 | printf("%s %s\n", uev_action_str[uevp->action], uevp->devpath); 127 | } 128 | 129 | static inline void ue_reset_event(struct uevent *uevp) { 130 | uevp->action = UEVENT_ACTION_INVALID; 131 | uevp->buflen = 0; 132 | uevp->devpath = NULL; 133 | } 134 | 135 | int ue_init_listener(struct uevent_listener *l) { 136 | memset(&l->nls, 0, sizeof(struct sockaddr_nl)); 137 | l->nls.nl_family = AF_NETLINK; 138 | l->nls.nl_pid = getpid(); 139 | l->nls.nl_groups = -1; 140 | 141 | l->pfd.events = POLLIN; 142 | l->pfd.fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); 143 | if (l->pfd.fd == -1) return ERR_LISTENER_NOT_ROOT; 144 | 145 | if (bind(l->pfd.fd, (struct sockaddr*)&(l->nls), sizeof(struct sockaddr_nl))) { 146 | return ERR_LISTENER_BIND; 147 | } 148 | 149 | return 0; 150 | } 151 | 152 | int ue_wait_for_event(struct uevent_listener *l, struct uevent *uevp) { 153 | ue_reset_event(uevp); 154 | while (poll(&(l->pfd), 1, -1) != -1) { 155 | int i, len = recv(l->pfd.fd, uevp->buf, sizeof(uevp->buf), MSG_DONTWAIT); 156 | if (len == -1) return ERR_LISTENER_RECV; 157 | if (ue_parse_event_msg(uevp, len) == 0) { 158 | UE_DEBUG("uevent successfully parsed\n"); 159 | return 0; 160 | } else { 161 | UE_DEBUG("skipped unsupported uevent:\n%s\n", uevp->buf); 162 | } 163 | } 164 | return ERR_LISTENER_POLL; 165 | } 166 | 167 | #endif 168 | 169 | --------------------------------------------------------------------------------