diff options
| author | Daniel Friesel <derf@derf.homelinux.org> | 2009-11-02 21:21:10 +0100 | 
|---|---|---|
| committer | Daniel Friesel <derf@derf.homelinux.org> | 2009-11-02 21:23:19 +0100 | 
| commit | 20bcc774686078cf7f5c58ef058ca2458b01086d (patch) | |
| tree | d1f9e6a90285db10bf28a2abecc1ef610ef64838 | |
| parent | 3c5e9f4f828c76de5824cf7ac440aa0cda45ccaf (diff) | |
Rewrite in C, release v2.0 (from branch 'v2')2.0
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | Changelog | 7 | ||||
| -rw-r--r-- | Makefile | 33 | ||||
| -rw-r--r-- | README | 9 | ||||
| -rwxr-xr-x | bin/envify | 2 | ||||
| -rwxr-xr-x | bin/envstore | 180 | ||||
| -rw-r--r-- | man/1/envify | 11 | ||||
| -rw-r--r-- | man/1/envify.pod | 29 | ||||
| -rw-r--r-- | man/1/envstore | 47 | ||||
| l--------- | man/1/envstore.pod | 1 | ||||
| -rw-r--r-- | provides/zsh/completions/_envstore | 8 | ||||
| -rw-r--r-- | src/envstore.c | 260 | ||||
| -rwxr-xr-x | test/main | 31 | 
13 files changed, 373 insertions, 247 deletions
| @@ -1 +1 @@ -/build/ +/bin/envstore @@ -1,3 +1,10 @@ +envstore 2.0 - Mon Nov 2 2009 + +    * Rewrite in C +    * Renamed envstore show to envstore list +    * Newlines in variable values are no longer supported +    * Introduces single-character arguments (envstore s == envstore save etc) +  envstore 1.2 - Thu Jul 9 2009      * Use Storable instead of Simplestore @@ -1,32 +1,31 @@ -prefix ?= /usr/local +CFLAGS = -Wall -Wextra -pedantic -O2 +prefix = /usr/local -manuals: build/envstore.1 build/envify.1 +all: bin/envstore -build/%.1: man/1/%.pod -	mkdir -p build -	pod2man $< > $@ +bin/%: src/%.c +	$(CC) $(CFLAGS) -o $@ $< -test: test/main -	zsh $< --extended - -install: manuals +install: bin/envstore  	mkdir -p $(prefix)/bin $(prefix)/share/man/man1  	cp bin/envstore $(prefix)/bin/envstore  	cp bin/envify $(prefix)/bin/envify -	cp build/envstore.1 $(prefix)/share/man/man1/envstore.1 -	cp build/envify.1 $(prefix)/share/man/man1/envify.1 +	cp man/1/envify $(prefix)/share/man/man1/envify.1 +	cp man/1/envstore $(prefix)/share/man/man1/envstore.1  	chmod 755 $(prefix)/bin/envstore  	chmod 755 $(prefix)/bin/envify -	chmod 644 $(prefix)/share/man/man1/envstore.1  	chmod 644 $(prefix)/share/man/man1/envify.1 +	chmod 644 $(prefix)/share/man/man1/envstore.1  uninstall: -	rm -f $(prefix)/bin/envstore -	rm -f $(prefix)/bin/envify -	rm -f $(prefix)/share/man/man1/envstore.1 +	rm -f $(prefix)/bin/envstore $(prefix)/bin/envify  	rm -f $(prefix)/share/man/man1/envify.1 +	rm -f $(prefix)/share/man/man1/envstore.1 + +test: +	zsh test/main --extended  clean: -	rm -rf build +	rm -f bin/envstore -.PHONY: install manuals test uninstall clean +.PHONY: all install uninstall test clean @@ -1,5 +1,5 @@  Requires: -  - perl >= 5.10 +C  Installation:  As user, run @@ -7,3 +7,10 @@ As user, run    make test  then, as root    make install + + + --- UPPATE NOTE --- + +When updating from envstore 1.x (perl) to 2.x (C), be sure to remove the old +store file. Otherwise, envstore may run an infinite loop while attempting to load +it. @@ -1,3 +1,3 @@  #!/bin/sh -eval `envstore eval` +eval $(envstore eval)  exec $* diff --git a/bin/envstore b/bin/envstore deleted file mode 100755 index 59d9a67..0000000 --- a/bin/envstore +++ /dev/null @@ -1,180 +0,0 @@ -#!/usr/bin/env perl -## envstore - save and load environment variables -## Copyright © 2009 by Daniel Friesel <derf@derf.homelinux.org> -## License: WTFPL <http://sam.zoy.org/wtfpl> -use strict; -use warnings; -use 5.010; -use Pod::Usage; -use Storable 'nstore', 'retrieve'; - -my $store_file = $ENV{ENVSTORE_FILE} || "/tmp/envstore-$>"; -my %store; -my $action = shift; -my $arg = shift; -my $arg2 = shift; - -sub usage { -	pod2usage( -		-message  => 'Syntax error', -		-exitval  => 1, -		-verbose  => 99, -		-sections => 'SYNOPSIS|DESCRIPTION', -		-output   => \*STDERR, -	); -	return; -} - -sub check_store { -	my ($mode, $uid); - -	if (-e $store_file) { -		($mode, $uid) = (stat($store_file))[2,4]; -	} -	else { -		return 0; -	} - -	if ($uid != $<) { -		print STDERR "envstore: store file is insecure (not owned by us)\n"; -		exit 1; -	} -	if (($mode & 0x00077) > 0) { -		print STDERR "envstore: store file is insecure (writable by group/others)\n"; -		exit 1; -	} -	return 1; -} - -sub load_store { -	if (check_store) { -		%store = %{retrieve($store_file) || {}}; -	} -	return; -} - -sub save_store { -	umask(0077); -	nstore(\%store, $store_file); -	return; -} - -sub get_keyvalue { -	my ($key, $value) = @_; -	if (not defined($value)) { -		if (exists($ENV{$key})) { -			$value = $ENV{$key}; -		} -		else { -			print STDERR "No such parameter: $key (perhaps you forgot to export it?)\n"; -			exit(1); -		} -	} -	return($key, $value); -} - -if ( -	not defined $action -	or ($action ~~ ['save', 'rm'] and not defined $arg) -) { -	usage; -} -load_store; - -given ($action) { -	when ('save') { -		my ($key, $value) = get_keyvalue($arg, $arg2); -		$store{$key} = $value; -		save_store; -	} -	when ('eval') { -		while (my ($key, $value) = each(%store)) { -			$value =~ s/'/'"'"'/g; -			print "export $key='$value'\n"; -		} -	} -	when (['show', 'list']) { -		while (my ($key, $value) = each(%store)) { -			printf("%-15s = %s\n", $key, $value); -		} -	} -	when ('rm') { -		delete($store{$arg}); -		save_store; -	} -	when ('clear') { -		unlink($store_file); -	} -	default { -		usage; -	} -} -__END__ - -=head1 NAME - -envstore - save and restore environment variables - -=head1 SYNOPSIS - -B<envstore> I<command> [ I<arguments> ] - -=head1 DESCRIPTION - -envstore can save and restore environment variables, thus transferring them -between different shells. - -I<command> must be one of - -=over - -=item B<clear> - -Forget all stored variables - -=item B<eval> - -Produce shell code for evaluation, restoring all saved variables - -=item B<save> I<variable> [I<value>] - -Save I<variable> either with its current shell value or with I<value> - -=item B<show> - -List saved variables in better readable format - -=item B<rm> I<variable> - -Remove I<variable> from store - -=back - -=head1 ENVIRONMENT - -=over - -=item B<ENVSTORE_FILE> - -The file in which the environment parameters are stored. By default -F</tmp/envstore-$UID> - -=back - -=head1 LIMITATIONS - -You should not store null bytes or similar extremely weird binary data. They -might work fine, but there's a high chance that somewhere, something goes wrong -and you start to lose bytes or get undefined behaviour. - -Also, since shells 'flatten' the input when using eval, newlines in variable -values will be stored correctly, but silently dropped when loading the -environment with eval.  You can work around that by doing -C<< envstore eval E<gt> $tmpfile; source $tmpfile >> or (in zsh) -C<< source E<lt>(envstore eval) >>. - -=head1 AUTHOR - -B<envstore> was written by Daniel Friesel E<lt>derf@derf.homelinux.orgE<gt> - -Original idea and script by Maximilian Gass E<lt>mxey@ghosthacking.netE<gt> diff --git a/man/1/envify b/man/1/envify new file mode 100644 index 0000000..50e9e3e --- /dev/null +++ b/man/1/envify @@ -0,0 +1,11 @@ +.TH ENVIFY 1 "2009-10-29" "Daniel Friesel" "User Commands" +.SH "NAME" +\fBenvify\fR \- execute a command in the environment saved with envstore +.SH "SYNOPSIS" +\fBenvify\fR \fIcommand\fR... +.SH "DESCRIPTION" +\fBenvify\fR loads the environment saved by \fBenvstore\fR and then executes \fIcommand\fR. +.SH "LIMITATIONS" +The \fIenvstore\fR\|(1) limitations for null bytes apply here as well. +.SH "SEE ALSO" +\fIenvstore\fR\|(1) diff --git a/man/1/envify.pod b/man/1/envify.pod deleted file mode 100644 index 396cd88..0000000 --- a/man/1/envify.pod +++ /dev/null @@ -1,29 +0,0 @@ -=pod - -=head1 NAME - -envify - execute a command in the environment saved with envstore - -=head1 SYNOPSIS - -B<envify> I<command>... - -=head1 DESCRIPTION - -B<envify> loads the environment saved by B<envstore> and then executes I<command>. - -=head1 LIMITATIONS - -The envstore(1) limitations for null bytes apply here as well. - -By default, newlines are not supported.  However, if you remove the newline from -the B<IFS> when calling envstore, newlines should work. -Example: C<< IFS=' ' envstore I<command> I<arguments>... >> - -=head1 SEE ALSO - -envstore(1) - -=cut - -vim:ft=pod diff --git a/man/1/envstore b/man/1/envstore new file mode 100644 index 0000000..2a65781 --- /dev/null +++ b/man/1/envstore @@ -0,0 +1,47 @@ +.TH ENVSTORE 1 "2009-10-29" "Daniel Friesel" "User Commands" +.SH "NAME" +\fBenvstore\fR \- execute a command in the environment saved with envstore +.SH "SYNOPSIS" +\fBenvstore\fR \fIcommand\fR [\fIargs\fR...] +.SH "DESCRIPTION" +\fBenvstore\fR can save and restore environment variables, +thus transferring them between different shells. +.PP +\fIcommand\fR must be one of +.TP +\fBclear\fR +Forget all stored variables + +.TP +\fBeval\fR +Produce shell code for evaluation, restoring all saved variables + +.TP +\fBlist\fR +List saved variables in better readable format + +.TP +\fBsave\fR \fIvariable\fR [\fIvalue\fR] +Save \fBvariable\fR either with its current shell value or with \fIvalue\fR + +.TP +\fBrm\fR \fIvariable\fR +Remove \fIvariable\fR from store +.PP +Note: Only the first char of \fIcommand\fR is checked, so "envstore e" is also +valid. +.SH "ENVIRONMENT" +.TP +\fBENVSTORE_FILE\fR +The file in which the environment parameters are stored. +By default /tmp/envstore\-\fIEUID\fR + +.SH "LIMITATIONS" +Variable names or values must not contain null bytes or newlines. +.PP +The current maximum length (in bytes) is 127 bytes for the variable name +and 255 bytes for its content. +.SH "AUTHOR" +\fBenvstore\fR was written by Daniel Friesel <derf@derf.homelinux.org> +.PP +Original idea and script by Maximilian Gass <mxey@ghosthacking.net> diff --git a/man/1/envstore.pod b/man/1/envstore.pod deleted file mode 120000 index 11ae3ec..0000000 --- a/man/1/envstore.pod +++ /dev/null @@ -1 +0,0 @@ -../../bin/envstore
\ No newline at end of file diff --git a/provides/zsh/completions/_envstore b/provides/zsh/completions/_envstore index 6124867..5851362 100644 --- a/provides/zsh/completions/_envstore +++ b/provides/zsh/completions/_envstore @@ -21,7 +21,7 @@ options_nofile=(  function _saved_param () {  	_wanted parameter expl 'saved parameter' \ -	compadd $(envstore eval -e | cut -d ' ' -f 2 | cut -d '=' -f 1) +	compadd $(envstore eval | cut -d ' ' -f 2 | cut -d '=' -f 1)  }  if (( CURRENT == 2 )) { @@ -32,13 +32,13 @@ if (( CURRENT == 2 )) {  	}  } elif (( CURRENT == 3 )) {  	case ${words[2]} in -		eval) +		e*)  			_arguments -s '2::option:(-e)'  		;; -		rm) +		r*)  			_saved_param  		;; -		save) +		s*)  			_parameters -g '(scalar|integer)*export*'  		;;  		*) diff --git a/src/envstore.c b/src/envstore.c new file mode 100644 index 0000000..171b786 --- /dev/null +++ b/src/envstore.c @@ -0,0 +1,260 @@ +/* + * Copyright © 2009 by Daniel Friesel <derf@derf.homelinux.org> + * License: WTFPL <http://sam.zoy.org/wtfpl> + * + * Function policy: Fail early (use err/errx if something goes wrong) + */ + +#include <err.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +/* + * This struct is part of a linked list and contains one shell parameter + */ + +struct parameter { +	char *name; +	char *content; +	struct parameter *next; +}; + + +/* + * Add element to linked list + * + * Parameters: list head (may be NULL), new list element + * Returns new list head + */ + +static struct parameter * list_add(struct parameter *first, struct parameter *new) { +	new->next = first; +	first = new; +	return first; +} + + +/* + * Remove element from linked list + * + * Parameters: list head, ->name part of element to remove + * Returns new list head (may be NULL) + */ + +static struct parameter * list_remove(struct parameter *first, char *string) { +	struct parameter *cur = first; +	struct parameter *prev = NULL; + +	while (cur != NULL) { +		if (strcmp(cur->name, string) == 0) { + +			if (prev == NULL) +				return cur->next; + +			prev->next = cur->next; +			return first; +		} +		prev = cur; +		cur = cur->next; +	} +	return first; +} + + +static void store_save(struct parameter *first, char *file) { +	struct parameter *cur = first; +	FILE *fp; +	char *tmpfile = malloc(strlen(file) + 5); + +	if (tmpfile == NULL) +		err(EXIT_FAILURE, "malloc"); + +	if (snprintf(tmpfile, strlen(file) + 5, "%s.tmp", file) < 3) +		err(EXIT_FAILURE, "snprintf"); + +	umask(0077); +	fp = fopen(tmpfile, "w+"); +	if (fp == NULL) +		err(EXIT_FAILURE, "Unable to save store to '%s'", file); + +	while (cur != NULL) { +		fprintf(fp, "%s %s\n", cur->name, cur->content); +		cur = cur->next; +	} + +	if (fclose(fp) != 0) +		err(EXIT_FAILURE, "fclose %s", file); + +	if (rename(tmpfile, file) != 0) +		err(EXIT_FAILURE, "Unable to rename '%s' to '%s'", tmpfile, file); +} + + +static struct parameter * store_load(char *file) { +	struct parameter *first = NULL; +	struct parameter *new; +	struct stat finfo; +	uid_t self_uid = geteuid(); +	char vname[128]; +	char vcontent[256]; +	FILE *fp = fopen(file, "r"); + +	/* Assume the store file does not exist and the store is empty +	 *   (-> return NULL for empty list) +	 * If it does in fact exist but has wrong permissions or similar, +	 * the user will only notice if it happens with envstore save/rm (FIXME) +	 */ +	if (fp == NULL) +		return NULL; + +	if (fstat(fileno(fp), &finfo) != 0) +		errx(EXIT_FAILURE, "Unable to verify store file permissions (%s)", file); + +	if (finfo.st_uid != self_uid) +		errx(EXIT_FAILURE, "Store file '%s' is insecure (must be owned by you, not uid %d)", file, finfo.st_uid); + +	if ((finfo.st_mode & 077) > 0) +		errx(EXIT_FAILURE, "Store file '%s' has insecure permissions %04o (recommended: 0600)", file, finfo.st_mode & 07777); + +	while (fscanf(fp, "%127s %255[^\n]\n", vname, vcontent) != EOF) { +		new = malloc(sizeof (struct parameter)); +		if (new == NULL) +			err(EXIT_FAILURE, "malloc"); + +		new->name = strdup(vname); +		new->content = strdup(vcontent); + +		if ((new->name == NULL) || (new->content) == NULL) +			err(EXIT_FAILURE, "strdup"); + +		first = list_add(first, new); +	} + +	if (fclose(fp) != 0) +		err(EXIT_FAILURE, "fclose %s", file); + +	return first; +} + + +static inline void command_clear(char *store_file) { +	/* +	 * No error checking - assume that the file didn't exist in the first place +	 * if unlink fails. +	 */ +	unlink(store_file); +} + + +static inline void command_eval(char *store_file) { +	struct parameter *first = store_load(store_file); +	struct parameter *cur = first; +	unsigned long int i; + +	while (cur != NULL) { + +		printf("export %s='", cur->name); +		for (i = 0; i < strlen(cur->content); i++) { +			if (cur->content[i] == '\'') +				fputs("'\"'\"'", stdout); +			else +				putchar(cur->content[i]); +		} +		fputs("'\n", stdout); +		cur = cur->next; +	} +} + + +static inline void command_list(char *store_file) { +	struct parameter *first = store_load(store_file); +	struct parameter *cur = first; + +	while (cur != NULL) { +		printf("%-15s = %s\n", cur->name, cur->content); +		cur = cur->next; +	} +} + + +static inline void command_rm(char *store_file, char *param) { +	struct parameter *first = store_load(store_file); +	first = list_remove(first, param); +	store_save(first, store_file); +} + + +static inline void command_save(char *store_file, char *param, char *value, int argc) { +	struct parameter *first = store_load(store_file); +	struct parameter new; +	char *newvalue; +	first = list_remove(first, param); + +	if (argc > 3) +		newvalue = value; +	else +		newvalue = getenv(param); + +	if (newvalue == NULL) +		errx(EXIT_FAILURE, "parameter '%s' has no value", param); + +	if ((strlen(param) > 127) || (strlen(newvalue) > 255)) +		errx(EXIT_FAILURE, "parameter or value too long (see man envstore -> LIMITATIONS)"); + +	new.name = param; +	new.content = newvalue; + +	first = list_add(first, &new); +	store_save(first, store_file); +} + + +int main(int argc, char **argv) { +	char *store_file; +	uid_t my_uid = geteuid(); + +	if (argc < 2) +		errx(EXIT_FAILURE, "Insufficient arguments"); + +	store_file = getenv("ENVSTORE_FILE"); +	if (store_file == NULL) { +		store_file = malloc(64); +		if (store_file == NULL) { +			err(EXIT_FAILURE, "malloc"); +		} +		if (snprintf(store_file, 63, "/tmp/envstore-%d", (int)my_uid) < 10) { +			err(EXIT_FAILURE, "snprintf"); +		} +	} + +	switch (argv[1][0]) { +		case 'c': +			command_clear(store_file); +			break; +		case 'e': +			command_eval(store_file); +			break; +		case 'l': +			command_list(store_file); +			break; +		case 'r': +			if (argc < 3) +				errx(EXIT_FAILURE, "Usage: rm <parameter>"); +			command_rm(store_file, argv[2]); +			break; +		case 's': +			if (argc < 3) +				errx(EXIT_FAILURE, "Usage: save <parameter> [value]"); +			command_save(store_file, argv[2], argv[3], argc); +			break; +		default: +			errx(EXIT_FAILURE, "Unknown action: %s", argv[1]); +			break; +	} + +	return EXIT_SUCCESS; +} @@ -14,6 +14,7 @@ while [[ $1 == --* ]] {  typeset envstore=${1-bin/envstore}  typeset store_file="/tmp/envstore-test-$UID" +typeset testdir=$(mktemp -d /tmp/envstore.XXXXXX)  export ENVSTORE_FILE=$store_file  trap "print -P '\n%N:%i: %B%F{red}Test faild!%F{default}%b'" ZERR  trap "$envstore clear" INT @@ -34,8 +35,16 @@ if ((help)) {  	exit 0  } -echo "# Documentation" -podchecker -warnings -warnings man/*/* +echo "# make" +make -s -B + +echo "# make install" +make -s install prefix=$testdir + +echo "# make uninstall" +make -s uninstall prefix=$testdir + +rm -r $testdir  echo "# $envstore clear"  $envstore clear @@ -95,6 +104,12 @@ eval $($envstore eval)  [[ $hello == "the ' dude" ]]  unset hello +echo "# $envstore save (multiple 's in value)" +$envstore save hello "the '' ' dude ' moose" +eval $(envstore eval) +[[ $hello == "the '' ' dude ' moose" ]] +unset hello +  echo "# $envstore save (UTF-8)"  export hello='mÿde Rentner… und so'  $envstore save hello @@ -105,16 +120,6 @@ unset hello  $envstore clear  if ((test_extended)) { -	echo "# $envstore save (newline in value)" -	export flurbl=$'yo my fresh\nhomies' -	$envstore save flurbl -	unset flurbl -	# eval does not like newlines. Not even in dash. -	source <($envstore eval) -	[[ $flurbl == $'yo my fresh\nhomies' ]] -	unset flurbl -	$envstore clear -  	echo "# $envstore save (binary values)"  	export noise=$'\xa0\xa5\x25\x01\x02\x77\xff\xf0'  	$envstore save noise @@ -132,7 +137,7 @@ if ((test_extended)) {  	! $envstore rm &> /dev/null  	echo "# other invocations" -	$envstore show +	$envstore list  	$envstore eval  	$envstore rm nonexistent  	$envstore clear | 
