]> git.saurik.com Git - cycript.git/blob - Console.cpp
9a60679ce5d2b3aab178307b026b5c872bd17694
[cycript.git] / Console.cpp
1 /* Cycript - Remove Execution Server and Disassembler
2 * Copyright (C) 2009 Jay Freeman (saurik)
3 */
4
5 /* Modified BSD License {{{ */
6 /*
7 * Redistribution and use in source and binary
8 * forms, with or without modification, are permitted
9 * provided that the following conditions are met:
10 *
11 * 1. Redistributions of source code must retain the
12 * above copyright notice, this list of conditions
13 * and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the
15 * above copyright notice, this list of conditions
16 * and the following disclaimer in the documentation
17 * and/or other materials provided with the
18 * distribution.
19 * 3. The name of the author may not be used to endorse
20 * or promote products derived from this software
21 * without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS''
24 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
25 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
26 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
28 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
30 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
33 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
34 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
35 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
36 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 */
38 /* }}} */
39
40 #define _GNU_SOURCE
41
42 #include <substrate.h>
43 #include "cycript.hpp"
44
45 #include <cstdio>
46 #include <sstream>
47
48 #include <setjmp.h>
49
50 #include <readline/readline.h>
51 #include <readline/history.h>
52
53 #include <sys/mman.h>
54
55 #include <errno.h>
56 #include <unistd.h>
57
58 #include <sys/types.h>
59 #include <sys/stat.h>
60 #include <fcntl.h>
61
62 #include "Cycript.tab.hh"
63
64 #include <sys/types.h>
65 #include <sys/socket.h>
66 #include <netinet/in.h>
67 #include <sys/un.h>
68
69 static volatile enum {
70 Working,
71 Parsing,
72 Running,
73 Sending,
74 Waiting,
75 } mode_;
76
77 static jmp_buf ctrlc_;
78
79 static void sigint(int) {
80 switch (mode_) {
81 case Working:
82 return;
83 case Parsing:
84 longjmp(ctrlc_, 1);
85 case Running:
86 throw "*** Ctrl-C";
87 case Sending:
88 return;
89 case Waiting:
90 return;
91 }
92 }
93
94 #if YYDEBUG
95 static bool bison_;
96 #endif
97 static bool strict_;
98 static bool pretty_;
99
100 void Setup(CYDriver &driver, cy::parser &parser) {
101 #if YYDEBUG
102 if (bison_)
103 parser.set_debug_level(1);
104 #endif
105 if (strict_)
106 driver.strict_ = true;
107 }
108
109 void Setup(CYOutput &out, CYDriver &driver) {
110 out.pretty_ = pretty_;
111
112 CYContext context(driver.pool_);
113 driver.program_->Replace(context);
114 }
115
116 void Run(int socket, const char *data, size_t size, FILE *fout = NULL, bool expand = false) {
117 CYPool pool;
118
119 const char *json;
120 if (socket == -1) {
121 mode_ = Running;
122 json = CYExecute(pool, data);
123 mode_ = Working;
124 if (json != NULL)
125 size = strlen(json);
126 } else {
127 mode_ = Sending;
128 CYSendAll(socket, &size, sizeof(size));
129 CYSendAll(socket, data, size);
130 mode_ = Waiting;
131 CYRecvAll(socket, &size, sizeof(size));
132 if (size == _not(size_t))
133 json = NULL;
134 else {
135 char *temp(new(pool) char[size + 1]);
136 CYRecvAll(socket, temp, size);
137 temp[size] = '\0';
138 json = temp;
139 }
140 mode_ = Working;
141 }
142
143 if (json != NULL && fout != NULL) {
144 if (!expand || json[0] != '"' && json[0] != '\'')
145 fputs(json, fout);
146 else for (size_t i(0); i != size; ++i)
147 if (json[i] != '\\')
148 fputc(json[i], fout);
149 else switch(json[++i]) {
150 case '\0': goto done;
151 case '\\': fputc('\\', fout); break;
152 case '\'': fputc('\'', fout); break;
153 case '"': fputc('"', fout); break;
154 case 'b': fputc('\b', fout); break;
155 case 'f': fputc('\f', fout); break;
156 case 'n': fputc('\n', fout); break;
157 case 'r': fputc('\r', fout); break;
158 case 't': fputc('\t', fout); break;
159 case 'v': fputc('\v', fout); break;
160 default: fputc('\\', fout); --i; break;
161 }
162
163 done:
164 fputs("\n", fout);
165 fflush(fout);
166 }
167 }
168
169 void Run(int socket, std::string &code, FILE *fout = NULL, bool expand = false) {
170 Run(socket, code.c_str(), code.size(), fout, expand);
171 }
172
173 static void Console(int socket) {
174 bool bypass(false);
175 bool debug(false);
176 bool expand(false);
177
178 FILE *fout(stdout);
179
180 rl_bind_key('\t', rl_insert);
181
182 struct sigaction action;
183 sigemptyset(&action.sa_mask);
184 action.sa_handler = &sigint;
185 action.sa_flags = 0;
186 sigaction(SIGINT, &action, NULL);
187
188 restart: for (;;) {
189 std::string command;
190 std::vector<std::string> lines;
191
192 bool extra(false);
193 const char *prompt("cy# ");
194
195 if (setjmp(ctrlc_) != 0) {
196 mode_ = Working;
197 fputs("\n", fout);
198 fflush(fout);
199 goto restart;
200 }
201
202 read:
203 mode_ = Parsing;
204 char *line(readline(prompt));
205 mode_ = Working;
206 if (line == NULL)
207 break;
208
209 if (!extra) {
210 extra = true;
211 if (line[0] == '?') {
212 std::string data(line + 1);
213 if (data == "bypass") {
214 bypass = !bypass;
215 fprintf(fout, "bypass == %s\n", bypass ? "true" : "false");
216 fflush(fout);
217 } else if (data == "debug") {
218 debug = !debug;
219 fprintf(fout, "debug == %s\n", debug ? "true" : "false");
220 fflush(fout);
221 } else if (data == "expand") {
222 expand = !expand;
223 fprintf(fout, "expand == %s\n", expand ? "true" : "false");
224 fflush(fout);
225 }
226 add_history(line);
227 goto restart;
228 }
229 }
230
231 command += line;
232
233 char *begin(line), *end(line + strlen(line));
234 while (char *nl = reinterpret_cast<char *>(memchr(begin, '\n', end - begin))) {
235 *nl = '\0';
236 lines.push_back(begin);
237 begin = nl + 1;
238 }
239
240 lines.push_back(begin);
241
242 free(line);
243
244 std::string code;
245
246 if (bypass)
247 code = command;
248 else {
249 CYDriver driver("");
250 cy::parser parser(driver);
251 Setup(driver, parser);
252
253 driver.data_ = command.c_str();
254 driver.size_ = command.size();
255
256 if (parser.parse() != 0 || !driver.errors_.empty()) {
257 for (CYDriver::Errors::const_iterator error(driver.errors_.begin()); error != driver.errors_.end(); ++error) {
258 cy::position begin(error->location_.begin);
259 if (begin.line != lines.size() || begin.column - 1 != lines.back().size() || error->warning_) {
260 cy::position end(error->location_.end);
261
262 if (begin.line != lines.size()) {
263 std::cerr << " | ";
264 std::cerr << lines[begin.line - 1] << std::endl;
265 }
266
267 std::cerr << "....";
268 for (size_t i(0); i != begin.column - 1; ++i)
269 std::cerr << '.';
270 if (begin.line != end.line || begin.column == end.column)
271 std::cerr << '^';
272 else for (size_t i(0), e(end.column - begin.column); i != e; ++i)
273 std::cerr << '^';
274 std::cerr << std::endl;
275
276 std::cerr << " | ";
277 std::cerr << error->message_ << std::endl;
278
279 add_history(command.c_str());
280 goto restart;
281 }
282 }
283
284 driver.errors_.clear();
285
286 command += '\n';
287 prompt = "cy> ";
288 goto read;
289 }
290
291 if (driver.program_ == NULL)
292 goto restart;
293
294 if (socket != -1)
295 code = command;
296 else {
297 std::ostringstream str;
298 CYOutput out(str);
299 Setup(out, driver);
300 out << *driver.program_;
301 code = str.str();
302 }
303 }
304
305 add_history(command.c_str());
306
307 if (debug)
308 std::cout << code << std::endl;
309
310 Run(socket, code, fout, expand);
311 }
312
313 fputs("\n", fout);
314 fflush(fout);
315 }
316
317 static void *Map(const char *path, size_t *psize) {
318 int fd;
319 _syscall(fd = open(path, O_RDONLY));
320
321 struct stat stat;
322 _syscall(fstat(fd, &stat));
323 size_t size(stat.st_size);
324
325 *psize = size;
326
327 void *base;
328 _syscall(base = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0));
329
330 _syscall(close(fd));
331 return base;
332 }
333
334 int main(int argc, char *argv[]) {
335 bool tty(isatty(STDIN_FILENO));
336 pid_t pid(_not(pid_t));
337 bool compile(false);
338
339 for (;;) switch (getopt(argc, argv, "cg:n:p:s")) {
340 case -1:
341 goto getopt;
342 case '?':
343 fprintf(stderr, "usage: cycript [-c] [-p <pid>] [<script> [<arg>...]]\n");
344 return 1;
345
346 case 'c':
347 compile = true;
348 break;
349
350 case 'g':
351 if (false);
352 #if YYDEBUG
353 else if (strcmp(optarg, "bison") == 0)
354 bison_ = true;
355 #endif
356 else {
357 fprintf(stderr, "invalid name for -g\n");
358 return 1;
359 }
360 break;
361
362 case 'n':
363 if (false);
364 else if (strcmp(optarg, "minify") == 0)
365 pretty_ = true;
366 else {
367 fprintf(stderr, "invalid name for -n\n");
368 return 1;
369 }
370 break;
371
372 case 'p': {
373 size_t size(strlen(optarg));
374 char *end;
375 pid = strtoul(optarg, &end, 0);
376 if (optarg + size != end) {
377 fprintf(stderr, "invalid pid for -p\n");
378 return 1;
379 }
380 } break;
381
382 case 's':
383 strict_ = true;
384 break;
385 } getopt:;
386
387 const char *script;
388
389 if (pid != _not(pid_t) && optind < argc - 1) {
390 fprintf(stderr, "-p cannot set argv\n");
391 return 1;
392 }
393
394 if (pid != _not(pid_t) && compile) {
395 fprintf(stderr, "-p conflicts with -c\n");
396 return 1;
397 }
398
399 if (optind == argc)
400 script = NULL;
401 else {
402 // XXX: const_cast?! wtf gcc :(
403 CYSetArgs(argc - optind - 1, const_cast<const char **>(argv + optind + 1));
404 script = argv[optind];
405 if (strcmp(script, "-") == 0)
406 script = NULL;
407 }
408
409 if (script == NULL && !tty && pid != _not(pid_t)) {
410 fprintf(stderr, "non-terminal attaching to remove console\n");
411 return 1;
412 }
413
414 int socket;
415
416 if (pid == _not(pid_t))
417 socket = -1;
418 else {
419 socket = _syscall(::socket(PF_UNIX, SOCK_STREAM, 0));
420
421 struct sockaddr_un address;
422 memset(&address, 0, sizeof(address));
423 address.sun_family = AF_UNIX;
424 sprintf(address.sun_path, "/tmp/.s.cy.%u", pid);
425
426 _syscall(connect(socket, reinterpret_cast<sockaddr *>(&address), SUN_LEN(&address)));
427 }
428
429 if (script == NULL && tty)
430 Console(socket);
431 else {
432 CYDriver driver(script ?: "<stdin>");
433 cy::parser parser(driver);
434 Setup(driver, parser);
435
436 char *start, *end;
437
438 if (script == NULL) {
439 start = NULL;
440 end = NULL;
441
442 driver.file_ = stdin;
443 } else {
444 size_t size;
445 start = reinterpret_cast<char *>(Map(script, &size));
446 end = start + size;
447
448 if (size >= 2 && start[0] == '#' && start[1] == '!') {
449 start += 2;
450
451 if (void *line = memchr(start, '\n', end - start))
452 start = reinterpret_cast<char *>(line);
453 else
454 start = end;
455 }
456
457 driver.data_ = start;
458 driver.size_ = end - start;
459 }
460
461 if (parser.parse() != 0 || !driver.errors_.empty()) {
462 for (CYDriver::Errors::const_iterator i(driver.errors_.begin()); i != driver.errors_.end(); ++i)
463 std::cerr << i->location_.begin << ": " << i->message_ << std::endl;
464 } else if (driver.program_ != NULL)
465 if (socket != -1)
466 Run(socket, start, end - start, stdout);
467 else {
468 std::ostringstream str;
469 CYOutput out(str);
470 Setup(out, driver);
471 out << *driver.program_;
472 std::string code(str.str());
473 if (compile)
474 std::cout << code;
475 else
476 Run(socket, code, stdout);
477 }
478 }
479
480 return 0;
481 }