/[LeafOK_CVS]/lbbs/src/io.c
ViewVC logotype

Contents of /lbbs/src/io.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.45 - (show annotations)
Tue Jun 17 13:25:49 2025 UTC (8 months, 4 weeks ago) by sysadm
Branch: MAIN
Changes since 1.44: +12 -8 lines
Content type: text/x-csrc
Add conditional compile flag for debugging code

1 /***************************************************************************
2 io.c - description
3 -------------------
4 Copyright : (C) 2004-2025 by Leaflet
5 Email : leaflet@leafok.com
6 ***************************************************************************/
7
8 /***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 3 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
17 #include "io.h"
18 #include "log.h"
19 #include "common.h"
20 #include <errno.h>
21 #include <stdio.h>
22 #include <stdarg.h>
23 #include <string.h>
24 #include <time.h>
25 #include <fcntl.h>
26 #include <unistd.h>
27 #include <sys/select.h>
28 #include <sys/ioctl.h>
29 #include <sys/epoll.h>
30 #include <libssh/libssh.h>
31 #include <libssh/server.h>
32 #include <libssh/callbacks.h>
33
34 static char stdout_buf[BUFSIZ];
35 static int stdout_buf_len = 0;
36 static int stdout_buf_offset = 0;
37
38 int prints(const char *format, ...)
39 {
40 char buf[BUFSIZ];
41 va_list args;
42 int ret;
43
44 va_start(args, format);
45 ret = vsnprintf(buf, sizeof(buf), format, args);
46 va_end(args);
47
48 if (ret > 0)
49 {
50 if (stdout_buf_len + ret > BUFSIZ)
51 {
52 iflush();
53 }
54
55 if (stdout_buf_len + ret <= BUFSIZ)
56 {
57 memcpy(stdout_buf + stdout_buf_len, buf, (size_t)ret);
58 stdout_buf_len += ret;
59 }
60 else
61 {
62 errno = EAGAIN;
63 ret = (BUFSIZ - stdout_buf_len - ret);
64 log_error("Output buffer is full, additional %d is required\n", ret);
65 }
66 }
67
68 return ret;
69 }
70
71 int outc(char c)
72 {
73 int ret;
74
75 if (stdout_buf_len + 1 > BUFSIZ)
76 {
77 iflush();
78 }
79
80 if (stdout_buf_len + 1 <= BUFSIZ)
81 {
82 stdout_buf[stdout_buf_len] = c;
83 stdout_buf_len++;
84 }
85 else
86 {
87 errno = EAGAIN;
88 ret = -1;
89 }
90
91 return ret;
92 }
93
94 int iflush(void)
95 {
96 int flags;
97 struct epoll_event ev, events[MAX_EVENTS];
98 int nfds, epollfd;
99 int retry;
100 int ret = 0;
101
102 epollfd = epoll_create1(0);
103 if (epollfd < 0)
104 {
105 log_error("epoll_create1() error (%d)\n", errno);
106 return -1;
107 }
108
109 ev.events = EPOLLOUT;
110 ev.data.fd = STDOUT_FILENO;
111 if (epoll_ctl(epollfd, EPOLL_CTL_ADD, STDOUT_FILENO, &ev) == -1)
112 {
113 log_error("epoll_ctl(STDOUT_FILENO) error (%d)\n", errno);
114 if (close(epollfd) < 0)
115 {
116 log_error("close(epoll) error (%d)\n");
117 }
118 return -1;
119 }
120
121 // Set STDOUT as non-blocking
122 flags = fcntl(STDOUT_FILENO, F_GETFL, 0);
123 fcntl(STDOUT_FILENO, F_SETFL, flags | O_NONBLOCK);
124
125 // Retry wait / flush for at most 3 times
126 retry = 3;
127 while (retry > 0 && !SYS_server_exit)
128 {
129 retry--;
130
131 nfds = epoll_wait(epollfd, events, MAX_EVENTS, 100); // 0.1 second
132
133 if (nfds < 0)
134 {
135 if (errno != EINTR)
136 {
137 log_error("epoll_wait() error (%d)\n", errno);
138 break;
139 }
140 continue;
141 }
142 else if (nfds == 0) // timeout
143 {
144 continue;
145 }
146
147 for (int i = 0; i < nfds; i++)
148 {
149 if (events[i].data.fd == STDOUT_FILENO)
150 {
151 while (stdout_buf_offset < stdout_buf_len && !SYS_server_exit) // write until complete or error
152 {
153 if (SSH_v2)
154 {
155 ret = ssh_channel_write(SSH_channel, stdout_buf + stdout_buf_offset, (uint32_t)(stdout_buf_len - stdout_buf_offset));
156 if (ret == SSH_ERROR)
157 {
158 log_error("ssh_channel_write() error: %s\n", ssh_get_error(SSH_session));
159 retry = 0;
160 break;
161 }
162 }
163 else
164 {
165 ret = (int)write(STDOUT_FILENO, stdout_buf + stdout_buf_offset, (size_t)(stdout_buf_len - stdout_buf_offset));
166 }
167 if (ret < 0)
168 {
169 if (errno == EAGAIN || errno == EWOULDBLOCK)
170 {
171 break;
172 }
173 else if (errno == EINTR)
174 {
175 continue;
176 }
177 else
178 {
179 log_error("write(STDOUT) error (%d)\n", errno);
180 retry = 0;
181 break;
182 }
183 }
184 else if (ret == 0) // broken pipe
185 {
186 retry = 0;
187 break;
188 }
189 else
190 {
191 stdout_buf_offset += ret;
192 if (stdout_buf_offset >= stdout_buf_len) // flush buffer completely
193 {
194 ret = 0;
195 stdout_buf_offset = 0;
196 stdout_buf_len = 0;
197 retry = 0;
198 break;
199 }
200 continue;
201 }
202 }
203 }
204 }
205 }
206
207 // Restore STDOUT flags
208 fcntl(STDOUT_FILENO, F_SETFL, flags);
209
210 if (close(epollfd) < 0)
211 {
212 log_error("close(epoll) error (%d)\n");
213 }
214
215 return ret;
216 }
217
218 int igetch(int timeout)
219 {
220 // static input buffer
221 static unsigned char buf[LINE_BUFFER_LEN];
222 static int len = 0;
223 static int pos = 0;
224
225 struct epoll_event ev, events[MAX_EVENTS];
226 int nfds, epollfd;
227 int ret;
228 int loop;
229
230 unsigned char tmp[LINE_BUFFER_LEN];
231 int out = KEY_NULL;
232 int in_esc = 0;
233 int in_ascii = 0;
234 int in_control = 0;
235 int i = 0;
236 int flags;
237
238 epollfd = epoll_create1(0);
239 if (epollfd < 0)
240 {
241 log_error("epoll_create1() error (%d)\n", errno);
242 return -1;
243 }
244
245 ev.events = EPOLLIN;
246 ev.data.fd = STDIN_FILENO;
247 if (epoll_ctl(epollfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev) == -1)
248 {
249 log_error("epoll_ctl(STDIN_FILENO) error (%d)\n", errno);
250
251 if (close(epollfd) < 0)
252 {
253 log_error("close(epoll) error (%d)\n");
254 }
255 return -1;
256 }
257
258 flags = fcntl(STDIN_FILENO, F_GETFL, 0);
259 fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
260
261 loop = 1;
262
263 while (loop && pos >= len && !SYS_server_exit)
264 {
265 len = 0;
266 pos = 0;
267
268 if (SSH_v2 && ssh_channel_is_closed(SSH_channel))
269 {
270 log_error("SSH channel is closed\n");
271 loop = 0;
272 break;
273 }
274
275 nfds = epoll_wait(epollfd, events, MAX_EVENTS, timeout);
276
277 if (nfds < 0)
278 {
279 if (errno != EINTR)
280 {
281 log_error("epoll_wait() error (%d)\n", errno);
282 break;
283 }
284 continue;
285 }
286 else if (nfds == 0) // timeout
287 {
288 out = KEY_TIMEOUT;
289 break;
290 }
291
292 for (int i = 0; i < nfds; i++)
293 {
294 if (events[i].data.fd == STDIN_FILENO)
295 {
296 while (len < sizeof(buf) && !SYS_server_exit) // read until complete or error
297 {
298 if (SSH_v2)
299 {
300 ret = ssh_channel_read_nonblocking(SSH_channel, buf + len, sizeof(buf) - (uint32_t)len, 0);
301 if (ret == SSH_ERROR)
302 {
303 log_error("ssh_channel_read_nonblocking() error: %s\n", ssh_get_error(SSH_session));
304 loop = 0;
305 break;
306 }
307 else if (ret == SSH_EOF)
308 {
309 loop = 0;
310 break;
311 }
312 else if (ret == 0)
313 {
314 out = 0;
315 break; // Check whether channel is still open
316 }
317 }
318 else
319 {
320 ret = (int)read(STDIN_FILENO, buf + len, sizeof(buf) - (size_t)len);
321 }
322 if (ret < 0)
323 {
324 if (errno == EAGAIN || errno == EWOULDBLOCK)
325 {
326 out = 0;
327 loop = 0;
328 break;
329 }
330 else if (errno == EINTR)
331 {
332 continue;
333 }
334 else
335 {
336 log_error("read(STDIN) error (%d)\n", errno);
337 loop = 0;
338 break;
339 }
340 }
341 else if (ret == 0) // broken pipe
342 {
343 loop = 0;
344 break;
345 }
346 else
347 {
348 len += ret;
349 continue;
350 }
351 }
352 }
353 }
354
355 // For debug
356 #ifdef _DEBUG
357 for (int j = pos; j < len; j++)
358 {
359 log_common("Debug: <--[%u]\n", (buf[j] + 256) % 256);
360 }
361 #endif
362 }
363
364 fcntl(STDIN_FILENO, F_SETFL, flags);
365
366 while (pos < len)
367 {
368 unsigned char c = buf[pos++];
369
370 if (c == KEY_CONTROL)
371 {
372 if (in_control == 0)
373 {
374 in_control = 1;
375 i = 0;
376 continue;
377 }
378 }
379
380 if (in_control)
381 {
382 tmp[i++] = c;
383 if (i >= 2)
384 {
385 out = (int)tmp[0] * 256 + tmp[1];
386 in_control = 0;
387 break;
388 }
389 continue;
390 }
391
392 if (c == KEY_ESC)
393 {
394 if (in_esc == 0)
395 {
396 in_esc = 1;
397 in_ascii = 1;
398 i = 0;
399 continue;
400 }
401 else
402 {
403 out = KEY_CSI;
404 in_esc = 0;
405 break;
406 }
407 }
408
409 in_esc = 0;
410
411 if (in_ascii)
412 {
413 tmp[i++] = c;
414 if (i == 2 && (tmp[0] == 79 || tmp[0] == 91))
415 {
416 in_ascii = 0;
417 switch (tmp[1])
418 {
419 case 65:
420 out = KEY_UP;
421 break;
422 case 66:
423 out = KEY_DOWN;
424 break;
425 case 67:
426 out = KEY_RIGHT;
427 break;
428 case 68:
429 out = KEY_LEFT;
430 break;
431 default:
432 in_ascii = 1;
433 }
434 if (!in_ascii)
435 {
436 break;
437 }
438 }
439 if (i == 2 && tmp[0] == 91) // Fterm
440 {
441 in_ascii = 0;
442 switch (tmp[1])
443 {
444 case 86:
445 out = KEY_SHIFT_F1;
446 break;
447 case 90:
448 out = KEY_SHIFT_F2;
449 break;
450 case 97:
451 out = KEY_SHIFT_F3;
452 break;
453 case 98:
454 out = KEY_SHIFT_F4;
455 break;
456 case 99:
457 out = KEY_SHIFT_F5;
458 break;
459 case 100:
460 out = KEY_SHIFT_F6;
461 break;
462 case 101:
463 out = KEY_SHIFT_F7;
464 break;
465 case 102:
466 out = KEY_SHIFT_F8;
467 break;
468 case 103:
469 out = KEY_SHIFT_F9;
470 break;
471 case 104:
472 out = KEY_SHIFT_F10;
473 break;
474 case 107:
475 out = KEY_CTRL_F1;
476 break;
477 case 108:
478 out = KEY_CTRL_F2;
479 break;
480 case 109:
481 out = KEY_CTRL_F3;
482 break;
483 case 112:
484 out = KEY_CTRL_F6;
485 break;
486 case 113:
487 out = KEY_CTRL_F7;
488 break;
489 case 114:
490 out = KEY_CTRL_F8;
491 break;
492 case 115:
493 out = KEY_CTRL_F9;
494 break;
495 case 116:
496 out = KEY_CTRL_F10;
497 break;
498 default:
499 in_ascii = 1;
500 }
501 if (!in_ascii)
502 {
503 break;
504 }
505 }
506 if (i == 2 && tmp[0] == 79) // Xterm
507 {
508 in_ascii = 0;
509 switch (tmp[1])
510 {
511 case 80:
512 out = KEY_F1;
513 break;
514 case 81:
515 out = KEY_F2;
516 break;
517 case 82:
518 out = KEY_F3;
519 break;
520 case 83:
521 out = KEY_F4;
522 break;
523 default:
524 in_ascii = 1;
525 }
526 if (!in_ascii)
527 {
528 break;
529 }
530 }
531 if (i == 3 && tmp[0] == 91 && tmp[2] == 126)
532 {
533 in_ascii = 0;
534 switch (tmp[1])
535 {
536 case 49:
537 out = KEY_HOME;
538 break;
539 case 50:
540 out = KEY_INS;
541 break;
542 case 51:
543 out = KEY_DEL;
544 break;
545 case 52:
546 out = KEY_END;
547 break;
548 case 53:
549 out = KEY_PGUP;
550 break;
551 case 54:
552 out = KEY_PGDN;
553 break;
554 default:
555 in_ascii = 1;
556 }
557 if (!in_ascii)
558 {
559 break;
560 }
561 }
562 if (i == 4 && tmp[0] == 91 && tmp[1] == 49 && tmp[3] == 126) // Fterm
563 {
564 in_ascii = 0;
565 switch (tmp[2])
566 {
567 case 49:
568 out = KEY_F1;
569 break;
570 case 50:
571 out = KEY_F2;
572 break;
573 case 51:
574 out = KEY_F3;
575 break;
576 case 52:
577 out = KEY_F4;
578 break;
579 case 53:
580 out = KEY_F5;
581 break;
582 case 55:
583 out = KEY_F6;
584 break;
585 case 56:
586 out = KEY_F7;
587 break;
588 case 57:
589 out = KEY_F8;
590 break;
591 default:
592 in_ascii = 1;
593 }
594 if (!in_ascii)
595 {
596 break;
597 }
598 }
599 if (i == 4 && tmp[0] == 91 && tmp[1] == 50 && tmp[3] == 126) // Fterm
600 {
601 in_ascii = 0;
602 switch (tmp[2])
603 {
604 case 48:
605 out = KEY_F9;
606 break;
607 case 49:
608 out = KEY_F10;
609 break;
610 case 50:
611 out = KEY_F11; // Fterm
612 break;
613 case 51:
614 out = KEY_F11; // Xterm
615 break;
616 case 52:
617 out = KEY_F12; // Xterm
618 break;
619 default:
620 in_ascii = 1;
621 }
622 if (!in_ascii)
623 {
624 break;
625 }
626 }
627 if (i == 5 && tmp[0] == 91 && tmp[1] == 49 && tmp[2] == 59 && tmp[3] == 53) // Xterm
628 {
629 in_ascii = 0;
630 switch (tmp[4])
631 {
632 case 65:
633 out = KEY_CTRL_UP;
634 break;
635 case 66:
636 out = KEY_CTRL_DOWN;
637 break;
638 case 67:
639 out = KEY_CTRL_RIGHT;
640 break;
641 case 68:
642 out = KEY_CTRL_LEFT;
643 break;
644 case 70:
645 out = KEY_CTRL_END;
646 break;
647 case 72:
648 out = KEY_CTRL_HOME;
649 break;
650 case 80:
651 out = KEY_CTRL_F1;
652 break;
653 case 81:
654 out = KEY_CTRL_F2;
655 break;
656 case 82:
657 out = KEY_CTRL_F3;
658 break;
659 default:
660 in_ascii = 1;
661 }
662 if (!in_ascii)
663 {
664 break;
665 }
666 }
667 if (i == 6 && tmp[0] == 91 && tmp[1] == 49 && tmp[3] == 59 && tmp[4] == 53 && tmp[5] == 126) // Xterm
668 {
669 in_ascii = 0;
670 switch (tmp[2])
671 {
672 case 53:
673 out = KEY_CTRL_F5;
674 break;
675 case 55:
676 out = KEY_CTRL_F6;
677 break;
678 case 56:
679 out = KEY_CTRL_F7;
680 break;
681 case 57:
682 out = KEY_CTRL_F8;
683 break;
684 default:
685 in_ascii = 1;
686 }
687 if (!in_ascii)
688 {
689 break;
690 }
691 }
692 if (i == 6 && tmp[0] == 91 && tmp[1] == 50 && tmp[3] == 59 && tmp[4] == 53 && tmp[5] == 126) // Xterm
693 {
694 in_ascii = 0;
695 switch (tmp[2])
696 {
697 case 48:
698 out = KEY_CTRL_F9;
699 break;
700 case 49:
701 out = KEY_CTRL_F10;
702 break;
703 case 51:
704 out = KEY_CTRL_F11;
705 break;
706 case 52:
707 out = KEY_CTRL_F12;
708 break;
709 default:
710 in_ascii = 1;
711 }
712 if (!in_ascii)
713 {
714 break;
715 }
716 }
717 if (i == 5 && tmp[0] == 91 && tmp[1] == 49 && tmp[2] == 59 && tmp[3] == 50) // Xterm
718 {
719 in_ascii = 0;
720 switch (tmp[4])
721 {
722 case 80:
723 out = KEY_SHIFT_F1;
724 break;
725 case 81:
726 out = KEY_SHIFT_F2;
727 break;
728 case 82:
729 out = KEY_SHIFT_F3;
730 break;
731 case 83:
732 out = KEY_SHIFT_F4;
733 break;
734 default:
735 in_ascii = 1;
736 }
737 if (!in_ascii)
738 {
739 break;
740 }
741 }
742 if (i == 6 && tmp[0] == 91 && tmp[1] == 49 && tmp[3] == 59 && tmp[4] == 50 && tmp[5] == 126) // Xterm
743 {
744 in_ascii = 0;
745 switch (tmp[2])
746 {
747 case 53:
748 out = KEY_SHIFT_F5;
749 break;
750 case 55:
751 out = KEY_SHIFT_F6;
752 break;
753 case 56:
754 out = KEY_SHIFT_F7;
755 break;
756 case 57:
757 out = KEY_SHIFT_F8;
758 break;
759 default:
760 in_ascii = 1;
761 }
762 if (!in_ascii)
763 {
764 break;
765 }
766 }
767 if (i == 6 && tmp[0] == 91 && tmp[1] == 50 && tmp[3] == 59 && tmp[4] == 50 && tmp[5] == 126) // Xterm
768 {
769 in_ascii = 0;
770 switch (tmp[2])
771 {
772 case 48:
773 out = KEY_SHIFT_F9;
774 break;
775 case 49:
776 out = KEY_SHIFT_F10;
777 break;
778 case 51:
779 out = KEY_SHIFT_F11;
780 break;
781 case 52:
782 out = KEY_SHIFT_F12;
783 break;
784 default:
785 in_ascii = 1;
786 }
787 if (!in_ascii)
788 {
789 break;
790 }
791 }
792
793 if (c == 'm')
794 {
795 in_ascii = 0;
796 }
797 continue;
798 }
799
800 out = ((int)c + 256) % 256;
801 break;
802 }
803
804 if (close(epollfd) < 0)
805 {
806 log_error("close(epoll) error (%d)\n");
807 }
808
809 // For ESC key
810 if (out == 0 && in_esc)
811 {
812 out = KEY_ESC;
813 }
814
815 // for debug
816 #ifdef _DEBUG
817 if (out != KEY_TIMEOUT && out != KEY_NULL)
818 {
819 log_common("Debug: -->[0x %x]\n", out);
820 }
821 #endif
822
823 return out;
824 }
825
826 int igetch_t(int sec)
827 {
828 int ch;
829 time_t t_begin = time(NULL);
830
831 do
832 {
833 ch = igetch(100);
834 } while (!SYS_server_exit && ch == KEY_TIMEOUT && (time(NULL) - t_begin < sec));
835
836 return ch;
837 }
838
839 void igetch_reset()
840 {
841 int ch;
842 do
843 {
844 ch = igetch(0);
845 } while (ch != KEY_NULL && ch != KEY_TIMEOUT);
846 }

webmaster@leafok.com
ViewVC Help
Powered by ViewVC 1.3.0-beta1