blob: f389651ce288b5912c93748b2bc55828ff563301 [file] [log] [blame] [raw]
/* A part of the Native C Library for Windows NT
Copyright 2007-2015 PC GO Ld.
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
This program 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 General Public License for more details.
*/
// Based on the GNU C Library
#define _NO_ENVIRON_VAR_DEF
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <wchar.h>
#include <assert.h>
//#include <stdio.h>
#define LOCK
#define UNLOCK
#ifdef USE_TSEARCH
#include <search.h>
/* This is a pointer to the root of the search tree with the known
values. */
static void *known_values;
# define KNOWN_VALUE(Str) \
({ \
void *value = tfind(Str, &known_values, (__compar_fn_t)strcmp); \
value != NULL ? *(char **) value : NULL; \
})
# define STORE_VALUE(Str) \
tsearch(Str, &known_values, (__compar_fn_t)strcmp)
#else
# define KNOWN_VALUE(Str) NULL
# define STORE_VALUE(Str) do { } while (0)
#endif
char **environ = NULL;
// I can't export the variable environ to the library, so define this function with a diffent name
char ***_environ() {
return &environ;
}
/* If this variable is not a null pointer we allocated the current environment. */
static char **last_environ;
/* This function is used by `setenv' and `putenv'. The difference between
the two functions is that for the former must create a new string which
is then placed in the environment, while the argument of `putenv'
must be used directly. This is all complicated by the fact that we try
to reuse values once generated for a `setenv' call since we can never
free the strings. */
static int __add_to_environ(const char *name, const char *value, const char *combined, int replace) {
register char **ep;
register size_t size;
const size_t namelen = strlen (name);
const size_t valuelen = value ? strlen (value) + 1 : 0;
LOCK;
/* We have to get the pointer now that we have the lock and not earlier
since another thread might have created a new environment. */
ep = environ;
size = 0;
if(ep) while(*ep) {
if(strncmp(*ep, name, namelen) == 0 && (*ep)[namelen] == '=') break;
size++;
ep++;
}
if(!ep || __builtin_expect(*ep == NULL, 1)) {
const size_t varlen = namelen + 1 + valuelen;
char **new_environ;
/* We allocated this space; we can extend it. */
new_environ = (char **)realloc(last_environ, (size + 2) * sizeof (char *));
if(!new_environ) {
UNLOCK;
return -1;
}
if(combined) new_environ[size] = (char *)combined;
else {
/* See whether the value is already known. */
#ifdef USE_TSEARCH
char *new_value = malloc(varlen);
if(!new_value) {
UNLOCK;
if(!last_environ) free(new_environ);
return -1;
}
//mempcpy(mempcpy(mempcpy(new_value, name, namelen), "=", 1), value, valuelen);
mempcpy(new_value, name, namelen);
new_value[namelen] = '=';
memcpy(new_value + namelen + 1, value, valuelen);
new_environ[size] = KNOWN_VALUE(new_value);
if(__builtin_expect(!new_environ[size], 1)) {
#endif
new_environ[size] = malloc(varlen);
if(!new_environ[size]) {
UNLOCK;
return -1;
}
#ifdef USE_TSEARCH
memcpy(new_environ[size], new_value, varlen);
#else
memcpy(new_environ[size], name, namelen);
new_environ[size][namelen] = '=';
memcpy(&new_environ[size][namelen + 1], value, valuelen);
#endif
STORE_VALUE(new_environ[size]);
#ifdef USE_TSEARCH
}
#endif
}
if(environ != last_environ) memcpy(new_environ, environ, size * sizeof(char *));
new_environ[size + 1] = NULL;
last_environ = environ = new_environ;
} else if(replace) {
char *np;
/* Use the user string if given. */
if(combined) np = (char *)combined;
else {
const size_t varlen = namelen + 1 + valuelen;
#ifdef USE_TSEARCH
char *new_value = malloc(varlen);
if(!new_value) {
UNLOCK;
return -1;
}
memcpy(new_value, name, namelen);
new_value[namelen] = '=';
memcpy(new_value + namelen + 1, value, valuelen);
np = KNOWN_VALUE(new_value);
if(__builtin_expect(!np, 1)) {
#endif
np = malloc(varlen);
if(!np) {
UNLOCK;
return -1;
}
#ifdef USE_TSEARCH
memcpy(np, new_value, varlen);
#else
memcpy(np, name, namelen);
np[namelen] = '=';
memcpy(np + namelen + 1, value, valuelen);
#endif
STORE_VALUE(np);
#ifdef USE_TSEARCH
}
#endif
}
*ep = np;
}
UNLOCK;
return 0;
}
int setenv(const char *name, const char *value, int replace) {
if(!name || !*name || strchr(name, '=')) {
errno = EINVAL;
return -1;
}
return __add_to_environ(name, value, NULL, replace);
}
int unsetenv(const char *name) {
if(!name || !*name || strchr(name, '=')) {
errno = EINVAL;
return -1;
}
size_t len = strlen(name);
LOCK;
char **ep = environ;
if(ep) while(*ep) {
if(strncmp(*ep, name, len) == 0 && (*ep)[len] == '=') {
char **dp = ep;
while(*dp) {
*dp = dp[1];
dp++;
}
} else ep++;
}
UNLOCK;
return 0;
}
int clearenv() {
LOCK;
if(environ) {
if(environ == last_environ) {
free(environ);
last_environ = NULL;
}
environ = NULL;
}
UNLOCK;
return 0;
}
char *getenv(const char *name) {
char **ep;
size_t len = strlen(name);
uint16_t name_start;
if(!name || !environ) return NULL;
if(!name[1]) {
/* The name of the variable consists of only one character. Therefore
the first two characters of the environment entry are this character
and a '=' character. */
#if __BYTE_ORDER == __LITTLE_ENDIAN || !_STRING_ARCH_unaligned
name_start = ('=' << 8) | *(const unsigned char *)name;
#else
# if __BYTE_ORDER == __BIG_ENDIAN
name_start = '=' | ((*(const unsigned char *)name) << 8);
# else
#error "Funny byte order."
# endif
#endif
for(ep = environ; *ep != NULL; ep++) {
#if _STRING_ARCH_unaligned
uint16_t ep_start = *(uint16_t *)*ep;
#else
uint16_t ep_start = (((unsigned char *)*ep)[0] | (((unsigned char *)*ep)[1] << 8));
#endif
if(name_start == ep_start) return *ep + 2;
}
} else {
#if _STRING_ARCH_unaligned
name_start = *(const uint16_t *)name;
#else
name_start = (((const unsigned char *)name)[0] | (((const unsigned char *)name)[1] << 8));
#endif
len -= 2;
name += 2;
for(ep = environ; *ep != NULL; ep++) {
#if _STRING_ARCH_unaligned
uint16_t ep_start = *(uint16_t *) *ep;
#else
uint16_t ep_start = (((unsigned char *) *ep)[0] | (((unsigned char *) *ep)[1] << 8));
#endif
if(name_start == ep_start && strncmp(*ep + 2, name, len) == 0 && (*ep)[len+2] == '=') {
return *ep + len + 3;
}
}
}
return NULL;
}
int putenv(char *s) {
const char *name_end = strchr(s, '=');
if(!name_end) {
unsetenv(s);
return 0;
}
size_t len = name_end - s;
char name[len + 1];
memcpy(name, s, len);
name[len] = 0;
return __add_to_environ(name, NULL, s, 1);
}
static int __putenv_c(const char *s) {
const char *name_end = strchr(s, '=');
if(!name_end) return -1;
size_t len = name_end - s;
char name[len + 1];
memcpy(name, s, len);
name[len] = 0;
return __add_to_environ(name, name_end + 1, NULL, 1);
}
void __initenv_from_envblock(const char *s) {
while(*s) {
__putenv_c(s);
s += strlen(s) + 1;
}
}
void __initenv_from_wenvblock(const wchar_t *s) {
while(*s) {
size_t len = wcslen(s) + 1;
size_t mlen = len * 2 - 1;
char buffer[mlen];
if(wcstombs(buffer, s, mlen) > 0) __putenv_c(buffer);
s += len;
}
}
wchar_t *__environ_to_wenvblock(wchar_t *buffer, size_t buffer_size) {
wchar_t *p = buffer;
char **e = environ;
while(*e) {
int len = mbstowcs(p, *e++, buffer_size / sizeof(wchar_t) - 1);
if(len < 0) continue;
assert(len * sizeof(wchar_t) < buffer_size);
p += len + 1;
buffer_size -= len * sizeof(wchar_t);
if(buffer_size / sizeof(wchar_t) - 1 == len || buffer_size < sizeof(wchar_t) * 3) {
p[-1] = 0;
break;
}
}
*p = 0;
return buffer;
}