1

Im trying to write and implement the command 'tree -pugs' in linux on C languange. It running well but I have leak issues when I run the program with valgrind flag. I have tried to free the variable but I got segmentation fault.

heres my code:

int count_dir = 0;
int count_file = 0;
int count_total = 0;
char *user_name = "";
char *group_name = "";
long file_size = 0;
char last_type;
char *pre_name;
mode_t pre_mode;
int tree_walk(const char *name, const struct stat *status, int type, struct FTW *ftw)
{
    
    if (type == FTW_D || type == FTW_F)
    {
        curr_level = ftw->level;

        if (pre_level != 0 && (count_file + count_dir != 0))
        {
            
            for (size_t i = 0; i < 9; i++)
            {
                printf("%c", Permissions[i]);
            }
            
            printf(" %s\t%s %15ld]  %s\n", user_name, group_name, file_size, pre_name);

            if ((count_dir + count_file) == count_total - 1)
            {
                for (size_t i = 0; i < 9; i++)
                {
                    printf("%c", Permissions[i]);
                }
                printf(" %s\t%s %15ld]  %s\n\n", user_name, group_name, file_size, pre_name);
            }
        }

        if (type == FTW_D && strcmp(".", name) != 0)
            count_dir++;
    }
    return 0;
}

int main(int argc, char const *argv[])
{
    int flag = 0;

    if (argc == 1)
    {
        nftw(".", tree_walk_counter, 10, flag);
        nftw(".", tree_walk, 10, flag);
    }
    else if (argc == 2)
    {
        nftw(argv[1], tree_walk_counter, 10, flag);
        nftw(argv[1], tree_walk, 10, flag);
    }
    else
    {
        fprintf(stderr, "write ./stree \"directory name\" or just ./stree\n");
        exit(1);
    }
    char * dirs;
    char * files; 
    if (count_dir == 1) dirs = "directory";
    else dirs = "directories";
    if (count_file == 1) files = "file";
    else files = "files";
    
    printf("%d %s, %d %s\n", count_dir, dirs, count_file, files);
    
    return 0;
}

when i run with valgring:


==7132== 
==7132== HEAP SUMMARY:
==7132==     in use at exit: 1,158 bytes in 182 blocks
==7132==   total heap usage: 369 allocs, 187 frees, 595,272 bytes allocated
==7132== 
==7132== LEAK SUMMARY:
==7132==    definitely lost: 1,137 bytes in 179 blocks
==7132==    indirectly lost: 0 bytes in 0 blocks
==7132==      possibly lost: 0 bytes in 0 blocks
==7132==    still reachable: 21 bytes in 3 blocks
==7132==         suppressed: 0 bytes in 0 blocks
==7132== Rerun with --leak-check=full to see details of leaked memory
==7132== 
==7132== For lists of detected and suppressed errors, rerun with: -s
==7132== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Any advice? I tried to put free on the variables but it did segmentation fault

Maoz Lev
  • 11
  • 3
  • *I tried to put free on the variables but it did segmentation fault* - surely you need to "put" it, but in a correct way. Since we don't know what you have done we can't tell what is wrong with it. – Eugene Sh. Nov 30 '21 at 16:56
  • _" I tried to put free on the variables "_: which variables? Show your code. Be aware that you can call `free` only and only on pointers that have been returned by `malloc` and friends – Jabberwocky Nov 30 '21 at 16:56
  • What about doing the tips from valgrind `Rerun with --leak-check=full to see details of leaked memory` ? – Ôrel Nov 30 '21 at 17:11
  • All the variable are at the top beneath the includes. They are there because I need to know the last file – Maoz Lev Nov 30 '21 at 17:11
  • @MaozLev don't describe your code, but __show__ it. You can [edit] your question. Show at least 3-4 lines of your code where you call `free`. But as I wrote before, you cannot free something that hasn't been returned by `malloc`. – Jabberwocky Nov 30 '21 at 17:35
  • 2
    Oh and `malloc(sizeof(getpwuid(lStatus.st_uid)->pw_name))` etc. is all wrong. You want `malloc(strlen(getpwuid(lStatus.st_uid)->pw_name) + 1)`, (the + 1 is not a typo). `sizeof` does not determine the length of a string. Read closely the chapter dealing with strings in your learning material. – Jabberwocky Nov 30 '21 at 17:37
  • Oups. and also `file_size = (intptr_t)(malloc(sizeof(lStatus.st_size)));` makes absolutely no sense. I think you have a lot of misconceptions about dynamic memory allocations `malloc` etc. and when to use it and when not. – Jabberwocky Nov 30 '21 at 17:45
  • You might want to try `-fsanitize=address` instead of valgrind. It surely would have found the `malloc(sizeof(char*))` problem identified two comments earlier. (You can't use both `-fsanitize=address` and `valgrind` at the same time) – ikegami Nov 30 '21 at 17:46

1 Answers1

1

This is a showcase for why casting the results of malloc is a poor decision in C. You are hiding a number of errors by explicitly casting pointer values (void *) to integer values (intptr_t), which then get implicitly converted to their left-hand side types (mode_t, char, long).

Explicit casts should be reserved for when you know exactly what you are doing. They should not be used to hammer solutions into place.

The main issue here is that you have largely misunderstood when it is appropriate to allocate dynamic memory, and how to do so.

For example, this global definition

mode_t pre_mode;

already allocates enough memory for a mode_t object. Additionally, its lifetime is the lifetime of your entire program. This rules out the two main reasons to use dynamic memory allocation in the first place.

These lines

    pre_mode = (intptr_t)(malloc(sizeof(lStatus.st_mode)));
    pre_mode = lStatus.st_mode;

are totally conflicted. The first line allocates sizeof (mode_t) bytes of memory, takes the resulting void * and explicitly casts it to an inptr_t, which in turn is implicitly cast to a mode_t value.

We can see -Wconversion highlights an issue with this

stree.c:187:28: warning: conversion from ‘long int’ to ‘mode_t’ {aka ‘unsigned int’} may change value [-Wconversion]
  187 |                 pre_mode = (intptr_t)(malloc(sizeof(lStatus.st_mode)));

as a potential change in the value might result in the memory address being the incorrect value to pass to free, even if cast back to an appropriate pointer type.

This value is discarded in the second line when you overwrite it. The memory is absolutely leaked at this point in time.

Likewise,

*Permissions = (intptr_t)(malloc(9));

allocates nine bytes, casts to an integer, and then places this value in *Permissions (a.k.a, Permissions[0]), as a char value.

Similar issues for last_type and file_size.

None of these require dynamic memory allocation, they already have all the memory they need, and are not of the correct type to hold memory addresses anyway.


Conversely, user_name and group_name do have a use for dynamic memory if they are to hold a variable length string, copied from getpwuid and getgrgid return values. It is of course important to have your own memory for these, as the structures returned by these functions likely occupy static memory, and will be overwritten by subsequent calls.

The problem is your attempted use of sizeof to determine the length of a string. The sizeof operator does not do this, as it is for determining the size (in bytes) of an object or type.

To figure out the length of a string, you use strlen. To have enough memory to hold this string and its null-terminating byte ('\0') you add one to this value.

struct passwd *pwd = getpwuid(lStatus.st_uid);         
user_name = malloc(strlen(pwd->pw_name) + 1);                 
strcpy(user_name, pwd->pw_name);

Oddly enough, you do this moments later with pre_name:

 pre_name = malloc(strlen(name + ftw->base) + 1);

Alternatively, you could use statically allocated buffers of reasonable sizes, and ensure any copied strings will not exceed these limits.


Finally, every single call to malloc will, at some point in time, require a mirrored call to free.

If you call malloc 7 times in a loop, you must also eventually call free 7 times, passing in each of the values returned from malloc.

Additionally, malloc can fail, returning NULL. You should consider what to do in this event.


Also,

if (file_name[i] == '/' & file_name[i + 1] == '.')

basic compiler warnings would let you know that this expression probably wants logical and (&&) not bitwise and (&). That said, this condition and its surrounding loop could be simplified to

if (name[ftw->base] == '.')
Oka
  • 23,367
  • 6
  • 42
  • 53