removed debug output
[k900ussd.git] / src / pnpty.c
blobbf4e9abb4ce6bc1980a7683d32b81af9bfa74650
1 /*
2 * coded by Ketmar // Vampire Avalon (psyc://ketmar.no-ip.org/~Ketmar)
3 * Understanding is not required. Only obedience.
5 * This program is free software. It comes without any warranty, to
6 * the extent permitted by applicable law. You can redistribute it
7 * and/or modify it under the terms of the Do What The Fuck You Want
8 * To Public License, Version 2, as published by Sam Hocevar. See
9 * http://sam.zoy.org/wtfpl/COPYING for more details.
11 #include "pnpty.h"
13 #include <errno.h>
14 #include <fcntl.h>
15 #include <pty.h>
16 #include <signal.h>
17 #include <stdio.h>
18 #include <string.h>
20 #include <sys/types.h>
21 #include <sys/wait.h>
24 //#define dlog(...) fprintf(stderr, __VA_ARGS)
25 #define dlog(...)
28 ////////////////////////////////////////////////////////////////////////////////
29 struct PnPty {
30 int fd;
31 pid_t pid;
35 ////////////////////////////////////////////////////////////////////////////////
36 static void pnptyKillChild (PnPty *pt) {
37 if (pt != NULL && pt->pid > 0) {
38 int status;
40 if (kill(pt->pid, 0) >= 0) kill(pt->pid, SIGKILL);
41 //if (kill(pt->pid, 0) >= 0) kill(pt->pid, SIGTERM);
42 /*TODO: wait a little, then kill that stubborn child*/
43 if (waitpid(pt->pid, &status, 0/*|WNOHANG*/) >= 0) {
45 pt->pid = 0;
50 static void pnptyClear (PnPty *pt) {
51 if (pt != NULL) {
52 if (pt->fd >= 0) close(pt->fd);
53 pt->fd = -1;
54 pnptyKillChild(pt);
59 static int setNonBlock (int fd, int doset) {
60 int flags;
62 if ((flags = fcntl(fd, F_GETFL, 0)) == -1) flags = 0;
63 if (doset) flags |= O_NONBLOCK; else flags &= ~O_NONBLOCK;
64 if (fcntl(fd, F_SETFL, flags) == -1) return -1;
66 return 0;
70 static int ensureModemIsReady (int fd) {
71 for (;;) {
72 char buf[1024];
73 int bufpos;
75 if (setNonBlock(fd, 0) < 0) return -1;
76 if (write(fd, "AT shit\r", 8) <= 0) return -1;
77 // read echo
78 for (;;) {
79 if (read(fd, buf, 1) != 1) return -1;
80 if (buf[0] == '\n') break;
82 if (setNonBlock(fd, 1) < 0) return -1;
84 // wait for 'ERROR'; 100 ms
85 bufpos = 0;
86 for (int f = 100; f > 0; --f) {
87 int rd = read(fd, buf+bufpos, sizeof(buf)-bufpos-1);
89 if (rd < 0) {
90 if (errno == EINTR) continue;
91 if (errno == EAGAIN) { usleep(1000); continue; }
92 // alas
93 return -1;
96 if (rd > 0) {
97 char *eol;
99 bufpos += rd;
100 buf[bufpos] = 0;
101 if ((eol = strchr(buf, '\n')) != NULL) {
102 int np = (eol-buf)+1;
104 if (eol > buf && eol[-1] == '\r') --eol;
105 *eol = 0;
106 //fprintf(stderr, "ensure: [%s]\n", buf);
107 if (strcmp(buf, "ERROR") == 0) return 0; // ok
108 if (np < bufpos) memmove(buf, buf+np, bufpos-np);
109 bufpos -= np;
112 if (bufpos >= sizeof(buf)-2) return -1; // alas
115 // and do it all again
118 return 0; // never reached
122 static int pnptyRun (PnPty *pt) {
123 if (pt != NULL) {
124 // let's prepare args
125 char *args[2];
127 args[0] = "/usr/bin/pnatd";
128 args[1] = NULL;
130 if ((pt->pid = forkpty(&pt->fd, NULL, NULL, NULL)) >= 0) {
131 if (pt->pid == 0) {
132 // child
133 execvp(args[0], args);
134 exit(99);
136 // parent
137 return 0;
138 } // else -- error
140 return -1;
144 ////////////////////////////////////////////////////////////////////////////////
145 void pnptyFree (PnPty *pt) {
146 if (pt != NULL) {
147 pnptyClear(pt);
148 free(pt);
153 PnPty *pnptyNew (void) {
154 PnPty *res = malloc(sizeof(PnPty));
156 if (res != NULL) {
157 res->fd = -1;
158 res->pid = 0;
160 if (pnptyRun(res) != 0 || ensureModemIsReady(res->fd) != 0) {
161 pnptyFree(res);
162 return NULL;
166 return res;
170 enum {
171 TIMEOUT = 40000
175 // remove empty lines (SLOOOW)
176 static void trimemptylines (char *str) {
177 for (char *eol = strrchr(str, '\n'); eol != NULL; eol = strrchr(str, '\n')) {
178 if (eol[1]) break; // non-empty line follows
179 if (eol > str && eol[-1] == '\r') --eol;
180 *eol = 0;
185 // return string or NULL on error
186 char *pnptySendCommandVA (PnPty *pt, const char *fmt, va_list args) {
187 if (pt != NULL && pt->fd >= 0 && pt->pid > 0) {
188 //static const char *eol = "\r\n";
189 char s[1024], *buf = s;
190 int sz = sizeof(s)-4;
191 int bufsz = 1024, bufpos = 0;
192 int waitcnt = 0, firstline = 1;
194 for (;;) {
195 va_list ap;
196 int n;
198 va_copy(ap, args);
199 n = vsnprintf(s, sz, fmt, ap);
200 va_end(ap);
202 if (n > -1 && n < sz) { sz = n; break; } // ok
203 if (n < 0) n = sz*2; else ++n;
205 if (buf == s) {
206 if ((buf = malloc(n+4)) == NULL) return NULL;
207 } else {
208 char *t;
210 if ((t = realloc(buf, n+4)) == NULL) { free(buf); return NULL; }
211 buf = t;
213 sz = n;
216 if (setNonBlock(pt->fd, 0) < 0) {
217 if (buf != s) free(buf);
218 return NULL;
221 dlog("sending: [%s]\n", s);
222 strcat(s, "\r");
223 sz = write(pt->fd, s, strlen(s));
224 if (buf != s) free(buf);
225 if (sz <= 0) return NULL;
227 if (setNonBlock(pt->fd, 1) < 0) return NULL;
228 if ((buf = calloc(bufsz+4, 1)) == NULL) return NULL;
230 for (;;) {
231 int rd;
233 if (bufpos >= bufsz) {
234 int newsz = bufsz+1024;
235 char *nb = realloc(buf, newsz+4);
237 if (nb == NULL) { free(buf); return NULL; }
238 memset(buf+bufpos, newsz-bufsz, 0);
239 buf = nb;
240 bufsz = newsz;
243 if ((rd = read(pt->fd, buf+bufpos, 1)) <= 0) {
244 const char *msg;
246 if (errno == EINTR) continue;
247 if (errno == EAGAIN) {
248 if (waitcnt++ < TIMEOUT) {
249 usleep(1000);
250 //sleep(1);
251 continue;
255 msg = strerror(errno);
256 dlog("rd: %d; [%s]\n", rd, msg);
257 free(buf);
258 fprintf(stderr, "ERROR!\n");
259 return NULL;
261 if (buf[bufpos] == 0) buf[bufpos] = ' ';
262 //dlog("rd=%d; char=%d (%c)\n", rd, (unsigned char)(buf[bufpos]), (buf[bufpos] >= 32 && buf[bufpos] != 128 ? buf[bufpos] : '.'));
263 ++bufpos;
265 if (buf[bufpos-1] == '\n') {
266 char *lastn;
267 int err = 0;
269 if (firstline) {
270 // ignore first line (this is our command echoed)
271 firstline = 0;
272 bufpos = 0;
273 memset(buf, 0, bufsz);
274 continue;
277 if (bufpos > 1 && buf[bufpos-2] == '\r') buf[(--bufpos)-1] = '\n';
278 dlog("===\n%s===\n", buf);
279 buf[bufpos-1] = 0;
280 if ((lastn = strrchr(buf, '\n')) == NULL) lastn = buf; else ++lastn;
282 if (strcmp(lastn, "OK") == 0) {
283 if (buf[0] == 1) buf[0] = ' ';
284 // remove OK line and last EOL
285 if (lastn > buf) {
286 lastn[-1] = 0;
287 lastn[lastn-2 >= buf && lastn[-2] == '\r' ? -2 : -1] = 0;
288 } else {
289 buf[0] = 0;
291 trimemptylines(buf);
292 //dlog("===\n%s===\n", buf);
293 break;
296 if (lastn[0] == '+' && lastn[1] == 'C' && strlen(lastn) >= 10) {
297 char oc = lastn[3];
299 lastn[3] = '?';
300 err = (strncmp(lastn, "+CM? ERROR", 10) == 0);
301 lastn[3] = oc;
304 if (err || strcmp(lastn, "ERROR") == 0) {
305 //dlog("===\n%s===\n", buf);
306 buf[bufpos-1] = '\n';
307 memmove(buf+1, buf, strlen(buf)+1);
308 buf[0] = '\1';
309 trimemptylines(buf);
310 break;
312 buf[bufpos-1] = '\n';
316 //dlog("DONE\n");
317 return buf;
320 return NULL;
324 __attribute__((format(printf,2,3))) char *pnptySendCommand (PnPty *pt, const char *fmt, ...) {
325 va_list ap;
326 char *res;
328 va_start(ap, fmt);
329 res = pnptySendCommandVA(pt, fmt, ap);
330 va_end(ap);
332 return res;
336 static int skipLine (PnPty *pt, int dump) {
337 int waitcnt = 0;
339 for (;;) {
340 int rd;
341 char ch;
343 if ((rd = read(pt->fd, &ch, 1)) <= 0) {
344 const char *msg;
346 if (errno == EINTR) continue;
347 if (errno == EAGAIN) {
348 if (waitcnt++ < TIMEOUT) { usleep(1000); continue; }
351 msg = strerror(errno);
352 dlog("rd: %d; [%s]\n", rd, msg);
353 fprintf(stderr, "ERROR!\n");
354 return -1;
356 if (dump) fputc(ch, stderr);
357 if (ch == '\n') return 0; // done
362 int pnptySendSMSCommand (PnPty *pt, const char *number, const char *str) {
363 if (pt != NULL && pt->fd >= 0 && pt->pid > 0 && number != NULL && number[0] && str != NULL && str[0]) {
364 //static const char *eol = "\r";
365 static const char *cmd0 = "AT+CMGS=\"";
366 static const char *cmd1 = "\"\r";
367 static const char *ctrlz = "\x1a";
369 int waitcnt = 0;
370 char repbuf[64];
371 int reppos;
372 int sz;
374 if (setNonBlock(pt->fd, 0) < 0) return -1;
375 //dlog("pnptySendSMSCommand: sending CMGS for '%s'\n", number);
376 if ((sz = write(pt->fd, cmd0, strlen(cmd0))) < 0) return -1;
377 if ((sz = write(pt->fd, number, strlen(number))) < 0) return -1;
378 if ((sz = write(pt->fd, cmd1, strlen(cmd1))) < 0) return -1;
380 // now we should receive "> " or some shit (?)
381 if (setNonBlock(pt->fd, 1) < 0) return -1;
383 if (skipLine(pt, 0) < 0) return -1;
384 //dlog("reading '> '...\n");
385 reppos = 0;
386 while (reppos < 2) {
387 int rd;
389 if ((rd = read(pt->fd, repbuf+reppos, 1)) <= 0) {
390 const char *msg;
392 if (errno == EINTR) continue;
393 if (errno == EAGAIN) {
394 if (waitcnt++ < TIMEOUT) { usleep(1000); continue; }
397 msg = strerror(errno);
398 dlog("rd: %d; [%s]\n", rd, msg);
399 fprintf(stderr, "ERROR!\n");
400 return -1;
402 ++reppos;
405 //dlog("REP: '%c%c'\n", repbuf[0], repbuf[1]);
407 if (repbuf[0] != '>' || repbuf[1] != ' ') {
408 // invalid answer; read till EOL and return error
409 dlog("REP: '%c%c'\n", repbuf[0], repbuf[1]);
410 skipLine(pt, 1);
411 return -1;
413 // now send text
414 //dlog("sending text...\n");
415 if (setNonBlock(pt->fd, 0) < 0) return -1;
416 if ((sz = write(pt->fd, str, strlen(str))) < 0) return -1;
417 if ((sz = write(pt->fd, ctrlz, strlen(ctrlz))) < 0) return -1;
418 // now read 'OK'
419 //dlog("reading reply...\n");
420 if (setNonBlock(pt->fd, 1) < 0) return -1;
421 if (skipLine(pt, 0) < 0) return -1; // skip echoed str
422 reppos = 0;
423 for (;;) {
424 int rd;
426 if ((rd = read(pt->fd, repbuf+reppos, 1)) <= 0) {
427 const char *msg;
429 if (errno == EINTR) continue;
430 if (errno == EAGAIN) {
431 if (waitcnt++ < TIMEOUT) { usleep(1000); continue; }
434 msg = strerror(errno);
435 dlog("rd: %d; [%s]\n", rd, msg);
436 fprintf(stderr, "ERROR!\n");
437 return -1;
439 //dlog("%c (%d)\n", (repbuf[reppos] > ' ' && repbuf[reppos] < 127 ? repbuf[reppos] : '.'), (unsigned int)(repbuf[reppos]));
440 // here we should receive "+CMGS: 87" or so
441 if (reppos == 0 && repbuf[0] != '+') {
442 skipLine(pt, 1);
443 return -1;
446 if (reppos == 0 && (repbuf[0] == '\n' || repbuf[0] == '\r')) {
447 dlog("%d skipped...\n", repbuf[0]);
448 continue; // skip all newlines
451 if (repbuf[reppos] == '\n') {
452 repbuf[reppos+1] = 0;
453 //dlog("SMSREP: %s", repbuf);
454 if (strncmp(repbuf, "+CMGS: ", 7) != 0) {
455 fprintf(stderr, "SMSERR: %s", repbuf);
456 return -1;
458 break;
460 if (reppos < sizeof(repbuf)-2) ++reppos;
462 //dlog("SMS COMPLETE!\n");
463 skipLine(pt, 1); // this MUST be "OK"; we should check it, but...
465 return 0;
468 return -2;