fish-shell/wutil.c

1027 lines
16 KiB
C

/** \file wutil.c
Wide character equivalents of various standard unix
functions. Also contains fallback implementations of a large number
of wide character unix functions.
*/
#include "config.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <wchar.h>
#include <wctype.h>
#include <string.h>
#include <dirent.h>
#include <stdarg.h>
#include <limits.h>
#include "util.h"
#include "common.h"
#include "wutil.h"
#define TMP_LEN_MIN 256
/**
Buffer for converting wide arguments to narrow arguments, used by
the \c wutil_wcs2str() function.
*/
static char *tmp=0;
/**
Length of the \c tmp buffer.
*/
static size_t tmp_len=0;
/**
Counts the number of calls to the wutil wrapper functions
*/
static int wutil_calls = 0;
void wutil_init()
{
}
void wutil_destroy()
{
free( tmp );
tmp=0;
tmp_len=0;
debug( 3, L"wutil functions called %d times", wutil_calls );
}
/**
Convert the specified wide aharacter string to a narrow character
string. This function uses an internal temporary buffer for storing
the result so subsequent results will overwrite previous results.
*/
static char *wutil_wcs2str( const wchar_t *in )
{
wutil_calls++;
size_t new_sz =MAX_UTF8_BYTES*wcslen(in)+1;
if( tmp_len < new_sz )
{
new_sz = maxi( new_sz, TMP_LEN_MIN );
tmp = realloc( tmp, new_sz );
if( !tmp )
{
die_mem();
}
tmp_len = new_sz;
}
wcstombs( tmp, in, tmp_len );
return tmp;
}
wchar_t *wgetcwd( wchar_t *buff, size_t sz )
{
char buffc[sz*MAX_UTF8_BYTES];
char *res = getcwd( buffc, sz*MAX_UTF8_BYTES );
if( !res )
return 0;
if( (size_t)-1 == mbstowcs( buff, buffc, sizeof( wchar_t ) * sz ) )
{
return 0;
}
return buff;
}
int wchdir( const wchar_t * dir )
{
char *tmp = wutil_wcs2str(dir);
return chdir( tmp );
}
FILE *wfopen(const wchar_t *path, const char *mode)
{
char *tmp =wutil_wcs2str(path);
FILE *res=0;
if( tmp )
{
res = fopen(tmp, mode);
}
return res;
}
FILE *wfreopen(const wchar_t *path, const char *mode, FILE *stream)
{
char *tmp =wutil_wcs2str(path);
FILE *res=0;
if( tmp )
{
res = freopen(tmp, mode, stream);
}
return res;
}
int wopen(const wchar_t *pathname, int flags, ...)
{
char *tmp =wutil_wcs2str(pathname);
int res=-1;
va_list argp;
if( tmp )
{
if( ! (flags & O_CREAT) )
{
res = open(tmp, flags);
}
else
{
va_start( argp, flags );
res = open(tmp, flags, va_arg(argp, int) );
va_end( argp );
}
}
return res;
}
int wcreat(const wchar_t *pathname, mode_t mode)
{
char *tmp =wutil_wcs2str(pathname);
int res = -1;
if( tmp )
{
res= creat(tmp, mode);
}
return res;
}
DIR *wopendir(const wchar_t *name)
{
char *tmp =wutil_wcs2str(name);
DIR *res = 0;
if( tmp )
{
res = opendir(tmp);
}
return res;
}
int wstat(const wchar_t *file_name, struct stat *buf)
{
char *tmp =wutil_wcs2str(file_name);
int res = -1;
if( tmp )
{
res = stat(tmp, buf);
}
return res;
}
int lwstat(const wchar_t *file_name, struct stat *buf)
{
char *tmp =wutil_wcs2str(file_name);
int res = -1;
if( tmp )
{
res = lstat(tmp, buf);
}
return res;
}
int waccess(const wchar_t *file_name, int mode)
{
char *tmp =wutil_wcs2str(file_name);
int res = -1;
if( tmp )
{
res= access(tmp, mode);
}
return res;
}
void wperror(const wchar_t *s)
{
if( s != 0 )
{
fwprintf( stderr, L"%ls: ", s );
}
fwprintf( stderr, L"%s\n", strerror( errno ) );
}
#if !HAVE_FWPRINTF
void pad( void (*writer)(wchar_t), int count)
{
int i;
if( count < 0 )
return;
for( i=0; i<count; i++ )
{
writer( L' ' );
}
}
/**
Generic formatting function. All other string formatting functions
are secretly a wrapper around this function. vgprintf does not
implement all the filters supported by printf, only those that are
currently used by fish. vgprintf internally uses snprintf to
implement the %f %d and %u filters.
Currently supported functionality:
- precision specification, both through .* and .N
- width specification through * and N
- long versions of all filters thorugh l and ll prefix
- Character outout using %c
- String output through %s
- Floating point number output through %f
- Integer output through %d or %i
- Unsigned integer output through %u
- Left padding using the - prefix
For a full description on the usage of *printf, see use 'man 3 printf'.
*/
static int vgwprintf( void (*writer)(wchar_t),
const wchar_t *filter,
va_list va )
{
const wchar_t *filter_org=filter;
int count=0;
for( ;*filter; filter++)
{
if(*filter == L'%')
{
int is_long=0;
int width = -1;
filter++;
int loop=1;
int precision=-1;
int pad_left = 1;
if( iswdigit( *filter ) )
{
width=0;
while( (*filter >= L'0') && (*filter <= L'9'))
{
width=10*width+(*filter++ - L'0');
}
}
while( loop )
{
switch(*filter)
{
case L'l':
/* Long variable */
is_long++;
filter++;
break;
case L'*':
/* Set minimum field width */
width = va_arg( va, int );
filter++;
break;
case L'-':
filter++;
pad_left=0;
break;
case L'.':
/*
Set precision.
*/
filter++;
if( *filter == L'*' )
{
precision = va_arg( va, int );
}
else
{
precision=0;
while( (*filter >= L'0') && (*filter <= L'9'))
{
precision=10*precision+(*filter++ - L'0');
}
}
break;
default:
loop=0;
break;
}
}
switch( *filter )
{
case L'c':
{
wchar_t c;
if( (width >= 0) && pad_left )
{
pad( writer, width-1 );
count += maxi( width-1, 0 );
}
c = is_long?va_arg(va, wchar_t):btowc(va_arg(va, int));
if( precision != 0 )
writer( c );
if( (width >= 0) && !pad_left )
{
pad( writer, width-1 );
count += maxi( width-1, 0 );
}
count++;
break;
}
case L's':
{
wchar_t *ss=0;
if( is_long )
{
ss = va_arg(va, wchar_t *);
}
else
{
char *ns = va_arg(va, char*);
if( ns )
{
ss = str2wcs( ns );
}
}
if( !ss )
{
return -1;
}
if( (width >= 0) && pad_left )
{
pad( writer, width-wcslen(ss) );
count += maxi(width-wcslen(ss), 0);
}
wchar_t *s=ss;
int precount = count;
while( *s )
{
if( (precision > 0) && (precision <= (count-precount) ) )
break;
writer( *(s++) );
count++;
}
if( (width >= 0) && !pad_left )
{
pad( writer, width-wcslen(ss) );
count += maxi( width-wcslen(ss), 0 );
}
if( !is_long )
free( ss );
break;
}
case L'd':
case L'i':
{
char str[32];
char *pos;
switch( is_long )
{
case 0:
{
int d = va_arg( va, int );
if( precision >= 0 )
snprintf( str, 32, "%.*d", precision, d );
else
snprintf( str, 32, "%d", d );
break;
}
case 1:
{
long d = va_arg( va, long );
if( precision >= 0 )
snprintf( str, 32, "%.*ld", precision, d );
else
snprintf( str, 32, "%ld", d );
break;
}
case 2:
{
long long d = va_arg( va, long long );
if( precision >= 0 )
snprintf( str, 32, "%.*lld", precision, d );
else
snprintf( str, 32, "%lld", d );
break;
}
default:
return -1;
}
if( (width >= 0) && pad_left )
{
pad( writer, width-strlen(str) );
count +=maxi(width-strlen(str), 0 );
}
pos = str;
while( *pos )
{
writer( *(pos++) );
count++;
}
if( (width >= 0) && !pad_left )
{
pad( writer, width-strlen(str) );
count += maxi(width-strlen(str), 0 );
}
break;
}
case L'u':
{
char str[32];
char *pos;
switch( is_long )
{
case 0:
{
unsigned d = va_arg( va, unsigned );
if( precision >= 0 )
snprintf( str, 32, "%.*u", precision, d );
else
snprintf( str, 32, "%u", d );
break;
}
case 1:
{
unsigned long d = va_arg( va, unsigned long );
if( precision >= 0 )
snprintf( str, 32, "%.*lu", precision, d );
else
snprintf( str, 32, "%lu", d );
break;
}
case 2:
{
unsigned long long d = va_arg( va, unsigned long long );
if( precision >= 0 )
snprintf( str, 32, "%.*llu", precision, d );
else
snprintf( str, 32, "%llu", d );
break;
}
default:
return -1;
}
if( (width >= 0) && pad_left )
{
pad( writer, width-strlen(str) );
count += maxi( width-strlen(str), 0 );
}
pos = str;
while( *pos )
{
writer( *(pos++) );
count++;
}
if( (width >= 0) && !pad_left )
{
pad( writer, width-strlen(str) );
count += maxi( width-strlen(str), 0 );
}
break;
}
case L'f':
{
char str[32];
char *pos;
double val = va_arg( va, double );
if( precision>= 0 )
{
if( width>= 0 )
{
snprintf( str, 32, "%*.*f", width, precision, val );
}
else
{
snprintf( str, 32, "%.*f", precision, val );
}
}
else
{
if( width>= 0 )
{
snprintf( str, 32, "%*f", width, val );
}
else
{
snprintf( str, 32, "%f", val );
}
}
pos = str;
while( *pos )
{
writer( *(pos++) );
count++;
}
break;
}
case L'n':
{
int *n = va_arg( va, int *);
*n = count;
break;
}
case L'%':
{
writer('%');
count++;
break;
}
default:
debug( 0, L"Unknown switch %lc in string %ls\n", *filter, filter_org );
// exit(1);
}
}
else
{
writer( *filter );
count++;
}
}
return count;
}
/**
Holds data for swprintf writer
*/
static struct
{
int count;
int max;
wchar_t *pos;
}
sw_data;
/**
Writers for string output
*/
static void sw_writer( wchar_t c )
{
if( sw_data.count < sw_data.max )
*(sw_data.pos++)=c;
sw_data.count++;
}
int vswprintf( wchar_t *out, size_t n, const wchar_t *filter, va_list va )
{
int written;
sw_data.pos=out;
sw_data.max=n;
sw_data.count=0;
written=vgwprintf( &sw_writer,
filter,
va );
if( written < n )
{
*sw_data.pos = 0;
}
else
{
written=-1;
}
return written;
}
int swprintf( wchar_t *out, size_t n, const wchar_t *filter, ... )
{
va_list va;
int written;
va_start( va, filter );
written = vswprintf( out, n, filter, va );
va_end( va );
return written;
}
/**
Holds auxiliary data for fwprintf and wprintf writer
*/
static FILE *fw_data;
static void fw_writer( wchar_t c )
{
putwc( c, fw_data );
}
/*
Writers for file output
*/
int vfwprintf( FILE *f, const wchar_t *filter, va_list va )
{
fw_data = f;
return vgwprintf( &fw_writer, filter, va );
}
int fwprintf( FILE *f, const wchar_t *filter, ... )
{
va_list va;
int written;
va_start( va, filter );
written = vfwprintf( f, filter, va );
va_end( va );
return written;
}
int vwprintf( const wchar_t *filter, va_list va )
{
return vfwprintf( stdout, filter, va );
}
int wprintf( const wchar_t *filter, ... )
{
va_list va;
int written;
va_start( va, filter );
written=vwprintf( filter, va );
va_end( va );
return written;
}
#endif
#ifndef HAVE_FGETWC
wint_t fgetwc(FILE *stream)
{
wchar_t res=0;
mbstate_t state;
memset (&state, '\0', sizeof (state));
while(1)
{
int b = fgetc( stream );
char bb;
int sz;
if( b == EOF )
return WEOF;
bb=b;
sz = mbrtowc( &res, &bb, 1, &state );
switch( sz )
{
case -1:
memset (&state, '\0', sizeof (state));
return WEOF;
case -2:
break;
case 0:
return 0;
default:
return res;
}
}
}
wint_t getwc(FILE *stream)
{
return fgetwc( stream );
}
#endif
#ifndef HAVE_FPUTWC
wint_t fputwc(wchar_t wc, FILE *stream)
{
int res;
char s[MB_CUR_MAX+1];
memset( s, 0, MB_CUR_MAX+1 );
wctomb( s, wc );
res = fputs( s, stream );
return res==EOF?WEOF:wc;
}
wint_t putwc(wchar_t wc, FILE *stream)
{
return fputwc( wc, stream );
}
#endif
#ifndef HAVE_WCSTOK
/*
Used by fallback wcstok. Borrowed from glibc
*/
static size_t fish_wcsspn (const wchar_t *wcs,
const wchar_t *accept )
{
register const wchar_t *p;
register const wchar_t *a;
register size_t count = 0;
for (p = wcs; *p != L'\0'; ++p)
{
for (a = accept; *a != L'\0'; ++a)
if (*p == *a)
break;
if (*a == L'\0')
return count;
else
++count;
}
return count;
}
/*
Used by fallback wcstok. Borrowed from glibc
*/
static wchar_t *fish_wcspbrk (const wchar_t *wcs, const wchar_t *accept)
{
while (*wcs != L'\0')
if (wcschr (accept, *wcs) == NULL)
++wcs;
else
return (wchar_t *) wcs;
return NULL;
}
/*
Fallback wcstok implementation. Borrowed from glibc.
*/
wchar_t *wcstok(wchar_t *wcs, const wchar_t *delim, wchar_t **save_ptr)
{
wchar_t *result;
if (wcs == NULL)
{
if (*save_ptr == NULL)
{
errno = EINVAL;
return NULL;
}
else
wcs = *save_ptr;
}
/* Scan leading delimiters. */
wcs += fish_wcsspn (wcs, delim);
if (*wcs == L'\0')
{
*save_ptr = NULL;
return NULL;
}
/* Find the end of the token. */
result = wcs;
wcs = fish_wcspbrk (result, delim);
if (wcs == NULL)
{
/* This token finishes the string. */
*save_ptr = NULL;
}
else
{
/* Terminate the token and make *SAVE_PTR point past it. */
*wcs = L'\0';
*save_ptr = wcs + 1;
}
return result;
}
#endif
#ifndef HAVE_WCSDUP
wchar_t *wcsdup( const wchar_t *in )
{
size_t len=wcslen(in);
wchar_t *out = malloc( sizeof( wchar_t)*(len+1));
if( out == 0 )
{
die_mem();
}
memcpy( out, in, sizeof( wchar_t)*(len+1));
return out;
}
#endif
#ifndef HAVE_WCSLEN
size_t wcslen(const wchar_t *in)
{
const wchar_t *end=in;
while( *end )
end++;
return end-in;
}
#endif
#ifndef HAVE_WCSCASECMP
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);
}
#endif
#ifndef HAVE_WCSNCASECMP
int wcsncasecmp( const wchar_t *a, const wchar_t *b, int count )
{
if( count == 0 )
return 0;
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 wcsncasecmp( a+1,b+1, count-1);
}
#endif
#ifndef HAVE_WCWIDTH
int wcwidth( wchar_t c )
{
if( c < 32 )
return 0;
if ( c == 127 )
return 0;
return 1;
}
#endif
#ifndef HAVE_WCSNDUP
wchar_t *wcsndup( const wchar_t *in, int c )
{
wchar_t *res = malloc( sizeof(wchar_t)*(c+1) );
if( res == 0 )
{
die_mem();
}
wcsncpy( res, in, c );
res[c] = L'\0';
return res;
}
#endif
long convert_digit( wchar_t d, int base )
{
long res=-1;
if( (d <= L'9') && (d >= 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;
}
#ifndef HAVE_WCSTOL
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++;
}
}
#endif