/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2021 John Tsiombikas 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 3 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 . */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "spnavd.h" #include "logger.h" #include "dev.h" #include "hotplug.h" #include "client.h" #include "proto_unix.h" #ifdef USE_X11 #include "proto_x11.h" #endif static void print_usage(const char *argv0); static void cleanup(void); static void redir_log(int fallback_syslog); static void daemonize(void); static int write_pid_file(void); static int find_running_daemon(void); static void handle_events(fd_set *rset); static void sig_handler(int s); static char *fix_path(char *str); char *cfgfile = DEF_CFGFILE; static char *logfile = DEF_LOGFILE; static int pfd[2]; int main(int argc, char **argv) { int i, pid, ret, become_daemon = 1; int force_logfile = 0; for(i=1; i max_fd) max_fd = fd; } dev = dev->next; } if((fd = get_hotplug_fd()) != -1) { FD_SET(fd, &rset); if(fd > max_fd) max_fd = fd; } /* the UNIX domain socket listening for connections */ if((fd = get_unix_socket()) != -1) { FD_SET(fd, &rset); if(fd > max_fd) max_fd = fd; } /* all the UNIX socket clients */ client_iter = first_client(); while(client_iter) { if(get_client_type(client_iter) == CLIENT_UNIX) { int s = get_client_socket(client_iter); assert(s >= 0); FD_SET(s, &rset); if(s > max_fd) max_fd = s; } client_iter = next_client(); } /* and the X server socket */ #ifdef USE_X11 if((fd = get_x11_socket()) != -1) { FD_SET(fd, &rset); if(fd > max_fd) max_fd = fd; } #endif /* also the self-pipe read-end for safe SIGHUP handling */ FD_SET(pfd[0], &rset); if(pfd[0] > max_fd) max_fd = fd; do { /* if there is at least one device out of the deadzone and repeat is enabled * wait for only as long as specified in cfg.repeat_msec */ struct timeval tv, *timeout = 0; if(cfg.repeat_msec >= 0) { dev = get_devices(); while(dev) { if(is_device_valid(dev) && !in_deadzone(dev)) { tv.tv_sec = cfg.repeat_msec / 1000; tv.tv_usec = cfg.repeat_msec % 1000; timeout = &tv; break; } dev = dev->next; } } ret = select(max_fd + 1, &rset, 0, 0, timeout); } while(ret == -1 && errno == EINTR); if(ret > 0) { handle_events(&rset); } else { if(cfg.repeat_msec >= 0) { dev = get_devices(); while(dev) { if(!in_deadzone(dev)) { repeat_last_event(dev); } dev = dev->next; } } } } return 0; /* unreachable */ } static void print_usage(const char *argv0) { printf("usage: %s [options]\n", argv0); printf("options:\n"); printf(" -d: do not daemonize\n"); printf(" -c : config file path (default: " DEF_CFGFILE ")\n"); printf(" -l |syslog: log file path or log to syslog (default: " DEF_LOGFILE ")\n"); printf(" -v: verbose output (use multiple times for greater effect)\n"); printf(" -V,-version: print version number and exit\n"); printf(" -h,-help: print usage information and exit\n"); } static void cleanup(void) { struct device *dev; #ifdef USE_X11 close_x11(); /* call to avoid leaving garbage in the X server's root windows */ #endif close_unix(); shutdown_hotplug(); dev = get_devices(); while(dev) { struct device *tmp = dev; dev = dev->next; remove_device(tmp); } remove(PIDFILE); } static void redir_log(int fallback_syslog) { int i, fd = -1; if(logfile) { fd = start_logfile(logfile); } if(fd >= 0 || fallback_syslog) { /* redirect standard input/output/error * best effort attempt to make either the logfile or the syslog socket * accessible through stdout/stderr, just in case any printfs survived * the logmsg conversion. */ for(i=0; i<3; i++) { close(i); } open("/dev/zero", O_RDONLY); if(fd == -1) { fd = start_syslog(SYSLOG_ID); dup(1); /* not guaranteed to work */ } else { dup(fd); } } setvbuf(stdout, 0, _IOLBF, 0); setvbuf(stderr, 0, _IONBF, 0); } static void daemonize(void) { int pid; chdir("/"); redir_log(1); /* release controlling terminal */ if((pid = fork()) == -1) { perror("failed to fork"); exit(1); } else if(pid) { exit(0); } setsid(); } static int write_pid_file(void) { FILE *fp; int pid = getpid(); if(!(fp = fopen(PIDFILE, "w"))) { return -1; } fprintf(fp, "%d\n", pid); fclose(fp); return 0; } static int find_running_daemon(void) { FILE *fp; int s, pid; struct sockaddr_un addr; /* try to open the pid-file */ if(!(fp = fopen(PIDFILE, "r"))) { return -1; } if(fscanf(fp, "%d\n", &pid) != 1) { fclose(fp); return -1; } fclose(fp); /* make sure it's not just a stale pid-file */ if((s = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) { return -1; } memset(&addr, 0, sizeof addr); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, SOCK_NAME, sizeof addr.sun_path); if(connect(s, (struct sockaddr*)&addr, sizeof addr) == -1) { close(s); return -1; } /* managed to connect alright, it's running... */ close(s); return pid; } static void handle_events(fd_set *rset) { int dev_fd, hotplug_fd; struct device *dev; struct dev_input inp; /* handle signal pipe */ if(FD_ISSET(pfd[0], rset)) { int tmp; read(pfd[0], &tmp, sizeof tmp); /* eat up the junk char */ read_cfg(cfgfile, &cfg); cfg_changed(); } /* handle anything coming through the UNIX socket */ handle_uevents(rset); #ifdef USE_X11 /* handle any X11 events (magellan protocol) */ handle_xevents(rset); #endif /* finally read any pending device input data */ dev = get_devices(); while(dev) { /* keep the next pointer because read_device can potentially destroy * the device node if the read fails. */ struct device *next = dev->next; if((dev_fd = get_device_fd(dev)) != -1 && FD_ISSET(dev_fd, rset)) { /* read an event from the device ... */ while(read_device(dev, &inp) != -1) { /* ... and process it, possibly dispatching a spacenav event to clients */ process_input(dev, &inp); } /* flush any pending events if we run out of input */ inp.type = INP_FLUSH; process_input(dev, &inp); } dev = next; } if((hotplug_fd = get_hotplug_fd()) != -1) { if(FD_ISSET(hotplug_fd, rset)) { handle_hotplug(); } } } void cfg_changed(void) { if(cfg.led != prev_cfg.led) { struct device *dev = get_devices(); while(dev) { if(is_device_valid(dev)) { if(verbose) { logmsg(LOG_INFO, "led %s, device: %s\n", cfg.led ? (cfg.led == LED_AUTO ? "auto" : "on"): "off", dev->name); } if(cfg.led == LED_ON || (cfg.led == LED_AUTO && first_client())) { set_device_led(dev, 1); } else { set_device_led(dev, 0); } } dev = dev->next; } } if(strcmp(cfg.serial_dev, prev_cfg.serial_dev) != 0) { struct device *dev, *iter = get_devices(); while(iter) { dev = iter; iter = iter->next; if(strcmp(dev->path, prev_cfg.serial_dev) == 0) { remove_device(dev); } } init_devices_serial(); } prev_cfg = cfg; } /* signals usr1 & usr2 are sent by the spnav_x11 script to start/stop the * daemon's connection to the X server. */ static void sig_handler(int s) { switch(s) { case SIGHUP: write(pfd[1], &s, 1); /* write *something* to the pipe to trigger a re-read */ break; case SIGSEGV: logmsg(LOG_ERR, "Segmentation fault caught, trying to exit gracefully\n"); case SIGINT: case SIGTERM: exit(0); #ifdef USE_X11 case SIGUSR1: init_x11(); break; case SIGUSR2: close_x11(); break; #endif default: break; } } static char *fix_path(char *str) { char *buf, *tmp; int sz, len; if(str[0] == '/') return str; len = strlen(str) + 1; /* +1 for the path separator */ sz = PATH_MAX; if(!(buf = malloc(sz + len))) { perror("failed to allocate path buffer"); return 0; } while(!getcwd(buf, sz)) { if(errno == ERANGE) { sz *= 2; if(!(tmp = realloc(buf, sz + len))) { perror("failed to reallocate path buffer"); free(buf); return 0; } buf = tmp; } else { perror("getcwd failed"); free(buf); return 0; } } sprintf(buf + strlen(buf), "/%s", str); return buf; }