// The udev-act program to be run on a per user basis to execute // commands when udev usb devices appear, disappear, etc. // #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; static const char * debug_file = NULL; struct config_info { config_info * next; string action; string usb_id; string command; }; struct config_info * config_list = NULL; struct config_info ** config_end = &config_list; // path under $HOME where the config file is located. // #define CONFIG_FILE_NAME "/.config/udev-act.conf" static void write_debug(string dbgstr) { ofstream outfile; outfile.open(debug_file, ios::app); outfile << dbgstr; outfile << endl; } // Read the config file and build config_list records // static void read_config_file() { char * homedir = getenv("HOME"); string cfile = homedir; cfile.append(CONFIG_FILE_NAME); ifstream s; s.open(cfile); if (s.is_open()) { string line; while (getline(s,line)) { if (line[0] != '#') { istringstream iss(line); string act; string id; string prog; if (iss >> act >> id >> prog) { config_info * ci = new config_info; ci->next = *config_end; *config_end = ci; config_end = &ci->next; ci->action = act; ci->usb_id = id; ci->command = prog; } } } s.close(); } else { cerr << "Missing config file.\n"; } config_info * ci = config_list; } // Run a command using the double fork technique so I don't need to // wait on kids. // static void run_command(string command) { pid_t kid = fork(); if (kid == 0) { // I am the kid, fork again to do the exec then exit, let // the system wait on the actual command, I won't have to. pid_t grandkid = fork(); if (grandkid == 0) { // Setup /dev/null as stdin, stdout, and stderr, close any // other file descriptors that might be hanging around. int null_out_fd = open("/dev/null", O_WRONLY); int null_in_fd = open("/dev/null", O_RDONLY); dup2(null_out_fd, 1 /* stdout */); dup2(null_out_fd, 2 /* stderr */); dup2(null_in_fd, 0 /* stdin */); for (int i = 3; i < 60000; ++i) { close(i); } // Now run the command if (debug_file != NULL) { string dbgstr; dbgstr.append("In run_command with: "); dbgstr.append(command); write_debug(dbgstr); } execl("/bin/sh", "/bin/sh", "-c", command.c_str(), NULL); // And if that somehow didn't work, exit _exit(2); } else { _exit(0); } } else { // Wait on the kid to exit (which it should do almost // immediately - as soon as it forks again). int status; waitpid(kid, &status, 0); } } // Extract first line of file and append to str, return true if // file existed and there was something appended, false otherwise // static bool append_line(string file, string & str) { bool rval = false; ifstream s; s.open(file); if (s.is_open()) { string line; if (getline(s,line)) { if (line.length() > 0) { str.append(line); rval = true; } } s.close(); } return rval; } // Monitor usb udev events and if they are associated with a path // containing a vendor and product id, compare to the config records // and run the commands given in the config. Loop forever doing this. // static void monitor_events() { struct udev *udev; struct udev_device *dev; struct udev_monitor *mon; int fd; udev = udev_new(); if (!udev) { cerr << "Can't create udev\n"; return; } mon = udev_monitor_new_from_netlink(udev, "udev"); udev_monitor_filter_add_match_subsystem_devtype(mon, "usb", NULL); udev_monitor_enable_receiving(mon); fd = udev_monitor_get_fd(mon); while (1) { fd_set fds; int ret; FD_ZERO(&fds); FD_SET(fd, &fds); ret = select(fd+1, &fds, NULL, NULL, NULL); if (ret > 0 && FD_ISSET(fd, &fds)) { dev = udev_monitor_receive_device(mon); if (dev) { string dbgstr; string curact = udev_device_get_action(dev); string dirname = "/sys"; dirname.append(udev_device_get_devpath(dev)); udev_device_unref(dev); string prodfile = dirname; prodfile.append("/idProduct"); string vendfile = dirname; vendfile.append("/idVendor"); if (debug_file != NULL) { dbgstr.append("curact: "); dbgstr.append(curact); dbgstr.append(", dirname: "); dbgstr.append(dirname); } string usbid; if (append_line(vendfile, usbid)) { if (debug_file != NULL) { dbgstr.append(", vendor id: "); dbgstr.append(usbid); } usbid.append(":"); if (append_line(prodfile, usbid)) { if (debug_file != NULL) { dbgstr.append(", product id: "); dbgstr.append(usbid); } config_info * ci = config_list; while (ci != NULL) { if ((ci->action.compare(curact) == 0) && (ci->usb_id.compare(usbid) == 0)) { if (debug_file != NULL) { dbgstr.append(", run command: "); dbgstr.append(ci->command); } run_command(ci->command); } ci = ci->next; } } } if (debug_file != NULL) { write_debug(dbgstr); } } } } udev_unref(udev); } int main(int argc, char ** argv) { if ((argc > 2) && (strcmp(argv[1], "-d") == 0)) { debug_file = argv[2]; } read_config_file(); monitor_events(); return 2; }