1

I was trying to write a program that checks out if a location exists under the /home/user directory. To do that, I had to get the username with the whoami command and add the output of it to the buffer to use the locate command.

However, even though the snprintf read the whoami command, it didn't read the rest. I made a couple of searches and came to a result that NULL may not be terminated at the end of the string. Nevertheless, I couldn't find out how to terminate it manually. I am not sure what the problem is, so, here I am.

Here is the code for a better demonstration of my issue:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#include <string.h>
    
char *readFile(char *);
bool check();
    
bool check() {
    char path[200] = { 0 };
    
    snprintf(path, 200, "/home/%s/.example", readFile("whoami"));
    
    char lll[300] = { 0 };
    
    snprintf(lll, 300, "locate %s", path);
    
    char *buffer = readFile(lll);
    
    if (strcmp(buffer, path) == 0) {
        return true;
    } else {
        return false;
    }
}

char *readFile(char cmd[200]) {
    char cmd1[99999] = { 0 };
    system("touch cmd");  
    snprintf(cmd1, 99999, "%s >> cmd", cmd);
    system(cmd1); 
        
    FILE *f = fopen("cmd", "rt");
    assert(f);
    fseek(f, 0, SEEK_END);
    long length = ftell(f);
    fseek(f, 0, SEEK_SET);
    char *buffer = (char *)malloc(length + 1);
    buffer[length] = '\0';
    fread(buffer, 1, length, f);
    fclose(f);
    system("rm cmd");
    return buffer;
}
    
int main() {
    int x = check();
    
    if (x == 1)
        printf("There is a location like that");
    else
        printf("There isn't");
    
    return 0;
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • You should be using `getuid()` with `getpwuid()` to find the username and home directory, instead of that mess. – Shawn Nov 08 '20 at 15:20
  • You can ensure a string that was prevented from overflowing a buffer is terminated, by writing a terminator to its last element: `path[199] = 0;`. However *"The `snprintf` function always stores a terminating null character, truncating the output if necessary."* It is `strncpy()` and `strncat()` which might not. – Weather Vane Nov 08 '20 at 15:27
  • what is the result of `readFile("whoami")`? this seems like a problem trivially solvable using a debugger – Chase Nov 08 '20 at 15:27
  • @Shawn Thank you for the suggestion! Can you give a source that illustrates its usage or documentation? – Omer Erbilgin Nov 08 '20 at 15:29
  • Do you mean me? Please see [`snprintf`](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/snprintf-snprintf-snprintf-l-snwprintf-snwprintf-l). Oh, you edited the request! – Weather Vane Nov 08 '20 at 15:30
  • @Chase it is 'eggman' which is my username in linux mint – Omer Erbilgin Nov 08 '20 at 15:30
  • @OmerErbilgin properly null terminated? – Chase Nov 08 '20 at 15:32
  • @WeatherVane Oh, so its just all about terminating the null. But how am I going to terminate null to the end of a buffer which is different from a char array. – Omer Erbilgin Nov 08 '20 at 15:32
  • The return value from `fread` tells you how many data items were read (of the size requested). If it's text that needs to be terminated, you are using the wrong reading function. `fread` is intended for use with binary data, although it is sometimes used to read an entire text file into a buffer for analysis. – Weather Vane Nov 08 '20 at 15:34
  • @WeatherVane Never thought about that, thanks for your answer and time! By the way, may I ask a recommendation for 'fread' if you dont mind. – Omer Erbilgin Nov 08 '20 at 15:39
  • My previous link posted has an index of functions. – Weather Vane Nov 08 '20 at 15:41
  • See the man pages and https://stackoverflow.com/questions/2910377/get-home-directory-in-linux – Shawn Nov 08 '20 at 19:25
  • If you want the output of the `whoami` command, just use `popen`, that is much simpler and safer than mucking around with temporary files. In addition, you can't assume the current home directory or default home directory is `/home/USERNAME`. The system administrator may use a different scheme. To get the current home directory you should read the `HOME` environment variable: `const char * homedir = getenv("HOME");` – HAL9000 Nov 08 '20 at 21:27
  • @HAL9000 Thanks for your contribution to my question but my main aim is to use the snprintf() without separating strings while combining them together. – Omer Erbilgin Nov 08 '20 at 21:32
  • @OmerErbilgin, yes I understand you want to know why `snprintf` doesn't give the result you expected. But my comment is also for other users. Somebody could use your code as example on how to get current homedir. – HAL9000 Nov 08 '20 at 21:46
  • You could use `popen` in `readFile()` instead of creating a temporary file. – KamilCuk Nov 08 '20 at 22:28

3 Answers3

1

The whoami command prints out the name of the current user, termintated by a newline. Reading back the file cmd into buffer will include that same newline character.

The path string will then be "/home/USERNAME\n/.example". Trying to execute locate /home/USERNAME\n/.example will probably confuse the system function.

The solution should then be to strip away the last newline in readFile.

HAL9000
  • 2,138
  • 1
  • 9
  • 20
  • Great! I solved the problem by using fflush(stdout) after adding the string that holds home path. Thanks for your help. – Omer Erbilgin Nov 09 '20 at 16:07
0

There are some problems in your readFile function:

  • the name is misleading, it should be runCommand or something equivalent.

  • you append the command output to the cmd file. This will cause unexpected results if the cmd file exists before you run your program. You should instead write:

    snprintf(cmd1, 99999, "%s > cmd", cmd);
    
  • the command output probably has a trailing newline. You should trim the output.

Here is a modified version:

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

char *readFile(const char *cmd) {
    /* construct the command */
    size_t size = 4 + strlen(cmd) + 1;
    char *cmd1 = malloc(size);
    assert(cmd1);
    snprintf(cmd1, size, "%s > cmd", cmd);

    /* run the command */
    system(cmd1);
    free(cmd1);
        
    /* read the output file */
    FILE *f = fopen("cmd", "rt");
    assert(f);
    fseek(f, 0, SEEK_END);
    long length = ftell(f);
    fseek(f, 0, SEEK_SET);
    char *buffer = malloc(length + 1);
    buffer[length] = '\0';
    fread(buffer, 1, length, f);
    fclose(f);
    unlink("cmd");

    /* trim trailing white space from the output */
    while (length > 0 && isspace((unsigned char)buffer[length - 1])) {
        buffer[--length] = '\0';
    }
    /* trim leading white space from the output */
    size_t i = 0;
    while (isspace((unsigned char)buffer[i]) {
        i++;
    }
    if (i > 0) {
        memmove(buffer, buffer + i, length - i + 1);
    }
    return buffer;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
0

Since Linux has a shortcut for your home directory ("~"), the whole "whoami"/"getuid" mess can be avoided by just using:

char *buffer = readFile("locate ~/.example");

You will still potentially have to trim off trailing newline and/or CR characters, perhaps with:

strtok(buffer,"\r\n\t ");

Or better yet, as @chqrlie suggests below.

SGeorgiades
  • 1,771
  • 1
  • 11
  • 11
  • Re *"Since Linux has a shortcut for your home directory ("~"),"*: Is it shell dependent (e.g. [Bash](https://en.wikipedia.org/wiki/Bash_%28Unix_shell%29)) or not? – Peter Mortensen Dec 11 '20 at 15:44
  • That may be correct, but a more universal solution would be to use "$HOME", which is defined under all shells. – SGeorgiades Jan 05 '21 at 22:40
  • @SGeorgiades Completely correct. I was just a beginner on C & bash, well I still am, so wasn't aware of that. To contribute, besides $HOME which returns more than the home directory, at least in ubuntu, I believe getenv() is also a cross platform method for getting standard paths. – Omer Erbilgin Jan 31 '21 at 02:51