-.\" $NetBSD: env.1,v 1.6 1997/10/18 13:55:22 lukem Exp $
-.\"
-.\" Copyright (c) 1980, 1990 The Regents of the University of California.
-.\" All rights reserved.
+.\" Copyright (c) 1980, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
.\"
.\" This code is derived from software contributed to Berkeley by
.\" the Institute of Electrical and Electronics Engineers, Inc.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
-.\" 3. All advertising materials mentioning features or use of this software
-.\" must display the following acknowledgement:
-.\" This product includes software developed by the University of
-.\" California, Berkeley and its contributors.
.\" 4. Neither the name of the University nor the names of its contributors
.\" may be used to endorse or promote products derived from this software
.\" without specific prior written permission.
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.\" from: @(#)printenv.1 6.7 (Berkeley) 7/28/91
-.\" $NetBSD: env.1,v 1.6 1997/10/18 13:55:22 lukem Exp $
+.\" From @(#)printenv.1 8.1 (Berkeley) 6/6/93
+.\" From FreeBSD: src/usr.bin/printenv/printenv.1,v 1.17 2002/11/26 17:33:35 ru Exp
+.\" $FreeBSD$
.\"
-.Dd August 27, 1993
+.Dd April 17, 2008
.Dt ENV 1
.Os
.Sh NAME
.Nm env
-.Nd set and print environment
+.Nd set environment and execute command, or print environment
.Sh SYNOPSIS
.Nm
-.Op Fl i
-.Op Ar name=value ...
-.Oo
-.Ar utility
-.Op argument ...
-.Oc
+.Op Fl iv
+.Op Fl P Ar altpath
+.Op Fl S Ar string
+.Op Fl u Ar name
+.Op Ar name Ns = Ns Ar value ...
+.Op Ar utility Op Ar argument ...
.Sh DESCRIPTION
+The
.Nm
-executes
+utility executes another
.Ar utility
after modifying the environment as
-specified on the command line. The option
-.Ar name=value
-specifies
-an environmental variable,
-.Ar name ,
+specified on the command line.
+Each
+.Ar name Ns = Ns Ar value
+option specifies the setting of an environment variable,
+.Ar name ,
with a value of
-.Ar value .
-The option
-.Sq Fl i
-causes
+.Ar value .
+All such environment variables are set before the
+.Ar utility
+is executed.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl i
+Execute the
+.Ar utility
+with only those environment variables specified by
+.Ar name Ns = Ns Ar value
+options.
+The environment inherited
+by
+.Nm
+is ignored completely.
+.\" -P
+.It Fl P Ar altpath
+Search the set of directories as specified by
+.Ar altpath
+to locate the specified
+.Ar utility
+program, instead of using the value of the
+.Ev PATH
+environment variable.
+.\" -S
+.It Fl S Ar string
+Split apart the given
+.Ar string
+into multiple strings, and process each of the resulting strings
+as separate arguments to the
+.Nm
+utility.
+The
+.Fl S
+option recognizes some special character escape sequences and
+also supports environment-variable substitution, as described
+below.
+.\" -u
+.It Fl u Ar name
+If the environment variable
+.Ar name
+is in the environment, then remove it before processing the
+remaining options.
+This is similar to the
+.Ic unset
+command in
+.Xr sh 1 .
+The value for
+.Ar name
+must not include the
+.Ql =
+character.
+.\" -v
+.It Fl v
+Print verbose information for each step of processing done by the
.Nm
-to completely ignore the environment
-it inherits.
+utility.
+Additional information will be printed if
+.Fl v
+is specified multiple times.
+.El
+.Pp
+The above options are only recognized when they are specified
+before any
+.Ar name Ns = Ns Ar value
+options.
.Pp
-If no
+If no
.Ar utility
is specified,
.Nm
prints out the names and values
-of the variables in the environment, with one
-.Ar name=value
-pair per line.
-.Sh DIAGNOSTICS
-If the
-.Ar utility
-is invoked, the exit status of
+of the variables in the environment, with one name/value pair per line.
+.\"
+.Ss Details of Fl S Ss (split-string) processing
+The processing of the
+.Fl S
+option will split the given
+.Ar string
+into separate arguments based on any space or <tab> characters found in the
+.Ar string .
+Each of those new arguments will then be treated as if it had been
+specified as a separate argument on the original
.Nm
-shall be the exit status of
-.Ar utility;
-otherwise, the
+command.
+.Pp
+Spaces and tabs may be embedded in one of those new arguments by using
+single
+.Pq Dq Li '
+or double
+.Pq Ql \&"
+quotes, or backslashes
+.Pq Ql \e .
+Single quotes will escape all non-single quote characters, up to
+the matching single quote.
+Double quotes will escape all non-double quote characters, up to
+the matching double quote.
+It is an error if the end of the
+.Ar string
+is reached before the matching quote character.
+.Pp
+If
+.Fl S
+would create a new argument that starts with the
+.Ql #
+character, then that argument and the remainder of the
+.Ar string
+will be ignored.
+The
+.Ql \e#
+sequence can be used when you want a new argument to start
+with a
+.Ql #
+character, without causing the remainder of the
+.Ar string
+to be skipped.
+.Pp
+While processing the
+.Ar string
+value,
+.Fl S
+processing will treat certain character combinations as escape
+sequences which represent some action to take.
+The character escape sequences are in backslash notation.
+The characters and their meanings are as follows:
+.Pp
+.Bl -tag -width indent -offset indent -compact
+.It Cm \ec
+Ignore the remaining characters in the
+.Ar string .
+This must not appear inside a double-quoted string.
+.It Cm \ef
+Replace with a <form-feed> character.
+.It Cm \en
+Replace with a <new-line> character.
+.It Cm \er
+Replace with a <carriage return> character.
+.It Cm \et
+Replace with a <tab> character.
+.It Cm \ev
+Replace with a <vertical tab> character.
+.It Cm \e#
+Replace with a
+.Ql #
+character.
+This would be useful when you need a
+.Ql #
+as the first character in one of the arguments created
+by splitting apart the given
+.Ar string .
+.It Cm \e$
+Replace with a
+.Ql $
+character.
+.It Cm \e_
+If this is found inside of a double-quoted string, then replace it
+with a single blank.
+If this is found outside of a quoted string, then treat this as the
+separator character between new arguments in the original
+.Ar string .
+.It Cm \e"
+Replace with a <double quote> character.
+.It Cm \e\'
+Replace with a <single quote> character.
+.It Cm \e\e
+Replace with a backslash character.
+.El
+.Pp
+The sequences for <single-quote> and backslash are the only sequences
+which are recognized inside of a single-quoted string.
+The other sequences have no special meaning inside a single-quoted
+string.
+All escape sequences are recognized inside of a double-quoted string.
+It is an error if a single
+.Ql \e
+character is followed by a character other than the ones listed above.
+.Pp
+The processing of
+.Fl S
+also supports substitution of values from environment variables.
+To do this, the name of the environment variable must be inside of
+.Ql ${} ,
+such as:
+.Li ${SOMEVAR} .
+The common shell syntax of
+.Li $SOMEVAR
+is not supported.
+All values substituted will be the values of the environment variables
+as they were when the
.Nm
-utility exits with one of the following values:
-.Bl -tag -width Ds
-.It 0
-The
+utility was originally invoked.
+Those values will not be checked for any of the escape sequences as
+described above.
+And any settings of
+.Ar name Ns = Ns Ar value
+will not effect the values used for substitution in
+.Fl S
+processing.
+.Pp
+Also,
+.Fl S
+processing can not reference the value of the special parameters
+which are defined by most shells.
+For instance,
+.Fl S
+can not recognize special parameters such as:
+.Ql $* ,
+.Ql $@ ,
+.Ql $# ,
+.Ql $?
+or
+.Ql $$
+if they appear inside the given
+.Ar string .
+.\"
+.Ss Use in shell-scripts
+The
.Nm
-utility completed successfully
-.It 1-125
-An error occurred in the
+utility is often used as the
+.Ar interpreter
+on the first line of interpreted scripts, as
+described in
+.Xr execve 2 .
+.Pp
+Note that the way the kernel parses the
+.Ql #!
+(first line) of an interpreted script has changed as of
+.Fx 6.0 .
+Prior to that, the
+.Fx
+kernel would split that first line into separate arguments based
+on any whitespace (space or <tab> characters) found in the line.
+So, if a script named
+.Pa /usr/local/bin/someport
+had a first line of:
+.Pp
+.Dl "#!/usr/local/bin/php -n -q -dsafe_mode=0"
+.Pp
+then the
+.Pa /usr/local/bin/php
+program would have been started with the arguments of:
+.Bd -literal -offset indent
+arg[0] = '/usr/local/bin/php'
+arg[1] = '-n'
+arg[2] = '-q'
+arg[3] = '-dsafe_mode=0'
+arg[4] = '/usr/local/bin/someport'
+.Ed
+.Pp
+plus any arguments the user specified when executing
+.Pa someport .
+However, this processing of multiple options on the
+.Ql #!
+line is not the way any other operating system parses the
+first line of an interpreted script.
+So after a change which was made for
+.Fx 6.0
+release, that script will result in
+.Pa /usr/local/bin/php
+being started with the arguments of:
+.Bd -literal -offset indent
+arg[0] = '/usr/local/bin/php'
+arg[1] = '-n -q -dsafe_mode=0'
+arg[2] = '/usr/local/bin/someport'
+.Ed
+.Pp
+plus any arguments the user specified.
+This caused a significant change in the behavior of a few scripts.
+In the case of above script, to have it behave the same way under
+.Fx 6.0
+as it did under earlier releases, the first line should be
+changed to:
+.Pp
+.Dl "#!/usr/bin/env -S /usr/local/bin/php -n -q -dsafe_mode=0"
+.Pp
+The
.Nm
-utility.
-.It 126
-The utility specified by
-.Ar utility
-was found, but could not be invoked.
-.It 127
-The utility specified by
-.Ar utility
+utility will be started with the entire line as a single
+argument:
+.Pp
+.Dl "arg[1] = '-S /usr/local/bin/php -n -q -dsafe_mode=0'"
+.Pp
+and then
+.Fl S
+processing will split that line into separate arguments before
+executing
+.Pa /usr/local/bin/php .
+.\"
+.Sh ENVIRONMENT
+The
+.Nm
+utility uses the
+.Ev PATH
+environment variable to locate the requested
+.Ar utility
+if the name contains no
+.Ql /
+characters, unless the
+.Fl P
+option has been specified.
+.Sh EXIT STATUS
+.Ex -std
+An exit status of 126 indicates that
+.Ar utility
+was found, but could not be executed.
+An exit status of 127 indicates that
+.Ar utility
could not be found.
-.El
+.Sh EXAMPLES
+Since the
+.Nm
+utility is often used as part of the first line of an interpreted script,
+the following examples show a number of ways that the
+.Nm
+utility can be useful in scripts.
+.Pp
+The kernel processing of an interpreted script does not allow a script
+to directly reference some other script as its own interpreter.
+As a way around this, the main difference between
+.Pp
+.Dl #!/usr/local/bin/foo
+and
+.Dl "#!/usr/bin/env /usr/local/bin/foo"
+.Pp
+is that the latter works even if
+.Pa /usr/local/bin/foo
+is itself an interpreted script.
+.Pp
+Probably the most common use of
+.Nm
+is to find the correct interpreter for a script, when the interpreter
+may be in different directories on different systems.
+The following example will find the
+.Ql perl
+interpreter by searching through the directories specified by
+.Ev PATH .
+.Pp
+.Dl "#!/usr/bin/env perl"
+.Pp
+One limitation of that example is that it assumes the user's value
+for
+.Ev PATH
+is set to a value which will find the interpreter you want
+to execute.
+The
+.Fl P
+option can be used to make sure a specific list of directories is
+used in the search for
+.Ar utility .
+Note that the
+.Fl S
+option is also required for this example to work correctly.
+.Pp
+.Dl "#!/usr/bin/env -S -P/usr/local/bin:/usr/bin perl"
+.Pp
+The above finds
+.Ql perl
+only if it is in
+.Pa /usr/local/bin
+or
+.Pa /usr/bin .
+That could be combined with the present value of
+.Ev PATH ,
+to provide more flexibility.
+Note that spaces are not required between the
+.Fl S
+and
+.Fl P
+options:
+.Pp
+.Dl "#!/usr/bin/env -S-P/usr/local/bin:/usr/bin:${PATH} perl"
.Sh COMPATIBILITY
-The historic
-.Fl
-option has been deprecated but is still supported in this implementation.
+The
+.Nm
+utility accepts the
+.Fl
+option as a synonym for
+.Fl i .
.Sh SEE ALSO
+.Xr printenv 1 ,
+.Xr sh 1 ,
.Xr execvp 3 ,
.Xr environ 7
.Sh STANDARDS
The
.Nm
utility conforms to
-.St -p1003.2-92 .
+.St -p1003.1-2001 .
+The
+.Fl P , S , u
+and
+.Fl v
+options are non-standard extensions supported by
+.Fx ,
+but which may not be available on other operating systems.
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 4.4 .
+The
+.Fl P , S
+and
+.Fl v
+options were added in
+.Fx 6.0 .
.Sh BUGS
+The
+.Nm
+utility does not handle values of
+.Ar utility
+which have an equals sign
+.Pq Ql =
+in their name, for obvious reasons.
+.Pp
+The
.Nm
-doesn't handle commands with equal
-.Pq Dq =
-signs in their
-names, for obvious reasons.
+utility does not take multibyte characters into account when
+processing the
+.Fl S
+option, which may lead to incorrect results in some locales.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
- * 3. All advertising materials mentioning features or use of this software
- * must display the following acknowledgement:
- * This product includes software developed by the University of
- * California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
#endif
#include <sys/cdefs.h>
-__RCSID("$FreeBSD: src/usr.bin/env/env.c,v 1.11 2002/09/04 23:28:59 dwmalone Exp $");
+__FBSDID("$FreeBSD$");
#include <err.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
+#include "envopts.h"
+
extern char **environ;
+int env_verbosity;
+
static void usage(void);
int
main(int argc, char **argv)
{
- char **ep, *p;
+ char *altpath, **ep, *p, **parg;
char *cleanenv[1];
- int ch;
+ int ch, want_clear;
+ int rtrn;
- while ((ch = getopt(argc, argv, "-i")) != -1)
+ altpath = NULL;
+ want_clear = 0;
+ while ((ch = getopt(argc, argv, "-iP:S:u:v")) != -1)
switch(ch) {
case '-':
case 'i':
- environ = cleanenv;
- cleanenv[0] = NULL;
+ want_clear = 1;
+ break;
+ case 'P':
+ altpath = strdup(optarg);
+ break;
+ case 'S':
+ /*
+ * The -S option, for "split string on spaces, with
+ * support for some simple substitutions"...
+ */
+ split_spaces(optarg, &optind, &argc, &argv);
+ break;
+ case 'u':
+ if (env_verbosity)
+ fprintf(stderr, "#env unset:\t%s\n", optarg);
+ rtrn = unsetenv(optarg);
+ if (rtrn == -1)
+ err(EXIT_FAILURE, "unsetenv %s", optarg);
+ break;
+ case 'v':
+ env_verbosity++;
+ if (env_verbosity > 1)
+ fprintf(stderr, "#env verbosity now at %d\n",
+ env_verbosity);
break;
case '?':
default:
usage();
}
- for (argv += optind; *argv && (p = strchr(*argv, '=')); ++argv)
- (void)putenv(*argv);
+ if (want_clear) {
+ environ = cleanenv;
+ cleanenv[0] = NULL;
+ if (env_verbosity)
+ fprintf(stderr, "#env clearing environ\n");
+ }
+ for (argv += optind; *argv && (p = strchr(*argv, '=')); ++argv) {
+ if (env_verbosity)
+ fprintf(stderr, "#env setenv:\t%s\n", *argv);
+ *p = '\0';
+ rtrn = setenv(*argv, p + 1, 1);
+ *p = '=';
+ if (rtrn == -1)
+ err(EXIT_FAILURE, "setenv %s", *argv);
+ }
if (*argv) {
+ if (altpath)
+ search_paths(altpath, argv);
+ if (env_verbosity) {
+ fprintf(stderr, "#env executing:\t%s\n", *argv);
+ for (parg = argv, argc = 0; *parg; parg++, argc++)
+ fprintf(stderr, "#env arg[%d]=\t'%s'\n",
+ argc, *parg);
+ if (env_verbosity > 1)
+ sleep(1);
+ }
execvp(*argv, argv);
err(errno == ENOENT ? 127 : 126, "%s", *argv);
}
usage(void)
{
(void)fprintf(stderr,
- "usage: env [-i] [name=value ...] [utility [argument ...]]\n");
+ "usage: env [-iv] [-P utilpath] [-S string] [-u name]\n"
+ " [name=value ...] [utility [argument ...]]\n");
exit(1);
}
--- /dev/null
+/*-
+ * Copyright (c) 2005 - Garance Alistair Drosehn <gad@FreeBSD.org>.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of the FreeBSD Project.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <err.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "envopts.h"
+
+static const char *
+ expand_vars(int in_thisarg, char **thisarg_p, char **dest_p,
+ const char **src_p);
+static int is_there(char *candidate);
+
+/*
+ * The is*() routines take a parameter of 'int', but expect values in the range
+ * of unsigned char. Define some wrappers which take a value of type 'char',
+ * whether signed or unsigned, and ensure the value ends up in the right range.
+ */
+#define isalnumch(Anychar) isalnum((u_char)(Anychar))
+#define isalphach(Anychar) isalpha((u_char)(Anychar))
+#define isspacech(Anychar) isspace((u_char)(Anychar))
+
+/*
+ * Routine to determine if a given fully-qualified filename is executable.
+ * This is copied almost verbatim from FreeBSD's usr.bin/which/which.c.
+ */
+static int
+is_there(char *candidate)
+{
+ struct stat fin;
+
+ /* XXX work around access(2) false positives for superuser */
+ if (access(candidate, X_OK) == 0 &&
+ stat(candidate, &fin) == 0 &&
+ S_ISREG(fin.st_mode) &&
+ (getuid() != 0 ||
+ (fin.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0)) {
+ if (env_verbosity > 1)
+ fprintf(stderr, "#env matched:\t'%s'\n", candidate);
+ return (1);
+ }
+ return (0);
+}
+
+/**
+ * Routine to search through an alternate path-list, looking for a given
+ * filename to execute. If the file is found, replace the original
+ * unqualified name with a fully-qualified path. This allows `env' to
+ * execute programs from a specific strict list of possible paths, without
+ * changing the value of PATH seen by the program which will be executed.
+ * E.G.:
+ * #!/usr/bin/env -S-P/usr/local/bin:/usr/bin perl
+ * will execute /usr/local/bin/perl or /usr/bin/perl (whichever is found
+ * first), no matter what the current value of PATH is, and without
+ * changing the value of PATH that the script will see when it runs.
+ *
+ * This is similar to the print_matches() routine in usr.bin/which/which.c.
+ */
+void
+search_paths(char *path, char **argv)
+{
+ char candidate[PATH_MAX];
+ const char *d;
+ char *filename, *fqname;
+
+ /* If the file has a `/' in it, then no search is done */
+ filename = *argv;
+ if (strchr(filename, '/') != NULL)
+ return;
+
+ if (env_verbosity > 1) {
+ fprintf(stderr, "#env Searching:\t'%s'\n", path);
+ fprintf(stderr, "#env for file:\t'%s'\n", filename);
+ }
+
+ fqname = NULL;
+ while ((d = strsep(&path, ":")) != NULL) {
+ if (*d == '\0')
+ d = ".";
+ if (snprintf(candidate, sizeof(candidate), "%s/%s", d,
+ filename) >= (int)sizeof(candidate))
+ continue;
+ if (is_there(candidate)) {
+ fqname = candidate;
+ break;
+ }
+ }
+
+ if (fqname == NULL) {
+ errno = ENOENT;
+ err(127, "%s", filename);
+ }
+ *argv = strdup(candidate);
+}
+
+/**
+ * Routine to split a string into multiple parameters, while recognizing a
+ * few special characters. It recognizes both single and double-quoted
+ * strings. This processing is designed entirely for the benefit of the
+ * parsing of "#!"-lines (aka "shebang" lines == the first line of an
+ * executable script). Different operating systems parse that line in very
+ * different ways, and this split-on-spaces processing is meant to provide
+ * ways to specify arbitrary arguments on that line, no matter how the OS
+ * parses it.
+ *
+ * Within a single-quoted string, the two characters "\'" are treated as
+ * a literal "'" character to add to the string, and "\\" are treated as
+ * a literal "\" character to add. Other than that, all characters are
+ * copied until the processing gets to a terminating "'".
+ *
+ * Within a double-quoted string, many more "\"-style escape sequences
+ * are recognized, mostly copied from what is recognized in the `printf'
+ * command. Some OS's will not allow a literal blank character to be
+ * included in the one argument that they recognize on a shebang-line,
+ * so a few additional escape-sequences are defined to provide ways to
+ * specify blanks.
+ *
+ * Within a double-quoted string "\_" is turned into a literal blank.
+ * (Inside of a single-quoted string, the two characters are just copied)
+ * Outside of a quoted string, "\_" is treated as both a blank, and the
+ * end of the current argument. So with a shelbang-line of:
+ * #!/usr/bin/env -SA=avalue\_perl
+ * the -S value would be broken up into arguments "A=avalue" and "perl".
+ */
+void
+split_spaces(const char *str, int *origind, int *origc, char ***origv)
+{
+ static const char *nullarg = "";
+ const char *bq_src, *copystr, *src;
+ char *dest, **newargv, *newstr, **nextarg, **oldarg;
+ int addcount, bq_destlen, copychar, found_sep, in_arg, in_dq, in_sq;
+
+ /*
+ * Ignore leading space on the string, and then malloc enough room
+ * to build a copy of it. The copy might end up shorter than the
+ * original, due to quoted strings and '\'-processing.
+ */
+ while (isspacech(*str))
+ str++;
+ if (*str == '\0')
+ return;
+ newstr = malloc(strlen(str) + 1);
+
+ /*
+ * Allocate plenty of space for the new array of arg-pointers,
+ * and start that array off with the first element of the old
+ * array.
+ */
+ newargv = malloc((*origc + (strlen(str) / 2) + 2) * sizeof(char *));
+ nextarg = newargv;
+ *nextarg++ = **origv;
+
+ /* Come up with the new args by splitting up the given string. */
+ addcount = 0;
+ bq_destlen = in_arg = in_dq = in_sq = 0;
+ bq_src = NULL;
+ for (src = str, dest = newstr; *src != '\0'; src++) {
+ /*
+ * This switch will look at a character in *src, and decide
+ * what should be copied to *dest. It only decides what
+ * character(s) to copy, it should not modify *dest. In some
+ * cases, it will look at multiple characters from *src.
+ */
+ copychar = found_sep = 0;
+ copystr = NULL;
+ switch (*src) {
+ case '"':
+ if (in_sq)
+ copychar = *src;
+ else if (in_dq)
+ in_dq = 0;
+ else {
+ /*
+ * Referencing nullarg ensures that a new
+ * argument is created, even if this quoted
+ * string ends up with zero characters.
+ */
+ copystr = nullarg;
+ in_dq = 1;
+ bq_destlen = dest - *(nextarg - 1);
+ bq_src = src;
+ }
+ break;
+ case '$':
+ if (in_sq)
+ copychar = *src;
+ else {
+ copystr = expand_vars(in_arg, (nextarg - 1),
+ &dest, &src);
+ }
+ break;
+ case '\'':
+ if (in_dq)
+ copychar = *src;
+ else if (in_sq)
+ in_sq = 0;
+ else {
+ /*
+ * Referencing nullarg ensures that a new
+ * argument is created, even if this quoted
+ * string ends up with zero characters.
+ */
+ copystr = nullarg;
+ in_sq = 1;
+ bq_destlen = dest - *(nextarg - 1);
+ bq_src = src;
+ }
+ break;
+ case '\\':
+ if (in_sq) {
+ /*
+ * Inside single-quoted strings, only the
+ * "\'" and "\\" are recognized as special
+ * strings.
+ */
+ copychar = *(src + 1);
+ if (copychar == '\'' || copychar == '\\')
+ src++;
+ else
+ copychar = *src;
+ break;
+ }
+ src++;
+ switch (*src) {
+ case '"':
+ case '#':
+ case '$':
+ case '\'':
+ case '\\':
+ copychar = *src;
+ break;
+ case '_':
+ /*
+ * Alternate way to get a blank, which allows
+ * that blank be used to separate arguments
+ * when it is not inside a quoted string.
+ */
+ if (in_dq)
+ copychar = ' ';
+ else {
+ found_sep = 1;
+ src++;
+ }
+ break;
+ case 'c':
+ /*
+ * Ignore remaining characters in the -S string.
+ * This would not make sense if found in the
+ * middle of a quoted string.
+ */
+ if (in_dq)
+ errx(1, "Sequence '\\%c' is not allowed"
+ " in quoted strings", *src);
+ goto str_done;
+ case 'f':
+ copychar = '\f';
+ break;
+ case 'n':
+ copychar = '\n';
+ break;
+ case 'r':
+ copychar = '\r';
+ break;
+ case 't':
+ copychar = '\t';
+ break;
+ case 'v':
+ copychar = '\v';
+ break;
+ default:
+ if (isspacech(*src))
+ copychar = *src;
+ else
+ errx(1, "Invalid sequence '\\%c' in -S",
+ *src);
+ }
+ break;
+ default:
+ if ((in_dq || in_sq) && in_arg)
+ copychar = *src;
+ else if (isspacech(*src))
+ found_sep = 1;
+ else {
+ /*
+ * If the first character of a new argument
+ * is `#', then ignore the remaining chars.
+ */
+ if (!in_arg && *src == '#')
+ goto str_done;
+ copychar = *src;
+ }
+ }
+ /*
+ * Now that the switch has determined what (if anything)
+ * needs to be copied, copy whatever that is to *dest.
+ */
+ if (copychar || copystr != NULL) {
+ if (!in_arg) {
+ /* This is the first byte of a new argument */
+ *nextarg++ = dest;
+ addcount++;
+ in_arg = 1;
+ }
+ if (copychar)
+ *dest++ = (char)copychar;
+ else if (copystr != NULL)
+ while (*copystr != '\0')
+ *dest++ = *copystr++;
+ } else if (found_sep) {
+ *dest++ = '\0';
+ while (isspacech(*src))
+ src++;
+ --src;
+ in_arg = 0;
+ }
+ }
+str_done:
+ *dest = '\0';
+ *nextarg = NULL;
+ if (in_dq || in_sq) {
+ errx(1, "No terminating quote for string: %.*s%s",
+ bq_destlen, *(nextarg - 1), bq_src);
+ }
+ if (env_verbosity > 1) {
+ fprintf(stderr, "#env split -S:\t'%s'\n", str);
+ oldarg = newargv + 1;
+ fprintf(stderr, "#env into:\t'%s'\n", *oldarg);
+ for (oldarg++; *oldarg; oldarg++)
+ fprintf(stderr, "#env &\t'%s'\n", *oldarg);
+ }
+
+ /* Copy the unprocessed arg-pointers from the original array */
+ for (oldarg = *origv + *origind; *oldarg; oldarg++)
+ *nextarg++ = *oldarg;
+ *nextarg = NULL;
+
+ /* Update optind/argc/argv in the calling routine */
+ *origind = 1;
+ *origc += addcount;
+ *origv = newargv;
+}
+
+/**
+ * Routine to split expand any environment variables referenced in the string
+ * that -S is processing. For now it only supports the form ${VARNAME}. It
+ * explicitly does not support $VARNAME, and obviously can not handle special
+ * shell-variables such as $?, $*, $1, etc. It is called with *src_p pointing
+ * at the initial '$', and if successful it will update *src_p, *dest_p, and
+ * possibly *thisarg_p in the calling routine.
+ */
+static const char *
+expand_vars(int in_thisarg, char **thisarg_p, char **dest_p, const char **src_p)
+{
+ const char *vbegin, *vend, *vvalue;
+ char *newstr, *vname;
+ int bad_reference;
+ size_t namelen, newlen;
+
+ bad_reference = 1;
+ vbegin = vend = (*src_p) + 1;
+ if (*vbegin++ == '{')
+ if (*vbegin == '_' || isalphach(*vbegin)) {
+ vend = vbegin + 1;
+ while (*vend == '_' || isalnumch(*vend))
+ vend++;
+ if (*vend == '}')
+ bad_reference = 0;
+ }
+ if (bad_reference)
+ errx(1, "Only ${VARNAME} expansion is supported, error at: %s",
+ *src_p);
+
+ /*
+ * We now know we have a valid environment variable name, so update
+ * the caller's source-pointer to the last character in that reference,
+ * and then pick up the matching value. If the variable is not found,
+ * or if it has a null value, then our work here is done.
+ */
+ *src_p = vend;
+ namelen = vend - vbegin + 1;
+ vname = malloc(namelen);
+ strlcpy(vname, vbegin, namelen);
+ vvalue = getenv(vname);
+ if (vvalue == NULL || *vvalue == '\0') {
+ if (env_verbosity > 2)
+ fprintf(stderr,
+ "#env replacing ${%s} with null string\n",
+ vname);
+ free(vname);
+ return (NULL);
+ }
+
+ if (env_verbosity > 2)
+ fprintf(stderr, "#env expanding ${%s} into '%s'\n", vname,
+ vvalue);
+
+ /*
+ * There is some value to copy to the destination. If the value is
+ * shorter than the ${VARNAME} reference that it replaces, then our
+ * caller can just copy the value to the existing destination.
+ */
+ if (strlen(vname) + 3 >= strlen(vvalue)) {
+ free(vname);
+ return (vvalue);
+ }
+
+ /*
+ * The value is longer than the string it replaces, which means the
+ * present destination area is too small to hold it. Create a new
+ * destination area, and update the caller's 'dest' variable to match.
+ * If the caller has already started copying some info for 'thisarg'
+ * into the present destination, then the new destination area must
+ * include a copy of that data, and the pointer to 'thisarg' must also
+ * be updated. Note that it is still the caller which copies this
+ * vvalue to the new *dest.
+ */
+ newlen = strlen(vvalue) + strlen(*src_p) + 1;
+ if (in_thisarg) {
+ **dest_p = '\0'; /* Provide terminator for 'thisarg' */
+ newlen += strlen(*thisarg_p);
+ newstr = malloc(newlen);
+ strcpy(newstr, *thisarg_p);
+ *thisarg_p = newstr;
+ } else {
+ newstr = malloc(newlen);
+ *newstr = '\0';
+ }
+ *dest_p = strchr(newstr, '\0');
+ free(vname);
+ return (vvalue);
+}
--- /dev/null
+/*-
+ * Copyright (c) 2005 - Garance Alistair Drosehn <gad@FreeBSD.org>.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of the FreeBSD Project.
+ *
+ * $FreeBSD$
+ */
+
+void search_paths(char *path, char **argv);
+void split_spaces(const char *str, int *origind, int *origc,
+ char ***origv);
+
+extern int env_verbosity;
.Ic -name ,
but the contents of the symbolic link are matched instead of the file
name.
+Note that this only matches broken symbolic links
+if symbolic links are being followed.
This is a GNU find extension.
.It Ic -ls
This primary always evaluates to true.
--- /dev/null
+ <dict>
+ <key>OpenSourceProject</key>
+ <string>find</string>
+ <key>OpenSourceVersion</key>
+ <string>2011-12-10</string>
+ <key>OpenSourceWebsiteURL</key>
+ <string>http://svnweb.freebsd.org/base/head/usr.bin/find/</string>
+ <key>OpenSourceSCM</key>
+ <string>svn co http://svn.freebsd.org/base/head/usr.bin/find/</string>
+ <key>OpenSourceImportDate</key>
+ <string>2012-01-06</string>
+ <key>OpenSourceModifications</key>
+ <array>
+ <string>Add -xattr and -xattrname options.</string>
+ <string>execplus conformance fixes.</string>
+ <string>Man page fix (4772561).</string>
+ <string>Adjust FTS usage due to API differences.</string>
+ <string>Avoid searching unsearchable directories (3849245).</string>
+ <string>Add missing call to fts_close (4608460).</string>
+ <string>Conformance fixes related to F_EXACTTIME.</string>
+ <string>Apple-specific implementation of -acl primary.</string>
+ <string>Conformance fix for f_name (6591280).</string>
+ <string>setmode conformance fix (3936046).</string>
+ <string>Use a saner MAXLOGNAME definition.</string>
+ <string>Workaround for lack of rpmatch function.</string>
+ <string>FreeBSD revisions 260336, 260355.</string>
+ </array>
+ <key>OpenSourceLicense</key>
+ <string>bsd</string>
+ </dict>
{
char fn[PATH_MAX];
const char *name;
+ ssize_t len;
if (plan->flags & F_LINK) {
- name = fn;
- if (readlink(entry->fts_accpath, fn, sizeof(fn)) == -1)
+ /*
+ * The below test both avoids obviously useless readlink()
+ * calls and ensures that symlinks with existent target do
+ * not match if symlinks are being followed.
+ * Assumption: fts will stat all symlinks that are to be
+ * followed and will return the stat information.
+ */
+ if (entry->fts_info != FTS_NSOK && entry->fts_info != FTS_SL &&
+ entry->fts_info != FTS_SLNONE)
return 0;
+ len = readlink(entry->fts_accpath, fn, sizeof(fn) - 1);
+ if (len == -1)
+ return 0;
+ fn[len] = '\0';
+ name = fn;
} else if (entry->fts_namelen == 0) {
name = basename(entry->fts_path);
} else
if [ "$(id -u)" = "0" ]; then
rc=0
- export FCODES=`mktemp -t updatedb`
+ export FCODES=`sudo -u nobody mktemp -t updatedb`
chown nobody $FCODES
tmpdb=`su -fm nobody -c "$0"` || rc=1
if [ $rc = 0 ]; then
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
-.\" 3. All advertising materials mentioning features or use of this software
-.\" must display the following acknowledgement:
-.\" This product includes software developed by the University of
-.\" California, Berkeley and its contributors.
.\" 4. Neither the name of the University nor the names of its contributors
.\" may be used to endorse or promote products derived from this software
.\" without specific prior written permission.
.\" SUCH DAMAGE.
.\"
.\" From: $OpenBSD: mktemp.1,v 1.8 1998/03/19 06:13:37 millert Exp $
-.\" $FreeBSD: src/usr.bin/mktemp/mktemp.1,v 1.21 2006/09/29 15:20:46 ru Exp $
+.\" $FreeBSD$
.\"
.Dd December 30, 2005
.Dt MKTEMP 1
result in
.Nm
selecting 1 of 56800235584 (62 ** 6) possible file names.
+On case-insensitive file systems, the effective number of unique
+names is significantly less; given six
+.Ql X Ns s ,
+.Nm
+will instead select 1 of 2176782336 (36 ** 6) possible unique file names.
.Pp
If
.Nm
be taken to ensure that it is appropriate to use an environment variable
potentially supplied by the user.
.Pp
+If no arguments are passed or if only the
+.Fl d
+flag is passed
+.Nm
+behaves as if
+.Fl t Li tmp
+was supplied.
+.Pp
Any number of temporary files may be created in a single invocation,
including one based on the internal template resulting from the
.Fl t
option is not encouraged.
.El
.Sh EXIT STATUS
-The
-.Nm
-utility
-exits 0 on success, and 1 if an error occurs.
+.Ex -std
.Sh EXAMPLES
The following
.Xr sh 1
*/
#include <err.h>
+#ifdef __APPLE__
+#include <limits.h>
+#endif
#include <paths.h>
#include <stdio.h>
#include <stdlib.h>
#ifndef lint
static const char rcsid[] =
- "$FreeBSD: src/usr.bin/mktemp/mktemp.c,v 1.5 2002/03/22 01:33:17 imp Exp $";
+ "$FreeBSD$";
#endif /* not lint */
static void usage(void);
const char *prefix;
char *name;
int dflag, qflag, tflag, uflag;
+#ifdef __APPLE__
+ char tmpbuf[PATH_MAX];
+ size_t len;
+#endif
ret = dflag = qflag = tflag = uflag = 0;
prefix = "mktemp";
argc -= optind;
argv += optind;
+ if (!tflag && argc < 1) {
+ tflag = 1;
+ prefix = "tmp";
+ }
+
if (tflag) {
+#ifdef __APPLE__
+ if (confstr(_CS_DARWIN_USER_TEMP_DIR, tmpbuf, sizeof(tmpbuf)) > 0) {
+ tmpdir = tmpbuf;
+ } else {
+ tmpdir = getenv("TMPDIR");
+ }
+
+ if (tmpdir == NULL) {
+ tmpdir = _PATH_TMP;
+ }
+
+ len = strlen(tmpdir);
+ if (len > 0 && tmpdir[len - 1] == '/') {
+ asprintf(&name, "%s%s.XXXXXXXX", tmpdir, prefix);
+ } else {
+ asprintf(&name, "%s/%s.XXXXXXXX", tmpdir, prefix);
+ }
+#else
tmpdir = getenv("TMPDIR");
if (tmpdir == NULL)
asprintf(&name, "%s%s.XXXXXXXX", _PATH_TMP, prefix);
- else {
- int len = strlen(tmpdir);
- if (len > 0 && tmpdir[len - 1] == '/')
- asprintf(&name, "%s%s.XXXXXXXX", tmpdir, prefix);
- else
- asprintf(&name, "%s/%s.XXXXXXXX", tmpdir, prefix);
- }
+ else
+ asprintf(&name, "%s/%s.XXXXXXXX", tmpdir, prefix);
+#endif
/* if this fails, the program is in big trouble already */
if (name == NULL) {
if (qflag)
else
errx(1, "cannot generate template");
}
- } else if (argc < 1) {
- usage();
}
/* generate all requested files */
}
static void
-usage()
+usage(void)
{
fprintf(stderr,
"usage: mktemp [-d] [-q] [-t prefix] [-u] template ...\n");
--- /dev/null
+ <dict>
+ <key>OpenSourceProject</key>
+ <string>mktemp</string>
+ <key>OpenSourceVersion</key>
+ <string>2012-11-18</string>
+ <key>OpenSourceWebsiteURL</key>
+ <string>http://svnweb.freebsd.org/base/head/usr.bin/mktemp/</string>
+ <key>OpenSourceSCM</key>
+ <string>svn co http://svn.freebsd.org/base/head/usr.bin/mktemp/</string>
+ <key>OpenSourceImportDate</key>
+ <string>2014-08-11</string>
+ <key>OpenSourceModifications</key>
+ <array>
+ <string>Avoid double-slash in output. (5334050)</string>
+ <string>Document case-insensitive file system behavior. (14280248)</string>
+ <string>Prefer _CS_DARWIN_USER_TEMP_DIR over TMPDIR and _PATH_TMP. (14338140)</string>
+ </array>
+ <key>OpenSourceLicense</key>
+ <string>bsd</string>
+ </dict>
size_t size = len + escapes + 1;
- char* segment = malloc(size);
+ char* segment = calloc(1, size);
if (segment == NULL) return NULL;
for (i = 0, j = 0; i < len; ++i, ++j) {
-.\" $NetBSD: printenv.1,v 1.5 1997/10/19 12:44:21 lukem Exp $
-.\"
.\" Copyright (c) 1980, 1990, 1993
-.\" The Regents of the University of California. All rights reserved.
+.\" The Regents of the University of California. All rights reserved.
.\"
.\" This code is derived from software contributed to Berkeley by
.\" the Institute of Electrical and Electronics Engineers, Inc.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
-.\" 3. All advertising materials mentioning features or use of this software
-.\" must display the following acknowledgement:
-.\" This product includes software developed by the University of
-.\" California, Berkeley and its contributors.
.\" 4. Neither the name of the University nor the names of its contributors
.\" may be used to endorse or promote products derived from this software
.\" without specific prior written permission.
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.\" from: @(#)printenv.1 8.1 (Berkeley) 6/6/93
-.\" $NetBSD: printenv.1,v 1.5 1997/10/19 12:44:21 lukem Exp $
+.\" @(#)printenv.1 8.1 (Berkeley) 6/6/93
+.\" $FreeBSD$
.\"
-.Dd June 6, 1993
+.Dd May 12, 2003
.Dt PRINTENV 1
.Os
.Sh NAME
.Nm
.Op Ar name
.Sh DESCRIPTION
+The
.Nm
-prints out the names and values of the variables in the environment,
-with one name/value pair per line. If
+utility prints out the names and values of the variables in the environment,
+with one name/value pair per line.
+If
.Ar name
is specified, only
its value is printed.
.Pp
-If a
-.Ar name
-is specified and it is not defined in the environment,
+Some shells may provide a builtin
.Nm
-returns exit status 1, else it returns status 0.
+command which is similar or identical to this utility.
+Consult the
+.Xr builtin 1
+manual page.
+.Sh EXIT STATUS
+.Ex -std
.Sh SEE ALSO
.Xr csh 1 ,
+.Xr env 1 ,
.Xr sh 1 ,
.Xr environ 7
+.Sh STANDARDS
+The
+.Nm
+utility is provided for compatibility with earlier
+.Bx
+and
+.Fx
+releases and is not specified by any standards.
+The functionality of
+.Nm
+can be duplicated with the
+.Xr echo 1
+and
+.Xr env 1
+utilities.
.Sh HISTORY
The
.Nm
-/* $NetBSD: printenv.c,v 1.6 1997/10/19 12:44:26 lukem Exp $ */
-
-/*
+/*-
* Copyright (c) 1987, 1993
- * Regents of the University of California. All rights reserved.
+ * The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
- * 3. All advertising materials mentioning features or use of this software
- * must display the following acknowledgement:
- * This product includes software developed by the University of
- * California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
* SUCH DAMAGE.
*/
-#include <sys/cdefs.h>
#ifndef lint
-__COPYRIGHT("@(#) Copyright (c) 1987, 1993\n\
- The Regents of the University of California. All rights reserved.\n");
+static const char copyright[] =
+"@(#) Copyright (c) 1987, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
#endif /* not lint */
+#if 0
#ifndef lint
-/*static char sccsid[] = "from: @(#)printenv.c 8.2 (Berkeley) 5/4/95";*/
-__RCSID("$NetBSD: printenv.c,v 1.6 1997/10/19 12:44:26 lukem Exp $");
+static char sccsid[] = "@(#)printenv.c 8.2 (Berkeley) 5/4/95";
#endif /* not lint */
+#endif
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
-int main __P((int, char **));
-void usage __P((void));
+void usage(void);
+extern char **environ;
/*
* printenv
* February, 1979
*/
int
-main(argc, argv)
- int argc;
- char *argv[];
+main(int argc, char *argv[])
{
- extern char **environ;
char *cp, **ep;
size_t len;
int ch;
}
void
-usage()
+usage(void)
{
(void)fprintf(stderr, "usage: printenv [name]\n");
exit(1);
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
-.\" 3. All advertising materials mentioning features or use of this software
-.\" must display the following acknowledgement:
-.\" This product includes software developed by the University of
-.\" California, Berkeley and its contributors.
.\" 4. Neither the name of the University nor the names of its contributors
.\" may be used to endorse or promote products derived from this software
.\" without specific prior written permission.
.\" SUCH DAMAGE.
.\"
.\" @(#)script.1 8.1 (Berkeley) 6/6/93
-.\" $FreeBSD: src/usr.bin/script/script.1,v 1.21 2004/07/03 00:24:43 ru Exp $
+.\" $FreeBSD$
.\"
-.Dd January 22, 2004
+.Dd December 4, 2013
.Dt SCRIPT 1
.Os
.Sh NAME
.Nd make typescript of terminal session
.Sh SYNOPSIS
.Nm
-.Op Fl akq
+.Op Fl adkpqr
+.Op Fl F Ar pipe
.Op Fl t Ar time
.Op Ar file Op Ar command ...
.Sh DESCRIPTION
or
.Pa typescript ,
retaining the prior contents.
+.It Fl d
+When playing back a session with the
+.Fl p
+flag, do not sleep between records when playing back a timestamped session.
+.It Fl F Ar pipe
+Immediately flush output after each write.
+This will allow a user to create a named pipe using
+.Xr mkfifo 1
+and another user may watch the live session using a utility like
+.Xr cat 1 .
+.\".It Fl f
+.\"Create
+.\".Ar file.filemon
+.\"or
+.\".Pa typescript.filemon
+.\"using
+.\".Xr filemon 4 .
.It Fl k
-Log keys sent to program as well as output.
+Log keys sent to the program as well as output.
+.It Fl p
+Play back a session recorded with the
+.Fl r
+flag in real time.
.It Fl q
-Run in quiet mode, omit the start and stop status messages.
+Run in quiet mode, omit the start, stop and command status messages.
+.It Fl r
+Record a session with input, output, and timestamping.
.It Fl t Ar time
-Specify time interval between flushing script output file.
+Specify the interval at which the script output file will be flushed
+to disk, in seconds.
A value of 0
causes
.Nm
-to flush for every character I/O event.
+to flush after every character I/O event.
The default interval is
30 seconds.
.El
utility works best with commands that do not manipulate the screen.
The results are meant to emulate a hardcopy terminal, not an addressable one.
.Sh ENVIRONMENT
-The following environment variable is utilized by
+The following environment variables are utilized by
.Nm :
.Bl -tag -width SHELL
+.It Ev SCRIPT
+The
+.Ev SCRIPT
+environment variable is added to the sub-shell.
+If
+.Ev SCRIPT
+already existed in the users environment,
+its value is overwritten within the sub-shell.
+The value of
+.Ev SCRIPT
+is the name of the
+.Ar typescript
+file.
.It Ev SHELL
If the variable
.Ev SHELL
.Ev SHELL
is not set, the Bourne shell
is assumed.
-(Most shells set this variable automatically).
+.Pq Most shells set this variable automatically .
.El
.Sh SEE ALSO
.Xr csh 1
-(for the
-.Em history
-mechanism).
+.\".Xr filemon 4
+.\".Po
+.\"for the
+.\".Em history
+.\"mechanism
+.\".Pc .
.Sh HISTORY
The
.Nm
command appeared in
.Bx 3.0 .
+.Pp
+The
+.Fl d ,
+.Fl p
+and
+.Fl r
+options first appeared in
+.Nx 2.0
+and were ported to
+.Fx 9.2 .
.Sh BUGS
The
.Nm
The slave terminal mode is checked
for ECHO mode to check when to avoid manual echo logging.
This does not
-work when in a raw mode where the program being run is doing manual echo.
+work when the terminal is in a raw mode where
+the program being run is doing manual echo.
+.Pp
+If
+.Nm
+reads zero bytes from the terminal, it switches to a mode when it
+only attempts to read
+once a second until there is data to read.
+This prevents
+.Nm
+from spinning on zero-byte reads, but might cause a 1-second delay in
+processing of user input.
/*
+ * Copyright (c) 2010, 2012 David E. O'Brien
* Copyright (c) 1980, 1992, 1993
* The Regents of the University of California. All rights reserved.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
- * 3. All advertising materials mentioning features or use of this software
- * must display the following acknowledgement:
- * This product includes software developed by the University of
- * California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
* SUCH DAMAGE.
*/
-#include <sys/cdefs.h>
-
-__FBSDID("$FreeBSD: src/usr.bin/script/script.c,v 1.24 2004/02/15 17:30:13 cperciva Exp $");
-
+#include <sys/param.h>
+__FBSDID("$FreeBSD$");
#ifndef lint
static const char copyright[] =
"@(#) Copyright (c) 1980, 1992, 1993\n\
The Regents of the University of California. All rights reserved.\n";
#endif
-
#ifndef lint
static const char sccsid[] = "@(#)script.c 8.1 (Berkeley) 6/6/93";
#endif
-#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/time.h>
+#include <sys/uio.h>
+#ifdef __APPLE__
+#include <libkern/OSByteOrder.h>
+#else
+#include <sys/endian.h>
+#endif
+#ifdef ENABLE_FILEMON
+#include <dev/filemon/filemon.h>
+#endif /* ENABLE_FILEMON */
#include <err.h>
#include <errno.h>
#include <fcntl.h>
-#ifndef __APPLE__
+#ifdef __APPLE__
+#include <util.h>
+#else
#include <libutil.h>
-#endif /* !__APPLE__ */
+#endif
#include <paths.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
-#ifdef __APPLE__
-#include <util.h>
-#endif /* __APPLE__ */
-FILE *fscript;
-int master, slave;
-int child;
-const char *fname;
-int qflg, ttyflg;
+#define DEF_BUF 65536
-struct termios tt;
+struct stamp {
+ uint64_t scr_len; /* amount of data */
+ uint64_t scr_sec; /* time it arrived in seconds... */
+ uint32_t scr_usec; /* ...and microseconds */
+ uint32_t scr_direction; /* 'i', 'o', etc (also indicates endianness) */
+};
-void done(int) __dead2;
-void dooutput(void);
-void doshell(char **);
-void fail(void);
-void finish(void);
+static FILE *fscript;
+static int master, slave;
+static int child;
+static const char *fname;
+static char *fmfname;
+#ifdef ENABLE_FILEMON
+static int fflg, qflg, ttyflg;
+#else /* !ENABLE_FILEMON */
+static int qflg, ttyflg;
+#endif /* ENABLE_FILEMON */
+static int usesleep, rawout;
+
+static struct termios tt;
+
+static void done(int) __dead2;
+static void doshell(char **);
+static void fail(void);
+static void finish(void);
+static void record(FILE *, char *, size_t, int);
+static void consume(FILE *, off_t, char *, int);
+static void playback(FILE *) __dead2;
static void usage(void);
int
int cc;
struct termios rtt, stt;
struct winsize win;
- int aflg, kflg, ch, n;
struct timeval tv, *tvp;
time_t tvec, start;
char obuf[BUFSIZ];
char ibuf[BUFSIZ];
fd_set rfd;
- int flushtime = 30;
+ int aflg, Fflg, kflg, pflg, ch, k, n;
+ int flushtime, readstdin;
+#ifdef ENABLE_FILEMON
+ int fm_fd, fm_log;
+#endif /* ENABLE_FILEMON */
+
+ aflg = Fflg = kflg = pflg = 0;
+ usesleep = 1;
+ rawout = 0;
+ flushtime = 30;
+#ifdef ENABLE_FILEMON
+ fm_fd = -1; /* Shut up stupid "may be used uninitialized" GCC
+ warning. (not needed w/clang) */
+#endif /* ENABLE_FILEMON */
- aflg = kflg = 0;
- while ((ch = getopt(argc, argv, "aqkt:")) != -1)
+#ifdef ENABLE_FILEMON
+ while ((ch = getopt(argc, argv, "adFfkpqrt:")) != -1)
+#else /* !ENABLE_FILEMON */
+ while ((ch = getopt(argc, argv, "adFkpqrt:")) != -1)
+#endif /* ENABLE_FILEMON */
switch(ch) {
case 'a':
aflg = 1;
break;
- case 'q':
- qflg = 1;
+ case 'd':
+ usesleep = 0;
break;
+ case 'F':
+ Fflg = 1;
+ break;
+#ifdef ENABLE_FILEMON
+ case 'f':
+ fflg = 1;
+ break;
+#endif /* ENABLE_FILEMON */
case 'k':
kflg = 1;
break;
+ case 'p':
+ pflg = 1;
+ break;
+ case 'q':
+ qflg = 1;
+ break;
+ case 'r':
+ rawout = 1;
+ break;
case 't':
flushtime = atoi(optarg);
if (flushtime < 0)
} else
fname = "typescript";
- if ((fscript = fopen(fname, aflg ? "a" : "w")) == NULL)
+ if ((fscript = fopen(fname, pflg ? "r" : aflg ? "a" : "w")) == NULL)
err(1, "%s", fname);
- if (ttyflg = isatty(STDIN_FILENO)) {
+#ifdef ENABLE_FILEMON
+ if (fflg) {
+ asprintf(&fmfname, "%s.filemon", fname);
+ if (!fmfname)
+ err(1, "%s.filemon", fname);
+ if ((fm_fd = open("/dev/filemon", O_RDWR)) == -1)
+ err(1, "open(\"/dev/filemon\", O_RDWR)");
+ if ((fm_log = open(fmfname, O_WRONLY | O_CREAT | O_TRUNC,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1)
+ err(1, "open(%s)", fmfname);
+ if (ioctl(fm_fd, FILEMON_SET_FD, &fm_log) < 0)
+ err(1, "Cannot set filemon log file descriptor");
+
+ /* Set up these two fd's to close on exec. */
+ (void)fcntl(fm_fd, F_SETFD, FD_CLOEXEC);
+ (void)fcntl(fm_log, F_SETFD, FD_CLOEXEC);
+ }
+#endif /* ENABLE_FILEMON */
+
+ if (pflg)
+ playback(fscript);
+
+ if ((ttyflg = isatty(STDIN_FILENO)) != 0) {
if (tcgetattr(STDIN_FILENO, &tt) == -1)
err(1, "tcgetattr");
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &win) == -1)
err(1, "openpty");
}
+ if (rawout)
+ record(fscript, NULL, 0, 's');
+
if (!qflg) {
tvec = time(NULL);
(void)printf("Script started, output file is %s\n", fname);
- (void)fprintf(fscript, "Script started on %s", ctime(&tvec));
+ if (!rawout) {
+ (void)fprintf(fscript, "Script started on %s",
+ ctime(&tvec));
+ if (argv[0]) {
+ fprintf(fscript, "command: ");
+ for (k = 0 ; argv[k] ; ++k)
+ fprintf(fscript, "%s%s", k ? " " : "",
+ argv[k]);
+ fprintf(fscript, "\n");
+ }
+ }
fflush(fscript);
+#ifdef ENABLE_FILEMON
+ if (fflg) {
+ (void)printf("Filemon started, output file is %s\n",
+ fmfname);
+ }
+#endif /* ENABLE_FILEMON */
}
if (ttyflg) {
rtt = tt;
}
if (child == 0)
doshell(argv);
-#ifdef __APPLE__
- (void)close(slave);
-#endif /* __APPLE__ */
+ close(slave);
- if (flushtime > 0)
- tvp = &tv;
- else
- tvp = NULL;
+#ifdef ENABLE_FILEMON
+ if (fflg && ioctl(fm_fd, FILEMON_SET_PID, &child) < 0)
+ err(1, "Cannot set filemon PID");
+#endif /* ENABLE_FILEMON */
- start = time(0);
- FD_ZERO(&rfd);
+ start = tvec = time(0);
+ readstdin = 1;
for (;;) {
+ FD_ZERO(&rfd);
FD_SET(master, &rfd);
- FD_SET(STDIN_FILENO, &rfd);
- if (flushtime > 0) {
- tv.tv_sec = flushtime;
+ if (readstdin)
+ FD_SET(STDIN_FILENO, &rfd);
+ if (!readstdin && ttyflg) {
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ tvp = &tv;
+ readstdin = 1;
+ } else if (flushtime > 0) {
+ tv.tv_sec = flushtime - (tvec - start);
tv.tv_usec = 0;
+ tvp = &tv;
+ } else {
+ tvp = NULL;
}
n = select(master + 1, &rfd, 0, 0, tvp);
if (n < 0 && errno != EINTR)
cc = read(STDIN_FILENO, ibuf, BUFSIZ);
if (cc < 0)
break;
- if (cc == 0)
- (void)write(master, ibuf, 0);
+ if (cc == 0) {
+ if (tcgetattr(master, &stt) == 0 &&
+ (stt.c_lflag & ICANON) != 0) {
+ (void)write(master, &stt.c_cc[VEOF], 1);
+ }
+ readstdin = 0;
+ }
if (cc > 0) {
+ if (rawout)
+ record(fscript, ibuf, cc, 'i');
(void)write(master, ibuf, cc);
if (kflg && tcgetattr(master, &stt) >= 0 &&
((stt.c_lflag & ECHO) == 0)) {
if (cc <= 0)
break;
(void)write(STDOUT_FILENO, obuf, cc);
- (void)fwrite(obuf, 1, cc, fscript);
+ if (rawout)
+ record(fscript, obuf, cc, 'o');
+ else
+ (void)fwrite(obuf, 1, cc, fscript);
}
tvec = time(0);
if (tvec - start >= flushtime) {
fflush(fscript);
start = tvec;
}
+ if (Fflg)
+ fflush(fscript);
}
finish();
done(0);
usage(void)
{
(void)fprintf(stderr,
- "usage: script [-akq] [-t time] [file [command ...]]\n");
+#ifdef ENABLE_FILEMON
+ "usage: script [-adfkpqr] [-t time] [file [command ...]]\n");
+#else /* !ENABLE_FILEMON */
+ "usage: script [-adkpqr] [-t time] [file [command ...]]\n");
+#endif /* ENABLE_FILEMON */
exit(1);
}
-void
+static void
finish(void)
{
- pid_t pid;
- int die, e, status;
-
- die = e = 0;
- while ((pid = wait3(&status, WNOHANG, 0)) > 0)
- if (pid == child) {
- die = 1;
- if (WIFEXITED(status))
- e = WEXITSTATUS(status);
- else if (WIFSIGNALED(status))
- e = WTERMSIG(status);
- else /* can't happen */
- e = 1;
- }
+ int e, status;
- if (die)
+ if (waitpid(child, &status, 0) == child) {
+ if (WIFEXITED(status))
+ e = WEXITSTATUS(status);
+ else if (WIFSIGNALED(status))
+ e = WTERMSIG(status);
+ else /* can't happen */
+ e = 1;
done(e);
+ }
}
-void
+static void
doshell(char **av)
{
const char *shell;
(void)close(master);
(void)fclose(fscript);
+ free(fmfname);
login_tty(slave);
+ setenv("SCRIPT", fname, 1);
if (av[0]) {
execvp(av[0], av);
warn("%s", av[0]);
fail();
}
-void
+static void
fail(void)
{
(void)kill(0, SIGTERM);
done(1);
}
-void
+static void
done(int eno)
{
time_t tvec;
if (ttyflg)
(void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &tt);
tvec = time(NULL);
+ if (rawout)
+ record(fscript, NULL, 0, 'e');
if (!qflg) {
- (void)fprintf(fscript,"\nScript done on %s", ctime(&tvec));
+ if (!rawout)
+ (void)fprintf(fscript,"\nScript done on %s",
+ ctime(&tvec));
(void)printf("\nScript done, output file is %s\n", fname);
+#ifdef ENABLE_FILEMON
+ if (fflg) {
+ (void)printf("Filemon done, output file is %s\n",
+ fmfname);
+ }
+#endif /* ENABLE_FILEMON */
}
(void)fclose(fscript);
(void)close(master);
exit(eno);
}
+
+static void
+record(FILE *fp, char *buf, size_t cc, int direction)
+{
+ struct iovec iov[2];
+ struct stamp stamp;
+ struct timeval tv;
+
+ (void)gettimeofday(&tv, NULL);
+ stamp.scr_len = cc;
+ stamp.scr_sec = tv.tv_sec;
+ stamp.scr_usec = tv.tv_usec;
+ stamp.scr_direction = direction;
+ iov[0].iov_len = sizeof(stamp);
+ iov[0].iov_base = &stamp;
+ iov[1].iov_len = cc;
+ iov[1].iov_base = buf;
+ if (writev(fileno(fp), &iov[0], 2) == -1)
+ err(1, "writev");
+}
+
+static void
+consume(FILE *fp, off_t len, char *buf, int reg)
+{
+ size_t l;
+
+ if (reg) {
+ if (fseeko(fp, len, SEEK_CUR) == -1)
+ err(1, NULL);
+ }
+ else {
+ while (len > 0) {
+ l = MIN(DEF_BUF, len);
+ if (fread(buf, sizeof(char), l, fp) != l)
+ err(1, "cannot read buffer");
+ len -= l;
+ }
+ }
+}
+
+#ifdef __APPLE__
+#define bswap32 OSSwapInt32
+#define bswap64 OSSwapInt64
+#endif /* __APPLE__ */
+#define swapstamp(stamp) do { \
+ if (stamp.scr_direction > 0xff) { \
+ stamp.scr_len = bswap64(stamp.scr_len); \
+ stamp.scr_sec = bswap64(stamp.scr_sec); \
+ stamp.scr_usec = bswap32(stamp.scr_usec); \
+ stamp.scr_direction = bswap32(stamp.scr_direction); \
+ } \
+} while (0/*CONSTCOND*/)
+
+static void
+playback(FILE *fp)
+{
+ struct timespec tsi, tso;
+ struct stamp stamp;
+ struct stat pst;
+ char buf[DEF_BUF];
+ off_t nread, save_len;
+ size_t l;
+ time_t tclock;
+ int reg;
+
+ if (fstat(fileno(fp), &pst) == -1)
+ err(1, "fstat failed");
+
+ reg = S_ISREG(pst.st_mode);
+
+ for (nread = 0; !reg || nread < pst.st_size; nread += save_len) {
+ if (fread(&stamp, sizeof(stamp), 1, fp) != 1) {
+ if (reg)
+ err(1, "reading playback header");
+ else
+ break;
+ }
+ swapstamp(stamp);
+ save_len = sizeof(stamp);
+
+ if (reg && stamp.scr_len >
+ (uint64_t)(pst.st_size - save_len) - nread)
+ errx(1, "invalid stamp");
+
+ save_len += stamp.scr_len;
+ tclock = stamp.scr_sec;
+ tso.tv_sec = stamp.scr_sec;
+ tso.tv_nsec = stamp.scr_usec * 1000;
+
+ switch (stamp.scr_direction) {
+ case 's':
+ if (!qflg)
+ (void)printf("Script started on %s",
+ ctime(&tclock));
+ tsi = tso;
+ (void)consume(fp, stamp.scr_len, buf, reg);
+ break;
+ case 'e':
+ if (!qflg)
+ (void)printf("\nScript done on %s",
+ ctime(&tclock));
+ (void)consume(fp, stamp.scr_len, buf, reg);
+ break;
+ case 'i':
+ /* throw input away */
+ (void)consume(fp, stamp.scr_len, buf, reg);
+ break;
+ case 'o':
+ tsi.tv_sec = tso.tv_sec - tsi.tv_sec;
+ tsi.tv_nsec = tso.tv_nsec - tsi.tv_nsec;
+ if (tsi.tv_nsec < 0) {
+ tsi.tv_sec -= 1;
+ tsi.tv_nsec += 1000000000;
+ }
+ if (usesleep)
+ (void)nanosleep(&tsi, NULL);
+ tsi = tso;
+ while (stamp.scr_len > 0) {
+ l = MIN(DEF_BUF, stamp.scr_len);
+ if (fread(buf, sizeof(char), l, fp) != l)
+ err(1, "cannot read buffer");
+
+ (void)write(STDOUT_FILENO, buf, l);
+ stamp.scr_len -= l;
+ }
+ break;
+ default:
+ errx(1, "invalid direction");
+ }
+ }
+ (void)fclose(fp);
+ exit(0);
+}
--- /dev/null
+ <dict>
+ <key>OpenSourceProject</key>
+ <string>script</string>
+ <key>OpenSourceVersion</key>
+ <string>2014-01-17</string>
+ <key>OpenSourceWebsiteURL</key>
+ <string>http://svnweb.freebsd.org/base/head/usr.bin/script/</string>
+ <key>OpenSourceSCM</key>
+ <string>svn co http://svn.freebsd.org/base/head/usr.bin/script/</string>
+ <key>OpenSourceImportDate</key>
+ <string>2014-09-22</string>
+ <key>OpenSourceModifications</key>
+ <array>
+ <string>Use OSByteOrder swap functions.</string>
+ <string>Include util.h instead of libutil.h.</string>
+ <string>Disable -f (filemon) feature, guarded with ENABLE_FILEMON.</string>
+ </array>
+ <key>OpenSourceLicense</key>
+ <string>bsd</string>
+ </dict>
FCE30EE014B536F200CC0294 /* locate.code.c in Sources */ = {isa = PBXBuildFile; fileRef = FCBA13A114A141A300AA698B /* locate.code.c */; };
FCE30EE114B536F800CC0294 /* locate.code.8 in CopyFiles */ = {isa = PBXBuildFile; fileRef = FCBA13B114A141A300AA698B /* locate.code.8 */; };
FCED3AF614B4FC1800C313C3 /* libpam.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = FCED3AF514B4FC1800C313C3 /* libpam.dylib */; };
+ FD88EB03198C5257006B7EFD /* envopts.c in Sources */ = {isa = PBXBuildFile; fileRef = FD88EB01198C5257006B7EFD /* envopts.c */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
FCE30ED214B5368A00CC0294 /* locate.bigram */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = locate.bigram; sourceTree = BUILT_PRODUCTS_DIR; };
FCE30EDE14B536C900CC0294 /* locate.code */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = locate.code; sourceTree = BUILT_PRODUCTS_DIR; };
FCED3AF514B4FC1800C313C3 /* libpam.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libpam.dylib; path = /usr/lib/libpam.dylib; sourceTree = "<absolute>"; };
+ FD88EB01198C5257006B7EFD /* envopts.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = envopts.c; sourceTree = "<group>"; };
+ FD88EB02198C5257006B7EFD /* envopts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = envopts.h; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
children = (
FCBA136214A141A300AA698B /* env.1 */,
FCBA136314A141A300AA698B /* env.c */,
+ FD88EB01198C5257006B7EFD /* envopts.c */,
+ FD88EB02198C5257006B7EFD /* envopts.h */,
);
path = env;
sourceTree = "<group>";
buildActionMask = 2147483647;
files = (
FCBA14FC14A144E500AA698B /* env.c in Sources */,
+ FD88EB03198C5257006B7EFD /* envopts.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
.Sh SYNOPSIS\r
.Nm\r
.Op Fl P\r
+.Op Fl r\r
+.Op Fl t Ar sleep_time\r
.Ar utility Op Ar argument ...\r
.Nm\r
.Fl p\r
+.Op Fl r\r
+.Op Fl t Ar sleep_time\r
.Nm\r
.Op Fl P\r
.Fl u Ar user\r
.Fl s Ar sys\r
.Fl i Ar idle\r
+.Nm\r
+.Op Fl P\r
+.Op Fl r\r
+.Op Fl t Ar sleep_time\r
+.Fl T Ar target_pid\r
.Sh DESCRIPTION\r
.Nm\r
is similar to\r
18.79 real 2.03 user 1.04 sys\r
.Ed\r
.Pp\r
+The\r
+.Nm\r
+program can also be used to print the usage of a process with\r
+.Fl T Ar target_pid\r
+, also printing the system wide usage\r
+.Pp\r
.Fl P\r
can be used to print time spent as a percentage of overall CPU capacity\r
of the system (capped at 100%)\r
+.Pp\r
+.Fl r\r
+can be used to print the usage iteratively with\r
+.Ar sleep_time\r
+seconds sleep between iterations\r
+.Pp\r
+.Fl t Ar sleep_time\r
+can be used to specify sleep_time between iterations, defaults to 1 second if not specified.\r
.Sh SEE ALSO\r
.Xr time 1 ,\r
.Xr top 1\r
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
+#include <libproc.h>
+#include <mach/mach_time.h>
static void usage(void);
static void do_print(void);
-static void do_difftime(bool usepercent, uint64_t olduser, uint64_t oldsystem, uint64_t oldidle);
+static void do_difftime(bool usepercent, uint64_t *olduser, uint64_t *oldsystem, uint64_t *oldidle);
+static void do_piddifftime(bool userpercent, int pid, uint64_t *old_pid_user, uint64_t *old_pid_system, uint64_t *old_pid_time);
static kern_return_t get_processor_time(uint64_t *user, uint64_t *sys, uint64_t *idle);
static kern_return_t get_processor_count(int *ncpu);
+static mach_timebase_info_data_t timebase_info;
int
main(int argc, char *argv[])
const char *optu = NULL;
const char *opts = NULL;
const char *opti = NULL;
+ const char *tpid = NULL;
+ const char *opt_sleep_time = NULL;
+ int sleep_time = 1;
+ int target_pid;
+ bool systemwide_time = false;
int pid;
int status;
uint64_t olduser, oldsystem, oldidle;
+ uint64_t old_pid_time;
+ uint64_t old_pid_user, old_pid_system;
kern_return_t kret;
bool usepercent = false;
+ bool recurring = false;
- while ((ch = getopt(argc, argv, "Ppu:s:i:")) != -1) {
+ while ((ch = getopt(argc, argv, "PrT:t:pu:s:i:")) != -1) {
switch (ch) {
case 'P':
usepercent = true;
break;
+ case 'r':
+ recurring = true;
+ break;
+ case 't':
+ opt_sleep_time = optarg;
+ break;
+ case 'T':
+ tpid = optarg;
+ break;
case 'p':
- do_print();
- exit(0);
+ systemwide_time = true;
break;
case 'u':
optu = optarg;
}
}
+ mach_timebase_info(&timebase_info);
+
+ if (opt_sleep_time) {
+ char *endstr;
+ sleep_time = (int)strtoul(opt_sleep_time, &endstr, 0);
+ if (opt_sleep_time[0] == '\0' || endstr[0] != '\0')
+ usage();
+ }
+
+ if (systemwide_time) {
+ bool first_pass = true;
+ olduser = oldsystem = oldidle = 0;
+
+ if (recurring != true) {
+ do_print();
+ exit(0);
+ }
+ do {
+ if (first_pass) {
+ do_difftime(false, &olduser, &oldsystem, &oldidle);
+ first_pass = false;
+ } else {
+ do_difftime(usepercent, &olduser, &oldsystem, &oldidle);
+ }
+ sleep(sleep_time);
+ } while (recurring);
+
+ exit(0);
+ }
+
+
+ if (tpid) {
+ char *endstr;
+ bool first_pass = true;
+
+ target_pid = (int)strtoul(tpid, &endstr, 0);
+ if (tpid[0] == '\0' || endstr[0] != '\0')
+ usage();
+
+ olduser = oldsystem = oldidle = 0;
+ old_pid_user = old_pid_system = old_pid_time = 0;
+
+ do {
+ if (first_pass) {
+ do_difftime(false, &olduser, &oldsystem, &oldidle);
+ do_piddifftime(false, target_pid, &old_pid_user, &old_pid_system, &old_pid_time);
+ first_pass = false;
+ } else {
+ do_difftime(usepercent, &olduser, &oldsystem, &oldidle);
+ do_piddifftime(usepercent, target_pid, &old_pid_user, &old_pid_system, &old_pid_time);
+ }
+
+ sleep(sleep_time);
+ } while (recurring);
+ exit(0);
+ }
+
if (optu || opts || opti) {
char *endstr;
if (opti[0] == '\0' || endstr[0] != '\0')
usage();
- do_difftime(usepercent, olduser, oldsystem, oldidle);
+ do_difftime(usepercent, &olduser, &oldsystem, &oldidle);
exit(0);
}
if (argc == 0)
usage();
- kret = get_processor_time(&olduser, &oldsystem, &oldidle);
- if (kret)
- errx(1, "Error getting processor time: %s (%d)", mach_error_string(kret), kret);
+ do {
+ kret = get_processor_time(&olduser, &oldsystem, &oldidle);
+ if (kret)
+ errx(1, "Error getting processor time: %s (%d)", mach_error_string(kret), kret);
+
+ switch(pid = vfork()) {
+ case -1: /* error */
+ perror("time");
+ exit(1);
+ /* NOTREACHED */
+ case 0: /* child */
+ execvp(*argv, argv);
+ perror(*argv);
+ _exit((errno == ENOENT) ? 127 : 126);
+ /* NOTREACHED */
+ }
- switch(pid = vfork()) {
- case -1: /* error */
- perror("time");
- exit(1);
- /* NOTREACHED */
- case 0: /* child */
- execvp(*argv, argv);
- perror(*argv);
- _exit((errno == ENOENT) ? 127 : 126);
- /* NOTREACHED */
- }
+ /* parent */
+ (void)signal(SIGINT, SIG_IGN);
+ (void)signal(SIGQUIT, SIG_IGN);
+ while (wait(&status) != pid);
+ (void)signal(SIGINT, SIG_DFL);
+ (void)signal(SIGQUIT, SIG_DFL);
- /* parent */
- (void)signal(SIGINT, SIG_IGN);
- (void)signal(SIGQUIT, SIG_IGN);
- while (wait(&status) != pid);
+ do_difftime(usepercent, &olduser, &oldsystem, &oldidle);
- do_difftime(usepercent, olduser, oldsystem, oldidle);
+ sleep(sleep_time);
+ } while (recurring);
exit (WIFEXITED(status) ? WEXITSTATUS(status) : EXIT_FAILURE);
static void
usage(void)
{
- fprintf(stderr, "usage: systime [-P] utility [argument ...]\n"
- " systime -p\n"
- " systime [-P] -u user -s sys -i idle\n");
+ fprintf(stderr, "usage: systime [-P] [-r] [-t sleep_time] utility [argument ...]\n"
+ " systime -p [-r] [-t sleep_time]\n"
+ " systime [-P] -u user -s sys -i idle\n"
+ " systime [-P] [-r] [-t sleep_time] -T target_pid\n");
exit(1);
}
}
static void
-do_difftime(bool usepercent, uint64_t olduser, uint64_t oldsystem, uint64_t oldidle)
+do_difftime(bool usepercent, uint64_t *olduser, uint64_t *oldsystem, uint64_t *oldidle)
{
uint64_t user, system, idle;
uint64_t userelapsed, systemelapsed, idleelapsed, totalelapsed;
if (kret)
errx(1, "Error getting processor time: %s (%d)", mach_error_string(kret), kret);
- userelapsed = user - olduser;
- systemelapsed = system - oldsystem;
- idleelapsed = idle - oldidle;
+ userelapsed = user - *olduser;
+ systemelapsed = system - *oldsystem;
+ idleelapsed = idle - *oldidle;
totalelapsed = userelapsed + systemelapsed + idleelapsed;
if (usepercent) {
((double)userelapsed) / 1000,
((double)systemelapsed) / 1000);
}
+ *olduser = user;
+ *oldsystem = system;
+ *oldidle = idle;
+}
+
+static void
+do_piddifftime(bool usepercent, int pid, uint64_t *old_pid_user, uint64_t *old_pid_system, uint64_t *old_pid_time)
+{
+ uint64_t pid_user, pid_system, pid_time;
+ uint64_t userelapsed, systemelapsed, totalelapsed;
+ struct proc_taskinfo info;
+ int size;
+
+ size = proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &info, sizeof(info));
+ if (size == PROC_PIDTASKINFO_SIZE) {
+ pid_user = info.pti_total_user;
+ pid_system = info.pti_total_system;
+ } else {
+ fprintf(stderr, "Error in proc_pidinfo(): %s",
+ strerror(errno));
+ exit(1);
+ }
+
+ pid_time = mach_absolute_time();
+
+ userelapsed = pid_user - *old_pid_user;
+ systemelapsed = pid_system - *old_pid_system;
+ totalelapsed = pid_time - *old_pid_time;
+
+ if (usepercent) {
+ fprintf(stderr, "Pid %d: %1.02f%% user %1.02f%% sys\n",
+ pid,
+ ((double)userelapsed * 100)/totalelapsed,
+ ((double)systemelapsed * 100)/totalelapsed);
+ } else {
+ fprintf(stderr, "Pid %d: %1.02f user %1.02f sys\n",
+ pid,
+ (((double)userelapsed) * timebase_info.numer / timebase_info.denom) / 1000000000,
+ (((double)systemelapsed) * timebase_info.numer / timebase_info.denom) / 1000000000);
+ }
+
+ *old_pid_user = pid_user;
+ *old_pid_system = pid_system;
+ *old_pid_time = pid_time;
+
}
static kern_return_t