1

I have a question pertaining to the extern char **environ. I'm trying to make a C program that counts the size of the environ list, copies it to an array of strings (array of array of chars), and then sorts it alphabetically with a bubble sort. It will print in name=value or value=name order depending on the format value.

I tried using strncpy to get the strings from environ to my new array, but the string values come out empty. I suspect I'm trying to use environ in a way I can't, so I'm looking for help. I've tried to look online for help, but this particular program is very limited. I cannot use system(), yet the only help I've found online tells me to make a program to make this system call. (This does not help).

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
extern char **environ;
int main(int argc, char *argv[])
{
    char **env = environ;
    int i = 0;
    int j = 0;
    printf("Hello world!\n");
    int listSZ = 0;
    char temp[1024];
    while(env[listSZ])
    {
        listSZ++;
    }
    printf("DEBUG: LIST SIZE = %d\n", listSZ);
    char **list = malloc(listSZ * sizeof(char**));
    char **sorted = malloc(listSZ * sizeof(char**));
    for(i = 0; i < listSZ; i++)
    {
        list[i] = malloc(sizeof(env[i]) * sizeof(char));        // set the 2D Array strings to size 80, for good measure
        sorted[i] = malloc(sizeof(env[i]) * sizeof(char));
    }
    while(env[i])
    {
        strncpy(list[i], env[i], sizeof(env[i]));
        i++;
    }           // copy is empty???

    for(i = 0; i < listSZ - 1; i++)
    {
        for(j = 0; j < sizeof(list[i]); j++)
        {
            if(list[i][j] > list[i+1][j])
            {
                strcpy(temp, list[i]);
                strcpy(list[i], list[i+1]);
                strcpy(list[i+1], temp);
                j = sizeof(list[i]);                    // end loop, we resolved this specific entry
            }
            // else continue
        }
    }

This is my code, help is greatly appreciated. Why is this such a hard to find topic? Is it the lack of necessity?

EDIT: Pasted wrong code, this was a separate .c file on the same topic, but I started fresh on another file.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • `strncpy` is not the right tool! read this: https://randomascii.wordpress.com/2013/04/03/stop-using-strncpy-already/ – chqrlie Apr 28 '18 at 09:40

3 Answers3

1

In a unix environment, the environment is a third parameter to main.

Try this:

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

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

   while (*envp) {
   printf("%s\n", *envp);
   *envp++;
   }
 }
Leonard
  • 13,269
  • 9
  • 45
  • 72
  • 1
    That is another way to get at the environment, but the global variable is also valid, and can be used even in functions other than `main()`, without `main()` having to pass its third parameter. – Jonathan Leffler Apr 28 '18 at 03:40
  • Very interesting, thanks. Why is it that there is a value for the number of arguments passed but not a similar value for the number of environment variables available? – jess Apr 28 '18 at 05:19
1

There are multiple problems with your code, including:

  • Allocating the 'wrong' size for list and sorted (you multiply by sizeof(char **), but should be multiplying by sizeof(char *) because you're allocating an array of char *. This bug won't actually hurt you this time. Using sizeof(*list) avoids the problem.
  • Allocating the wrong size for the elements in list and sorted. You need to use strlen(env[i]) + 1 for the size, remembering to allow for the null that terminates the string.
  • You don't check the memory allocations.
  • Your string copying loop is using strncpy() and shouldn't (actually, you should seldom use strncpy()), not least because it is only copying 4 or 8 bytes of each environment variable (depending on whether you're on a 32-bit or 64-bit system), and it is not ensuring that they're null terminated strings (just one of the many reasons for not using strncpy().
  • Your outer loop of your 'sorting' code is OK; your inner loop is 100% bogus because you should be using the length of one or the other string, not the size of the pointer, and your comparisons are on single characters, but you're then using strcpy() where you simply need to move pointers around.
  • You allocate but don't use sorted.
  • You don't print the sorted environment to demonstrate that it is sorted.
  • Your code is missing the final }.

Here is some simple code that uses the standard C library qsort() function to do the sorting, and simulates POSIX strdup() under the name dup_str() — you could use strdup() if you have POSIX available to you.

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

extern char **environ;

/* Can also be spelled strdup() and provided by the system */
static char *dup_str(const char *str)
{
    size_t len = strlen(str) + 1;
    char *dup = malloc(len);
    if (dup != NULL)
        memmove(dup, str, len);
    return dup;
}

static int cmp_str(const void *v1, const void *v2)
{
    const char *s1 = *(const char **)v1;
    const char *s2 = *(const char **)v2;
    return strcmp(s1, s2);
}

int main(void)
{
    char **env = environ;
    int listSZ;

    for (listSZ = 0; env[listSZ] != NULL; listSZ++)
        ;
    printf("DEBUG: Number of environment variables = %d\n", listSZ);

    char **list = malloc(listSZ * sizeof(*list));
    if (list == NULL)
    {
        fprintf(stderr, "Memory allocation failed!\n");
        exit(EXIT_FAILURE);
    }

    for (int i = 0; i < listSZ; i++)
    {
        if ((list[i] = dup_str(env[i])) == NULL)
        {
            fprintf(stderr, "Memory allocation failed!\n");
            exit(EXIT_FAILURE);
        }
    }

    qsort(list, listSZ, sizeof(list[0]), cmp_str);

    for (int i = 0; i < listSZ; i++)
        printf("%2d: %s\n", i, list[i]);

    return 0;
}

Other people pointed out that you can get at the environment via a third argument to main(), using the prototype int main(int argc, char **argv, char **envp). Note that Microsoft explicitly supports this. They're correct, but you can also get at the environment via environ, even in functions other than main(). The variable environ is unique amongst the global variables defined by POSIX in not being declared in any header file, so you must write the declaration yourself.

Note that the memory allocation is error checked and the error reported on standard error, not standard output.

Clearly, if you like writing and debugging sort algorithms, you can avoid using qsort(). Note that string comparisons need to be done using strcmp(), but you can't use strcmp() directly with qsort() when you're sorting an array of pointers because the argument types are wrong.

Part of the output for me was:

DEBUG: Number of environment variables = 51
 0: Apple_PubSub_Socket_Render=/private/tmp/com.apple.launchd.tQHOVHUgys/Render
 1: BASH_ENV=/Users/jleffler/.bashrc
 2: CDPATH=:/Users/jleffler:/Users/jleffler/src:/Users/jleffler/src/perl:/Users/jleffler/src/sqltools:/Users/jleffler/lib:/Users/jleffler/doc:/Users/jleffler/work:/Users/jleffler/soq/src
 3: CLICOLOR=1
 4: DBDATE=Y4MD-
…
47: VISUAL=vim
48: XPC_FLAGS=0x0
49: XPC_SERVICE_NAME=0
50: _=./pe17

If you want to sort the values instead of the names, you have to do some harder work. You'd need to define what output you wish to see. There are multiple ways of handling that sort.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Why use `memmove` instead of `memcpy` in dup_str`? and pardon my pickiness, but `len` is an unfortunate name for what is really a size, not the length of the string. – chqrlie Apr 28 '18 at 09:37
  • I always use `memmove()` because it always works. Since `memcy()` doesn’t exist, I assume you have `memcpy()` in mind. It doesn’t always work. Granted, it could be used safely here, but I prefer to reach safer coding. As to `Len`: that is the length of the memory block I’m allocating and copying. – Jonathan Leffler Apr 28 '18 at 09:40
  • Your argument for `memmove` makes sense, but regarding `len`, the name `size` would be less confusing for the *size* of the memory block. The C Standard consistently uses *size* to refer to the size in bytes of the object allocated by `malloc` to avoid confusion with the *length* of the array is contains, expressed in a potentially different unit and the length of a string which does not include the null terminator. Avoiding potential confusion is also reaching for safer coding. – chqrlie Apr 28 '18 at 09:45
0

To get the environment variables, you need to declare main like this:

int main(int argc, char **argv, char **env);

The third parameter is the NULL-terminated list of environment variables. See:

#include <stdio.h>

int main(int argc, char **argv, char **environ)
{
    for(size_t i = 0; env[i]; ++i)
        puts(environ[i]);

    return 0;
}

The output of this is:

LD_LIBRARY_PATH=/home/shaoran/opt/node-v6.9.4-linux-x64/lib:
LS_COLORS=rs=0:di=01;34:ln=01;36:m
...

Note also that sizeof(environ[i]) in your code does not get you the length of the string, it gets you the size of a pointer, so

strncpy(list[i], environ[i], sizeof(environ[i]));

is wrong. Also the whole point of strncpy is to limit based on the destination, not on the source, otherwise if the source is larger than the destination, you will still overflow the buffer. The correct call would be

strncpy(list[i], environ[i], 80);
list[i][79] = 0;

Bare in mind that strncpy might not write the '\0'-terminating byte if the destination is not large enough, so you have to make sure to terminate the string. Also note that 79 characters might be too short for storing env variables. For example, my LS_COLORS variable is huge, at least 1500 characters long. You might want to do your list[i] = malloc calls based based on strlen(environ[i])+1.

Another thing: your swapping

strcpy(temp, list[i]);
strcpy(list[i], list[i+1]);
strcpy(list[i+1], temp);
j = sizeof(list[i]);

works only if all list[i] point to memory of the same size. Since the list[i] are pointers, the cheaper way of swapping would be by swapping the pointers instead:

char *tmp = list[i];
list[i] = list[i+1];
list[i+1] = tmp;

This is more efficient, is a O(1) operation and you don't have to worry if the memory spaces are not of the same size.

What I don't get is, what do you intend with j = sizeof(list[i])? Not only that sizeof(list[i]) returns you the size of a pointer (which will be constant for all list[i]), why are you messing with the running variable j inside the block? If you want to leave the loop, the do break. And you are looking for strlen(list[i]): this will give you the length of the string.

Pablo
  • 13,271
  • 4
  • 39
  • 59
  • I certainly appreciate your feedback, you've given me a lot to work with. I suppose I didn't quite understand what I was working with when trying to get sizeof the pointer. I'll work with this and see how it turns out. Thanks again! – Dawson Binder Apr 28 '18 at 02:54
  • That's one way to do it; using `environ` is another. – Jonathan Leffler Apr 28 '18 at 03:36