missing NULL terminator in set_config_x
[geda-gaf.git] / gschem / src / x_controlfd.c
blobad5824c074c7e2898934aff8ec60a9d5af5fb011
1 /* gEDA - GPL Electronic Design Automation
2 * gschem - gEDA Schematic Capture
3 * Copyright (C) 1998-2010 Ales Hvezda
4 * Copyright (C) 1998-2020 gEDA Contributors (see ChangeLog for details)
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.
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.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 /*! \file x_controlfd.c
22 * \brief Remote-control gschem via a file descriptor.
24 * This feature allows project managers like Igor2's "genxproj" to
25 * issue certain actions in gschem. It is implemented as a plain text
26 * protocol and can be enabled via the command-line options
27 * `--control-fd=stdin' or `--control-fd=FD'.
29 * See \ref help_string for a list of commands.
32 #include <config.h>
34 #include "gschem.h"
36 #include <glib-unix.h>
38 static const char *help_string =
39 "The following commands are supported:\n"
40 "\n"
41 " visit FILE\n"
42 " save FILE\n"
43 " save-all\n"
44 " revert FILE\n"
45 " close FILE\n"
46 " patch-filename FILE PATCHFILE\n"
47 " import-patch FILE [PATCHFILE]\n"
48 " quit\n"
49 " help\n"
50 "\n"
51 "File paths must be absolute, except for patch filenames which are resolved\n"
52 "relative to the main file. Arguments are separated by spaces; spaces and\n"
53 "backslashes inside arguments must be escaped with a backslash.\n";
55 static int control_fd = -1;
56 static guint tag = 0;
57 static FILE *stream = NULL;
59 static gboolean can_read (gint fd, GIOCondition condition, gpointer user_data);
62 void
63 x_controlfd_parsearg (char *optarg)
65 long int val;
66 char *endptr = NULL;
68 if (strcmp (optarg, "stdin") == 0) {
69 control_fd = STDIN_FILENO;
70 return;
73 errno = 0;
74 val = strtol (optarg, &endptr, 10);
76 if ((errno != 0 && val == 0) ||
77 (errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))) {
78 fprintf (stderr, "%s: strtol: \"%s\": %s\n",
79 g_get_prgname (), optarg, strerror (errno));
80 exit (EXIT_FAILURE);
83 if (endptr == optarg || *endptr != '\0') {
84 fprintf (stderr, "%s: \"%s\": Not a valid integer\n",
85 g_get_prgname (), optarg);
86 exit (EXIT_FAILURE);
89 if (val < 0 || val > INT_MAX) {
90 fprintf (stderr, "%s: \"%s\": File descriptor out of range\n",
91 g_get_prgname (), optarg);
92 exit (EXIT_FAILURE);
95 control_fd = val;
99 void
100 x_controlfd_init (void)
102 if (control_fd != -1) {
103 stream = fdopen (control_fd, "r");
104 if (stream == NULL)
105 g_warning (_("Can't open control fd %d: %s\n"),
106 control_fd, strerror (errno));
109 if (stream != NULL)
110 tag = g_unix_fd_add (control_fd, G_IO_IN, can_read, NULL);
114 void
115 x_controlfd_free (void)
117 if (tag != 0) {
118 (void) g_source_remove (tag);
119 tag = 0;
124 /*! \brief Search for a page by filename, optionally opening it.
126 * Searches all open windows for a page with filename \a arg, and
127 * return the found window and page. The filename must be absolute
128 * and is normalized in the normal manner. If the page wasn't found
129 * and \a open_if_not_found is \c TRUE, open the file in the last
130 * openend window.
132 * \returns whether a page has been found (or opened)
134 static gboolean
135 find_page (GschemToplevel **w_current_return, PAGE **page_return,
136 const gchar *arg, gboolean open_if_not_found)
138 gchar *fn;
139 GError *err = NULL;
141 GschemToplevel *w_current = NULL;
142 PAGE *page;
144 if (!g_path_is_absolute (arg)) {
145 fprintf (stderr, "Path must be absolute\n");
146 return FALSE;
149 fn = f_normalize_filename (arg, &err);
150 if (fn == NULL) {
151 fprintf (stderr, "%s: %s\n", arg, err->message);
152 return FALSE;
155 for (const GList *l = global_window_list; l != NULL; l = l->next) {
156 w_current = GSCHEM_TOPLEVEL (l->data);
157 page = s_page_search (gschem_toplevel_get_toplevel (w_current), fn);
158 if (page == NULL || page->is_untitled)
159 continue;
161 *w_current_return = w_current;
162 *page_return = page;
163 g_free (fn);
164 return TRUE;
167 if (!open_if_not_found) {
168 fprintf (stderr, "%s: File hasn't been opened\n", fn);
169 g_free (fn);
170 return FALSE;
173 /* arbitrarily use last window to open file */
174 g_return_val_if_fail (w_current != NULL, FALSE);
175 page = x_highlevel_open_page (w_current, fn);
176 g_free (fn);
177 if (page == NULL)
178 return FALSE;
180 *w_current_return = w_current;
181 *page_return = page;
182 return TRUE;
186 /*! \brief Split argument string into individual tokens.
188 * Modifies \a buf in place.
190 * On error, prints a message and doesn't change the return arguments.
192 static void
193 split_args (gchar ***args_return, gint *count_return, gchar *buf)
195 GSList *tokens = NULL;
196 size_t len = strlen (buf);
197 size_t skip = 0;
199 do {
200 size_t sp = strchrnul (buf + skip, ' ') - buf;
201 size_t bs = strchrnul (buf + skip, '\\') - buf;
203 if (sp < bs) {
204 if (sp != 0)
205 tokens = g_slist_prepend (tokens, g_strndup (buf, sp));
206 buf += sp + 1;
207 len -= sp + 1;
208 skip = 0;
209 } else if (bs < sp) {
210 if (buf[bs + 1] != ' ' && buf[bs + 1] != '\\') {
211 fprintf (stderr, "Backslash may only be followed by space or another "
212 "backslash\n");
213 g_slist_free (tokens);
214 return;
216 memmove (buf + bs, buf + bs + 1, len - bs); /* include trailing NUL */
217 skip = bs + 1;
218 } else {
219 if (buf[0] != '\0')
220 tokens = g_slist_prepend (tokens, g_strdup (buf));
221 break;
223 } while (1);
225 gint count = g_slist_length (tokens);
226 gchar **args = g_new (char *, count);
228 GSList *l = tokens;
229 for (unsigned int i = 0; i < count; i++) {
230 args[count - i - 1] = (char *) l->data;
231 l = l->next;
234 g_slist_free (tokens);
236 *args_return = args;
237 *count_return = count;
241 /*! \brief Parse and execute the command passed in \a buf.
243 * Modifies \a buf in place.
245 static void
246 process_command (gchar *buf)
248 gchar **args = NULL;
249 gint count = 0;
251 split_args (&args, &count, buf);
253 if (count == 0) {
254 /* empty line */
255 g_free (args);
256 return;
259 GschemToplevel *w_current;
260 PAGE *page;
262 if (strcmp (args[0], "visit") == 0) {
263 if (count != 2)
264 fprintf (stderr, "Command usage: visit FILE\n");
265 else if (find_page (&w_current, &page, args[1], TRUE)) {
266 x_window_set_current_page (w_current, page);
267 x_window_present (w_current);
271 else if (strcmp (args[0], "save") == 0) {
272 if (count != 2)
273 fprintf (stderr, "Command usage: save FILE\n");
274 else if (find_page (&w_current, &page, args[1], FALSE))
275 x_highlevel_save_page (w_current, page);
278 else if (strcmp (args[0], "save-all") == 0) {
279 if (count != 1)
280 fprintf (stderr, "Command usage: save-all\n");
281 else
282 for (const GList *l = global_window_list; l != NULL; l = l->next)
283 x_highlevel_save_all (GSCHEM_TOPLEVEL (l->data));
286 else if (strcmp (args[0], "revert") == 0) {
287 if (count != 2)
288 fprintf (stderr, "Command usage: revert FILE\n");
289 else if (find_page (&w_current, &page, args[1], FALSE))
290 x_highlevel_revert_page (w_current, page);
293 else if (strcmp (args[0], "close") == 0) {
294 if (count != 2)
295 fprintf (stderr, "Command usage: close FILE\n");
296 else if (find_page (&w_current, &page, args[1], FALSE))
297 x_highlevel_close_page (w_current, page);
300 else if (strcmp (args[0], "patch-filename") == 0) {
301 if (count != 3)
302 fprintf (stderr, "Command usage: patch-filename FILE PATCHFILE\n");
303 else if (find_page (&w_current, &page, args[1], FALSE)) {
304 g_free (page->patch_filename);
305 if (g_path_is_absolute (args[2]))
306 page->patch_filename = g_strdup (args[2]);
307 else {
308 gchar *dir = g_path_get_dirname (page->page_filename);
309 page->patch_filename = g_build_filename (dir, args[2], NULL);
310 g_free (dir);
315 else if (strcmp (args[0], "import-patch") == 0) {
316 if (count != 2 && count != 3)
317 fprintf (stderr, "Command usage: import-patch FILE [PATCHFILE]\n");
318 else if (find_page (&w_current, &page, args[1], FALSE)) {
319 if (count == 3) {
320 g_free (page->patch_filename);
321 if (g_path_is_absolute (args[2]))
322 page->patch_filename = g_strdup (args[2]);
323 else {
324 gchar *dir = g_path_get_dirname (page->page_filename);
325 page->patch_filename = g_build_filename (dir, args[2], NULL);
326 g_free (dir);
329 if (page->patch_filename == NULL)
330 fprintf (stderr, "No patch filename given\n");
331 else
332 x_patch_do_import (w_current, page);
336 else if (strcmp (args[0], "quit") == 0) {
337 if (count != 1)
338 fprintf (stderr, "Command usage: quit\n");
339 else
340 x_window_close_all (NULL);
343 else if (strcmp (args[0], "help") == 0 ||
344 strcmp (args[0], "h") == 0 ||
345 strcmp (args[0], "?") == 0) {
346 if (count != 1)
347 fprintf (stderr, "Command usage: help\n");
348 else
349 fprintf (stderr, "%s", help_string);
352 else
353 fprintf (stderr, "Unknown command \"%s\"\n", args[0]);
355 for (unsigned int i = 0; i < count; i++)
356 g_free (args[i]);
357 g_free (args);
361 /*! \brief Called when \c control_fd becomes ready to read.
363 * \returns whether the function expects to be called again
365 static gboolean
366 can_read (gint fd, GIOCondition condition, gpointer user_data)
368 char *buf = NULL;
369 size_t bufsize = 0;
370 ssize_t len;
372 errno = 0;
373 len = getline (&buf, &bufsize, stream);
375 if (errno != 0) {
376 g_warning (_("Can't read from control fd %d: %s\n"),
377 control_fd, strerror (errno));
378 goto error;
381 if (len == -1) {
382 g_warning (_("Received EOF on control fd %d\n"), control_fd);
383 goto error;
386 if (len == 0) {
387 g_warning (_("Read zero bytes from control fd %d\n"), control_fd);
388 goto error;
391 buf[len - 1] = '\0';
393 process_command (buf);
395 free (buf);
396 return TRUE;
398 error:
399 free (buf);
400 fclose (stream);
401 tag = 0;
402 return FALSE;