diff --git a/configure.ac b/configure.ac index b6740213..def81024 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ dnl Process this File with autoconf to produce a configure script. AC_PREREQ(2.61) -AC_INIT([gsocket], 1.4.24) +AC_INIT([gsocket], 1.4.25) dnl AC_CONFIG_AUX_DIR(config-x86_64-apple-darwin19.6.0) AC_CONFIG_AUX_DIR(config) AC_CANONICAL_SYSTEM diff --git a/include/gsocket/buf.h b/include/gsocket/buf.h index 382bdcb3..906be879 100644 --- a/include/gsocket/buf.h +++ b/include/gsocket/buf.h @@ -14,10 +14,14 @@ typedef struct void GS_BUF_init(GS_BUF *gsb, size_t sz_min_free); void GS_BUF_free(GS_BUF *gsb); int GS_BUF_resize(GS_BUF *gsb, size_t sz_new); -int GS_BUF_add(GS_BUF *gsb, size_t len); +int GS_BUF_add_length(GS_BUF *gsb, size_t len); int GS_BUF_add_data(GS_BUF *gsb, void *data, size_t len); int GS_BUF_del(GS_BUF *gsb, size_t len); +int GS_BUF_memmove(GS_BUF *gsb, void *data, size_t len); +#define GS_BUF_empty(gsb) (gsb)->sz_used = 0; +#define GS_BUF_DATA(gsb) (gsb)->data +#define GS_BUF_IS_INIT(gsb) ((gsb)->sz_max_add!=0) #define GS_BUF_UNUSED(gsb) ((gsb)->sz_total - (gsb)->sz_used) #define GS_BUF_RSRC(gsb) (gsb)->data #define GS_BUF_WDST(gsb) ((uint8_t *)(gsb)->data + (gsb)->sz_used) diff --git a/include/gsocket/gsocket.h b/include/gsocket/gsocket.h index 80b2833c..ee73d179 100644 --- a/include/gsocket/gsocket.h +++ b/include/gsocket/gsocket.h @@ -41,10 +41,12 @@ #define GS_TOKEN_SIZE (16) /* 128 bit */ #define GS_TV_TO_USEC(tv) ((uint64_t)(tv)->tv_sec * 1000000 + (tv)->tv_usec) +#define GS_TV_TO_MSEC(tv) ((uint64_t)(tv)->tv_sec * 1000 + (tv)->tv_usec/1000) #define GS_TV_DIFF(tv_a, tv_b) (GS_TV_TO_USEC(tv_b) - GS_TV_TO_USEC(tv_a)) #define GS_SEC_TO_USEC(sec) ((uint64_t)sec * 1000000) #define GS_MSEC_TO_USEC(ms) ((uint64_t)ms * 1000) #define GS_USEC_TO_SEC(usec) (usec / 1000000) +#define GS_USEC_TO_MSEC(usec) (usec / 1000) #define GS_USEC_TO_TV(tv, usec) do { (tv)->tv_sec = (usec) / 1000000; (tv)->tv_usec = (usec) % 1000000; } while(0) #define GS_SECRET_MAX_LEN (256 / 8) /* max length in bytes */ diff --git a/lib/buf.c b/lib/buf.c index 373623da..e368b8d0 100644 --- a/lib/buf.c +++ b/lib/buf.c @@ -40,7 +40,7 @@ GS_BUF_resize(GS_BUF *gsb, size_t sz_new) } int -GS_BUF_add(GS_BUF *gsb, size_t len) +GS_BUF_add_length(GS_BUF *gsb, size_t len) { // Bail. There is sz_max_add space available but looks like caller wrote // more ata... @@ -65,6 +65,17 @@ GS_BUF_add_data(GS_BUF *gsb, void *data, size_t len) return 0; } +int +GS_BUF_memmove(GS_BUF *gsb, void *data, size_t len) +{ + GS_BUF_resize(gsb, len); + memmove((uint8_t *)gsb->data + gsb->sz_used, data, len); + + gsb->sz_used += len; + + return 0; +} + /* * Consume data from beginning. */ diff --git a/man/gs-netcat.1 b/man/gs-netcat.1 index 67a6f22e..fb593c7c 100755 --- a/man/gs-netcat.1 +++ b/man/gs-netcat.1 @@ -52,7 +52,7 @@ Generate a secure random password and output it to standard output. .It Fl l Server mode. The default mode is client. .It Fl q -Quite mode. Do not output any warnings or errors. +Quiet mode. Do not output any warnings or errors. .It Fl w Client to wait for the listening server to become available. .It Fl r diff --git a/tools/4_gs-netcat.c b/tools/4_gs-netcat.c index 7457ea67..989cbdeb 100644 --- a/tools/4_gs-netcat.c +++ b/tools/4_gs-netcat.c @@ -1147,10 +1147,10 @@ my_getopt(int argc, char *argv[]) if (gopt.is_daemon) { if (gopt.is_logfile == 0) - gopt.is_quite = 1; + gopt.is_quiet = 1; } - if (gopt.is_quite != 0) + if (gopt.is_quiet != 0) { gopt.log_fp = NULL; gopt.err_fp = NULL; diff --git a/tools/common.h b/tools/common.h index c1dea370..4f0fc98a 100644 --- a/tools/common.h +++ b/tools/common.h @@ -130,7 +130,7 @@ struct _gopt int is_multi_peer; /* -p / -S / -d [client & server] */ int is_daemon; int is_logfile; - int is_quite; + int is_quiet; int is_win_resized; // window size changed (signal) int is_console; // console is being displayed int is_pong_pending; // Server: Answer to PING waiting to be send @@ -210,7 +210,8 @@ struct _peer #define GSC_FL_IS_SERVER (0x01) -extern struct _gopt gopt; +extern struct _gopt gopt; // declared in utils.c + #define xfprintf(fp, a...) do {if (fp != NULL) { fprintf(fp, a); fflush(fp); } } while (0) #define int_ntoa(x) inet_ntoa(*((struct in_addr *)&x)) @@ -233,16 +234,34 @@ extern struct _gopt gopt; #define D_BYEL(a) "\033[1;33m"a"\033[0m" #define D_BBLU(a) "\033[1;34m"a"\033[0m" #define D_BMAG(a) "\033[1;35m"a"\033[0m" + #ifdef DEBUG -# define DEBUGF(a...) do{xfprintf(gopt.err_fp, "DEBUG %s:%d: ", __func__, __LINE__); xfprintf(gopt.err_fp, a); }while(0) -# define DEBUGF_R(a...) do{xfprintf(gopt.err_fp, "DEBUG %s:%d: ", __func__, __LINE__); xfprintf(gopt.err_fp, "\033[1;31m"); xfprintf(gopt.err_fp, a); xfprintf(gopt.err_fp, "\033[0m"); }while(0) -# define DEBUGF_G(a...) do{xfprintf(gopt.err_fp, "DEBUG %s:%d: ", __func__, __LINE__); xfprintf(gopt.err_fp, "\033[1;32m"); xfprintf(gopt.err_fp, a); xfprintf(gopt.err_fp, "\033[0m"); }while(0) -# define DEBUGF_B(a...) do{xfprintf(gopt.err_fp, "DEBUG %s:%d: ", __func__, __LINE__); xfprintf(gopt.err_fp, "\033[1;34m"); xfprintf(gopt.err_fp, a); xfprintf(gopt.err_fp, "\033[0m"); }while(0) -# define DEBUGF_Y(a...) do{xfprintf(gopt.err_fp, "DEBUG %s:%d: ", __func__, __LINE__); xfprintf(gopt.err_fp, "\033[1;33m"); xfprintf(gopt.err_fp, a); xfprintf(gopt.err_fp, "\033[0m"); }while(0) -# define DEBUGF_M(a...) do{xfprintf(gopt.err_fp, "DEBUG %s:%d: ", __func__, __LINE__); xfprintf(gopt.err_fp, "\033[1;35m"); xfprintf(gopt.err_fp, a); xfprintf(gopt.err_fp, "\033[0m"); }while(0) -# define DEBUGF_C(a...) do{xfprintf(gopt.err_fp, "DEBUG %s:%d: ", __func__, __LINE__); xfprintf(gopt.err_fp, "\033[1;36m"); xfprintf(gopt.err_fp, a); xfprintf(gopt.err_fp, "\033[0m"); }while(0) -# define DEBUGF_W(a...) do{xfprintf(gopt.err_fp, "DEBUG %s:%d: ", __func__, __LINE__); xfprintf(gopt.err_fp, "\033[1;37m"); xfprintf(gopt.err_fp, a); xfprintf(gopt.err_fp, "\033[0m"); }while(0) -#else +struct _g_debug_ctx +{ + struct timeval tv_last; + struct timeval tv_now; +}; + +extern struct _g_debug_ctx g_dbg_ctx; // declared in utils.c + +#define DEBUGF_T(xcolor, a...) do { \ + gettimeofday(&g_dbg_ctx.tv_now, NULL); \ + if (g_dbg_ctx.tv_last.tv_sec == 0) { memcpy(&g_dbg_ctx.tv_last, &g_dbg_ctx.tv_now, sizeof g_dbg_ctx.tv_last); } \ + xfprintf(gopt.err_fp, "DEBUG %4llu %s:%d %s", GS_TV_TO_MSEC(&g_dbg_ctx.tv_now) - GS_TV_TO_MSEC(&g_dbg_ctx.tv_last), __func__, __LINE__, xcolor?xcolor:""); \ + memcpy(&g_dbg_ctx.tv_last, &g_dbg_ctx.tv_now, sizeof g_dbg_ctx.tv_last); \ + xfprintf(gopt.err_fp, a); \ + if (xcolor) { xfprintf(gopt.err_fp, "\033[0m"); } \ +} while (0) + +# define DEBUGF(a...) do{DEBUGF_T(NULL, a); } while(0) +# define DEBUGF_R(a...) do{DEBUGF_T("\033[1;31m", a); } while(0) +# define DEBUGF_G(a...) do{DEBUGF_T("\033[1;32m", a); } while(0) +# define DEBUGF_B(a...) do{DEBUGF_T("\033[1;34m", a); } while(0) +# define DEBUGF_Y(a...) do{DEBUGF_T("\033[1;33m", a); } while(0) +# define DEBUGF_M(a...) do{DEBUGF_T("\033[1;35m", a); } while(0) +# define DEBUGF_C(a...) do{DEBUGF_T("\033[1;36m", a); } while(0) +# define DEBUGF_W(a...) do{DEBUGF_T("\033[1;37m", a); } while(0) +#else // DEBUG # define DEBUGF(a...) # define DEBUGF_R(a...) # define DEBUGF_G(a...) @@ -251,6 +270,7 @@ extern struct _gopt gopt; # define DEBUGF_M(a...) # define DEBUGF_C(a...) # define DEBUGF_W(a...) +# define DEBUGF_A(a...) #endif // Increase ptr by number of characters added to ptr. @@ -319,7 +339,7 @@ extern struct _gopt gopt; #ifdef DEBUG # define HEXDUMP(a, _len) do { \ size_t _n = 0; \ - xfprintf(gopt.err_fp, "%s:%d HEX ", __FILE__, __LINE__); \ + xfprintf(gopt.err_fp, "%s:%d HEX[%zd] ", __FILE__, __LINE__, _len); \ while (_n < (_len)) xfprintf(gopt.err_fp, "%2.2x", ((unsigned char *)a)[_n++]); \ xfprintf(gopt.err_fp, "\n"); \ } while (0) diff --git a/tools/console.c b/tools/console.c index 292104d1..d1cbecfd 100644 --- a/tools/console.c +++ b/tools/console.c @@ -47,6 +47,13 @@ static const char *sb_color = "\x1B[44m\x1B[30m"; // Black on Blue #define GS_CONSOLE_BUF_SIZE (1024) #define GS_CONDIS_ROWS (GS_CONSOLE_ROWS - 2) +enum _gs_ut_cursor_flags { + GS_UT_CURSOR_ON = 0x01, + GS_UT_CURSOR_OFF = 0x02 +}; +enum _gs_ut_cursor_flags ut_cursor; + + struct _console_info { char statusbar[512]; @@ -141,15 +148,24 @@ tty_write(void *src, size_t len) static int is_cursor_in_console; static void -console_cursor_off(void) +cursor_to_ut(void) { - tty_write("\x1B""8", 2); // Move cursor to upper tier + char buf[64]; + char *end = buf + sizeof (buf); + char *ptr = buf; + // If Upper Tier disabled the cursor then do NOT show it. + if (ut_cursor == GS_UT_CURSOR_OFF) + SXPRINTF(ptr, end - ptr, "\x1B[?25l"); + + SXPRINTF(ptr, end - ptr, "\x1B""8"); + + tty_write(buf, ptr - buf); is_cursor_in_console = 0; } static void -console_cursor_on(void) +cursor_to_lt(void) { char buf[64]; char *end = buf + sizeof (buf); @@ -158,15 +174,20 @@ console_cursor_on(void) int row = gopt.winsize.ws_row; int col = 1 + GS_CONSOLE_PROMPT_LEN + MIN(rl.pos, rl.visible_len); - DEBUGF_W("Console Cursor ON (%d:%df)\n", row, col); - // ESC[?2004l = Reset bracketed paste mode + // DEBUGF_W("Cursor to CONSOLE (Lower Tier) (%d:%df)\n", row, col); SXPRINTF(ptr, end - ptr, "\x1B[%d;%df", row, col); + // ESC[?2004l = Reset bracketed paste mode if (is_console_cursor_needs_reset) { SXPRINTF(ptr, end - ptr, "\x1B[?2004l"); is_console_cursor_needs_reset = 0; } + // If Upper Tier disabled the cursor then show it in console + // DEBUGF_R("ut-cursor = %d\n", ut_cursor); + if (ut_cursor == GS_UT_CURSOR_OFF) + SXPRINTF(ptr, end - ptr, "\x1B[?25h"); + tty_write(buf, ptr - buf); is_cursor_in_console = 1; @@ -551,9 +572,7 @@ CONSOLE_check_esc(uint8_t c, uint8_t *submit) if (gopt.is_console == 0) return 0; // Ignore if no console - console_cursor_off(); - // console_stop(); - // gopt.is_console = 0; + cursor_to_ut(); return 0; case 'B': // DOWN if (esc == 0) @@ -561,7 +580,7 @@ CONSOLE_check_esc(uint8_t c, uint8_t *submit) if (gopt.is_console == 0) return 0; // Ignore if no console // Arrow Down - console_cursor_on(); + cursor_to_lt(); return 0; case GS_CONSOLE_ESC_CHR: case GS_CONSOLE_ESC_LCHR: @@ -634,235 +653,253 @@ static struct _pat cls_pattern[] = { {"\x1B""c", 2, 4} // Reset terminal to initial state }; -#ifdef DEBUG /* - * For debugging only. - * Find the next ansi sequence. - * Return the length of the sequence. Set 'is_ansi' if it's an ansi sequence. - * The sequence can be a non-ansi sequence (e.g. normal data) or an ansi sequnce. + * Parse output and check for a any terminal escape sequence that clears + * the screen. + * + * FIXME-PERFORMANCE: Could substitute [J and [2J and [0J with code + * that goes to last line, then clears line '[K' and then scrools up + * x line to clea the screen. That way the console would not need + * to be re-drawn on every 'clear screen' by the app. + * + * Return 0 if not found. + * cls_code = 1 => Clear screen + * cls_code = 2 => Switched to screen buffer + * cls_code = 3 => Switched to normal buffer + * + * amount => Amount of data save to process (remaining is part of an + * unfinished ansi sequence). */ -static size_t -ansi_next(void *data, size_t len, int *is_ansi) + +// Parse through the ansi sequence until it is finished. +// Return length of ansi sequence or 0 if more data is required (ansi sequence hasnt finished yet) +static size_t +ansi_until_end(uint8_t *src, size_t src_sz, int *ignore) { - static int in_esc; - static int in_esc_pos; - uint8_t *src = (uint8_t *)data; + uint8_t *src_end = src + src_sz; uint8_t *src_orig = src; - uint8_t *src_end = src + len; - in_esc_pos = 0; - in_esc = 0; - *is_ansi = 0; - if (*src == '\x1B') - { - src++; - *is_ansi = 1; - in_esc = 1; - } + // Must start with ^[ + XASSERT(*src == '\x1b', "src not starting with 0x1B (0x02%c)\n", *src); + src += 1; + *ignore = 0; - for (; src < src_end; src++) + while (src < src_end) { - if (in_esc == 0) + if (*src == '\x1B') { - if (*src != '\x1B') - continue; - - *is_ansi = 0; + // Huh? An ESC inside an ansi sequence? + *ignore = 1; return src - src_orig; } - // HERE: in escape sequence - // Check when escape finishes - if (*src == '\x1B') - break; - in_esc_pos++; - - if (in_esc_pos == 1) + if (src > src_orig + 16) { - // Check if multi character esc sequence - if (*src == '[') - continue; - if (*src == '(') - continue; - if (*src == ')') - continue; - if (*src == '#') - continue; // Esc-#2 - if (*src == '6') - continue; // Esc-6n - if (*src == '5') - continue; - if (*src == '0') - continue; - if (*src == '3') - continue; - - break; + // ESC sequence is to long. We are not interested.... + *ignore = 1; + return src - src_orig; } - if (in_esc_pos >= 2) + // Check if this is the end of an ansi sequence + if ((*src >= 'a') && (*src <= 'z')) { - if ((*src >= '0') && (*src <= '9')) - continue; - if (*src == ';') - continue; - if (*src == '?') - continue; + src++; + return src - src_orig; + } - break; + if ((*src >= 'A') && (*src <= 'Z')) + { + src++; + return src - src_orig; } - break; + src++; } - return src - src_orig + 1; + + return 0; // Not enough data // src - src_orig; } -// For debugging -static void -ansi_output(void *data, size_t len) +static size_t +ansi_until_esc(uint8_t *src, size_t src_sz, int *in_esc) { - uint8_t *src = (uint8_t *)data; - uint8_t *src_end = src + len; - int is_ansi; - size_t n; - char buf[64]; + uint8_t *src_end = src + src_sz; + uint8_t *src_orig = src; while (src < src_end) { - n = ansi_next(src, src_end - src, &is_ansi); - XASSERT(n > 0, "n is 0\n"); - if (is_ansi) + if (*src == '\x1B') { - snprintf(buf, sizeof buf, "%.*s", (int)(n - 1), src + 1); - DEBUGF_B("ansi: %s\n", buf); + *in_esc = 1; + // DEBUGF("at pos %zd=0x%02x\n", src - src_orig, *src); + break; } - else - HEXDUMP(src, n); - src += n; + src++; } + + return src - src_orig; } -#endif -static uint8_t cls_buf[8]; -static size_t cls_pos; -/* - * Parse output and check for a any terminal escape sequence that clears - * the screen. - * - * FIXME-PERFORMANCE: Could substitute [J and [2J and [0J with code - * that goes to last line, then clears line '[K' and then scrools up - * x line to clea the screen. That way the console would not need - * to be re-drawn on every 'clear screen' by the app. - * - * Return 0 if not found. - * cls_code = 1 => Clear screen - * cls_code = 2 => Switched to screen buffer - * cls_code = 3 => Switched to normal buffer - * - * amount => Amount of data save to process (remaining is part of an - * unfinished ansi sequence). - */ +static int in_esc; +// Parse 'src' for an ansi sequence that we might be interested in. +// *tail_len contains a number of bytes if there is an incomplete ansi-sequence (and we +// do not have enough data yet) +// +// Return: Length of data in dst. static void -ansi_parse(void *data, size_t len, size_t *amount, int *cls_code) +ansi_parse(uint8_t *src, size_t src_sz, GS_BUF *dst, size_t *tail_len, int *cls_code) { - static int in_esc; - static int in_esc_pos; - uint8_t *src = (uint8_t *)data; - uint8_t *src_orig = src; - uint8_t *src_end = src + len; - int rv = 0; + uint8_t *src_end = src + src_sz; + size_t len; + int ignore; + *tail_len = 0; while (src < src_end) { - if (*src == '\x1B') + if (in_esc) { - in_esc = 1; - *amount = src - src_orig; - in_esc_pos = 0; - /* Start of pattern */ - cls_pos = 0; - } else { - if (in_esc == 0) - goto skip; // ESC not yet encountered - } + len = ansi_until_end(src, src_end - src, &ignore); + // DEBUGF("esc len=%zd, ignore=%d, dst=%zd, left=%zd\n", len, ignore, GS_BUF_USED(dst), src_end - src); + if (len == 0) + { + // Not enough data + DEBUGF_R("Not Enough Data. TAIL %zd\n", src_end - src); + DEBUGF("esc len=%zd, ignore=%d, dst=%zd, left=%zd\n", len, ignore, GS_BUF_USED(dst), src_end - src); + HEXDUMP(src, src_end - src); + *tail_len = src_end - src; + return; //break; + } - // Check when escape finishes - while (in_esc != 0) - { - if (*src == '\x1B') + in_esc = 0; +#ifdef DEBUG + // Output some ANSI but ignore some often re-occuring codes: + while (1) + { + if (len <= 4) + break; // Ignore short ones...like [1m + if ((len == 8) && (src[7] == 'm')) + break; // Ingore [39;49m to debug 'top' + DEBUGF_B("ANSI %.*s\n", (int)len -1, src+1); break; - in_esc_pos++; - - if (in_esc_pos == 1) + } +#endif + if (ignore) { - // Check if multi character esc sequence - if (*src == '[') - break; - if (*src == '(') - break; - if (*src == ')') - break; - if (*src == '#') - break; // Esc-#2 - if (*src == '6') - break; // Esc-6n - if (*src == '5') - break; - if (*src == '0') - break; - if (*src == '3') - break; + GS_BUF_add_data(dst, src, len); + src += len; + continue; } - if (in_esc_pos >= 2) + // Check if the Upper Tier (ut) wants the cursor prompt ON or OFF + // Check for this even if the console is closed so that when we open the console + // that the right cursor can be displayed + int is_substitute = 0; + while (len == 6) { - if ((*src >= '0') && (*src <= '9')) - break; - if (*src == ';') + if (memcmp(src + 1, "[?25l", 5) == 0) + ut_cursor = GS_UT_CURSOR_OFF; // OFF + else if (memcmp(src + 1, "[?25h", 5) == 0) + ut_cursor = GS_UT_CURSOR_ON; // ON + else break; - if (*src == '?') + + // DEBUGF_R("ut_cursor=%d, in-console=%d\n", ut_cursor, is_cursor_in_console); + // If cursor is in console then ignore all requests + if (is_cursor_in_console) + { + is_substitute = 1; + src += len; break; + } + break; } + if (is_substitute) + continue; - // *src is last character of escape sequence - in_esc = 0; - break; - } + // Check for Bracketed paste mode [?2004l + if (len == 8) + { + if (memcmp(src + 1, "[?2004l", 7) == 0) + is_console_cursor_needs_reset = 0; + else if (memcmp(src + 1, "[?2004h", 7) == 0) + is_console_cursor_needs_reset = 1; + } - // None of our sequences is longer than this. - if (cls_pos >= sizeof cls_buf) - goto skip; - /* Record sequence */ - cls_buf[cls_pos] = *src; - cls_pos++; + // If console is not open then we do not have to check any other ansi symboles + if (gopt.is_console == 0) + { + GS_BUF_add_data(dst, src, len); + src += len; + continue; + } - // Any sequence we are interested in is at least 2 chars long - if (cls_pos < 2) - goto skip; + // Check if this was a cursor-position request that moved the course + // outside its boundary (and into our console, like debian's top does (!)) + // '\x1b' + '[1;1h' + is_substitute = 0; + while (1) + { + if (len < 6) + break; + // DEBUGF_W("len %d\n", len); + if ((src[len-1] != 'H') && (src[len-1] != 'h')) + break; + // search for ';' between src+2 and src+len + uint8_t *ptr = src+2; + for (ptr = src + 2; ptr < src+len; ptr++) + { + if (*ptr == ';') + break; + } + if (*ptr != ';') + break; - //Check if any ESC sequence matches - int i; - for (i = 0; i < sizeof cls_pattern / sizeof *cls_pattern; i++) - { - if (cls_pattern[i].len != cls_pos) - continue; - if (memcmp(cls_pattern[i].data, cls_buf, cls_pos) != 0) + int row = atoi((char *)src+2); + int col = atoi((char *)ptr+1); + // DEBUGF_W("pos %d:%d\n", row, col); + if (row > gopt.winsize.ws_row - GS_CONSOLE_ROWS) + { + char buf[32]; + snprintf(buf, sizeof buf, "\x1B[%d;%dH\r\n", gopt.winsize.ws_row - GS_CONSOLE_ROWS, col); + DEBUGF_R("DENIED. Changed to: %s\n", buf + 1); + + GS_BUF_add_data(dst, buf, strlen(buf)); + src += len; + is_substitute = 1; + } + break; + } + if (is_substitute) continue; - rv = cls_pattern[i].type; - cls_pos = 0; + + // Check for any ANSI sequence that may have cleared the screen: + int i; + for (i = 0; i < sizeof cls_pattern / sizeof *cls_pattern; i++) + { + if (cls_pattern[i].len != len) + continue; + if (memcmp(cls_pattern[i].data, src, len) != 0) + continue; + DEBUGF_W("CLS found %d\n", cls_pattern[i].type); + *cls_code = cls_pattern[i].type; + } + + // We are not interested to substitute it. Let it pass through. + GS_BUF_add_data(dst, src, len); + src += len; + } else { + // DEBUGF_Y("#%zd not in esc\n", src - src_orig); + len = ansi_until_esc(src, src_end - src, &in_esc); + GS_BUF_add_data(dst, src, len); + src += len; // *src points to ESC or is done. } -skip: - src++; - } - // Not stuck inside esc sequence. - if (in_esc == 0) - *amount = len; - *cls_code = rv; + } } +GS_BUF g_dst; +GS_BUF g_ansi; /* * Buffered write to ansi terminal. All output to terminal needs to be analyzed * and checked for 'clear screen' ansi code. If found then the console needs @@ -879,49 +916,58 @@ ansi_parse(void *data, size_t len, size_t *amount, int *cls_code) * - If half way inside an ansi sequence then buffer the remaining * - Also return if an ansi sequence was sent that clears the screen */ -static uint8_t ansi_buf[64]; -static size_t ansi_buf_len; +// Parse ANSI: +// 1. Find any ansi sequence that clears the screen (so we know when to draw our console again) +// 2. Substitute ESC-sequences with our own to stop console from getting fucked. +// 3. If the ESC-sequence stops half way then write *dst and record +// the remaining sequence (if we have that much space) static ssize_t -ansi_write(int fd, void *data, size_t len, int *cls_code) +ansi_write(int fd, void *src, size_t src_len, int *cls_code) { - size_t amount = 0; + // size_t amount = 0; + size_t tail_len = 0; + size_t src_len_orig = src_len; - ansi_parse(data, len, &amount, cls_code); - // DEBUGF_W("len = %zd amount = %zd\n", len, amount); - if (amount == 0) - goto done; - if (ansi_buf_len > 0) + if (!GS_BUF_IS_INIT(&g_dst)) { - if (write(fd, ansi_buf, ansi_buf_len) != ansi_buf_len) - return -1; - ansi_buf_len = 0; + GS_BUF_init(&g_dst, 1024); + GS_BUF_init(&g_ansi, 1024); } - if (write(fd, data, amount) != amount) - return -1; -#ifdef DEBUG - // ansi_output(data, amount); -#endif - - if (amount < len) + if (GS_BUF_USED(&g_ansi) > 0) { - uint8_t *end = ansi_buf + sizeof (ansi_buf); - uint8_t *ptr = ansi_buf + ansi_buf_len; + GS_BUF_add_data(&g_ansi, src, src_len); + src = GS_BUF_DATA(&g_ansi); + src_len = GS_BUF_USED(&g_ansi); + } - XASSERT(end - ptr >= len - amount, "ANSI buffer to small\n"); + // HEXDUMP(src, src_len); + ansi_parse(src, src_len, &g_dst, &tail_len, cls_code); - memcpy(ptr, (uint8_t *)data + amount, len - amount); - ansi_buf_len += (len - amount); - // HEXDUMPF(ansi_buf, ansi_buf_len, "ansi buffer (%zd)", ansi_buf_len); + if (GS_BUF_USED(&g_dst) > 0) + { + if (write(fd, GS_BUF_DATA(&g_dst), GS_BUF_USED(&g_dst)) != GS_BUF_USED(&g_dst)) + { + DEBUGF_R("Failed to write() all data...\n"); // SHOULD NOT HAPPEN + return -1; + } + } + GS_BUF_empty(&g_dst); + GS_BUF_empty(&g_ansi); + + if (tail_len > 0) + { + // Use memmove() here because src might be pointing to same data but further along + GS_BUF_memmove(&g_ansi, src + src_len - tail_len, tail_len); } -done: + // From the caller's perspective this function has processed all data // and this function will buffer (if needed) any data not yet passed // to 'write()'. Thus return 'len' here to satisfy caller that all supplied // data is or will be processed. - return len; + return src_len_orig; } static int is_console_before_sb; // before Alternate Screen Buffer @@ -953,15 +999,22 @@ CONSOLE_write(int fd, void *data, size_t len) /* Move cursor to upper tier if cursor inside console */ if (is_cursor_in_console) - tty_write("\x1B""8", 2); // Restore cursor to upper tier + { + if (ut_cursor == GS_UT_CURSOR_OFF) + tty_write("\x1B[?25l\x1B""8", 6+2); // Restore cursor to upper tier + else + tty_write("\x1B""8", 2); // Restore cursor to upper tier + } ssize_t sz; sz = ansi_write(fd, data, len, &is_detected_clearscreen); + if (gopt.is_console == 0) + return sz; // The write() to upper tier may have set some funky paste modes // and we need to reset this for console input. - if (sz > 0) - is_console_cursor_needs_reset = 1; + // if (sz > 0) + // is_console_cursor_needs_reset = 1; // if (len > 16) // HEXDUMP(data, MIN(16, len)); @@ -998,16 +1051,17 @@ CONSOLE_write(int fd, void *data, size_t len) console_start(); } - if (gopt.is_console == 0) - return sz; + // if (gopt.is_console == 0) + // return sz; if (is_detected_clearscreen) + { console_draw(fd, 1 /*force*/); + } if (is_cursor_in_console) { - DEBUGF("is_cursor_in_console is true\n"); - console_cursor_on(); + cursor_to_lt(); } return sz; @@ -1092,8 +1146,11 @@ console_start(void) SXPRINTF(ptr, end - ptr, "\x1b[1;%dr", row); // Restore cursor to saved location SXPRINTF(ptr, end - ptr, "\x1B""8"); - tty_write(buf, ptr - buf); + + gopt.is_console = 1; + + cursor_to_lt(); // Start with cursor in console } /* @@ -1111,10 +1168,15 @@ console_stop(void) SXPRINTF(ptr, end - ptr, "\x1B[J"); // Reset scroll size SXPRINTF(ptr, end - ptr, "\x1B[r"); + // Upper Tier wants cursor OFF + if (ut_cursor == GS_UT_CURSOR_OFF) + SXPRINTF(ptr, end - ptr, "\x1B[?25l"); // Restore cursor to upper tier (shell) SXPRINTF(ptr, end - ptr, "\x1B""8"); + tty_write(buf, ptr - buf); is_cursor_in_console = 0; + gopt.is_console = 0; } /* @@ -1160,12 +1222,10 @@ CONSOLE_action(struct _peer *p, uint8_t key) { // Close console and restore cursor console_stop(); - gopt.is_console = 0; return 0; } console_start(); - gopt.is_console = 1; GS_condis_pos(&gs_condis, (gopt.winsize.ws_row - GS_CONSOLE_ROWS) + 1 + 1, gopt.winsize.ws_col); if (is_console_welcome_msg == 0) @@ -1177,7 +1237,6 @@ CONSOLE_action(struct _peer *p, uint8_t key) GS_condis_add(&gs_condis, 0, "Type 'help' for a list of commands."); is_console_welcome_msg = 1; } - console_cursor_on(); // Start with cursor in console // Draw console needed? Resizing remote will trigger a CLEAR (=> re-draw) mk_statusbar(); console_draw(p->fd_out, 1); diff --git a/tools/console_display.c b/tools/console_display.c index 73b73f72..72c64db3 100644 --- a/tools/console_display.c +++ b/tools/console_display.c @@ -41,7 +41,7 @@ GS_condis_add(GS_CONDIS *cd, int level, const char *str) size_t len; struct condis_line *cdl = &cd->cdl[cd->pos_add]; - DEBUGF("-> '%s'\n", str); + DEBUGF("%s\n", str); len = MIN(sizeof cdl->line - 1, strlen(str)); memcpy(cdl->line, str, len); cdl->line[len] = 0x00; diff --git a/tools/filetransfer-test.c b/tools/filetransfer-test.c index 4ad3e443..c9bd374e 100644 --- a/tools/filetransfer-test.c +++ b/tools/filetransfer-test.c @@ -241,7 +241,7 @@ mk_packet(void) // DEBUGF("Packet type=%u length %zu + %zu\n", hdr->type, sizeof *hdr, sz); XASSERT(sz + sizeof *hdr <= GS_BUF_UNUSED(&gsb), "Oops, GS_FT_packet() to long. sz=%zu, unusued=%zu.\n", sz, GS_BUF_UNUSED(&gsb)); - GS_BUF_add(&gsb, sizeof *hdr + sz); + GS_BUF_add_length(&gsb, sizeof *hdr + sz); return 0; } diff --git a/tools/filetransfer.c b/tools/filetransfer.c index d2406ad5..3bb63fa6 100644 --- a/tools/filetransfer.c +++ b/tools/filetransfer.c @@ -209,7 +209,7 @@ GS_FT_add_file(GS_FT *ft, uint32_t id, const char *fname, size_t len, int64_t fs return -1; // protocol error. Not 0 terminated. // FIXME: sanitize file name - DEBUGF_Y("#%u ADD-FILE - size %"PRIu64", fperm 0%o, '%s' mtime=%d flags=0x%02x\n", id, fsize, fperm, fname, mtime, flags); + DEBUGF_Y("#%u ADD-FILE - size %"PRIu64", fperm 0%o, '%s' mtime=%d flags=0x%02x (n_items=%d)\n", id, fsize, fperm, fname, mtime, flags, ft->fadded.n_items); char fn_local[4096]; char *wdir = GS_getpidwd(ft->pid); @@ -359,6 +359,12 @@ GS_FT_list_add(GS_FT *ft, uint32_t globbing_id, const char *fname, size_t len, i { DEBUGF_G("Last file for this globbing id. Free'ing get-list\n"); free_get_li(li); + + // Scenario: All requested files got skipped (already exist). + // - no get request (for file data) outstanding. + // Trigger that GS_FT_packet() is called to return GS_FT_TYPE_DONE (all files transfered) + if ((ft->n_files_waiting == 0) && (ft->plistreq_waiting.n_items == 0)) + ft->is_want_write = 1; } return 0; } @@ -657,13 +663,18 @@ GS_FT_list_add_files(GS_FT *ft, uint32_t globbing_id, const char *pattern, size_ { int ret; char err[128]; + int cwd_fd; if (pattern[len] != '\0') return -1; // protocol error. Not 0-terminated. DEBUGF_Y("G#%u GET-ADD-FILE: %s\n", globbing_id, pattern); + + cwd_fd = open(".", O_RDONLY); + if (cwd_fd < 0) + DEBUGF_R("open(.): %s\n", strerror(errno)); char *ptr = GS_getpidwd(ft->pid); - ret = chdir(ptr); + ret = chdir(ptr); // Temporarily change CWD for globbing to work. if (ret != 0) { @@ -684,6 +695,12 @@ GS_FT_list_add_files(GS_FT *ft, uint32_t globbing_id, const char *pattern, size_ } done: + if (cwd_fd >= 0) + { + // Change back to original CWD. + if (fchdir(cwd_fd) == 0) {} // ignore results + close(cwd_fd); + } XFREE(ptr); ft->is_want_write = 1; return ret; @@ -791,6 +808,10 @@ receiving_complete(GS_FT *ft, struct _gs_ft_file *f) ft_done(ft); ft_del(f->li); + + // Trigger that GS_FT_packet() is called to return GS_FT_TYPE_DONE (all files transfered) + if ((ft->n_files_waiting == 0) && (ft->plistreq_waiting.n_items == 0)) + ft->is_want_write = 1; } else { // SERVER (put, upload: all data received) XFCLOSE(f->fp); @@ -962,7 +983,7 @@ mkdirpm(const char *path, mode_t mode, uint32_t mtime) struct stat res; char *f = NULL; - DEBUGF("mkdirpm(%s)\n", path); + // DEBUGF("mkdirpm(%s)\n", path); // Return 0 if directory already exist if (stat(path, &res) == 0) { @@ -993,7 +1014,7 @@ mkdirpm(const char *path, mode_t mode, uint32_t mtime) if (S_ISDIR(res.st_mode)) { // HERE: Parent directory exists. - DEBUGF_W("1-mkdir(%s)\n", path); + // DEBUGF_W("1-mkdir(%s)\n", path); if (mkdir_agressive(path, mode, mtime) != 0) rv = -1; goto done; @@ -1035,7 +1056,7 @@ mkdirpm(const char *path, mode_t mode, uint32_t mtime) *ptr = '\0'; else new_mode = mode; // last part of directory - DEBUGF_W("2-mkdir(%s, 0%o)\n", f, new_mode); + // DEBUGF_W("2-mkdir(%s, 0%o)\n", f, new_mode); if (mkdir_agressive(f, new_mode, mtime) != 0) { rv = -1; @@ -1168,6 +1189,7 @@ gs_ft_switch(GS_FT *ft, uint32_t id, int64_t fz_remote, struct _gs_ft_file **act if (new->fz_remote == 0) { // Zero File Size + DEBUGF("ZERO sized file received\n"); receiving_complete(ft, new); return; } @@ -1238,13 +1260,20 @@ ft_del(GS_LIST_ITEM *li) // Human readable bps string static void -mk_bps(char *str, size_t sz, uint64_t duration, uint64_t amount, int err) +mk_bps(char *str, size_t sz, uint64_t duration, uint64_t amount, int err, int is_zero) { if (err != 0) { snprintf(str, sz, "ERROR"); return; } + + if (is_zero) + { + snprintf(str, sz, "zero size"); + return; + } + if (duration > 0) GS_format_bps(str, sz, (amount * 1000000 / duration), "/s"); else @@ -1270,7 +1299,12 @@ mk_stats_file(GS_FT *ft, uint32_t id, struct _gs_ft_file *f, const char *name, i if (f != NULL) { + // Check if this file had zero size + if ((f->fz_remote == 0) && (f->fz_local == 0)) + s.is_zero = 1; + s.xfer_amount = f->xfer_amount; + if (f->usec_start > f->usec_end) f->usec_end = GS_usec(); @@ -1283,7 +1317,8 @@ mk_stats_file(GS_FT *ft, uint32_t id, struct _gs_ft_file *f, const char *name, i s.xfer_duration = (f->usec_end - f->usec_start) - f->usec_suspend_duration; } - mk_bps(s.speed_str, sizeof s.speed_str, s.xfer_duration, s.xfer_amount, err); + + mk_bps(s.speed_str, sizeof s.speed_str, s.xfer_duration, s.xfer_amount, err, s.is_zero); // Global stats for all files ft->stats.xfer_duration += s.xfer_duration; @@ -1313,7 +1348,7 @@ mk_stats_ft(GS_FT *ft) { GS_FT_stats *st = &ft->stats; - mk_bps(st->speed_str, sizeof st->speed_str, st->xfer_duration, st->xfer_amount, st->n_files_success==0?1:0); + mk_bps(st->speed_str, sizeof st->speed_str, st->xfer_duration, st->xfer_amount, st->n_files_success==0?1:0, 0 /*is_zero*/); } void @@ -1359,7 +1394,7 @@ GS_FT_status(GS_FT *ft, uint32_t id, uint8_t code, const char *err_str, size_t l if (err_str[len] != '\0') return; // protocol error. Not 0 terminated. - DEBUGF_R("#%u STATUS: code=%u (%s)\n", id, code, err_str); + DEBUGF_R("#%u STATUS: code=%u [%s](remote says='%s')\n", id, code, GS_FT_strerror(code), err_str); // There can not be an error in fqueue or plistreq or flist as those are // local lists. Here we only care about lists that send a request @@ -1432,6 +1467,7 @@ GS_FT_status(GS_FT *ft, uint32_t id, uint8_t code, const char *err_str, size_t l // Report stats to caller. Tread ERR_COMPLETED not as an error. mk_stats_file(ft, id, f, name, err); } + } if (f != NULL) @@ -1439,8 +1475,10 @@ GS_FT_status(GS_FT *ft, uint32_t id, uint8_t code, const char *err_str, size_t l if (li->data == ft->active_put_file) ft->active_put_file = NULL; - ft_done(ft); - ft_del(li); + if (ft->is_server == 0) + ft_done(ft); + + ft_del(li); // SERVER & CLIENT } else { // From a PATTERN request (LISTREQ, Client, get, download). // File structure is not available. @@ -1448,6 +1486,17 @@ GS_FT_status(GS_FT *ft, uint32_t id, uint8_t code, const char *err_str, size_t l GS_LIST_del(li); } + DEBUGF("WAITING: %d %d\n", ft->n_files_waiting, ft->plistreq_waiting.n_items); + if (ft->is_server == 0) + { + // CLIENT + // Trigger caller to call GS_FT_packet() so that caller gets the GS_FT_DONE return + // value to then output (and reset) the stats. This can happen when a zero-sized file + // is transfered. + if ((ft->n_files_waiting == 0) && (ft->plistreq_waiting.n_items == 0)) + ft->is_want_write = 1; + } + } /* diff --git a/tools/filetransfer.h b/tools/filetransfer.h index ded622c6..0d70026b 100644 --- a/tools/filetransfer.h +++ b/tools/filetransfer.h @@ -44,6 +44,7 @@ struct _gs_ft_stats_file const char *fname; uint64_t xfer_duration; // Actual transfer time (without suspension) uint64_t xfer_amount; // Actual data transfered + int is_zero; char speed_str[GS_FT_SPEEDSTR_MAXSIZE]; // Speed (bps). Human readable string. }; @@ -114,9 +115,7 @@ typedef struct int is_paused_data; // write() blocked. Queue control data. Pause sending file data int n_files_waiting; // Files waiting for completion or error FIXME: This should be n_requests_waiting - int is_want_write; - // ..and be a counter of all outstanding requests we are awaiting an answer for.... - // int n_listreply_waiting; + int is_want_write; // FT has data to write. Requesting call to GS_FT_packet(). // Statistics total (all files) GS_FT_stats stats; diff --git a/tools/filetransfer_mgr.c b/tools/filetransfer_mgr.c index 10fd0a90..675b2633 100644 --- a/tools/filetransfer_mgr.c +++ b/tools/filetransfer_mgr.c @@ -101,6 +101,8 @@ pkt_cb_switch(uint8_t chn, const uint8_t *data, size_t len, void *arg) memcpy(&hdr, data, sizeof hdr); GS_FT_switch(ft, ntohl(hdr.id), ntohll(hdr.offset)); + if (GS_FT_WANT_WRITE(ft)) + GS_SELECT_FD_SET_W(peer->gs); } /* SERVER receiving DATA from client */ @@ -147,6 +149,13 @@ pkt_cb_error(uint8_t chn, const uint8_t *data, size_t len, void *arg) memcpy(&hdr, data, sizeof hdr); GS_FT_status(ft, ntohl(hdr.id), hdr.code, (char *)p->str, len - sizeof hdr - 1); + if (GS_FT_WANT_WRITE(ft)) + { + // GS_FT-stack wants caller to call GS_FT_packet(). The only way we can + // trigger this is to set FD_SET_W() and we know that after select() + // we call GS_FT_packet()... + GS_SELECT_FD_SET_W(peer->gs); + } } // Output total stats @@ -224,6 +233,7 @@ GS_FTM_mk_packet(GS_FT *ft, uint8_t *dst, size_t dlen) // DEBUGF_G("TYPE NONE\n"); return 0; case GS_FT_TYPE_DONE: + // DEBUGF_W("GS_FT_TYPE_DONE\n"); // CLIENT only: done with all files. // FIXME: for a 'get' request this is triggered very late and not as soon // as the filetransfer is done (because select() only waits for reading diff --git a/tools/man_gs-netcat.h b/tools/man_gs-netcat.h index 58c19cbe..8f42e4bf 100644 --- a/tools/man_gs-netcat.h +++ b/tools/man_gs-netcat.h @@ -45,7 +45,7 @@ OPTIONS\n\ \n\ -l Server mode. The default mode is client.\n\ \n\ - -q Quite mode. Do not output any warnings or errors.\n\ + -q Quiet mode. Do not output any warnings or errors.\n\ \n\ -w Client to wait for the listening server to become available.\n\ \n\ diff --git a/tools/utils.c b/tools/utils.c index 69512256..f63232a7 100644 --- a/tools/utils.c +++ b/tools/utils.c @@ -1,12 +1,17 @@ +// #define DEBUG_CTX_DECLARED (1) // All others define this as extern + #include "common.h" #include "utils.h" #include "console.h" struct _gopt gopt; - +#ifdef DEBUG +struct _g_debug_ctx g_dbg_ctx; +#endif extern char **environ; + /* * Add list of argv's from GSOCKET_ARGS to argv[] * result: argv[0] + GSOCKET_ARGS + argv[1..n] @@ -198,7 +203,7 @@ usage(const char *params) fprintf(stderr, " -L Logfile\n"); break; case 'q': - fprintf(stderr, " -q Quite. No log output\n"); + fprintf(stderr, " -q Quiet. No log output\n"); break; case 'r': fprintf(stderr, " -r Receive-only. Terminate when no more data.\n"); @@ -277,7 +282,7 @@ do_getopt(int argc, char *argv[]) gopt.is_use_tor = 1; break; case 'q': - gopt.is_quite = 1; + gopt.is_quiet = 1; break; case 'r': gopt.is_receive_only = 1;