Related
Before I start, I just want to say that this is for a school assignment of mine. I'm really close to finishing, except well, since I'm here, obvious to say, I'm stuck on a problem :(.
First of I'll explain what my assignment wants:
The assignment wants me to create a command-line program in C that allows the user to type in N number of /bin/ commands (E.g. > /bin/ps /bin/ls "/bin/which gcc"). Once the user enters the command and hits the enter key, the parent process (the parent process is the program) will create N child processes (i.e. no. of /bin/ commands entered = no. of child processes parent process will create). Each child will run one of the N commands. All the children will be running concurrently, with the parent waiting for each child to terminate.
Once a child terminates, the parent will print whether the command executed successfully or not (E.g. "Command /bin/ps has completed successfully" or "Command /bin/ps has not completed successfully") and once all children have been terminated, the parent will print "All done, bye!"
The issue:
So I've managed to get my child processes to run concurrently, the only issue is that I'm not sure how to pipe the value of the command (like /bin/ps or /bin/which gcc) from the child process to the parent process to print out the success or not message. I've tried putting the write pipe above my execv which allows me to pipe what I want but the execv won't output anything and I can't put my pipe code below my execv because in that case, then while my execv output will show, my pipe won't. I did think that it might be due to close(1) but commenting that out didn't change the result.
So what I am trying to achieve is something like this:
> /bin/ls "/bin/which gcc" /bin/domainname /bin/fake_command
Output:
/usr/bin/gcc
localdomain
Command /bin/which gcc has completed successfully
Command /bin/domainname has completed successfully
a.txt b.c
Command /bin/ls has completed successfully
Command /bin/fake_command has not completed successfully
All done, bye!
>
But right now, I'm getting:
> /bin/ls "/bin/which gcc" /bin/domainname /bin/fake_command
Output:
Command /bin/which gcc has completed successfully
Command /bin/domainname has completed successfully
Command /bin/ls has completed successfully
Command /bin/fake_command has not completed successfully
>
As you can see, my execv output for the /bin/ commands aren't shown in the output.
I've tried searching SO for people who faced this similar issue as me but none of their solutions managed to work for me which is why I'm asking here. If there's anything you're not clear about, please let me know and I will try my best to explain.
The code:
q1.c
#include "q1.h"
int main(int argc, char *argv[])
{
int
child_status,
pipe_array[2];
pid_t child;
char *success_or_fail;
char *msg_buffer = malloc(CHAR_MAX);
if (msg_buffer == NULL)
{
return -1;
}
struct timespec tw = {.tv_sec = 0, .tv_nsec = 10000000L};
Tuple *process_tuple;
size_t tuple_size = sizeof(*process_tuple) + sizeof(pid_t) + sizeof(char *);
process_tuple = calloc(argc, tuple_size);
if (process_tuple == NULL)
{
return -1;
}
// if (pipe(pipe_array) == -1)
// {
// perror("pipe: ");
// return -1;
// }
for (int j = 1; j < argc; j++)
{
child = fork();
if (child == 0)
{
int
executed,
num_of_words,
num_of_chars;
char
string[strlen(argv[j]) + 1],
*backup = argv[j];
snprintf(string, sizeof(string), "%s", argv[j]);
num_of_chars = get_num_of_chars(string);
num_of_words = get_num_of_words(string);
char *command[num_of_chars + 1];
preparing_the_command(num_of_words, string, command);
// close(pipe_array[0]);
// close(1);
// dup2(pipe_array[1], STDOUT_FILENO);
// write(pipe_array[1], backup, sizeof(backup));
process_tuple[j - 1].pid = getpid();
process_tuple[j - 1].command = backup;
printf(" %i-PID -> %i\n %i-Command -> %s\n\n", process_tuple[j - 1].pid, process_tuple[j - 1].pid, process_tuple[j - 1].pid, process_tuple[j - 1].command);
executed = execv(command[0], command);
nanosleep(&tw, 0);
if (executed == -1)
{
exit(EXIT_FAILURE);
}
else
{
exit(EXIT_SUCCESS);
}
}
else if (child == -1)
{
perror("fork() failed: ");
exit(EXIT_FAILURE);
}
}
printf(" PID -> %i\n Command -> %s\n\n", process_tuple[0].pid, process_tuple[0].command);
// while ((child = waitpid(-1, &child_status, 0)) != -1)
// {
// for (int o = 0; o < argc; o++)
// {
// printf(" PID -> %i\n Command -> %s\n\n", process_tuple[o].pid, process_tuple[o].command);
// }
// close(0);
// close(pipe_array[1]);
//
// dup2(pipe_array[0], STDIN_FILENO);
// char *recipient;
//
// read(pipe_array[0], recipient, sizeof(recipient));
// if (!(WIFEXITED(child_status) && (WEXITSTATUS(child_status) == 0)))
// {
// success_or_fail = "not completed successfully";
// }
// else
// {
// success_or_fail = "completed successfully";
// }
// snprintf(msg_buffer, CHAR_MAX, "Command %s has %s\n", recipient, success_or_fail);
// fputs(msg_buffer, stdout);
// }
fputs("All done, bye!\n", stdout);
free(msg_buffer);
return 0;
}
int get_num_of_chars(const char string[])
{
int
i = 0,
num_of_chars = 0;
while (string[i++] != '\0')
{
if (string[i] != ' ' && string[i] != '\t')
{
num_of_chars++;
}
}
return num_of_chars;
}
int get_num_of_words(const char string[])
{
int
i = 0,
num_of_words = 0;
bool is_not_separator = false;
while (string[i++] != '\0')
{
if (string[i] == ' ' || string[i] == '\t')
{
is_not_separator = false;
}
else if (!is_not_separator)
{
is_not_separator = true;
num_of_words++;
}
}
return num_of_words;
}
void preparing_the_command(int num_of_words, char string[], char *command[])
{
char *token;
for (int j = 0; j < num_of_words && (token = strtok_r(string, " ", &string)); j++)
{
command[j] = token;
}
command[num_of_words] = (void *) NULL;
}
q1.h
#ifndef ASSIGNMENT2Q1_Q1_H
#define ASSIGNMENT2Q1_Q1_H
/***************
** LIBRARIES **
***************/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <limits.h>
/*************
** STRUCTS **
*************/
typedef struct
{
pid_t pid;
char *command;
} Tuple;
/*************************
** FUNCTION PROTOTYPES **
*************************/
int get_num_of_chars(const char string[]);
int get_num_of_words(const char string[]);
void preparing_the_command(int num_of_words, char string[], char *command[]);
#endif //ASSIGNMENT2Q1_Q1_H
I am trying to write a mock shell that saves command line history and overrides the signal action for SIGINT to trigger printing the previous 10 commands entered by the user. As far as I am aware, everything from handling all of the signals to updating cursor and running commands using execvp works fine.
I however am running into 2 problems that I am having a hard time trying to wrap my head around.
Trouble trying to actually copy the contents of the input buffer to my own c-string vector, namely histv. After calling read and storing user input in buf, I try to copy the contents of buf to the location at histv[cursor % MAX_HISTORY] (the reason I am using modulus is because it seems easier to use a kind of circular array instead of handling the case that cursor rises to some number greater than MAX_HISTORY)
When I try running the process as a background process I always get an execvp error, even when the command is valid. I know it's happing after the parent process gets the SIGUSR2 signal and creates a new child to run the commands. So I am assuming it has something to do with what happens to argv after the child process kills itself and raises SIGUSR2
Below is the code for the entire program. It is a bit messy in some spots, but overall it's rather simple. I have used all of memcpy, strcpy, and strncpy to no avail. They all compile and run without errors, but none of them seem to do anything.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
// limits
#define MAX_LINE 80
#define MAX_HISTORY 10
// function headers
void print_history(unsigned int const, char**);
void handler_func(int);
void read_func(char*[], char[], char**, unsigned int const);
int parse_args(char*, char**, size_t);
// globals
volatile sig_atomic_t sig_caught = 0;
void print_history (const unsigned int cursor, char **histv) {
int temp = (cursor > MAX_HISTORY) ? (cursor - MAX_HISTORY) : 0;
puts("\n\nprinting the previous ten commands...");
printf("cursor %d", cursor);
for (int i = 1; temp < cursor; temp++) {
printf("%d%s\n", i++, histv[temp % MAX_HISTORY]);
}
}
void handler_func(int sig)
{
/* update loop control variable */
sig_caught = 1;
}
int main(void)
{
// declare sigaction struct
struct sigaction sigactor;
// initialize sigaction struct
sigactor.sa_handler = handler_func;
sigemptyset(&sigactor.sa_mask);
sigactor.sa_flags = 0;
// set up sigaction for SIGINT
if (sigaction(SIGINT, &sigactor, NULL) == -1) {
perror("siagction() failed");
_exit(EXIT_FAILURE);
}
// set the buffer to no buffering
setvbuf(stdout, NULL, _IONBF, 0);
unsigned int cursor = 0;
/* initlialize history vector */
char **histv = (char**)malloc(sizeof(char*) * MAX_HISTORY);
for (int i = 0; i < MAX_HISTORY; i++)
histv[i] = (char*)malloc(sizeof(char) * MAX_LINE);
// enter shell loop
while (1) {
/* fork process and get child pid */
int cpid;
char *argsv[MAX_LINE/2+1];
char buf[MAX_LINE];
while(!sig_caught) {
cpid = fork();
/* child */
if (cpid == 0) {
read_func(argsv, buf, histv, cursor);
}
/* fork error */
else if (cpid < 0) {
perror("Error forking process");
_exit(EXIT_FAILURE);
}
/* parent process begins here */
else {
/* variable to store status returned from child*/
int cstatus;
/* suspend parent until child exits *
* store return status in cstatus */
waitpid(cpid, &cstatus, 0);
/* get status from child process and check for SIGTERM *
* SIGTERM is raised by child when someone enters '!q' */
switch(WTERMSIG(cstatus))
{
/* user wants to quit */
case SIGTERM:
puts("User issued quit command");
for (int i = 0; i < MAX_HISTORY; i++)
free((void *)histv[i]);
free((void *)histv);
_exit(EXIT_SUCCESS);
/* invalid string length */
case SIGUSR1:
puts("Please enter a valid string");
break;
/* background process */
case SIGUSR2:
cpid = fork();
if (cpid < 0) perror("Error forking process...");
else if (cpid == 0) {
if (execvp(argsv[0], argsv) < 0) {
--cursor;
perror("execvp");
kill(getpid(), SIGUSR1);
}
}
}
if (!sig_caught) cursor++;
}
}// signal loop
kill (cpid, SIGTERM);
print_history(cursor, histv);
fflush(stdout);
sig_caught = 0;
}
}
void read_func(char *argsv[], char buf[], char *histv[], unsigned int const cursor)
{
printf("\nCMD > ");
int background = 0;
size_t length = read(STDIN_FILENO, buf, MAX_LINE);
if (length > 80 || length <= 0) kill(getpid(), SIGUSR1);
/* copy buffer into history and update cursor */
memcpy(histv[cursor % MAX_HISTORY], buf, length);
printf("cursor %d", cursor);
/* parse arguments and return number of arguments */
background = parse_args(buf, argsv, length);
/* user entered quit command or string is invalid */
if (background == -1) kill(getpid(), SIGTERM);
/* signal parent to run process in the background */
if (background == 1) kill(getpid(), SIGUSR2);
/* run command */
if (execvp(argsv[0], argsv) < 0) {
perror("execvp");
_exit(EXIT_FAILURE);
}
}
int parse_args(char buf[], char *argsv[], size_t length)
{
int i, /* loop index for accessing buf array */
start, /* index where beginning of next command parameter is */
ct, /* index of where to place the next parameter into args[] */
bckg; /* background flag */
/* read what the user enters on the command line */
ct = 0;
start = -1;
bckg = 0;
if (buf[0] == '!' && buf[1] == 'q') return -1;
/* examine every character in the buf */
for (i = 0; i < length; i++) {
switch (buf[i]){
case ' ':
case '\t': /* argument separators */
if(start != -1){
argsv[ct] = &buf[start]; /* set up pointer */
ct++;
}
buf[i] = '\0'; /* add a null char; make a C string */
start = -1;
break;
case '\n': /* should be the final char examined */
if (start != -1){
argsv[ct] = &buf[start];
ct++;
}
buf[i] = '\0';
argsv[ct] = NULL; /* no more arguments to this command */
break;
case '&':
bckg = 1;
buf[i] = '\0';
break;
default: /* some other character */
if (start == -1)
start = i;
}
}
argsv[ct] = NULL; /* just in case the input line was > 80 */
return bckg;
}
Side note, the parse_args function was initially given to us and I changed it a bit to work with the rest of program, but for the most part I did not write that function.
Please go easy on me, it's been a long time since I've used C for anything and a lot of what I am doing here took a lot of effort to begin understanding how this program behaves. (~:
So both of the problems was with the fact that the memory wasn't being shared, as o11c described. I fixed the issue by using mmap instead of malloc which in turn simplified my program as I no longer had to handle the memory management. Changes are described below.
char **histv = (char**)malloc(sizeof(char*) * MAX_HISTORY);
for (int i = 0; i < MAX_HISTORY; i++)
histv[i] = (char*)malloc(sizeof(char) * MAX_LINE);
was changed to
char **histv = (char**)mmap(NULL, (sizeof(char*) * MAX_HISTORY), (PROT_READ | PROT_WRITE), (MAP_SHARED | MAP_ANONYMOUS), -1, 0);
for (int i = 0; i < MAX_HISTORY; i++) histv[i] = (char*)mmap(NULL, (sizeof(char) * MAX_LINE), (PROT_READ | PROT_WRITE), (MAP_SHARED | MAP_ANONYMOUS), -1, 0);
While I do not know if this is the best way to do this, it solved my problem.
Also note that I did explicitly unmap the memory using munmap even though it technically should be handled automatically on exit.
I wrote a little daemon.
This is the flow of the daemon, in general:
Get variables
Get all rows from the database (may be either MySQL or Oracle) that meets the query parameters (in this case get all rows that has this current time).
If any rows were found then run a Perl script for each row (using execv).
This daemon works well, but the problem is that when I have two rows or more coming back from the query, they start, but the Perl script outputs are mixed. They are supposed to run independently without interfering with each other.
Am I doing something wrong?
Is this a memory leak?
Here is my code :
/*
============================================================================
Name : main.c
Description : Daemon for scheduler
============================================================================
*/
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <syslog.h>
#include <string.h>
#include <strings.h>
#include <regex.h>
#include <time.h>
#ifdef MYSQL_CODE
#include <mysql.h>
#endif
#ifdef ORACLE_CODE
#include <ocilib.h>
#endif
#define DAEMON_NAME "schedulerd"
void start_job(char *automation_user,char *automation_path,char *automation_log_path, char *id, char *site, char *db_type,char *db_host,char *db_user,char *db_password,char *db_schemata){
pid_t pid;
pid = fork();
if (pid < 0) { exit(EXIT_FAILURE); }
//We got a good pid, Continue to the next result
if (pid > 0) {
return;
}
char *file_format = "scheduler";
char *seperator = "_";
char *postfix = "_XXXXXX";
char *extension = ".log";
//get Time
time_t now;
struct tm *now_tm;
char hour[2],min[2],sec[2];
now = time(NULL);
now_tm = localtime(&now);
sprintf(hour, "%d", now_tm->tm_hour);
sprintf(min, "%d", now_tm->tm_min);
sprintf(sec, "%d", now_tm->tm_sec);
char *str = (char *)automation_log_path;
strcat(str,(char *)file_format);
strcat(str,seperator);
strcat(str,hour);
strcat(str,seperator);
strcat(str,min);
strcat(str,seperator);
strcat(str,sec);
strcat(str,postfix);
// buffer to hold the temporary file name
char nameBuff[128];
int filedes = -1;
// memset the buffers to 0
memset(nameBuff,0,sizeof(nameBuff));
// Copy the relevant information in the buffers
strncpy(nameBuff,str,128);
// Create the temporary file, this function will replace the 'X's
filedes = mkstemp(nameBuff);
if(filedes<1)
{
syslog (LOG_NOTICE, "Creation of temp file [%s] failed with error [%s]\n",nameBuff,strerror(errno));
}else{
mode_t perm = 0644;
fchmod(filedes, perm); //Change permission to the file so everyone can read
close(filedes); // Close created file
//Rename file
int ret;
char newname[128];
sprintf(newname, "%s%s", nameBuff,extension);
ret = rename(nameBuff, newname);
if(ret != 0) {
syslog (LOG_NOTICE, "Renaming of temp file %s to %s failed \n",nameBuff,newname);
exit(EXIT_FAILURE);
}
char statement[256];
sprintf(statement, "UPDATE scheduler_list SET log_file='%s' WHERE id='%s'", newname,id);
syslog (LOG_NOTICE,"Adding to DB : %s\n", statement);
char command[2048];
sprintf(command, "cd %s ; ./runner.pl -site %s -log %s -scheduler_id %s -command \\\"./run_me.pl\\\"", automation_path,site,newname,id);
//sprintf(command, "cd /net/10.7.5.50/opt/trunk/ ; ./runner.pl -site %s -log %s -scheduler_id %s -command \\\"./run_me.pl\\\"",site,newname,id);
if (strcasestr(db_type,"mysql")){/* mysql */
#ifdef MYSQL_CODE
MYSQL *conn;
//mysql_free_result(res); // Free mysql
conn = mysql_init(NULL);
/* Connect to database */
if (!mysql_real_connect(conn, db_host,db_user, db_password, db_schemata, 0, NULL, 0)) {
syslog (LOG_NOTICE,"%s\n", mysql_error(conn));
exit(EXIT_FAILURE);
}
if (mysql_query(conn,statement)) {
syslog (LOG_NOTICE,"%s\n", mysql_error(conn));
exit(EXIT_FAILURE);
}
#endif
}else{
#ifdef ORACLE_CODE
OCI_Connection* cn;
OCI_Statement* st;
OCI_Initialize(NULL, NULL, OCI_ENV_DEFAULT);
char query_command[128];
sprintf(query_command, "%s:1521/%s", db_host,db_schemata);
cn = OCI_ConnectionCreate(query_command, db_user, db_password, OCI_SESSION_DEFAULT);
st = OCI_StatementCreate(cn);
OCI_Prepare(st, statement);
OCI_Execute(st);
OCI_Commit(cn);
OCI_Cleanup();
#endif
}
char *args[] = {"sudo", "-u", automation_user, "bash","-c",command,NULL};
FILE *log_file_h = fopen(newname, "w");
if (log_file_h == NULL)
{
syslog (LOG_NOTICE,"Error opening file %s !\n",newname);
exit(EXIT_FAILURE);
}
syslog (LOG_NOTICE,"Starting scheduler job %s , command : %s",id, command);
fclose(log_file_h);
execv("/usr/bin/sudo",args);
syslog (LOG_NOTICE,"Failed to start job %s ",id);
perror("error");
}
exit(EXIT_FAILURE);
}
void process(char *automation_user,char *automation_path, char *automation_log_path ,char *db_type,char *db_host,char *db_user,char *db_password,char *db_schemata){
if (strcasestr(db_type,"mysql")){/* mysql */
#ifdef MYSQL_CODE
MYSQL *conn;
MYSQL_RES *res;
MYSQL_ROW row;
conn = mysql_init(NULL);
/* Connect to database */
if (!mysql_real_connect(conn, db_host,db_user, db_password, db_schemata, 0, NULL, 0)) {
syslog (LOG_NOTICE,"%s\n", mysql_error(conn));
return;
}
/* send SQL query */
if (mysql_query(conn, "SELECT id,site from scheduler_list where start_date = DATE_FORMAT(now(),'%Y-%m-%d %k:%i:00') AND id != father_id AND run='yes'")) {
syslog (LOG_NOTICE,"%s\n", mysql_error(conn));
return;
}
res = mysql_use_result(conn);
/* output table name */
while ((row = mysql_fetch_row(res)) != NULL){
char *id = malloc(strlen(row[0]) +1);
strcpy(id,row[0]);
char *site = malloc(strlen(row[1]) +1);
strcpy(site,row[1]);
start_job(automation_user,automation_path,automation_log_path,id,site,db_type, db_host,db_user,db_password,db_schemata);
}
/* close connection */
mysql_free_result(res);
mysql_close(conn);
#endif
}else{/* oracle */
#ifdef ORACLE_CODE
OCI_Connection* cn;
OCI_Statement* st;
OCI_Resultset* rs;
OCI_Initialize(NULL, NULL, OCI_ENV_DEFAULT);
char query_command[128];
sprintf(query_command, "%s:1521/%s", db_host,db_schemata);
cn = OCI_ConnectionCreate(query_command, db_user, db_password, OCI_SESSION_DEFAULT);
st = OCI_StatementCreate(cn);
OCI_ExecuteStmt(st, "SELECT id,site from scheduler_list where to_char(start_date, 'yyyy-mm-dd hh24:mi') = to_char(SYSDATE, 'yyyy-mm-dd hh24:mi') AND id != father_id AND run='yes'");
rs = OCI_GetResultset(st);
while (OCI_FetchNext(rs)){
char *id = malloc(strlen(OCI_GetString(rs, 1)) +1);
strcpy(id,OCI_GetString(rs,1));
char *site = malloc(strlen(OCI_GetString(rs,2)) +1);
strcpy(site,OCI_GetString(rs,2));
start_job(automation_user,automation_path,automation_log_path,id,site,db_type, db_host,db_user,db_password,db_schemata);
}
OCI_Cleanup();
#endif
}
}
char * set_conf_param (char *line, int addSlash){
char *param = malloc(strlen(line) + 2 + addSlash);
strcpy(param,line);
param = strchr(line,'=');
param = param+1; //Remove '='
strtok(param, "\n"); //remove /n
if (addSlash == 1){
int len = strlen(param);
param[len] = '/';
param[len+1] = '\0';
}
return strdup(param);
}
int main(int argc, char *argv[]) {
FILE * fp;
char * line = NULL;
size_t len = 0;
int found_db = 0;
ssize_t read;
pid_t pid, sid;
char *automation_user=NULL,*automation_log_path=NULL ,*db_type=NULL, *db_host=NULL , *db_user=NULL, *db_password=NULL, *db_schemata=NULL;
char *automation_path = getenv("AUTOMATION_PATH");
//char *automation_path = "/net/10.7.5.50/opt/trunk/";
char *automation_user_search = "automation_user=";
char *automation_log_path_search = "automation_log=";
char *db_type_search = "type=";
char *db_host_search = "host=";
char *db_user_search = "user=";
char *db_password_search = "password=";
char *db_schemata_search = "schemata=";
const char comment = '#';
/* Change the working directory to the root directory */
/* or another appropriated directory */
chdir(automation_path);
//Set our Logging Mask and open the Log
setlogmask(LOG_UPTO(LOG_NOTICE));
openlog(DAEMON_NAME, LOG_CONS | LOG_NDELAY | LOG_PERROR | LOG_PID, LOG_USER);
syslog(LOG_NOTICE, "Entering Daemon");
//Read framework.conf
fp = fopen("framework.conf", "r");
if (fp == NULL){
syslog (LOG_NOTICE,"Failed to open framework.conf");
exit(1);
}
//Read framework.conf
fp = fopen("framework.conf", "r");
if (fp == NULL){
syslog (LOG_NOTICE,"Failed to open framework.conf");
exit(1);
}
while ((read = getline(&line, &len, fp)) != -1) {
//If line commented
if (strchr(line,comment) != NULL){
continue;
}
if (strstr(line,automation_user_search) != NULL){
automation_user = set_conf_param(line,0);
}
else if (strstr(line,automation_log_path_search) != NULL){
automation_log_path = set_conf_param(line,1);
}
else if (db_type!=NULL && strcasestr(line,db_type) != NULL){
found_db = 1;
}
else if (strstr(line,db_type_search) != NULL){
db_type = set_conf_param(line,0);
}
else if (found_db && db_host==NULL && strstr(line,db_host_search) != NULL){
db_host = set_conf_param(line,0);
}
else if (found_db && db_user==NULL && strstr(line,db_user_search) != NULL){
db_user = set_conf_param(line,0);
}
else if (found_db && db_password==NULL && strstr(line,db_password_search) != NULL){
db_password = set_conf_param(line,0);
}
else if (found_db && db_schemata==NULL && strstr(line,db_schemata_search) != NULL){
db_schemata = set_conf_param(line,0);
}
}
fclose(fp);
if (line)
free(line);
if (automation_user==NULL){
automation_user = "root";
}
//Fork the Parent Process
pid = fork();
if (pid < 0) { exit(EXIT_FAILURE); }
//We got a good pid, Close the Parent Process
if (pid > 0) { exit(EXIT_SUCCESS); }
//Change File Mask
umask(0);
//Create a new Signature Id for our child
sid = setsid();
if (sid < 0) { exit(EXIT_FAILURE); }
//Close Standard File Descriptors
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
//----------------
//Main Process
//----------------
while(1){
process(automation_user,automation_path,automation_log_path,db_type,db_host,db_user,db_password,db_schemata); //Run our Process
sleep(60); //Sleep for 60 seconds
}
//Close the log
closelog ();
exit(EXIT_FAILURE);
}
You don't appear to have made any effort to write a minimal, complete verifiable example.
This is just a dump of your whole program, including dependencies on a config file you don't provide, and databases whose schemata you don't show. None of which are even relevant to your question.
At least you edited out the company name, but for reference it's still visible in the edit history.
threads in C are writing each other stdout
I can't see any threads in your program, and you don't fork threads anyway - what you have are child processes
... are writing each other stdout
well, stdout is inherited from the parent, so all children (and their sudo/bash/perl/whatever children) are writing to the same place. The writes themselves are locked, but can be interleaved.
If you want their output not to be interleaved, have them write to different places, and figure out how to display/combine them later.
Per-child-process temporary files are a popular choice, but note the parent will have to track child process completion in order to know when to print and then delete each file. Also, you should probably detach child processes from the terminal, close stdin, and consider what to do with stderr.
Is this a memory leak?
No.
I guess (after a quick glimpse) that the problem is in
execv("/usr/bin/sudo",args);
you should make sure that only one instance is running.
this can be done by mutual exclusion.
A good description is on:
http://www.thegeekstuff.com/2012/05/c-mutex-examples/
In short:
// create a global mutex variable
pthread_mutex_t lock;
// initialise it in main
pthread_mutex_init(&lock, NULL);
// enclose your critical path
pthread_mutex_lock(&lock);
execv("/usr/bin/sudo",args);
pthread_mutex_unlock(&lock);
Hope it Helps
I want to implement my own shell. I'm stucked on implementing background process . Actually ,I write some piece of code but I'm not sure if it is work or not .
my code :
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define MAX_LINE 80 /* 80 chars per line, per command, should be enough. */
#define HISTORY_SIZE 5 /*keep track of 5 most recent commands*/
int cmd_count; /*global to keep track of most recent commands entered*/
char history[HISTORY_SIZE][MAX_LINE]; /* global so it can be accessed in interrupt handler. */
void viewHistory()
{
int i;
if (cmd_count < 1)
printf("No command history to show. \n");
else {
printf("\n\n");
for (i = (cmd_count >= HISTORY_SIZE) ? cmd_count - HISTORY_SIZE:0;
i < cmd_count; i++)
printf("%d: %s\n",i+1,history[i%HISTORY_SIZE]);
}
//printf("SystemsIIShell->");
}
int setup(char inputBuffer[], char *args[],int *background)
{
int length, /* # of characters in the command line */
i, /* loop index for accessing inputBuffer array */
start, /* index where beginning of next command parameter is */
ct; /* index of where to place the next parameter into args[] */
int temp;
ct = 0;
/* 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 */
}else{
inputBuffer[length]='\0';
if(inputBuffer[0]=='r'){
if(inputBuffer[1]=='r'){
if(cmd_count==0){
printf("No recent command can be found in the history. \n");
return 0;
}
strcpy(inputBuffer,history[(cmd_count)% HISTORY_SIZE]);
}else{
temp = atoi(&inputBuffer[1]);
if(temp < 1 || temp > cmd_count || temp <= cmd_count -HISTORY_SIZE){
printf("Command number cannot be found. \n");
return 0;
}
strcpy(inputBuffer,history[(temp-1)%HISTORY_SIZE]);
}
length = strlen(inputBuffer);
}
cmd_count++;
strcpy(history[(cmd_count-1)%HISTORY_SIZE], inputBuffer);
for (i = 0; i < length; i++) {
if (inputBuffer[i] == '&') {
inputBuffer[i] = '\0';
*background = 1;
--length;
break;
}
}
}
/* examine every character in the inputBuffer */
for (i = 0; i < length; i++) {
switch (inputBuffer[i]){
case ' ':
case '\t' : /* argument separators */
if(start != -1){
args[ct] = &inputBuffer[start]; /* set up pointer */
ct++;
}
inputBuffer[i] = '\0'; /* add a null char; make a C string */
start = -1;
break;
case '\n': /* should be the final char examined */
if (start != -1){
args[ct] = &inputBuffer[start];
ct++;
}
inputBuffer[i] = '\0';
args[ct] = NULL; /* no more arguments to this command */
break;
case '&':
*background = 1;
inputBuffer[i] = '\0';
break;
default : /* some other character */
if (start == -1)
start = i;
}
}
args[ct] = NULL; /* just in case the input line was > 80 */
}
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("SystemsIIShell->");
fflush(0);
setup(inputBuffer, args, &background); /* get next command */
pid_t child; /* process id for child */
int status; /* status for execvp */
child = fork(); /* create a child process*/
if(child < 0){ /* if the child process didn't return 0, the fork is failed */
printf("Fork failed! \n");
}else if(child==0){ /* child process */
if(inputBuffer[0]=='history' || inputBuffer[0] =='h'){
viewHistory();
return 0;
}
status = execvp(args[0],args);
if(status !=0){
printf("%s: command not found. \n", args[0]);
}
}else{ /* parent process */
if(background == 0)
waitpid(child,&background,0);
}
/* 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. */
}return 0;
}
I don't add whole code but the other things are true. I call execv and it works. When I write on the console :
output terminal :
$ gedit ------->it works correctly because it is a foreground
$ gedit & -----> it opens a gedit file which name is "&"
$ firefox ---> it works correctly
$ firefox & ---> it opens a firefox window which url is www.&.com
How can fix it ?any suggestions ?
editing part : https://github.com/iqbalhasnan/CSE2431-System-II/blob/master/lab2/lab2.c --> I use this code as a reference
you're not implementing background processes, you're trying to start a background process using the syntax of the already-implemented-shell on your computer (but honestly, it's pretty hard to tell what's going on with that indentation. That's really bad. Can you make it readable, please?). That '&' character is recognised by your shell, not by execvp. Have a look at this similar looking question which was the first hit in a google search for your problem.
I have an assignment to make a shell in C code, and I have a solution that works most of the time. My solution works if the program exists, and I can exit my shell with either Control-D or by typing exit. But when I try a command that I know doesn't exist, my shell will print an error message saying command not found but I will have to either type exit or press Control-D the same amount of times as a invalid command was entered i.e. if I type a wrong command 3 times, I then have to hit Control-D 3 times. I really don't know what is going on here. I checked all the variables and read is -1 when I press Control-D but the if statement seems to be skipped.
Here is the parts of my source code that I think the problem is in:
comp20200Shell.c
#include "comp20200Shell_header.h"
#include <signal.h>
/*
* Name: ****
* Student Number: ****
* Email: ****
*
* This is the main function of my shell implementation.
*
*/
int main(void)
{
bool end_program = false;
size_t length = 0;
ssize_t read;
char* current_directory = NULL;
char* current_time = NULL;
/* Sets up signal handler to catch SIGINT*/
if(signal(SIGINT, sigintHandler) == SIG_ERR)
{
error("An error occured while setting a signal handler\n");
}
/* Infinitive loop, so after command or invalid comman will prompt again*/
while(end_program != true)
{
char* input = NULL;
/* Gets current working directory */
current_directory = return_current_directory();
/* Gets current date and time */
current_time = return_time();
/* Prints Prompt */
printf("%s\x5b%s\x5d %s%s %s%s%s", MAGENTA_TEXT, current_time, GREEN_TEXT, current_directory, BLUE_TEXT, PROMPT, RESET_COLOUR);
/* Frees the pointers returned by return_time() and return_current_directory() */
free(current_time);
free(current_directory);
/* Reads one line from standard input */
read = getline(&input, &length, stdin);
/* Checks if ctrl d, i.e. end of file is found or exit is typed */
if(strcmp(input, "exit\n") == 0 || read == -1)
{
if(read == -1)
{
putchar('\n');
}
/* Frees input */
free(input);
return(0);
}
/* Removes newline character that will be at the end */
remove_trailing_newline(input);
/* Passes input to process input, and the return value is passed in to process errors */
process_errors(process_input(&input));
/* Frees input */
free(input);
}
return(0);
}
process_input.c
#include "comp20200Shell_header.h"
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/*
* Name: ****
* Student Number: ****
* Email: ****
*
* This function is used to process the command entered by the user
*
* return: the error value or 0 when everything whent ok
* arguments: the command entered by the user
*
*/
int process_input(char** input)
{
bool redirect_stdout = false;
bool redirect_stderr = false;
pid_t child_pid;
int child_status;
char** argument = malloc(sizeof(char*));
int count = 0;
char* temp = strtok(*input, " ");
while(temp != NULL)
{
argument[count] = temp;
count ++;
argument = realloc(argument, (count+2) * sizeof(char *));
temp = strtok(NULL, " ");
}
argument[count] = NULL;
if(argument[0] == NULL)
{
return(0);
}
else if(strcmp(argument[0], "cd") == 0)
{
return(change_directory(argument[1]));
}
int index;
for(index = 1; argument[index] != NULL; index++)
{
if(strcmp(argument[index], ">0") == 0)
{
if(argument[index + 1] == NULL)
{
return(EINVAL);
}
redirect_stdout = true;
break;
}
else if(strcmp(argument[index], ">2") == 0)
{
if(argument[index + 1] == NULL)
{
return(EINVAL);
}
redirect_stderr = true;
break;
}
}
child_pid = fork();
if(child_pid == 0)
{
int file;
if(redirect_stdout == true)
{
file = open(argument[index + 1], O_WRONLY|O_CREAT|O_TRUNC, 0666);
dup2(file, 1);
edit_arguments(argument, index);
execvp(argument[0], argument);
return(-1);
}
else if(redirect_stderr == true)
{
file = open(argument[index + 1], O_WRONLY|O_CREAT|O_TRUNC, 0666);
dup2(file, 2);
edit_arguments(argument, index);
execvp(argument[0], argument);
return(-1);
}
execvp(argument[0], argument);
return(-1);
}
else
{
wait(&child_status);
}
return(child_status);
}
comp20200Shell_header.h
/*
* Name: ****
* Student Number: ****
* Email: ****
*
* This is my header file, It includes all common used headerfiles on the top.
* Any specific header file that is only used once will be included with the .c file that needs it.
*
*/
/* included headerfiles begin */
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdbool.h>
/* included headerfiles end */
/* defenitions begin */
#define PROMPT "# "
#define BUFFER_SIZE 1024
#define BLUE_TEXT "\x1B[34m"
#define MAGENTA_TEXT "\x1B[35m"
#define GREEN_TEXT "\x1B[32m"
#define RESET_COLOUR "\x1B[0m"
/* defenitions end */
/* Function prototypes begin */
void remove_trailing_newline(char *input);
void sigintHandler(int sig_num);
int process_input(char** input);
char* return_time(void);
void error(const char *fmt, ...);
int change_directory(char* path);
char* return_current_directory(void);
void process_errors(int return_value);
void edit_arguments(char** argument, int index);
/* Function prototypes end */
I have omitted the rest of the source code as I don't think the problem is there.
In your child, after the call to execvp you need to call exit(EXIT_FAILURE); instead of return -1;. Otherwise your child will continue running, and will interpret the next command (that is why you need to exit N times where N is the number of inexistant commands you tried to invoke).
After the change, your parent process will see that the child terminated with a non-zero return code and should interpret the error code. There is no real way to distinguish between a failure from the execvp (due to a non-existent command) or from the invoked process. I would recommend printing the error from execvp if there is one in the child before the exit.
Note that if execvp succeed, it will never return, so the code following a call to execvp can only be executed if the command failed.
So, my recommendation is doing this:
if(child_pid == 0)
{
int file;
if(redirect_stdout == true)
{
file = open(argument[index + 1], O_WRONLY|O_CREAT|O_TRUNC, 0666);
dup2(file, 1);
edit_arguments(argument, index);
execvp(argument[0], argument);
perror("execvp");
exit(EXIT_FAILURE);
}
else if(redirect_stderr == true)
{
file = open(argument[index + 1], O_WRONLY|O_CREAT|O_TRUNC, 0666);
dup2(file, 2);
edit_arguments(argument, index);
execvp(argument[0], argument);
perror("execvp");
exit(EXIT_FAILURE);
}
execvp(argument[0], argument);
perror("execvp");
exit(EXIT_FAILURE);
}
else
{
wait(&child_status);
}
You should be doing exit(1); or equivalent instead of return(-1);. You might want to use _exit(1);, or _exit(255); (or _exit(-1);, but it is equivalent to _exit(255);). You might well want to print an error message to standard error before you exit.
When you don't exit, you end up with two, then three, then N shells all trying to read input from the terminal. You have to make each one quit separately by indicating EOF with Control-D. If you tried typing commands, then it would become a lottery which shell gets each character, and that leads to chaos (and grave danger; you may have thought you typed grep fungible form.file | tr -d 'fr' > /tmp/x33 but if one of the shells got rm -fr /, you've got trouble!).
Instead of return -1, you can use exit(1) or exit (-1) to exit from that portion if it fails to execute due to some error.