mirror of
git://git.code.sf.net/p/cdesktopenv/code
synced 2025-03-09 15:50:02 +00:00
This commit fixes 'command -x' to adapt to OS limitations with
regards to data alignment in the arguments list. A feature test is
added that detects if the OS aligns the argument on 32-bit or
64-bit boundaries or not at all, allowing 'command -x' to avoid
E2BIG errors while maximising efficiency.
Also, as of now, 'command -x' is a way to bypass built-ins and
run/query an external command. Built-ins do not limit the length of
their argument list, so '-x' never made sense to use for them. And
because '-x' hangs on Linux and macOS on every ksh93 release
version to date (see acf84e96
), few use it, so there is little
reason not to make this change.
Finally, this fixes a longstanding bug that caused the minimum exit
status of 'command -x' to be 1 if a command with many arguments was
divided into several command invocations. This is done by replacing
broken flaggery with a new SH_XARG state flag bit.
src/cmd/ksh93/features/externs:
- Add new C feature test detecting byte alignment in args list.
The test writes a #define ARG_ALIGN_BYTES with the amount of
bytes the OS aligns arguments to, or zero for no alignment.
src/cmd/ksh93/include/defs.h:
- Add new SH_XARG state bit indicating 'command -x' is active.
src/cmd/ksh93/sh/path.c: path_xargs():
- Leave extra 2k in the args buffer instead of 1k, just to be sure;
some commands add large environment variables these days.
- Fix a bug in subtracting the length of existing arguments and
environment variables. 'size -= strlen(cp)-1;' subtracts one less
than the size of cp, which makes no sense; what is necessary is
to substract the length plus one to account for the terminating
zero byte, i.e.: 'size -= strlen(cp)+1'.
- Use the ARG_ALIGN_BYTES feature test result to match the OS's
data alignment requirements.
- path_spawn(): E2BIG: Change to checking SH_XARG state bit.
src/cmd/ksh93/bltins/whence.c: b_command():
- Allow combining -x with -p, -v and -V with the expected results
by setting P_FLAG to act like 'whence -p'. E.g., as of now,
command -xv printf
is equivalent to
whence -p printf
but note that 'whence' has no equivalent of 'command -pvx printf'
which searches $(getconf PATH) for a command.
- When -x will run a command, now set the new SH_XARG state flag.
src/cmd/ksh93/sh/xec.c: sh_exec():
- Change to using the new SH_XARG state bit.
- Skip the check for built-ins if SH_XARG is active, so that
'command -x' now always runs an external command.
src/lib/libcmd/date.c, src/lib/libcmd/uname.c:
- These path-bound builtins sometimes need to run the external
system command by the same name, but they did that by hardcoding
an unportable direct path. Now that 'command -x' runs an external
command, change this to using 'command -px' to guarantee using
the known-good external system utility in the default PATH.
- In date.c, fix the format string passed to 'command -px date'
when setting the date; it was only compatible with BSD systems.
Use the POSIX variant on non-BSD systems.
516 lines
15 KiB
C
516 lines
15 KiB
C
/***********************************************************************
|
|
* *
|
|
* This software is part of the ast package *
|
|
* Copyright (c) 1992-2012 AT&T Intellectual Property *
|
|
* and is licensed under the *
|
|
* Eclipse Public License, Version 1.0 *
|
|
* by AT&T Intellectual Property *
|
|
* *
|
|
* A copy of the License is available at *
|
|
* http://www.eclipse.org/org/documents/epl-v10.html *
|
|
* (with md5 checksum b35adb5213ca9657e911e9befb180842) *
|
|
* *
|
|
* Information and Software Systems Research *
|
|
* AT&T Research *
|
|
* Florham Park NJ *
|
|
* *
|
|
* Glenn Fowler <gsf@research.att.com> *
|
|
* David Korn <dgk@research.att.com> *
|
|
* *
|
|
***********************************************************************/
|
|
#pragma prototyped
|
|
/*
|
|
* Glenn Fowler
|
|
* AT&T Research
|
|
*
|
|
* date -- set/display date
|
|
*/
|
|
|
|
static const char usage[] =
|
|
"[-?\n@(#)$Id: date (AT&T Research) 2011-01-27 $\n]"
|
|
USAGE_LICENSE
|
|
"[+NAME?date - set/list/convert dates]"
|
|
"[+DESCRIPTION?\bdate\b sets the current date and time (with appropriate"
|
|
" privilege), lists the current date or file dates, or converts"
|
|
" dates.]"
|
|
"[+?Most common \adate\a forms are recognized, including those for"
|
|
" \bcrontab\b(1), \bls\b(1), \btouch\b(1), and the default"
|
|
" output from \bdate\b itself.]"
|
|
"[+?If the \adate\a operand consists of 4, 6, 8, 10 or 12 digits followed"
|
|
" by an optional \b.\b and two digits then it is interpreted as:"
|
|
" \aHHMM.SS\a, \addHHMM.SS\a, \ammddHHMM.SS\a, \ammddHHMMyy.SS\a or"
|
|
" \ayymmddHHMM.SS\a, or \ammddHHMMccyy.SS\a or \accyymmddHHMM.SS\a."
|
|
" Conflicting standards and practice allow a leading or trailing"
|
|
" 2 or 4 digit year for the 10 and 12 digit forms; the X/Open trailing"
|
|
" form is used to disambiguate (\btouch\b(1) uses the leading form.)"
|
|
" Avoid the 10 digit form to avoid confusion. The digit fields are:]{"
|
|
" [+cc?Century - 1, 19-20.]"
|
|
" [+yy?Year in century, 00-99.]"
|
|
" [+mm?Month, 01-12.]"
|
|
" [+dd?Day of month, 01-31.]"
|
|
" [+HH?Hour, 00-23.]"
|
|
" [+MM?Minute, 00-59.]"
|
|
" [+SS?Seconds, 00-60.]"
|
|
"}"
|
|
"[+?If more than one \adate\a operand is specified then:]{"
|
|
" [+1.?Each operand sets the reference date for the next"
|
|
" operand.]"
|
|
" [+2.?The date is listed for each operand.]"
|
|
" [+3.?The system date is not set.]"
|
|
"}"
|
|
|
|
"[a:access-time|atime?List file argument access times.]"
|
|
"[c:change-time|ctime?List file argument change times.]"
|
|
"[d:date?Use \adate\a as the current date and do not set the system"
|
|
" clock.]:[date]"
|
|
"[e:epoch?Output the date in seconds since the epoch."
|
|
" Equivalent to \b--format=%s\b.]"
|
|
"[E:elapsed?Interpret pairs of arguments as start and stop dates, sum the"
|
|
" differences between all pairs, and list the result as a"
|
|
" \bfmtelapsed\b(3) elapsed time on the standard output. If there are"
|
|
" an odd number of arguments then the last time argument is differenced"
|
|
" with the current time.]"
|
|
"[f:format?Output the date according to the \bstrftime\b(3) \aformat\a."
|
|
" For backwards compatibility, a first argument of the form"
|
|
" \b+\b\aformat\a is equivalent to \b-f\b format."
|
|
" \aformat\a is in \bprintf\b(3) style, where %\afield\a names"
|
|
" a fixed size field, zero padded if necessary,"
|
|
" and \\\ac\a and \\\annn\a sequences are as in C. Invalid"
|
|
" %\afield\a specifications and all other characters are copied"
|
|
" without change. \afield\a may be preceded by \b%-\b to turn off"
|
|
" padding or \b%_\b to pad with space, otherwise numeric fields"
|
|
" are padded with \b0\b and string fields are padded with space."
|
|
" \afield\a may also be preceded by \bE\b for alternate era"
|
|
" representation or \bO\b for alternate digit representation (if"
|
|
" supported by the current locale.) Finally, an integral \awidth\a"
|
|
" preceding \afield\a truncates the field to \awidth\a characters."
|
|
" The fields are:]:[format]{"
|
|
" [+%?% character]"
|
|
" [+a?abbreviated weekday name]"
|
|
" [+A?full weekday name]"
|
|
" [+b?abbreviated month name]"
|
|
" [+B?full month name]"
|
|
" [+c?\bctime\b(3) style date without the trailing newline]"
|
|
" [+C?2-digit century]"
|
|
" [+d?day of month number]"
|
|
" [+D?date as \amm/dd/yy\a]"
|
|
" [+e?blank padded day of month number]"
|
|
" [+f?print a date with the format '\%Y.\%m.\%d-\%H:\%M:\%S']"
|
|
" [+F?%ISO 8601:2000 standard date format; equivalent to Y-%m-%d]"
|
|
" [+g?\bls\b(1) \b-l\b recent date with \ahh:mm\a]"
|
|
" [+G?\bls\b(1) \b-l\b distant date with \ayyyy\a]"
|
|
" [+h?abbreviated month name]"
|
|
" [+H?24-hour clock hour, zero-padded]"
|
|
" [+i?international \bdate\b(1) date with time zone type name]"
|
|
" [+I?12-hour clock hour, zero-padded]"
|
|
" [+j?1-offset Julian date]"
|
|
" [+J?0-offset Julian date]"
|
|
" [+k?24-hour clock hour, blank-padded]"
|
|
" [+K?all numeric date; equivalent to \b%Y-%m-%d+%H:%M:%S\b; \b%_[EO]]K\b for space separator, %OK adds \b.%N\b, \b%EK\b adds \b%.N%z\b, \b%_EK\b adds \b.%N %z\b]"
|
|
" [+l?12-hour clock hour, blank-padded]"
|
|
" [+L?locale default date format]"
|
|
" [+m?month number]"
|
|
" [+M?minutes]"
|
|
" [+n?newline character]"
|
|
" [+N?nanoseconds 000000000-999999999]"
|
|
" [+p?meridian (e.g., \bAM\b or \bPM\b)]"
|
|
" [+q?quarter of the year]"
|
|
" [+Q?\a<del>recent<del>distant<del>\a: \a<del>\a is a unique"
|
|
" delimter character; \arecent\a format for recent"
|
|
" dates, \adistant\a format otherwise]"
|
|
" [+r?12-hour time as \ahh:mm:ss meridian\a]"
|
|
" [+R?24-hour time as \ahh:mm\a]"
|
|
" [+s?number of seconds since the epoch; \a.prec\a preceding"
|
|
" \bs\b appends \aprec\a nanosecond digits, \b9\b if"
|
|
" \aprec\a is omitted]"
|
|
" [+S?seconds 00-60]"
|
|
" [+t?tab character]"
|
|
" [+T?24-hour time as \ahh:mm:ss\a]"
|
|
" [+u?weekday number 1(Monday)-7]"
|
|
" [+U?week number with Sunday as the first day]"
|
|
" [+V?ISO week number (i18n is \afun\a)]"
|
|
" [+w?weekday number 0(Sunday)-6]"
|
|
" [+W?week number with Monday as the first day]"
|
|
" [+x?locale date style that includes month, day and year]"
|
|
" [+X?locale time style that includes hours and minutes]"
|
|
" [+y?2-digit year (you'll be sorry)]"
|
|
" [+Y?4-digit year]"
|
|
" [+z?time zone \aSHHMM\a west of GMT offset where S is"
|
|
" \b+\b or \b-\b, use pad _ for \aSHH:MM\a]"
|
|
" [+Z?time zone name]"
|
|
" [+=[=]][-+]]flag?set (default or +) or clear (-) \aflag\a"
|
|
" for the remainder of \aformat\a, or for the remainder"
|
|
" of the process if \b==\b is specified. \aflag\a may be:]{"
|
|
" [+l?enable leap second adjustments]"
|
|
" [+n?convert \b%S\b as \b%S.%N\b]"
|
|
" [+u?UTC time zone]"
|
|
" }"
|
|
" [+#?equivalent to %s]"
|
|
" [+??alternate?use \aalternate\a format if a default format"
|
|
" override has not been specified, e.g., \bls\b(1) uses"
|
|
" \"%?%l\"; export TM_OPTIONS=\"format='\aoverride\a'\""
|
|
" to override the default]"
|
|
"}"
|
|
"[i:incremental|adjust?Set the system time in incremental adjustments to"
|
|
" avoid complete time shift shock. Negative adjustments still maintain"
|
|
" monotonic increasing time. Not available on all systems.]"
|
|
"[L:last?List only the last time for multiple \adate\a operands.]"
|
|
"[l:leap-seconds?Include leap seconds in time calculations. Leap seconds"
|
|
" after the ast library release date are not accounted for.]"
|
|
"[m:modify-time|mtime?List file argument modify times.]"
|
|
"[n!:network?Set network time.]"
|
|
"[p:parse?Add \aformat\a to the list of \bstrptime\b(3) parse conversion"
|
|
" formats. \aformat\a follows the same conventions as the"
|
|
" \b--format\b option, with the addition of these format"
|
|
" fields:]:[format]{"
|
|
" [+|?If the format failed before this point then restart"
|
|
" the parse with the remaining format.]"
|
|
" [+&?Call the \btmdate\b(3) heuristic parser. This is"
|
|
" is the default when \b--parse\b is omitted.]"
|
|
"}"
|
|
"[R:rfc-2822?List date and time in RFC 2822 format "
|
|
"(%a, %-e %h %Y %H:%M:%S %z).]"
|
|
"[T:rfc-3339?List date and time in RFC 3339 format according to "
|
|
"\atype\a:]:[type]"
|
|
"{"
|
|
"[d:date?(%Y-%m-%d)]"
|
|
"[s:seconds?(%Y-%m-%d %H:%M:%S%_z)]"
|
|
"[n:ns|nanoseconds?(%Y-%m-%d %H:%M:%S.%N%_z)]"
|
|
"}"
|
|
"[s:show?Show the date without setting the system time.]"
|
|
"[u:utc|gmt|zulu|universal?Output dates in \acoordinated universal time\a (UTC).]"
|
|
"[U:unelapsed?Interpret each argument as \bfmtelapsed\b(3) elapsed"
|
|
" time and list the \bstrelapsed\b(3) 1/\ascale\a seconds.]#[scale]"
|
|
"[z:list-zones?List the known time zone table and exit. The table columns"
|
|
" are: country code, standard zone name, savings time zone name,"
|
|
" minutes west of \bUTC\b, and savings time minutes offset. Blank"
|
|
" or empty entries are listed as \b-\b.]"
|
|
|
|
"\n"
|
|
"\n[ +format | date ... | file ... ]\n"
|
|
"\n"
|
|
|
|
"[+SEE ALSO?\bcrontab\b(1), \bls\b(1), \btouch\b(1), \bfmtelapsed\b(3),"
|
|
" \bstrftime\b(3), \bstrptime\b(3), \btm\b(3)]"
|
|
;
|
|
|
|
#include <cmd.h>
|
|
#include <ls.h>
|
|
#include <proc.h>
|
|
#include <tmx.h>
|
|
#include <times.h>
|
|
|
|
typedef struct Fmt
|
|
{
|
|
struct Fmt* next;
|
|
char* format;
|
|
} Fmt_t;
|
|
|
|
#ifndef ENOSYS
|
|
#define ENOSYS EINVAL
|
|
#endif
|
|
|
|
/*
|
|
* set the system clock
|
|
* the standards wimped out here
|
|
*/
|
|
|
|
static int
|
|
settime(Shbltin_t* context, const char* cmd, Time_t now, int adjust, int network)
|
|
{
|
|
char** argv;
|
|
char* args[7];
|
|
char buf[1024];
|
|
|
|
if (!adjust && !network)
|
|
return tmxsettime(now);
|
|
argv = args;
|
|
*argv++ = "command";
|
|
*argv++ = "-px";
|
|
*argv++ = "date";
|
|
if (streq(astconf("UNIVERSE", NiL, NiL), "att"))
|
|
{
|
|
tmxfmt(buf, sizeof(buf), "%m%d%H" "%M%Y.%S", now);
|
|
if (adjust)
|
|
*argv++ = "-a";
|
|
}
|
|
else
|
|
{
|
|
#if __FreeBSD__ || __NetBSD__ || __OpenBSD__ || __bsdi__ || __DragonFly__
|
|
tmxfmt(buf, sizeof(buf), "%Y%m%d%H" "%M.%S", now);
|
|
#else
|
|
tmxfmt(buf, sizeof(buf), "%m%d%H" "%M%Y.%S", now);
|
|
#endif
|
|
if (network)
|
|
*argv++ = "-n";
|
|
if (tm_info.flags & TM_UTC)
|
|
*argv++ = "-u";
|
|
}
|
|
*argv++ = buf;
|
|
*argv = 0;
|
|
if (!sh_run(context, argv - args, args))
|
|
return 0;
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* convert s to Time_t with error checking
|
|
*/
|
|
|
|
static Time_t
|
|
convert(register Fmt_t* f, char* s, Time_t now)
|
|
{
|
|
char* t;
|
|
char* u;
|
|
|
|
do
|
|
{
|
|
now = tmxscan(s, &t, f->format, &u, now, 0);
|
|
if (!*t && (!f->format || !*u))
|
|
break;
|
|
} while (f = f->next);
|
|
if (!f || *t)
|
|
error(3, "%s: invalid date specification", f ? t : s);
|
|
return now;
|
|
}
|
|
|
|
int
|
|
b_date(int argc, register char** argv, Shbltin_t* context)
|
|
{
|
|
register int n;
|
|
register char* s;
|
|
register Fmt_t* f;
|
|
char* t;
|
|
unsigned long u;
|
|
Time_t now;
|
|
Time_t ts;
|
|
Time_t te;
|
|
Time_t e;
|
|
char buf[1024];
|
|
Fmt_t* fmts;
|
|
Fmt_t fmt;
|
|
struct stat st;
|
|
|
|
char* cmd = argv[0]; /* original command path */
|
|
char* format = 0; /* tmxfmt() format */
|
|
char* string = 0; /* date string */
|
|
int elapsed = 0; /* args are start/stop pairs */
|
|
int filetime = 0; /* use this st_ time field */
|
|
int increment = 0; /* incrementally adjust time */
|
|
int last = 0; /* display the last time arg */
|
|
Tm_zone_t* listzones = 0; /* known time zone table */
|
|
int network = 0; /* don't set network time */
|
|
int show = 0; /* show date and don't set */
|
|
int unelapsed = 0; /* fmtelapsed() => strelapsed */
|
|
|
|
cmdinit(argc, argv, context, ERROR_CATALOG, 0);
|
|
tm_info.flags = TM_DATESTYLE;
|
|
fmts = &fmt;
|
|
fmt.format = "";
|
|
fmt.next = 0;
|
|
for (;;)
|
|
{
|
|
switch (optget(argv, usage))
|
|
{
|
|
case 'a':
|
|
case 'c':
|
|
case 'm':
|
|
filetime = opt_info.option[1];
|
|
continue;
|
|
case 'd':
|
|
string = opt_info.arg;
|
|
show = 1;
|
|
continue;
|
|
case 'e':
|
|
format = "%s";
|
|
continue;
|
|
case 'E':
|
|
elapsed = 1;
|
|
continue;
|
|
case 'f':
|
|
format = opt_info.arg;
|
|
continue;
|
|
case 'i':
|
|
increment = 1;
|
|
continue;
|
|
case 'l':
|
|
tm_info.flags |= TM_LEAP;
|
|
continue;
|
|
case 'L':
|
|
last = 1;
|
|
continue;
|
|
case 'n':
|
|
network = 1;
|
|
continue;
|
|
case 'p':
|
|
if (!(f = newof(0, Fmt_t, 1, 0)))
|
|
error(ERROR_SYSTEM|3, "out of space [format]");
|
|
f->next = fmts;
|
|
f->format = opt_info.arg;
|
|
fmts = f;
|
|
continue;
|
|
case 'R':
|
|
format = "%a, %-e %h %Y %H:%M:%S %z";
|
|
continue;
|
|
case 's':
|
|
show = 1;
|
|
continue;
|
|
case 'T':
|
|
switch (opt_info.num)
|
|
{
|
|
case 'd':
|
|
format = "%Y-%m-%d";
|
|
continue;
|
|
case 'n':
|
|
format = "%Y-%m-%d %H:%M:%S.%N%_z";
|
|
continue;
|
|
case 's':
|
|
format = "%Y-%m-%d %H:%M:%S%_z";
|
|
continue;
|
|
}
|
|
continue;
|
|
case 'u':
|
|
tm_info.flags |= TM_UTC;
|
|
continue;
|
|
case 'U':
|
|
unelapsed = (int)opt_info.num;
|
|
continue;
|
|
case 'z':
|
|
listzones = tm_data.zone;
|
|
continue;
|
|
case '?':
|
|
error(ERROR_USAGE|4, "%s", opt_info.arg);
|
|
continue;
|
|
case ':':
|
|
error(2, "%s", opt_info.arg);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
argv += opt_info.index;
|
|
if (error_info.errors)
|
|
error(ERROR_USAGE|4, "%s", optusage(NiL));
|
|
now = tmxgettime();
|
|
if (listzones)
|
|
{
|
|
s = "-";
|
|
while (listzones->standard)
|
|
{
|
|
if (listzones->type)
|
|
s = listzones->type;
|
|
sfprintf(sfstdout, "%3s %4s %4s %4d %4d\n", s, *listzones->standard ? listzones->standard : "-", listzones->daylight ? listzones->daylight : "-", listzones->west, listzones->dst);
|
|
listzones++;
|
|
show = 1;
|
|
}
|
|
}
|
|
else if (elapsed)
|
|
{
|
|
e = 0;
|
|
while (s = *argv++)
|
|
{
|
|
if (!(t = *argv++))
|
|
{
|
|
argv--;
|
|
t = "now";
|
|
}
|
|
ts = convert(fmts, s, now);
|
|
te = convert(fmts, t, now);
|
|
if (te > ts)
|
|
e += te - ts;
|
|
else
|
|
e += ts - te;
|
|
}
|
|
sfputr(sfstdout, fmtelapsed((unsigned long)tmxsec(e), 1), '\n');
|
|
show = 1;
|
|
}
|
|
else if (unelapsed)
|
|
{
|
|
while (s = *argv++)
|
|
{
|
|
u = strelapsed(s, &t, unelapsed);
|
|
if (*t)
|
|
error(3, "%s: invalid elapsed time", s);
|
|
sfprintf(sfstdout, "%lu\n", u);
|
|
}
|
|
show = 1;
|
|
}
|
|
else if (filetime)
|
|
{
|
|
if (!*argv)
|
|
error(ERROR_USAGE|4, "%s", optusage(NiL));
|
|
n = argv[1] != 0;
|
|
while (s = *argv++)
|
|
{
|
|
if (stat(s, &st))
|
|
error(2, "%s: not found", s);
|
|
else
|
|
{
|
|
switch (filetime)
|
|
{
|
|
case 'a':
|
|
now = tmxgetatime(&st);
|
|
break;
|
|
case 'c':
|
|
now = tmxgetctime(&st);
|
|
break;
|
|
default:
|
|
now = tmxgetmtime(&st);
|
|
break;
|
|
}
|
|
tmxfmt(buf, sizeof(buf), format, now);
|
|
if (n)
|
|
sfprintf(sfstdout, "%s: %s\n", s, buf);
|
|
else
|
|
sfprintf(sfstdout, "%s\n", buf);
|
|
show = 1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((s = *argv) && !format && *s == '+')
|
|
{
|
|
format = s + 1;
|
|
argv++;
|
|
s = *argv;
|
|
}
|
|
if (s || (s = string))
|
|
{
|
|
if (*argv && string)
|
|
error(ERROR_USAGE|4, "%s", optusage(NiL));
|
|
now = convert(fmts, s, now);
|
|
if (*argv && (s = *++argv))
|
|
{
|
|
show = 1;
|
|
do
|
|
{
|
|
if (!last)
|
|
{
|
|
tmxfmt(buf, sizeof(buf), format, now);
|
|
sfprintf(sfstdout, "%s\n", buf);
|
|
}
|
|
now = convert(fmts, s, now);
|
|
} while (s = *++argv);
|
|
}
|
|
}
|
|
else
|
|
show = 1;
|
|
if (format || show)
|
|
{
|
|
tmxfmt(buf, sizeof(buf), format, now);
|
|
sfprintf(sfstdout, "%s\n", buf);
|
|
}
|
|
else if (settime(context, cmd, now, increment, network))
|
|
error(ERROR_SYSTEM|3, "cannot set system time");
|
|
}
|
|
while (fmts != &fmt)
|
|
{
|
|
f = fmts;
|
|
fmts = fmts->next;
|
|
free(f);
|
|
}
|
|
tm_info.flags = 0;
|
|
if (show && sfsync(sfstdout))
|
|
error(ERROR_system(0), "write error");
|
|
return error_info.errors != 0;
|
|
}
|