~sircmpwn/ctools

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
4 2

[PATCH] Implement env

Details
Message ID
<20190918050210.10992-1-christopher.vittal@gmail.com>
DKIM signature
missing
Download raw message
Patch: +186 -1
---
This is my implementation of the env command. There is still currently
one GNU-ism 'getopt(argc, argv, "+i")'. I couldn't figure out how to
process the args in the way I needed without using the '+' to tell glibc
to process the args in a POSIX compliant way.

 STATUS           |  2 +-
 doc/ctools.7.scd |  2 ++
 doc/env.1.scd    | 44 +++++++++++++++++++++++++++
 doc/meson.build  |  1 +
 meson.build      |  1 +
 src/env.c        | 78 ++++++++++++++++++++++++++++++++++++++++++++++++
 test/env         | 58 +++++++++++++++++++++++++++++++++++
 test/meson.build |  1 +
 8 files changed, 186 insertions(+), 1 deletion(-)
 create mode 100644 doc/env.1.scd
 create mode 100644 src/env.c
 create mode 100644 test/env

diff --git a/STATUS b/STATUS
index 2ceb6c3..0823c9f 100644
--- a/STATUS
+++ b/STATUS
@@ -43,7 +43,7 @@ T       dirname
T       du
T       echo
T       ed*
T       env
  D     env
    W   ex
T       expand
T       expr
diff --git a/doc/ctools.7.scd b/doc/ctools.7.scd
index 01bdcb2..ee5e1ca 100644
--- a/doc/ctools.7.scd
+++ b/doc/ctools.7.scd
@@ -35,6 +35,8 @@ shell environment. These tools are used for tasks such as:
:  Compare two files
|  *comm*(1)
:  Compare two sorted files
|  *env*(1)
:  Run command with a specified environment
|  *false*(1)
:  Exit with status code 1
|  *head*(1)
diff --git a/doc/env.1.scd b/doc/env.1.scd
new file mode 100644
index 0000000..bb6b41a
--- /dev/null
+++ b/doc/env.1.scd
@@ -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.
diff --git a/doc/meson.build b/doc/meson.build
index 4b9bf8c..fd1633c 100644
--- a/doc/meson.build
+++ b/doc/meson.build
@@ -12,6 +12,7 @@ man_files = [
	'cksum.1',
	'cmp.1',
	'comm.1',
	'env.1',
	'false.1',
	'head.1',
	'link.1',
diff --git a/meson.build b/meson.build
index 80100fe..3dcbec3 100644
--- a/meson.build
+++ b/meson.build
@@ -19,6 +19,7 @@ oneshots = [
	'cksum',
	'cmp',
	'comm',
	'env',
	'false',
	'head',
	'logname',
diff --git a/src/env.c b/src/env.c
new file mode 100644
index 0000000..fabc9d8
--- /dev/null
+++ b/src/env.c
@@ -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;
	}
}
diff --git a/test/env b/test/env
new file mode 100644
index 0000000..05e40c1
--- /dev/null
+++ b/test/env
@@ -0,0 +1,58 @@
#!/bin/sh

. "$HARNESS" env

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=$(env -i PATH="$PATH" uname -a)
	uname=$(uname -a)
	[ "$ev" = "$uname" ]
)

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
diff --git a/test/meson.build b/test/meson.build
index b7b4baa..8851e06 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -9,6 +9,7 @@ test_files = [
	'cksum',
	'cmp',
	'comm',
	'env',
	'false',
	'head',
	'logname',
--
2.23.0
Details
Message ID
<BX50V7OLQ4P9.2GOZ7G6VAO0AN@koishi>
In-Reply-To
<20190918050210.10992-1-christopher.vittal@gmail.com> (view parent)
DKIM signature
missing
Download raw message
On Wed Sep 18, 2019 at 1:02 AM Christopher Vittal wrote:
> This is my implementation of the env command. There is still currently
> one GNU-ism 'getopt(argc, argv, "+i")'. I couldn't figure out how to
> process the args in the way I needed without using the '+' to tell glibc
> to process the args in a POSIX compliant way.

Hm, this isn't acceptable. We assume a POSIX environment, and this use
of getopt will actually *break* in a POSIX environment. I think we just
need to tell glibc users to set POSIXLY_CORRECT in their environment to
make glibc do the right goddamn thing for once.

[PATCH v2] Implement env

Details
Message ID
<20190920214121.28771-1-christopher.vittal@gmail.com>
In-Reply-To
<20190918050210.10992-1-christopher.vittal@gmail.com> (view parent)
DKIM signature
missing
Download raw message
Patch: +185 -1
---
v1 -> v2:
    Removed GNU-ism

 STATUS           |  2 +-
 doc/ctools.7.scd |  2 ++
 doc/env.1.scd    | 44 +++++++++++++++++++++++++++
 doc/meson.build  |  1 +
 meson.build      |  1 +
 src/env.c        | 78 ++++++++++++++++++++++++++++++++++++++++++++++++
 test/env         | 57 +++++++++++++++++++++++++++++++++++
 test/meson.build |  1 +
 8 files changed, 185 insertions(+), 1 deletion(-)
 create mode 100644 doc/env.1.scd
 create mode 100644 src/env.c
 create mode 100755 test/env

diff --git a/STATUS b/STATUS
index 7559016..a860565 100644
--- a/STATUS
+++ b/STATUS
@@ -43,7 +43,7 @@ T       diff
T       du
T       echo
T       ed*
T       env
  D     env
    W   ex
T       expand
T       expr
diff --git a/doc/ctools.7.scd b/doc/ctools.7.scd
index 0700d69..726ae48 100644
--- a/doc/ctools.7.scd
+++ b/doc/ctools.7.scd
@@ -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)
diff --git a/doc/env.1.scd b/doc/env.1.scd
new file mode 100644
index 0000000..bb6b41a
--- /dev/null
+++ b/doc/env.1.scd
@@ -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.
diff --git a/doc/meson.build b/doc/meson.build
index 1d4c346..48097c9 100644
--- a/doc/meson.build
+++ b/doc/meson.build
@@ -13,6 +13,7 @@ man_files = [
	'cmp.1',
	'comm.1',
	'dirname.1',
	'env.1',
	'false.1',
	'head.1',
	'link.1',
diff --git a/meson.build b/meson.build
index c0c37d5..6d8d2ce 100644
--- a/meson.build
+++ b/meson.build
@@ -20,6 +20,7 @@ oneshots = [
	'cmp',
	'comm',
	'dirname',
	'env',
	'false',
	'head',
	'logname',
diff --git a/src/env.c b/src/env.c
new file mode 100644
index 0000000..db86b97
--- /dev/null
+++ b/src/env.c
@@ -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;
	}
}
diff --git a/test/env b/test/env
new file mode 100755
index 0000000..6e8823b
--- /dev/null
+++ b/test/env
@@ -0,0 +1,57 @@
#!/bin/sh
. "$HARNESS" env

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=$(env -i PATH="$PATH" uname -a)
	uname=$(uname -a)
	[ "$ev" = "$uname" ]
)

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
diff --git a/test/meson.build b/test/meson.build
index eeb57d7..10f9288 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -10,6 +10,7 @@ test_files = [
	'cmp',
	'comm',
	'dirname',
	'env',
	'false',
	'head',
	'logname',
--
2.23.0

Re: [PATCH v2] Implement env

Details
Message ID
<BX94HKV53VL5.5YUSTP57QSD3@homura>
In-Reply-To
<20190920214121.28771-1-christopher.vittal@gmail.com> (view parent)
DKIM signature
missing
Download raw message
Tests fail:

should_invoke_cmd_with_options FAIL

Otherwise this looks good.

[PATCH v3] Implement env

Details
Message ID
<20190925162139.9009-1-christopher.vittal@gmail.com>
In-Reply-To
<20190918050210.10992-1-christopher.vittal@gmail.com> (view parent)
DKIM signature
missing
Download raw message
Patch: +186 -1
---
v2 -> v3:
    Changed test to get around potential environment consequences in the
    uname utility. Now we just use head instead.

 STATUS           |  2 +-
 doc/ctools.7.scd |  2 ++
 doc/env.1.scd    | 44 +++++++++++++++++++++++++++
 doc/meson.build  |  1 +
 meson.build      |  1 +
 src/env.c        | 78 ++++++++++++++++++++++++++++++++++++++++++++++++
 test/env         | 58 +++++++++++++++++++++++++++++++++++
 test/meson.build |  1 +
 8 files changed, 186 insertions(+), 1 deletion(-)
 create mode 100644 doc/env.1.scd
 create mode 100644 src/env.c
 create mode 100755 test/env

diff --git a/STATUS b/STATUS
index 7559016..a860565 100644
--- a/STATUS
+++ b/STATUS
@@ -43,7 +43,7 @@ T       diff
T       du
T       echo
T       ed*
T       env
  D     env
    W   ex
T       expand
T       expr
diff --git a/doc/ctools.7.scd b/doc/ctools.7.scd
index 0700d69..726ae48 100644
--- a/doc/ctools.7.scd
+++ b/doc/ctools.7.scd
@@ -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)
diff --git a/doc/env.1.scd b/doc/env.1.scd
new file mode 100644
index 0000000..bb6b41a
--- /dev/null
+++ b/doc/env.1.scd
@@ -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.
diff --git a/doc/meson.build b/doc/meson.build
index 1d4c346..48097c9 100644
--- a/doc/meson.build
+++ b/doc/meson.build
@@ -13,6 +13,7 @@ man_files = [
	'cmp.1',
	'comm.1',
	'dirname.1',
	'env.1',
	'false.1',
	'head.1',
	'link.1',
diff --git a/meson.build b/meson.build
index c0c37d5..6d8d2ce 100644
--- a/meson.build
+++ b/meson.build
@@ -20,6 +20,7 @@ oneshots = [
	'cmp',
	'comm',
	'dirname',
	'env',
	'false',
	'head',
	'logname',
diff --git a/src/env.c b/src/env.c
new file mode 100644
index 0000000..db86b97
--- /dev/null
+++ b/src/env.c
@@ -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;
	}
}
diff --git a/test/env b/test/env
new file mode 100755
index 0000000..7ee2ef0
--- /dev/null
+++ b/test/env
@@ -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
diff --git a/test/meson.build b/test/meson.build
index eeb57d7..10f9288 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -10,6 +10,7 @@ test_files = [
	'cmp',
	'comm',
	'dirname',
	'env',
	'false',
	'head',
	'logname',
--
2.20.1
Review patch Export thread (mbox)