summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Friesel <derf@derf.homelinux.org>2009-11-02 21:21:10 +0100
committerDaniel Friesel <derf@derf.homelinux.org>2009-11-02 21:23:19 +0100
commit20bcc774686078cf7f5c58ef058ca2458b01086d (patch)
treed1f9e6a90285db10bf28a2abecc1ef610ef64838
parent3c5e9f4f828c76de5824cf7ac440aa0cda45ccaf (diff)
Rewrite in C, release v2.0 (from branch 'v2')2.0
-rw-r--r--.gitignore2
-rw-r--r--Changelog7
-rw-r--r--Makefile33
-rw-r--r--README9
-rwxr-xr-xbin/envify2
-rwxr-xr-xbin/envstore180
-rw-r--r--man/1/envify11
-rw-r--r--man/1/envify.pod29
-rw-r--r--man/1/envstore47
l---------man/1/envstore.pod1
-rw-r--r--provides/zsh/completions/_envstore8
-rw-r--r--src/envstore.c260
-rwxr-xr-xtest/main31
13 files changed, 373 insertions, 247 deletions
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 <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;
+}
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