Strange behavior when manipulating char* array in C - c

I'm trying to create a command line shell for an Operating Systems class. One of our assignments is to create a builtin "history" command that prints out the last 10 commands executed in the shell. Here is the code I have written for the "history" command:
char* cmd_hsitory[10]; // This is a global variable
int add_history(char **args) {
cmd_history[9] = NULL;
for(int i = 8; i >= 0; i--) {
cmd_history[i+1] = cmd_history[i];
}
cmd_history[0] = *args;
return 1;
}
Where the char **args argument is the last command exectued. Here is the function that prints the history:
int lsh_history(char **args) {
printf("Last 10 commands: \n");
for(int i = 0; i < 10; i++) {
printf("%s\n", cmd_history[i]);
}
return 1;
}
Some strange behavior is happening with this code. For instance, when I run the commands [cd, cd, ls, history] in succession, this is the printed output:
Last 10 commands:
ls
ls
cd
(null)
(null)
(null)
(null)
(null)
(null)
(null)
The first problem here is that I ran the cd command twice, and the ls command only once. If I run the "history" command again I get:
Last 10 commands:
history
ls
ls
cd
(null)
(null)
(null)
(null)
(null)
(null)
This seems correct with the exception of the 2 ls commands vs the 1 cd command.
This isn't very consistent, though, as sometimes I'll get mixed-up commands and the "history" command will show up several times.
If someone told me what's glaringly wrong with my code, that would be of great help. Thanks!
EDIT: Here is the full source code:
P.s. Most of this code is pulled from the internet (Stephen Brennan) and I'm building on top of it to learn. I will not submit this code as my assignment.
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
/*
Function Declaration for history queue
*/
int add_history(char **args);
/*
Function Declarations for builtin shell commands:
*/
int lsh_cd(char **args);
int lsh_help(char **args);
int lsh_exit(char **args);
int lsh_history(char **args);
/*
List of builtin commands, followed by their corresponding functions.
*/
char *builtin_str[] = {
"cd",
"help",
"exit",
"history"
};
int (*builtin_func[]) (char **) = {
&lsh_cd,
&lsh_help,
&lsh_exit,
&lsh_history
};
char *cmd_history[10];
int lsh_num_builtins() {
return sizeof(builtin_str) / sizeof(char *);
}
int add_history(char **args) {
cmd_history[9] = NULL;
for(int i = 8; i >= 0; --i) {
cmd_history[i+1] = cmd_history[i];
}
cmd_history[0] = NULL;
cmd_history[0] = *args;
return 1;
}
/*
Builtin function implementations.
*/
/**
#brief Builtin command: command history.
#param args List of args. args[0] is "history".
#return Always returns 1 to continue executing.
*/
int lsh_history(char **args) {
printf("Last 10 commands: \n");
for(int i = 0; i < 10; i++) {
printf("%s\n", cmd_history[i]);
}
return 1;
}
/**
#brief Bultin command: change directory.
#param args List of args. args[0] is "cd". args[1] is the directory.
#return Always returns 1, to continue executing.
*/
int lsh_cd(char **args)
{
if (args[1] == NULL) {
chdir("/Users/Landon/");
} else {
if (chdir(args[1]) != 0) {
perror("lsh");
}
}
return 1;
}
/**
#brief Builtin command: print help.
#param args List of args. Not examined.
#return Always returns 1, to continue executing.
*/
int lsh_help(char **args)
{
int i;
printf("Stephen Brennan's LSH\n");
printf("Type program names and arguments, and hit enter.\n");
printf("The following are built in:\n");
for (i = 0; i < lsh_num_builtins(); i++) {
printf(" %s\n", builtin_str[i]);
}
printf("Use the man command for information on other programs.\n");
return 1;
}
/**
#brief Builtin command: exit.
#param args List of args. Not examined.
#return Always returns 0, to terminate execution.
*/
int lsh_exit(char **args)
{
return 0;
}
/**
#brief Launch a program and wait for it to terminate.
#param args Null terminated list of arguments (including program).
#return Always returns 1, to continue execution.
*/
int lsh_launch(char **args)
{
pid_t pid;
int status;
pid = fork();
if (pid == 0) {
// Child process
if (execvp(args[0], args) == -1) {
perror("lsh");
}
exit(EXIT_FAILURE);
} else if (pid < 0) {
// Error forking
perror("lsh");
} else {
// Parent process
do {
waitpid(pid, &status, WUNTRACED);
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
}
return 1;
}
/**
#brief Execute shell built-in or launch program.
#param args Null terminated list of arguments.
#return 1 if the shell should continue running, 0 if it should terminate
*/
int lsh_execute(char **args)
{
int i;
if (args[0] == NULL) {
// An empty command was entered.
return 1;
}
for (i = 0; i < lsh_num_builtins(); i++) {
if (strcmp(args[0], builtin_str[i]) == 0) {
return (*builtin_func[i])(args);
}
}
return lsh_launch(args);
}
#define LSH_RL_BUFSIZE 1024
/**
#brief Read a line of input from stdin.
#return The line from stdin.
*/
char *lsh_read_line(void)
{
int bufsize = LSH_RL_BUFSIZE;
int position = 0;
char *buffer = malloc(sizeof(char) * bufsize);
int c;
if (!buffer) {
fprintf(stderr, "lsh: allocation error\n");
exit(EXIT_FAILURE);
}
while (1) {
// Read a character
c = getchar();
if (c == EOF) {
exit(EXIT_SUCCESS);
} else if (c == '\n') {
buffer[position] = '\0';
return buffer;
} else {
buffer[position] = c;
}
position++;
// If we have exceeded the buffer, reallocate.
if (position >= bufsize) {
bufsize += LSH_RL_BUFSIZE;
buffer = realloc(buffer, bufsize);
if (!buffer) {
fprintf(stderr, "lsh: allocation error\n");
exit(EXIT_FAILURE);
}
}
}
}
#define LSH_TOK_BUFSIZE 64
#define LSH_TOK_DELIM " \t\r\n\a"
/**
#brief Split a line into tokens (very naively).
#param line The line.
#return Null-terminated array of tokens.
*/
char **lsh_split_line(char *line)
{
int bufsize = LSH_TOK_BUFSIZE, position = 0;
char **tokens = malloc(bufsize * sizeof(char*));
char *token, **tokens_backup;
if (!tokens) {
fprintf(stderr, "lsh: allocation error\n");
exit(EXIT_FAILURE);
}
token = strtok(line, LSH_TOK_DELIM);
while (token != NULL) {
tokens[position] = token;
position++;
if (position >= bufsize) {
bufsize += LSH_TOK_BUFSIZE;
tokens_backup = tokens;
tokens = realloc(tokens, bufsize * sizeof(char*));
if (!tokens) {
free(tokens_backup);
fprintf(stderr, "lsh: allocation error\n");
exit(EXIT_FAILURE);
}
}
token = strtok(NULL, LSH_TOK_DELIM);
}
tokens[position] = NULL;
return tokens;
}
/**
#brief Loop getting input and executing it.
*/
void lsh_loop(void)
{
char *line;
char **args;
int status;
do {
printf("> ");
line = lsh_read_line();
args = lsh_split_line(line);
status = lsh_execute(args);
add_history(args);
free(line);
free(args);
} while (status);
}
/**
#brief Main entry point.
#param argc Argument count.
#param argv Argument vector.
#return status code
*/
int main(int argc, char **argv)
{
// Load config files, if any.
// Run command loop.
lsh_loop();
// Perform any shutdown/cleanup.
return EXIT_SUCCESS;
}

You need to strdup() the arg strings over to your cmd_history pointers, not just have your cmd_history pointers reference the original command string. When you free(line), you're putting the memory that your cmd_history pointers are referencing back into the free pool. In the next iteration of the loop you may or may not overwrite that data.

You wrote ((Comments Added)):
void lsh_loop(void)
{
...
line = lsh_read_line(); //mallocs line
args = lsh_split_line(line); //mallocs args array; consisting of references into line[]
status = lsh_execute(args); //no promblem here
add_history(args); // ***PERMANETNLY*** stores args[0] into cmd_history[]!!!
free(line); //frees line[]
free(args); //frees the pointer array into freed line[], which was copied to cmd_history[] --> cmd_history[] points to unallocated memory!!!
}
The main problem is related to strtok(): It returns a pointer into the spitted string, within the original string: If you later free the token pointer array or the original string and use it later --> UB.
Additionally lsh_history() kills also last entry which would leave a memory leak behind, if you would allocated that entry firmly.
Solution:
Establish for cmd_history[] your own storage of the strings, duplicate and free them properly in lsh_history().

Related

I'm trying to execute read in lines from file in C in a shell environment

I can compile the code, execute it with the file as a command line argument, but nothing happens. The interactive mode function prompts as normal, but not the batchMode function.
I'm trying to read in a line, and then execute that line.
example file
date
ls -la
cd
(Without spacing between lines. I can't get the formatting right on here.)
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#define bSize 1000
void driveLoop();
char *userInput(void);
void removeExit(char *original, char *subString); // removes string with substring "exit"
void batchMode(char *c);
int main(int argc, char **argv){
char *fTemp;
if (argc == 1)
driveLoop(); // calls the loop function that accepts input and executes commands.
else if (argc == 2)
batchMode(&argv[1][0]);
return 0;
}
void driveLoop(void){
char *comTokens[100];
char *tempTokens;
char *command;
char *cd;
char *cdDir;
char* cdTemp;
char cdBuf[bSize];
char checkExit[] = "exit";
for (;;){
printf("> ");
command = userInput(); // reads input
if (!*command) // allows for empty string error
break;
char *exitPtr = strstr(command, "exit"); // returns a value to a pointer if substring is found
removeExit(command, "exit");
puts(command); // updates the array after the function filter
int i = 0;
tempTokens = strtok(command, " \t\n"); // tokens are how the computer recognizes shell commands
while (tempTokens && i < 99){ // geeksforgeeks.com
comTokens[i++] = tempTokens;
tempTokens = strtok(NULL, "\t\n");
}
if (strcmp(comTokens[0], "exit") == 0) // exit if input is "exit" only
exit(0);
if(strcmp(comTokens[0], "cd") == 0){ // built in change directory command
cd = getcwd(cdBuf, sizeof(cdBuf));
cdDir = strcat(cd, "/");
cdTemp = strcat(cdDir, comTokens[1]); // cplusplus.com reference
chdir(cdTemp);
continue;
}
comTokens[i] = NULL;
pid_t cFork = fork(); // creates duplicate child process of parent
if (cFork == (pid_t) - 1){ // error check
perror("fork");
}
else if (cFork == 0) { // error codes found on cplusplus.com
execvp(comTokens[0], comTokens);
perror("exec");
}
else { // children are returned. parent executes
int status;
waitpid(cFork, &status, 0);
if (exitPtr != NULL){ // if substring exit was found, exit the program
exit(0);
}
}
}
}
char *userInput(void){ // referenced Linux man page - getline(3) (linux.die.net)
char *input = NULL;
size_t size = 0;
getline(&input, &size, stdin); // updates the size as it goes along
return input;
}
void removeExit(char *original, char *subString){ // removes exit from string
char *ex;
int len = strlen(subString);
while ((ex = strstr(original, subString))){ // Referenced from a Stack Overflow page.
*ex = '\0';
strcat(original, ex+len);
}
}
void batchMode(char *c){
char *tok[100];
char *batchTokens;
char *batchBuffer = NULL;
size_t batchSize = 0;
FILE *fp = fopen(c, "r");
unsigned int line = 1;
char buffer[bSize];
while(fgets(buffer, sizeof(buffer), fp)){
int i = 0;
char *toks = strtok(buffer, "\t\n");
while (toks && i < 99){
tok[i] = malloc (strlen(toks) + 1);
strcpy(tok[i++], toks);
toks = strtok(NULL, " \t\n");
}
tok[i] = NULL;
pid_t bFork = fork();
if (bFork == (pid_t) - 1)
perror("fork");
else if (bFork == 0){
execvp(tok[i], tok);
perror("exec");
}
else {
int status;
waitpid(bFork, &status, 0);
}
}
}
side note. This is a re-attempt from a previous question that was locked for inadequate information. I've updated my code and tried to be as detailed as possible.
I'll happily provide anything further to help answer my question.
Thank you all.
edit
I put in a fprintf to verify that it reads the file in, and it does.
First note in your input file, the last command cd isn't a system command, it is a shell built-it, so you would expect it to fail (unless handled specially).
Allocating for each token (tok[i]) as discussed with either strdup (if available) or simply malloc (strlen(toks) + 1); allows you to copy the current token to the block of memory allocated. Now each tok[i] will reference the individual token saved (instead of all pointing to the last token -- due to all being assigned the same pointer)
The biggest logic error in batchMode as your call to execvp with execvp (tok[i], tok); instead of properly providing the file to execute as tok[0]. Be mindful if the file to execute isn't in your PATH, you must provide an absolute path in your input file.
Making the changes, your batchMode could be written as follows (and removing all the unused variables and moving char *tok[100]; within the while loop so it is declared within that scope):
#include <sys/types.h>
#include <sys/wait.h>
...
void batchMode(char *c)
{
char buffer[bSize];
FILE *fp = fopen (c, "r");
if (!fp) {
perror ("fopen-c");
return;
}
while (fgets (buffer, sizeof(buffer), fp)) {
int i = 0;
char *tok[100];
char *toks = strtok(buffer, " \t\n");
while (toks && i < 99){
tok[i] = malloc (strlen(toks) + 1);
strcpy(tok[i++], toks);
toks = strtok(NULL, " \t\n");
}
tok[i] = NULL;
pid_t bFork = fork();
if (bFork == (pid_t) - 1)
perror("fork");
else if (bFork == 0){
execvp (tok[0], tok);
perror("exec");
}
else {
int status;
waitpid(bFork, &status, 0);
}
}
}
Example Input Files
I have two simple input files tested:
$ cat dat/toksfile.txt
echo hello
echo goodbye
$ cat dat/toksfile2.txt
date
ls -al dat/toksfile.txt
cd
Example Use/Output
$ ./bin/shellorbatch dat/toksfile.txt
hello
goodbye
$ ./bin/shellorbatch dat/toksfile2.txt
Tue Feb 4 23:47:00 CST 2020
-rw-r--r-- 1 david david 24 Feb 4 23:24 dat/toksfile.txt
exec: No such file or directory
Look things over and let me know if you have questions:

C Language- freeing memory after using strtok to build char**

and sorry for the title I couldn't think of a better way to phrase it.
so I have a C assignment working with fork and exec.
I have three programs called ps, echo and history all of them take different arguments. The final program is called shell and it takes in commands from stdin and calls for exec upon taking a proper command.
example:
ps -a
echo Hello World
history 1.txt
once it reads a line and find it's a valid command it makes a child process and calls for exec.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
const int MAX_LINE = 100;
const char *HISTORY = "./history";
const char *PS = "./ps";
const char *ECHO = "./echo";
void call_cmd(int cmd, char *const argv[]);
/* main function */
int main(int argc, char** argv)
{
FILE * out;
char line[MAX_LINE], line_print[MAX_LINE], seps[] = " \n", rm[80];
char *first, *tmp, ** params;
pid_t pid;
int cmd = -1, i = 0,j= 0;
if (argc != 2)
{
printf("Invalid arguments");
return EXIT_FAILURE;
}
out = fopen(argv[1],"w");
if (out == NULL)
{
perror("Couldn't open file to write");
return EXIT_FAILURE;
}
while(fgets(line,sizeof(line),stdin) != NULL)
{
strcpy(line_print,line);
params = (char**) malloc(sizeof(char*));
tmp = strtok(line,seps);
while (tmp != NULL)
{
if(i != 0)
params = (char**) realloc(params,sizeof(char*) * (i + 1));
params[i] = tmp;
j++;
tmp = strtok(NULL,seps);
i++;
}
first = params[0];
if (strcmp("exit",first) == 0)
{
sprintf(rm,"rm %s",argv[1]);
system(rm);
exit(0);
}
if(strcmp("echo",first) == 0)
cmd = 0;
if(strcmp("history",first) == 0)
cmd = 1;
if(strcmp("ps",first) == 0)
cmd = 2;
if(cmd == -1){
perror("\nInvalid Command\n");
}
if(cmd >= 0)
{
fprintf(out,"%s",line_print);
pid = fork();
if (pid == -1)
{
perror("Error Creating Child");
return EXIT_FAILURE;
}
if(pid == 0)
{
call_cmd(cmd,params);
exit(0);
}
}
for (i = 0; i < j ; i++)
free(params[i]);
free(params);
i = j = 0;
cmd = -1;
}
fclose(out);
return EXIT_SUCCESS;
}
void call_cmd(int cmd, char *const argv[])
{
switch(cmd)
{
case 0:
execv(ECHO, argv);
break;
case 1:
execv(HISTORY, argv);
break;
default:
execv(PS, argv);
break;
}
}
that is my code so far, it behaves in a weird way causing segmentation faults,
I'm pretty sure it's because of the way I split the parameters and free them.
example output:
*** Error in `./shell': double free or corruption (out): 0x00007ffe58f1a630 ***
Parent Id: 1928
Aborted (core dumped)
so I keep editing the for loop
for (i = 0; i < j ; i++)
free(params[i]);
all that does is just jump from double free to segmentation faults or I write a command like ps or history and it does nothing, so I must be doing something but I'm truly lost been trying to fix it for two days with, so if you see what I did wrong please point it out.
Thank you.
strtok parses a string in-place so you should not free the individual results. They are portions of the original string. You can use the POSIX function strdup to make copies that can be free'd, and will persist beyond the life of the original buffer contents.
You should add
params[0] = NULL;
right after the initial malloc (or use calloc) otherwise you'll be using an unitialized pointer if the line is empty. Then at the end
free(params);
you don't need to free any of params[i] since those are pointers into the local line[] buffer.

realloc reports incorrect checksum

I have this C program attempting a text line reading function that should be able to deal with lines of arbitrary length. It works by maintaining a buffer, whose size is doubled whenever there is need for more room.
The actual method is here:
/*******************************************************************************
* Attempts to expand the line buffer. If succeeded, TRUE is returned. *
*******************************************************************************/
static char* try_expand(char* buffer, int* p_buffer_length)
{
*p_buffer_length *= 2;
puts("Before realloc");
char* s = realloc(buffer, *p_buffer_length);
puts("After realloc");
if (s)
{
return s;
}
// Once here, realloc failed.
char* s2 = malloc(*p_buffer_length);
if (!s2)
{
return NULL;
}
strncpy(s2, buffer, *p_buffer_length / 2);
free(buffer);
return s2;
}
I am working on Mac OS X, and whenever the buffer expansion takes place, the program crashes and the system reports:
malloc: *** error for object 0x100105568: incorrect checksum for freed object - object was probably modified after being freed.
Everything else is here:
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define HELP_FLAG "-h"
#define VERSION_FLAG "-v"
#define FLAG_DESC "%-5s"
#define INITIAL_BUFFER_SIZE 8
#define FALSE 0
#define TRUE (~FALSE)
/*******************************************************************************
* This routine removes all leading and trailing whitespace from a string, *
* doing that in-place. (Total of two passes.) *
*******************************************************************************/
static char* trim_inplace(char* start)
{
return start;
/*
for (char* end = &start[strlen(start) - 1];
isspace(*end) && end >= start; --end)
{
*end = '\0';
}
while (isspace(*start))
{
++start;
}
return start;*/
}
/*******************************************************************************
* Processes a single line and handles everything needed for dealing with lines *
* of arbitrary length. *
*******************************************************************************/
static int process_line(char** p_buffer, int* p_buffer_length, FILE* file)
{
size_t current_index = 0;
for (;;)
{
char* ret = fgets(*p_buffer + current_index, *p_buffer_length, file);
if (!ret)
{
//puts("!ret is true.");
return FALSE;
}
// Find out whether we have a newline character, which would imply that
// we have an entire line read.
for (size_t i = 0; i < *p_buffer_length; ++i)
{
if ((*p_buffer)[i] == '\n')
{
//(*p_buffer)[i + 1] = '\0';
puts(trim_inplace(*p_buffer));
return TRUE;
}
}
// -1 for skipping the NULL-terminator.
current_index += *p_buffer_length - 1;
char* new_buffer;
// Once here, the current line does not fit in 'p_buffer'. Expand the
// array by doubling its capacity.
if (!(new_buffer = try_expand(*p_buffer, p_buffer_length)))
{
perror("Could not expand the line buffer");
free(*p_buffer);
exit(EXIT_FAILURE);
}
else
{
*p_buffer = new_buffer;
}
}
}
/*******************************************************************************
* Processes a file. *
*******************************************************************************/
static void process_file(char** p_buffer, int* p_buffer_length, FILE* file)
{
while (!feof(file))
{
process_line(p_buffer, p_buffer_length, file);
}
}
/*******************************************************************************
* Prints the help message and exits. *
*******************************************************************************/
static void print_help()
{
printf("Usage: trim [" HELP_FLAG "] [" VERSION_FLAG "] " \
"[FILE1, [FILE2, [...]]]\n" \
" " FLAG_DESC " Print the help message and exit.\n" \
" " FLAG_DESC " Print the version message and exit.\n" \
" If no files specified, reads from standard input.\n",
HELP_FLAG,
VERSION_FLAG);
}
/*******************************************************************************
* Prints the version string. *
*******************************************************************************/
static void print_version()
{
printf("trim 1.618\n" \
"By Rodion \"rodde\" Efremov 08.04.2015 Helsinki\n");
}
/*******************************************************************************
* Prints the erroneous flag. *
*******************************************************************************/
static void print_bad_flag(const char* flag)
{
printf("Unknown flag \"%s\"\n", flag);
}
/*******************************************************************************
* Checks the flags. *
*******************************************************************************/
static void check_flags(int argc, char** argv)
{
for (size_t i = 1; i < argc; ++i)
{
if (strcmp(argv[i], HELP_FLAG) == 0)
{
print_help();
exit(EXIT_SUCCESS);
}
else if (strcmp(argv[i], VERSION_FLAG) == 0)
{
print_version();
exit(EXIT_SUCCESS);
}
else if (argv[i][0] == '-')
{
print_bad_flag(argv[i]);
exit(EXIT_FAILURE);
}
}
}
/*******************************************************************************
* The entry point for a trivial line trimmer. *
*******************************************************************************/
int main(int argc, char** argv)
{
check_flags(argc, argv);
int buffer_length = INITIAL_BUFFER_SIZE;
char* buffer = malloc(buffer_length);
if (argc < 2)
{
// If realloc changes the location of memory, we need to know this.
process_file(&buffer, &buffer_length, stdin);
fclose(stdin);
return EXIT_SUCCESS;
}
for (size_t i = 1; i < argc; ++i)
{
FILE* file = fopen(argv[i], "r");
if (!file)
{
perror("Error opening a file");
return (EXIT_FAILURE);
}
process_file(&buffer, &buffer_length, file);
fclose(file);
}
}
The only observation I have made, is that if the input line requires only one expansion of the line buffer, everything is O.K. However, if the input line is large enough to require at least two expansions, the program crashes. What am I doing wrong here?
When you read further chunks in process_line(), you pass the wrong size:
fgets(*p_buffer + current_index, *p_buffer_length, file);
Should be
fgets(*p_buffer + current_index, *p_buffer_length - current_index, file);

Why are lines, output by this program, duplicated?

Ok, so, I am currently working on implementing a simpleshell history. Part of the requirement is to output all the commands to a file having them numbered per line.
I have everything else working well at the moment, but the output to the file is giving me a very specific issue. It is saving the commands and putting them in the file numbered, but it is giving me around 3 duplicate copies per command. Ex:
1 hello
1 hello
1 hello
2 there1 hello
2 there3 how1 hello
2 there3 how4 are1 hello
2 there3 how4 are5 you
I have searched over the previous topics on this site, and read up on file i/o. As far as I can tell, I am doing it correctly:
#include "parser.h"
#include "shell.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct node {
char info[MAXINPUTLINE]; //Struct setup for linked list.
struct node *link;
} *start;
void create(char[]); //Function prototypes.
void insert_end(char[]);
void last_cmd(char[]);
int main(void) {
char input[MAXINPUTLINE];
int count = 2;
start=NULL;
char data[MAXINPUTLINE];
FILE *file;
file = fopen(".simpleshell_history", "w");
char *history="history";
char *last="!!";
signal_c_init();
printf("Welcome to the sample shell! You may enter commands here, one\n");
printf("per line. When you're finished, press Ctrl+D on a line by\n");
printf("itself. I understand basic commands and arguments separated by\n");
printf("spaces, redirection with < and >, up to two commands joined\n");
printf("by a pipe, tilde expansion, and background commands with &.\n\n");
printf("\nclsh$ ");
fgets(data, sizeof(data), stdin);
create(data);
fprintf(file, "%d ", 1);
fprintf(file, "%s", data);
parse(data);
printf("\nclsh$ ");
while (fgets(input, sizeof(input), stdin)) {
stripcrlf(input);
parse(input);
insert_end(input);
fprintf(file, "%d ", count);
fprintf(file, "%s", input);
count++;
printf("\nclsh$ ");
}
fclose(file);
return 0;
}
//Functions below.
//Creation Function.
void create(char data[])
{
struct node *temp;
temp = (struct node *)malloc(sizeof(struct node));
if (start == NULL)
{
strcpy(temp->info, data);
temp->link=NULL;
start=temp;
}
}
//Insertion function
void insert_end(char data[])
{
struct node *ptr, *tempnode;
ptr = start;
while(1)
{
if(ptr->link != NULL)
{
ptr=ptr->link;
}
else
break;
}
tempnode=(struct node *)malloc(sizeof(struct node));
strcpy(tempnode->info, data);
tempnode->link=NULL;
ptr->link=tempnode;
}
//Last command repeat function
void last_cmd(char input[])
{
struct node *ptr;
ptr = start;
int i;
int length=0;
char temp[MAXINPUTLINE];
while (ptr!=NULL)
{
ptr=ptr->link;
length++;
}
ptr=start;
for (i=0; i<length-1; i++)
{
ptr=ptr->link;
strcpy(temp, ptr->info);
}
printf("%s", temp);
parse(temp);
}
I understand that some of my previous questions have not been received well, but I have tried my hardest on this, and am only asking this question as a final resort. I hope that I have done it in a manner that isn't annoying or against any rules, and can work to improve my reputation around here.
Thank you.
Edit: Ok, so I've isolated the problem down to the file that parses the input. It is causing the duplication of the lines, but I can't figure out how at this point. Any help is appreciated, but I will keep at it either way, thanks again for all the tips and advice.
Parser:
#include "parser.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pwd.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/resource.h>
#include <sys/wait.h>
/* Static Variables Referenced only in this file */
static int pipefd[2];
static int background;
void parse(char *cmdline)
{
char *cmdpart[2];
pipefd[0] = PARSE_NOPIPE; /* Init: default is no pipe */
background = checkbackground(cmdline);
/* Separate into individual commands if there is a pipe symbol. */
if (strstr(cmdline, "|"))
pipefd[0] = PARSE_USEPIPE;
/* Must do the strtok() stuff before calling parse_cmd because
strtok is used in parse_cmd or the functions parse_cmd calls. */
cmdpart[0] = strtok(cmdline, "|");
cmdpart[1] = strtok((char *)NULL, "|");
parse_cmd(cmdpart[0]);
if (cmdpart[1]) parse_cmd(cmdpart[1]);
}
/* parse_cmd will do what is necessary to separate out cmdpart and run
the specified command. */
void parse_cmd(char *cmdpart)
{
int setoutpipe = 0; /* TRUE if need to set up output pipe
after forking */
int pid; /* Set to pid of child process */
int fd; /* fd to use for input redirection */
char *args[MAXARGS + 5];
char *filename; /* Filename to use for I/O redirection */
splitcmd(cmdpart, args);
if (pipefd[0] == PARSE_USEPIPE) {
pipe(pipefd);
setoutpipe = 1;
}
pid = fork();
if (!pid) { /* child */
if (setoutpipe) {
dup2(pipefd[1], 1); /* connect stdout to pipe if necessary */
}
if (!setoutpipe && (pipefd[0] > -1)) {
/* Need to set up an input pipe. */
dup2(pipefd[0], 0);
}
filename = parseredir('<', args);
if (filename) { /* Input redirection */
fd = open(filename, O_RDONLY);
if (!fd) {
fprintf(stderr, "Couldn't redirect from %s", filename);
exit(255);
}
dup2(fd, 0);
}
if ((filename = parseredir('>', args))) { /* Output redirection */
fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (!fd) {
fprintf(stderr, "Couldn't redirect to %s\n", filename);
exit(255);
}
dup2(fd, 1);
}
if (!args[0]) {
fprintf(stderr, "No program name specified.\n");
exit(255);
}
execvp(args[0], args);
/* If failed, die. */
exit(255);
} else { /* parent */
if ((!background) &&
(!setoutpipe))
waitpid(pid, (int *)NULL, 0);
else
if (background)
fprintf(stderr, "BG process started: %d\n", (int) pid);
if (pipefd[0] > -1) { /* Close the pipe if necessary. */
if (setoutpipe)
close(pipefd[1]);
else
close(pipefd[0]);
}
} /* if (!pid) */
freeargs(args);
} /* parse_cmd() */
/* splitcmd() will split a string into its component parts.
Since splitcmd() uses strdup, freeargs() should be called on the
args array after it is not used anymore. */
void splitcmd(char *cmdpart, char *args[])
{
int counter = 0;
char *tempstr;
tempstr = strtok(cmdpart, " ");
args[0] = (char *)NULL;
while (tempstr && (counter < MAXARGS - 1)) {
args[counter] = strdup(expandtilde(tempstr));
args[counter + 1] = (char *)NULL;
counter++;
tempstr = strtok(NULL, " ");
}
if (tempstr) { /* Broke out of loop because of num of args */
fprintf(stderr, "WARNING: argument limit reached, command may be truncated.\n");
}
}
/* expandtilde() will perform tilde expansion on str if necessary. */
char *expandtilde(char *str)
{
static char retval[MAXINPUTLINE];
char tempstr[MAXINPUTLINE];
char *homedir;
char *tempptr;
int counter;
if (str[0] != '~') return str; /* No tilde -- no expansion. */
strcpy(tempstr, (str + 1)); /* Make a temporary copy of the string */
if ((tempstr[0] == '/') || (tempstr[0] == 0))
tempptr = (char *)NULL;
else { /* Only parse up to a slash */
/* strtok() cannot be used here because it is being used in the function
that calls expandtilde(). Therefore, use a simple substitute. */
if (strstr(tempstr, "/"))
*(strstr(tempstr, "/")) = 0;
tempptr = tempstr;
}
if ((!tempptr) || !tempptr[0]) { /* Get user's own homedir */
homedir = gethomedir();
} else { /* Get specified user's homedir */
homedir = getuserhomedir(tempptr);
}
/* Now generate the output string in retval. */
strcpy(retval, homedir); /* Put the homedir in there */
/* Now take care of adding in the rest of the parameter */
counter = 1;
while ((str[counter]) && (str[counter] != '/')) counter++;
strcat(retval, (str + counter));
return retval;
}
/* freeargs will free up the memory that was dynamically allocated for the
array */
void freeargs(char *args[])
{
int counter = 0;
while (args[counter]) {
free(args[counter]);
counter++;
}
}
/* Calculates number of arguments in args */
void calcargc(char *args[], int *argc)
{
*argc = 0;
while (args[*argc]) {
(*argc)++; /* Increment while non-null */
}
(*argc)--; /* Decrement after finding a null */
}
/* parseredir will see if it can find a redirection operator oper
in the array args[], and, if so, it will return the parameter (filename)
to that operator. */
char *parseredir(char oper, char *args[])
{
int counter;
int argc;
static char retval[MAXINPUTLINE];
calcargc(args, &argc);
for (counter = argc; counter >= 0; counter--) {
fflush(stderr);
if (args[counter][0] == oper) {
if (args[counter][1]) { /* Filename specified without a space */
strcpy(retval, args[counter] + 1);
argsdelete(args + counter);
return retval;
} else { /* Space seperates oper from filename */
if (!args[counter+1]) { /* Missing filename */
fprintf(stderr, "Error: operator %c without filename", oper);
exit(255);
}
strcpy(retval, args[counter+1]);
argsdelete(args + counter + 1);
argsdelete(args + counter);
return retval;
}
}
}
return NULL; /* No match */
}
/* Argsdelete will remove a string from the array */
void argsdelete(char *args[])
{
int counter = 0;
if (!args[counter]) return; /* Empty argument list: do nothing */
free(args[counter]);
while (args[counter]) {
args[counter] = args[counter + 1];
counter++;
}
}
Edit: Ok, so I got it. The parse(input) and stripcrlf(input) were messing with it. How, I'm not sure, but that's beside the point. What I needed to do in the end was to not just open up the file and leave it open until it was closed just before the end of the program. I had to do this: fopen(".simpleshell_history", "a"); fprintf(file, "%d ", count); fprintf(file, "%s\n", input); fclose(file); Basically, I had to open the file, put the stuff in, and then close it before the parser and such could have an effect. The output is perfect now. Thanks everyone for your help.
I removed irrelevant lines from the question code:
#include <stdio.h>
#include <string.h>
#define MAXINPUTLINE 100
int main(void) {
char input[MAXINPUTLINE];
int count = 2;
// start=NULL;
char data[MAXINPUTLINE];
FILE *file;
I modified the file name from ".simpleshell_history" to "simpleshell_history" so that the file was not hidden (for testing purposes).
file = fopen("simpleshell_history", "w");
// char *history="history";
// char *last="!!";
// signal_c_init();
printf("Welcome to the sample shell! You may enter commands here, one\n");
printf("per line. When you're finished, press Ctrl+D on a line by\n");
printf("itself. I understand basic commands and arguments separated by\n");
printf("spaces, redirection with < and >, up to two commands joined\n");
printf("by a pipe, tilde expansion, and background commands with &.\n\n");
printf("\nclsh$ ");
fgets(data, sizeof(data), stdin);
// create(data);
fprintf(file, "%d ", 1);
fprintf(file, "%s", data);
// parse(data);
printf("\nclsh$ ");
while (fgets(input, sizeof(input), stdin)) {
// stripcrlf(input);
// parse(input);
// insert_end(input);
fprintf(file, "%d ", count);
fprintf(file, "%s", input);
I added the following two lines to allow a way to break out of the loop.
if(0 == strncmp("quit", input, 4))
break;
count++;
printf("\nclsh$ ");
}
fclose(file);
return 0;
}
I ran the program:
> ./test
Welcome to the sample shell! You may enter commands here, one
per line. When you're finished, press Ctrl+D on a line by
itself. I understand basic commands and arguments separated by
spaces, redirection with < and >, up to two commands joined
by a pipe, tilde expansion, and background commands with &.
clsh$ hello
clsh$ there
clsh$ QUIT
clsh$ QUIT
clsh$ quit
The content of the file "simpleshell_history":
1 hello
2 there
3 QUIT
4 QUIT
5 quit
The code seems to execute as expected with no unexpected "Output file content duplication".

cat with fork execvp pipe and dup2

This is one step of a set of exercises I'm doing. The program I write should take more than two arguments. The use of the first argument is not implemented yet. The rest of the arguments are a list of directories.
In this step what I have to do is to create an instance of cat for each directory given in arguments, take the contents of the all files of each directory using cat and print the content. I should be able to handle paths such as /home/directory and /home/directory/ both (with the last / or without)
Currently what I am doing is trying to run cat with argument /home/directory/* so that it will read all the files in the given directory and return the content of them. This is my code:
#include "Step1.h"
int main(int argc,char* argv[])
{
if(argc < 3)
{
printf("Usage: ./Step1 <exclusions file> <folder1> <folder2> <folder3> ...\n");
return -1;
}
int i;
for(i=2; i<argc; i++)
{
int catpipe[2];
if(pipe(catpipe))
{
printf("Error in pipe\n");
return -1;
}
pid_t pid = fork();
if(pid < 0)
{
printf("Error in fork\n");
return -1;
}
if(!pid)
{
dup2(catpipe[1],1); // cat pipe is used to get the output of cat program into this program.
close(catpipe[1]);
close(catpipe[0]);
char* args[3];
args[0] = "/bin/cat";
int length = strlen(argv[i]);
char* path;
if(argv[i][length - 1] != '/') // the path given does not have the ending /
{
path = malloc(length + 3);
strcpy(path,argv[i]);
path[length] = '/'; //append / at the end
path[length+1] = '*'; // append * at the end
path[length+2] = '\0';
}
else
{
path = malloc(length + 2); // the path contains the ending /
strcpy(path,argv[i]);
path[length] = '*'; // append * at the end
path[length+1] = '\0';
}
args[1] = path;
args[2] = NULL;
printf("%s\n",path);
execvp("/bin/cat",args);
}
close(catpipe[1]);
char buffer[200];
int total = read(catpipe[0],buffer,200); // read the output of cat program and print it.
buffer[total]='\0';
printf("The buffer contains: %s\n",buffer);
}
return 0;
}
I ran this code as follows:
mod#mod-Inspiron-N5110:~/project$ ./Step1 exclusions ./testdirectory1 ./testdirectory2/
and the result I got is:
/bin/cat: ./testdirectory1/*: No such file or directory
The buffer contains:
The buffer contains: ./testdirectory2/*
mod#mod-Inspiron-N5110:~/project$ /bin/cat: ./testdirectory2/*: No such file or directory
mod#mod-Inspiron-N5110:~/project$
but when I do:
mod#mod-Inspiron-N5110:~/project$ /bin/cat ./testdirectory1/*
The result is:
Testline 1 of testfile1 in testdirectory1
Test line 1 of testfile1 in testdirectory1
Testline 1 of testfile2 in testdirectory1
Testline 1 of testfile3 in testdirectory1
Please help me to get this result with my program.
Thanks in advance
I think I figured out the answer for my own question. I'm posting this in belief that somebody just like me would find it useful one day. But I'm not very good at C yet. So if this answer is wrong or if there's some mistake/weakness in this, feel free to correct it.
The key for my answer is using the function wordexp(). It can be used to expand the given path: tilde, regular expressions, variables, and all. So this is my answer:
#include "Step1.h"
int main(int argc,char* argv[])
{
if(argc < 3)
{
printf("Usage: ./Step1 <exclusions file> <folder1> <folder2> <folder3> ...\n");
return -1;
}
int i;
for(i=2; i<argc; i++)
{
int catpipe[2];
if(pipe(catpipe))
{
printf("Error in pipe\n");
return -1;
}
pid_t pid = fork();
if(pid < 0)
{
printf("Error in fork\n");
return -1;
}
if(!pid)
{
dup2(catpipe[1],1); // cat pipe is used to get the output of cat program into this program.
close(catpipe[1]);
close(catpipe[0]);
int length = strlen(argv[i]);
char* path;
if(argv[i][length - 1] != '/') // the path given does not have the ending /
{
path = malloc(length + 3);
strcpy(path,argv[i]);
path[length] = '/'; //append / at the end
path[length+1] = '*'; // append * at the end
path[length+2] = '\0';
}
else
{
path = malloc(length + 2); // the path contains the ending /
strcpy(path,argv[i]);
path[length] = '*'; // append * at the end
path[length+1] = '\0';
}
wordexp_t p;
char **w;
wordexp(path, &p, 0);
char** args = malloc((p.we_wordc + 2) * sizeof(char*));
args[0] = "/bin/cat";
w = p.we_wordv;
int j;
for (j = 0; j < p.we_wordc; j++)
args[j+1] = w[j];
args[p.we_wordc + 1] = NULL;
execvp("/bin/cat",args);
}
close(catpipe[1]);
char buffer[1024];
int total = read(catpipe[0],buffer,1024); // read the output of cat program and print it.
buffer[total]='\0';
printf("The buffer contains: %s\n",buffer);
}
return 0;
}
So basically what you get wrong in your code is that you assume that if you give /directory/* as an argument to cat, cat will expand it as "many files", whereas cat understands that you want to open a file called *.
The simple way to have a * expansion would be to call cat through a shell, which will be doing the expansion from * to "many files".
To achieve so, you can replace your execvp() with a system(…) call, which is basically execl("sh", "sh", "-c", …, NULL) where … is your command (i.e. you could simply call "/bin/sh", "-c", "/bin/cat", "/directory/*") and there * should be expanded by the shell.
Or you can work your own recursive directory walk algorithm, such as that one.

Resources