Thread: Help with a simple Terminal Project

  1. #1
    Registered User
    Join Date
    Sep 2010
    Posts
    8

    Help with a simple Terminal Project

    Hello all. I've used the threads in this board before for a lot of help, but just now posting.
    I'm trying to code a simple Terminal as part of an OS course. I feel like I have a good start on it, and I'm trying to make sure my input function works as I want it to.
    The only problem, for some reason I'm getting nothing. I'm guessing there's an error early on in main() but I can't find it for the life of me.
    reason I believe it's here, is that I'm not even getting my prompt, which should come up before any input.
    prompt is this part:

    char cwd[512];
    getcwd(cwd, 512);
    printf("%s>", cwd);

    just after the start of the while loop in main.
    It's been a long time since I've had to work in C, so I know my pointer-fu is off. Please be gentle. Any help greatly appreciated.
    Code:
    int getInput(char *outputBuffer, int currentPos, char *breakCharacter){
    
                                                        //Declare:
    
        char *output = outputBuffer + currentPos;        //outputBuffer
    
        int position = currentPos;                        //outputPosition = 0
    
        char innerBuffer[256];                            //innerBuffer
    
        char *broken = breakCharacter;                    //brokenCharacter
    
        
    
        //while not broken
    
        while(1){
    
            //read to innerBuffer
    
            int bytesRead;
    
            bytesRead = read(0, innerBuffer, 256);
    
            //for each character in innerBuffer
    
            int i;
    
            for(i = 0; i < bytesRead; i++){
    
                //if current character is an ignore character
    
                    //- do nothing
    
                if(bytesRead - i >= 3 && ((innerBuffer[i] == 27 && innerBuffer[i+1] == 91 && innerBuffer[i+2] ==68)
    
                || (innerBuffer[i] == 27 && innerBuffer[i+1] == 91 && innerBuffer[i+2] ==67)))
    
                    continue;
    
                //else if current character is a break character
    
                    //- set brokenCharacter
    
                    //- break out of loops
    
                else if(innerBuffer[i] == '\n'){
    
                    *broken = '\n';
    
                    break;
    
                }
    
                else if(bytesRead - i >= 3 && (innerBuffer[i] == 27 && innerBuffer[i+1] == 91 && innerBuffer[i+2] ==65)){
    
                    *broken = 'u'; //for up, saves 2 characters and allows for more uniform code.
    
                    break;
    
                }
    
                else if(bytesRead - i >= 3 && (innerBuffer[i] == 27 && innerBuffer[i+1] == 91 && innerBuffer[i+2] ==66)){
    
                    *broken = 'd'; //for down
    
                    break;
    
                }
    
                //else if current character is backspace or delete
    
                //- print \b
    
                //- decrement outputPosition
    
                if(innerBuffer[i] == 8 || innerBuffer[i] == 127){
    
                    if(position > 0){
    
                        putchar('\b');
    
                        position--;
    
                    }
    
                }
    
                //else
    
                //- print current character
    
                //- copy the current character to outBuffer at 
    
                  //outputPosition
    
                //- increment the outputPosition
    
                else{
    
                    putchar(innerBuffer[i]);
    
                    output[position] = innerBuffer[i];
    
                    position++;
    
                }
    
            }
    
            
    
        }
    
        //Return:
    
        //outputBuffer
    
        //size of outputBuffer
    
        //brokenCharacter
    
        outputBuffer = output;
    
        return position;
    
    }
    
    
    
    int main()
    
    {
    
        /*
    
        //configure the input (termios)
    
        // get the original configuration
    
        struct termios origConfig;
    
        tcgetattr(0, &origConfig);
    
    
    
        // create a copy of the original configuration
    
        struct termios newConfig = origConfig;
    
    
    
        // adjust the new configuration
    
        newConfig.c_lflag &= ~(ICANON|ECHO);
    
        newConfig.c_cc[VMIN] = 10;
    
        newConfig.c_cc[VTIME] = 2;
    
    
    
        // set the new configuration
    
        tcsetattr(0, TCSANOW, &newConfig);
    
        */
    
        char command[256];
    
        char breakCharacter;
    
        char *brokenCharacter = &breakCharacter;
    
        int commandSize = 0;
    
        //while not exited
    
        while(1){
    
            //print prompt
    
            char cwd[512];
    
            getcwd(cwd, 512);
    
            printf("%s>", cwd);
    
            //read input (break on up, down and \n)
    
            commandSize = getInput(command, commandSize, brokenCharacter);
    
            //if broken on up or down
    
            if(breakCharacter == 'd' || breakCharacter == 'u'){
    
                //clear the current input
    
                int i;
    
                for(i = commandSize; i > 0; i++){
    
                    putchar('\b');
    
                    putchar(' ');
    
                    putchar('\b');
    
                }
    
                //execute appropriate command to generate corresponding message
    
                if(breakCharacter == 'u'){    
    
                    strcpy(command, getenv("UserName"));
    
                    commandSize = strlen(command);
    
                }
    
                else{
    
                    time_t rawtime;
    
                    struct tm * timeinfo;
    
                    time ( &rawtime );
    
                    timeinfo = localtime ( &rawtime );
    
    
    
                    strcpy(command, asctime(timeinfo));
    
                    commandSize = strlen(command);
    
                }
    
                continue;
    
            }
    
            else{
    
                //parse input into a command
    
                //if cd command
    
                    //change the current working directory
    
                //else if exit command
    
                    //break
    
                    break;
    
                //else
    
                    //execute the command
    
            }
    
        }
    
        //restore the input configuration (termios)
    
        //tcsetattr(0, TCSANOW, &origConfig);
    
        return 0;
    
    }
    largely commented area at the beginning of main is to set terminal io, but commented in the off chance it was the issue.

  2. #2
    Registered User
    Join Date
    Mar 2008
    Location
    Coimbra, Portugal
    Posts
    85
    You could try stepping through it with a debugger. I can't see anything wrong with the code up until that point myself, maybe others can...

    EDIT: Is it possible that part of your I/O setup code is disabling terminal output?
    Last edited by Jorl17; 09-18-2010 at 06:44 PM.

  3. #3
    Registered User
    Join Date
    Sep 2010
    Posts
    8

    Should have said more

    Actually, I've been stepping through for a good 30 minutes now. It seems I get random errors. Using ddd on linux. Keeps giving syntax errors on areas after the prompt should be going out.
    Also, that I/O setup portion is commented out, so it shouldn't be affecting it now (It basically turns off echo, and sets a minimum time and maximum bytes of data between reads so as to handle special characters)

  4. #4
    Registered User
    Join Date
    Mar 2008
    Location
    Coimbra, Portugal
    Posts
    85
    I just compiled it and debugged it. It's stuck at your getInput function. It so just happens that, since it gets stuck, the data doesn't get flushed to stdout. Do this:

    Code:
    printf("%s >", cwd); fflush(stdout);
    And, voila!

  5. #5
    Registered User
    Join Date
    Sep 2010
    Posts
    8
    Perfect, now I know where I need to start looking!
    Thank you!

  6. #6
    Registered User
    Join Date
    Sep 2010
    Posts
    8
    OK, my issue is definitely in the getInput process.
    I think my issue was in using read() to get my information. fread works better (gets me my prompt) but still not getting past that statement.
    Is anyone here good with Terminal IO configuration? resources I have on it aren't the greatest.
    Code:
    //configure the input (termios)
    
    	// get the original configuration
    
    	struct termios origConfig;
    
    	tcgetattr(0, &origConfig);
    
    
    
    	// create a copy of the original configuration
    
    	struct termios newConfig = origConfig;
    
    
    
    	// adjust the new configuration
    
    	newConfig.c_lflag &= ~(ICANON|ECHO);
    
    	newConfig.c_cc[VMIN] = 10;
    
    	newConfig.c_cc[VTIME] = 2;
    
    
    
    	// set the new configuration
    
    	tcsetattr(0, TCSANOW, &newConfig);
    What I'm trying to do is to control input to not echo, and to give me every character as they type so I can check for special characters. This was the code we were given as an example, but since all my special characters are backspace, delete, and arrow keys, I think I should be able to change VMIN to 3 (3 bytes of data each) and VTIME to 1 (since they'll come in simultaneously).
    Otherwise, I'm supposed to be getting each keypress as it comes, check for special characters (ones mentioned above, plus '\n'), and then output, unless it is a special character.
    I'll be the first to admit, I have a fairly lousy understanding of C input/output, since most of my background is in Java, C++, and Python.
    Any advice on what I should be using to get the input? fgets, fread?
    Again, I'm still fiddling with this on my own, but any help is greatly appreciated.

  7. #7
    Registered User
    Join Date
    Sep 2010
    Posts
    8
    Quote Originally Posted by codecaine_21 View Post
    This is pretty damn intimidating!! I am a beginner at C, i know all of the basics like i/o, arrays, etc. But this piece of code looks so long and very complex!!! How long did it take you to learn this??
    Let's just say I've been in school way too long.
    ...
    I started learning basic and simple flashcard programs in 8th grade, and then did a lot of reading on my own in highschool. Since then I've been in CS courses for about 8 years. Lots of this is stuff I've learned in C++ courses, and this is really only the second course I've had to use C in.
    In case any other students read this, if you have the time and money, spend a few years in community college taking courses on every language you can. When you finally get your butt in gear and go for your Degree, you'll normally find yourself well ahead of the crowd.

  8. #8
    Registered User
    Join Date
    Sep 2010
    Posts
    8
    having a lot more luck just using getchar and putchar. It effectively does everything I need it to.
    Any info on my input situation still much appreciated.

  9. #9
    Registered User
    Join Date
    Sep 2010
    Posts
    8
    Well, I've gotten pretty far. input and output seem to work alright, I can run commands (although I've only really tested ls), and it will exit on exit. Only issue is my cd command seems to go on and off. Works the first time when do "cd test" and go to projectFolder/test, works again when I do "cd .." and then once more so I'm at the folder above. trying to go back to projectFolder "cd projectFolder" it just doesn't change the directory.
    Since this is just homework and not a real world application, I'm not hurting, but it's bugging me like all get out trying to figure out why this is happening.
    Full code below compiles and runs on my linux partition if anyone wants to try it and tell me what they think/know is happening
    Code:
    #include <stdio.h>
    
    #include <stdlib.h>
    
    #include <unistd.h>
    
    #include <string.h>
    
    #include <sys/wait.h>
    
    #include <sys/types.h>
    
    #include <time.h>
    
    #include <signal.h>
    
    #include <termios.h>
    
    
    
    
    
    int getInput(char *outputBuffer, int currentPos, char *breakCharacter){
    
                                                        //Declare:
    
        char *output = outputBuffer + currentPos;        //outputBuffer
    
        int position = currentPos;                        //outputPosition = 0
    
        char *broken = breakCharacter;                    //brokenCharacter
    
        char tempChar, tempChar1, tempChar2;
    
        
    
        //while not broken
    
        while(1){
    
            //read to innerBuffer
    
            tempChar = getc(stdin);
    
            //for each character in innerBuffer
    
            //if current character is an ignore character
    
                //- do nothing
    
            if(tempChar == 27){
    
                tempChar1 = getc(stdin);
    
                tempChar2 = getc(stdin);
    
                if(tempChar2 == 68 || tempChar2 == 67)
    
                    continue;
    
                else if(tempChar2 ==65){
    
                *broken = 'u'; //for up, saves 2 characters and allows for more uniform code.
    
                break;
    
                }
    
                else if(tempChar2 == 66){
    
                    *broken = 'd'; //for down
    
                    break;
    
                }
    
            }
    
            //else if current character is a break character
    
                //- set brokenCharacter
    
                //- break out of loops
    
            else if(tempChar == '\n'){
    
                *broken = '\n';
    
                break;
    
            }
    
            //else if current character is backspace or delete
    
            //- print \b
    
            //- decrement outputPosition
    
            if(tempChar == 8 || tempChar == 127){
    
                if(position > 0){
    
                    putchar('\b');
    
                    putchar(' ');
    
                    putchar('\b');
    
                    position--;
    
                }
    
            }
    
            //else
    
            //- print current character
    
            //- copy the current character to outBuffer at 
    
              //outputPosition
    
            //- increment the outputPosition
    
            else{
    
                putchar(tempChar);
    
                output[position] = tempChar;
    
                position++;
    
            }
    
                    
    
        }
    
        //Return:
    
        //outputBuffer
    
        //size of outputBuffer
    
        //brokenCharacter
    
        outputBuffer = output;
    
        return position;
    
    }
    
    
    
    int main()
    
    {
    
        //configure the input (termios)
    
        // get the original configuration
    
        struct termios origConfig;
    
        tcgetattr(0, &origConfig);
    
    
    
        // create a copy of the original configuration
    
        struct termios newConfig = origConfig;
    
    
    
        // adjust the new configuration
    
        newConfig.c_lflag &= ~(ICANON|ECHO);
    
        newConfig.c_cc[VMIN] = 3;
    
        newConfig.c_cc[VTIME] = 1;
    
    
    
        // set the new configuration
    
        tcsetattr(0, TCSANOW, &newConfig);
    
        
    
        char command[512];
    
        char *runCommand[10];
    
        char breakCharacter = '\n';
    
        char *brokenCharacter = &breakCharacter;
    
        int commandSize = 0;
    
        //while not exited
    
        //print prompt
    
        char cwd[512];
    
        getcwd(cwd, 512);
    
        printf("%s>", cwd);
    
        while(1){
    
            //read input (break on up, down and \n)
    
            commandSize = getInput(command, commandSize, brokenCharacter);
    
            //if broken on up or down
    
            if(breakCharacter == 'd' || breakCharacter == 'u'){
    
                //clear the current input
    
                int i;
    
                for(i = commandSize; i > 0; i--){
    
                    putchar('\b');
    
                    putchar(' ');
    
                    putchar('\b');
    
                }
    
                //execute appropriate command to generate corresponding message
    
                if(breakCharacter == 'u'){    
    
                    strcpy(command, getlogin());
    
                    commandSize = strlen(command);
    
                    printf("%s", command);
    
                }
    
                else{
    
                    time_t rawtime;
    
                    struct tm * timeinfo;
    
                    time ( &rawtime );
    
                    timeinfo = localtime ( &rawtime );
    
    
    
                    strcpy(command, asctime(timeinfo));
    
                    commandSize = strlen(command)-1;
    
                    command[commandSize] = '\0';
    
                    printf("%s", command);
    
                }
    
                continue;
    
            }
    
            else{
    
                //parse input into a command
    
                char *token = strtok(command, " ");
    
                int i = 0;
    
                while(token != NULL)
    
                {
    
                    if(i > 8){
    
                        printf("I didn't account for a command with more than 9 tokens, sorry!");
    
                        return -1;
    
                    }
    
                    runCommand[i] = token;
    
                    token = strtok(NULL, " ");
    
                    i++;
    
                }
    
                runCommand[i] = NULL;
    
                //if cd command
    
                if(strcmp(runCommand[0], "cd") == 0){
    
                    //change the current working directory
    
                    chdir(runCommand[1]);
    
                    putchar('\n');
    
                    getcwd(cwd, 512);
    
                    printf("%s>", cwd);
    
                    for(i = 0; i < commandSize; i++){
    
                        command[i] = '\0';
    
                    }                
    
                    commandSize = 0;
    
                    for(i = 0; i < 9; i++)
    
                        runCommand[i] = NULL;
    
                }
    
                //else if exit command
    
                else if(strcmp(runCommand[0], "exit") == 0){
    
                    //break
    
                    break;
    
                }
    
                //else
    
                else{
    
                    //execute the command
    
                    int status; //holds status
    
                    int pid = fork(); //initiates fork and stores pid, 0 for child >0 for parent.
    
                    if(pid == 0){ //child process code
    
                        //execute command.
    
                        putchar('\n');
    
                        execvp(runCommand[0], runCommand);
    
                        perror(NULL);
    
                        exit(1);
    
                    }
    
                    else{ //parent process code
    
                        //waits until child process (pid) is finished.
    
                        waitpid(pid, &status, WUNTRACED);
    
                        for(i = 0; i < commandSize; i++){
    
                            command[i] = '\0';
    
                            }                
    
                        commandSize = 0;
    
                        for(i = 0; i < 9; i++)
    
                        runCommand[i] = NULL;
    
                        getcwd(cwd, 512);
    
                        printf("%s>", cwd);
    
                    }
    
                }
    
            }
    
        }
    
        //restore the input configuration (termios)
    
        tcsetattr(0, TCSANOW, &origConfig);
    
        return 0;
    
    }
    Also, big thanks to Jorl17 for the initial advice that got me past that block.

  10. #10
    Registered User
    Join Date
    Mar 2008
    Location
    Coimbra, Portugal
    Posts
    85
    No problem

    I'd like to add that I didn't clearly specify why you needed fflush(stdout).

    STDOUT is set to Line Buffering, which will only flush the data in the buffer when a newline is found, or when the buffer is full. Since you weren't outputting a newline when you pressed a key or when you presented your initial "cmd", said data wasn't being flushed. In order to avoid having to constntly flush data, you can try and use setvbuf - C++ Reference to change the buffer mode. You may specify NULL as the buffer argument and get an internal buffer allocated for yourself.

    Now I'll try to dig deeper into your code...

  11. #11
    Registered User
    Join Date
    Mar 2008
    Location
    Coimbra, Portugal
    Posts
    85
    Ok, browsing through your code, here's what I initially have to say:

    1. You aren't NULL-terminating your command string. If I am getting the code right, then you should add command[commandSize] = '\0'; right after your call to getInput. This also fixes an issue seen after using strtok. Namely, if you typed "echo hey", BOTH these strings would lack NULL-termination.

    2. I'm not sure I get some of the hardcoded values you use. I get 27 (ESC key), but I don't get why you then use 68, 67, 65 and 66 ('D', 'C', 'A' and 'B'). Are you sure this is what you want? If it is want you want, why not just use the characters themselves instead of harcoded ASCII values? (edit: got it, read below)

    3. You aren't handling the case that no command at all was given. For instance, if a user merely presses enter. That can be fixed by checking if commandSize==0, because you do not change it if you instantly find a newline. i.e.:

    Code:
    if(commandSize==0) {
            printf("\n%s>", cwd);
            continue;
    }
    That pretty much fixes all the errors I was having. It fixes all the errors that valgrind was also printing, as well as all the segfaults.

    EDIT: About 2. I see what you mean with those values, they are key sequences, sorry for not getting that. Still, maybe use some #defs to make that clearer? Keep in mind that the code I posted in 3 will also apply to this situation. To really only insert a newline and print the cwd when enter is pressed, check that *brokenCharacter ALSO equals '\n', besides commandSize being 0. Otherwise pressing up or down will print the cwd again.
    Last edited by Jorl17; 09-19-2010 at 09:23 AM.

  12. #12
    Registered User
    Join Date
    Sep 2010
    Posts
    8
    hmm... The null termination may be something, but most seemed to work without it, so not too sure.
    like you said later, 2 was checking for up arrow, down arrow, left arrow, and right arrow.
    It's in the comments. Those all seemed to work perfectly, but null termination shouldn't have been an issue in this. Also, I might have cause issues for other special keys that have the same first two bytes as the arrow keys.
    on 3. Hmm... you know I'm not sure I handled an empty command, but I remember empty commands behaving correctly. could be execvp handles empty commands.
    Thanks again for the ideas. the null termination may have somehow been my problem with the cd command. Thanks again for the help, and I'll probably spend a lot more time on these boards now.

  13. #13
    Registered User
    Join Date
    Mar 2008
    Location
    Coimbra, Portugal
    Posts
    85
    Quote Originally Posted by Sretsam View Post
    hmm... The null termination may be something, but most seemed to work without it, so not too sure.
    like you said later, 2 was checking for up arrow, down arrow, left arrow, and right arrow.
    It's in the comments. Those all seemed to work perfectly, but null termination shouldn't have been an issue in this. Also, I might have cause issues for other special keys that have the same first two bytes as the arrow keys.
    on 3. Hmm... you know I'm not sure I handled an empty command, but I remember empty commands behaving correctly. could be execvp handles empty commands.
    Thanks again for the ideas. the null termination may have somehow been my problem with the cd command. Thanks again for the help, and I'll probably spend a lot more time on these boards now.
    May I suggest getting used to running your code through valgrind? It'll help you spot many of these errors, I love it! With all the modifications I mentioned, your code resulted in perfect crash-free errors here (and no valgrind errors). Whereas the original one crashed after some commands and gave non null-terminated arguments to execvp(), etc...

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 2
    Last Post: 08-15-2010, 08:41 PM
  2. Console, Terminal and Terminal Emulator
    By lehe in forum C Programming
    Replies: 4
    Last Post: 02-15-2009, 09:59 PM
  3. Dynamic Binding
    By gpr1me in forum C++ Programming
    Replies: 1
    Last Post: 03-24-2006, 09:01 AM
  4. Another question on classes
    By hpy_gilmore8 in forum C++ Programming
    Replies: 26
    Last Post: 05-24-2003, 09:11 AM
  5. Simple project becomes very annoying.
    By ... in forum C++ Programming
    Replies: 6
    Last Post: 02-27-2002, 11:42 PM