Serket
Serket

Reputation: 4465

(C) scanf not working when the terminate value is [^\n]

I am creating a program where you can execute terminal commands via the program. I want to access a directory (using cd /Users/user/Desktop) but since scanf terminates at a whitespace, I was forced to change the terminate value to [^\n]. That's when the error popped up. Whenever I enter in a command, it executes said command, but then the next time the program goes through the (infinite) loop, it doesn't stop executing the line before the scanf function. This hasn't happened when the terminate value was %s. Here is the code for the program:

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

void execute(){
    char* command = (char *) malloc(15);
    char* output = (char *) malloc(4096);
    printf(">> ");
    scanf("%[^\n]", command);            //Here is the scanf function
    FILE* cmd = popen(command, "r");
    fread(output, sizeof(output), 32000, cmd);
    if (strlen(output) != 0){
        printf("\n%s\n", output);
    }
    free(output);
    pclose(cmd);
}

int main(){
    while (1){
        execute();
    }
}

Here is the output when the terminate value is [^\n]:

>> ls

Applications
Desktop
Documents
//Here the rest of the contents in my user folder appear (twice for some reason, that's also an issue related to this).

>> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> 
>> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> 
>> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> 
>> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> 
// This then goes on forever

and here is the output when the terminate value is %s:

>> ls

Applications
Desktop
Documents
//Here the rest of the contents in my user folder appear (once)

>> //Here I can input things again

Could someone show me how to fix this? (I tried gets(), same outcome)

Upvotes: 3

Views: 685

Answers (2)

Serket
Serket

Reputation: 4465

Here is the fixed code (other than the cd issue) (Another user has posted the final version of the code. Their answer has been marked as the correct answer). IGNORE THE FOLLOWING CODE, IT IS NO LONGER RELEVANT:

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

int main(){

    while(1){
        char* command = (char *) malloc(30);
        char* output = (char *)  malloc(5000);
        printf(">> ");
        fgets(command, 30, stdin);
        printf("%s", command);
        FILE *cmd = popen(command, "r");
        while (fgets(output, 5000, cmd) != NULL){
            printf("%s", output);
        }
        pclose(cmd);
        free(command);
        free(output);
    }
    return 0;
}

Upvotes: -1

Since you're telling scanf not to read the \n, it's leaving it in stdin, so when the loop repeats, it's still there and causes the next iteration to immediately return an empty string. To fix it, a few choices:

  • Change "%[^\n]" to " %[^\n]" to ignore leading whitespace.
  • Add getchar(); after scanf("%[^\n]", command); to consume the newline.
  • Use fgets or some other reading function other than scanf. (Not gets; it can't be used safely!)

You're also not checking whether scanf returned anything, so if it failed to parse anything at all, then you're passing uninitialized memory to popen.

Side note: there's lots of other bugs in your program, but they're not relevant to your immediate problem:

  1. You leak the memory of command each time through your loop. To fix it, add free(command); somewhere after the popen and before the end of the function.
  2. You didn't pass a maximum field width to scanf, so if you enter more than 14 characters, you'll have a buffer overflow and corrupt memory. To fix it, change %[^\n] to %14[^\n] (14 and not 15 so there's room for the null terminator). You should also detect the case of a partial read and handle it properly, to avoid echo Their alarm is set from deleting files called is and set, for example.
  3. sizeof(output) will be the size of the pointer, not of the memory you allocated that it points to, and 32000 seems to have come out of thin air. Change sizeof(output) to 1 and 32000 to 4096.
  4. fread doesn't null-terminate its output, so printing it with %s will print uninitialized memory after it. To fix it, either use something other than fread to get the output, or use its return value and make sure you only print that many characters.

With your "fixed" version, there's still a few more problems:

  • Your first fgets won't buffer-overflow now, but overly-long strings will still cause problems. In particular, if a user enters a command that's too long, it will act as if they hit Enter in the middle of it, resulting in two partial commands being ran.
  • Doing cd in a subprocess doesn't affect the parent process or any other subprocesses. To make it work, you'd need to check to see if the command they entered starts with cd, and if so, call chdir directly instead of doing popen.

Here's a way that works right:

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

int main(){
    char *command = NULL;
    size_t commandsize = 0;
    char *output = (char *)malloc(5000);
    while(1){
        printf(">> ");
        ssize_t commandlen = getline(&command, &commandsize, stdin);
        if(commandlen < 0) {
            // assume EOF. Small chance that it was an error though
            break;
        }
        printf("%s", command);
        if(!strcmp(command, "exit\n")) {
            break;
        }
        if(!strncmp(command, "cd ", 3)) {
            command[commandlen - 1] = '\0'; // remove the newline
            if(chdir(command + 3)) {
                perror("chdir");
            }
            continue;
        }
        FILE *cmd = popen(command, "r");
        if(!cmd) {
            perror("popen");
            continue;
        }
        size_t sz;
        while ((sz = fread(output, 1, 5000, cmd)) > 0){
            fwrite(output, 1, sz, stdout);
        }
        pclose(cmd);
    }
    free(command);
    free(output);
    return 0;
}

A few notes about it:

  • Instead of malloc and freeing stuff every time through the loop, I moved those variables up to function-scope so it only happens once the whole time the program's running.
  • I use getline to read the whole line, and automatically allocate however much space is necessary. This, like popen, isn't part of standard C but is part of POSIX.
  • I use fread and fwrite to shuttle data from the process we started, to avoid having to think about null terminators. I assume you're eventually going to do some sort of processing on this data; if not, consider using system instead of popen, which will automatically write the output back to the user.

Upvotes: 5

Related Questions