Getline behavior with printf in loop - c

Why do I need to hit enter twice to get the current directory to change? I notice that if I change the location of the printf statement in the loop, the behavior changes. I don't understand why that is.
Working code for you below. It compiles on a Ubuntu system. It works fairly minimally. Most of it is usage from man-pages.
#define _GNU_SOURCE // http://man7.org/linux/man-pages/man3/getline.3.html
#include <stdio.h>
#include <unistd.h> // http://man7.org/linux/man-pages/man3/getcwd.3.html
int main() {
char *line = NULL;
size_t linecap = 0;
ssize_t linelen;
while ((linelen = getline(&line, &linecap, stdin)) > 0) {
char * buf = NULL;
size_t size = 1000;
char * s = getcwd(buf, size);
printf("%s# ", s);
*(line+linelen-1) = '\0';
chdir(&line[3]);
}
return 0;
}
I don't get the output to the program, which I show below (first, I need to hit enter once to get the prompt -- that's okay and intended for this example).
/path/to/dir# cd ..
/path/to/dir#
/path/to#

Your loop does the following logic:
Wait for user input
Print the current directory
Change the current directory
So each time you enter user input, it will show the old directory, change directory (with no visible output) and then wait for new input. This is indeed what your sample output shows.
To get your desired behaviour, move the getcwd and display down to below the chdir call.
Note that if the person subsequently enters a string shorter than 3 (e.g. they press Enter again, I did while testing and so did you) you go on to call chdir(&line[3]); anyway, causing undesired results (possibly UB) since this is past the end of the string. On my system it repeated the chdir(".."). To fix this , you should probably check linelen >= 3 (and also that it did actually start with cd), and not call chdir if not.

Related

Different execution flow using read() and fgets() in C

I have a sample program that takes in an input from the terminal and executes it in a cloned child in a subshell.
#define _GNU_SOURCE
#include <stdlib.h>
#include <sys/wait.h>
#include <sched.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
int clone_function(void *arg) {
execl("/bin/sh", "sh", "-c", (char *)arg, (char *)NULL);
}
int main() {
while (1) {
char data[512] = {'\0'};
int n = read(0, data, sizeof(data));
// fgets(data, 512, stdin);
// int n = strlen(data);
if ((strcmp(data, "exit\n") != 0) && n > 1) {
char *line;
char *lines = strdup(data);
while ((line = strsep(&lines, "\n")) != NULL && strcmp(line, "") != 0) {
void *clone_process_stack = malloc(8192);
void *stack_top = clone_process_stack + 8192;
int clone_flags = CLONE_VFORK | CLONE_FS;
clone(clone_function, stack_top, clone_flags | SIGCHLD, (void *)line);
int status;
wait(&status);
free(clone_process_stack);
}
} else {
exit(0);
}
}
return 0;
}
The above code works in an older Linux system (with minimal RAM( but not in a newer one. Not works means that if I type a simple command like "ls" I don't see the output on the console. But with the older system I see it.
Also, if I run the same code on gdb in debugger mode then I see the output printed onto the console in the newer system as well.
In addition, if I use fgets() instead of read() it works as expected in both systems without an issue.
I have been trying to understand the behavior and I couldn't figure it out. I tried doing an strace. The difference I see is that the wait() return has the output of the ls program in the cases it works and nothing for the cases it does not work.
Only thing I can think of is that read(), since its not a library function has undefined behavior across systems. But I can't agree as to how its affecting the output.
Can someone point me out to why I might be observing this behavior?
EDIT
The code is compiled as:
gcc test.c -o test
strace when it's not working as expected is shown below
strace when it's working as expected (only difference is I added a printf("%d\n", n); following the call for read())
Thank you
Shabir
There are multiple problems in your code:
a successful read system call can return any non zero number between 1 and the buffer size depending on the type of handle and available input. It does not stop at newlines like fgets(), so you might get line fragments, multiple lines, or multiple lines and a line fragment.
furthermore, if read fills the whole buffer, as it might when reading from a regular file, there is no trailing null terminator, so passing the buffer to string functions has undefined behavior.
the test if ((strcmp(data, "exit\n") != 0) && n > 1) { is performed in the wrong order: first test if read was successful, and only then test the buffer contents.
you do not set the null terminator after the last byte read by read, relying on buffer initialization, which is wasteful and insufficient if read fills the whole buffer. Instead you should make data one byte longer then the read size argument, and set data[n] = '\0'; if n > 0.
Here are ways to fix the code:
using fgets(), you can remove the line splitting code: just remove initial and trailing white space, ignore empty and comment lines, clone and execute the commands.
using read(), you could just read one byte at a time, collect these into the buffer until you have a complete line, null terminate the buffer and use the same rudimentary parser as above. This approach mimics fgets(), by-passing the buffering performed by the standard streams: it is quite inefficient but avoids reading from handle 0 past the end of the line, thus leaving pending input available for the child process to read.
It looks like 8192 is simply too small a value for stack size on a modern system. execl needs more than that, so you are hitting a stack overflow. Increase the value to 32768 or so and everything should start working again.

In a get_line implementation, how do I allow the user to move their cursor?

So, I'm currently working on a small shell.
I get user input with my own getline implementation, which is repeatedly calling fgetc(stdin) and realloc-ing to read a line.
How do I allow the user to use the left and right keys to move the cursor in the input he's currently writing?
The function:
#define LINE_BUFSIZE 256
static char *get_line(void)
{
char *line = malloc(LINE_BUFSIZE);
char *linep = line;
size_t lenmax = LINE_BUFSIZE;
size_t len = lenmax;
int c;
if (!line)
return NULL;
for (;;) {
c = fgetc(stdin);
if (c == EOF)
break;
if (--len == 0) {
len = lenmax;
lenmax *= 3;
lenmax /= 2;
char *linen = realloc(linep, lenmax);
if (!linen) {
free(linep);
return NULL;
}
line = linen + (line - linep);
linep = linen;
}
if ((*line++ = c) == '\n')
break;
}
*line = '\0';
return linep;
}
There are basically three ways to do this. In decreasing order of effort:
Put terminal in raw mode to be able to receive characters such as Ctrl-B and so on, then process them. That's reinventing the wheel, really, and unless you're willing to spend lots of time for nothing (unless for learning), don't go there.
As this has been solved a hundred times over, and is needed by many programs, a library named termcap has been developed to abstract the terminal capabilities. If you want your program to not just work in an xterm, but also on other terminals, this is the way to go.
Use the GNU readline library. From its manual:
#include <stdio.h>
#include <readline/readline.h>
#include <readline/history.h>
char *
readline (const char *prompt);
DESCRIPTION
readline will read a line from the terminal and return it, using prompt
as a prompt. If prompt is NULL or the empty string, no prompt is
issued. The line returned is allocated with malloc(3); the caller must
free it when finished. The line returned has the final newline
removed, so only the text of the line remains.
readline offers editing capabilities while the user is entering the
line. By default, the line editing commands are similar to those of
emacs. A vi-style line editing interface is also available.

Process returned -1 (0xFFFFFFFF)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main() {
printf("Transactional Shell Command Test.\n");
while(1) {
printf("Queue:");
char input[500];
fgets (input, 500, stdin);
if(strstr(input, "qb-write")){
printf("These are the commands you have queued:\n");
FILE *cmd = popen("cat /home/$USER/.queueBASH_transactions", "r");
char buf[256];
while (fgets(buf, sizeof(buf), cmd) != 0) {
printf("%s\n",buf);
}
pclose(cmd);
}
system(strncat("echo ",strncat(input," >> /home/$USER/.qb_transactions",500),500));
usleep(20000);
}
return 0;
}
I am attempting to make a concept for a transactional shell, and I'm having it output every command you enter into a file in the user's home directory. It's not completely finished, but I'm doing one part at a time. When I put in any input to the "shell", it crashes. Codeblocks tells me "Process returned -1 (0xFFFFFFFF)" and then the usual info about runtime. What am I doing wrong here?
strncat appends to its first argument in place, so you need to pass it a writable buffer as the first argument. You're passing a string literal ("echo "), which depending on your compiler and runtime environment may either overwrite unpredictable parts of the memory, or crash because it's trying to write to read-only memory.
char command[500];
strcpy(command, "echo ");
strncat(command, input, sizeof(command)-1-strlen(command));
strncat(command, " >> /home/$USER/.qb_transactions", sizeof(command)-1-strlen(command));
system(command);
As with the rest of your code, I've omitted error checking, so the command will be truncated if it doesn't fit the buffer. Also note that repeated calls to strncat are inefficient since they involve traversing the string many times to determine its end; it would be more efficient to use the return value and keep track of the remaining buffer size, but I'm leaving this as a follow-up exercise.
Of course invoking a shell to append to a file is a bad idea in the first place. If the input contains shell special characters, they'll be evaluated. You should open the log file and write to it directly.
char log_file[PATH_MAX];
strcpy(log_file, getenv("HOME"));
strncat(log_file, "/.qb_transactions", PATH_MAX-1-strlen(log_file));
FILE *log_file = fopen(log_file, "a");
…
while (1) {
…
fputs(cmd, log_file);
}
fclose(log_file);
(Once again, error checking omitted.)

Need clarification regarding gets function in C

I am new to C programming. I am trying to practice a simple exercise problem: It is the following:
Write a program that reads input lines one by one until the end of file is reached, determines the length of each input line, and then prints only the longest line that was found. You may assume the maximum input line length is 1000 characters.
My code is pasted here:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_LEN 1000
int main(void)
{
char line[MAX_LEN], line_temp[MAX_LEN];
int largest_line_size = 0;
int current_line_size;
while ( gets ( line_temp ) != NULL)
{
if ( ( current_line_size = strlen( line_temp ) ) > largest_line_size )
{
strcpy(line, line_temp);
largest_line_size = current_line_size;
}
/*printf("%s\n", line); */
}
printf("%d", largest_line_size);
printf("%s", line);
return EXIT_SUCCESS;
}
The problem is that the code runs, accepts input, but I never get to a point where the while loop breaks. How do I encounters a NULL pointer when using gets() function?
Your help is much appreciated.
Thanks,
The question says You must read the input from a file.
Why are you accepting it at run time from the user?
For example, if fr is a file, you can do the following.
fr = fopen ("filename", "rt"); /* open the file for reading */
/* filename is the name of the file */
/* "rt" means open the file for reading text */
while(fgets(line_temp,MAX_LEN, fr) != NULL)
{ //...do your stuff }
Note that fgets() returns NULL when there are no more lines in the file.
I have figured out the problem myself. The way you get to terminate the loop in the above code when reading characters from stdin is by pressing CTRL-D (Unix) or CTRL-Z in Windows. In either cases, an EOF is triggered and the gets or fgets command returns a NULL pointer. Sorry for wasting time, if that.
Thanks

Forking with command line arguments

I am building a Linux Shell, and my current headache is passing command line arguments to forked/exec'ed programs and system functions.
Currently all input is tokenized on spaces and new lines, in a global variable char * parsed_arguments. For example, the input dir /usa/folderb would be tokenized as:
parsed_arguments[0] = dir
parsed_arguments[1] = /usa/folderb
parsed_arguments tokenizes everything perfectly; My issue now is that i wish to only take a subset of parsed_arguments, which excludes the command/ first argument/path to executable to run in the shell, and store them in a new array, called passed_arguments.
so in the previous example dir /usa/folderb
parsed_arguments[0] = dir
parsed_arguments[1] = /usa/folderb
passed_arguments[0] = /usa/folderb
passed_arguments[1] = etc....
Currently I am not having any luck with this so I'm hoping someone could help me with this. Here is some code of what I have working so far:
How I'm trying to copy arguments:
void command_Line()
{
int i = 1;
for(i;parsed_arguments[i]!=NULL;i++)
printf("%s",parsed_arguments[i]);
}
Function to read commands:
void readCommand(char newcommand[]){
printf("readCommand: %s\n", newcommand);
//parsed_arguments = (char* malloc(MAX_ARGS));
// strcpy(newcommand,inputstring);
parsed = parsed_arguments;
*parsed++ = strtok(newcommand,SEPARATORS); // tokenize input
while ((*parsed++ = strtok(NULL,SEPARATORS)))
//printf("test1\n"); // last entry will be NULL
//passed_arguments=parsed_arguments[1];
if(parsed[0]){
char *initial_command =parsed[0];
parsed= parsed_arguments;
while (*parsed) fprintf(stdout,"%s\n ",*parsed++);
// free (parsed);
// free(parsed_arguments);
}//end of if
command_Line();
}//end of ReadCommand
Forking function:
else if(strstr(parsed_arguments[0],"./")!=NULL)
{
int pid;
switch(pid=fork()){
case -1:
printf("Fork error, aborting\n");
abort();
case 0:
execv(parsed_arguments[0],passed_arguments);
}
}
This is what my shell currently outputs. The first time I run it, it outputs something close to what I want, but every subsequent call breaks the program. In addition, each additional call appends the parsed arguments to the output.
This is what the original shell produces. Again it's close to what I want, but not quite. I want to omit the command (i.e. "./testline").
Your testline program is a sensible one to have in your toolbox; I have a similar program that I call al (for Argument List) that prints its arguments, one per line. It doesn't print argv[0] though (I know it is called al). You can easily arrange for your testline to skip argv[0] too. Note that Unix convention is that argv[0] is the name of the program; you should not try to change that (you'll be fighting against the entire system).
#include <stdio.h>
int main(int argc, char **argv)
{
while (*++argv != 0)
puts(*argv);
return 0;
}
Your function command_line() is also reasonable except that it relies unnecessarily on global variables. Think of global variables as a nasty smell (H2S, for example); avoid them when you can. It should be more like:
void command_Line(char *argv[])
{
for (int i = 1; argv[i] != NULL; i++)
printf("<<%s>>\n", argv[i]);
}
If you're stuck with C89, you'll need to declare int i; outside the loop and use just for (i = 1; ...) in the loop control. Note that the printing here separates each argument on a line on its own, and encloses it in marker characters (<< and >> — change to suit your whims and prejudices). It would be fine to skip the newline in the loop (maybe use a space instead), and then add a newline after the loop (putchar('\n');). This makes a better, more nearly general purpose debug routine. (When I write a 'dump' function, I usually use void dump_argv(FILE *fp, const char *tag, char *argv[]) so that I can print to standard error or standard output, and include a tag string to identify where the dump is written.)
Unfortunately, given the fragmentary nature of your readCommand() function, it is not possible to coherently critique it. The commented out lines are enough to elicit concern, but without the actual code you're running, we can't guess what problems or mistakes you're making. As shown, it is equivalent to:
void readCommand(char newcommand[])
{
printf("readCommand: %s\n", newcommand);
parsed = parsed_arguments;
*parsed++ = strtok(newcommand, SEPARATORS);
while ((*parsed++ = strtok(NULL, SEPARATORS)) != 0)
{
if (parsed[0])
{
char *initial_command = parsed[0];
parsed = parsed_arguments;
while (*parsed)
fprintf(stdout, "%s\n ", *parsed++);
}
}
command_Line();
}
The variables parsed and parsed_arguments are both globals and the variable initial_command is set but not used (aka 'pointless'). The if (parsed[0]) test is not safe; you incremented the pointer in the previous line, so it is pointing at indeterminate memory.
Superficially, judging from the screen shots, you are not resetting the parsed_arguments[] and/or passed_arguments[] arrays correctly on the second use; it might be an index that is not being set to zero. Without knowing how the data is allocated, it is hard to know what you might be doing wrong.
I recommend closing this question, going back to your system and producing a minimal SSCCE. It should be under about 100 lines; it need not do the execv() (or fork()), but should print the commands to be executed using a variant of the command_Line() function above. If this answer prevents you deleting (closing) this question, then edit it with your SSCCE code, and notify me with a comment to this answer so I get to see you've done that.

Resources