ARRAYS in C a hassle - c

I have been trying to program a UNIX style shell command prompt in C. Within this program I need it to keep track of the commands that have already been used, so the user can recall the last command by entering 'r'. I made a globally initialized array to hold strings. Whenever the array of characters a user entered needs to be saved, I add it to the global array. I have tried memcpy, simply copying each value using a loop, and just copying the pointer. None of these have been working. I am not super familiar with C and I am sure it is a pointer problem.
Whenever I copy the pointer of inputBuffer to my global array string (it does get copied), however upon leaving the setup function this pointer disappears? I am not exactly sure what I am doing wrong.
Test:
(1)user input --> ls
string[0] = ls
(2)user input --> r
inputBuffer = ls
string[recent] = ls
incorrectly does...
inputBuffer = r
string[recent] = r
(I have included the relevant parts of the code.)
#define MAX_LINE 80 /* 80 chars per line, per command, should be enough. */
#define SAVED_BUFFER 100
char *string[SAVED_BUFFER];
int p = 0;
int recent = -1;
int stringSize = 0;
void setup(char inputBuffer[], char *args[],int *background)
{
int length, /* # of characters in the command line *
/* read what the user enters on the command line */
length = read(STDIN_FILENO, inputBuffer, MAX_LINE);
start = -1;
if (length == 0)
exit(0); /* ^d was entered, end of user command stream */
if (length < 0){
perror("error reading the command");
exit(-1); /* terminate with error code of -1 */
}
if (inputBuffer[0] == 'r' && inputBuffer[1] == '\n' && stringSize > 0) {
int k;
memcpy(inputBuffer, string[recent], strlen(string[recent]) + 1);
printf("%s",inputBuffer);
printf("%s",string[recent]);
}
else {
string[p] = inputBuffer;
printf("%s", string[0]);
stringSize++;
recent++; // one behind strings current array location, to get history
p++; // current string array spot
}
}
int main(void)
{
char inputBuffer[MAX_LINE]; /* buffer to hold the command entered */
int background; /* equals 1 if a command is followed by '&' */
char *args[MAX_LINE/2+1];/* command line (of 80) has max of 40 arguments */
while (1) { /* Program terminates normally inside setup */
background = 0;
printf("COMMAND2->");
fflush(0);
setup(inputBuffer, args, &background); /* get next command */
}
}

When you "save the input buffer" you actually only store a pointer to the inputBuffer array:
string[p] = inputBuffer;
The actual data is not copied, you just store a pointer to the global input buffer. When the next input replaces the old content of inputBuffer, you will see the new content even if you access it through string[recent].
The calls to memcpy don't actually do anything, since the passed input and output buffer all refer to the same memory.
To actually store a copy of the data you have to allocate new memory to store the copy. Since you are dealing with strings, this is most easily done with strdup(), which duplicates a string:
string[p] = strdup(inputBuffer);
Later, once you are done with such a copy and don't need it anymore you have to free the memory used by the copy:
free(string[p]);

Have you tried changing
char *string[SAVED_BUFFER];
to
char string[SAVED_BUFFER][MAX_LINE];
I think that's how you're treating it in the code

Related

Backspace and multibyte characters using getchar() in C

I was reading BeeJ's C programming guide and copied his readline() function, that reads a line from stdin. Due to the way it's implemented it has no problem reading multibyte characters, as it reallocates the spaces in accordance with the total amount of received bytes, and as such, it has no problem with unicode input. Here's a program with the function included:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define printPrompt printf("db > ")
/* The readLine function, allocates memory for a short string and
** reads characters into it. When the string's size limit is met,
** the same memory block is reallocated, but twice the size.
** Shamelessly stolen from BeeJ's guide to C programming |=
*/
char* read_line(void) {
int i = 0; /* Position of the current character */
int linbuf = 4; /* Size of our line in memory, will be
duplicated once the line length surpasses it */
char* lin; /* The pointer value to our line */
int c; /* The value we'll use to accept characters */
if( !(lin = malloc( linbuf*sizeof(char))) )
return NULL;
while( c = getchar(), c != '\n' && c != EOF ) {
/* Check if the amount of bytes accepted has surpassed the
* amount of memory we've allocated so far */
if(i == linbuf - 1) {
/* If it did, reallocate double the space */
linbuf *= 2;
char* tmpbuf = realloc(lin, linbuf);
/* If the space couldn't have been allocated then we'd
* run out of memory. Delete everything and abort. */
if(tmpbuf == NULL) {
free(tmpbuf);
return NULL;
}
/* If we've arrived here that means there were no
* problems, so we'll assign the newly reallocated
* memory to "lin" */
lin = tmpbuf;
}
/* Add the new character to our allocated space */
lin[i++] = c;
}
/* If we've received an EOF signal after having read 0
* characters, we'd like to delete our allocated memory and
* return a NULL */
if(c == EOF && i == 0) {
free(lin);
return NULL;
}
/* Here we'll shrink the allocated memory to perfectly fit our
* string */
if(i < linbuf - 1) {
char* tmpbuf = realloc(lin, i + 1);
if(tmpbuf != NULL)
lin = tmpbuf;
}
/* Here we'll terminate the string */
lin[i] = '\0';
/* Finally, we'll return it */
return lin;
}
int main(int argc, char* argv[]) {
char* hey = read_line();
printf("%s\n", hey);
return 0;
}
An input of
Hello, World! (:
would result in the output of
Hello, World! (:
An input of multibyte characters such as
שלום, עולם! (:
would result in the correct output of
שלום, עולם! (:
However, if I were to press the backspace key, it would only delete a one byte character, resulting in garbled output; an input of (backspaces marked as \b):
שיהיה לכם בוקר טוב\b\b\b\b\b\b\b\bערב טוב
which is supposed to end up being:
שיהיה לכם ערב טוב
actually ends up being:
�שיהיה לכם בוק�ערב טוב
My computer runs a Musl-libc version of Void Linux, and I compiled the program with both tcc and gcc, both yielding the same results.
Does this problem have to do with my libc, with my terminal (suckless st), with my kernel, or is it something I'm missing in the code? Whatever might be the case, is there any way I can handle it, preferably without using any external libraries such as ICU or what have you?
"is there any way I can handle it [...] without using any external libraries" The answer is a big fat no. Unless you are prepared to write a big and complex library yourself, that is.
With external libraries this is trivial:
sudo apt install libreadline-dev # no idea how to say that in Void
#include <stdio.h>
#include <readline/readline.h>
#include <readline/history.h> // optional, to enable line history
int main()
{
using_history(); // optional
char* s;
while ((s = readline("Type something > ")))
{
printf("You have typed: %s\n", s);
add_history(s);
}
printf ("Bye!\n");
}
With this, you get a bunch of goodies for free, including full Unicode-aware line editing, programmable keybindings, and input history.
Edit On a machine I first checked this, your program behaved like you describe. But on another machine, which happens to be my home desktop, it works as expected, no weird backspace behaviour at all. I checked both X11 terminals and the text linux tty. So I guess there is something with some kernels and/or terminals after all.
Later edit There is an stty setting that controls this behaviour, at least for UTF-8.
stty iutf8
and your program should behave as expected, with no big fat libraries needed.

Program copying files incorrectly

So for an assignment in my Systems class, we have to make a program that takes a file in a given source and copies it to a given destination using one of three copyfile() functions.
When running the program in the command line, it will take in three arguments: ./cptest, the input file location, and the output file location, as well as two optional arguments: the number for which copyfile() function you would like to use as well as the buffer size.
If you choose 1 for the copyfile() function parameter, the program should copy the file using formatted I/O (file handles), and then copy the file one character at a time.
If you choose 2 for the copyfile() function parameter, the program should copy the file using integer file descriptors, and then copy the file one character at a time.
If you choose 3 for the copyfile() function parameter, the program should allocate a buffer of size equal to what size you put in for the buffer size parameter (say 1024), then use read() to read from the input file up to that many bytes at a time.
If you do not put in a third parameter (copyfile() function number), then the program will automatically use copyfile3() with whatever buffer size you decided with the fourth parameter.
If you do not enter a fourth parameter (buffer size), then the program will set it to 1024 by default and use it if necessary (not needed for copyfile1() or 2()).
Before and after each copyfile() function is called, the program uses gettimeofday() to make a timestamp. The program then makes a new timestamp to find how long the copying took using the difference in seconds and microseconds between the after timestamp and the before timestamp, then prints all this information out.
For example if I put in: ./cptest ~/My_Documents/photo.JPG ~/assig6/photo.JPG 3
The program should return:
Timestamp Before Copying:
Seconds: 1425150842, Microseconds: 914511
Timestamp After Copying:
Seconds: 1425150842, Microseconds: 927662
Copying took 0 seconds, 13151 microseconds
So now that you know what the program does, let's get down to the issue. Every time I run the programs with any of the copyfile() functions, be it 1, 2, or 3, technically speaking, the copying works fine, the file ends up in the correct destination, and the program returns all the correct information in terms of the timestamps.
However, in reality, it does not work, as when I use copyfile1(), it copies all of the bytes, but when I try to open the file - say a .jpg - it says that it can't view the file because it appears to be damaged, corrupted, or too large. When I use copyfile2() and copyfile3(), the file in the destination has only 1 KB, and when I don't pass in a third or fourth parameter, the output file has 0 KB. The same thing happens with word documents.
Is there a reason why it copies the files fine but the file is damaged in the process? It seems as though the code is right but the end result is not.
Here's the main file cptest.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <fcntl.h>
#include "cptest.h"
/** cptest.cpp
* A file copying program.
* Derived partially from caesar.cpp by Horstmann and Budd from big C++
*/
/** Prints usage instructions.
* #param program_name the name of this program
*/
void usage(char* program_name) {
// Prints out usage instructions if incorrect number of parameters is passed in
printf("Usage: %s infile outfile copy_function_number buffer_size \n", program_name);
}
/** Prints file opening error message
* #param filename the name of the file that could not be opened
*/
void open_file_error(char* filename) {
// Error message if unable to open the file
printf("Error opening file %s\n", filename);
}
/** Main program: copies a file.
* #param argc Number of command-line arguments (including program name).
* #param argv Array of pointers to character arays holding arguments.
* #return 0 if successful, 1 if failure.
*/
int main(int argc, char* argv[]) {
char* infilename; // Name of input file
char* outfilename; // Name of output file
int copy_function_number; // Chooses which copy function is used based on argv[3]
int buffer_size; // Size of buffer
int returnstatus; // 0 or 1 depending on success or failure to copy the file to the destination
struct timeval* before = malloc(sizeof(struct timeval)); // Struct for time before copying is done
struct timeval* after = malloc(sizeof(struct timeval)); // Struct for time after copying is done
if (argc != 3 && argc != 4 && argc != 5) {
usage(argv[0]); // Must have 2, 3, or 4 arguments in addition to ./cptest.
return 1; // Failure!
}
infilename = argv[1]; // Sets first parameter to the input file name
outfilename = argv[2]; // Sets second parameter to the output file name
if(argv[3] == NULL) {
copy_function_number = 3; // Program uses copyfile3 if no third parameter is entered
}
else {
copy_function_number = atoi(argv[3]); // Otherwise program uses whatever function is passed by third parameter
}
if (argv[4] == NULL) {
buffer_size = 1024; // Sets buffer size to 1024 if no fourth parameter is entered
}
else {
buffer_size = atoi(argv[4]); // Otherwise buffer size is whatever user enters as fourth parameter
}
if (copy_function_number == 1) {
gettimeofday(before, NULL); // Get timestamp before the copying
// Perform the copying with copyfile1() if the third parameter is 1
returnstatus = copyfile1(infilename, outfilename);
gettimeofday(after, NULL); // Get timestamp after the copying
}
if (copy_function_number == 2) {
gettimeofday(before, NULL); // Get timestamp before the copying
// Perform the copying with copyfile2() if the third parameter is 2
returnstatus = copyfile2(infilename, outfilename);
gettimeofday(after, NULL); // Get timestamp after the copying
}
if (copy_function_number == 3) {
gettimeofday(before, NULL); // Get timestamp before the copying
// Perform the copying with copyfile3() if the third parameter is 3
returnstatus = copyfile3(infilename, outfilename, buffer_size);
gettimeofday(after, NULL); // Get timestamp after the copying
}
else {
if (copy_function_number != 1 || copy_function_number != 2 || copy_function_number != 3 || argv[3] == NULL) {
gettimeofday(before, NULL); // Get timestamp before the copying
// Perform the copying with copyfile3() if no third parameter is entered
returnstatus = copyfile3(infilename, outfilename, buffer_size);
gettimeofday(after, NULL); // Get timestamp after the copying
}
}
struct timeval *copytime = difference_in_time(before, after); // Struct for time after copying is done
// Print out information of the timestamp before copying
printf("Timestamp Before Copying: \n Seconds: %d, Microseconds: %d\n", before->tv_sec, before->tv_usec);
// Print out information of the timestamp after copying
printf("Timestamp After Copying: \n Seconds: %d, Microseconds: %d\n", after->tv_sec, after->tv_usec);
// Print out information of the difference between the timestamps (how long the copying took)
printf("Copying took %d seconds, %d microseconds\n", copytime->tv_sec, copytime->tv_usec);
return returnstatus; // 0 if successful copy, 1 if unsuccessful.
}
/** Copies one file to another using formatted I/O, one character at a time.
* #param infilename Name of input file
* #param outfilename Name of output file
* #return 0 if successful, 1 if error.
*/
int copyfile1(char* infilename, char* outfilename) {
FILE* infile; //File handle for source.
FILE* outfile; // File handle for destination.
infile = fopen(infilename, "r"); // Open the input file.
if (infile == NULL) {
open_file_error(infilename); // Error message if there was a problem opening the input file.
return 1; // Failure!
}
outfile = fopen(outfilename, "w"); // Open the output file.
if (outfile == NULL) {
open_file_error(outfilename); // Error message if there was a problem opening the output file.
return 1; // Failure!
}
int intch; // Character read from input file. must be an int to catch EOF.
unsigned char ch; // Character stripped down to a byte.
// Read each character from the file, checking for EOF.
while ((intch = fgetc(infile)) != EOF) {
ch = (unsigned char) intch; // Convert to one-byte char.
fputc(ch, outfile); // Write out.
}
fclose(infile); // Close the input file.
fclose(outfile); // Close the output file.
return 0; // Success!
}
/** Copies one file to another using integer file descriptors instead of file handles, one character at a time.
* #param infilename Name of input file
* #param outfilename Name of output file
* #return 0 if successful, 1 if error.
*/
int copyfile2(char* infilename, char* outfilename) {
int infile; //File handle for source.
int outfile; // File handle for destination.
// Allocates a buffer for the chars that will be read from the input and written to the output.
char buffer[1024];
infile = open(infilename, O_RDONLY); // Open the input file.
if (infile < 0) {
open_file_error(infilename); // Error message if there was a problem opening the input file.
return 1; // Failure!
}
outfile = open(outfilename, O_WRONLY | O_CREAT); // Open the output file.
if (outfile < 0) {
open_file_error(outfilename); // Error message if there was a problem opening the output file.
return 1; // Failure!
}
int intchin; // Character read from input file. must be an int to catch EOF.
int intchout; // Character written to the output file. must be an int to catch EOF.
// Size of the buffer so that when you are copying extremely large files, it does not have to go through 200000 loop iterations
int buffer_size = 1024;
unsigned char ch; // Character stripped down to a byte.
// Read each character from the file, checking for 0.
while ((intchin = read(infile, buffer, buffer_size)) != 0) {
ch = (unsigned char) intchin; // Convert to one-byte char.
intchout = write(outfile, buffer, ch); // Write out.
}
close(intchin); // Close the input file.
close(intchout); // Close the output file.
return 0; // Success!
}
/** Copies one file to another using integer file descriptors, buffer_size characters at a time.
* #param infilename Name of input file
* #param outfilename Name of output file
* #param buffer_size Size of the buffer for reading and writing
* #return 0 if successful, 1 if error.
*/
int copyfile3(char* infilename, char* outfilename, int buffer_size) {
int infile; //File handle for source.
int outfile; // File handle for destination.
// Allocates a buffer of size buffer_size for the chars that will be read from the input and written to the output.
char* buffer = (char*) malloc(sizeof(char)* buffer_size);
infile = open(infilename, O_RDONLY); // Open the input file.
if (infile < 0) {
open_file_error(infilename); // Error message if there was a problem opening the input file.
return 1; // Failure!
}
outfile = open(outfilename, O_WRONLY | O_CREAT); // Open the output file.
if (outfile < 0) {
open_file_error(outfilename); // Error message if there was a problem opening the output file.
return 1; // Failure!
}
int intchin; // Character read from input file. must be an int to catch EOF.
int intchout; // Character written to the output file. must be an int to catch EOF.
unsigned char ch; // Character stripped down to a byte.
// Read each character from the file, checking for 0.
while ((intchin = read(infile, buffer, buffer_size)) != 0) {
ch = (unsigned char) intchin; // Convert to one-byte char.
intchout = write(outfile, buffer, ch); // Write out.
}
close(intchin); // Close the input file.
close(intchout); // Close the output file.
free(buffer); // Free the buffer that was allocated.
return 0; // Success!
}
/** Makes a new timeval struct that determines the difference between two timestamps
* #param time1 Struct containing the information for the first timestamp
* #param time2 Struct containing the information for the second timestamp
* #return The struct made using the two parameters
*/
struct timeval* difference_in_time(struct timeval* time1, struct timeval* time2) {
struct timeval* copytime = malloc(sizeof(struct timeval)); // Allocates a struct to hold the difference between the two timestamps.
if ((time2->tv_sec - time1->tv_sec) < 0) {
// Error message for if the first timestamp entered was before the second timestamp.
printf("Seconds value is negative! time1 should be before time2!\n");
}
if ((time2->tv_usec - time1->tv_usec) < 0) {
// Handles if the difference in microseconds between the second and first timestamps would be negative, subtracting 1 from the seconds.
copytime->tv_sec = (time2->tv_sec - time1->tv_sec) - 1;
// Handles if the difference in microseconds between the second and first timestamps would be negative, subtracting the difference from 1000000.
copytime->tv_usec = 1000000 - (time2->tv_usec - time1->tv_usec);
}
else {
// Otherwise the seconds for the third timestamp is the difference between the seconds of the second and first timestamps.
copytime->tv_sec = (time2->tv_sec - time1->tv_sec);
// Otherwise the microseconds for the third timestamp is the difference between the microseconds of the second and first timestamps.
copytime->tv_usec = (time2->tv_usec - time1->tv_usec);
}
return copytime; // Return the new timestamp created.
}
Here's the header file cptest.h:
#ifndef CPTEST_H
#define CPTEST_H
// function prototypes
void usage(char* program_name);
void open_file_error(char* filename);
int copyfile1(char* infilename, char* outfilename);
int copyfile2(char* infilename, char* outfilename);
int copyfile3(char* infilename, char* outfilename, int buffer_size);
struct timeval* difference_in_time(struct timeval* time1, struct timeval* time2);
#endif
And here's the makefile:
cptest: cptest.o
gcc -g cptest.o -o cptest
cptest.o: cptest.c cptest.h
gcc -c -g cptest.c
clean:
rm -f *.o cptest
docs:
doxygen
chmod a+r html/*
cp -p html/* ~/public_html/cs2303assig6
EDIT: Thought it might be easier to show you what happens using the readme.txt we need to include with our program as an example. Shortened it down to the useful information so you don't have to through more walls of text.
Original:
Name: *My name*
Section: CS2303 C01
What Program Does: *Explanation of what program does*
Example: *Example of what should show up when running program in the command line*
Results: *Test data for time it took for each function and using different buffer sizes to find most efficient function and buffer size*
Compiling:
To compile the code, use 'make' in the command line. The makefile should already link all of the .o files to the executable, so the work is done for you.
Result when using copyfile1():
so the work is done for you.S2303 C01
What Program Does: *Explanation of what program does*
Example: *Example of what should show up when running program in the command line*
Results: *Test data for time it took for each function and using different buffer sizes to find most efficient function and buffer size*
Compiling:
To compile the code, use 'make' in the command line. The makefile should already link all of the .o files to the executable, so the work is done for you.
Result when using copyfile2() and 3():
so the work is done for you.
copyfile2 and 3 are completely broken because they cast the number of characters read into a char. As the buffer size is often 1024, this cast gives a result of 0 so nothing is written.
For copyfile 1, I'd recommend opening the files with a binary flag ("rb" or "wb") although I'm not convinced that is the issue.

segmentation fault core dumped c

I'm new to C, I got an error today which is:
segmentation fault core dumped
I used gdb to track the code, I found that the error occurs in this line:
if (!strcmp(user_pass, passwddata->passwd))
Where user_pass is a char array, and passwddata is a struct, passwd is a member of the struct, which is also a type of char array, I tried to change the code to
if (!strcmp(user_pass, "ttt"))
The error didn't occur, so I guess the error occurs on that struct, if more code is needed, I can add it, here I want to understand under what condition can such kind of error occur on a struct?
Here is the code:
int main(int argc, char *argv[]) {
mypwent *passwddata; /* this has to be redefined in step 2 */
/* see pwent.h */
char important[LENGTH] = "***IMPORTANT***";
char user[LENGTH];
//char *c_pass; //you might want to use this variable later...
char prompt[] = "password: ";
char *user_pass;
sighandler();
while (TRUE) {
/* check what important variable contains - do not remove, part of buffer overflow test */
printf("Value of variable 'important' before input of login name: %s\n",
important);
printf("login: ");
fflush(NULL); /* Flush all output buffers */
__fpurge(stdin); /* Purge any data in stdin buffer */
if (gets(user) == NULL) /* gets() is vulnerable to buffer */
{
exit(0); /* overflow attacks. */
}
printf("******************* %s\n",user);
/* check to see if important variable is intact after input of login name - do not remove */
printf("Value of variable 'important' after input of login name: %*.*s\n",
LENGTH - 1, LENGTH - 1, important);
user_pass = getpass(prompt);
passwddata = getpwnam(user);
printf("^^^^^^^^^^^^^^^^^^^^^^^^^^ %s\n", user_pass);
if (passwddata != NULL) {
/* You have to encrypt user_pass for this to work */
/* Don't forget to include the salt */
if (!strcmp(user_pass, "ttt")) {
printf(" You're in !\n");
/* check UID, see setuid(2) */
/* start a shell, use execve(2) */
}
}
printf("Login Incorrect \n");
}
return 0;
}
Most likely passwd or passwddata is NULL, in the latter case the -> is attempting to deference the NULL pointer and thus it's crashing.
By changing your code to:
if (!strcmp(user_pass, "ttt"))
You isolated the first part, so you know user_pass is OK. You can use a debugger or some checks and printf's to get the values of passwddata and passwd to close on what the problem is.
Now that you've updated the code you know the problem is with passwd. You start with an empty pointer:
mypwent *passwddata;
Later you set the pointer to the return of getpwnam, presumably this is a pointer to a structure of type mypwent that you allocated some memory for:
passwddata = getpwnam(user);
You have a check to make sure passwddata isn't null:
if (passwddata != NULL) {
if (!strcmp(user_pass, "ttt")) {
So now you've checked everything except passwd, passing a null into strcmp() will cause it to crash with that message, so I'm guessing you only allocated memory for the structure and not for the char array within the structure.
My guess would be that passwddata is NULL.
Or the buffer passwddata->passwd points to
shorter than the string in user_pass
and
not null-terminated
Together those two make strcmp access beyond the limits of the second argument's buffer and hence cause the error.

Segmentation Fault - strcpy() - C

I'm implementing a history feature for a command line shell. I've implemented a circular array to hold to ten most recent commands. Each command is also labeled by an integer specifying which total command is. For Example, if 30 total commands were entered, the ten commands in the circular array would be numbered (30, 29, 28, 27,...,21).
If a user were to insert the command "r" followed by a number labeling one of the ten instructions then that instruction is supposed to run. I keep running into a seg fault when trying to ensure that a two word command is accepted properly. Can anyone help point out what the problem is.
int main(void)
{
char inputBuffer[MAX_LINE]; /* buffer to hold the command entered */
int background; /* equals 1 if a command is followed by '&' */
char *args[MAX_LINE/2+1];/* command line (of 80) has max of 40 arguments */
int position, count, rnum = 0;
char historyArray[10][MAX_LINE];
char *holder[MAX_LINE]={0};
while (1){ /* Program terminates normally inside setup */
background = 0;
printf("COMMAND->");
fflush(0);
setup(inputBuffer, args, &background); /* get next command */
position = (count % MOD_VAL);
strcpy(historyArray[position],args[0]);
if(!strcmp("rr",args[0]))
{
strcpy(historyArray[position],historyArray[((position-1)+MOD_VAL)%MOD_VAL]);
printf("%i",count);
printf("%c",'.');
printf("%c",' ');
printf("%s",historyArray[position]);
printf("%c",'\n');
strcpy(args[0],historyArray[position]);
}
else if(!strcmp("r",args[0])) //SEG FAULT OCCURING IN THIS ELSE-IF BLOCK!
{
//args[1] will hold given number
printf("%c",'\n');
printf("%s",args[0]);
printf("%s",args[1]);
printf("%s",args[2]);
printf("%c",'\n'); //PRINT STATEMENTS FOR DEBUGGING
strncpy(holder[0], args[2], MAX_LINE - 1); //SEG FAULT
rnum = atoi(args[1]);
strcpy(historyArray[position],historyArray[((position-(count-rnum))+MOD_VAL)%MOD_VAL]);
strcpy(args[0],historyArray[position]); //CHANGES VALUES OF args[1], args[2]
if(holder[0] != NULL)
{
strncpy(args[1],holder[0],MAX_LINE-1);
args[2] = NULL;
}
else
{
args[1] = NULL;
}
printf("%c",'\n');
printf("%s",args[0]);
printf("%s",args[1]);
printf("%s",args[2]);
printf("%c",'\n');
}
else if(!(strcmp("h",args[0]))||!(strcmp("history",args[0])))
{
int counter = 0;
while(counter < 10)
{
printf("%i",(count - counter));
printf("%c",'.');
printf("%c",' ');
printf("%s", historyArray[((position - counter + MOD_VAL)%MOD_VAL)]);
printf("%c",' ');
printf("%c",'\n');
counter ++;
if(counter > count)
break;
}
}
count++;
pid_t pid1; //Initialize pid_t variable to hold process identifier
pid1 = fork(); //Fork process and assign process identifier to "pid1"
if (pid1 == 0) //Child process
{
//Child process executes the command specified by the user and
//then quits.
execvp(args[0], args);
exit(0);
}
else //Parent process
{
if (background != 1)//Check for inclusion of '&' in command
{
wait(NULL); //Wait for child process to finish executing
}
}
/* the steps are:
(1) fork a child process using fork()
(2) the child process will invoke execvp()
(3) if background == 0, the parent will wait,
otherwise returns to the setup() function. */
}
}
Any assistance is appreciated!
-MATT
Here your args is the array of character pointers.
But strcpy requires two arguments - that should be array or character pointer to which memory allocated by malloc
But your strcpy(historyArray[position],args[0]); takes one argument as character pointer which will not be accepted.
so you can either change the args[] to args[][] or args[0] = malloc(some_no), segfault will be removed.
You note that the crash occurs on the line
else if(!strcmp("r",args[0]))
If I were you, I would load the core file in a debugger and see what the value of args[0] is when passed to strcmp().
I expect you have compiler warnings about type mismatch between char and char*. You declare args as char*. That means args[0] is a char, not a char*. To compare the single character, just use a character instead of strcmp():
else if ('r' != args[0])
Some notes on pitfalls with C's string handling:
strcmp() isn't safe with respect to array boundaries when its arguments are not correctly NUL-terminated
use strncmp() to provide a limit to the number of characters compared
although strncpy() guards against array boundaries, it does not guarantee to NUL-terminate the destination string
strcpy() does not respect array boundaries; it is your responsibility to ensure the destination array is large enough to receive the string being copied to it
You are missing to allocated memory the the char pointers held in args and holder.
So referring to those as pointers to 0-terminated character arrays ("strings") via the str*() family of functions, leads to undefined bahaviour, as the str*() function try to derefernce those pointers not point to valid memory.

Writing a command line shell with C; trying to use ncurses/C for the first time

I'm working on a class project in which I must write a command line shell with the following requirements:
The shell must able to read buffered input
Buffer should be 64 characters
Error conditions should be handled
Exceeded buffer size
Interruptions (when a signal arrives) – see the man page for read()
Invalid input (unparsable characters, blank lines, etc)
Any other error that may be encountered.
Shell must have a history of at least 20 items, and the history must not be of a static size. When the history buffer is full, the oldest item is removed and the newest item added.
Programs should be able to run in the foreground or background. (using &)
Ctrl-D will exit the shell
Ctrl-C will print the complete history
The Command ‘history’ will also print the complete history. Newest items will be at the bottom of the list.
All other signals will be trapped and displayed to the user in the shell
Program will use the read() command to read in input, unless the arrow keys are supported
I have opted to implement arrow keys for history cycling, so I'm using ncurses for input, rather than read(). I think I'm doing all right using strtok() to parse input, and fork() and execvp() to run the processes, but I'm not doing all right implementing ncurses correctly. All I've gotten it to do so far is init a new screen, display the prompt, then segfault upon any key press. Not good.
I reckon the problem must be in my design. I'm not wrapping my head around ncurses too well. What sort of data structures should I be using for this project? How should I handle the ncurses setup, teardown, and everything in between? What's the deal with windows and screens, and should I have a single globally accessible window/screen that I work with? Also, I've been trying to use a char* for the input buffer, and a char** for the command history, but I have no experience in C, so despite reading up on malloc, calloc, and realloc, I'm not sure of the best way to store commands in the buffer and the history. Any tips on managing these char arrays?
tl;dr: How do I use ncurses correctly to make a command line shell, and how do I handle the command memory management with C?
I realize this is a pretty hefty question. :(
edit: I have already seen http://www.gnu.org/software/libc/manual/html_node/Implementing-a-Shell.html and http://www.linuxinfor.com/english/NCURSES-Programming/ but the ncurses documentation has actually too much overhead. I just want to use its ability to recognize arrow keys.
Here's some sample code which:
Performs dynamic memory allocation.
Reads from the console in non-blocking mode.
Uses VT100 codes to print a frame buffer to the console.
It compiles on Linux using GCC without warnings or errors. It's far from bug free, but it should give you some ideas of what's possible. Compile and run it, pressing [up] and [down] will print messages, typing characters and hitting [enter] will "execute" the command.
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
/** VT100 command to clear the screen. Use puts(VT100_CLEAR_SCREEN) to clear
* the screen. */
#define VT100_CLEAR_SCREEN "\033[2J"
/** VT100 command to reset the cursor to the top left hand corner of the
* screen. */
#define VT100_CURSOR_TO_ORIGIN "\033[H"
struct frame_s
{
int x;
int y;
char *data;
};
static int draw_frame(struct frame_s *frame)
{
int row;
char *data;
int attrib;
puts(VT100_CLEAR_SCREEN);
puts(VT100_CURSOR_TO_ORIGIN);
for (row = 0, data = frame->data; row < frame->y; row++, data += frame->x)
{
/* 0 for normal, 1 for bold, 7 for reverse. */
attrib = 0;
/* The VT100 commands to move the cursor, set the attribute, and the
* actual frame line. */
fprintf(stdout, "\033[%d;%dH\033[0m\033[%dm%.*s", row + 1, 0, attrib, frame->x, data);
fflush(stdout);
}
return (0);
}
int main(void)
{
const struct timespec timeout = { .tv_sec = 1, .tv_nsec = 0 };
struct frame_s frame;
struct termios tty_old;
struct termios tty_new;
unsigned char line[128];
unsigned int count = 0;
int ret;
struct pollfd fds[1];
sigset_t sigmask;
struct tm *tp;
time_t current_time;
/* Set up a little frame. */
frame.x = 80;
frame.y = 5;
frame.data = malloc(frame.x * frame.y);
if (frame.data == NULL)
{
fprintf(stderr, "No memory\n");
exit (1);
}
memset(frame.data, ' ', frame.x * frame.y);
/* Get the terminal state. */
tcgetattr(STDIN_FILENO, &tty_old);
tty_new = tty_old;
/* Turn off "cooked" mode (line buffering) and set minimum characters
* to zero (i.e. non-blocking). */
tty_new.c_lflag &= ~ICANON;
tty_new.c_cc[VMIN] = 0;
/* Set the terminal attributes. */
tcsetattr(STDIN_FILENO, TCSANOW, &tty_new);
/* Un-mask all signals while in ppoll() so any signal will cause
* ppoll() to return prematurely. */
sigemptyset(&sigmask);
fds[0].events = POLLIN;
fds[0].fd = STDIN_FILENO;
/* Loop forever waiting for key presses. Update the output on every key
* press and every 1.0s (when ppoll() times out). */
do
{
fds[0].revents = 0;
ret = ppoll(fds, sizeof(fds) / sizeof(struct pollfd), &timeout, &sigmask);
if (fds[0].revents & POLLIN)
{
ret = read(STDIN_FILENO, &line[count], sizeof(line) - count);
if (ret > 0)
{
line[count + ret] = '\0';
if (strcmp(&line[count], "\033[A") == 0)
{
snprintf(frame.data, frame.x, "up");
count = 0;
}
else if (strcmp(&line[count], "\033[B") == 0)
{
snprintf(frame.data, frame.x, "down");
count = 0;
}
else if (line[count] == 127) // backspace
{
if (count != 0) { count -= ret;}
}
else if (line[count] == '\n')
{
snprintf(frame.data, frame.x, "entered: %s", line);
count = 0;
}
else
{
count += ret;
}
}
}
/* Print the current time to the output buffer. */
current_time = time(NULL);
tp = localtime(&current_time);
strftime(&frame.data[1 * frame.x], frame.x, "%Y/%m/%d %H:%M:%S", tp);
/* Print the command line. */
line[count] = '\0';
snprintf(&frame.data[(frame.y - 1) * frame.x], frame.x, "$ %s", line);
draw_frame(&frame);
}
while (1);
/* Restore terminal and free resources. */
tcsetattr(STDIN_FILENO, TCSANOW, &tty_old);
free(frame.data);
return (0);
}
If your input buffer is defined to be 64 characters, then I would recommend using a char array instead of a char*. Something like char input_buffer[65]; should serve your purposes (add an extra character for the trailing '\0').
As far as command history goes, you can use a two-dimensional array for that. Something like char command_history[20][65]; should let you store 20 old commands of 64 characters each.
Allocating these buffers statically should make things a bit easier for you, as you won't have to worry about malloc and friends.
It's hard to give you too much specific advice without seeing your code. I have a feeling that you are making the same type of mistakes that are typical to people first learning C. Can you post the part of your code that is giving you problems so that we can learn more about what you are doing?
Update after posted provided code:
One problem I'm seeing is that the function takeInput doesn't have a return statement. When you use input = takeInput(); inside your main function, the value of input isn't being set to what you think it is. It's probably not a valid pointer, which is causing your line that says input[j] to segfault.
Your usage of cmdHistory also needs revisiting. You allocate it with cmdHistory = (char**)calloc(21,sizeof(int));, which gives you enough space to store 21 integers. In the function printHistory, you pass elements of cmdHistory to printw as if they were strings (they're only integers). This is most definitely not doing what you want it to do. Instead, your allocation logic for cmdHistory needs to look more like your de-allocation logic (except backwards). Allocate an array of char**, then iterate through the array, assigning each pointer to a newly-allocated buffer. Just like you have one free statement for each element in the array plus a free for the array as a whole, you should have one malloc for each element plus one malloc for the array as a whole.
Even if you can't use a statically-allocated stack, try writing your program using one anyway. This will let you work the kinks out of your key detection logic, etc without having to worry about the dynamic memory part of the program. Once the rest of it is working, go back in and swap out the static memory for dynamic memory allocation. That way, you're only having to debug a little bit at a time.
Have you looked at the Readline library? It's ideal for use in your project.
http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html

Resources