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

Contents of /lbbs/src/io.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.51 - (show annotations)
Wed Jul 2 09:07:35 2025 UTC (8 months, 2 weeks ago) by sysadm
Branch: MAIN
Changes since 1.50: +12 -6 lines
Content type: text/x-csrc
Convert \r\n to \r to avoid duplicate change line in editor

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

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