requesting help with setenv/unsetenv

If you're coding on or porting to IRIX, this is the forum for discussion.
User avatar
gijoe77
Posts: 215
Joined: Fri Jun 22, 2018 9:17 pm

requesting help with setenv/unsetenv

Post by gijoe77 » Mon Jul 16, 2018 9:07 pm

I'm currently working on a port of generator (another sega genesis emulator but with segaCD support http://www.squish.net/generator/cbiere/generator/ )

There is a call to "unsetenv()", which appears to be missing in IRIX. The old Nekochan wiki page (https://web.archive.org/web/20170303162 ... ki/MIPSpro) has the code to put inline for the setenv() function, and I'm having a hard time finding a call for unsetenv() that I can copy/paste inline as well, wondering if someone can help me out.

Also my googling keeps pointing me to GNUlib, but I can't seem to figure out how to download it or even how to use it... can anyone explain this to me like an idiot - i sure do feel like an idiot...

mrthinlysliced
Posts: 43
Joined: Mon May 14, 2018 9:21 am
Location: Colchester. UK

Re: requesting help with setenv/unsetenv

Post by mrthinlysliced » Tue Jul 17, 2018 5:12 am

Dunno if it helps, but you can find the function that "git" uses when it can't find unsetenv here:

https://github.com/git/git/blob/master/ ... unsetenv.c

Of course you can strip out the MINGW bit - and you'll need to find a way to do the right thing with it (I don't know if it's appropriate as an inline function - there doesn't seem to be any kind of locking / mutual exclusion) but it might be a place to start.

Looks like it is directly modifying the environ[] table of the process (basically is shuffling all the entries after the value to remove forwards one and then writing a terminator).

TruHobbyist
Posts: 25
Joined: Tue May 15, 2018 12:04 am

Re: requesting help with setenv/unsetenv

Post by TruHobbyist » Tue Jul 17, 2018 5:15 am

Hi gijoe!!!

My solution would be: create your own *env()-functions.

Some background: when main() is called, it gets a pointer to all process environment variables defined at this point. Where is that pointer?

Here:

int main(int argc, char *argv[], char *envp[])


Now we have a null-terminated array of pointers to strings (string in c = contiguos sequence of chars), stored in process memory and which you can manipulate, carefully.

The following pseudo code is just my approach to a possible solution, it's neither tested/verified nor optimized. In the end this is all about learning...

Code: Select all


getenv(char *envvar):
{
	/* pseudo code for gijoe77 - yes, you can */

	char *retval;
	char **ep = envp
	char *s;

	while (ep != NULL)
	{
		*ep now points to the next "envvar=value" string, for example "HOME=/usr/people/root"
		s = *ep

		Process that string:
		1. Look for char '=' in s
			1.1 If found, you now have two parts:
				Part1: envvar (for example HOME)
				Part2: value (for example /usr/people/root)
			1.2 If not found, continue

		2. Compare first part to requested envvar
			2.1 If they match:
				2.1.1 Return corresponding value (Part2)
					value_length = strlen(Part2)
					retval = malloc(value_length + 1) // + 1 for terminating null-byte
					strncpy(retval, Part2, value_length)
					retval[value_length] = 0; // terminate
					return

			2.2 If they don't match, continue


		Continue to next envvar=value string:
		ep++
	}
}


setenv(char *envvar, char *newvalue)
{
	/* pseudo code for gijoe77 - go, go, go */

	int string_length;
	int new_length;
	char *newp;

	Construct new string based on arguments:
	new_length = strlen(envvar) + strlen("=") + strlen(newvalue);
	newp = malloc(new_length + 1)
	strncat(newp, envvar, strlen(envvar))
	strncat(newp, "=", strlen("="))
	strncat(newp, newvalue, strlen(newvalue))
	newp[new_length] = 0;


	Now, two cases here:
		Case 1: the variable to be set already exists (modify existing envvar)
		Case 2: it does NOT already exist (need to extend envp)

	Case 1:
		Loop through envp (as seen in getenv())
			Lookup envvar

				Modify existing value
					Here you have to consider two cases again - oh boy, this never ends:
						Case 1: the new value fits in allocated string (zero out value part and copy-in newvalue)
						Case 2: it does not fit (new allocation necessary)

					I'd recommend always assuming case 2, since it does work for case 1 as well:
					envp[index_to_be_modified] = newp;


	Case 2:
		Need to extend envp to create room for new pointer to envvar=newvalue

		Try one of two options:
		Option 1: extend existing envp
		Option 2: create new, larger envp, say envp2, copy envp to envp2 and add envvar=newvalue

		NOTE: I have never implemented these functions myself and can't assure any of these options work.

		Option 1:
			Get length of envp:
			envp_length = 0
			while (envp[envp_length] != NULL) envp_length++;

			Add one more pointer for envvar=newvalue:
			envp_length++

			Add one more for the terminating NULL-pointer:
			envp_length++

			Extend existing envp:
			envp = realloc(envp_length * sizeof(char *))

			Add envvar=newvalue
			envp[envp_length - 1] = newp;

			Terminate:
			envp[envp_length] = 0;

		Option 2:
			Get length of envp:
			envp_length = 0
			while (envp[envp_length] != NULL) envp_length++;

			Add one more pointer for envvar=newvalue:
			envp_length++

			Add one more for the terminating NULL-pointer:
			envp_length++

			Create new envp, named envp2:
			envp2 = malloc(envp_length * sizeof(char *))

			Copy old envp strings to new envp2 (except terminating NULL-pointer):
			for (i = 0; i < (envp_length - 1); i++)
				Get length of string:
				string_length = strlen(envp[i])

				Alloc space for string:
				envp2[i] = malloc(string_length + 1)

				Copy string:
				strncpy(envp2[i], envp[i], string_length)

				Terminate:
				envp2[i][string_length] = 0


			Add envvar=newvalue
			envp2[envp_length - 1] = newp;

			Terminate:
			envp2[envp_length] = 0;

			Change old envp:
			envp = envp2

			NOTE: It *may* require a "deeper" modification of the process memory.
			Specifically, the address of the old envp needs to be patched throughout all process memory, wherever it is used. A more complicated
			scenario can arise if kernel structures for that process (process block?) need to be patched too. hmmmm...
}


unsetenv(char *envvar)
{
	/* pseudo code for gijoe77 - there's no idiocy, never. There's just evolution */

	For the sake of simplicity, I'll just quickly note what needs to be done, since above descriptions already include all operations needed:

	Step 1: Loop through envp, looking for envvar to be deleted

	Step 2: If found, you get two parts of the envp array:
		Part1: contains all strings ("envvar=value") before the matching string
		Part2: contains all strings after the matching string

		Create a new envp, envp2, and copy both parts, consecutively (that is, leaving out the envvar=value to be deleted)

	Step 3: realloc envp to contain envp2 and copy back strings from envp2 to envp
}

A nice add-on to this solution would be an implementation of unsetenv(char *envvar, char *value), such that:

1. if both arguments are specified, delete only matching strings
2. if envvar and no value is specified, works just like normal unsetenv(char *envvar)
3. if no envvar and value is specified, delete all strings that match the corresponding value part


Anyway, hope this helps.

Tru

User avatar
gijoe77
Posts: 215
Joined: Fri Jun 22, 2018 9:17 pm

Re: requesting help with setenv/unsetenv

Post by gijoe77 » Tue Jul 17, 2018 5:53 pm

TruHobbyist wrote:
Tue Jul 17, 2018 5:15 am
Hi gijoe!!!


A nice add-on to this solution would be an implementation of unsetenv(char *envvar, char *value), such that:

1. if both arguments are specified, delete only matching strings
2. if envvar and no value is specified, works just like normal unsetenv(char *envvar)
3. if no envvar and value is specified, delete all strings that match the corresponding value part


Anyway, hope this helps.

Tru
yes it was all very helpful - so to start I tried to implement setenv() as per the nekochan MIPSpro wiki page:

Code: Select all

setenv() missing in IRIX
There is putenv()...

/* setenv is missing on win32, solaris, IRIX and HPUX */
static void setenv(const char *name, const char *val, int _xx)
{
    int len  = strlen(name) + strlen(val) + 2;
    char *env = malloc(len);

    if (env != NULL) 
    {
      strcpy(env, name);
      strcat(env, "=");
      strcat(env, val);
      putenv(env);

      /* free( env );  */ 
    }

}
although I removed the 3rd "int _xx" function input because I just couldn't figure out what it was supposed to do, so below is what im trying to implement. I appear to be making some silly mistakes but I just can't seem to figure it out. Here is what I have:

setenv.h

Code: Select all

//filename setenv.h

#ifndef _setenv_h
#define _setenv_h

/* setenv is missing on win32, solaris, IRIX and HPUX */
static void setenv(const char*, const char*);

#endif
setenv.c

Code: Select all

//filename setenv.c

#include <stdio.h> /* for NULL */
#include "setenv.h"

/* setenv is missing on win32, solaris, IRIX and HPUX */
/* putenv() does exists in IRIX */
static void setenv(const char *name, const char *val)
{
    int len  = strlen(name) + strlen(val) + 2;
    char *env = malloc(len);

    if (env != NULL) 
    {
      strcpy(env, name);
      strcat(env, "=");
      strcat(env, val);
      putenv(env);

      /* free( env );  */ 
    }

}
setenv_main.c

Code: Select all

//filename setenv_main.c 

#include <stdio.h>
#include "setenv.h"
 
char *varName = "DISPLAY";
char *varValue = "localhost:0.0";

int main()
{
	printf("Setting DISPLAY env variable\n");
	setenv(VarName, varValue);
}
what I'm trying to do to compile the test program is:

Code: Select all

gcc -c setenv.c
gcc -c setenv_main.c
gcc -o setenv_main setenv_main.o setenv.o
when I use cc its bombs out on setenv.c (this was a copy/paste from the Nekoware wiki so I'm not sure what to make of it, but gcc gets past it):

Code: Select all

-bash-4.2$ cc -c setenv.c
cc-1140 cc: ERROR File = setenv.c, Line = 11
  A value of type "int" cannot be used to initialize an entity of type "char *".

      char *env = malloc(len);
                  ^

1 error detected in the compilation of "setenv.c".in
-bash-4.2$ 
-bash-4.2$ gcc -c setenv.c
-bash-4.2$ gcc -c setenv_main.c
setenv.h:7: warning: 'setenv' used but never defined
-bash-4.2$ gcc -o setenv_main setenv_main.o setenv.o
ld32: ERROR   33 : Unresolved text symbol "setenv" -- 1st referenced by setenv_main.o.
        Use linker option -v to see when and which objects, archives and dsos are loaded.  
ld32: INFO    152: Output file removed because of error.
collect2: ld returned 2 exit status
-bash-4.2$ 
I'm sure this must be simple, but how can I link this correctly? Attached are all the source files

User avatar
dexter1
Posts: 68
Joined: Thu May 24, 2018 9:30 am
Location: Zoetermeer, The Netherlands

Re: requesting help with setenv/unsetenv

Post by dexter1 » Tue Jul 17, 2018 7:59 pm

You need to cast the result argument of malloc when assigning it to a pointer:

Code: Select all

char * env = (char *) malloc(len);

User avatar
gijoe77
Posts: 215
Joined: Fri Jun 22, 2018 9:17 pm

Re: requesting help with setenv/unsetenv

Post by gijoe77 » Tue Jul 17, 2018 8:49 pm

dexter1 wrote:
Tue Jul 17, 2018 7:59 pm
You need to cast the result argument of malloc when assigning it to a pointer:

Code: Select all

char * env = (char *) malloc(len);
ah ok, gonna have to read up a bit on casting, not something I really have dealt with. I got past that but am having this issue now:

Code: Select all

-bash-4.2$ cc -c setenv.c
-bash-4.2$ cc -c setenv_main.c
cc-1113 cc: ERROR File = setenv.h, Line = 7
  The function "setenv" was referenced but not defined.

  static void setenv(const char*, const char*);
              ^

1 error detected in the compilation of "setenv_main.c".
-bash-4.2$ 
Is this a problem with void? I believe I have the header and c file correct, not sure why its saying its not defined...

User avatar
dexter1
Posts: 68
Joined: Thu May 24, 2018 9:30 am
Location: Zoetermeer, The Netherlands

Re: requesting help with setenv/unsetenv

Post by dexter1 » Tue Jul 17, 2018 10:32 pm

This could be because the function is declared static. Try omitting 'static' from either/ or the setenv.c and setenv.h

jpstewart
Posts: 27
Joined: Wed May 23, 2018 11:19 am
Location: Southwestern Ontario, Canada

Re: requesting help with setenv/unsetenv

Post by jpstewart » Wed Jul 18, 2018 11:14 am

gijoe77 wrote:
Tue Jul 17, 2018 5:53 pm
I removed the 3rd "int _xx" function input because I just couldn't figure out what it was supposed to do
The function prototype on Linux / glibc calls the third parameter "overwrite" and offers the following description:
The setenv() function adds the variable name to the environment with the value value, if name does not already exist. If name does exist in the environment, then its value is changed to value if overwrite is nonzero; if overwrite is zero, then the value of name is not changed (and setenv() returns a success status).
IMHO your setenv() replacement should still take three parameters for compatibility with existing code, even if the third one is ignored (as is the case with the sample code posted earlier in this thread). AFAICT the POSIX standard for setenv() specifies all three so code will call it with three parameters.
SGI: Indigo, Indigo2, Octane, Origin 300
Sun: SPARCstation 20, Ultra 2, Blade 2500, T5240
HP: 9000/380, 425e, C8000
Digital: DECstation 5000/125

User avatar
gijoe77
Posts: 215
Joined: Fri Jun 22, 2018 9:17 pm

Re: requesting help with setenv/unsetenv

Post by gijoe77 » Wed Jul 18, 2018 6:26 pm

dexter1 wrote:
Tue Jul 17, 2018 10:32 pm
This could be because the function is declared static. Try omitting 'static' from either/ or the setenv.c and setenv.h
Yes that was it, I've been reading up on static/extern, it makes sense to me now. I think I am starting to understand the way the setenv() function was written on the wiki. It was set to static because it was meant to stay within the scope of whatever c file made the call and not be seen by any other c files, I suppose so it wouldn't cause any potential problems somewhere else... From what I read removing the "static" implies the function is now an extern function, but this is only for functions... let me know if I have that wrong.

Also it seems IRIX does have a getenv() function - found that by accident in some man pages :)
jpstewart wrote:
Wed Jul 18, 2018 11:14 am
IMHO your setenv() replacement should still take three parameters for compatibility with existing code, even if the third one is ignored (as is the case with the sample code posted earlier in this thread). AFAICT the POSIX standard for setenv() specifies all three so code will call it with three parameters.
Yeah, good idea. I re-wrote the the h and c files, it appears to me setenv() is now working the way it should (well, I didn't enable the overwrite code but maybe I'll circle back to that later):

setenv.h

Code: Select all

//filename setenv.h

#ifndef _setenv_h
#define _setenv_h

/* setenv is missing on win32, solaris, IRIX and HPUX */
extern void setenv(const char*, const char*, int);

#endif
setenv.c

Code: Select all

//filename setenv.c

#include <stdio.h> /* for NULL */
#include <stdlib.h> /* for free() */
#include "setenv.h"

/* setenv is missing on win32, solaris, IRIX and HPUX */
/* putenv() does exists in IRIX */
void setenv(const char *name, const char *val, int _xx)
{
    int len  = strlen(name) + strlen(val) + 2;
    char *env = (char *) malloc(len);

    if (env != NULL) 
    {
      strcpy(env, name);
      strcat(env, "=");
      strcat(env, val);
      putenv(env);
      free(env); 
    }

}
setenv_main.c

Code: Select all

//filename setenv_main.c 

#include <stdio.h>
#include <stdlib.h> /* for getenv() */
#include "setenv.h"
 
char *varName = "DISPLAY";
char *varValue = "localhost:0.0";
char *varCurrentValue;

int main()
{
	varCurrentValue = getenv(varName);
	printf("\noriginal env setting: DISPLAY=%s\n",varCurrentValue); 
	printf("Setting DISPLAY env variable to %s\n",varValue);
	setenv(varName, varValue, 1);
	varCurrentValue = getenv(varName);
	printf("new env setting: DISPLAY=%s\n\n",varCurrentValue); 
        return(0);
}
output:

Code: Select all

-bash-4.2$ cc -c setenv.c
-bash-4.2$ cc -c setenv_main.c
-bash-4.2$ cc -o setenv_main setenv_main.o setenv.o
-bash-4.2$ echo $DISPLAY
:0
-bash-4.2$ ./setenv_main

original env setting: DISPLAY=:0
Setting DISPLAY env variable to localhost:0.0
new env setting: DISPLAY=localhost:0.0

-bash-4.2$ echo $DISPLAY
:0
-bash-4.2$ 
I will now be trying to implement a unsetenv() function...

TruHobbyist
Posts: 25
Joined: Tue May 15, 2018 12:04 am

Re: requesting help with setenv/unsetenv

Post by TruHobbyist » Thu Jul 19, 2018 4:54 am

Hi gijoe,

in your setenv.c, delete the line with free() after putenv(). The string passed to putenv() should be in global (static, constant - preferrably in writable data sections) or mallocd (heap) space. By freeing your buffer after putenv(), you are returning it to the system, allowing it to be reused in subsequent mallocs, thus using it effectively as an automatic variable on stack (which will eventually be reused by subsequent function calls).

I had been working on Tru64 this morning and saw it doesn't implement setenv either, so I implemented one with args and error checking. It is already refactored, so try not getting too annoyed with the if's:

Code: Select all

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>


int setenv(const char *envname, const char *envval, int overwrite)
{
        char *s;
        char *exists;
        /* envname length */
        size_t en_len;

        /* Arguments sanity checks */
        /* envname, envval */
        if ((envname == NULL)
                || (*envname == 0)
                || (envval == NULL)
                || (*envval == 0))
                goto error_args;

        /* overwrite */
        /* Overwrite can be any value: zero or non-zero*/


        /* Cases */
        if ((overwrite != 0) || ((overwrite == 0) && ((exists = getenv(envname)) == NULL)))
        {
                /* envname does not exist or, if it exists, it is allowed to be overwritten. */

                /* malloc */
                /* No error return value for strlen defined */
                en_len = strlen(envname);
                if ((s = (char *) malloc(en_len + strlen(envval) + 2)) == NULL)
                        goto error_malloc;

                /* build string, s, for putenv */
                s[0] = 0;
                if (strcat(s, envname) == NULL)
                        goto error;

                s[en_len] = '=';
                s[en_len + 1] = 0;

                if (strcat(s, envval) == NULL)
                        goto error;

                /* putenv */
                if (putenv(s) != 0)
                        goto error;

                return 0;
        }
        else if ((overwrite == 0) && (exists != NULL))
                return 0;


        /* Never reached. For compiler. */
        return -1;

error:
        /* Roll back and return */
        free(s);

error_malloc:
        errno = ENOMEM;
        return -1;

error_args:
        errno = EINVAL;
        return -1;
}
EDITED: Optimized the code.

Post Reply