/** \file common.c Various functions, mostly string utilities, that are used by most parts of fish. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_NCURSES_H #include #else #include #endif #if HAVE_TERMIO_H #include #endif #include #include "config.h" #include "util.h" #include "wutil.h" #include "common.h" #include "expand.h" #include "proc.h" #include "wildcard.h" #include "parser.h" /** Error message to show on string convertion error */ #define STR2WCS_MSG "fish: Invalid multibyte sequence \'" /** The maximum number of minor errors to report. Further errors will be omitted. */ #define ERROR_MAX_COUNT 1 /** The number of microseconds to wait between polls when attempting to acquire a lockfile */ #define LOCKPOLLINTERVAL 10 struct termios shell_modes; /** Number of error encountered. This is reset after each command, and used to limit the number of error messages on commands with many string convertion problems. */ static int error_count=0; int error_max=1; wchar_t ellipsis_char; static int c1=0, c2=0, c3=0, c4=0, c5; char *profile=0; wchar_t *program_name; int debug_level=1; static int block_count=0; void common_destroy() { debug( 3, L"Calls: wcsdupcat %d, wcsdupcat2 %d, wcsndup %d, str2wcs %d, wcs2str %d", c1, c2, c3, c4, c5 ); } wchar_t **list_to_char_arr( array_list_t *l ) { wchar_t ** res = malloc( sizeof(wchar_t *)*(al_get_count( l )+1) ); int i; if( res == 0 ) { die_mem(); } for( i=0; i= *len ) { int new_len = maxi( 128, (*len)*2); buff = realloc( buff, sizeof(wchar_t)*new_len ); if( buff == 0 ) { die_mem(); } else { *len = new_len; *b = buff; } } errno=0; c = getwc( f ); if( errno == EILSEQ ) { getc( f ); continue; } //fwprintf( stderr, L"b\n" ); switch( c ) { /* End of line */ case WEOF: case L'\n': case L'\0': buff[i]=L'\0'; unblock(); return i; /* Ignore carriage returns */ case L'\r': break; default: buff[i++]=c; break; } } unblock(); } /** Wrapper for wcsfilecmp */ static int completion_cmp( const void *a, const void *b ) { wchar_t *c= *((wchar_t **)a); wchar_t *d= *((wchar_t **)b); return wcsfilecmp( c, d ); } void sort_list( array_list_t *comp ) { qsort( comp->arr, al_get_count( comp ), sizeof( void*), &completion_cmp ); } wchar_t *str2wcs( const char *in ) { c4++; wchar_t *res; res = malloc( sizeof(wchar_t)*(strlen(in)+1) ); if( !res ) { die_mem(); } if( (size_t)-1 == mbstowcs( res, in, sizeof(wchar_t)*(strlen(in)) +1) ) { error_count++; if( error_count <=error_max ) { fflush( stderr ); write( 2, STR2WCS_MSG, strlen(STR2WCS_MSG) ); write( 2, in, strlen(in )); write( 2, "\'\n", 2 ); } free(res); return 0; } return res; } void error_reset() { error_count=0; } char *wcs2str( const wchar_t *in ) { c5++; char *res = malloc( MAX_UTF8_BYTES*wcslen(in)+1 ); if( res == 0 ) { die_mem(); } wcstombs( res, in, MAX_UTF8_BYTES*wcslen(in)+1 ); res = realloc( res, strlen( res )+1 ); return res; } char **wcsv2strv( const wchar_t **in ) { int count =0; int i; while( in[count] != 0 ) count++; char **res = malloc( sizeof( char *)*(count+1)); if( res == 0 ) { die_mem(); } for( i=0; i= L'0') ) { res = d - L'0'; } else if( (d <= L'z') && (d >= L'a') ) { res = d + 10 - L'a'; } else if( (d <= L'Z') && (d >= L'A') ) { res = d + 10 - L'A'; } if( res >= base ) { res = -1; } return res; } long wcstol(const wchar_t *nptr, wchar_t **endptr, int base) { long long res=0; int is_set=0; if( base > 36 ) { errno = EINVAL; return 0; } while( 1 ) { long nxt = convert_digit( *nptr, base ); if( endptr != 0 ) *endptr = (wchar_t *)nptr; if( nxt < 0 ) { if( !is_set ) { errno = EINVAL; } return res; } res = (res*base)+nxt; is_set = 1; if( res > LONG_MAX ) { errno = ERANGE; return LONG_MAX; } if( res < LONG_MIN ) { errno = ERANGE; return LONG_MIN; } nptr++; } } /*$OpenBSD: strlcat.c,v 1.11 2003/06/17 21:56:24 millert Exp $*/ /* * Copyright (c) 1998 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /** Appends src to string dst of size siz (unlike wcsncat, siz is the full size of dst, not space left). At most siz-1 characters will be copied. Always NUL terminates (unless siz <= wcslen(dst)). Returns wcslen(src) + MIN(siz, wcslen(initial dst)). If retval >= siz, truncation occurred. This is the OpenBSD strlcat function, modified for wide characters, and renamed to reflect this change. */ size_t wcslcat(wchar_t *dst, const wchar_t *src, size_t siz) { register wchar_t *d = dst; register const wchar_t *s = src; register size_t n = siz; size_t dlen; /* Find the end of dst and adjust bytes left but don't go past end */ while (n-- != 0 && *d != '\0') d++; dlen = d - dst; n = siz - dlen; if (n == 0) return(dlen + wcslen(s)); while (*s != '\0') { if (n != 1) { *d++ = *s; n--; } s++; } *d = '\0'; return(dlen + (s - src)); /* count does not include NUL */ } /*$OpenBSD: strlcpy.c,v 1.8 2003/06/17 21:56:24 millert Exp $*/ /* * Copyright (c) 1998 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /** Copy src to string dst of size siz. At most siz-1 characters will be copied. Always NUL terminates (unless siz == 0). Returns wcslen(src); if retval >= siz, truncation occurred. This is the OpenBSD strlcpy function, modified for wide characters, and renamed to reflect this change. */ size_t wcslcpy(wchar_t *dst, const wchar_t *src, size_t siz) { register wchar_t *d = dst; register const wchar_t *s = src; register size_t n = siz; /* Copy as many bytes as will fit */ if (n != 0 && --n != 0) { do { if ((*d++ = *s++) == 0) break; } while (--n != 0); } /* Not enough room in dst, add NUL and traverse rest of src */ if (n == 0) { if (siz != 0) *d = '\0'; /* NUL-terminate dst */ while (*s++) ; } return(s - src - 1); /* count does not include NUL */ } wchar_t *wcsdup( const wchar_t *in ) { wchar_t *out = malloc( sizeof( wchar_t)*(wcslen(in)+1)); if( out == 0 ) { die_mem(); } wcscpy( out, in ); return out; } int wcscasecmp( const wchar_t *a, const wchar_t *b ) { if( *a == 0 ) { return (*b==0)?0:-1; } else if( *b == 0 ) { return 1; } int diff = towlower(*a)-towlower(*b); if( diff != 0 ) return diff; else return wcscasecmp( a+1,b+1); } int wcsvarname( wchar_t *str ) { while( *str ) { if( (!iswalnum(*str)) && (*str != L'_' ) ) { return 0; } str++; } return 1; } #if !HAVE_WCWIDTH /** Return the number of columns used by a character. In locales without a native wcwidth, Unicode is probably so broken that it isn't worth trying to implement a real wcwidth. This wcwidth assumes any printing character takes up one column. */ int wcwidth( wchar_t c ) { if( c < 32 ) return 0; if ( c == 127 ) return 0; return 1; } #endif /** The glibc version of wcswidth seems to hang on some strings. fish uses this replacement. */ int my_wcswidth( const wchar_t *c ) { int res=0; while( *c ) { int w = wcwidth( *c++ ); if( w < 0 ) w = 1; if( w > 2 ) w=1; res += w; } return res; } wchar_t *quote_end( const wchar_t *in ) { int level=1; int offset = (*in != L'\"'); in++; while(1) { /* fwprintf( stderr, L"Check %c\n", *tok->buff );*/ switch( *in ) { case L'\\': in++; if( *in == L'\0' ) { return 0; } break; case L'\"': case L'\'': if( (((level+offset) % 2)?L'\"':L'\'') == *in ) { level--; } else { level++; } break; } if( (*in == L'\0') ||(level==0)) break; in++; } return level?0:(wchar_t *)in; } void fish_setlocale(int category, const wchar_t *locale) { char *lang = wcs2str( locale ); setlocale(category,lang); free( lang ); /* Use ellipsis if on known unicode system, otherwise use $ */ if( wcslen( locale ) ) { ellipsis_char = wcsstr( locale, L".UTF")?L'\u2026':L'$'; } else { char *lang = getenv( "LANG" ); if( lang ) ellipsis_char = strstr( lang, ".UTF")?L'\u2026':L'$'; else ellipsis_char = L'$'; } } int contains_str( const wchar_t *a, ... ) { wchar_t *arg; va_list va; int res = 0; va_start( va, a ); while( (arg=va_arg(va, wchar_t *) )!= 0 ) { if( wcscmp( a,arg) == 0 ) { res=1; break; } } va_end( va ); return res; } int read_blocked(int fd, void *buf, size_t count) { int res; sigset_t chldset, oldset; sigemptyset( &chldset ); sigaddset( &chldset, SIGCHLD ); sigprocmask(SIG_BLOCK, &chldset, &oldset); res = read( fd, buf, count ); sigprocmask( SIG_SETMASK, &oldset, 0 ); return res; } int writeb( tputs_arg_t b ) { write( 1, &b, 1 ); // putc( b, stdout ); return 0; } void die_mem() { /* int *foo=0; *foo = 6; */ debug( 0, L"Out of memory, shutting down fish." ); exit(1); } void debug( int level, wchar_t *msg, ... ) { va_list va; if( level > debug_level ) return; va_start( va, msg ); fwprintf( stderr, L"%ls: ", program_name ); vfwprintf( stderr, msg, va ); va_end( va ); fwprintf( stderr, L"\n" ); } wchar_t *escape( wchar_t *in, int escape_all ) { wchar_t *killme=in; wchar_t *out = malloc( sizeof(wchar_t)*(wcslen(in)*4 + 1)); if( out != 0 ) { wchar_t *pos=out; while( *in != 0 ) { switch( *in ) { case L'\t': *(pos++) = L'\\'; *(pos++) = L't'; break; case L'\n': *(pos++) = L'\\'; *(pos++) = L'n'; break; case L'\b': *(pos++) = L'\\'; *(pos++) = L'b'; break; case L'\r': *(pos++) = L'\\'; *(pos++) = L'r'; break; case L'\e': *(pos++) = L'\\'; *(pos++) = L'e'; break; case L'\\': case L'&': case L'$': case L' ': case L'#': case L'^': case L'<': case L'>': case L'@': case L'(': case L')': case L'{': case L'}': case L'?': case L'*': case L'|': case L';': case L':': case L'\'': case L'\"': if( escape_all ) *pos++ = L'\\'; *pos++ = *in; break; default: if( *in < 32 ) { int tmp = (*in)%16; *pos++ = L'\\'; *pos++ = L'x'; *pos++ = ((*in>15)? L'1' : L'0'); *pos++ = tmp > 9? L'a'+(tmp-10):L'0'+tmp; } else *pos++ = *in; break; } in++; } *pos = 0; free(killme); } return out; } wchar_t *unescape( wchar_t * in, int escape_special ) { int in_pos, out_pos, len = wcslen( in ); int c; int bracket_count=0; wchar_t prev=0; for( in_pos=0, out_pos=0; in_pos 0 && in[out_pos-1]==ANY_STRING ) { out_pos--; in[out_pos] = ANY_STRING_RECURSIVE; } else in[out_pos]=ANY_STRING; } else in[out_pos]=in[in_pos]; break; case L'?': if( escape_special ) in[out_pos]=ANY_CHAR; else in[out_pos]=in[in_pos]; break; case L'$': if( escape_special ) in[out_pos]=VARIABLE_EXPAND; else in[out_pos]=in[in_pos]; break; case L'{': if( escape_special ) { bracket_count++; in[out_pos]=BRACKET_BEGIN; } else in[out_pos]=in[in_pos]; break; case L'}': if( escape_special ) { bracket_count--; in[out_pos]=BRACKET_END; } else in[out_pos]=in[in_pos]; break; case L',': if( escape_special && bracket_count && prev!=BRACKET_SEP) { in[out_pos]=BRACKET_SEP; } else in[out_pos]=in[in_pos]; break; case L'\'': case L'\"': { wchar_t *end = quote_end( &in[in_pos] ); int len; if( end == 0 ) { error( SYNTAX_ERROR, L"Unexpected end of string", -1 ); return in; } len = end- &in[in_pos]-1; if( escape_special) in[out_pos++]=INTERNAL_SEPARATOR; memmove( &in[out_pos], &in[in_pos+1], sizeof(wchar_t)*(len) ); in_pos += len+1; out_pos += len-1; if( escape_special) in[++out_pos]=INTERNAL_SEPARATOR; break; } default: in[out_pos] = in[in_pos]; break; } } } in[out_pos]=L'\0'; return in; } /** Convert a pid_t to a decimal string representation. The str parameter must contain sufficient space. The conservatively approximate maximum number of characters a pid_t will represent is given by: (int)(3.1 * sizeof(pid_t) + CHAR_BIT + 1) Returns the length of the string */ static int pidtostr( pid_t pid, char *str ) { int len, i = 0; int dig; /* Store digits in reverse order into string */ while( pid != 0 ) { dig = pid % 10; str[i] = '0' + dig; pid = ( pid - dig ) / 10; i++; } len = i; /* Reverse digits */ i /= 2; while( i ) { i--; dig = str[i]; str[i] = str[len - 1 - i]; str[len - 1 - i] = dig; } return len; } /** Create a copy of str adding this process's decimalised pid The memory returned should be freed using free() */ static char *dup_str_with_pid( const char *str ) { int pidlen, len = strlen( str ); /* allocate enough extra space for max size of pid when converted to a string; 3.1 ~= log(10)2 */ char *newstr = malloc( len + 1 + (int)(3.1 * sizeof(pid_t) * CHAR_BIT + 1) ); if ( newstr == NULL ) { return newstr; } memcpy( newstr, str, len ); pidlen = pidtostr( getpid(), newstr + len ); newstr[len + pidlen] = '\0'; return newstr; } /** Attempt to acquire a lock based on a lockfile, waiting LOCKPOLLINTERVAL nanoseconds between polls and timing out after timeout seconds, thereafter forcibly attempting to obtain the lock. Returns 1 on success, 0 on failure. A temporary file based on adding the pid to the lockfile name is used, so be aware that such a file will be deleted if it already exists. To remove the lock the lockfile must be unlinked. */ int acquire_lock_file( const char *lockfile, const int timeout, int force ) { int fd, timed_out = 0; int ret = 0; /* early exit returns failure */ struct timespec pollint; struct timeval start, end; double elapsed; struct stat statbuf; /* (Re)create a unique file and check that it has one only link. The check would be subject to a race if the filename were not unique. */ char *linkfile = dup_str_with_pid( lockfile ); if( linkfile == NULL ) { goto done; } (void)unlink( linkfile ); if( ( fd = open( linkfile, O_CREAT|O_RDONLY ) ) == -1 ) { goto done; } close( fd ); if( stat( linkfile, &statbuf ) != 0 || statbuf.st_nlink != 1 ) { goto done; } if( gettimeofday( &start, NULL ) != 0 ) { goto done; } end = start; pollint.tv_sec = 0; pollint.tv_nsec = LOCKPOLLINTERVAL * 1000000; do { /* Try to create a hard link to the unique file from the lockfile. This will only succeed if the lockfile does not already exist. It is guaranteed to provide race-free semantics over NFS which the alternative of calling open(O_EXCL|O_CREAT) on the lockfile is not. The lock succeeds if the call to link returns 0 or the link count on the unique file increases to 2. */ if( link( linkfile, lockfile ) == 0 || ( stat( linkfile, &statbuf ) == 0 && statbuf.st_nlink == 2 ) ) { /* Successful lock */ ret = 1; break; } elapsed = end.tv_sec + end.tv_usec/1000000.0 - ( start.tv_sec + start.tv_usec/1000000.0 ); /* The check for elapsed < 0 is to deal with the unlikely event that after the loop is entered the system time is set forward past the loop's end time. This would otherwise result in a (practically) infinite loop. */ if( timed_out || elapsed >= timeout || elapsed < 0 ) { if ( timed_out == 0 && force ) { /* Timed out and force was specified - attempt to remove stale lock and try a final time */ (void)unlink( lockfile ); timed_out = 1; continue; } else { /* Timed out and final try was unsuccessful or force was not specified */ break; } } nanosleep( &pollint, NULL ); } while( gettimeofday( &end, NULL ) == 0 ); done: /* The linkfile is not needed once the lockfile has been created */ (void)unlink( linkfile ); free( linkfile ); return ret; }