diff options
author | minus <minus@mnus.de> | 2015-08-19 01:35:01 +0200 |
---|---|---|
committer | minus <minus@mnus.de> | 2015-08-20 15:24:33 +0200 |
commit | 773e85c681ee4faecf353de7066b536f1a50ff61 (patch) | |
tree | b238ed5c6fb89b5970fad05581e23713c540ef01 /sway/ipc.c | |
parent | added i3-ipc support/parsing (diff) | |
download | sway-773e85c681ee4faecf353de7066b536f1a50ff61.tar.gz sway-773e85c681ee4faecf353de7066b536f1a50ff61.tar.zst sway-773e85c681ee4faecf353de7066b536f1a50ff61.zip |
properly handle IPC clients
Diffstat (limited to 'sway/ipc.c')
-rw-r--r-- | sway/ipc.c | 191 |
1 files changed, 131 insertions, 60 deletions
@@ -1,3 +1,5 @@ | |||
1 | // See https://i3wm.org/docs/ipc.html for protocol information | ||
2 | |||
1 | #include <errno.h> | 3 | #include <errno.h> |
2 | #include <string.h> | 4 | #include <string.h> |
3 | #include <sys/socket.h> | 5 | #include <sys/socket.h> |
@@ -6,6 +8,8 @@ | |||
6 | #include <wlc/wlc.h> | 8 | #include <wlc/wlc.h> |
7 | #include <unistd.h> | 9 | #include <unistd.h> |
8 | #include <stdlib.h> | 10 | #include <stdlib.h> |
11 | #include <stropts.h> | ||
12 | #include <sys/ioctl.h> | ||
9 | #include "ipc.h" | 13 | #include "ipc.h" |
10 | #include "log.h" | 14 | #include "log.h" |
11 | #include "config.h" | 15 | #include "config.h" |
@@ -15,10 +19,18 @@ static int ipc_socket = -1; | |||
15 | 19 | ||
16 | static const char ipc_magic[] = {'i', '3', '-', 'i', 'p', 'c'}; | 20 | static const char ipc_magic[] = {'i', '3', '-', 'i', 'p', 'c'}; |
17 | 21 | ||
18 | int ipc_handle_connection(int fd, uint32_t mask, void *data); | 22 | struct ipc_client { |
19 | size_t ipc_handle_command(char **reply_data, char *data, ssize_t length); | 23 | struct wlc_event_source *event_source; |
20 | size_t ipc_format_reply(char **data, enum ipc_command_type command_type, const char *payload, uint32_t payload_length); | 24 | int fd; |
25 | uint32_t payload_length; | ||
26 | enum ipc_command_type current_command; | ||
27 | }; | ||
21 | 28 | ||
29 | int ipc_handle_connection(int fd, uint32_t mask, void *data); | ||
30 | int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data); | ||
31 | void ipc_client_disconnect(struct ipc_client *client); | ||
32 | void ipc_client_handle_command(struct ipc_client *client); | ||
33 | bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t payload_length); | ||
22 | 34 | ||
23 | void init_ipc() { | 35 | void init_ipc() { |
24 | ipc_socket = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); | 36 | ipc_socket = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); |
@@ -46,91 +58,150 @@ void init_ipc() { | |||
46 | 58 | ||
47 | int ipc_handle_connection(int fd, uint32_t mask, void *data) { | 59 | int ipc_handle_connection(int fd, uint32_t mask, void *data) { |
48 | sway_log(L_DEBUG, "Event on IPC listening socket"); | 60 | sway_log(L_DEBUG, "Event on IPC listening socket"); |
49 | int client_socket = accept(ipc_socket, NULL, NULL); | 61 | assert(mask == WLC_EVENT_READABLE); |
50 | if (client_socket == -1) { | ||
51 | char error[256]; | ||
52 | strerror_r(errno, error, sizeof(error)); | ||
53 | sway_log(L_INFO, "Unable to accept IPC client connection: %s", error); | ||
54 | return 0; | ||
55 | } | ||
56 | 62 | ||
57 | char buf[1024]; | 63 | int client_fd = accept(ipc_socket, NULL, NULL); |
58 | // Leave one byte of space at the end of the buffer for NULL terminator | 64 | if (client_fd == -1) { |
59 | ssize_t received = recv(client_socket, buf, sizeof(buf) - 1, 0); | 65 | sway_log_errno(L_INFO, "Unable to accept IPC client connection"); |
60 | if (received == -1) { | ||
61 | char error[256]; | ||
62 | strerror_r(errno, error, sizeof(error)); | ||
63 | sway_log(L_INFO, "Unable to receive from IPC client: %s", error); | ||
64 | close(client_socket); | ||
65 | return 0; | 66 | return 0; |
66 | } | 67 | } |
67 | 68 | ||
68 | char *reply_buf; | 69 | struct ipc_client* client = malloc(sizeof(struct ipc_client)); |
69 | size_t reply_length = ipc_handle_command(&reply_buf, buf, received); | 70 | client->payload_length = 0; |
70 | sway_log(L_DEBUG, "IPC reply: %s", reply_buf); | 71 | client->fd = client_fd; |
72 | client->event_source = wlc_event_loop_add_fd(client_fd, WLC_EVENT_READABLE, ipc_client_handle_readable, client); | ||
71 | 73 | ||
72 | if (send(client_socket, reply_buf, reply_length, 0) == -1) { | ||
73 | char error[256]; | ||
74 | strerror_r(errno, error, sizeof(error)); | ||
75 | sway_log(L_INFO, "Unable to send to IPC client: %s", error); | ||
76 | } | ||
77 | |||
78 | free(reply_buf); | ||
79 | close(client_socket); | ||
80 | return 0; | 74 | return 0; |
81 | } | 75 | } |
82 | 76 | ||
83 | static const int ipc_header_size = sizeof(ipc_magic)+8; | 77 | static const int ipc_header_size = sizeof(ipc_magic)+8; |
84 | 78 | ||
85 | size_t ipc_handle_command(char **reply_data, char *data, ssize_t length) { | 79 | int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data) { |
86 | // See https://i3wm.org/docs/ipc.html for protocol details | 80 | struct ipc_client *client = data; |
81 | sway_log(L_DEBUG, "Event on IPC client socket %d", client_fd); | ||
87 | 82 | ||
88 | if (length < ipc_header_size) { | 83 | if (mask & WLC_EVENT_ERROR) { |
89 | sway_log(L_DEBUG, "IPC data too short"); | 84 | sway_log(L_INFO, "IPC Client socket error, removing client"); |
90 | return false; | 85 | ipc_client_disconnect(client); |
86 | return 0; | ||
87 | } | ||
88 | |||
89 | if (mask & WLC_EVENT_HANGUP) { | ||
90 | ipc_client_disconnect(client); | ||
91 | return 0; | ||
91 | } | 92 | } |
92 | 93 | ||
93 | if (memcmp(data, ipc_magic, sizeof(ipc_magic)) != 0) { | 94 | int read_available; |
95 | ioctl(client_fd, FIONREAD, &read_available); | ||
96 | |||
97 | // Wait for the rest of the command payload in case the header has already been read | ||
98 | if (client->payload_length > 0) { | ||
99 | if (read_available >= client->payload_length) { | ||
100 | ipc_client_handle_command(client); | ||
101 | } | ||
102 | else { | ||
103 | sway_log(L_DEBUG, "Too little data to read payload on IPC Client socket, waiting for more (%d < %d)", read_available, client->payload_length); | ||
104 | } | ||
105 | return 0; | ||
106 | } | ||
107 | |||
108 | if (read_available < ipc_header_size) { | ||
109 | sway_log(L_DEBUG, "Too little data to read header on IPC Client socket, waiting for more (%d < %d)", read_available, ipc_header_size); | ||
110 | return 0; | ||
111 | } | ||
112 | |||
113 | char buf[ipc_header_size]; | ||
114 | ssize_t received = recv(client_fd, buf, ipc_header_size, 0); | ||
115 | if (received == -1) { | ||
116 | sway_log_errno(L_INFO, "Unable to receive header from IPC client"); | ||
117 | ipc_client_disconnect(client); | ||
118 | return 0; | ||
119 | } | ||
120 | |||
121 | if (memcmp(buf, ipc_magic, sizeof(ipc_magic)) != 0) { | ||
94 | sway_log(L_DEBUG, "IPC header check failed"); | 122 | sway_log(L_DEBUG, "IPC header check failed"); |
95 | return false; | 123 | ipc_client_disconnect(client); |
124 | return 0; | ||
96 | } | 125 | } |
97 | 126 | ||
98 | uint32_t payload_length = *(uint32_t *)&data[sizeof(ipc_magic)]; | 127 | client->payload_length = *(uint32_t *)&buf[sizeof(ipc_magic)]; |
99 | uint32_t command_type = *(uint32_t *)&data[sizeof(ipc_magic)+4]; | 128 | client->current_command = (enum ipc_command_type) *(uint32_t *)&buf[sizeof(ipc_magic)+4]; |
100 | 129 | ||
101 | if (length != payload_length + ipc_header_size) { | 130 | if (read_available - received >= client->payload_length) { |
102 | // TODO: try to read enough data | 131 | ipc_client_handle_command(client); |
103 | sway_log(L_DEBUG, "IPC payload size mismatch"); | 132 | } |
104 | return false; | 133 | |
134 | return 0; | ||
135 | } | ||
136 | |||
137 | void ipc_client_disconnect(struct ipc_client *client) | ||
138 | { | ||
139 | if (!sway_assert(client != NULL, "client != NULL")) { | ||
140 | return; | ||
141 | } | ||
142 | |||
143 | sway_log(L_INFO, "IPC Client %d disconnected", client->fd); | ||
144 | wlc_event_source_remove(client->event_source); | ||
145 | close(client->fd); | ||
146 | free(client); | ||
147 | } | ||
148 | |||
149 | void ipc_client_handle_command(struct ipc_client *client) { | ||
150 | if (!sway_assert(client != NULL, "client != NULL")) { | ||
151 | return; | ||
152 | } | ||
153 | |||
154 | char buf[client->payload_length + 1]; | ||
155 | if (client->payload_length > 0) | ||
156 | { | ||
157 | ssize_t received = recv(client->fd, buf, client->payload_length, 0); | ||
158 | if (received == -1) | ||
159 | { | ||
160 | sway_log_errno(L_INFO, "Unable to receive payload from IPC client"); | ||
161 | ipc_client_disconnect(client); | ||
162 | return; | ||
163 | } | ||
105 | } | 164 | } |
106 | 165 | ||
107 | switch (command_type) { | 166 | switch (client->current_command) { |
108 | case IPC_COMMAND: | 167 | case IPC_COMMAND: |
109 | { | 168 | { |
110 | char *cmd = &data[ipc_header_size]; | 169 | buf[client->payload_length] = '\0'; |
111 | data[ipc_header_size + payload_length] = '\0'; | 170 | bool success = handle_command(config, buf); |
112 | bool success = handle_command(config, cmd); | 171 | char reply[64]; |
113 | char buf[64]; | 172 | int length = snprintf(reply, sizeof(reply), "{\"success\":%s}", success ? "true" : "false"); |
114 | int length = snprintf(buf, sizeof(buf), "{\"success\":%s}", success ? "true" : "false"); | 173 | ipc_send_reply(client, reply, (uint32_t) length); |
115 | return ipc_format_reply(reply_data, IPC_COMMAND, buf, (uint32_t) length); | 174 | break; |
116 | } | 175 | } |
117 | default: | 176 | default: |
118 | sway_log(L_INFO, "Unknown IPC command type %i", command_type); | 177 | sway_log(L_INFO, "Unknown IPC command type %i", client->current_command); |
119 | return false; | 178 | ipc_client_disconnect(client); |
179 | break; | ||
120 | } | 180 | } |
181 | |||
182 | client->payload_length = 0; | ||
121 | } | 183 | } |
122 | 184 | ||
123 | size_t ipc_format_reply(char **data, enum ipc_command_type command_type, const char *payload, uint32_t payload_length) { | 185 | bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t payload_length) { |
124 | assert(data); | ||
125 | assert(payload); | 186 | assert(payload); |
126 | 187 | ||
127 | size_t length = ipc_header_size + payload_length; | 188 | char data[ipc_header_size]; |
128 | *data = malloc(length); | ||
129 | 189 | ||
130 | memcpy(*data, ipc_magic, sizeof(ipc_magic)); | 190 | memcpy(data, ipc_magic, sizeof(ipc_magic)); |
131 | *(uint32_t *)&((*data)[sizeof(ipc_magic)]) = payload_length; | 191 | *(uint32_t *)&(data[sizeof(ipc_magic)]) = payload_length; |
132 | *(uint32_t *)&((*data)[sizeof(ipc_magic)+4]) = command_type; | 192 | *(uint32_t *)&(data[sizeof(ipc_magic)+4]) = client->current_command; |
133 | memcpy(&(*data)[ipc_header_size], payload, payload_length); | 193 | |
194 | if (write(client->fd, data, ipc_header_size) == -1) { | ||
195 | sway_log_errno(L_INFO, "Unable to send header to IPC client"); | ||
196 | ipc_client_disconnect(client); | ||
197 | return false; | ||
198 | } | ||
199 | |||
200 | if (write(client->fd, payload, payload_length) == -1) { | ||
201 | sway_log_errno(L_INFO, "Unable to send payload to IPC client"); | ||
202 | ipc_client_disconnect(client); | ||
203 | return false; | ||
204 | } | ||
134 | 205 | ||
135 | return length; | 206 | return true; |
136 | } | 207 | } |