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