mirror of
https://https.git.savannah.gnu.org/git/bash.git
synced 2026-06-21 12:57:58 +02:00
1670 lines
35 KiB
Modula-2
1670 lines
35 KiB
Modula-2
This file is printf.def, from which is created printf.c.
|
|
It implements the builtin "printf" in Bash.
|
|
|
|
Copyright (C) 1997-2025 Free Software Foundation, Inc.
|
|
|
|
This file is part of GNU Bash, the Bourne Again SHell.
|
|
|
|
Bash is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Bash is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Bash. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
$PRODUCES printf.c
|
|
|
|
$BUILTIN printf
|
|
$FUNCTION printf_builtin
|
|
$SHORT_DOC printf [-v var] format [arguments]
|
|
Formats and prints ARGUMENTS under control of the FORMAT.
|
|
|
|
Options:
|
|
-v var assign the output to shell variable VAR rather than
|
|
display it on the standard output
|
|
|
|
FORMAT is a character string which contains three types of objects: plain
|
|
characters, which are simply copied to standard output; character escape
|
|
sequences, which are converted and copied to the standard output; and
|
|
format specifications, each of which causes printing of the next successive
|
|
argument.
|
|
|
|
In addition to the standard format characters csndiouxXeEfFgGaA described
|
|
in printf(3), printf interprets:
|
|
|
|
%b expand backslash escape sequences in the corresponding argument
|
|
%q quote the argument in a way that can be reused as shell input
|
|
%Q like %q, but apply any precision to the unquoted argument before
|
|
quoting
|
|
%(fmt)T output the date-time string resulting from using FMT as a format
|
|
string for strftime(3)
|
|
|
|
The format is re-used as necessary to consume all of the arguments. If
|
|
there are fewer arguments than the format requires, extra format
|
|
specifications behave as if a zero value or null string, as appropriate,
|
|
had been supplied.
|
|
|
|
Exit Status:
|
|
Returns success unless an invalid option is given or a write or assignment
|
|
error occurs.
|
|
$END
|
|
|
|
#include <config.h>
|
|
|
|
#include "../bashtypes.h"
|
|
|
|
#include <errno.h>
|
|
#if defined (HAVE_LIMITS_H)
|
|
# include <limits.h>
|
|
#else
|
|
/* Assume 32-bit ints. */
|
|
# define INT_MAX 2147483647
|
|
# define INT_MIN (-2147483647-1)
|
|
#endif
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <stdckdint.h>
|
|
#include <stdio.h>
|
|
#include <chartypes.h>
|
|
|
|
#ifdef HAVE_INTTYPES_H
|
|
# include <inttypes.h>
|
|
#endif
|
|
|
|
#include "posixtime.h"
|
|
#include "../bashansi.h"
|
|
#include "../bashintl.h"
|
|
|
|
#define NEED_STRFTIME_DECL
|
|
|
|
#include "../shell.h"
|
|
#include "shmbutil.h"
|
|
#include "stdc.h"
|
|
#include "bashgetopt.h"
|
|
#include "common.h"
|
|
|
|
#if !defined (errno)
|
|
extern int errno;
|
|
#endif
|
|
|
|
/* We free the buffer used by mklong() if it's `too big'. */
|
|
#define PRETURN(value) \
|
|
do \
|
|
{ \
|
|
QUIT; \
|
|
retval = value; \
|
|
if (conv_bufsize > 4096 ) \
|
|
{ \
|
|
free (conv_buf); \
|
|
conv_bufsize = 0; \
|
|
conv_buf = 0; \
|
|
} \
|
|
if (vflag) \
|
|
{ \
|
|
SHELL_VAR *v; \
|
|
v = builtin_bind_variable (vname, vbuf, bindflags); \
|
|
stupidly_hack_special_variables (vname); \
|
|
if (v == 0 || ASSIGN_DISALLOWED (v, 0)) \
|
|
retval = EXECUTION_FAILURE; \
|
|
if (vbsize > 4096) \
|
|
{ \
|
|
free (vbuf); \
|
|
vbsize = 0; \
|
|
vbuf = 0; \
|
|
} \
|
|
else if (vbuf) \
|
|
vbuf[0] = 0; \
|
|
} \
|
|
else \
|
|
{ \
|
|
if (ferror (stdout) == 0) \
|
|
fflush (stdout); \
|
|
QUIT; \
|
|
if (ferror (stdout)) \
|
|
{ \
|
|
sh_wrerror (); \
|
|
clearerr (stdout); \
|
|
retval = EXECUTION_FAILURE; \
|
|
} \
|
|
} \
|
|
return (retval); \
|
|
} \
|
|
while (0)
|
|
|
|
#define PC(c) \
|
|
do { \
|
|
char b[2]; \
|
|
tw++; \
|
|
b[0] = c; b[1] = '\0'; \
|
|
if (vflag) \
|
|
vbadd (b, 1); \
|
|
else \
|
|
putchar (c); \
|
|
QUIT; \
|
|
} while (0)
|
|
|
|
#define PF(f, func) \
|
|
do { \
|
|
int nw; \
|
|
if (vflag == 0) \
|
|
clearerr (stdout); \
|
|
errno = 0; \
|
|
if (have_fieldwidth && have_precision) \
|
|
nw = vflag ? vbprintf (f, fieldwidth, precision, func) : printf (f, fieldwidth, precision, func); \
|
|
else if (have_fieldwidth) \
|
|
nw = vflag ? vbprintf (f, fieldwidth, func) : printf (f, fieldwidth, func); \
|
|
else if (have_precision) \
|
|
nw = vflag ? vbprintf (f, precision, func) : printf (f, precision, func); \
|
|
else \
|
|
nw = vflag ? vbprintf (f, func) : printf (f, func); \
|
|
if (nw < 0 || (vflag == 0 && ferror (stdout))) \
|
|
{ \
|
|
QUIT; \
|
|
builtin_error ("%s", strerror (errno)); \
|
|
PRETURN (EXECUTION_FAILURE); \
|
|
} \
|
|
tw += nw; \
|
|
QUIT; \
|
|
} while (0)
|
|
|
|
#define SKIP1 "#'-+ 0"
|
|
#define LENMODS "hjlLtz"
|
|
|
|
#ifndef TIMELEN_MAX
|
|
# define TIMELEN_MAX 128
|
|
#endif
|
|
|
|
extern time_t shell_start_time;
|
|
|
|
#if !HAVE_ASPRINTF
|
|
extern int asprintf (char **, const char *, ...) __attribute__((__format__ (printf, 2, 3)));
|
|
#endif
|
|
|
|
#if !HAVE_VSNPRINTF
|
|
extern int vsnprintf (char *, size_t, const char *, va_list) __attribute__((__format__ (printf, 3, 0)));
|
|
#endif
|
|
|
|
static inline void printf_erange (char *);
|
|
static inline void report_erange (char *, char *);
|
|
static int printstr (char *, char *, size_t, int, int);
|
|
static int tescape (char *, char *, int *, int *);
|
|
static char *bexpand (char *, size_t, int *, size_t *);
|
|
static char *vbadd (char *, int);
|
|
static int vbprintf (const char *, ...) __attribute__((__format__ (printf, 1, 2)));
|
|
static char *mklong (char *, char *, size_t);
|
|
static int getchr (void);
|
|
static char *getstr (void);
|
|
static int getint (int);
|
|
static intmax_t getintmax (void);
|
|
static uintmax_t getuintmax (void);
|
|
|
|
#if defined (HAVE_LONG_DOUBLE) && HAVE_DECL_STRTOLD && !defined(STRTOLD_BROKEN)
|
|
typedef long double floatmax_t;
|
|
# define USE_LONG_DOUBLE 1
|
|
# define FLOATMAX_CONV "L"
|
|
# define FLOATMAX_CONVLEN 1
|
|
# define strtofltmax strtold
|
|
#else
|
|
typedef double floatmax_t;
|
|
# define USE_LONG_DOUBLE 0
|
|
# define FLOATMAX_CONV ""
|
|
# define FLOATMAX_CONVLEN 0
|
|
# define strtofltmax strtod
|
|
#endif
|
|
static double getdouble (void);
|
|
static floatmax_t getfloatmax (void);
|
|
|
|
static intmax_t asciicode (void);
|
|
|
|
#if defined (HANDLE_MULTIBYTE)
|
|
static wchar_t *getwidestr (size_t *);
|
|
static wint_t getwidechar (void);
|
|
static char *convwidestr (wchar_t *, int);
|
|
static char *convwidechar (wint_t, int);
|
|
static int printwidestr (char *, wchar_t *, size_t, int, int);
|
|
#endif
|
|
|
|
static WORD_LIST *garglist, *orig_arglist;
|
|
static int retval;
|
|
static int conversion_error;
|
|
|
|
/* printf -v var support */
|
|
static int vflag = 0;
|
|
static int bindflags = 0;
|
|
static char *vbuf, *vname;
|
|
static size_t vbsize;
|
|
static size_t vblen;
|
|
|
|
/* printf format numbered argument support */
|
|
static char **narg_argv;
|
|
static int narg_argc;
|
|
static int narg_maxind;
|
|
static int narg_curind;
|
|
|
|
static intmax_t tw;
|
|
|
|
static char *conv_buf;
|
|
static size_t conv_bufsize;
|
|
|
|
static inline int
|
|
decodeint (char **str, int diagnose, int overflow_return)
|
|
{
|
|
int pr, v;
|
|
char *ps;
|
|
|
|
ps = *str;
|
|
pr = *ps++ - '0';
|
|
v = 0;
|
|
|
|
/* use C23 macros to check overflow */
|
|
for (; DIGIT (*ps); ps++)
|
|
{
|
|
v |= ckd_mul (&pr, pr, 10);
|
|
v |= ckd_add (&pr, pr, *ps - '0');
|
|
}
|
|
if (v && diagnose)
|
|
report_erange (*str, ps);
|
|
|
|
*str = ps;
|
|
return (v ? overflow_return : pr);
|
|
}
|
|
|
|
int
|
|
printf_builtin (WORD_LIST *list)
|
|
{
|
|
int ch, fieldwidth, precision;
|
|
int have_fieldwidth, have_precision, use_Lmod, altform, longform;
|
|
char convch, thisch, nextch, *format, *modstart, *precstart, *fmt, *start;
|
|
#if defined (HANDLE_MULTIBYTE)
|
|
char mbch[25]; /* 25 > MB_LEN_MAX, plus can handle 4-byte UTF-8 and large Unicode characters*/
|
|
int mbind, mblen, mb_cur_max;
|
|
#endif
|
|
#if defined (ARRAY_VARS)
|
|
int arrayflags;
|
|
#endif
|
|
|
|
conversion_error = 0;
|
|
vflag = 0;
|
|
|
|
reset_internal_getopt ();
|
|
while ((ch = internal_getopt (list, "v:")) != -1)
|
|
{
|
|
switch (ch)
|
|
{
|
|
case 'v':
|
|
vname = list_optarg;
|
|
bindflags = 0;
|
|
#if defined (ARRAY_VARS)
|
|
SET_VFLAGS (list_optflags, arrayflags, bindflags);
|
|
retval = valid_identifier (vname) || valid_array_reference (vname, arrayflags);
|
|
#else
|
|
retval = valid_identifier (vname);
|
|
#endif
|
|
if (retval)
|
|
{
|
|
vflag = 1;
|
|
if (vbsize == 0)
|
|
vbuf = xmalloc (vbsize = 16);
|
|
vblen = 0;
|
|
if (vbuf)
|
|
vbuf[0] = 0;
|
|
}
|
|
else
|
|
{
|
|
sh_invalidid (vname);
|
|
return (EX_USAGE);
|
|
}
|
|
break;
|
|
CASE_HELPOPT;
|
|
default:
|
|
builtin_usage ();
|
|
return (EX_USAGE);
|
|
}
|
|
}
|
|
list = loptend; /* skip over possible `--' */
|
|
|
|
if (list == 0)
|
|
{
|
|
builtin_usage ();
|
|
return (EX_USAGE);
|
|
}
|
|
|
|
/* Allow printf -v var "" to act like var="" */
|
|
if (vflag && list->word->word && list->word->word[0] == '\0')
|
|
{
|
|
SHELL_VAR *v;
|
|
v = builtin_bind_variable (vname, "", 0);
|
|
stupidly_hack_special_variables (vname);
|
|
return ((v == 0 || ASSIGN_DISALLOWED (v, 0)) ? EXECUTION_FAILURE : EXECUTION_SUCCESS);
|
|
}
|
|
|
|
/* If the format string is empty after preprocessing, return immediately. */
|
|
if (list->word->word == 0 || list->word->word[0] == '\0')
|
|
return (EXECUTION_SUCCESS);
|
|
|
|
format = list->word->word;
|
|
tw = 0;
|
|
retval = EXECUTION_SUCCESS;
|
|
|
|
garglist = orig_arglist = list->next;
|
|
|
|
mb_cur_max = MB_CUR_MAX;
|
|
|
|
/* Basic algorithm is to scan the format string for conversion
|
|
specifications -- once one is found, find out if the field
|
|
width or precision is a '*'; if it is, gather up value. Note,
|
|
format strings are reused as necessary to use up the provided
|
|
arguments, arguments of zero/null string are provided to use
|
|
up the format string. */
|
|
do
|
|
{
|
|
tw = 0;
|
|
/* find next format specification */
|
|
for (fmt = format; *fmt; fmt++)
|
|
{
|
|
precision = fieldwidth = 0;
|
|
have_fieldwidth = have_precision = altform = longform = 0;
|
|
precstart = 0;
|
|
|
|
if (*fmt == '\\')
|
|
{
|
|
fmt++;
|
|
/* A NULL third argument to tescape means to bypass the
|
|
special processing for arguments to %b. */
|
|
#if defined (HANDLE_MULTIBYTE)
|
|
/* Accommodate possible use of \u or \U, which can result in
|
|
multibyte characters */
|
|
memset (mbch, '\0', sizeof (mbch));
|
|
fmt += tescape (fmt, mbch, &mblen, (int *)NULL);
|
|
for (mbind = 0; mbind < mblen; mbind++)
|
|
PC (mbch[mbind]);
|
|
#else
|
|
fmt += tescape (fmt, &nextch, (int *)NULL, (int *)NULL);
|
|
PC (nextch);
|
|
#endif
|
|
fmt--; /* for loop will increment it for us again */
|
|
continue;
|
|
}
|
|
|
|
if (*fmt != '%')
|
|
{
|
|
#if defined (HANDLE_MULTIBYTE)
|
|
size_t l;
|
|
int i;
|
|
l = mbcharlen (fmt, mb_cur_max);
|
|
for (i = 0; i < l; i++, fmt++)
|
|
PC (*fmt);
|
|
fmt--; /* for loop will increment it for us again */
|
|
#else
|
|
PC (*fmt);
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
/* ASSERT(*fmt == '%') */
|
|
start = fmt++;
|
|
|
|
if (*fmt == '%') /* %% prints a % */
|
|
{
|
|
PC ('%');
|
|
continue;
|
|
}
|
|
|
|
/* Found format specification, skip to field width. We check for
|
|
alternate form for possible later use. */
|
|
for (; *fmt && strchr(SKIP1, *fmt); ++fmt)
|
|
if (*fmt == '#')
|
|
altform++;
|
|
|
|
/* Skip optional field width. */
|
|
if (*fmt == '*')
|
|
{
|
|
fmt++;
|
|
have_fieldwidth = 1;
|
|
/* Handle field with overflow by ignoring fieldwidth for now.
|
|
getint() prints a message. */
|
|
fieldwidth = getint (0);
|
|
}
|
|
else
|
|
while (DIGIT (*fmt))
|
|
fmt++;
|
|
|
|
/* Skip optional '.' and precision */
|
|
if (*fmt == '.')
|
|
{
|
|
++fmt;
|
|
if (*fmt == '*')
|
|
{
|
|
fmt++;
|
|
have_precision = 1;
|
|
/* Handle precision overflow by ignoring precision for now.
|
|
getint() prints a message.
|
|
"A negative precision is treated as if it were missing." */
|
|
precision = getint (-1);
|
|
}
|
|
else
|
|
{
|
|
/* Negative precisions are allowed but treated as if the
|
|
precision were missing; I would like to allow a leading
|
|
`+' in the precision number as an extension, but lots
|
|
of asprintf/fprintf implementations get this wrong. */
|
|
#if 0
|
|
if (*fmt == '-' || *fmt == '+')
|
|
#else
|
|
if (*fmt == '-')
|
|
#endif
|
|
fmt++;
|
|
if (DIGIT (*fmt))
|
|
precstart = fmt;
|
|
while (DIGIT (*fmt))
|
|
fmt++;
|
|
}
|
|
}
|
|
|
|
/* skip possible format modifiers */
|
|
modstart = fmt;
|
|
use_Lmod = 0;
|
|
while (*fmt && strchr (LENMODS, *fmt))
|
|
{
|
|
use_Lmod |= USE_LONG_DOUBLE && *fmt == 'L';
|
|
longform |= *fmt == 'l';
|
|
fmt++;
|
|
}
|
|
|
|
if (*fmt == 0)
|
|
{
|
|
builtin_error (_("`%s': missing format character"), start);
|
|
PRETURN (EXECUTION_FAILURE);
|
|
}
|
|
|
|
convch = *fmt;
|
|
thisch = modstart[0];
|
|
nextch = modstart[1];
|
|
modstart[0] = convch;
|
|
modstart[1] = '\0';
|
|
|
|
QUIT;
|
|
switch(convch)
|
|
{
|
|
case 'c':
|
|
case 'C':
|
|
{
|
|
char p;
|
|
|
|
#if defined (HANDLE_MULTIBYTE)
|
|
if ((longform || convch == 'C') && locale_mb_cur_max > 1)
|
|
{
|
|
wchar_t wc, ws[2];
|
|
int r;
|
|
|
|
wc = getwidechar ();
|
|
ws[0] = wc;
|
|
ws[1] = L'\0';
|
|
|
|
/* If %lc is supplied a null argument, posix interp 1647
|
|
says it should produce a single null byte. */
|
|
if (wc == L'\0')
|
|
r = printstr (start, "", 1, fieldwidth, precision);
|
|
else
|
|
r = printwidestr (start, ws, 1, fieldwidth, precision);
|
|
if (r < 0)
|
|
PRETURN (EXECUTION_FAILURE);
|
|
break;
|
|
}
|
|
#endif
|
|
p = getchr ();
|
|
PF(start, p);
|
|
break;
|
|
}
|
|
|
|
case 's':
|
|
case 'S':
|
|
{
|
|
char *p;
|
|
#if defined (HANDLE_MULTIBYTE)
|
|
if ((longform || convch == 'S') && locale_mb_cur_max > 1)
|
|
{
|
|
wchar_t *wp;
|
|
size_t slen;
|
|
int r;
|
|
|
|
wp = getwidestr (&slen);
|
|
r = printwidestr (start, wp, slen, fieldwidth, precision);
|
|
FREE (wp);
|
|
if (r < 0)
|
|
PRETURN (EXECUTION_FAILURE);
|
|
break;
|
|
}
|
|
#endif
|
|
#if 0 /*TAG:bash-5.4*/
|
|
/* If altform, treat like %b */
|
|
if (altform == 0)
|
|
#endif
|
|
{
|
|
p = getstr ();
|
|
PF(start, p);
|
|
break;
|
|
}
|
|
}
|
|
/*FALLTHROUGH*/
|
|
|
|
case 'b': /* expand escapes in argument */
|
|
{
|
|
char *p, *xp;
|
|
size_t rlen;
|
|
int r;
|
|
|
|
p = getstr ();
|
|
ch = r = 0;
|
|
rlen = 0;
|
|
xp = bexpand (p, strlen (p), &ch, &rlen);
|
|
|
|
if (xp)
|
|
{
|
|
/* Have to use printstr because of possible NUL bytes
|
|
in XP -- printf does not handle that well. */
|
|
r = printstr (start, xp, rlen, fieldwidth, precision);
|
|
if (r < 0)
|
|
retval = EXECUTION_FAILURE;
|
|
free (xp);
|
|
}
|
|
|
|
if (ch || r < 0)
|
|
PRETURN (retval);
|
|
break;
|
|
}
|
|
|
|
case '(':
|
|
{
|
|
char *timefmt, timebuf[TIMELEN_MAX], *t;
|
|
size_t n;
|
|
int r;
|
|
intmax_t arg;
|
|
time_t secs;
|
|
struct tm *tm;
|
|
|
|
modstart[1] = nextch; /* restore char after left paren */
|
|
timefmt = xmalloc (strlen (fmt) + 3);
|
|
fmt++; /* skip over left paren */
|
|
for (t = timefmt, n = 1; *fmt; )
|
|
{
|
|
if (*fmt == '(')
|
|
n++;
|
|
else if (*fmt == ')')
|
|
n--;
|
|
if (n == 0)
|
|
break;
|
|
*t++ = *fmt++;
|
|
}
|
|
*t = '\0'; /*(*/
|
|
if (*fmt != ')' || *++fmt != 'T')
|
|
{
|
|
builtin_warning (_("`%c': invalid time format specification"), *fmt);
|
|
fmt = start;
|
|
free (timefmt);
|
|
PC (*fmt);
|
|
continue;
|
|
}
|
|
if (timefmt[0] == '\0')
|
|
{
|
|
timefmt[0] = '%';
|
|
timefmt[1] = 'X'; /* locale-specific current time - should we use `+'? */
|
|
timefmt[2] = '\0';
|
|
}
|
|
/* argument is seconds since the epoch with special -1 and -2 */
|
|
/* default argument is equivalent to -1; special case */
|
|
arg = garglist ? getintmax () : -1;
|
|
if (arg == -1)
|
|
secs = NOW; /* roughly date +%s */
|
|
else if (arg == -2)
|
|
secs = shell_start_time; /* roughly $SECONDS */
|
|
else
|
|
secs = arg;
|
|
#if defined (HAVE_TZSET)
|
|
sv_tz ("TZ"); /* XXX -- just make sure */
|
|
#endif
|
|
tm = localtime (&secs);
|
|
if (tm == 0)
|
|
{
|
|
secs = 0;
|
|
tm = localtime (&secs);
|
|
}
|
|
n = tm ? strftime (timebuf, sizeof (timebuf), timefmt, tm) : 0;
|
|
free (timefmt);
|
|
if (n == 0)
|
|
timebuf[0] = '\0';
|
|
else
|
|
timebuf[sizeof(timebuf) - 1] = '\0';
|
|
/* convert to %s format that preserves fieldwidth and precision */
|
|
modstart[0] = 's';
|
|
modstart[1] = '\0';
|
|
r = printstr (start, timebuf, strlen (timebuf), fieldwidth, precision); /* XXX - %s for now */
|
|
if (r < 0)
|
|
PRETURN (EXECUTION_FAILURE);
|
|
break;
|
|
}
|
|
|
|
case 'n':
|
|
{
|
|
char *var;
|
|
|
|
var = getstr ();
|
|
if (var && *var)
|
|
{
|
|
if (valid_identifier (var))
|
|
bind_var_to_int (var, tw, 0);
|
|
else
|
|
{
|
|
sh_invalidid (var);
|
|
PRETURN (EXECUTION_FAILURE);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'q': /* print with shell quoting */
|
|
case 'Q':
|
|
{
|
|
char *p, *xp;
|
|
int r;
|
|
size_t slen;
|
|
|
|
r = 0;
|
|
p = getstr ();
|
|
/* Decode precision and apply it to the unquoted string. */
|
|
if (convch == 'Q' && (have_precision || precstart))
|
|
{
|
|
if (precstart)
|
|
{
|
|
char *prec;
|
|
prec = precstart;
|
|
precision = decodeint (&prec, 0, -1);
|
|
}
|
|
slen = strlen (p);
|
|
/* printf precision works in bytes. */
|
|
if (precision >= 0 && precision < slen)
|
|
p[precision] = '\0';
|
|
}
|
|
if (p && *p == 0) /* XXX - getstr never returns null */
|
|
xp = savestring ("''");
|
|
else if (ansic_shouldquote (p))
|
|
xp = ansic_quote (p, 0, (int *)0);
|
|
else if (altform)
|
|
xp = sh_single_quote (p);
|
|
else
|
|
xp = sh_backslash_quote (p, 0, 3);
|
|
if (xp)
|
|
{
|
|
slen = strlen (xp);
|
|
if (convch == 'Q')
|
|
{
|
|
/* check for string length overflow when adjusting precision */
|
|
if (ckd_add (&precision, slen, 0))
|
|
{
|
|
builtin_error ("%%Q: %s %s", _("string length"), strerror (ERANGE));
|
|
precision = -1;
|
|
}
|
|
}
|
|
/* Use printstr to get fieldwidth and precision right. */
|
|
r = printstr (start, xp, slen, fieldwidth, precision);
|
|
/* Let PRETURN print the error message. */
|
|
free (xp);
|
|
}
|
|
|
|
if (r < 0)
|
|
PRETURN (EXECUTION_FAILURE);
|
|
break;
|
|
}
|
|
|
|
case 'd':
|
|
case 'i':
|
|
{
|
|
char *f;
|
|
long p;
|
|
intmax_t pp;
|
|
|
|
pp = getintmax ();
|
|
if (pp < LONG_MIN || pp > LONG_MAX)
|
|
{
|
|
f = mklong (start, PRIdMAX, sizeof (PRIdMAX) - 2);
|
|
PF (f, pp);
|
|
}
|
|
else
|
|
{
|
|
/* Optimize the common case where the integer fits
|
|
in "long". This also works around some long
|
|
long and/or intmax_t library bugs in the common
|
|
case, e.g. glibc 2.2 x86. */
|
|
p = pp;
|
|
f = mklong (start, "l", 1);
|
|
PF (f, p);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'o':
|
|
case 'u':
|
|
case 'x':
|
|
case 'X':
|
|
{
|
|
char *f;
|
|
unsigned long p;
|
|
uintmax_t pp;
|
|
|
|
p = pp = getuintmax ();
|
|
if (p != pp)
|
|
{
|
|
f = mklong (start, PRIdMAX, sizeof (PRIdMAX) - 2);
|
|
PF (f, pp);
|
|
}
|
|
else
|
|
{
|
|
f = mklong (start, "l", 1);
|
|
PF (f, p);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'e':
|
|
case 'E':
|
|
case 'f':
|
|
case 'F':
|
|
case 'g':
|
|
case 'G':
|
|
#if defined (HAVE_PRINTF_A_FORMAT)
|
|
case 'a':
|
|
case 'A':
|
|
#endif
|
|
{
|
|
char *f;
|
|
|
|
if (use_Lmod || posixly_correct == 0)
|
|
{
|
|
floatmax_t p;
|
|
|
|
p = getfloatmax ();
|
|
f = mklong (start, FLOATMAX_CONV, FLOATMAX_CONVLEN);
|
|
PF (f, p);
|
|
}
|
|
else /* posixly_correct */
|
|
{
|
|
double p;
|
|
|
|
p = getdouble ();
|
|
f = mklong (start, "", 0);
|
|
PF (f, p);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* We don't output unrecognized format characters; we print an
|
|
error message and return a failure exit status. */
|
|
default:
|
|
builtin_error (_("`%c': invalid format character"), convch);
|
|
PRETURN (EXECUTION_FAILURE);
|
|
}
|
|
|
|
modstart[0] = thisch;
|
|
modstart[1] = nextch;
|
|
}
|
|
|
|
if (vflag == 0 && ferror (stdout))
|
|
{
|
|
/* PRETURN will print error message. */
|
|
PRETURN (EXECUTION_FAILURE);
|
|
}
|
|
}
|
|
while (garglist && garglist != list->next);
|
|
|
|
if (conversion_error)
|
|
retval = EXECUTION_FAILURE;
|
|
|
|
PRETURN (retval);
|
|
}
|
|
|
|
static inline void
|
|
printf_erange (char *s)
|
|
{
|
|
builtin_error ("%s: %s", s, strerror(ERANGE));
|
|
conversion_error = 1;
|
|
}
|
|
|
|
static inline void
|
|
report_erange (char *s, char *e)
|
|
{
|
|
unsigned char sc;
|
|
|
|
sc = *e;
|
|
*e = 0;
|
|
printf_erange (s);
|
|
*e = sc;
|
|
}
|
|
|
|
/* We duplicate a lot of what printf(3) does here. */
|
|
/* FMT: format
|
|
STRING: expanded string argument
|
|
LEN: length of expanded string
|
|
FIELDWIDTH: argument for width of `*'
|
|
PRECISION: argument for precision of `*'
|
|
|
|
Returns -1 on detectable write error, 0 otherwise. */
|
|
|
|
static int
|
|
printstr (char *fmt, char *string, size_t len, int fieldwidth, int precision)
|
|
{
|
|
#if 0
|
|
char *s;
|
|
#endif
|
|
int padlen, nc, ljust, i;
|
|
int fw, pr; /* fieldwidth and precision */
|
|
|
|
if (string == 0)
|
|
string = "";
|
|
|
|
#if 0
|
|
s = fmt;
|
|
#endif
|
|
if (*fmt == '%')
|
|
fmt++;
|
|
|
|
ljust = fw = 0;
|
|
pr = -1;
|
|
|
|
/* skip flags */
|
|
while (strchr (SKIP1, *fmt))
|
|
{
|
|
if (*fmt == '-')
|
|
ljust = 1;
|
|
fmt++;
|
|
}
|
|
|
|
/* get fieldwidth, if present. rely on caller to clamp fieldwidth at INT_MAX */
|
|
if (*fmt == '*')
|
|
{
|
|
fmt++;
|
|
fw = fieldwidth;
|
|
if (fw < 0)
|
|
{
|
|
fw = -fw;
|
|
ljust = 1;
|
|
}
|
|
}
|
|
else if (DIGIT (*fmt))
|
|
fw = decodeint (&fmt, 1, 0);
|
|
|
|
/* get precision, if present. doesn't handle negative precisions */
|
|
if (*fmt == '.')
|
|
{
|
|
fmt++;
|
|
if (*fmt == '*')
|
|
{
|
|
fmt++;
|
|
pr = precision;
|
|
}
|
|
else if (DIGIT (*fmt))
|
|
{
|
|
pr = decodeint (&fmt, 1, -1);
|
|
/* pr < precision means we adjusted precision in printf_builtin
|
|
for the quoted string length (%Q), so we use the adjusted value */
|
|
if (pr < precision)
|
|
pr = precision;
|
|
}
|
|
else
|
|
pr = 0; /* "a null digit string is treated as zero" */
|
|
}
|
|
|
|
#if 0
|
|
/* If we remove this, get rid of `s'. */
|
|
if (*fmt != 'b' && *fmt != 'q')
|
|
{
|
|
internal_error (_("format parsing problem: %s"), s);
|
|
fw = pr = 0;
|
|
}
|
|
#endif
|
|
|
|
/* chars from string to print */
|
|
nc = (pr >= 0 && pr <= len) ? pr : len;
|
|
|
|
padlen = fw - nc;
|
|
if (padlen < 0)
|
|
padlen = 0;
|
|
if (ljust)
|
|
padlen = -padlen;
|
|
|
|
/* leading pad characters */
|
|
for (; padlen > 0; padlen--)
|
|
PC (' ');
|
|
|
|
/* output NC characters from STRING */
|
|
for (i = 0; i < nc; i++)
|
|
PC (string[i]);
|
|
|
|
/* output any necessary trailing padding */
|
|
for (; padlen < 0; padlen++)
|
|
PC (' ');
|
|
|
|
return ((vflag == 0 && ferror (stdout)) ? -1 : 0);
|
|
}
|
|
|
|
#if defined (HANDLE_MULTIBYTE)
|
|
/* A wide-character version of printstr */
|
|
static int
|
|
printwidestr (char *fmt, wchar_t *wstring, size_t len, int fieldwidth, int precision)
|
|
{
|
|
char *s;
|
|
char *string;
|
|
int padlen, nc, ljust, i;
|
|
int fw, pr; /* fieldwidth and precision */
|
|
|
|
if (wstring == 0)
|
|
wstring = L"";
|
|
|
|
#if 0
|
|
s = fmt;
|
|
#endif
|
|
if (*fmt == '%')
|
|
fmt++;
|
|
|
|
ljust = fw = 0;
|
|
pr = -1;
|
|
|
|
/* skip flags */
|
|
while (strchr (SKIP1, *fmt))
|
|
{
|
|
if (*fmt == '-')
|
|
ljust = 1;
|
|
fmt++;
|
|
}
|
|
|
|
/* get fieldwidth, if present. rely on caller to clamp fieldwidth at INT_MAX */
|
|
if (*fmt == '*')
|
|
{
|
|
fmt++;
|
|
fw = fieldwidth;
|
|
if (fw < 0)
|
|
{
|
|
fw = -fw;
|
|
ljust = 1;
|
|
}
|
|
}
|
|
else if (DIGIT (*fmt))
|
|
fw = decodeint (&fmt, 1, 0);
|
|
|
|
/* get precision, if present. doesn't handle negative precisions */
|
|
if (*fmt == '.')
|
|
{
|
|
fmt++;
|
|
if (*fmt == '*')
|
|
{
|
|
fmt++;
|
|
pr = precision;
|
|
}
|
|
else if (DIGIT (*fmt))
|
|
{
|
|
pr = decodeint (&fmt, 1, -1);
|
|
/* pr < precision means we adjusted precision in printf_builtin
|
|
for the quoted string length (%Q), so we use the adjusted value */
|
|
if (pr < precision)
|
|
pr = precision;
|
|
}
|
|
else
|
|
pr = 0; /* "a null digit string is treated as zero" */
|
|
}
|
|
|
|
/* chars from wide string to print */
|
|
nc = (pr >= 0 && pr <= len) ? pr : len;
|
|
|
|
padlen = fw - nc;
|
|
if (padlen < 0)
|
|
padlen = 0;
|
|
if (ljust)
|
|
padlen = -padlen;
|
|
|
|
/* leading pad characters */
|
|
for (; padlen > 0; padlen--)
|
|
PC (' ');
|
|
|
|
/* convert WSTRING to multibyte character STRING, honoring PRECISION */
|
|
string = convwidestr (wstring, pr);
|
|
|
|
/* output STRING, assuming that convwidestr has taken care of the precision
|
|
and returned only the necessary bytes. */
|
|
for (i = 0; string[i]; i++)
|
|
PC (string[i]);
|
|
|
|
/* output any necessary trailing padding */
|
|
for (; padlen < 0; padlen++)
|
|
PC (' ');
|
|
|
|
free (string);
|
|
return ((vflag == 0 && ferror (stdout)) ? -1 : 0);
|
|
}
|
|
#endif
|
|
|
|
/* Convert STRING by expanding the escape sequences specified by the
|
|
POSIX standard for printf's `%b' format string. If SAWC is non-null,
|
|
perform the processing appropriate for %b arguments. In particular,
|
|
recognize `\c' and use that as a string terminator. If we see \c, set
|
|
*SAWC to 1 before returning. LEN is the length of STRING. */
|
|
|
|
/* Translate a single backslash-escape sequence starting at ESTART (the
|
|
character after the backslash) and return the number of characters
|
|
consumed by the sequence. CP is the place to return the translated
|
|
value. *SAWC is set to 1 if the escape sequence was \c, since that means
|
|
to short-circuit the rest of the processing. If SAWC is null, we don't
|
|
do the \c short-circuiting, and \c is treated as an unrecognized escape
|
|
sequence; we also bypass the other processing specific to %b arguments. */
|
|
static int
|
|
tescape (char *estart, char *cp, int *lenp, int *sawc)
|
|
{
|
|
register char *p;
|
|
int temp, c, evalue;
|
|
unsigned long uvalue;
|
|
|
|
p = estart;
|
|
if (lenp)
|
|
*lenp = 1;
|
|
|
|
switch (c = *p++)
|
|
{
|
|
case 'a': *cp = '\a'; break;
|
|
|
|
case 'b': *cp = '\b'; break;
|
|
|
|
case 'e':
|
|
case 'E': *cp = '\033'; break; /* ESC -- non-ANSI */
|
|
|
|
case 'f': *cp = '\f'; break;
|
|
|
|
case 'n': *cp = '\n'; break;
|
|
|
|
case 'r': *cp = '\r'; break;
|
|
|
|
case 't': *cp = '\t'; break;
|
|
|
|
case 'v': *cp = '\v'; break;
|
|
|
|
/* The octal escape sequences are `\0' followed by up to three octal
|
|
digits (if SAWC), or `\' followed by up to three octal digits (if
|
|
!SAWC). As an extension, we allow the latter form even if SAWC. */
|
|
case '0': case '1': case '2': case '3':
|
|
case '4': case '5': case '6': case '7':
|
|
evalue = OCTVALUE (c);
|
|
for (temp = 2 + (!evalue && !!sawc); ISOCTAL (*p) && temp--; p++)
|
|
evalue = (evalue * 8) + OCTVALUE (*p);
|
|
*cp = evalue & 0xFF;
|
|
break;
|
|
|
|
/* And, as another extension, we allow \xNN, where each N is a
|
|
hex digit. */
|
|
case 'x':
|
|
for (temp = 2, evalue = 0; ISXDIGIT ((unsigned char)*p) && temp--; p++)
|
|
evalue = (evalue * 16) + HEXVALUE (*p);
|
|
if (p == estart + 1)
|
|
{
|
|
builtin_error (_("missing hex digit for \\x"));
|
|
*cp = '\\';
|
|
return 0;
|
|
}
|
|
*cp = evalue & 0xFF;
|
|
break;
|
|
|
|
#if defined (HANDLE_MULTIBYTE)
|
|
case 'u':
|
|
case 'U':
|
|
temp = (c == 'u') ? 4 : 8; /* \uNNNN \UNNNNNNNN */
|
|
for (uvalue = 0; ISXDIGIT ((unsigned char)*p) && temp--; p++)
|
|
uvalue = (uvalue * 16) + HEXVALUE (*p);
|
|
if (p == estart + 1)
|
|
{
|
|
builtin_error (_("missing unicode digit for \\%c"), c);
|
|
*cp = '\\';
|
|
return 0;
|
|
}
|
|
if (uvalue <= 0x7f) /* <= 0x7f translates directly */
|
|
*cp = uvalue;
|
|
else
|
|
{
|
|
temp = u32cconv (uvalue, cp);
|
|
cp[temp] = '\0';
|
|
if (lenp)
|
|
*lenp = temp;
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
case '\\': /* \\ -> \ */
|
|
*cp = c;
|
|
break;
|
|
|
|
/* SAWC == 0 means that \', \", and \? are recognized as escape
|
|
sequences, though the only processing performed is backslash
|
|
removal. */
|
|
case '\'': case '"': case '?':
|
|
if (!sawc)
|
|
*cp = c;
|
|
else
|
|
{
|
|
*cp = '\\';
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case 'c':
|
|
if (sawc)
|
|
{
|
|
*sawc = 1;
|
|
break;
|
|
}
|
|
/* other backslash escapes are passed through unaltered */
|
|
default:
|
|
*cp = '\\';
|
|
return 0;
|
|
}
|
|
return (p - estart);
|
|
}
|
|
|
|
static char *
|
|
bexpand (char *string, size_t len, int *sawc, size_t *lenp)
|
|
{
|
|
int temp, c;
|
|
char *ret, *r, *s, *send;
|
|
#if defined (HANDLE_MULTIBYTE)
|
|
char mbch[25];
|
|
int mbind, mblen;
|
|
#endif
|
|
DECLARE_MBSTATE;
|
|
|
|
if (string == 0 || len == 0)
|
|
{
|
|
if (sawc)
|
|
*sawc = 0;
|
|
if (lenp)
|
|
*lenp = 0;
|
|
ret = (char *)xmalloc (1);
|
|
ret[0] = '\0';
|
|
return (ret);
|
|
}
|
|
|
|
#if defined (HANDLE_MULTIBYTE)
|
|
/* same logic as lib/sh/strtrans.c:ansicstr() */
|
|
temp = 4*len + 4;
|
|
if (temp < 12)
|
|
temp = 12; /* ensure enough for eventual u32cesc */
|
|
ret = (char *)xmalloc (temp);
|
|
#else
|
|
ret = (char *)xmalloc (len + 1);
|
|
#endif
|
|
|
|
send = string + len;
|
|
for (r = ret, s = string; s && *s; )
|
|
{
|
|
if (s[1] == '\0')
|
|
{
|
|
*r++ = *s;
|
|
break;
|
|
}
|
|
else if (*s != '\\')
|
|
{
|
|
COPY_CHAR_P (r, s, send);
|
|
continue;
|
|
}
|
|
else
|
|
s++; /* *s == '\\' */
|
|
|
|
temp = 0;
|
|
#if defined (HANDLE_MULTIBYTE)
|
|
memset (mbch, '\0', sizeof (mbch));
|
|
s += tescape (s, mbch, &mblen, &temp);
|
|
#else
|
|
s += tescape (s, &c, (int *)NULL, &temp);
|
|
#endif
|
|
if (temp)
|
|
{
|
|
if (sawc)
|
|
*sawc = 1;
|
|
break;
|
|
}
|
|
|
|
#if defined (HANDLE_MULTIBYTE)
|
|
for (mbind = 0; mbind < mblen; mbind++)
|
|
*r++ = mbch[mbind];
|
|
#else
|
|
*r++ = c;
|
|
#endif
|
|
}
|
|
|
|
*r = '\0';
|
|
if (lenp)
|
|
*lenp = r - ret;
|
|
return ret;
|
|
}
|
|
|
|
static char *
|
|
vbadd (char *buf, int blen)
|
|
{
|
|
size_t nlen;
|
|
|
|
nlen = vblen + blen + 1;
|
|
if (nlen >= vbsize)
|
|
{
|
|
vbsize = ((nlen + 63) >> 6) << 6;
|
|
vbuf = (char *)xrealloc (vbuf, vbsize);
|
|
}
|
|
|
|
if (blen == 1)
|
|
vbuf[vblen++] = buf[0];
|
|
else if (blen > 1)
|
|
{
|
|
FASTCOPY (buf, vbuf + vblen, blen);
|
|
vblen += blen;
|
|
}
|
|
vbuf[vblen] = '\0';
|
|
|
|
#ifdef DEBUG
|
|
if (strlen (vbuf) != vblen)
|
|
internal_error ("printf:vbadd: vblen (%zu) != strlen (vbuf) (%zu)", vblen, strlen (vbuf));
|
|
#endif
|
|
|
|
return vbuf;
|
|
}
|
|
|
|
static int
|
|
vbprintf (const char *format, ...)
|
|
{
|
|
va_list args;
|
|
size_t nlen;
|
|
int blen;
|
|
|
|
va_start (args, format);
|
|
blen = vsnprintf (vbuf + vblen, vbsize - vblen, format, args);
|
|
va_end (args);
|
|
if (blen < 0)
|
|
return (blen);
|
|
|
|
nlen = vblen + blen + 1;
|
|
if (nlen >= vbsize)
|
|
{
|
|
vbsize = ((nlen + 63) >> 6) << 6;
|
|
vbuf = (char *)xrealloc (vbuf, vbsize);
|
|
va_start (args, format);
|
|
blen = vsnprintf (vbuf + vblen, vbsize - vblen, format, args);
|
|
va_end (args);
|
|
if (blen < 0)
|
|
return (blen);
|
|
}
|
|
|
|
vblen += blen;
|
|
vbuf[vblen] = '\0';
|
|
|
|
#ifdef DEBUG
|
|
if (strlen (vbuf) != vblen)
|
|
internal_error ("printf:vbprintf: vblen (%zu) != strlen (vbuf) (%zu)", vblen, strlen (vbuf));
|
|
#endif
|
|
|
|
return (blen);
|
|
}
|
|
|
|
static char *
|
|
mklong (char *str, char *modifiers, size_t mlen)
|
|
{
|
|
size_t len, slen;
|
|
|
|
slen = strlen (str);
|
|
len = slen + mlen + 1;
|
|
|
|
if (len > conv_bufsize)
|
|
{
|
|
conv_bufsize = (((len + 1023) >> 10) << 10);
|
|
conv_buf = (char *)xrealloc (conv_buf, conv_bufsize);
|
|
}
|
|
|
|
FASTCOPY (str, conv_buf, slen - 1);
|
|
FASTCOPY (modifiers, conv_buf + slen - 1, mlen);
|
|
|
|
conv_buf[len - 2] = str[slen - 1];
|
|
conv_buf[len - 1] = '\0';
|
|
return (conv_buf);
|
|
}
|
|
|
|
static inline char *
|
|
getarg (void)
|
|
{
|
|
return (garglist ? garglist->word->word : 0);
|
|
}
|
|
|
|
static inline void
|
|
advancearg (void)
|
|
{
|
|
garglist = garglist->next;
|
|
}
|
|
|
|
static int
|
|
getchr (void)
|
|
{
|
|
int ret;
|
|
char *arg;
|
|
|
|
arg = getarg ();
|
|
|
|
if (arg == 0)
|
|
return ('\0');
|
|
|
|
ret = (int)arg[0];
|
|
|
|
advancearg ();
|
|
return ret;
|
|
}
|
|
|
|
static char *
|
|
getstr (void)
|
|
{
|
|
char *ret;
|
|
|
|
ret = getarg ();
|
|
if (ret == 0)
|
|
return ("");
|
|
|
|
advancearg ();
|
|
return ret;
|
|
}
|
|
|
|
/* POSIX.2 says ``...a diagnostic message shall be written to standard
|
|
error, and the utility shall not exit with a zero exit status, but
|
|
shall continue processing any remaining operands and shall write the
|
|
value accumulated at the time the error was detected to standard
|
|
output.'' */
|
|
|
|
static inline void
|
|
chk_converror (char *s, char *ep)
|
|
{
|
|
if (*ep || ep == s)
|
|
{
|
|
sh_invalidnum (s);
|
|
conversion_error = 1;
|
|
}
|
|
else if (errno == ERANGE)
|
|
printf_erange (s);
|
|
}
|
|
|
|
/* Don't call getintmax here because it may consume an argument on error, and
|
|
we call this to get field width/precision arguments. */
|
|
static int
|
|
getint (int overflow_retval)
|
|
{
|
|
intmax_t ret;
|
|
char *ep, *arg;
|
|
int overflow;
|
|
|
|
arg = getarg ();
|
|
|
|
if (arg == 0)
|
|
return (0);
|
|
|
|
if (arg[0] == '\'' || arg[0] == '"')
|
|
return asciicode ();
|
|
|
|
errno = 0;
|
|
ret = strtoimax (arg, &ep, 0);
|
|
if (overflow = (errno == ERANGE) || (ret < INT_MIN || ret > INT_MAX))
|
|
errno = ERANGE; /* force errno */
|
|
|
|
chk_converror (arg, ep);
|
|
|
|
advancearg ();
|
|
return (overflow ? overflow_retval : (int)ret);
|
|
}
|
|
|
|
static intmax_t
|
|
getintmax (void)
|
|
{
|
|
intmax_t ret;
|
|
char *ep, *arg;
|
|
|
|
arg = getarg ();
|
|
|
|
if (arg == 0)
|
|
return (0);
|
|
|
|
if (arg[0] == '\'' || arg[0] == '"')
|
|
return asciicode ();
|
|
|
|
errno = 0;
|
|
ret = strtoimax (arg, &ep, 0);
|
|
|
|
chk_converror (arg, ep);
|
|
|
|
advancearg ();
|
|
return (ret);
|
|
}
|
|
|
|
static uintmax_t
|
|
getuintmax (void)
|
|
{
|
|
uintmax_t ret;
|
|
char *ep, *arg;
|
|
|
|
arg = getarg ();
|
|
|
|
if (arg == 0)
|
|
return (0);
|
|
|
|
if (arg[0] == '\'' || arg[0] == '"')
|
|
return asciicode ();
|
|
|
|
errno = 0;
|
|
ret = strtoumax (arg, &ep, 0);
|
|
|
|
chk_converror (arg, ep);
|
|
|
|
advancearg ();
|
|
return (ret);
|
|
}
|
|
|
|
static double
|
|
getdouble (void)
|
|
{
|
|
double ret;
|
|
char *ep, *arg;
|
|
|
|
arg = getarg ();
|
|
|
|
if (arg == 0)
|
|
return (0);
|
|
|
|
if (arg[0] == '\'' || arg[0] == '"')
|
|
return asciicode ();
|
|
|
|
errno = 0;
|
|
ret = strtod (arg, &ep);
|
|
|
|
chk_converror (arg, ep);
|
|
|
|
advancearg ();
|
|
return (ret);
|
|
}
|
|
|
|
static floatmax_t
|
|
getfloatmax (void)
|
|
{
|
|
floatmax_t ret;
|
|
char *ep, *arg;
|
|
|
|
arg = getarg ();
|
|
|
|
if (arg == 0)
|
|
return (0);
|
|
|
|
if (arg[0] == '\'' || arg[0] == '"')
|
|
return asciicode ();
|
|
|
|
errno = 0;
|
|
ret = strtofltmax (arg, &ep);
|
|
|
|
chk_converror (arg, ep);
|
|
|
|
advancearg ();
|
|
return (ret);
|
|
}
|
|
|
|
/* NO check is needed for garglist here. */
|
|
static intmax_t
|
|
asciicode (void)
|
|
{
|
|
register intmax_t ch;
|
|
char *arg;
|
|
#if defined (HANDLE_MULTIBYTE)
|
|
wchar_t wc;
|
|
size_t slen, mblength;
|
|
#endif
|
|
DECLARE_MBSTATE;
|
|
|
|
arg = getarg ();
|
|
#if defined (HANDLE_MULTIBYTE)
|
|
slen = strlen (arg+1);
|
|
wc = 0;
|
|
mblength = mbrtowc (&wc, arg+1, slen, &state);
|
|
if (MB_INVALIDCH (mblength) == 0)
|
|
ch = wc; /* XXX */
|
|
else
|
|
#endif
|
|
ch = (unsigned char)arg[1];
|
|
|
|
advancearg ();
|
|
return (ch);
|
|
}
|
|
|
|
#if defined (HANDLE_MULTIBYTE)
|
|
static wchar_t *
|
|
getwidestr (size_t *lenp)
|
|
{
|
|
wchar_t *ws;
|
|
const char *mbs;
|
|
size_t slen, mblength;
|
|
char *arg;
|
|
DECLARE_MBSTATE;
|
|
|
|
arg = getarg ();
|
|
if (arg == 0)
|
|
{
|
|
if (lenp)
|
|
*lenp = 0;
|
|
return NULL;
|
|
}
|
|
|
|
mbs = arg;
|
|
slen = strlen (mbs);
|
|
ws = (wchar_t *)xmalloc ((slen + 1) * sizeof (wchar_t));
|
|
mblength = mbsrtowcs (ws, &mbs, slen + 1, &state);
|
|
if (lenp)
|
|
*lenp = mblength;
|
|
|
|
if (MB_INVALIDCH (mblength))
|
|
{
|
|
int i;
|
|
for (i = 0; i < slen; i++)
|
|
ws[i] = (wchar_t)arg[i];
|
|
ws[slen] = L'\0';
|
|
if (lenp)
|
|
*lenp = slen;
|
|
}
|
|
|
|
advancearg ();
|
|
return (ws);
|
|
}
|
|
|
|
static wint_t
|
|
getwidechar (void)
|
|
{
|
|
wchar_t wc;
|
|
size_t slen, mblength;
|
|
char *arg;
|
|
DECLARE_MBSTATE;
|
|
|
|
arg = getarg ();
|
|
if (arg == 0)
|
|
return L'\0';
|
|
|
|
wc = 0;
|
|
mblength = mbrtowc (&wc, arg, locale_mb_cur_max, &state);
|
|
if (MB_INVALIDCH (mblength))
|
|
wc = (wchar_t)arg[0];
|
|
|
|
advancearg ();
|
|
return (wc);
|
|
}
|
|
|
|
/* The basic approach is to take the wide character string, apply any
|
|
precision in terms of characters (otherwise the precision is useless),
|
|
compute the size of the output buffer, call wcsrtombs to convert back
|
|
to multibyte characters, and return the result. */
|
|
static char *
|
|
convwidestr (wchar_t *ws, int prec)
|
|
{
|
|
const wchar_t *ts;
|
|
wchar_t wc;
|
|
char *ret;
|
|
size_t rlen, rsize;
|
|
DECLARE_MBSTATE;
|
|
|
|
ts = (const wchar_t *)ws;
|
|
|
|
if (prec >= 0)
|
|
{
|
|
rsize = prec * MB_CUR_MAX;
|
|
ret = (char *)xmalloc (rsize + 1);
|
|
#if defined (HAVE_WCSNRTOMBS)
|
|
rlen = wcsnrtombs (ret, &ts, prec, rsize, &state);
|
|
#else
|
|
wc = ws[prec];
|
|
ws[prec] = L'\0';
|
|
rlen = wcsrtombs (ret, &ts, rsize, &state);
|
|
ws[prec] = wc;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
rlen = wcsrtombs (NULL, &ts, 0, &state);
|
|
if (rlen != (size_t)-1)
|
|
{
|
|
memset (&state, '\0', sizeof (mbstate_t));
|
|
ret = (char *)xmalloc (rlen + 1);
|
|
rlen = wcsrtombs (ret, &ts, rlen, &state);
|
|
}
|
|
else
|
|
ret = (char *)xmalloc (1);
|
|
}
|
|
if (MB_INVALIDCH (rlen))
|
|
rlen = 0;
|
|
|
|
ret[rlen] = '\0';
|
|
return ret;
|
|
}
|
|
|
|
static char *
|
|
convwidechar (wint_t wi, int prec)
|
|
{
|
|
wchar_t wc;
|
|
char *ret;
|
|
size_t rlen;
|
|
DECLARE_MBSTATE;
|
|
|
|
wc = (wchar_t)wi;
|
|
ret = (char *)xmalloc (MB_LEN_MAX + 1);
|
|
rlen = wcrtomb (ret, wc, &state);
|
|
if (MB_INVALIDCH (rlen))
|
|
rlen = 0;
|
|
|
|
ret[rlen] = '\0';
|
|
return ret;
|
|
}
|
|
#endif
|