Use a program run helper rather than a script
[geany-mirror.git] / src / geany-run-helper.c
blobe15dfac7682d3b1d15ec8688ae10dcf92216ed1d
1 /*
2 * geany-run-helper.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2016 Colomban Wendling <ban(at)herbesfolles(dot)org>
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 along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 /* Helper program to run a command, print its return code and wait on the user */
23 #include <glib.h>
24 #include <stdio.h>
26 #ifdef G_OS_WIN32
29 * Uses GetCommandLineW() and CreateProcessW(). It would be a lot shorter to use
30 * _wspawnvp(), but like other argv-based Windows APIs (exec* family) it is broken
31 * when it comes to "control" characters in the arguments like spaces and quotes:
32 * it seems to basically do `CreateProcessW(" ".join(argv))`, which means it
33 * re-interprets it as a command line a second time.
35 * Interesting read: https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
37 * FIXME: maybe just use spawn.c itself? That would make the actual logic more
38 * convoluted (trip around from commandline (UTF16) -> argv (UTF8) -> commandline (UTF16))
39 * but would have all the spawn logic in one place.
41 * FIXME: handle cmd.exe's quoting rules? Does that even apply on cmd.exe's
42 * command line itself, or only inside the script? (it probably applies to the
43 * argument of /C I guess). That would mean to have a special mode for this,
44 * just know we're calling it, or inspect the first argument of what we are
45 * supposed to launch and figure out whether it's cmd.exe or not. Darn it.
48 #include <windows.h>
50 static void w32_perror(const gchar *prefix)
52 gchar *msg = g_win32_error_message(GetLastError());
53 fprintf(stderr, "%s: %s\n", prefix, msg);
54 g_free(msg);
57 /* Based on spawn_get_program_name().
58 * FIXME: this seems unable to handle an argument containing an escaped quote,
59 * but OTOH we expect the cmdline to be valid and Windows doesn't allow quotes
60 * in filenames */
61 static LPWSTR w32_strip_first_arg(LPWSTR command_line)
63 while (*command_line && wcschr(L" \t\r\n", *command_line))
64 command_line++;
66 if (*command_line == L'"')
68 command_line++;
69 LPWSTR p = wcschr(command_line, L'"');
70 if (p)
71 command_line = p + 1;
72 else
73 command_line = wcschr(command_line, L'\0');
75 else
77 while (*command_line && ! wcschr(L" \t", *command_line))
78 command_line++;
81 while (*command_line && wcschr(L" \t\r\n", *command_line))
82 command_line++;
84 return command_line;
87 int main(void)
89 int exit_status = 1;
90 STARTUPINFOW startup;
91 PROCESS_INFORMATION process;
92 LPWSTR command_line = GetCommandLineW();
93 LPWSTR auto_close_arg;
95 ZeroMemory(&startup, sizeof startup);
96 startup.cb = sizeof startup;
98 auto_close_arg = command_line = w32_strip_first_arg(command_line); // strip argv[0]
99 command_line = w32_strip_first_arg(command_line); // strip argv[1]
100 if (! command_line || ! *command_line)
101 fprintf(stderr, "Invalid or missing command\n");
102 else if ((auto_close_arg[0] != L'0' && auto_close_arg[0] != L'1') ||
103 ! isspace(auto_close_arg[1]))
104 fprintf(stderr, "USAGE: geany-run-script 0|1 command...\n");
105 else if (! CreateProcessW(NULL, command_line, NULL, NULL, TRUE, 0, NULL, NULL, &startup, &process))
106 w32_perror("CreateProcessW()");
107 else
109 DWORD code;
111 CloseHandle(process.hThread);
112 if (WaitForSingleObject(process.hProcess, INFINITE) == WAIT_FAILED)
113 w32_perror("WaitForSingleObject()");
114 else if (! GetExitCodeProcess(process.hProcess, &code))
115 w32_perror("GetExitCodeProcess()");
116 else
118 printf("\n\n------------------\n");
119 printf("(program exited with status %d)\n", code);
120 exit_status = code;
122 CloseHandle(process.hProcess);
125 if (*auto_close_arg != L'1')
127 printf("Press return to continue\n");
128 getc(stdin);
131 return exit_status;
134 #else
136 #include <sys/types.h>
137 #include <sys/wait.h>
138 #include <unistd.h>
139 #include <stdio.h>
140 #include <errno.h>
142 int main(int argc, char **argv)
144 int exit_status = 1;
145 const char *auto_close_arg;
147 if (argc < 3 || ((argv[1][0] != '0' && argv[1][0] != '1') || argv[1][1] != 0))
149 fprintf(stderr, "USAGE: %s 1|0 command...\n", argv[0]);
150 return 1;
153 auto_close_arg = argv[1];
154 /* strip argv[0] and auto-close argument */
155 argv += 2;
156 argc -= 2;
158 pid_t pid = fork();
159 if (pid < 0)
160 perror("fork()");
161 else if (pid == 0)
163 /* in the child */
164 execvp(*argv, argv);
165 perror("execvp()");
166 return 127;
168 else
170 int status;
171 int ret;
175 ret = waitpid(pid, &status, 0);
177 while (ret == -1 && errno == EINTR);
179 printf("\n\n------------------\n");
180 if (ret == -1)
181 perror("waitpid()");
182 else if (WIFEXITED(status))
184 printf("(program exited with status %d)\n", WEXITSTATUS(status));
185 exit_status = WEXITSTATUS(status);
187 else if (WIFSIGNALED(status))
189 printf("(program exited with signal %d)\n", WTERMSIG(status));
190 #ifdef WCOREDUMP
191 if (WCOREDUMP(status))
192 printf("(core dumped)\n");
193 #endif
195 else
196 fprintf(stderr, "something funky happened to the child\n");
199 if (*auto_close_arg != '1')
201 printf("Press return to continue\n");
202 getc(stdin);
205 return exit_status;
208 #endif