drm/linux: Add list_for_each_entry_safe_reverse()
[dragonfly.git] / sbin / svc / remote.c
1 /*
2  * Copyright (c) 2014 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Matthew Dillon <dillon@backplane.com>
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 /*
35  * Handle remote listen/connect and parsing operations.
36  */
37
38 #include "svc.h"
39
40 typedef struct SvcConnect {
41         struct SvcConnect *next;
42         command_t *cmd;
43         char      *label;
44         pthread_t td;
45         int     active;
46         int     fd;
47         int     rc;
48         FILE    *fpr;
49         FILE    *fpw;
50 } connect_t;
51
52 static void *remote_connect_thread(void *arg);
53 static void *remote_listener_thread(void *arg);
54 static void *remote_accepted_thread(void *arg);
55 static void remote_issue(connect_t *conn, command_t *cmd);
56 static int decode_args(connect_t *conn, char ***avp,
57                         const char *ptr, size_t len);
58
59 connect_t *CHead;
60 connect_t **CNextP = &CHead;
61
62
63 /*
64  * Execute cmd on the running service by connecting to the service, passing-in
65  * the command, and processing results.
66  *
67  * Called only by master process
68  */
69 void
70 remote_execute(command_t *cmd, const char *label)
71 {
72         connect_t *conn = calloc(sizeof(*conn), 1);
73
74         conn->fd = -1;
75         conn->cmd = cmd;
76         conn->label = strdup(label);
77         conn->active = 1;
78         conn->next = *CNextP;
79         *CNextP = conn;
80
81         pthread_create(&conn->td, NULL, remote_connect_thread, conn);
82 }
83
84 /*
85  * Threaded connect/execute
86  */
87 static
88 void *
89 remote_connect_thread(void *arg)
90 {
91         connect_t *conn;
92         command_t *cmd;
93         struct sockaddr_un sou;
94         size_t len;
95         char *ptr;
96
97         conn = arg;
98         cmd = conn->cmd;
99
100         bzero(&sou, sizeof(sou));
101         if ((conn->fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0) {
102                 sou.sun_family = AF_UNIX;
103                 snprintf(sou.sun_path, sizeof(sou.sun_path),
104                          "%s/service.%s.sk", cmd->piddir, conn->label);
105                 len = strlen(sou.sun_path);
106                 len = offsetof(struct sockaddr_un, sun_path[len+1]);
107
108                 if (connect(conn->fd, (void *)&sou, len) < 0) {
109                         close(conn->fd);
110                         conn->fd = -1;
111                 }
112         }
113         if (conn->fd >= 0) {
114                 /*
115                  * Issue command
116                  */
117                 conn->fpr = fdopen(conn->fd, "r");
118                 conn->fpw = fdopen(dup(conn->fd), "w");
119                 conn->fd = -1;
120                 setvbuf(conn->fpr, NULL, _IOFBF, 0);
121                 setvbuf(conn->fpw, NULL, _IOFBF, 0);
122                 remote_issue(conn, cmd);
123                 while ((ptr = fgetln(conn->fpr, &len)) != NULL) {
124                         if (len == 2 && ptr[0] == '.' && ptr[1] == '\n')
125                                 break;
126
127                         /*
128                          * de-escape ..
129                          */
130                         if (len == 3 && ptr[0] == '.' && ptr[1] == '.' &&
131                             ptr[2] == '\n') {
132                                 ++ptr;
133                                 --len;
134                         }
135                         fwrite(ptr, 1, len, cmd->fp);
136                         fflush(cmd->fp);
137                 }
138                 conn->rc = 0;
139                 conn->active = 0;
140                 fclose(conn->fpr);
141                 fclose(conn->fpw);
142                 conn->fpr = NULL;
143                 conn->fpw = NULL;
144         } else {
145                 /*
146                  * Connection failed
147                  */
148                 fprintf(cmd->fp,
149                         "Unable to connect to service %s\n",
150                         conn->label);
151                 conn->rc = 1;
152                 conn->active = 0;
153                 if (cmd->force_remove_files) {
154                         fprintf(cmd->fp,
155                                 "Removing pid and socket files for %s\n",
156                                 conn->label);
157                         remove_pid_and_socket(cmd, conn->label);
158                 }
159         }
160         return NULL;
161 }
162
163 /*
164  * Called only by master process
165  *
166  * Collect status from all remote commands.
167  */
168 int
169 remote_wait(void)
170 {
171         connect_t *scan;
172         int rc = 0;
173
174         while ((scan = CHead) != NULL) {
175                 if (pthread_join(scan->td, NULL) < 0)
176                         continue;
177                 assert(scan->active == 0);
178                 rc += scan->rc;
179                 CHead = scan->next;
180
181                 if (scan->fpr) {
182                         fclose(scan->fpr);
183                         scan->fpr = NULL;
184                 }
185                 if (scan->fpw) {
186                         fclose(scan->fpw);
187                         scan->fpw = NULL;
188                 }
189                 if (scan->fd >= 0) {
190                         close(scan->fd);
191                         scan->fd = -1;
192                 }
193                 if (scan->label) {
194                         free(scan->label);
195                         scan->label = NULL;
196                 }
197                 scan->cmd = NULL;
198                 free(scan);
199         }
200         return rc;
201 }
202
203 /*
204  * Create the unix domain socket and pid file for the service
205  * and start a thread to accept and process connections.
206  *
207  * Return 0 on success, non-zero if the socket could not be created.
208  */
209 void
210 remote_listener(command_t *cmd, int lfd)
211 {
212         connect_t *conn;
213
214         /*
215          * child, create our unix domain socket listener thread.
216          */
217         conn = calloc(sizeof(*conn), 1);
218         conn->fd = lfd;
219         conn->cmd = cmd;
220         conn->label = strdup(cmd->label);
221         conn->active = 1;
222
223         conn->next = *CNextP;
224         *CNextP = conn;
225         pthread_create(&conn->td, NULL, remote_listener_thread, conn);
226 }
227
228 static void *
229 remote_listener_thread(void *arg)
230 {
231         connect_t *lconn = arg;
232         connect_t *conn;
233         struct sockaddr_un sou;
234         socklen_t len;
235
236         conn = calloc(sizeof(*conn), 1);
237         for (;;) {
238                 len = sizeof(sou);
239                 conn->fd = accept(lconn->fd, (void *)&sou, &len);
240                 conn->label = strdup(lconn->label);
241                 if (conn->fd < 0) {
242                         if (errno == EINTR)
243                                 continue;
244                         break;
245                 }
246                 pthread_create(&conn->td, NULL, remote_accepted_thread, conn);
247                 conn = calloc(sizeof(*conn), 1);
248         }
249         free(conn);
250
251         return NULL;
252 }
253
254 static void *
255 remote_accepted_thread(void *arg)
256 {
257         connect_t *conn = arg;
258         command_t cmd;
259         char *ptr;
260         size_t len;
261         int rc;
262         int ac;
263         char **av;
264
265         pthread_detach(conn->td);
266         conn->fpr = fdopen(conn->fd, "r");
267         conn->fpw = fdopen(dup(conn->fd), "w");
268         conn->fd = -1;
269         setvbuf(conn->fpr, NULL, _IOFBF, 0);
270         setvbuf(conn->fpw, NULL, _IOFBF, 0);
271
272         while ((ptr = fgetln(conn->fpr, &len)) != NULL) {
273                 ac = decode_args(conn, &av, ptr, len);
274                 rc = process_cmd(&cmd, conn->fpw, ac, av);
275                 cmd.cmdline = 0;        /* we are the remote */
276                 cmd.commanded = 1;      /* commanded action (vs automatic) */
277                 sreplace(&cmd.label, conn->label);
278                 if (rc == 0) {
279                         pthread_mutex_lock(&serial_mtx);
280                         rc = execute_cmd(&cmd);
281                         pthread_mutex_unlock(&serial_mtx);
282                 }
283                 free_cmd(&cmd);
284                 afree(&av);
285                 fwrite(".\n", 2, 1, conn->fpw);
286                 fflush(conn->fpw);
287         }
288         fclose(conn->fpr);
289         fclose(conn->fpw);
290         conn->fpr = NULL;
291         conn->fpw = NULL;
292         free(conn->label);
293         free(conn);
294
295         return NULL;
296 }
297
298 /*
299  * Issue the command to the remote, encode the arguments.
300  */
301 static
302 void
303 remote_issue(connect_t *conn, command_t *cmd)
304 {
305         int i;
306
307         for (i = 1; i < cmd->orig_ac; ++i) {
308                 const char *str = cmd->orig_av[i];
309
310                 if (i != 1)
311                         putc(' ', conn->fpw);
312                 while (*str) {
313                         if (*str == ' ' || *str == '\\' || *str == '\n')
314                                 putc('\\', conn->fpw);
315                         putc(*str, conn->fpw);
316                         ++str;
317                 }
318         }
319         putc('\n', conn->fpw);
320         fflush(conn->fpw);
321 }
322
323 /*
324  * Decode arguments
325  */
326 static int
327 decode_args(connect_t *conn __unused, char ***avp, const char *ptr, size_t len)
328 {
329         char **av;
330         char *arg;
331         size_t i;
332         size_t j;
333         int acmax;
334         int ac;
335
336         if (len && ptr[len-1] == '\n')
337                 --len;
338
339         acmax = 3;      /* av[0], first arg, terminating NULL */
340         for (i = 0; i < len; ++i) {
341                 if (ptr[i] == ' ')
342                         ++acmax;
343         }
344         av = calloc(sizeof(char *), acmax);
345         av[0] = NULL;
346         ac = 1;
347
348         i = 0;
349         while (i < len) {
350                 for (j = i; j < len; ++j) {
351                         if (ptr[j] == ' ')
352                                 break;
353                 }
354                 arg = malloc(j - i + 1);        /* worst case arg size */
355                 j = 0;
356                 while (i < len) {
357                         if (ptr[i] == ' ')
358                                 break;
359                         if (ptr[i] == '\\' && i + 1 < len) {
360                                 arg[j++] = ptr[i+1];
361                                 i += 2;
362                         } else {
363                                 arg[j++] = ptr[i];
364                                 i += 1;
365                         }
366                 }
367                 arg[j] = 0;
368                 av[ac++] = arg;
369                 if (i < len && ptr[i] == ' ')
370                         ++i;
371         }
372         av[ac] = NULL;
373
374 #if 0
375         fprintf(conn->fpw, "DECODE ARGS: ");
376         for (i = 1; i < (size_t)ac; ++i)
377                 fprintf(conn->fpw, " \"%s\"", av[i]);
378         fprintf(conn->fpw, "\n");
379         fflush(conn->fpw);
380 #endif
381
382         *avp = av;
383         return ac;
384 }