~sircmpwn/ctools

ccc1d877f5242b0efece4ef4ccab83f37e4bb222 — Christopher Vittal 1 year, 28 days ago 16cab05
Implement env
8 files changed, 186 insertions(+), 1 deletions(-)

M STATUS
M doc/ctools.7.scd
A doc/env.1.scd
M doc/meson.build
M meson.build
A src/env.c
A test/env
M test/meson.build
M STATUS => STATUS +1 -1
@@ 43,7 43,7 @@ T       diff
T       du
T       echo
T       ed*
T       env
  D     env
    W   ex
T       expand
T       expr

M doc/ctools.7.scd => doc/ctools.7.scd +2 -0
@@ 37,6 37,8 @@ shell environment. These tools are used for tasks such as:
:  Compare two sorted files
|  *dirname*(1)
:  Print the directory part of a pathname
|  *env*(1)
:  Run command with a specified environment
|  *false*(1)
:  Exit with status code 1
|  *head*(1)

A doc/env.1.scd => doc/env.1.scd +44 -0
@@ 0,0 1,44 @@
env(1) "ctools"

# NAME

env - set the environment to run a program

# SYNOPSIS

*env* [-i] [_name_=_value_]... [_utility_ [_argument_...]]

# DESCRIPTION

*env* will update the environment based on its arguments, then invoke
_utility_, passing _arguments_ to it.

If _utility_ is not provided, the resulting environment is written to standard
output with one _name_=_value_ pair per line.

# OPTIONS

*-i*
	Clear the inherited environment before setting new variables and invoking
	_utility_.

# UNSPECIFIED BEHAVIOR

The POSIX standard does not unambiguously specify the behavior of this command
under certain conditions. Under such conditions, the ctools implementation of
*env* behaves as follows:

- If the first argument is *'-'*, *env* will exit with an error.

# NOTES

The ctools implementation of *env* uses _execvp_ to execute _utility_. Should
PATH be unset, _execvp_ may search some default path set by the system and/or
C library.

# DISCLAIMER

This command is part of ctools and is compatible with POSIX-1.2017, and may
optionally support XSI extensions. This man page is not intended to be a
complete reference, and where it disagrees with the specification, the
specification takes precedence.

M doc/meson.build => doc/meson.build +1 -0
@@ 13,6 13,7 @@ man_files = [
	'cmp.1',
	'comm.1',
	'dirname.1',
	'env.1',
	'false.1',
	'head.1',
	'link.1',

M meson.build => meson.build +1 -0
@@ 20,6 20,7 @@ oneshots = [
	'cmp',
	'comm',
	'dirname',
	'env',
	'false',
	'head',
	'logname',

A src/env.c => src/env.c +78 -0
@@ 0,0 1,78 @@
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

extern char **environ;

static void
usage(void)
{
	fputs("env [-i] [name=value]... [utility [argument...]]\n", stderr);
}

int
main(int argc, char *argv[])
{
	if (argc > 1 && strcmp(argv[1], "-") == 0) {
		usage();
		return 1;
	}

	char opt;
	bool clear = false;
	while ((opt = getopt(argc, argv, "i")) != -1) {
		switch (opt) {
		case 'i':
			clear = true;
			break;
		default:
			usage();
			return 1;
		}
	}

	if (clear) {
		environ = NULL;
	}

	argc = argc - optind;
	argv = &argv[optind];

	int cmd_idx = 0;
	for (; cmd_idx < argc; cmd_idx++) {
		if (!strchr(argv[cmd_idx], '=')) {
			break;
		}
	}

	for (int i = 0; i < cmd_idx; i++) {
		if (putenv(argv[i]) != 0) {
			perror("env");
			return 1;
		}
	}

	/* No command, print the env */
	if (cmd_idx == argc) {
		for (int i = 0; environ[i]; i++) {
			puts(environ[i]);
		}
		return 0;
	}

	argv = &argv[cmd_idx];

	execvp(argv[0], argv);

	/* if exec returns, it's errored */
	fprintf(stderr, "env: '%s': %s\n", argv[0], strerror(errno));
	switch (errno) {
	case ENOENT:
		return 127;
	default:
		return 126;
	}
}

A test/env => test/env +58 -0
@@ 0,0 1,58 @@
#!/bin/sh
tool=env
. "$HARNESS"

export FOO=bar
export BAZ=quux

echo ls > "$TMPDIR"/test-script

should_clear_env() (
	ev="$(env -i)"
	[ -z "$ev" ]
)

should_print_env() (
	ev="$(env | grep '^FOO')"
	[ "$ev" = "FOO=bar" ]
	ev="$(env | grep '^BAZ')"
	[ "$ev" = "BAZ=quux" ]
)

should_invoke_cmd() (
	ev="$(env basename a/b/c.d)"
	[ "$ev" = "c.d" ]
)

should_invoke_cmd_clean_env() (
	ev=$(env -i BAR=foo env)
	[ "$ev" = "BAR=foo" ]
)

should_invoke_cmd_with_options() (
	ev=$(largefile | env -i PATH="$PATH" head -n 15)
	largefile_head=$(largefile | head -n 15)
	[ "$ev" = "$largefile_head" ]
)

should_fail_127() (
	ev="$(env program-does-not-exist 2> /dev/null)"
	[ $? = 127 ] && [ -z "$ev" ]
)

should_fail_126() (
	ev="$(env "$TMPDIR"/test-script 2> /dev/null)"
	[ $? = 126 ] && [ -z "$ev" ]
)

should_handle_ddash env cat /dev/null

runtests \
	should_handle_ddash \
	should_clear_env \
	should_print_env \
	should_invoke_cmd \
	should_invoke_cmd_clean_env \
	should_invoke_cmd_with_options \
	should_fail_127 \
	should_fail_126

M test/meson.build => test/meson.build +1 -0
@@ 10,6 10,7 @@ test_files = [
	'cmp',
	'comm',
	'dirname',
	'env',
	'false',
	'head',
	'logname',