1/*
2 * Copyright 2005, Ingo Weinhold, bonefish@users.sf.net.
3 * Distributed under the terms of the MIT License.
4 */
5
6#include <errno.h>
7#include <stdio.h>
8#include <string.h>
9#include <unistd.h>
10#include <sys/socket.h>
11#include <sys/un.h>
12
13#include "external_commands.h"
14#include "fs_shell_command_unix.h"
15
16static int sClientConnection = -1;
17
18static int
19get_command_socket()
20{
21	static int fd = -1;
22	static bool initialized = false;
23	if (!initialized) {
24		// get the listener socket
25		fd = socket(AF_UNIX, SOCK_STREAM, 0);
26		if (fd < 0)
27			return -1;
28
29		// bind it to the port
30		sockaddr_un addr;
31		unlink(kFSShellCommandSocketAddress);
32		addr.sun_family = AF_UNIX;
33		strcpy(addr.sun_path, kFSShellCommandSocketAddress);
34		int addrLen = addr.sun_path + strlen(addr.sun_path) + 1 - (char*)&addr;
35		if (bind(fd, (sockaddr*)&addr, addrLen) < 0) {
36			close(fd);
37			return -1;
38		}
39
40		// start listening
41		if (listen(fd, 1) < 0) {
42			close(fd);
43			return -1;
44		}
45
46		initialized = true;
47	}
48
49	return fd;
50}
51
52
53static int
54get_client_connection()
55{
56	if (sClientConnection >= 0)
57		return sClientConnection;
58
59	// get the listener socket
60	int commandFD = get_command_socket();
61	if (commandFD < 0)
62		return -1;
63
64	// accept a connection
65	do {
66		sockaddr_un addr;
67		socklen_t addrLen = sizeof(addr);
68		sClientConnection = accept(commandFD, (sockaddr*)&addr, &addrLen);
69	} while (sClientConnection < 0 && errno == EINTR);
70
71	return sClientConnection;
72}
73
74
75static void
76close_client_connection()
77{
78	if (sClientConnection >= 0) {
79		close(sClientConnection);
80		sClientConnection = -1;
81	}
82}
83
84
85char *
86get_external_command(const char *prompt, char *input, int len)
87{
88	do {
89		// get a connection
90		int connection = get_client_connection();
91		if (connection < 0)
92			return NULL;
93
94		// read until we have a full command
95		external_command_message message;
96		int toRead = sizeof(message);
97		char *buffer = (char*)&message;
98		while (toRead > 0) {
99			int bytesRead = read(connection, buffer, toRead);
100			if (bytesRead < 0) {
101				if (errno == EINTR) {
102					continue;
103				} else {
104					fprintf(stderr, "Reading from connection failed: %s\n", strerror(errno));
105					break;
106				}
107			}
108
109			// connection closed?
110			if (bytesRead == 0)
111				break;
112
113			buffer += bytesRead;
114			toRead -= bytesRead;
115		}
116
117		// connection may be broken: discard it
118		if (toRead > 0) {
119			close_client_connection();
120			continue;
121		}
122
123		// get the len of the command
124		message.command[sizeof(message.command) - 1] = '\0';
125		int commandLen = strlen(message.command) + 1;
126		if (commandLen <= 1) {
127			fprintf(stderr, "No command given.\n");
128			continue;
129		}
130		if (commandLen > len) {
131			fprintf(stderr, "Command too long. Ignored.\n");
132			continue;
133		}
134
135		// copy the command
136		memcpy(input, message.command, commandLen);
137		input[len - 1] = '\0';	// always NULL-terminate
138		return input;
139
140	} while (true);
141}
142
143
144void
145reply_to_external_command(int result)
146{
147	if (sClientConnection < 0)
148		return;
149
150	// prepare the reply
151	external_command_reply reply;
152	reply.error = result;
153
154	// send the reply
155	int toWrite = sizeof(reply);
156	char *replyBuffer = (char*)&reply;
157	ssize_t bytesWritten;
158	do {
159		bytesWritten = write(sClientConnection, replyBuffer, toWrite);
160		if (bytesWritten > 0) {
161			replyBuffer += bytesWritten;
162			toWrite -= bytesWritten;
163		}
164	} while (toWrite > 0 && !(bytesWritten < 0 && errno != EINTR));
165
166	// connection may be broken: discard it
167	if (bytesWritten < 0)
168		close_client_connection();
169}
170
171
172void
173external_command_cleanup()
174{
175	unlink(kFSShellCommandSocketAddress);
176}
177