Rewrite getenv(3), setenv(3), unsetenv(3) and clearenv(3).

This commit is contained in:
Jonas 'Sortie' Termansen 2014-01-17 01:14:29 +01:00
parent af9ea5df04
commit e91cde379a
11 changed files with 418 additions and 226 deletions

View File

@ -127,6 +127,25 @@ If there is ever going to be a path limit, it'll probably be either this value
or higher. Ideally, your programs ought to work with paths of any reasonable
length.
putenv
------
This is a poorly designed interface for manipulating the environment which
interacts quite badly with interfaces such as setenv and unsetenv. The major
problem is that putenv makes the input string itself part of the environment,
but setenv makes a copy of the input string part of the environment. This means
that unsetenv (as well as putenv and setenv when changing an existing variable)
has to somehow know whether the a given entry in environ was allocated by setenv
and whether to free it. This isn't helped by the fact that the environ symbol
is publicly accessible and callers of putenv can change the environment by
editing the string the caller inserted. This means that the implementations of
setenv and unsetenv must do a considerable amount of book-keeping behind the
scenes to figure out whether a string was allocated by setenv or face memory
leaks when environment variables are changed or unset. The solution to get rid
of all the needless complexity putenv forces upon the other functions is simply:
Don't provide putenv in the first place and fix any software that uses putenv to
just call setenv instead.
sdl-config
----------

View File

@ -371,15 +371,18 @@ stdio/vscanf.o \
stdlib/atexit.o \
stdlib/canonicalize_file_name_at.o \
stdlib/canonicalize_file_name.o \
stdlib/env.o \
stdlib/clearenv.o \
stdlib/_Exit.o \
stdlib/exit.o \
stdlib/getenv.o \
stdlib/mkstemp.o \
stdlib/mktemp.o \
stdlib/on_exit.o \
stdlib/rand.o \
stdlib/realpath.o \
stdlib/setenv.o \
stdlib/system.o \
stdlib/unsetenv.o \
sys/display/dispmsg_issue.o \
sys/ioctl/ioctl.o \
sys/kernelinfo/kernelinfo.o \
@ -470,6 +473,7 @@ unistd/confstr.o \
unistd/dup2.o \
unistd/dup3.o \
unistd/dup.o \
unistd/environ.o \
unistd/execle.o \
unistd/execl.o \
unistd/execlp.o \

View File

@ -88,10 +88,12 @@ void* bsearch(const void*, const void*, size_t, size_t, int (*)(const void*, con
void* calloc(size_t, size_t);
char* canonicalize_file_name(const char* path);
char* canonicalize_file_name_at(int dirfd, const char* path);
int clearenv(void);
div_t div(int, int);
void exit(int) __attribute__ ((__noreturn__));
void _Exit(int status) __attribute__ ((__noreturn__));
void free(void*);
char* getenv(const char*);
long labs(long);
ldiv_t ldiv(long, long);
long long llabs(long long);
@ -105,7 +107,6 @@ int mkstemp(char*);
char* mktemp(char* templ);
#endif
int on_exit(void (*function)(int, void*), void* arg);
int putenv(char*);
void qsort(void*, size_t, size_t, int (*)(const void*, const void*));
int rand(void);
void* realloc(void*, size_t);
@ -124,23 +125,6 @@ int unsetenv(const char*);
size_t wcstombs(char* __restrict, const wchar_t *__restrict, size_t);
int wctomb(char*, wchar_t);
#if defined(_SORTIX_SOURCE) || defined(_WANT_SORTIX_ENV)
const char* const* getenviron(void);
size_t envlength(void);
const char* getenvindexed(size_t index);
const char* sortix_getenv(const char* name);
#endif
#if (defined(_SOURCE_SOURCE) && __SORTIX_STDLIB_REDIRECTS) || \
defined(_WANT_SORTIX_ENV)
const char* getenv(const char* name) asm ("sortix_getenv");
#else
char* getenv(const char*);
#endif
#if defined(_SORTIX_SOURCE) || defined(_SVID_SOURCE) || defined(_XOPEN_SOURCE) \
|| defined(_GNU_SOURCE) || defined(_BSD_SOURCE)
int clearenv(void);
#endif
#if defined(__is_sortix_libc)
struct exit_handler
{

View File

@ -295,8 +295,11 @@ typedef __pid_t pid_t;
typedef __useconds_t useconds_t;
#endif
#if defined(_WANT_ENVIRON)
extern char** environ;
#if defined(__is_sortix_libc)
extern char** __environ_malloced;
extern size_t __environ_used;
extern size_t __environ_length;
#endif
/* TODO: These are not implemented in sortix libc yet. */

38
libc/stdlib/clearenv.cpp Normal file
View File

@ -0,0 +1,38 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2014.
This file is part of the Sortix C Library.
The Sortix C Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
The Sortix C Library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with the Sortix C Library. If not, see <http://www.gnu.org/licenses/>.
stdlib/clearenv.cpp
Clear the environment and set environ to NULL.
*******************************************************************************/
#include <stdlib.h>
#include <unistd.h>
extern "C" int clearenv()
{
if ( environ == __environ_malloced )
{
for ( size_t i = 0; environ[i]; i++ )
free(environ[i]);
free(environ);
}
environ = NULL;
return 0;
}

View File

@ -1,200 +0,0 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2012.
This file is part of the Sortix C Library.
The Sortix C Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
The Sortix C Library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with the Sortix C Library. If not, see <http://www.gnu.org/licenses/>.
stdlib/env.cpp
Environmental variables utilities.
*******************************************************************************/
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
/* Since legacy applications rely on being able to modify the environ variable,
we have to keep track of whether it is what we expect and whether we know the
size of it. If it changed, we need to recount, as well as malloc a copy of it
if we wish to make changes. */
extern "C" { char** environ = NULL; }
static char** environ_malloced = NULL;
static char** environ_counted = NULL;
size_t environlen = 0;
size_t environroom = 0;
static inline bool environismalloced()
{
return environ && environ == environ_malloced;
}
extern "C" const char* const* getenviron(void)
{
return environ;
}
extern "C" size_t envlength(void)
{
if ( !environ ) { return 0; }
if ( environ_counted != environ )
{
size_t count = 0;
while ( environ[count] ) { count++; }
environ_counted = environ;
environlen = count;
}
return environlen;
}
extern "C" const char* getenvindexed(size_t index)
{
if ( envlength() <= index ) { errno = EBOUND; return NULL; }
return environ[index];
}
extern "C" const char* sortix_getenv(const char* name)
{
size_t equalpos = strcspn(name, "=");
if ( name[equalpos] == '=' ) { return NULL; }
size_t namelen = equalpos;
size_t envlen = envlength();
for ( size_t i = 0; i < envlen; i++ )
{
if ( strncmp(name, environ[i], namelen) ) { continue; }
if ( environ[i][namelen] != '=' ) { continue; }
return environ[i] + namelen + 1;
}
return NULL;
}
extern "C" char* getenv(const char* name)
{
return (char*) sortix_getenv(name);
}
static bool makeenvironmalloced()
{
if ( environismalloced() ) { return true; }
size_t envlen = envlength();
size_t newenvlen = envlen;
size_t newenvsize = sizeof(char*) * (newenvlen+1);
char** newenviron = (char**) malloc(newenvsize);
if ( !newenviron ) { return false; }
size_t sofar = 0;
for ( size_t i = 0; i < envlen; i++ )
{
newenviron[i] = strdup(environ[i]);
if ( !newenviron[i] ) { goto cleanup; }
sofar = i;
}
newenviron[envlen] = NULL;
environlen = environroom = newenvlen;
environ = environ_malloced = environ_counted = newenviron;
return true;
cleanup:
for ( size_t i = 0; i < sofar; i++ ) { free(newenviron[i]); }
free(newenviron);
return false;
}
extern "C" int clearenv(void)
{
if ( !environ ) { return 0; }
if ( environismalloced() )
{
for ( char** varp = environ; *varp; varp++ ) { free(*varp); }
free(environ);
}
environ = environ_counted = environ_malloced = NULL;
return 0;
}
static bool doputenv(char* str, char* freeme, bool overwrite)
{
if ( !makeenvironmalloced() ) { free(freeme); return false; }
size_t strvarnamelen = strcspn(str, "=");
for ( size_t i = 0; i < envlength(); i++ )
{
char* var = environ[i];
if ( strncmp(str, var, strvarnamelen) ) { continue; }
if ( !overwrite ) { free(freeme); return true; }
free(var);
environ[i] = str;
return true;
}
if ( environlen == environroom )
{
size_t newenvironroom = environroom ? 2 * environroom : 16;
size_t newenvironsize = sizeof(char*) * (newenvironroom+1);
char** newenviron = (char**) realloc(environ, newenvironsize);
if ( !newenviron ) { free(freeme); return false; }
environ = environ_malloced = environ_counted = newenviron;
environroom = newenvironroom;
}
environ[environlen++] = str;
environ[environlen] = NULL;
return true;
}
extern "C" int setenv(const char* name, const char* value, int overwrite)
{
if ( !name || !name[0] || strchr(name, '=') ) { errno = EINVAL; return -1; }
char* str = (char*) malloc(strlen(name) + 1 + strlen(value) + 1);
if ( !str ) { return -1; }
stpcpy(stpcpy(stpcpy(str, name), "="), value);
if ( !doputenv(str, str, overwrite) ) { return -1; }
return 0;
}
extern "C" int putenv(char* str)
{
if ( !strchr(str, '=') ) { errno = EINVAL; return -1; }
// TODO: HACK: This voilates POSIX as it mandates that callers must be able
// to modify the environment by modifying the string. However, this is bad
// design! It also means we got a memory leak since we can't safely free
// strings from the environment when overriding them. Therefore we create
// a copy of the strings here and use the copy instead. This is a problem
// for some applications that will subtly break.
char* strcopy = strdup(str);
if ( !strcopy )
return -1;
if ( !doputenv(strcopy, strcopy, true) ) { return -1; }
return 0;
}
extern "C" int unsetenv(const char* name)
{
size_t equalpos = strcspn(name, "=");
if ( name[equalpos] == '=' ) { return 0; }
size_t namelen = equalpos;
size_t envlen = envlength();
for ( size_t i = 0; i < envlen; i++ )
{
if ( strncmp(name, environ[i], namelen) ) { continue; }
if ( environ[i][namelen] != '=' ) { continue; }
if ( environismalloced() )
{
char* str = environ[i];
free(str);
}
if ( i < envlen-1 ) { environ[i] = environ[envlen-1]; }
environ[--environlen] = NULL;
return 0;
}
return 0;
}

57
libc/stdlib/getenv.cpp Normal file
View File

@ -0,0 +1,57 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2014.
This file is part of the Sortix C Library.
The Sortix C Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
The Sortix C Library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with the Sortix C Library. If not, see <http://www.gnu.org/licenses/>.
stdlib/getenv.cpp
Get an environment variable.
*******************************************************************************/
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static inline
bool matches_environment_variable(const char* what,
const char* name, size_t name_length)
{
return !strncmp(what, name, name_length) && what[name_length] == '=';
}
extern "C" char* getenv(const char* name)
{
if ( !name )
return errno = EINVAL, (char*) NULL;
// Verify the name doesn't contain a '=' character.
size_t name_length = 0;
while ( name[name_length] )
if ( name[name_length++] == '=' )
return errno = EINVAL, (char*) NULL;
if ( !environ )
return NULL;
// Find the environment variable with the given name.
for ( size_t i = 0; environ[i]; i++ )
if ( matches_environment_variable(environ[i], name, name_length) )
return environ[i] + name_length + 1;
return NULL;
}

176
libc/stdlib/setenv.cpp Normal file
View File

@ -0,0 +1,176 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2014.
This file is part of the Sortix C Library.
The Sortix C Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
The Sortix C Library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with the Sortix C Library. If not, see <http://www.gnu.org/licenses/>.
stdlib/setenv.cpp
Set an environment variable.
*******************************************************************************/
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static inline
bool matches_environment_variable(const char* what,
const char* name, size_t name_length)
{
return !strncmp(what, name, name_length) && what[name_length] == '=';
}
static char* create_entry(const char* name, size_t name_length,
const char* value, size_t value_length)
{
size_t result_length = name_length + 1 + value_length;
size_t result_size = sizeof(char) * (result_length + 1);
char* result = (char*) malloc(result_size);
if ( !result )
return NULL;
stpcpy(stpcpy(stpcpy(result, name), "="), value);
return result;
}
static bool set_entry_at(char** environ, size_t position,
const char* name, size_t name_length,
const char* value, size_t value_length)
{
char* new_entry = create_entry(name, name_length, value, value_length);
if ( !new_entry)
return false;
free(environ[position]);
environ[position] = new_entry;
return true;
}
static bool recover_environment()
{
// It is undefined behavior to change environ to another value (manually
// taking control of the environment) and reusing any of the pointers from
// the former environ array. A program that assigns to environ must use its
// own memory for the environ array and all the strings pointed to by it.
// TODO: Is that actually undefined behavior?
// Destroy the old environ array and its entries that we'll assume is no
// longer used as that would be undefined behavior as said above.
if ( __environ_malloced )
{
for ( size_t i = 0; i < __environ_used; i++ )
free(__environ_malloced[i]);
free(__environ_malloced);
__environ_malloced = 0;
__environ_length = 0;
__environ_used = 0;
}
// Prepare a new environ array into which we'll store all the strings.
size_t environ_length = 0;
if ( environ )
while ( environ[environ_length] )
environ_length++;
size_t environ_size = sizeof(char*) * (environ_length + 1);
char** environ_malloced = (char**) malloc(environ_size);
if ( !environ_malloced )
return false;
// Duplicate all the environment variables into the new environment.
for ( size_t i = 0; i < environ_length; i++ )
{
if ( !(environ_malloced[i] = strdup(environ[i])) )
{
for ( size_t n = 0; n < i; n++ )
free(environ_malloced[n]);
free(environ_malloced);
return false;
}
}
environ_malloced[environ_length] = NULL;
// Switch to the newly recovered environment.
environ = __environ_malloced = environ_malloced;
__environ_used = environ_length;
__environ_length = environ_length;
return true;
}
extern "C" int setenv(const char* name, const char* value, int overwrite)
{
if ( !name || !value )
return errno = EINVAL, -1;
// Verify the name doesn't contain a '=' character.
size_t name_length = 0;
while ( name[name_length] )
if ( name[name_length++] == '=' )
return errno = EINVAL, -1;
// Take back control of the environment if the user changed the environ
// pointer to something the user controls or if the environment has not been
// initialized yet besides the read-only initial environment.
if ( (!environ || environ != __environ_malloced) && !recover_environment() )
return -1;
size_t value_length = strlen(value);
// Attempt to locate an existing environment variable with this name.
for ( size_t i = 0; i < __environ_used; i++ )
{
char* previous_entry = environ[i];
if ( !matches_environment_variable(previous_entry, name, name_length) )
continue;
// Report success if the caller doesn't want us to change an existing
// environment variable, but still set the variable if not set yet.
if ( !overwrite )
return 0;
// Avoid the need to allocate a new string if there is room in the old
// allocation for the contents of the new string.
char* previous_value = previous_entry + name_length + 1;
size_t previous_value_length = strlen(previous_value);
if ( value_length <= previous_value_length )
{
strcpy(previous_value, value);
return 0;
}
// Insert the new entry in the environ array at the same location.
bool result = set_entry_at(environ, i, name, name_length, value, value_length);
return result ? 0 : -1;
}
// Expand the environ array if it is currently fully used such that we have
// room to insert the new entry below.
if ( __environ_used == __environ_length )
{
size_t new_length = __environ_length ? 2 * __environ_length : 16;
size_t new_size = sizeof(char*) * (new_length + 1);
char** new_environ = (char**) realloc(environ, new_size);
if ( !new_environ )
return -1;
environ = __environ_malloced = new_environ;
for ( size_t i = __environ_used; i <= new_length; i++ )
environ[i] = NULL;
__environ_length = new_length;
}
// Append the new entry to the environ array.
bool result = set_entry_at(environ, __environ_used++, name, name_length, value, value_length);
return result ? 0 : -1;
}

79
libc/stdlib/unsetenv.cpp Normal file
View File

@ -0,0 +1,79 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2014.
This file is part of the Sortix C Library.
The Sortix C Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
The Sortix C Library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with the Sortix C Library. If not, see <http://www.gnu.org/licenses/>.
stdlib/unsetenv.cpp
Unset an environment variable.
*******************************************************************************/
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static inline
bool matches_environment_variable(const char* what,
const char* name, size_t name_length)
{
return !strncmp(what, name, name_length) && what[name_length] == '=';
}
extern "C" int unsetenv(const char* name)
{
if ( !name )
return errno = EINVAL, -1;
// Verify the name doesn't contain a '=' character.
size_t name_length = 0;
while ( name[name_length] )
if ( name[name_length++] == '=' )
return errno = EINVAL, -1;
if ( !environ )
return 0;
// Delete all variables from the environment with the given name.
for ( size_t i = 0; environ[i]; i++ )
{
if ( matches_environment_variable(environ[i], name, name_length) )
{
// Free the string and move the array around in constant time if
// the setenv implementation is in control of the array.
if ( environ == __environ_malloced )
{
free(environ[i]);
environ[i] = environ[__environ_used-1];
environ[--__environ_used] = NULL;
}
// Otherwise, we'll simply clear that entry in environ, but we can't
// do it in constant time as we don't know how large the array is.
else
{
for ( size_t n = i; environ[n]; n++ )
environ[n] = environ[n+1];
}
// This entry was deleted so we'll need to process it again.
i--;
}
}
return 0;
}

30
libc/unistd/environ.cpp Normal file
View File

@ -0,0 +1,30 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2014.
This file is part of the Sortix C Library.
The Sortix C Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
The Sortix C Library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with the Sortix C Library. If not, see <http://www.gnu.org/licenses/>.
unistd/environ.cpp
Environment variables.
*******************************************************************************/
#include <unistd.h>
extern "C" { char** environ = NULL; }
extern "C" { char** __environ_malloced = NULL; }
extern "C" { size_t __environ_used = 0; }
extern "C" { size_t __environ_length = 0; }

View File

@ -341,10 +341,8 @@ readcmd:
if ( !strcmp(argv[0], "env") )
{
for ( size_t i = 0; i < envlength(); i++ )
{
printf("%s\n", getenvindexed(i));
}
for ( size_t i = 0; environ[i]; i++ )
printf("%s\n", environ[i]);
exit(0);
}
@ -481,8 +479,12 @@ int get_and_run_command(FILE* fp, const char* fpname, bool interactive,
if ( strchr(command, '=') && !strchr(command, ' ') && !strchr(command, '\t') )
{
if ( putenv(strdup(command)) )
error(1, errno, "putenv");
const char* key = command;
char* equal = strchr(command, '=');
*equal = '\0';
const char* value = equal + 1;
if ( setenv(key, value, 1) < 0 )
error(1, errno, "setenv");
return status = 0;
}