aboutsummaryrefslogtreecommitdiff
path: root/libbb
diff options
context:
space:
mode:
authorNguyễn Thái Ngọc Duy <pclouds@gmail.com>2010-09-14 14:09:23 +1000
committerNguyễn Thái Ngọc Duy <pclouds@gmail.com>2010-09-14 14:09:23 +1000
commitee7c9b2c212fc7db80cce945e094fc2601092283 (patch)
tree24e51b27dbc3e9ab0b00c5839a6822604c02187c /libbb
parentf28d4b20905b5b1f52ffa52060a0c6caf4b055ba (diff)
parent99862cbfad9c36b4f8f4378c3a7a9f077c239f20 (diff)
downloadbusybox-w32-ee7c9b2c212fc7db80cce945e094fc2601092283.tar.gz
busybox-w32-ee7c9b2c212fc7db80cce945e094fc2601092283.tar.bz2
busybox-w32-ee7c9b2c212fc7db80cce945e094fc2601092283.zip
Merge remote branch 'origin/master'
Diffstat (limited to 'libbb')
-rw-r--r--libbb/Config.src4
-rw-r--r--libbb/appletlib.c1
-rw-r--r--libbb/default_error_retval.c2
-rw-r--r--libbb/lineedit.c742
-rw-r--r--libbb/unicode.c2
5 files changed, 381 insertions, 370 deletions
diff --git a/libbb/Config.src b/libbb/Config.src
index 9b01757c6..74dc9c549 100644
--- a/libbb/Config.src
+++ b/libbb/Config.src
@@ -78,9 +78,9 @@ config FEATURE_EDITING_HISTORY
78config FEATURE_EDITING_SAVEHISTORY 78config FEATURE_EDITING_SAVEHISTORY
79 bool "History saving" 79 bool "History saving"
80 default y 80 default y
81 depends on ASH && FEATURE_EDITING 81 depends on FEATURE_EDITING
82 help 82 help
83 Enable history saving in ash shell. 83 Enable history saving in shells.
84 84
85config FEATURE_TAB_COMPLETION 85config FEATURE_TAB_COMPLETION
86 bool "Tab completion" 86 bool "Tab completion"
diff --git a/libbb/appletlib.c b/libbb/appletlib.c
index 751000e5d..6a304ded3 100644
--- a/libbb/appletlib.c
+++ b/libbb/appletlib.c
@@ -655,6 +655,7 @@ static int busybox_main(char **argv)
655 "See source distribution for full notice.\n" 655 "See source distribution for full notice.\n"
656 "\n" 656 "\n"
657 "Usage: busybox [function] [arguments]...\n" 657 "Usage: busybox [function] [arguments]...\n"
658 " or: busybox --list[-full]\n"
658 " or: function [arguments]...\n" 659 " or: function [arguments]...\n"
659 "\n" 660 "\n"
660 "\tBusyBox is a multi-call binary that combines many common Unix\n" 661 "\tBusyBox is a multi-call binary that combines many common Unix\n"
diff --git a/libbb/default_error_retval.c b/libbb/default_error_retval.c
index 4da5b3efb..4f6395fa2 100644
--- a/libbb/default_error_retval.c
+++ b/libbb/default_error_retval.c
@@ -15,4 +15,4 @@
15 15
16#include "libbb.h" 16#include "libbb.h"
17 17
18int xfunc_error_retval = EXIT_FAILURE; 18uint8_t xfunc_error_retval = EXIT_FAILURE;
diff --git a/libbb/lineedit.c b/libbb/lineedit.c
index 866f9230d..3af9f4e64 100644
--- a/libbb/lineedit.c
+++ b/libbb/lineedit.c
@@ -1,6 +1,6 @@
1/* vi: set sw=4 ts=4: */ 1/* vi: set sw=4 ts=4: */
2/* 2/*
3 * Termios command line History and Editing. 3 * Command line editing.
4 * 4 *
5 * Copyright (c) 1986-2003 may safely be consumed by a BSD or GPL license. 5 * Copyright (c) 1986-2003 may safely be consumed by a BSD or GPL license.
6 * Written by: Vladimir Oleynik <dzo@simtreas.ru> 6 * Written by: Vladimir Oleynik <dzo@simtreas.ru>
@@ -99,7 +99,6 @@ static bool BB_ispunct(CHAR_T c) { return ((unsigned)c < 256 && ispunct(c)); }
99 99
100 100
101enum { 101enum {
102 /* We use int16_t for positions, need to limit line len */
103 MAX_LINELEN = CONFIG_FEATURE_EDITING_MAX_LEN < 0x7ff0 102 MAX_LINELEN = CONFIG_FEATURE_EDITING_MAX_LEN < 0x7ff0
104 ? CONFIG_FEATURE_EDITING_MAX_LEN 103 ? CONFIG_FEATURE_EDITING_MAX_LEN
105 : 0x7ff0 104 : 0x7ff0
@@ -151,14 +150,6 @@ struct lineedit_statics {
151#if ENABLE_FEATURE_EDITING_ASK_TERMINAL 150#if ENABLE_FEATURE_EDITING_ASK_TERMINAL
152 smallint sent_ESC_br6n; 151 smallint sent_ESC_br6n;
153#endif 152#endif
154
155 /* Formerly these were big buffers on stack: */
156#if ENABLE_FEATURE_TAB_COMPLETION
157 char exe_n_cwd_tab_completion__dirbuf[MAX_LINELEN];
158 char input_tab__matchBuf[MAX_LINELEN];
159 int16_t find_match__int_buf[MAX_LINELEN + 1]; /* need to have 9 bits at least */
160 int16_t find_match__pos_buf[MAX_LINELEN + 1];
161#endif
162}; 153};
163 154
164/* See lineedit_ptr_hack.c */ 155/* See lineedit_ptr_hack.c */
@@ -195,7 +186,7 @@ static void deinit_S(void)
195{ 186{
196#if ENABLE_FEATURE_EDITING_FANCY_PROMPT 187#if ENABLE_FEATURE_EDITING_FANCY_PROMPT
197 /* This one is allocated only if FANCY_PROMPT is on 188 /* This one is allocated only if FANCY_PROMPT is on
198 * (otherwise it points to verbatim prompt (NOT malloced) */ 189 * (otherwise it points to verbatim prompt (NOT malloced)) */
199 free((char*)cmdedit_prompt); 190 free((char*)cmdedit_prompt);
200#endif 191#endif
201#if ENABLE_USERNAME_OR_HOMEDIR 192#if ENABLE_USERNAME_OR_HOMEDIR
@@ -214,7 +205,7 @@ static size_t load_string(const char *src, int maxsize)
214 ssize_t len = mbstowcs(command_ps, src, maxsize - 1); 205 ssize_t len = mbstowcs(command_ps, src, maxsize - 1);
215 if (len < 0) 206 if (len < 0)
216 len = 0; 207 len = 0;
217 command_ps[len] = 0; 208 command_ps[len] = BB_NUL;
218 return len; 209 return len;
219} 210}
220static unsigned save_string(char *dst, unsigned maxsize) 211static unsigned save_string(char *dst, unsigned maxsize)
@@ -233,18 +224,21 @@ static unsigned save_string(char *dst, unsigned maxsize)
233 while (dstpos < maxsize) { 224 while (dstpos < maxsize) {
234 wchar_t wc; 225 wchar_t wc;
235 int n = srcpos; 226 int n = srcpos;
236 while ((wc = command_ps[srcpos]) != 0 227
228 /* Convert up to 1st invalid byte (or up to end) */
229 while ((wc = command_ps[srcpos]) != BB_NUL
237 && !unicode_is_raw_byte(wc) 230 && !unicode_is_raw_byte(wc)
238 ) { 231 ) {
239 srcpos++; 232 srcpos++;
240 } 233 }
241 command_ps[srcpos] = 0; 234 command_ps[srcpos] = BB_NUL;
242 n = wcstombs(dst + dstpos, command_ps + n, maxsize - dstpos); 235 n = wcstombs(dst + dstpos, command_ps + n, maxsize - dstpos);
243 if (n < 0) /* should not happen */ 236 if (n < 0) /* should not happen */
244 break; 237 break;
245 dstpos += n; 238 dstpos += n;
246 if (wc == 0) /* usually is */ 239 if (wc == BB_NUL) /* usually is */
247 break; 240 break;
241
248 /* We do have invalid byte here! */ 242 /* We do have invalid byte here! */
249 command_ps[srcpos] = wc; /* restore it */ 243 command_ps[srcpos] = wc; /* restore it */
250 srcpos++; 244 srcpos++;
@@ -606,55 +600,62 @@ static void add_match(char *matched)
606} 600}
607 601
608#if ENABLE_FEATURE_USERNAME_COMPLETION 602#if ENABLE_FEATURE_USERNAME_COMPLETION
609static void username_tab_completion(char *ud, char *with_shash_flg) 603/* Replace "~user/..." with "/homedir/...".
604 * The parameter is malloced, free it or return it
605 * unchanged if no user is matched.
606 */
607static char *username_path_completion(char *ud)
610{ 608{
611 struct passwd *entry; 609 struct passwd *entry;
612 int userlen; 610 char *tilde_name = ud;
611 char *home = NULL;
613 612
614 ud++; /* ~user/... to user/... */ 613 ud++; /* skip ~ */
615 userlen = strlen(ud); 614 if (*ud == '/') { /* "~/..." */
615 home = home_pwd_buf;
616 } else {
617 /* "~user/..." */
618 ud = strchr(ud, '/');
619 *ud = '\0'; /* "~user" */
620 entry = getpwnam(tilde_name + 1);
621 *ud = '/'; /* restore "~user/..." */
622 if (entry)
623 home = entry->pw_dir;
624 }
625 if (home) {
626 ud = concat_path_file(home, ud);
627 free(tilde_name);
628 tilde_name = ud;
629 }
630 return tilde_name;
631}
616 632
617 if (with_shash_flg) { /* "~/..." or "~user/..." */ 633/* ~use<tab> - find all users with this prefix.
618 char *sav_ud = ud - 1; 634 * Return the length of the prefix used for matching.
619 char *home = NULL; 635 */
636static NOINLINE unsigned complete_username(const char *ud)
637{
638 /* Using _r function to avoid pulling in static buffers */
639 char line_buff[256];
640 struct passwd pwd;
641 struct passwd *result;
642 unsigned userlen;
620 643
621 if (*ud == '/') { /* "~/..." */ 644 ud++; /* skip ~ */
622 home = home_pwd_buf; 645 userlen = strlen(ud);
623 } else { 646
624 /* "~user/..." */ 647 setpwent();
625 char *temp; 648 while (!getpwent_r(&pwd, line_buff, sizeof(line_buff), &result)) {
626 temp = strchr(ud, '/'); 649 /* Null usernames should result in all users as possible completions. */
627 *temp = '\0'; /* ~user\0 */ 650 if (/*!userlen || */ strncmp(ud, pwd.pw_name, userlen) == 0) {
628 entry = getpwnam(ud); 651 add_match(xasprintf("~%s/", pwd.pw_name));
629 *temp = '/'; /* restore ~user/... */
630 ud = temp;
631 if (entry)
632 home = entry->pw_dir;
633 }
634 if (home) {
635 if ((userlen + strlen(home) + 1) < MAX_LINELEN) {
636 /* /home/user/... */
637 sprintf(sav_ud, "%s%s", home, ud);
638 }
639 }
640 } else {
641 /* "~[^/]*" */
642 /* Using _r function to avoid pulling in static buffers */
643 char line_buff[256];
644 struct passwd pwd;
645 struct passwd *result;
646
647 setpwent();
648 while (!getpwent_r(&pwd, line_buff, sizeof(line_buff), &result)) {
649 /* Null usernames should result in all users as possible completions. */
650 if (/*!userlen || */ strncmp(ud, pwd.pw_name, userlen) == 0) {
651 add_match(xasprintf("~%s/", pwd.pw_name));
652 }
653 } 652 }
654 endpwent();
655 } 653 }
654 endpwent();
655
656 return 1 + userlen;
656} 657}
657#endif /* FEATURE_COMMAND_USERNAME_COMPLETION */ 658#endif /* FEATURE_USERNAME_COMPLETION */
658 659
659enum { 660enum {
660 FIND_EXE_ONLY = 0, 661 FIND_EXE_ONLY = 0,
@@ -662,22 +663,19 @@ enum {
662 FIND_FILE_ONLY = 2, 663 FIND_FILE_ONLY = 2,
663}; 664};
664 665
665static int path_parse(char ***p, int flags) 666static int path_parse(char ***p)
666{ 667{
667 int npth; 668 int npth;
668 const char *pth; 669 const char *pth;
669 char *tmp; 670 char *tmp;
670 char **res; 671 char **res;
671 672
672 /* if not setenv PATH variable, to search cur dir "." */
673 if (flags != FIND_EXE_ONLY)
674 return 1;
675
676 if (state->flags & WITH_PATH_LOOKUP) 673 if (state->flags & WITH_PATH_LOOKUP)
677 pth = state->path_lookup; 674 pth = state->path_lookup;
678 else 675 else
679 pth = getenv("PATH"); 676 pth = getenv("PATH");
680 /* PATH=<empty> or PATH=:<empty> */ 677
678 /* PATH="" or PATH=":"? */
681 if (!pth || !pth[0] || LONE_CHAR(pth, ':')) 679 if (!pth || !pth[0] || LONE_CHAR(pth, ':'))
682 return 1; 680 return 1;
683 681
@@ -691,12 +689,13 @@ static int path_parse(char ***p, int flags)
691#endif 689#endif
692 if (!tmp) 690 if (!tmp)
693 break; 691 break;
694 if (*++tmp == '\0') 692 tmp++;
693 if (*tmp == '\0')
695 break; /* :<empty> */ 694 break; /* :<empty> */
696 npth++; 695 npth++;
697 } 696 }
698 697
699 res = xmalloc(npth * sizeof(char*)); 698 *p = res = xmalloc(npth * sizeof(res[0]));
700 res[0] = tmp = xstrdup(pth); 699 res[0] = tmp = xstrdup(pth);
701 npth = 1; 700 npth = 1;
702 while (1) { 701 while (1) {
@@ -712,235 +711,241 @@ static int path_parse(char ***p, int flags)
712 break; /* :<empty> */ 711 break; /* :<empty> */
713 res[npth++] = tmp; 712 res[npth++] = tmp;
714 } 713 }
715 *p = res;
716 return npth; 714 return npth;
717} 715}
718 716
719static void exe_n_cwd_tab_completion(char *command, int type) 717/* Complete command, directory or file name.
718 * Return the length of the prefix used for matching.
719 */
720static NOINLINE unsigned complete_cmd_dir_file(const char *command, int type)
720{ 721{
721 DIR *dir;
722 struct dirent *next;
723 struct stat st;
724 char *path1[1]; 722 char *path1[1];
725 char **paths = path1; 723 char **paths = path1;
726 int npaths; 724 int npaths;
727 int i; 725 int i;
728 char *found; 726 unsigned pf_len;
729 char *pfind = strrchr(command, '/'); 727 const char *pfind;
730/* char dirbuf[MAX_LINELEN]; */ 728 char *dirbuf = NULL;
731#define dirbuf (S.exe_n_cwd_tab_completion__dirbuf)
732 729
733 npaths = 1; 730 npaths = 1;
734 path1[0] = (char*)"."; 731 path1[0] = (char*)".";
735 732
736 if (pfind == NULL) { 733 pfind = strrchr(command, '/');
737 /* no dir, if flags==EXE_ONLY - get paths, else "." */ 734 if (!pfind) {
738 npaths = path_parse(&paths, type); 735 if (type == FIND_EXE_ONLY)
736 npaths = path_parse(&paths);
739 pfind = command; 737 pfind = command;
740 } else { 738 } else {
739 /* point to 'l' in "..../last_component" */
740 pfind++;
741 /* dirbuf = ".../.../.../" */ 741 /* dirbuf = ".../.../.../" */
742 safe_strncpy(dirbuf, command, (pfind - command) + 2); 742 dirbuf = xstrndup(command, pfind - command);
743#if ENABLE_FEATURE_USERNAME_COMPLETION 743#if ENABLE_FEATURE_USERNAME_COMPLETION
744 if (dirbuf[0] == '~') /* ~/... or ~user/... */ 744 if (dirbuf[0] == '~') /* ~/... or ~user/... */
745 username_tab_completion(dirbuf, dirbuf); 745 dirbuf = username_path_completion(dirbuf);
746#endif 746#endif
747 paths[0] = dirbuf; 747 path1[0] = dirbuf;
748 /* point to 'l' in "..../last_component" */
749 pfind++;
750 } 748 }
749 pf_len = strlen(pfind);
751 750
752 for (i = 0; i < npaths; i++) { 751 for (i = 0; i < npaths; i++) {
752 DIR *dir;
753 struct dirent *next;
754 struct stat st;
755 char *found;
756
753 dir = opendir(paths[i]); 757 dir = opendir(paths[i]);
754 if (!dir) 758 if (!dir)
755 continue; /* don't print an error */ 759 continue; /* don't print an error */
756 760
757 while ((next = readdir(dir)) != NULL) { 761 while ((next = readdir(dir)) != NULL) {
758 int len1; 762 unsigned len;
759 const char *str_found = next->d_name; 763 const char *name_found = next->d_name;
760 764
761 /* matched? */ 765 /* .../<tab>: bash 3.2.0 shows dotfiles, but not . and .. */
762 if (strncmp(str_found, pfind, strlen(pfind))) 766 if (!pfind[0] && DOT_OR_DOTDOT(name_found))
763 continue; 767 continue;
764 /* not see .name without .match */ 768 /* match? */
765 if (*str_found == '.' && *pfind == '\0') { 769 if (strncmp(name_found, pfind, pf_len) != 0)
766 if (NOT_LONE_CHAR(paths[i], '/') || str_found[1]) 770 continue; /* no */
767 continue; 771
768 str_found = ""; /* only "/" */ 772 found = concat_path_file(paths[i], name_found);
769 }
770 found = concat_path_file(paths[i], str_found);
771 /* hmm, remove in progress? */
772 /* NB: stat() first so that we see is it a directory; 773 /* NB: stat() first so that we see is it a directory;
773 * but if that fails, use lstat() so that 774 * but if that fails, use lstat() so that
774 * we still match dangling links */ 775 * we still match dangling links */
775 if (stat(found, &st) && lstat(found, &st)) 776 if (stat(found, &st) && lstat(found, &st))
776 goto cont; 777 goto cont; /* hmm, remove in progress? */
777 /* find with dirs? */
778 if (paths[i] != dirbuf)
779 strcpy(found, next->d_name); /* only name */
780 778
781 len1 = strlen(found); 779 /* Save only name */
782 found = xrealloc(found, len1 + 2); 780 len = strlen(name_found);
783 found[len1] = '\0'; 781 found = xrealloc(found, len + 2); /* +2: for slash and NUL */
784 found[len1+1] = '\0'; 782 strcpy(found, name_found);
785 783
786 if (S_ISDIR(st.st_mode)) { 784 if (S_ISDIR(st.st_mode)) {
787 /* name is a directory */ 785 /* name is a directory, add slash */
788 if (found[len1-1] != '/') { 786 found[len] = '/';
789 found[len1] = '/'; 787 found[len + 1] = '\0';
790 }
791 } else { 788 } else {
792 /* not put found file if search only dirs for cd */ 789 /* skip files if looking for dirs only (example: cd) */
793 if (type == FIND_DIR_ONLY) 790 if (type == FIND_DIR_ONLY)
794 goto cont; 791 goto cont;
795 } 792 }
796 /* Add it to the list */ 793 /* add it to the list */
797 add_match(found); 794 add_match(found);
798 continue; 795 continue;
799 cont: 796 cont:
800 free(found); 797 free(found);
801 } 798 }
802 closedir(dir); 799 closedir(dir);
803 } 800 } /* for every path */
801
804 if (paths != path1) { 802 if (paths != path1) {
805 free(paths[0]); /* allocated memory is only in first member */ 803 free(paths[0]); /* allocated memory is only in first member */
806 free(paths); 804 free(paths);
807 } 805 }
808#undef dirbuf 806 free(dirbuf);
807
808 return pf_len;
809} 809}
810 810
811/* build_match_prefix:
812 * On entry, match_buf contains everything up to cursor at the moment <tab>
813 * was pressed. This function looks at it, figures out what part of it
814 * constitutes the command/file/directory prefix to use for completion,
815 * and rewrites match_buf to contain only that part.
816 */
817#define dbg_bmp 0
818/* Helpers: */
811/* QUOT is used on elements of int_buf[], which are bytes, 819/* QUOT is used on elements of int_buf[], which are bytes,
812 * not Unicode chars. Therefore it works correctly even in Unicode mode. 820 * not Unicode chars. Therefore it works correctly even in Unicode mode.
813 */ 821 */
814#define QUOT (UCHAR_MAX+1) 822#define QUOT (UCHAR_MAX+1)
815 823static void remove_chunk(int16_t *int_buf, int beg, int end)
816#define int_buf (S.find_match__int_buf)
817#define pos_buf (S.find_match__pos_buf)
818/* is must be <= in */
819static void collapse_pos(int is, int in)
820{ 824{
821 memmove(int_buf+is, int_buf+in, (MAX_LINELEN+1-in)*sizeof(int_buf[0])); 825 /* beg must be <= end */
822 memmove(pos_buf+is, pos_buf+in, (MAX_LINELEN+1-in)*sizeof(pos_buf[0])); 826 if (beg == end)
827 return;
828
829 while ((int_buf[beg] = int_buf[end]) != 0)
830 beg++, end++;
831
832 if (dbg_bmp) {
833 int i;
834 for (i = 0; int_buf[i]; i++)
835 bb_putchar((unsigned char)int_buf[i]);
836 bb_putchar('\n');
837 }
823} 838}
824static NOINLINE int find_match(char *matchBuf, int *len_with_quotes) 839/* Caller ensures that match_buf points to a malloced buffer
840 * big enough to hold strlen(match_buf)*2 + 2
841 */
842static NOINLINE int build_match_prefix(char *match_buf)
825{ 843{
826 int i, j; 844 int i, j;
827 int command_mode; 845 int command_mode;
828 int c, c2; 846 int16_t *int_buf = (int16_t*)match_buf;
829/* Were local, but it uses too much stack */
830/* int16_t int_buf[MAX_LINELEN + 1]; */
831/* int16_t pos_buf[MAX_LINELEN + 1]; */
832
833 /* set to integer dimension characters and own positions */
834 for (i = 0;; i++) {
835 int_buf[i] = (unsigned char)matchBuf[i];
836 if (int_buf[i] == 0) {
837 pos_buf[i] = -1; /* end-fo-line indicator */
838 break;
839 }
840 pos_buf[i] = i;
841 }
842 847
843 /* mask \+symbol and convert '\t' to ' ' */ 848 if (dbg_bmp) printf("\n%s\n", match_buf);
844 for (i = j = 0; matchBuf[i]; i++, j++) { 849
845 if (matchBuf[i] == '\\') { 850 /* Copy in reverse order, since they overlap */
846 collapse_pos(j, j + 1); 851 i = strlen(match_buf);
847 int_buf[j] |= QUOT; 852 do {
848 i++; 853 int_buf[i] = (unsigned char)match_buf[i];
854 i--;
855 } while (i >= 0);
856
857 /* Mark every \c as "quoted c" */
858 for (i = 0; int_buf[i]; i++) {
859 if (int_buf[i] == '\\') {
860 remove_chunk(int_buf, i, i + 1);
861 int_buf[i] |= QUOT;
849 } 862 }
850 } 863 }
851 /* mask "symbols" or 'symbols' */ 864 /* Quote-mark "chars" and 'chars', drop delimiters */
852 c2 = 0; 865 {
853 for (i = 0; int_buf[i]; i++) { 866 int in_quote = 0;
854 c = int_buf[i]; 867 i = 0;
855 if (c == '\'' || c == '"') { 868 while (int_buf[i]) {
856 if (c2 == 0) 869 int cur = int_buf[i];
857 c2 = c; 870 if (!cur)
858 else { 871 break;
859 if (c == c2) 872 if (cur == '\'' || cur == '"') {
860 c2 = 0; 873 if (!in_quote || (cur == in_quote)) {
861 else 874 in_quote ^= cur;
862 int_buf[i] |= QUOT; 875 remove_chunk(int_buf, i, i + 1);
876 continue;
877 }
863 } 878 }
864 } else if (c2 != 0 && c != '$') 879 if (in_quote)
865 int_buf[i] |= QUOT; 880 int_buf[i] = cur | QUOT;
881 i++;
882 }
866 } 883 }
867 884
868 /* skip commands with arguments if line has commands delimiters */ 885 /* Remove everything up to command delimiters:
869 /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */ 886 * ';' ';;' '&' '|' '&&' '||',
887 * but careful with '>&' '<&' '>|'
888 */
870 for (i = 0; int_buf[i]; i++) { 889 for (i = 0; int_buf[i]; i++) {
871 c = int_buf[i]; 890 int cur = int_buf[i];
872 c2 = int_buf[i + 1]; 891 if (cur == ';' || cur == '&' || cur == '|') {
873 j = i ? int_buf[i - 1] : -1; 892 int prev = i ? int_buf[i - 1] : 0;
874 command_mode = 0; 893 if (cur == '&' && (prev == '>' || prev == '<')) {
875 if (c == ';' || c == '&' || c == '|') { 894 continue;
876 command_mode = 1 + (c == c2); 895 } else if (cur == '|' && prev == '>') {
877 if (c == '&') { 896 continue;
878 if (j == '>' || j == '<') 897 }
879 command_mode = 0; 898 remove_chunk(int_buf, 0, i + 1 + (cur == int_buf[i + 1]));
880 } else if (c == '|' && j == '>') 899 i = -1; /* back to square 1 */
881 command_mode = 0;
882 }
883 if (command_mode) {
884 collapse_pos(0, i + command_mode);
885 i = -1; /* hack incremet */
886 } 900 }
887 } 901 }
888 /* collapse `command...` */ 902 /* Remove all `cmd` */
889 for (i = 0; int_buf[i]; i++) { 903 for (i = 0; int_buf[i]; i++) {
890 if (int_buf[i] == '`') { 904 if (int_buf[i] == '`') {
891 for (j = i + 1; int_buf[j]; j++) 905 for (j = i + 1; int_buf[j]; j++) {
892 if (int_buf[j] == '`') { 906 if (int_buf[j] == '`') {
893 collapse_pos(i, j + 1); 907 /* `cmd` should count as a word:
894 j = 0; 908 * `cmd` c<tab> should search for files c*,
895 break; 909 * not commands c*. Therefore we don't drop
910 * `cmd` entirely, we replace it with single `.
911 */
912 remove_chunk(int_buf, i, j);
913 goto next;
896 } 914 }
897 if (j) { 915 }
898 /* not found closing ` - command mode, collapse all previous */ 916 /* No closing ` - command mode, remove all up to ` */
899 collapse_pos(0, i + 1); 917 remove_chunk(int_buf, 0, i + 1);
900 break; 918 break;
901 } else 919 next: ;
902 i--; /* hack incremet */
903 } 920 }
904 } 921 }
905 922
906 /* collapse (command...(command...)...) or {command...{command...}...} */ 923 /* Remove "cmd (" and "cmd {"
907 c = 0; /* "recursive" level */ 924 * Example: "if { c<tab>"
908 c2 = 0; 925 * In this example, c should be matched as command pfx.
926 */
909 for (i = 0; int_buf[i]; i++) { 927 for (i = 0; int_buf[i]; i++) {
910 if (int_buf[i] == '(' || int_buf[i] == '{') { 928 if (int_buf[i] == '(' || int_buf[i] == '{') {
911 if (int_buf[i] == '(') 929 remove_chunk(int_buf, 0, i + 1);
912 c++; 930 i = -1; /* back to square 1 */
913 else
914 c2++;
915 collapse_pos(0, i + 1);
916 i = -1; /* hack incremet */
917 }
918 }
919 for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++) {
920 if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) {
921 if (int_buf[i] == ')')
922 c--;
923 else
924 c2--;
925 collapse_pos(0, i + 1);
926 i = -1; /* hack incremet */
927 } 931 }
928 } 932 }
929 933
930 /* skip first not quote space */ 934 /* Remove leading unquoted spaces */
931 for (i = 0; int_buf[i]; i++) 935 for (i = 0; int_buf[i]; i++)
932 if (int_buf[i] != ' ') 936 if (int_buf[i] != ' ')
933 break; 937 break;
934 if (i) 938 remove_chunk(int_buf, 0, i);
935 collapse_pos(0, i);
936 939
937 /* set find mode for completion */ 940 /* Determine completion mode */
938 command_mode = FIND_EXE_ONLY; 941 command_mode = FIND_EXE_ONLY;
939 for (i = 0; int_buf[i]; i++) { 942 for (i = 0; int_buf[i]; i++) {
940 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') { 943 if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') {
941 if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY 944 if (int_buf[i] == ' '
942 && matchBuf[pos_buf[0]] == 'c' 945 && command_mode == FIND_EXE_ONLY
943 && matchBuf[pos_buf[1]] == 'd' 946 && (char)int_buf[0] == 'c'
947 && (char)int_buf[1] == 'd'
948 && i == 2 /* -> int_buf[2] == ' ' */
944 ) { 949 ) {
945 command_mode = FIND_DIR_ONLY; 950 command_mode = FIND_DIR_ONLY;
946 } else { 951 } else {
@@ -949,44 +954,32 @@ static NOINLINE int find_match(char *matchBuf, int *len_with_quotes)
949 } 954 }
950 } 955 }
951 } 956 }
952 for (i = 0; int_buf[i]; i++) 957 if (dbg_bmp) printf("command_mode(0:exe/1:dir/2:file):%d\n", command_mode);
953 /* "strlen" */; 958
954 /* find last word */ 959 /* Remove everything except last word */
960 for (i = 0; int_buf[i]; i++) /* quasi-strlen(int_buf) */
961 continue;
955 for (--i; i >= 0; i--) { 962 for (--i; i >= 0; i--) {
956 c = int_buf[i]; 963 int cur = int_buf[i];
957 if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') { 964 if (cur == ' ' || cur == '<' || cur == '>' || cur == '|' || cur == '&') {
958 collapse_pos(0, i + 1); 965 remove_chunk(int_buf, 0, i + 1);
959 break; 966 break;
960 } 967 }
961 } 968 }
962 /* skip first not quoted '\'' or '"' */ 969
963 for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++) 970 /* Convert back to string of _chars_ */
964 /*skip*/; 971 i = 0;
965 /* collapse quote or unquote // or /~ */ 972 while ((match_buf[i] = int_buf[i]) != '\0')
966 while ((int_buf[i] & ~QUOT) == '/'
967 && ((int_buf[i+1] & ~QUOT) == '/' || (int_buf[i+1] & ~QUOT) == '~')
968 ) {
969 i++; 973 i++;
970 }
971 974
972 /* set only match and destroy quotes */ 975 if (dbg_bmp) printf("final match_buf:'%s'\n", match_buf);
973 j = 0;
974 for (c = 0; pos_buf[i] >= 0; i++) {
975 matchBuf[c++] = matchBuf[pos_buf[i]];
976 j = pos_buf[i] + 1;
977 }
978 matchBuf[c] = '\0';
979 /* old length matchBuf with quotes symbols */
980 *len_with_quotes = j ? j - pos_buf[0] : 0;
981 976
982 return command_mode; 977 return command_mode;
983} 978}
984#undef int_buf
985#undef pos_buf
986 979
987/* 980/*
988 * display by column (original idea from ls applet, 981 * Display by column (original idea from ls applet,
989 * very optimized by me :) 982 * very optimized by me [Vladimir] :)
990 */ 983 */
991static void showfiles(void) 984static void showfiles(void)
992{ 985{
@@ -1043,156 +1036,173 @@ static char *add_quote_for_spec_chars(char *found)
1043} 1036}
1044 1037
1045/* Do TAB completion */ 1038/* Do TAB completion */
1046static void input_tab(smallint *lastWasTab) 1039static NOINLINE void input_tab(smallint *lastWasTab)
1047{ 1040{
1041 char *chosen_match;
1042 char *match_buf;
1043 size_t len_found;
1044 /* Length of string used for matching */
1045 unsigned match_pfx_len = match_pfx_len;
1046 int find_type;
1047#if ENABLE_UNICODE_SUPPORT
1048 /* cursor pos in command converted to multibyte form */
1049 int cursor_mb;
1050#endif
1048 if (!(state->flags & TAB_COMPLETION)) 1051 if (!(state->flags & TAB_COMPLETION))
1049 return; 1052 return;
1050 1053
1051 if (!*lastWasTab) { 1054 if (*lastWasTab) {
1052 char *tmp, *tmp1; 1055 /* The last char was a TAB too.
1053 size_t len_found; 1056 * Print a list of all the available choices.
1054/* char matchBuf[MAX_LINELEN]; */ 1057 */
1055#define matchBuf (S.input_tab__matchBuf) 1058 if (num_matches > 0) {
1056 int find_type; 1059 /* cursor will be changed by goto_new_line() */
1057 int recalc_pos; 1060 int sav_cursor = cursor;
1058#if ENABLE_UNICODE_SUPPORT 1061 goto_new_line();
1059 /* cursor pos in command converted to multibyte form */ 1062 showfiles();
1060 int cursor_mb; 1063 redraw(0, command_len - sav_cursor);
1061#endif 1064 }
1065 return;
1066 }
1062 1067
1063 *lastWasTab = TRUE; /* flop trigger */ 1068 *lastWasTab = 1;
1069 chosen_match = NULL;
1064 1070
1065 /* Make a local copy of the string -- 1071 /* Make a local copy of the string up to the position of the cursor.
1066 * up to the position of the cursor */ 1072 * build_match_prefix will expand it into int16_t's, need to allocate
1067 save_string(matchBuf, cursor + 1); 1073 * twice as much as the string_len+1.
1068#if ENABLE_UNICODE_SUPPORT 1074 * (we then also (ab)use this extra space later - see (**))
1069 cursor_mb = strlen(matchBuf); 1075 */
1076 match_buf = xmalloc(MAX_LINELEN * sizeof(int16_t));
1077#if !ENABLE_UNICODE_SUPPORT
1078 save_string(match_buf, cursor + 1); /* +1 for NUL */
1079#else
1080 {
1081 CHAR_T wc = command_ps[cursor];
1082 command_ps[cursor] = BB_NUL;
1083 save_string(match_buf, MAX_LINELEN);
1084 command_ps[cursor] = wc;
1085 cursor_mb = strlen(match_buf);
1086 }
1070#endif 1087#endif
1071 tmp = matchBuf; 1088 find_type = build_match_prefix(match_buf);
1072 1089
1073 find_type = find_match(matchBuf, &recalc_pos); 1090 /* Free up any memory already allocated */
1074 1091 free_tab_completion_data();
1075 /* Free up any memory already allocated */
1076 free_tab_completion_data();
1077 1092
1078#if ENABLE_FEATURE_USERNAME_COMPLETION 1093#if ENABLE_FEATURE_USERNAME_COMPLETION
1079 /* If the word starts with `~' and there is no slash in the word, 1094 /* If the word starts with `~' and there is no slash in the word,
1080 * then try completing this word as a username. */ 1095 * then try completing this word as a username. */
1081 if (state->flags & USERNAME_COMPLETION) 1096 if (state->flags & USERNAME_COMPLETION)
1082 if (matchBuf[0] == '~' && strchr(matchBuf, '/') == NULL) 1097 if (match_buf[0] == '~' && strchr(match_buf, '/') == NULL)
1083 username_tab_completion(matchBuf, NULL); 1098 match_pfx_len = complete_username(match_buf);
1084#endif 1099#endif
1085 /* Try to match any executable in our path and everything 1100 /* Try to match a command in $PATH, or a directory, or a file */
1086 * in the current working directory */ 1101 if (!matches)
1087 if (!matches) 1102 match_pfx_len = complete_cmd_dir_file(match_buf, find_type);
1088 exe_n_cwd_tab_completion(matchBuf, find_type); 1103 /* Remove duplicates */
1089 /* Sort, then remove any duplicates found */ 1104 if (matches) {
1090 if (matches) { 1105 unsigned i;
1091 unsigned i; 1106 unsigned n = 0;
1092 int n = 0; 1107 qsort_string_vector(matches, num_matches);
1093 qsort_string_vector(matches, num_matches); 1108 for (i = 0; i < num_matches - 1; ++i) {
1094 for (i = 0; i < num_matches - 1; ++i) { 1109 //if (matches[i] && matches[i+1]) { /* paranoia */
1095 if (matches[i] && matches[i+1]) { /* paranoia */ 1110 if (strcmp(matches[i], matches[i+1]) == 0) {
1096 if (strcmp(matches[i], matches[i+1]) == 0) { 1111 free(matches[i]);
1097 free(matches[i]); 1112 //matches[i] = NULL; /* paranoia */
1098 matches[i] = NULL; /* paranoia */ 1113 } else {
1099 } else { 1114 matches[n++] = matches[i];
1100 matches[n++] = matches[i];
1101 }
1102 } 1115 }
1103 } 1116 //}
1104 matches[n] = matches[i];
1105 num_matches = n + 1;
1106 } 1117 }
1107 /* Did we find exactly one match? */ 1118 matches[n++] = matches[i];
1108 if (!matches || num_matches > 1) { /* no */ 1119 num_matches = n;
1109 beep(); 1120 }
1110 if (!matches) 1121 /* Did we find exactly one match? */
1111 return; /* not found */ 1122 if (num_matches != 1) { /* no */
1112 /* find minimal match */ 1123 char *cp;
1113 tmp1 = xstrdup(matches[0]); 1124 beep();
1114 for (tmp = tmp1; *tmp; tmp++) { 1125 if (!matches)
1115 for (len_found = 1; len_found < num_matches; len_found++) { 1126 goto ret; /* no matches at all */
1116 if (matches[len_found][tmp - tmp1] != *tmp) { 1127 /* Find common prefix */
1117 *tmp = '\0'; 1128 chosen_match = xstrdup(matches[0]);
1118 break; 1129 for (cp = chosen_match; *cp; cp++) {
1119 } 1130 unsigned n;
1131 for (n = 1; n < num_matches; n++) {
1132 if (matches[n][cp - chosen_match] != *cp) {
1133 goto stop;
1120 } 1134 }
1121 } 1135 }
1122 if (*tmp1 == '\0') { /* have unique */
1123 free(tmp1);
1124 return;
1125 }
1126 tmp = add_quote_for_spec_chars(tmp1);
1127 free(tmp1);
1128 } else { /* one match */
1129 tmp = add_quote_for_spec_chars(matches[0]);
1130 /* for next completion current found */
1131 *lastWasTab = FALSE;
1132
1133 len_found = strlen(tmp);
1134 if (tmp[len_found-1] != '/') {
1135 tmp[len_found] = ' ';
1136 tmp[len_found+1] = '\0';
1137 }
1138 } 1136 }
1137 stop:
1138 if (cp == chosen_match) { /* have unique prefix? */
1139 goto ret; /* no */
1140 }
1141 *cp = '\0';
1142 cp = add_quote_for_spec_chars(chosen_match);
1143 free(chosen_match);
1144 chosen_match = cp;
1145 len_found = strlen(chosen_match);
1146 } else { /* exactly one match */
1147 /* Next <tab> is not a double-tab */
1148 *lastWasTab = 0;
1149
1150 chosen_match = add_quote_for_spec_chars(matches[0]);
1151 len_found = strlen(chosen_match);
1152 if (chosen_match[len_found-1] != '/') {
1153 chosen_match[len_found] = ' ';
1154 chosen_match[++len_found] = '\0';
1155 }
1156 }
1139 1157
1140 len_found = strlen(tmp);
1141#if !ENABLE_UNICODE_SUPPORT 1158#if !ENABLE_UNICODE_SUPPORT
1142 /* have space to place the match? */ 1159 /* Have space to place the match? */
1143 /* The result consists of three parts with these lengths: */ 1160 /* The result consists of three parts with these lengths: */
1144 /* (cursor - recalc_pos) + len_found + (command_len - cursor) */ 1161 /* cursor + (len_found - match_pfx_len) + (command_len - cursor) */
1145 /* it simplifies into: */ 1162 /* it simplifies into: */
1146 if ((int)(len_found + command_len - recalc_pos) < S.maxsize) { 1163 if ((int)(len_found - match_pfx_len + command_len) < S.maxsize) {
1164 int pos;
1165 /* save tail */
1166 strcpy(match_buf, &command_ps[cursor]);
1167 /* add match and tail */
1168 sprintf(&command_ps[cursor], "%s%s", chosen_match + match_pfx_len, match_buf);
1169 command_len = strlen(command_ps);
1170 /* new pos */
1171 pos = cursor + len_found - match_pfx_len;
1172 /* write out the matched command */
1173 redraw(cmdedit_y, command_len - pos);
1174 }
1175#else
1176 {
1177 /* Use 2nd half of match_buf as scratch space - see (**) */
1178 char *command = match_buf + MAX_LINELEN;
1179 int len = save_string(command, MAX_LINELEN);
1180 /* Have space to place the match? */
1181 /* cursor_mb + (len_found - match_pfx_len) + (len - cursor_mb) */
1182 if ((int)(len_found - match_pfx_len + len) < MAX_LINELEN) {
1183 int pos;
1147 /* save tail */ 1184 /* save tail */
1148 strcpy(matchBuf, command_ps + cursor); 1185 strcpy(match_buf, &command[cursor_mb]);
1186 /* where do we want to have cursor after all? */
1187 strcpy(&command[cursor_mb], chosen_match + match_pfx_len);
1188 len = load_string(command, S.maxsize);
1149 /* add match and tail */ 1189 /* add match and tail */
1150 sprintf(&command_ps[cursor - recalc_pos], "%s%s", tmp, matchBuf); 1190 sprintf(&command[cursor_mb], "%s%s", chosen_match + match_pfx_len, match_buf);
1151 command_len = strlen(command_ps); 1191 command_len = load_string(command, S.maxsize);
1152 /* new pos */
1153 recalc_pos = cursor - recalc_pos + len_found;
1154 /* write out the matched command */ 1192 /* write out the matched command */
1155 redraw(cmdedit_y, command_len - recalc_pos); 1193 /* paranoia: load_string can return 0 on conv error,
1156 } 1194 * prevent passing pos = (0 - 12) to redraw */
1157#else 1195 pos = command_len - len;
1158 { 1196 redraw(cmdedit_y, pos >= 0 ? pos : 0);
1159 char command[MAX_LINELEN];
1160 int len = save_string(command, sizeof(command));
1161 /* have space to place the match? */
1162 /* (cursor_mb - recalc_pos) + len_found + (len - cursor_mb) */
1163 if ((int)(len_found + len - recalc_pos) < MAX_LINELEN) {
1164 /* save tail */
1165 strcpy(matchBuf, command + cursor_mb);
1166 /* where do we want to have cursor after all? */
1167 strcpy(&command[cursor_mb - recalc_pos], tmp);
1168 len = load_string(command, S.maxsize);
1169 /* add match and tail */
1170 sprintf(&command[cursor_mb - recalc_pos], "%s%s", tmp, matchBuf);
1171 command_len = load_string(command, S.maxsize);
1172 /* write out the matched command */
1173 redraw(cmdedit_y, command_len - len);
1174 }
1175 }
1176#endif
1177 free(tmp);
1178#undef matchBuf
1179 } else {
1180 /* Ok -- the last char was a TAB. Since they
1181 * just hit TAB again, print a list of all the
1182 * available choices... */
1183 if (matches && num_matches > 0) {
1184 /* changed by goto_new_line() */
1185 int sav_cursor = cursor;
1186
1187 /* Go to the next line */
1188 goto_new_line();
1189 showfiles();
1190 redraw(0, command_len - sav_cursor);
1191 } 1197 }
1192 } 1198 }
1199#endif
1200 ret:
1201 free(chosen_match);
1202 free(match_buf);
1193} 1203}
1194 1204
1195#endif /* FEATURE_COMMAND_TAB_COMPLETION */ 1205#endif /* FEATURE_TAB_COMPLETION */
1196 1206
1197 1207
1198line_input_t* FAST_FUNC new_line_input_t(int flags) 1208line_input_t* FAST_FUNC new_line_input_t(int flags)
@@ -1892,7 +1902,7 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li
1892{ 1902{
1893 int len; 1903 int len;
1894#if ENABLE_FEATURE_TAB_COMPLETION 1904#if ENABLE_FEATURE_TAB_COMPLETION
1895 smallint lastWasTab = FALSE; 1905 smallint lastWasTab = 0;
1896#endif 1906#endif
1897 smallint break_out = 0; 1907 smallint break_out = 0;
1898#if ENABLE_FEATURE_EDITING_VI 1908#if ENABLE_FEATURE_EDITING_VI
@@ -2169,7 +2179,7 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li
2169 2179
2170 ic = lineedit_read_key(read_key_buffer); 2180 ic = lineedit_read_key(read_key_buffer);
2171 if (errno) /* error */ 2181 if (errno) /* error */
2172 goto prepare_to_die; 2182 goto return_error_indicator;
2173 if (ic == ic_raw) { /* "cc", "dd" */ 2183 if (ic == ic_raw) { /* "cc", "dd" */
2174 input_backward(cursor); 2184 input_backward(cursor);
2175 goto clear_to_eol; 2185 goto clear_to_eol;
@@ -2233,7 +2243,7 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li
2233//FIXME: unicode case? 2243//FIXME: unicode case?
2234 ic = lineedit_read_key(read_key_buffer); 2244 ic = lineedit_read_key(read_key_buffer);
2235 if (errno) /* error */ 2245 if (errno) /* error */
2236 goto prepare_to_die; 2246 goto return_error_indicator;
2237 if (ic < ' ' || ic > 255) { 2247 if (ic < ' ' || ic > 255) {
2238 beep(); 2248 beep();
2239 } else { 2249 } else {
@@ -2305,9 +2315,9 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li
2305 * or exit if len=0 and no chars to delete */ 2315 * or exit if len=0 and no chars to delete */
2306 if (command_len == 0) { 2316 if (command_len == 0) {
2307 errno = 0; 2317 errno = 0;
2308#if ENABLE_FEATURE_EDITING_VI 2318
2309 prepare_to_die: 2319 case -1: /* error (e.g. EIO when tty is destroyed) */
2310#endif 2320 IF_FEATURE_EDITING_VI(return_error_indicator:)
2311 break_out = command_len = -1; 2321 break_out = command_len = -1;
2312 break; 2322 break;
2313 } 2323 }
@@ -2317,7 +2327,7 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li
2317// /* Control-V -- force insert of next char */ 2327// /* Control-V -- force insert of next char */
2318// if (c == CTRL('V')) { 2328// if (c == CTRL('V')) {
2319// if (safe_read(STDIN_FILENO, &c, 1) < 1) 2329// if (safe_read(STDIN_FILENO, &c, 1) < 1)
2320// goto prepare_to_die; 2330// goto return_error_indicator;
2321// if (c == 0) { 2331// if (c == 0) {
2322// beep(); 2332// beep();
2323// break; 2333// break;
@@ -2370,7 +2380,7 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li
2370 2380
2371#if ENABLE_FEATURE_TAB_COMPLETION 2381#if ENABLE_FEATURE_TAB_COMPLETION
2372 if (ic_raw != '\t') 2382 if (ic_raw != '\t')
2373 lastWasTab = FALSE; 2383 lastWasTab = 0;
2374#endif 2384#endif
2375 } /* while (1) */ 2385 } /* while (1) */
2376 2386
@@ -2464,7 +2474,7 @@ int main(int argc, char **argv)
2464 l = read_line_input(prompt, buff); 2474 l = read_line_input(prompt, buff);
2465 if (l <= 0 || buff[l-1] != '\n') 2475 if (l <= 0 || buff[l-1] != '\n')
2466 break; 2476 break;
2467 buff[l-1] = 0; 2477 buff[l-1] = '\0';
2468 printf("*** read_line_input() returned line =%s=\n", buff); 2478 printf("*** read_line_input() returned line =%s=\n", buff);
2469 } 2479 }
2470 printf("*** read_line_input() detect ^D\n"); 2480 printf("*** read_line_input() detect ^D\n");
diff --git a/libbb/unicode.c b/libbb/unicode.c
index c4b5f86ee..70c6abe00 100644
--- a/libbb/unicode.c
+++ b/libbb/unicode.c
@@ -131,7 +131,7 @@ size_t FAST_FUNC wcstombs(char *dest, const wchar_t *src, size_t n)
131 size_t len = wcrtomb_internal(tbuf, wc); 131 size_t len = wcrtomb_internal(tbuf, wc);
132 132
133 if (len > n) 133 if (len > n)
134 len = n; 134 break;
135 memcpy(dest, tbuf, len); 135 memcpy(dest, tbuf, len);
136 if (wc == L'\0') 136 if (wc == L'\0')
137 return org_n - n; 137 return org_n - n;