1 /*
2 * netevent - low-level event-device sharing
3 *
4 * Copyright (C) 2017,2018 Wolfgang Bumiller <wry.git@bumiller.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19 #include <stdarg.h>
20 #include <stdlib.h>
21 #include <getopt.h>
22 #include <poll.h>
23 #include <signal.h>
24 #include <sys/wait.h>
25 #include <sys/socket.h>
26
27 #include <algorithm>
28 #include <vector>
29 #include <map>
30 using std::vector;
31 using std::map;
32
33 #include "main.h"
34
35 #define OUTPUT_CHANGED_EVENT "output-changed"
36 #define DEVICE_LOST_EVENT "device-lost"
37 #define GRAB_CHANGED_EVENT "grab-changed"
38
39 static void
usage_daemon(FILE * out,int exit_status)40 usage_daemon [[noreturn]] (FILE *out, int exit_status)
41 {
42 ::fprintf(out,
43 "usage: netevent daemon [options] SOCKETNAME\n"
44 "options:\n"
45 " -h, --help show this help message\n"
46 " -s, --source=FILE run commands from FILE on startup\n"
47 );
48 ::exit(exit_status);
49 }
50
51 struct FDCallbacks {
52 function<void()> onRead;
53 function<void()> onHUP;
54 function<void()> onError;
55 function<void()> onRemove;
56 };
57
58 struct Command {
59 int client_;
60 string command_;
61 };
62
63 struct Input {
64 uint16_t id_;
65 uniq<InDevice> device_;
66 };
67
68 struct FILEHandle {
69 FILE *file_;
FILEHandleFILEHandle70 FILEHandle(FILE *file) : file_(file) {}
FILEHandleFILEHandle71 FILEHandle(FILEHandle&& o) : file_(o.file_) {
72 o.file_ = nullptr;
73 }
74 FILEHandle(const FILEHandle&) = delete;
~FILEHandleFILEHandle75 ~FILEHandle() {
76 if (file_)
77 ::fclose(file_);
78 }
79 };
80
81 struct HotkeyDef {
82 uint16_t device;
83 uint16_t type;
84 uint16_t code;
85 int32_t value;
86
operator <HotkeyDef87 constexpr bool operator<(const HotkeyDef& r) const {
88 return (device < r.device || (device == r.device &&
89 (type < r.type || (type == r.type &&
90 (code < r.code || (code == r.code &&
91 (value < r.value)))))));
92 }
93 };
94
95 #pragma clang diagnostic push
96 #pragma clang diagnostic ignored "-Wexit-time-destructors"
97 #pragma clang diagnostic ignored "-Wglobal-constructors"
98
99 static int gServerFD;
100 static bool gQuit = false;
101 static vector<int> gFDRemoveQueue;
102 static vector<struct pollfd> gFDAddQueue;
103 static map<int, FDCallbacks> gFDCBs;
104 static map<int, FILEHandle> gCommandClients;
105 static vector<Command> gCommandQueue;
106 static vector<uint16_t> gInputIDFreeList;
107 static map<string, Input> gInputs;
108 static map<string, IOHandle> gOutputs;
109 static struct {
110 int fd = -1;
111 string name;
112 } gCurrentOutput;
113 static bool gGrab = false;
114 static map<HotkeyDef, string> gHotkeys;
115 static map<string, string> gEventCommands;
116
117 static vector<function<void()>> gPreExecStack;
118 #pragma clang diagnostic pop
119
120 #if 0
121 template<typename T>
122 static void
123 vectorRemove(vector<T>& vec, T&& value)
124 {
125 auto iter = vec.find(value);
126 if (iter != vec.end())
127 vec.erase(iter);
128 }
129 #endif
130
131 static void parseClientCommand(int clientfd, const char *cmd, size_t length);
132
133 static ScopeGuard
preExec(function<void ()> func)134 preExec(function<void()> func)
135 {
136 auto idx = gPreExecStack.size();
137 gPreExecStack.emplace_back(move(func));
138 return {[idx]() { gPreExecStack[idx] = nullptr; }};
139 }
140
141 static void
daemon_preExec()142 daemon_preExec()
143 {
144 for (auto& f: gPreExecStack)
145 if (f)
146 f();
147 ::close(gServerFD);
148 gCommandClients.clear(); // closes fds
149 gInputs.clear(); // closes event devices
150 gOutputs.clear();
151 gFDCBs.clear();
152 }
153
154 #if 0
155 template<typename T, typename U>
156 static void
157 mapRemove(map<T,U>& m, T key)
158 {
159 auto iter = m.find(key);
160 if (iter != m.end())
161 m.erase(iter);
162 }
163 #endif
164
165 static void
removeFD(int fd)166 removeFD(int fd)
167 {
168 if (fd < 0)
169 return;
170 if (std::find(gFDRemoveQueue.begin(), gFDRemoveQueue.end(), fd)
171 == gFDRemoveQueue.end())
172 {
173 gFDRemoveQueue.push_back(fd);
174 }
175 }
176
177 static void
removeOutput(int fd)178 removeOutput(int fd) {
179 removeFD(fd);
180 }
181
182 static void
removeOutput(const string & name)183 removeOutput(const string& name)
184 {
185 auto iter = gOutputs.find(name);
186 if (iter == gOutputs.end())
187 throw MsgException("no such output: %s", name.c_str());
188 removeOutput(iter->second.fd());
189 }
190
191 static bool
writeToOutput(int fd,const void * data,size_t size)192 writeToOutput(int fd, const void *data, size_t size)
193 {
194 if (::write(fd, data, size) != static_cast<ssize_t>(size)) {
195 ::fprintf(stderr, "error writing to output, dropping\n");
196 removeOutput(fd);
197 return false;
198 }
199 return true;
200 }
201
202 static void
announceDeviceRemoval(Input & input)203 announceDeviceRemoval(Input& input)
204 {
205 NE2Packet pkt = {};
206 ::memset(&pkt, 0, sizeof(pkt));
207 pkt.cmd = htobe16(uint16_t(NE2Command::RemoveDevice));
208 pkt.remove_device.id = htobe16(input.id_);
209
210 for (auto& oi: gOutputs)
211 (void)writeToOutput(oi.second.fd(), &pkt, sizeof(pkt));
212 }
213
214 static void
cleanupDeviceHotkeys(uint16_t id)215 cleanupDeviceHotkeys(uint16_t id)
216 {
217 for (auto i = gHotkeys.begin(); i != gHotkeys.end();) {
218 if (i->first.device == id)
219 i = gHotkeys.erase(i);
220 else
221 ++i;
222 }
223 }
224
225 static void
processRemoveQueue()226 processRemoveQueue()
227 {
228 for (int fd : gFDRemoveQueue) {
229 auto cbs = gFDCBs.find(fd);
230 if (cbs == gFDCBs.end())
231 throw Exception("FD without cleanup callback");
232 cbs->second.onRemove();
233 gFDCBs.erase(cbs);
234 }
235
236 gFDRemoveQueue.clear();
237 }
238
239 static void
disconnectClient(int fd)240 disconnectClient(int fd)
241 {
242 removeFD(fd);
243 }
244
245 static void
finishClientRemoval(int fd)246 finishClientRemoval(int fd) {
247 auto iter = gCommandClients.find(fd);
248 if (iter != gCommandClients.end()) {
249 gCommandClients.erase(iter);
250 return;
251 }
252 throw Exception("finishClientRemoval: failed to find fd");
253 }
254
255 static void
queueCommand(int clientfd,string line)256 queueCommand(int clientfd, string line)
257 {
258 gCommandQueue.emplace_back(Command{clientfd, move(line)});
259 }
260
261
262 static void
readCommand(FILE * file)263 readCommand(FILE *file)
264 {
265 char *line = nullptr;
266 scope (exit) { ::free(line); };
267 size_t len = 0;
268 errno = 0;
269 auto got = ::getline(&line, &len, file);
270 if (got < 0) {
271 if (errno)
272 ::fprintf(stderr,
273 "error reading from command client: %s\n",
274 ::strerror(errno));
275 disconnectClient(::fileno(file));
276 return;
277 }
278 if (got == 0) { // EOF
279 disconnectClient(::fileno(file));
280 return;
281 }
282
283 int fd = ::fileno(file);
284 queueCommand(fd, line);
285 }
286
287 static void
addFD(int fd,short events=POLLIN|POLLHUP|POLLERR)288 addFD(int fd, short events = POLLIN | POLLHUP | POLLERR)
289 {
290 gFDAddQueue.emplace_back(pollfd { fd, events, 0 });
291 }
292
293 static void
newCommandClient(Socket & server)294 newCommandClient(Socket& server)
295 {
296 IOHandle h = server.accept();
297 int fd = h.fd();
298
299 FILE *buffd = ::fdopen(fd, "rb");
300 if (!buffd)
301 throw ErrnoException("fdopen() failed");
302 FILEHandle bufhandle { buffd };
303 (void)h.release();
304
305 addFD(fd);
306 gFDCBs[fd] = FDCallbacks {
307 [buffd]() { readCommand(buffd); },
308 [fd]() { disconnectClient(fd); },
309 [fd]() { disconnectClient(fd); },
310 [fd]() { finishClientRemoval(fd); },
311 };
312 gCommandClients.emplace(fd, move(bufhandle));
313 }
314
315 #pragma clang diagnostic push
316 #pragma clang diagnostic ignored "-Wformat-nonliteral"
317 static void
toClient(int fd,const char * fmt,...)318 toClient(int fd, const char *fmt, ...)
319 {
320 char buf[4096];
321 va_list ap;
322 va_start(ap, fmt);
323 auto length = ::vsnprintf(buf, sizeof(buf), fmt, ap);
324 int err = errno;
325 va_end(ap);
326 if (length <= 0) {
327 ::fprintf(stderr, "faield to format client response: %s\n",
328 ::strerror(err));
329 disconnectClient(fd);
330 }
331 if (fd < 0)
332 ::fwrite(buf, size_t(length), 1, stderr);
333 else if (::write(fd, buf, size_t(length)) != length) {
334 ::fprintf(stderr,
335 "failed to write response to client command\n");
336 disconnectClient(fd);
337 }
338 }
339 #pragma clang diagnostic pop
340
341 static uint16_t
getNextInputID()342 getNextInputID()
343 {
344 if (gInputs.size() > UINT16_MAX)
345 throw Exception(
346 "too many input devices (... the heck are you doing?)");
347
348 if (gInputIDFreeList.empty())
349 return static_cast<uint16_t>(gInputs.size());
350
351 auto next = gInputIDFreeList.back();
352 gInputIDFreeList.pop_back();
353 return static_cast<uint16_t>(next);
354 }
355
356 static void
freeInputID(uint16_t id)357 freeInputID(uint16_t id)
358 {
359 gInputIDFreeList.push_back(id);
360 }
361
362 static void
closeDevice(InDevice * device)363 closeDevice(InDevice *device)
364 {
365 removeFD(device->fd());
366 }
367
368 static void
finishDeviceRemoval(InDevice * device)369 finishDeviceRemoval(InDevice *device)
370 {
371 for (auto i = gInputs.begin(); i != gInputs.end(); ++i) {
372 if (i->second.device_.get() == device) {
373 if (!device->persistent())
374 announceDeviceRemoval(i->second);
375 cleanupDeviceHotkeys(i->second.id_);
376 gInputs.erase(i);
377 return;
378 }
379 }
380 throw Exception("finishDeviceRemoval: failed to find device");
381 }
382
383 static void
fireEvent(int clientfd,const char * event)384 fireEvent(int clientfd, const char *event)
385 {
386 auto iter = gEventCommands.find(event);
387 if (iter == gEventCommands.end())
388 return;
389 auto& cmd = iter->second;
390 parseClientCommand(clientfd, cmd.c_str(), cmd.length());
391 }
392
393 static void
setEnvVar(const char * name,const char * value)394 setEnvVar(const char *name, const char *value)
395 {
396 if (::setenv(name, value, 1) != 0) {
397 ::fprintf(stderr,
398 "error setting environment variable %s to %s: %s\n",
399 name, value, ::strerror(errno));
400 }
401 }
402
403 static void
useOutput(int clientfd,const string & name)404 useOutput(int clientfd, const string& name)
405 {
406 auto iter = gOutputs.find(name);
407 if (iter == gOutputs.end())
408 throw MsgException("no such output: %s", name.c_str());
409 gCurrentOutput.fd = iter->second.fd();
410 gCurrentOutput.name = name;
411
412 setEnvVar("NETEVENT_OUTPUT_NAME", name.c_str());
413 fireEvent(clientfd, OUTPUT_CHANGED_EVENT);
414 }
415
416 static bool
tryHotkey(uint16_t device,uint16_t type,uint16_t code,int32_t value)417 tryHotkey(uint16_t device, uint16_t type, uint16_t code, int32_t value)
418 {
419 if (type >= EV_CNT)
420 return false;
421 HotkeyDef def { device, type, code, value };
422 auto cmd = gHotkeys.find(def);
423 if (cmd == gHotkeys.end())
424 return false;
425 queueCommand(-1, cmd->second);
426 return true;
427 }
428
429 static void
grab(int clientfd,bool on)430 grab(int clientfd, bool on)
431 {
432 gGrab = on;
433 setEnvVar("NETEVENT_GRABBING", on ? "1" : "0");
434 for (auto& i: gInputs)
435 i.second.device_->grab(on);
436 fireEvent(clientfd, GRAB_CHANGED_EVENT);
437 }
438
439 static void
lostCurrentOutput()440 lostCurrentOutput()
441 {
442 gCurrentOutput.fd = -1;
443 gCurrentOutput.name = "<none>";
444 if (gGrab)
445 grab(-1, false);
446 }
447
448 static void
readFromDevice(InDevice * device,uint16_t id)449 readFromDevice(InDevice *device, uint16_t id)
450 {
451 NE2Packet pkt = {};
452 try {
453 if (!device->read(&pkt.event.event)) {
454 fireEvent(-1, DEVICE_LOST_EVENT);
455 return closeDevice(device);
456 }
457 } catch (const Exception& ex) {
458 ::fprintf(stderr, "error reading device: %s\n", ex.what());
459 return closeDevice(device);
460 }
461
462 const auto& ev = pkt.event.event;
463 if (tryHotkey(id, ev.type, ev.code, ev.value))
464 return;
465
466 if (gCurrentOutput.fd == -1)
467 return;
468
469 // FIXME: currently we only write when grabbing; if anyone needs to b
470 // e able to control this separately... PR welcome
471 if (!gGrab)
472 return;
473
474 pkt.cmd = htobe16(uint16_t(NE2Command::DeviceEvent));
475 pkt.event.id = htobe16(id);
476 pkt.event.event.toNet();
477 if (mustWrite(gCurrentOutput.fd, &pkt, sizeof(pkt)))
478 return;
479
480 // on error we drop the output:
481 ::fprintf(stderr, "error writing to output %s: %s\n",
482 gCurrentOutput.name.c_str(), ::strerror(errno));
483 removeOutput(gCurrentOutput.fd);
484 lostCurrentOutput();
485 }
486
487 static bool
announceDevice(Input & input,int fd)488 announceDevice(Input& input, int fd)
489 {
490 try {
491 input.device_->writeNE2AddDevice(fd, input.id_);
492 return true;
493 } catch (const Exception& ex) {
494 ::fprintf(stderr,
495 "error creating device on output, dropping: %s\n",
496 ex.what());
497 removeOutput(fd);
498 return false;
499 }
500 }
501
502 static void
announceDevice(Input & input)503 announceDevice(Input& input)
504 {
505 for (auto& oi: gOutputs)
506 announceDevice(input, oi.second.fd());
507 }
508
509 static void
announceAllDevices(int fd)510 announceAllDevices(int fd)
511 {
512 for (auto& i: gInputs) {
513 if (!announceDevice(i.second, fd))
514 break;
515 }
516 }
517
518 static void
addDevice(const string & name,const char * path)519 addDevice(const string& name, const char *path)
520 {
521 if (gInputs.find(name) != gInputs.end())
522 throw MsgException("output already exists: %s", name.c_str());
523
524 auto id = getNextInputID();
525 try {
526 Input input { id, uniq<InDevice> { new InDevice { path } } };
527 InDevice *weakdevptr = input.device_.get();
528 int fd = weakdevptr->fd();
529
530 announceDevice(input);
531
532 addFD(fd);
533 gFDCBs[fd] = FDCallbacks {
534 [=]() { readFromDevice(weakdevptr, id); },
535 [=]() {
536 fireEvent(-1, DEVICE_LOST_EVENT);
537 closeDevice(weakdevptr);
538 },
539 [=]() {
540 fireEvent(-1, DEVICE_LOST_EVENT);
541 closeDevice(weakdevptr);
542 },
543 [=]() { finishDeviceRemoval(weakdevptr); },
544 };
545 gInputs.emplace(name, move(input));
546 } catch (const std::exception&) {
547 freeInputID(id);
548 throw;
549 }
550 }
551
552 static InDevice*
findDevice(const string & name)553 findDevice(const string& name)
554 {
555 auto iter = gInputs.find(name);
556 if (iter == gInputs.end())
557 throw MsgException("no such device: %s", name.c_str());
558 return iter->second.device_.get();
559 }
560
561 static void
removeDevice(const string & name)562 removeDevice(const string& name)
563 {
564 closeDevice(findDevice(name));
565 }
566
567 static void
finishOutputRemoval(int fd)568 finishOutputRemoval(int fd)
569 {
570 if (gCurrentOutput.fd == fd)
571 lostCurrentOutput();
572 for (auto i = gOutputs.begin(); i != gOutputs.end(); ++i) {
573 if (i->second.fd() == fd) {
574 gOutputs.erase(i);
575 return;
576 }
577 }
578 throw MsgException("finishOutputRemove: faile dot find fd");
579 }
580
581 // TODO:
582 // - tcp:IP PORT
583 // - tcp:HOSTNAME PORT
584 // - tcp:[IP] PORT
585 // - tcp:[HOSTNAME] PORT
586 // Unsafe but still useful.
587 // - tcp:DESTINATION PORT CERT KEY CACERTorPATH
588 // - tcp:[DESTINATION] PORT CERT KEY CACERTorPATH
589 // Annoying & require an ssl lib but more useful than the non-ssl
590 // variant...
591 static void
addOutput_Finish(const string & name,IOHandle handle,bool skip_announce)592 addOutput_Finish(const string& name, IOHandle handle, bool skip_announce)
593 {
594 int fd = handle.fd();
595 writeHello(fd);
596 if (!skip_announce)
597 announceAllDevices(fd);
598 gOutputs.emplace(name, move(handle));
599 gFDCBs.emplace(fd, FDCallbacks {
600 [fd]() {
601 ::fprintf(stderr, "onRead on output");
602 removeFD(fd);
603 },
604 [fd]() { removeFD(fd); },
605 [fd]() { removeFD(fd); },
606 [fd]() { finishOutputRemoval(fd); },
607 });
608 }
609
610 static IOHandle
addOutput_Open(const char * path)611 addOutput_Open(const char *path)
612 {
613 // Use O_NDELAY to not hang on FIFOs. FIFOs should already be waiting
614 // for our connection, we remove O_NONBLOCK below again.
615 int fd = ::open(path, O_WRONLY | O_NDELAY);
616 if (fd < 0)
617 throw ErrnoException("open(%s)", path);
618 IOHandle handle { fd };
619
620 int flags = ::fcntl(fd, F_GETFL);
621 if (flags == -1)
622 throw ErrnoException("failed to get output flags");
623 if (::fcntl(fd, F_SETFL, flags & ~(O_NONBLOCK)) != 0)
624 throw ErrnoException("failed to remove O_NONBLOCK");
625
626 return handle;
627 }
628
629 static IOHandle
addOutput_Exec(const char * path)630 addOutput_Exec(const char *path)
631 {
632 int pfd[2];
633 if (::pipe(pfd) != 0)
634 throw ErrnoException("pipe() failed");
635 IOHandle pr { pfd[0] };
636 IOHandle pw { pfd[1] };
637
638 pid_t pid = ::fork();
639 if (pid == -1)
640 throw ErrnoException("fork() failed");
641
642 if (!pid) {
643 pw.close();
644
645 if (pr.fd() != 0) {
646 if (::dup2(pr.fd(), 0) != 0) {
647 ::perror("dup2");
648 ::exit(-1);
649 }
650 pr.close();
651 }
652 daemon_preExec();
653 ::execlp("/bin/sh", "/bin/sh", "-c", path, nullptr);
654 ::perror("exec() failed");
655 ::exit(-1);
656 }
657 pr.close();
658
659 return pw;
660 }
661
662 static IOHandle
addOutput_Unix(const char * path)663 addOutput_Unix(const char *path)
664 {
665 Socket socket;
666 if (path[0] == '@')
667 socket.connectUnix<true>(path+1);
668 else
669 socket.connectUnix<false>(path);
670 return socket.intoIOHandle();
671 }
672
673 static void
addOutput(const string & name,const char * path,bool skip_announce)674 addOutput(const string& name, const char *path, bool skip_announce)
675 {
676 if (gOutputs.find(name) != gOutputs.end())
677 throw MsgException("output already exists: %s", name.c_str());
678
679 IOHandle handle;
680 if (::strncmp(path, "exec:", sizeof("exec:")-1) == 0)
681 handle = addOutput_Exec(path+(sizeof("exec:")-1));
682 else if (::strncmp(path, "unix:", sizeof("unix:")-1) == 0)
683 handle = addOutput_Unix(path+(sizeof("unix:")-1));
684 else
685 handle = addOutput_Open(path);
686
687 return addOutput_Finish(name, move(handle), skip_announce);
688 }
689
690 static void
addOutput(int clientfd,const vector<string> & args)691 addOutput(int clientfd, const vector<string>& args)
692 {
693 bool skip_announce = false;
694 size_t at = 2;
695 if (args.size() > at && args[at] == "--resume") {
696 ++at;
697 skip_announce = true;
698 }
699
700 if (at+1 >= args.size())
701 throw Exception(
702 "'output add' requires a name and a path");
703
704 const string& name = args[at++];
705
706 string cmd = join(' ', args.begin()+ssize_t(at), args.end());
707 addOutput(name, cmd.c_str(), skip_announce);
708 toClient(clientfd, "added output %s\n", name.c_str());
709 }
710
711 static void
grabCommand(int clientfd,const char * state)712 grabCommand(int clientfd, const char *state)
713 {
714 if (parseBool(&gGrab, state)) {
715 // nothing
716 }
717 else if (!::strcasecmp(state, "toggle"))
718 {
719 gGrab = !gGrab;
720 }
721 else
722 throw MsgException("unknown grab state: %s", state);
723 grab(clientfd, gGrab);
724 }
725
726 static void
addHotkey(uint16_t device,uint16_t type,uint16_t code,int32_t value,string command)727 addHotkey(uint16_t device, uint16_t type, uint16_t code, int32_t value,
728 string command)
729 {
730 if (type >= EV_CNT)
731 throw MsgException("unknown event type: %u", type);
732
733
734 gHotkeys[HotkeyDef{device, type, code, value}] = move(command);
735 }
736
737 static void
removeHotkey(uint16_t device,uint16_t type,uint16_t code,int32_t value)738 removeHotkey(uint16_t device, uint16_t type, uint16_t code, int32_t value)
739 {
740 if (type >= EV_CNT)
741 throw MsgException("unknown event type: %u", type);
742 gHotkeys.erase(HotkeyDef{device, type, code, value});
743 }
744
745 static void
shellCommand(const char * cmd)746 shellCommand(const char *cmd)
747 {
748 pid_t pid = ::fork();
749 if (pid == -1)
750 throw ErrnoException("fork() failed");
751 if (!pid) {
752 daemon_preExec();
753 ::execlp("/bin/sh", "/bin/sh", "-c", cmd, nullptr);
754 ::perror("exec() failed");
755 ::exit(-1);
756 }
757 int status = 0;
758 do {
759 // wait
760 } while (::waitpid(pid, &status, 0) != pid);
761 }
762
763 static inline constexpr bool
isWhite(char c)764 isWhite(char c)
765 {
766 return c == ' ' || c == '\t' || c == '\r' || c == '\n';
767 }
768
769 static bool
skipWhite(const char * & p)770 skipWhite(const char* &p)
771 {
772 while (isWhite(*p))
773 ++p;
774 return *p != 0;
775 }
776
777 static string
parseString(const char * & p)778 parseString(const char* &p)
779 {
780 string str;
781 char quote = *p++;
782 bool escape = false;
783 while (*p && *p != quote) {
784 if (escape) {
785 escape = false;
786 switch (*p) {
787 case '\\': str.append(1, '\\'); break;
788 case 't': str.append(1, '\t'); break;
789 case 'r': str.append(1, '\r'); break;
790 case 'n': str.append(1, '\n'); break;
791 case 'f': str.append(1, '\f'); break;
792 case 'v': str.append(1, '\v'); break;
793 case 'b': str.append(1, '\b'); break;
794 case '"': str.append(1, '\"'); break;
795 case '\'': str.append(1, '\''); break;
796 case '0': str.append(1, '\0'); break;
797 default:
798 str.append(1, '\\');
799 str.append(1, *p);
800 break;
801 }
802 } else if (*p == '\\') {
803 escape = true;
804 } else {
805 str.append(1, *p);
806 }
807 ++p;
808 }
809 if (*p) // skip quote
810 ++p;
811 return str;
812 }
813
814 static void
clientCommand_Device(int clientfd,const vector<string> & args)815 clientCommand_Device(int clientfd, const vector<string>& args)
816 {
817 if (args.size() < 2)
818 throw Exception("'device': missing subcommand");
819
820 if (args[1] == "add") {
821 if (args.size() != 4)
822 throw Exception(
823 "'device add' requires a name and a path");
824 addDevice(args[2], args[3].c_str());
825 toClient(clientfd, "added device %s\n", args[2].c_str());
826 }
827 else if (args[1] == "remove") {
828 if (args.size() != 3)
829 throw Exception(
830 "'device remove' requires a name");
831 removeDevice(args[2]);
832 toClient(clientfd, "removing device %s\n",
833 args[2].c_str());
834 }
835 else if (args[1] == "rename") {
836 if (args.size() != 4)
837 throw Exception(
838 "'device rename' requires a device and a name");
839 auto dev = findDevice(args[2]);
840 dev->setName(args[3]);
841 toClient(clientfd, "renamed device %s to %s\n",
842 dev->realName().c_str(), args[3].c_str());
843 }
844 else if (args[1] == "reset-name") {
845 if (args.size() != 3)
846 throw Exception(
847 "'device reset-name' requires a device");
848 auto dev = findDevice(args[2]);
849 dev->resetName();
850 toClient(clientfd, "reset name of device %s\n",
851 dev->realName().c_str());
852 }
853 else if (args[1] == "set-persistent") {
854 if (args.size() != 4)
855 throw Exception(
856 "'device set-persistent' requires a device"
857 " and a boolean");
858 auto dev = findDevice(args[2]);
859 bool value;
860 if (parseBool(&value, args[3].c_str())) {
861 dev->persistent(value);
862 if (value)
863 toClient(clientfd,
864 "device %s made persistent\n",
865 args[2].c_str());
866 else
867 toClient(clientfd,
868 "device %s made removable\n",
869 args[2].c_str());
870 } else {
871 toClient(clientfd, "not a boolean: '%s'\n",
872 args[3].c_str());
873 }
874 }
875 else
876 throw MsgException("unknown device subcommand: %s",
877 args[1].c_str());
878 }
879
880 static void
clientCommand_Output(int clientfd,const vector<string> & args)881 clientCommand_Output(int clientfd, const vector<string>& args)
882 {
883 if (args.size() < 2)
884 throw Exception("'output': missing subcommand");
885
886 if (args[1] == "add") {
887 addOutput(clientfd, args);
888 }
889 else if (args[1] == "remove") {
890 if (args.size() != 3)
891 throw Exception(
892 "'output remove' requires a name");
893 removeOutput(args[2]);
894 toClient(clientfd, "removing output %s\n",
895 args[2].c_str());
896 }
897 else if (args[1] == "use") {
898 if (args.size() != 3)
899 throw Exception(
900 "'output use' requires a name");
901 useOutput(clientfd, args[2]);
902 toClient(clientfd, "output = %s\n",
903 gCurrentOutput.name.c_str());
904 }
905 else
906 throw MsgException("unknown output subcommand: %s",
907 args[1].c_str());
908 }
909
910 static void
clientCommand_Hotkey(int clientfd,const vector<string> & args)911 clientCommand_Hotkey(int clientfd, const vector<string>& args)
912 {
913 if (args.size() < 2)
914 throw Exception("'hotkey': missing subcommand");
915
916 if (args[1] == "add") {
917 if (args.size() < 5)
918 throw Exception(
919 "'hotkey add' requires"
920 " a device, a hotkey and a command");
921 auto input = gInputs.find(args[2]);
922 if (input == gInputs.end())
923 throw MsgException("no such device: %s",
924 args[2].c_str());
925
926 const auto& hotkeydef = args[3];
927 auto dot1 = hotkeydef.find(':');
928 if (dot1 == hotkeydef.npos || dot1 >= hotkeydef.length()-1)
929 throw MsgException("invalid hotkey definition: %s",
930 hotkeydef.c_str());
931 auto dot2 = hotkeydef.find(':', dot1+1);
932 if (dot2 == hotkeydef.npos || dot2 >= hotkeydef.length()-1)
933 throw MsgException("invalid hotkey definition: %s",
934 hotkeydef.c_str());
935
936
937 unsigned int type = String2EV(hotkeydef.c_str(), dot1);
938 if (type == unsigned(-1))
939 throw MsgException("no such event type: %s",
940 hotkeydef.c_str());
941 if (type > EV_MAX)
942 throw MsgException("bad event type: %u", type);
943
944 unsigned long code = 0xffff+1;
945 if (!parseULong(&code, hotkeydef.c_str() + dot1+1, dot2-dot1-1)
946 || code > 0xffff)
947 throw MsgException("bad event code: %s",
948 hotkeydef.c_str() + dot1+1);
949 long value;
950 if (!parseLong(&value, hotkeydef.c_str() + dot2+1, size_t(-1)))
951 throw MsgException("bad event value: %s",
952 hotkeydef.c_str() + dot2+1);
953
954 string cmd = join(' ', args.begin()+4, args.end());
955 addHotkey(input->second.id_,
956 uint16_t(type), uint16_t(code), int32_t(value),
957 cmd.c_str());
958 toClient(clientfd,
959 "added hotkey %u:%u:%i for device %u\n",
960 type, code, value, input->second.id_);
961 }
962 else if (args[1] == "remove") {
963 if (args.size() != 4)
964 throw Exception(
965 "'hotkey remove': requires device and event code");
966
967 auto input = gInputs.find(args[2]);
968 if (input == gInputs.end())
969 throw MsgException("no such device: %s",
970 args[2].c_str());
971
972 const auto& hotkeydef = args[3];
973 auto dot1 = hotkeydef.find(':');
974 if (dot1 == hotkeydef.npos || dot1 >= hotkeydef.length()-1)
975 throw MsgException("invalid hotkey definition: %s",
976 hotkeydef.c_str());
977 auto dot2 = hotkeydef.find(':', dot1+1);
978 if (dot2 == hotkeydef.npos || dot2 >= hotkeydef.length()-1)
979 throw MsgException("invalid hotkey definition: %s",
980 hotkeydef.c_str());
981
982
983 unsigned int type = String2EV(hotkeydef.c_str(), dot1);
984 if (type == unsigned(-1))
985 throw MsgException("no such event type: %s",
986 hotkeydef.c_str());
987 if (type > EV_MAX)
988 throw MsgException("bad event type: %u", type);
989
990 unsigned long code = 0xffff+1;
991 if (!parseULong(&code, hotkeydef.c_str() + dot1+1, dot2-dot1-1)
992 || code > 0xffff)
993 throw MsgException("bad event code: %s",
994 hotkeydef.c_str() + dot1+1);
995 long value;
996 if (!parseLong(&value, hotkeydef.c_str() + dot2+1, size_t(-1)))
997 throw MsgException("bad event value: %s",
998 hotkeydef.c_str() + dot2+1);
999
1000 removeHotkey(input->second.id_,
1001 uint16_t(type), uint16_t(code), int32_t(value));
1002 toClient(clientfd,
1003 "removed hotkey %u:%u:%i for device %u\n",
1004 type, code, value, input->second.id_);
1005
1006 }
1007 else
1008 throw MsgException("unknown hotkey subcommand: %s",
1009 args[1].c_str());
1010 }
1011
1012 static void
clientCommand_Info(int clientfd,const vector<string> & args)1013 clientCommand_Info(int clientfd, const vector<string>& args)
1014 {
1015 (void)args;
1016
1017 toClient(clientfd, "Grab: %s\n", gGrab ? "on" : "off");
1018 toClient(clientfd, "Inputs: %zu\n", gInputs.size());
1019 for (auto& i: gInputs) {
1020 toClient(clientfd, " %u: %s: %i\n",
1021 i.second.id_,
1022 i.first.c_str(),
1023 i.second.device_->fd());
1024 }
1025
1026 toClient(clientfd, "Outputs: %zu\n", gOutputs.size());
1027 for (auto& i: gOutputs) {
1028 toClient(clientfd, " %s: %i\n",
1029 i.first.c_str(),
1030 i.second.fd());
1031 }
1032
1033 toClient(clientfd, "Current output: %i: %s\n",
1034 gCurrentOutput.fd, gCurrentOutput.name.c_str());
1035
1036 toClient(clientfd, "Hotkeys:\n");
1037 for (const auto& hi: gHotkeys) {
1038 toClient(clientfd, " %u: %s:%u:%i => %s\n",
1039 hi.first.device,
1040 EV2String(hi.first.type),
1041 hi.first.code,
1042 hi.first.value,
1043 hi.second.c_str());
1044 }
1045 toClient(clientfd, "Event actions:\n");
1046 for (const auto& i: gEventCommands) {
1047 toClient(clientfd, " '%s': %s\n",
1048 i.first.c_str(),
1049 i.second.c_str());
1050 }
1051 }
1052
1053 static void
clientCommand_Action(int clientfd,const vector<string> & args)1054 clientCommand_Action(int clientfd, const vector<string>& args)
1055 {
1056 if (args.size() < 2)
1057 throw Exception("'action': missing subcommand");
1058 const string& cmd = args[1];
1059
1060 if (args.size() < 3)
1061 throw Exception("'action': missing action");
1062 const string& action = args[2];
1063
1064 if (cmd == "remove") {
1065 if (args.size() != 3)
1066 throw Exception("'action': excess parameters");
1067 auto iter = gEventCommands.find(action);
1068 if (iter == gEventCommands.end())
1069 return;
1070 gEventCommands.erase(iter);
1071 toClient(clientfd, "removed on-'%s' command\n", action.c_str());
1072 }
1073 else if (cmd == "set") {
1074 if (args.size() < 4)
1075 throw Exception("'action': missing command");
1076 string cmdstring = join(' ', args.begin()+3, args.end());
1077 auto iter = gEventCommands.find(action);
1078 if (iter != gEventCommands.end())
1079 toClient(clientfd, "replaced on-'%s' command\n",
1080 action.c_str());
1081 else
1082 toClient(clientfd, "added on-'%s' command\n",
1083 action.c_str());
1084 gEventCommands[action] = move(cmdstring);
1085 }
1086 else
1087 throw MsgException("'action': unknown subcommand: %s",
1088 cmd.c_str());
1089 }
1090
1091 static void sourceCommandFile(int clientfd, const char *path);
1092 static void
clientCommand(int clientfd,const vector<string> & args)1093 clientCommand(int clientfd, const vector<string>& args)
1094 {
1095 if (args.empty())
1096 return;
1097
1098 if (args[0] == "nop") {
1099 } else if (args[0] == "device")
1100 clientCommand_Device(clientfd, args);
1101 else if (args[0] == "output")
1102 clientCommand_Output(clientfd, args);
1103 else if (args[0] == "hotkey")
1104 clientCommand_Hotkey(clientfd, args);
1105 else if (args[0] == "action")
1106 clientCommand_Action(clientfd, args);
1107 else if (args[0] == "info")
1108 clientCommand_Info(clientfd, args);
1109 else if (args[0] == "grab") {
1110 if (args.size() != 2)
1111 throw Exception("'grab' requires 1 parameter");
1112 grabCommand(clientfd, args[1].c_str());
1113 //toClient(clientfd, "grab = %u\n", gGrab ? 1 : 0);
1114 }
1115 else if (args[0] == "use") {
1116 if (args.size() != 2)
1117 throw Exception("'use' requires 1 parameter");
1118 useOutput(clientfd, args[1]);
1119 //toClient(clientfd, "output = %s\n",
1120 // gCurrentOutput.name.c_str());
1121 }
1122 else if (args[0] == "exec") {
1123 if (args.size() < 2)
1124 throw Exception("'exec' requires 1 parameter");
1125 string cmd = join(' ', args.begin()+1, args.end());
1126 shellCommand(cmd.c_str());
1127 }
1128 else if (args[0] == "source") {
1129 if (args.size() != 2)
1130 throw Exception("'source' requires 1 parameter");
1131 sourceCommandFile(clientfd, args[1].c_str());
1132 }
1133 else if (args[0] == "quit") {
1134 gQuit = true;
1135 }
1136 else
1137 throw MsgException("unknown command: %s", args[0].c_str());
1138
1139 if (clientfd < 0)
1140 return;
1141 // If it came from an actual client we send an OK back
1142 toClient(clientfd, "Ok.\n");
1143 }
1144
1145 static void
parseClientCommand(int clientfd,const char * cmd,size_t length)1146 parseClientCommand(int clientfd, const char *cmd, size_t length)
1147 {
1148 if (!length)
1149 return;
1150
1151 auto end = cmd + length;
1152
1153 if (!skipWhite(cmd))
1154 return;
1155
1156 vector<string> args;
1157 bool escape = false;
1158 while (cmd < end) {
1159 if (!skipWhite(cmd))
1160 break;
1161
1162 if (!escape) {
1163 if (*cmd == '\\') {
1164 ++cmd;
1165 escape = true;
1166 continue;
1167 } else if (*cmd == ';') {
1168 ++cmd;
1169 if (!args.empty()) {
1170 clientCommand(clientfd, args);
1171 args.clear();
1172 }
1173 continue;
1174 }
1175 else if (*cmd == '"' || *cmd == '\'') {
1176 args.emplace_back(parseString(cmd));
1177 continue;
1178 }
1179 }
1180
1181 escape = false;
1182 string arg;
1183 auto beg = cmd;
1184 do {
1185 if (!escape && *cmd == '\\') {
1186 arg.append(beg, cmd);
1187 ++cmd;
1188 beg = cmd;
1189 escape = true;
1190 } else {
1191 ++cmd;
1192 escape = false;
1193 }
1194 } while (*cmd && !isWhite(*cmd) && (escape || *cmd != ';'));
1195 arg.append(beg, cmd);
1196 args.emplace_back(move(arg));
1197 escape = false;
1198 }
1199
1200 if (!args.empty())
1201 clientCommand(clientfd, args);
1202 }
1203
1204 static void
processCommandQueue()1205 processCommandQueue()
1206 {
1207 for (const auto& command: gCommandQueue) {
1208 try {
1209 parseClientCommand(command.client_,
1210 command.command_.c_str(),
1211 command.command_.length());
1212 } catch (const Exception& ex) {
1213 toClient(command.client_,
1214 "ERROR: %s\n", ex.what());
1215 }
1216 }
1217 gCommandQueue.clear();
1218 }
1219
1220 static void
sourceCommandFile(int clientfd,const char * path)1221 sourceCommandFile(int clientfd, const char *path)
1222 {
1223 FILE *file = ::fopen(path, "rb");
1224 if (!file)
1225 throw ErrnoException("open(%s)", path);
1226 char *line = nullptr;
1227
1228 auto exec_guard = preExec([file,&line]() {
1229 ::fclose(file);
1230 ::free(line);
1231 });
1232 scope (exit) {
1233 ::fclose(file);
1234 ::free(line);
1235 };
1236
1237 size_t bufsize = 0;
1238 ssize_t length;
1239 while ((length = ::getline(&line, &bufsize, file)) != -1) {
1240 if (!length)
1241 continue;
1242 line[--length] = 0;
1243 const char *p = line;
1244 while (*p && isspace(*p)) {
1245 ++p;
1246 --length;
1247 }
1248 if (!*p || *p == '#')
1249 continue;
1250 parseClientCommand(clientfd, p, size_t(length));
1251 }
1252 if (::feof(file))
1253 return;
1254 if (errno)
1255 throw ErrnoException("error reading from %s", path);
1256 }
1257
1258 static void
signull(int sig)1259 signull(int sig)
1260 {
1261 switch (sig) {
1262 case SIGTERM:
1263 case SIGQUIT:
1264 case SIGINT:
1265 // but this is actually the default
1266 default:
1267 gQuit = true;
1268 break;
1269 case SIGCHLD:
1270 {
1271 int status = 0;
1272 do {
1273 // reap zombies
1274 } while (::waitpid(-1, &status, WNOHANG) > 0);
1275 break;
1276 }
1277 }
1278 }
1279
1280 int
cmd_daemon(int argc,char ** argv)1281 cmd_daemon(int argc, char **argv)
1282 {
1283 static struct option longopts[] = {
1284 { "help", no_argument, nullptr, 'h' },
1285 { "source", required_argument, nullptr, 's' },
1286 { nullptr, 0, nullptr, 0 }
1287 };
1288
1289 vector<const char*> command_files;
1290
1291 int c, optindex = 0;
1292 opterr = 1;
1293 while (true) {
1294 c = ::getopt_long(argc, argv, "hls:", longopts, &optindex);
1295 if (c == -1)
1296 break;
1297
1298 switch (c) {
1299 case 'h':
1300 usage_daemon(stdout, EXIT_SUCCESS);
1301 // break; usage is [[noreturn]]
1302 case 's':
1303 command_files.push_back(optarg);
1304 break;
1305 case '?':
1306 break;
1307 default:
1308 ::fprintf(stderr, "getopt error\n");
1309 return -1;
1310 }
1311 }
1312
1313 if (optind+1 != argc) {
1314 ::fprintf(stderr, "missing socket name\n");
1315 return 2;
1316 }
1317
1318 const char *sockname = argv[optind++];
1319
1320 signal(SIGINT, signull);
1321 signal(SIGTERM, signull);
1322 signal(SIGQUIT, signull);
1323 signal(SIGCHLD, signull);
1324 signal(SIGPIPE, SIG_IGN);
1325
1326 Socket server;
1327 if (sockname[0] == '@')
1328 server.listenUnix<true>(&sockname[1]);
1329 else {
1330 (void)::unlink(sockname);
1331 server.listenUnix<false>(sockname);
1332 }
1333
1334 gServerFD = server.fd();
1335
1336 vector<struct pollfd> pfds;
1337 pfds.resize(1);
1338 pfds[0].fd = server.fd();
1339
1340 gFDCBs[server.fd()] = FDCallbacks {
1341 [&]() { newCommandClient(server); },
1342 [ ]() { gQuit = true; },
1343 [ ]() { gQuit = true; },
1344 [ ]() { throw Exception("removed server socket"); },
1345 };
1346
1347 for (auto& i: pfds) {
1348 i.events = POLLIN | POLLHUP | POLLERR;
1349 i.revents = 0;
1350 }
1351
1352 for (auto file: command_files)
1353 sourceCommandFile(-1, file);
1354 command_files.clear();
1355 command_files.shrink_to_fit();
1356 while (!gQuit) {
1357 processCommandQueue();
1358
1359 if (!gFDAddQueue.empty()) {
1360 pfds.insert(pfds.end(), gFDAddQueue.begin(),
1361 gFDAddQueue.end());
1362 gFDAddQueue.clear();
1363 }
1364
1365 pfds.erase(
1366 std::remove_if(pfds.begin(), pfds.end(),
1367 [](struct pollfd& pfd) {
1368 return std::find(gFDRemoveQueue.begin(),
1369 gFDRemoveQueue.end(),
1370 pfd.fd)
1371 != gFDRemoveQueue.end();
1372 }
1373 ),
1374 pfds.end());
1375 processRemoveQueue();
1376
1377 // after processing commands, we may want to quit:
1378 if (gQuit)
1379 break;
1380
1381 auto got = ::poll(pfds.data(), nfds_t(pfds.size()), -1);
1382 if (got == -1) {
1383 if (errno == EINTR) {
1384 ::fprintf(stderr, "interrupted\n");
1385 continue;
1386 }
1387 throw ErrnoException("poll interrupted");
1388 }
1389 if (!got)
1390 ::fprintf(stderr, "empty poll?\n");
1391
1392 for (auto& i: pfds) {
1393 auto cbs = gFDCBs.find(i.fd);
1394 auto revents = i.revents;
1395 i.revents = 0;
1396
1397 if (cbs == gFDCBs.end())
1398 throw Exception(
1399 "internal: callback map broken");
1400
1401 if (revents & POLLERR)
1402 cbs->second.onError();
1403 if (gQuit) break;
1404 if (revents & POLLHUP)
1405 cbs->second.onHUP();
1406 if (gQuit) break;
1407 if (revents & POLLIN)
1408 cbs->second.onRead();
1409 if (gQuit) break;
1410 }
1411
1412 }
1413 ::fprintf(stderr, "shutting down\n");
1414
1415 gFDRemoveQueue.clear();
1416 gFDCBs.clear(); // destroy possible captures
1417 gCommandClients.clear(); // disconnect all clients
1418
1419 return 0;
1420 }
1421