Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
requesting help with setenv/unsetenv
#1
requesting help with setenv/unsetenv
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/201703031622...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...
gijoe77 Offline
Tezro

Posts: 349
Threads: 23
Joined: Jun 2018
Find Reply
07-17-2018, 01:07 AM
#2
Re: requesting help with setenv/unsetenv
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/c...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).
mrthinlysliced Offline
Octane

Posts: 71
Threads: 5
Joined: May 2018
Find Reply
07-17-2018, 09:12 AM
#3
Re: requesting help with setenv/unsetenv
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:
<i>
</i>
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
TruHobbyist Offline
O2

Posts: 43
Threads: 6
Joined: May 2018
Find Reply
07-17-2018, 09:15 AM
#4
Re: requesting help with setenv/unsetenv
TruHobbyist post_id=1556 time=1531818928 user_id=84 Wrote: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:
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:
//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:
//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:
//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:
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:
-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


Attached Files
.tar   setenv_program.tar (Size: 10 KB / Downloads: 13)
gijoe77 Offline
Tezro

Posts: 349
Threads: 23
Joined: Jun 2018
Find Reply
07-17-2018, 09:53 PM
#5
Re: requesting help with setenv/unsetenv
You need to cast the result argument of malloc when assigning it to a pointer:
Code:
char * env = (char *) malloc(len);
dexter1 Offline
Global Moderator
******

Posts: 129
Threads: 8
Joined: May 2018
Find Reply
07-17-2018, 11:59 PM
#6
Re: requesting help with setenv/unsetenv
dexter1 post_id=1567 time=1531871996 user_id=141 Wrote:You need to cast the result argument of malloc when assigning it to a pointer:
Code:
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:
-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...
gijoe77 Offline
Tezro

Posts: 349
Threads: 23
Joined: Jun 2018
Find Reply
07-18-2018, 12:49 AM
#7
Re: requesting help with setenv/unsetenv
This could be because the function is declared static. Try omitting 'static' from either/ or the setenv.c and setenv.h
dexter1 Offline
Global Moderator
******

Posts: 129
Threads: 8
Joined: May 2018
Find Reply
07-18-2018, 02:32 AM
#8
Re: requesting help with setenv/unsetenv
gijoe77 post_id=1565 time=1531864421 user_id=243 Wrote: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:
Quote: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
jpstewart Offline
O2

Posts: 42
Threads: 0
Joined: May 2018
Find Reply
07-18-2018, 03:14 PM
#9
Re: requesting help with setenv/unsetenv
dexter1 post_id=1570 time=1531881159 user_id=141 Wrote: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 Smile

jpstewart post_id=1575 time=1531926848 user_id=129 Wrote: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:
//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:
//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:
//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:
-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...
gijoe77 Offline
Tezro

Posts: 349
Threads: 23
Joined: Jun 2018
Find Reply
07-18-2018, 10:26 PM
#10
Re: requesting help with setenv/unsetenv
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:
<i>
</i>#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.
TruHobbyist Offline
O2

Posts: 43
Threads: 6
Joined: May 2018
Find Reply
07-19-2018, 08:54 AM


Forum Jump:


Users browsing this thread: 1 Guest(s)