From 20bcc774686078cf7f5c58ef058ca2458b01086d Mon Sep 17 00:00:00 2001 From: Daniel Friesel Date: Mon, 2 Nov 2009 21:21:10 +0100 Subject: Rewrite in C, release v2.0 (from branch 'v2') --- .gitignore | 2 +- Changelog | 7 + Makefile | 33 +++-- README | 9 +- bin/envify | 2 +- bin/envstore | 180 ------------------------- man/1/envify | 11 ++ man/1/envify.pod | 29 ----- man/1/envstore | 47 +++++++ man/1/envstore.pod | 1 - provides/zsh/completions/_envstore | 8 +- src/envstore.c | 260 +++++++++++++++++++++++++++++++++++++ test/main | 31 +++-- 13 files changed, 373 insertions(+), 247 deletions(-) delete mode 100755 bin/envstore create mode 100644 man/1/envify delete mode 100644 man/1/envify.pod create mode 100644 man/1/envstore delete mode 120000 man/1/envstore.pod create mode 100644 src/envstore.c diff --git a/.gitignore b/.gitignore index 84c048a..a9fa4e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -/build/ +/bin/envstore diff --git a/Changelog b/Changelog index 354ed13..8015119 100644 --- a/Changelog +++ b/Changelog @@ -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 diff --git a/Makefile b/Makefile index 090bf11..551c15c 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/README b/README index e71ff34..276767f 100644 --- a/README +++ b/README @@ -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. diff --git a/bin/envify b/bin/envify index da9c178..7e15ab8 100755 --- a/bin/envify +++ b/bin/envify @@ -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 -## License: 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 I [ I ] - -=head1 DESCRIPTION - -envstore can save and restore environment variables, thus transferring them -between different shells. - -I must be one of - -=over - -=item B - -Forget all stored variables - -=item B - -Produce shell code for evaluation, restoring all saved variables - -=item B I [I] - -Save I either with its current shell value or with I - -=item B - -List saved variables in better readable format - -=item B I - -Remove I from store - -=back - -=head1 ENVIRONMENT - -=over - -=item B - -The file in which the environment parameters are stored. By default -F - -=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 $tmpfile; source $tmpfile >> or (in zsh) -C<< source E(envstore eval) >>. - -=head1 AUTHOR - -B was written by Daniel Friesel Ederf@derf.homelinux.orgE - -Original idea and script by Maximilian Gass Emxey@ghosthacking.netE 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 I... - -=head1 DESCRIPTION - -B loads the environment saved by B and then executes I. - -=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 when calling envstore, newlines should work. -Example: C<< IFS=' ' envstore I I... >> - -=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 +.PP +Original idea and script by Maximilian Gass 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 + * License: WTFPL + * + * Function policy: Fail early (use err/errx if something goes wrong) + */ + +#include +#include +#include +#include +#include +#include +#include + +/* + * 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 "); + command_rm(store_file, argv[2]); + break; + case 's': + if (argc < 3) + errx(EXIT_FAILURE, "Usage: save [value]"); + command_save(store_file, argv[2], argv[3], argc); + break; + default: + errx(EXIT_FAILURE, "Unknown action: %s", argv[1]); + break; + } + + return EXIT_SUCCESS; +} diff --git a/test/main b/test/main index 6d7cfeb..8f65706 100755 --- a/test/main +++ b/test/main @@ -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 -- cgit v1.2.3