Update README.md
[puttycyg-ng.git] / windows / cygterm.c
blobeec124db6e9cb8e95a623fe6ded70ad1c6a278ad
1 #include <stdio.h> /* sprintf */
2 #include <string.h>
3 #include <limits.h> /* INT_MAX */
4 #include "putty.h"
5 #include "cthelper/cthelper.h"
6 #include "cthelper/message.h"
8 #define CYGTERM_MAX_BACKLOG 16384
10 #define CYGTERM_NAME "Cygterm"
12 #ifdef __INTERIX
13 #define CTHELPER "posix.exe /u /c cthelper.exe"
14 #else
15 #define CTHELPER "cthelper"
16 #endif
18 #if !defined(DEBUG)
19 #define cygterm_debug(f,...)
20 #elif !defined(cygterm_debug)
21 #define cygterm_debug(f,...) debug(("%s:%d:%s: "f"\n",__FILE__,__LINE__,__FUNCTION__,##__VA_ARGS__))
22 #endif
24 #define putenv _putenv
26 typedef struct cygterm_backend_data {
27 const struct plug_function_table *fn;
28 void *frontend;
29 Socket a;
30 Socket s;
31 PROCESS_INFORMATION pi;
32 HANDLE ctl;
33 Config cfg;
34 int bufsize;
35 int editing, echoing;
36 int exitcode;
37 } *Local;
40 /* Plug functions for cthelper data connection */
41 static void
42 cygterm_log(Plug p, int type, SockAddr addr, int port, const char *error_msg, int error_code)
44 /* Do nothing */
47 static int
48 cygterm_closing(Plug plug, const char *error_msg, int error_code, int calling_back)
50 Local local = (Local)plug;
51 cygterm_debug("top");
52 if (local->s) {
53 sk_close(local->s);
54 local->s = NULL;
56 /* check for errors from cthelper */
57 CloseHandle(local->ctl);
58 /* wait for cthelper */
59 if (local->pi.hProcess != INVALID_HANDLE_VALUE) {
60 if (WAIT_OBJECT_0 == WaitForSingleObject(local->pi.hProcess, 2000)) {
61 DWORD status;
62 GetExitCodeProcess(local->pi.hProcess, &status);
63 switch (status) {
64 case CthelperSuccess:
65 break;
66 case CthelperInvalidUsage:
67 case CthelperInvalidPort:
68 case CthelperConnectFailed:
69 local->exitcode = INT_MAX;
70 error_msg = "Internal error";
71 break;
72 case CthelperPtyforkFailure:
73 local->exitcode = INT_MAX;
74 error_msg = "Failed to allocate pseudoterminal";
75 break;
76 case CthelperExecFailure:
77 local->exitcode = INT_MAX;
78 error_msg = "Failed to execute command";
79 break;
83 /* this calls cygterm_exitcode() */
84 notify_remote_exit(local->frontend);
85 if (error_msg) {
86 cygterm_debug("error_msg: %s", error_msg);
87 connection_fatal(local->frontend, "%s", error_msg);
89 return 0;
92 static int
93 cygterm_receive(Plug plug, int urgent, char *data, int len)
95 Local local = (Local)plug;
96 int backlog;
97 cygterm_debug("backend -> display %u", len);
98 // dmemdump(data, len);
99 backlog = from_backend(local->frontend, 0, data, len);
100 // dmemdumpl(data, len);
101 sk_set_frozen(local->s, backlog > CYGTERM_MAX_BACKLOG);
102 cygterm_debug("OK");
103 return 1;
106 static void
107 cygterm_sent(Plug plug, int bufsize)
109 Local local = (Local)plug;
110 local->bufsize = bufsize;
113 static void
114 cygterm_size(void *handle, int width, int height);
116 static int
117 cygterm_accepting(Plug plug, OSSocket sock)
119 Local local = (Local)plug;
120 cygterm_debug("top");
121 local->s = sk_register(sock, plug);
122 sk_set_frozen(local->s, 0);
123 /* Reset terminal size */
124 cygterm_size(local, local->cfg.width, local->cfg.height);
125 cygterm_debug("OK");
126 return 0;
130 static char *getCygwinBin(void);
131 static void appendPath(const char *append);
132 static size_t makeAttributes(char *buf, Config *cfg);
133 static const char *spawnChild(char *cmd, LPPROCESS_INFORMATION ppi, PHANDLE pin);
135 /* Backend functions for the cygterm backend */
137 static const char *
138 cygterm_init(void *frontend_handle, void **backend_handle,
139 Config *cfg,
140 char *unused_host, int unused_port,
141 char **realhost, int nodelay, int keepalive)
143 /* XXX: I'm not sure if it is OK to overload Plug like this.
144 * cygterm_accepting should only be used for the listening socket
145 * (local->a) while the cygterm_closing, cygterm_receive, and cygterm_sent
146 * should be used only for the actual connection (local->s).
148 static const struct plug_function_table fn_table = {
149 cygterm_log,
150 cygterm_closing,
151 cygterm_receive,
152 cygterm_sent,
153 cygterm_accepting
155 Local local;
156 const char *command;
157 char cmdline[2 * MAX_PATH];
158 int cport;
159 const char *err;
160 int cmdlinelen;
162 cygterm_debug("top");
164 local = snew(struct cygterm_backend_data);
165 local->fn = &fn_table;
166 local->a = NULL;
167 local->s = NULL;
168 local->cfg = *cfg;
169 local->editing = 0;
170 local->echoing = 0;
171 local->exitcode = 0;
172 *backend_handle = local;
174 local->frontend = frontend_handle;
176 /* set up listen socket for communication with child */
177 cygterm_debug("setupCygTerm");
179 /* let sk use INADDR_LOOPBACK and let WinSock choose a port */
180 local->a = sk_newlistener(0, 0, (Plug)local, 1, ADDRTYPE_IPV4);
181 if ((err = sk_socket_error(local->a)) != NULL)
182 goto fail_free;
184 /* now, get the port that WinSock chose */
185 /* XXX: Is there another function in PuTTY to do this? */
186 cygterm_debug("getting port");
187 cport = sk_getport(local->a);
188 if (cport == -1) {
189 err = "Failed to get port number for cthelper";
190 goto fail_close;
193 if (strchr(local->cfg.termtype, ' ')) {
194 err = "term type contains spaces";
195 goto fail_close;
198 /* Build cthelper command line */
199 cmdlinelen = sprintf(cmdline, CTHELPER" %u %s ", cport, local->cfg.termtype);
200 cmdlinelen += makeAttributes(cmdline + cmdlinelen, &local->cfg);
202 command = cfg->cygcmd;
203 cygterm_debug("command is :%s:", command);
204 /* A command of "." or "-" tells us to pass no command arguments to
205 * cthelper which will then run the user's shell under Cygwin. */
206 if ((command[0]=='-'||command[0]=='.') && command[1]=='\0')
208 else if (cmdlinelen + strlen(command) + 2 > sizeof cmdline) {
209 err = "command is too long";
210 goto fail_close;
212 else {
213 cmdlinelen += sprintf(cmdline + cmdlinelen, " %s", command);
216 /* Add the Cygwin /bin path to the PATH. */
217 if (cfg->cygautopath) {
218 char *cygwinBinPath = getCygwinBin();
219 if (!cygwinBinPath) {
220 /* we'll try anyway */
221 cygterm_debug("cygwin bin directory not found");
223 else {
224 cygterm_debug("found cygwin directory: %s", cygwinBinPath);
225 appendPath(cygwinBinPath);
226 sfree(cygwinBinPath);
230 cygterm_debug("starting cthelper: %s", cmdline);
231 if ((err = spawnChild(cmdline, &local->pi, &local->ctl)))
232 goto fail_close;
234 /* This should be set to the local hostname, Apparently, realhost is used
235 * only to set the window title.
237 strcpy(*realhost = smalloc(sizeof CYGTERM_NAME), CYGTERM_NAME);
238 cygterm_debug("OK");
239 return 0;
241 fail_close:
242 sk_close(local->a);
243 fail_free:
244 sfree(local);
245 return err;
248 static void
249 cygterm_free(void *handle)
251 Local local = handle;
252 cygterm_debug("top");
253 sfree(local);
256 static void
257 cygterm_reconfig(void *handle, Config *cfg)
259 Local local = handle;
260 cygterm_debug("top");
261 local->cfg = *cfg;
264 static int
265 cygterm_send(void *handle, char *buf, int len)
267 Local local = handle;
268 cygterm_debug("frontend -> pty %u", len);
269 // dmemdump(buf, len);
270 #if 0
271 /* HACK */
273 int i;
274 for (i = 0; i < len - 1; i++)
275 if (buf[i] == 033 && !(buf[i+1]&0x80)) {
276 memmove(buf + i, buf + i + 1, --len - i);
277 buf[i] |= 0x80;
280 #endif
281 if (local->s != 0)
282 local->bufsize = sk_write(local->s, buf, len);
283 cygterm_debug("OK");
284 return local->bufsize;
287 static int
288 cygterm_sendbuffer(void *handle)
290 Local local = handle;
291 cygterm_debug("top");
292 return local->bufsize;
295 static void
296 cygterm_size(void *handle, int width, int height)
298 Local local = handle;
299 cygterm_debug("top");
300 cygterm_debug("size=%d,%d (last=%d,%d)",
301 width, height, local->cfg.width, local->cfg.height);
302 local->cfg.width = width;
303 local->cfg.height = height;
304 if (local->s) {
305 DWORD n;
306 Message m;
307 m.size = MESSAGE_MIN + sizeof(m.msg);
308 m.type = MSG_RESIZE;
309 m.msg.resize.width = width;
310 m.msg.resize.height = height;
311 cygterm_debug("WriteFile %p %p:%u", local->ctl, &m, m.size);
312 WriteFile(local->ctl, (const char *)&m, m.size, &n, 0);
313 cygterm_debug("WriteFile returns %d");
317 static void
318 cygterm_special(void *handle, Telnet_Special code)
320 cygterm_debug("top");
323 static const struct telnet_special *
324 cygterm_get_specials(void *handle)
326 cygterm_debug("top");
327 return NULL;
330 static int
331 cygterm_connected(void *handle)
333 Local local = handle;
334 cygterm_debug("top");
335 return local->s != NULL;
338 static int
339 cygterm_exitcode(void *handle)
341 Local local = handle;
342 cygterm_debug("top");
343 return local->exitcode;
346 static int
347 cygterm_sendok(void *handle)
349 cygterm_debug("top");
350 return 1;
353 static void
354 cygterm_unthrottle(void *handle, int backlog)
356 Local local = handle;
357 cygterm_debug("top");
358 sk_set_frozen(local->s, backlog > CYGTERM_MAX_BACKLOG);
361 static int
362 cygterm_ldisc(void *handle, int option)
364 Local local = handle;
365 cygterm_debug("cygterm_ldisc: %d", option);
366 switch (option) {
367 case LD_EDIT:
368 return local->editing;
369 case LD_ECHO:
370 return local->echoing;
372 return 0;
375 static void
376 cygterm_provide_ldisc(void *handle, void *ldisc)
378 cygterm_debug("top");
381 static void
382 cygterm_provide_logctx(void *handle, void *logctx)
384 cygterm_debug("top");
387 static int
388 cygterm_cfg_info(void *handle)
390 return 0;
393 Backend cygterm_backend = {
394 cygterm_init,
395 cygterm_free,
396 cygterm_reconfig,
397 cygterm_send,
398 cygterm_sendbuffer,
399 cygterm_size,
400 cygterm_special,
401 cygterm_get_specials,
402 cygterm_connected,
403 cygterm_exitcode,
404 cygterm_sendok,
405 cygterm_ldisc,
406 cygterm_provide_ldisc,
407 cygterm_provide_logctx,
408 cygterm_unthrottle,
409 cygterm_cfg_info,
414 /* like strcpy(), but return pointer to terminating null character */
415 static char *
416 strecpy(char *d,const char *s)
418 while ((*d = *s++))
419 d++;
420 return d;
423 /* Make cthelper attribute string from PuTTY Config */
424 static size_t
425 makeAttributes(char *buf, Config *cfg)
427 char *e = buf;
429 if (cfg->bksp_is_delete)
430 e = strecpy(e, ":erase=^?");
431 else
432 e = strecpy(e, ":erase=^H");
434 e += sprintf(e, ":size=%d,%d", cfg->height, cfg->width);
436 /* TODO: other options? localedit? localecho? */
438 return e - buf;
442 /* Utility functions for spawning cthelper process */
443 static BOOL
444 getRegistry(char *valueData, LPDWORD psize, HKEY key, const char *subKey, const char *valueName)
446 HKEY k;
447 LONG ret;
449 if (ERROR_SUCCESS != (ret = RegOpenKey(key, subKey, &k)))
450 return ret;
452 ERROR_SUCCESS == (ret = RegQueryInfoKey(k, 0, 0, 0, 0, 0, 0, 0, 0, psize, 0, 0))
453 && ERROR_SUCCESS == (ret = RegQueryValueEx(k, valueName, 0, 0, valueData, psize));
455 RegCloseKey(k);
456 return ret;
459 /* As of Cygwin 1.7, one of these keys contains the Cygwin install root. */
460 #define CYGWIN_U_SETUP_ROOTDIR \
461 HKEY_CURRENT_USER,\
462 "Software\\Cygwin\\setup",\
463 "rootdir"
464 #define CYGWIN_S_SETUP_ROOTDIR \
465 HKEY_LOCAL_MACHINE,\
466 "Software\\Cygwin\\setup",\
467 "rootdir"
469 static char *
470 getCygwinBin(void)
472 char *dir;
473 DWORD size = MAX_PATH;
475 dir = smalloc(size);
476 dir[0] = '\0';
478 if (ERROR_SUCCESS == getRegistry(dir, &size, CYGWIN_U_SETUP_ROOTDIR)
479 || ERROR_SUCCESS == getRegistry(dir, &size, CYGWIN_S_SETUP_ROOTDIR))
481 strcat(dir, "\\bin");
483 else
485 sfree(dir);
486 dir = 0;
489 return dir;
492 static void
493 appendPath(const char *append)
495 char *path;
496 char *newPath;
498 cygterm_debug("getting PATH");
499 if (!(path = getenv("PATH"))) {
500 cygterm_debug("hmm.. PATH not set");
501 path = "";
503 cygterm_debug("alloc newPath");
504 newPath = smalloc(5 + strlen(append) + 1 + strlen(path) + 1);
505 cygterm_debug("init newPath");
506 sprintf(newPath, "PATH=%s;%s", path, append);
507 cygterm_debug("set newPath");
508 putenv(newPath);
509 cygterm_debug("free newPath");
510 sfree(newPath);
513 static const char *
514 spawnChild(char *cmd, LPPROCESS_INFORMATION ppi, PHANDLE pin)
516 STARTUPINFO si = {sizeof(si)};
517 SECURITY_ATTRIBUTES sa = {sizeof(sa)};
518 HANDLE in;
520 /* Create an anonymous pipe over which to send events such as resize */
521 sa.lpSecurityDescriptor = NULL;
522 sa.bInheritHandle = TRUE;
523 if (!CreatePipe(&in, pin, &sa, 1))
524 return "failed to create event pipe";
526 /* cthelper will use stdin to get event messages */
527 si.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
528 si.hStdInput = in;
529 si.wShowWindow = SW_HIDE;
531 /* We allow cthelper to inherit handles. I have no idea if there are
532 * other inheritable handles in PuTTY that this will effect. cthelper
533 * will attempt to close all open descriptors.
535 if (!CreateProcess(
536 NULL, cmd, /* command line */
537 NULL, NULL, /* no process or thread security attributes */
538 TRUE, /* inherit handles */
539 CREATE_NEW_CONSOLE, /* create a new console window */
540 NULL, /* use parent environment */
541 0, /* use parent working directory */
542 &si, /* STARTUPINFO sets stdin to read end of pipe */
543 ppi))
545 CloseHandle(in);
546 CloseHandle(*pin);
547 *pin = INVALID_HANDLE_VALUE;
548 return "failed to run cthelper";
551 /* close the read end of the pipe */
552 CloseHandle(in);
554 return 0;
557 /* ex:set ts=4 sw=4: */