I am trying to understand how the seek pointer mechanism works when a parent process opens a new file and then creating a new child process using fork().
Assume I have the following code :
int main(){
int fd = open("myFile.txt", O_RDWR | O_CREAT)
if(fork() == 0){
write(fd,"stack overflow", 16);
close(fd);
}
else{
wait(NULL);
char buff[17];
read(fd, &buff, 16);
printf("%s", buff);
}
}
I get nothing printing to stdout, but I don't really understand why would it behave that way. Wouldn't it be smarter if there were a different seek pointer for read and write so running this code will result in printing "stack overflow" to stdout?
Apparently not, Would like to get an explanation
Both processes are using the same file description (i.e. the same open file).
They are accessing the same file pointer because it's part of the same file description. When one of them writes some characters, the file pointer advances for all of them, because it's the same file pointer.
You can get the file pointer by calling lseek(fd, 0, SEEK_CUR). If you print the file pointer before starting the child program, and after waiting for it, you should see that it has changed.
Related
I have a method I call from the main method called that executes ls-l on a certain directory, I want it to execute it and send the result as a string to the main method.
My current flawed code:
char *lsl(){
char *stringts=malloc(1024);
chdir("/Users/file/path");
char * lsargs[] = { "/bin/ls" , "-l", NULL};
stringts="The result of ls-l in the created directory is:"+ execv(lsargs[0], lsargs);
return stringts;
}
Currently I am only getting the exec output on the screen, I understand why this is happening(exec getting called before reaching return point). However I don't know how I could possibly do what I want and if it's actually doable.
I was thinking of using pipes and dup2() so I don't let the exec function use stdout but I don't know if it would be possible to put the output in a string.
As Jonathan Leffler already pointed out in comments, there is no '+' operator for concatenating strings in C.
A possibility to dynamically extends strings is to use realloc together with strcat.
For each number of bytes you read from the pipe, you could check the remaining capacity of the originally allocated memory for the string and, if this is not enough, reallocate twice the size.
You have to keep track of the size of the current string yourself. You could do this with a variable of type size_t.
If you combine this with the popen handling, it could look something like this:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
FILE *fp;
if ((fp = popen("ls -l", "r")) == NULL) {
perror("popen failed");
return EXIT_FAILURE;
}
size_t str_size = 1024;
char *stringts = malloc(str_size);
if (!stringts) {
perror("stringts allocation failed");
return EXIT_FAILURE;
}
stringts[0] = '\0';
char buf[128];
size_t n;
while ((n = fread(buf, 1, sizeof(buf) - 1, fp)) > 0) {
buf[n] = '\0';
size_t capacity = str_size - strlen(stringts) - 1;
while (n > capacity) {
str_size *= 2;
stringts = realloc(stringts, str_size);
if (!stringts) {
perror("stringts realloation failed");
return EXIT_FAILURE;
}
capacity = str_size - strlen(stringts) - 1;
}
strcat(stringts, buf);
}
printf("%s\n", stringts);
free(stringts);
if (pclose(fp) != 0) {
perror("pclose failed");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
You have several flaws in your code:
char *lsl(){
char *stringts=malloc(1024);
chdir("/Users/file/path");
char * lsargs[] = { "/bin/ls" , "-l", NULL};
stringts="The result of ls-l in the created directory is:"+ execv(lsargs[0], lsargs);
return stringts;
}
If you malloc(3) a 1024 byte buffer into stringts pointer, but then you assign a different value to the pointer, making your buffer to be lost in the immensity of your RAM.
When you do execv(2) call, all the memory of your process is freed by the kernel and reloaded with an execution of the command ls -l, you'll get the output in the standard output of the process, and then you'll get the prompt of the shell. This makes the rest of your program unuseful, as once you exec, there's no way back, and your program is unloaded and freed.
You can add (+) to a pointer value (you indeed add to the address pointing to the string "The result of the ls -l..." and ---as the result of exec is nothing, as a new program is loaded--- you get nothing) If execv fails, then you get a pointer pointing to the previous char to that string, which is a valid expression in C, but makes your program to behave erratically in an Undefined Behaviour. Use strcpy(3), strcat(3), or snprintf(3), depending on the exact text you want to copy in the space of the buffer you allocated.
Your return an invalid address as a result. The problem here is that, if execv(2) works, it doesn't return. Only if it fails you get an invalid pointer that you cannot use (by the reason above), and of course ls -l has not been executed. Well, you don't say what you got as ouptut, so it is difficult for me to guess if you actually exec()d the program or not.
On other side, you have a popen(3) library function that allows you to execute a subprogram and allows you to read from a file descriptor its output (I recommend you not to chdir gratuitously in your program, as that is a global change in your program environment, IMHO it is better to pass ls(1) the directory you want to list as a parameter)
#include <stdio.h>
FILE *lsl() {
/* the call creates a FILE * descriptor that you can use as input and
* read the output of the ls command. It's bad resources use to try to
* read all in a string and return the string instead. Better read as
* much as you can/need and then pclose() the descriptor. */
return popen("/bin/ls -l /Users/file/path|", "rt");
}
and then you can read (as it can be very long output, you probably don't have enought buffer space to handle it all in memory if you have a huge directory)
FILE *dir = lsl();
if (dir) {
char buffer[1024];
while (fgets(buffer, sizeof buffer, dir)) {
process_line_of_lsl(buffer);
}
pclose(dir); /* you have to use pclose(3) with popen(3) */
}
If you don't want to use popen(3), then you cannot use execv(2) alone, and you have to fork(2) first, to create a new process, and exec() in the child process (after mounting the redirection yourself). Read a good introduction to fork()/exec() and how to redirect I/O between fork() and exec(), as it is far longer and detailed to put it here (again)
I'm trying to output ordered set of lines created by multiple processes.
I found that printf() and fprintf() is not suitable for such task. Right now I'm using this set of commands:
sprintf(buff,"%d: some string", (*counter)++); //build string (write can't do that)
write(file, buff, strlen(buff));
memset(buff, 0, strlen(buff)); //clear buffer before next writing
File opening and starting processes is shown below:
int file;
int main(){
pid_t busPID, riderPID;
file = open("outputFile.txt", O_WRONLY | O_CREAT | O_APPEND | O_TRUNC, 0666);
if((busPID = fork()) == 0){
bus();
else{
if((riderPID = fork()) == 0){
for(int i = 0; i < 10; i++){
rider();
}
}else{
pid_t waitPID;
while ((waitPid = wait(&status)) > 0); //wait for all riders to finish
}
}
waitpid(busPID, NULL, 0);
return 0;
}
Here are functions, which prints output:
void bus() {
char buff[50];
//writing
do {
//writing
if(*onStop > 0) {
//writing
sem_post(semRider);
sem_wait(semBus);
//writing
*onStop = 0; //internal value, irrelevant for answer
}
//writing
usleep(rand()%busSleep);
//writing
sem_post(semRider);
sem_wait(semBus);
departuredAkt += temp; //internal value, irrelevant for answer
} while(departuredAkt < departuredTotal);
//writing
exit(EXIT_SUCCESS); //exit this process
}
void rider() {
char buff[50];
//writing
int pos = ++(*onStop);
//writing
sem_wait(semRider);
//writing
sem_post(semBus);
sem_wait(semRider);
//writing
sem_post(semBus);
exit(EXIT_SUCCESS);
}
There is only 1 process using bus() function and N processes using rider()function (specified by argument). desired output is:
1: bus string
2: bus string
3: bus string
4: rider 1 string
5: rider 1 string
.
.
.
25: rider N string
26: bus string
My current output looks like this:
1: bus string
2: bus string
3: bus string
4: rider 1 string
6: bus string //here is the problem
5: rider 1 string
Question is, how can I achieve printing lines in correct order?
First of all, side note: never use sprintf, this function is completely unsecure. Use snprintf. Example:
snprintf (buff, sizeof (buff), "%d: some string", (*counter)++);
Second: you missed information we need to understand your question. I mean the following information:
How exactly you opened file?
How you started you processes?
Are this processes really processes or they are threads?
Did you open file in one process and somehow shared this opened file with other processes or did you opened the file in each process separately?
This details are critical for understanding your question.
Next time you will writing some question, please, provide FULL EXAMPLE. I. e. minimal working example we can compile and run. It should include all relevant details, i. e. starting processes, opening files, etc. And, of course, you should strip all unnecessary details.
Okey, there is two different notion in POSIX: "file descripTOR" and "file descripTION". Websearch for them. Type "man 2 open" in UNIX shell and read carefully, this manual page talks about distinction.
Exact details about how you started your processes and how you opened your file causes (or not causes) sharing of file description between processes and thus affects behavior of "write".
I wrote big text about file descriptors and descriptions. I put it here: https://zerobin.net/?eb2d99ee02f36b92#hQY7vTMCD9ekThAod+bmjlJgnlBxyDSXCYcgmjVSu2w= , because it is not much relevant to this question, but still will be useful for education.
Okey, what to do?
Well, if you for whatever reason cannot share ONE file DESCRIPTION, then simply open file with O_APPEND. :) You don't need to open the file every time you write to it. Simply open it with O_APPEND one time in each process and all will be OK.
I have a program A that takes two arguments from stdin and exits with a unique code depending on the arguments. I am writing a program B that calls program A using fork and exec and let program B print out the code program A exits with. For some reason, program A doesn't seem to be getting the data I piped through to it in the child process of fork. I'm not sure if I'm piping the correct data to the child process.
Could someone help me please? Thanks!
Here is my code:
int program_B(void) {
char var_a[256];
char var_b[256];
int fd[2];
// Read from stdin
char *sendarray[2];
sendarray[0] = var_a;
sendarray[1] = var_b;
if(fgets(var_a, MAXLINE, stdin) == NULL) {
perror("fgets");
exit(1);
}
if(fgets(var_b, MAXLINE, stdin) == NULL) {
perror("fgets");
exit(1);
}
if (pipe(fd) == -1) {
perror("pipe");
exit(1);
}
int pid = fork();
// Child process -- error seems to be here.
if (pid == 0) {
close(fd[1]);
dup2(fd[0], fileno(stdin));
close(fd[0]);
execl("program_A", NULL);
perror("exec");
exit(1);
} else {
close(fd[0]);
write(fd[1], sendarray, 2*sizeof(char*));
close (fd[1]);
int status;
if (wait(&status) != -1) {
if (WIFEXITED(status)) {
printf("%d\n", WEXITSTATUS(status));
} else {
perror("wait");
exit(1);
}
}
}
return 0;
}
You are piping the wrong data to the child process.
I am assuming var_a and var_b are the strings you want to send to program A. They are both of type array of chars, which in C is the same thing as pointer to char (Actually there is a small difference between pointers and arrays but this is irrelevant for this problem). So they are actually just pointers to the first byte of each argument. sendarray, however is an array of char-pointers which is the same thing as a pointer to char-pointer. Keep this in mind for a second.
When calling write() the 2nd parameter tells it where the data is in memory. By passing sendarray, write thinks this sendarray points the data you want to write although it actually points to yet another pointer. So what happens is that the pointer values of var_a and var_b (which is what sendarray points to), are written to the pipe.
So you have to pass var_a and var_b to write(), since those are pointers to the actual data you want to send. Also you have to know how long (how many bytes) this data is. If var_a and var_b point to null-terminated strings, you can use strlen() to determine their length.
One last thing: I don't know how exactly your program A obtains 2 arguments from a continuous byte stream like stdin, but assuming it reads it line by line, you obviously have to send a new-line character from program B, as well.
So putting it all together your write statements should look something like this:
write(fd[1], var_a, strlen(var_a));
write(fd[1], "\n", 1);
write(fd[1], var_b, strlen(var_b));
Of course, if any of the assumptions I made is wrong, you have to adopt this code appropriately.
I have a solution here that supposedly opens a file and changes the last character of it. I don't quite understand how this works. Could you please explain?
void readlast()
{
int handle = open("./file.txt", O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
if (handle < 0)
{
return;
}
Okay, this part opens the file and if it doesn't work, returns.
First question: Why is a file opening an integer (int handle)? What is being stored in it?
char c='N';
lseek(handle, -2*sizeof(c), SEEK_END);
lseek apparently changes the location of a reader. So I guess this sets the reader to the end of a file(SEEK_END). But why do we need an offset of -2*sizeof(c) if we just want to write one character?
write(handle, &c, sizeof(c));
close(handle);
}
I do understand this last part.
Thank you!
Normally a file descriptor is returned by open() and it is an integer. 0 and 1 are customarily standard I/O.
File size - 2 [octets] is the offset of last character/byte.
I'm just starting to learn C programming and I have some uncertainty about fork(), exec(), pipe(), etc.
I've developed this code, but when I execute it, the variable c remains empty, so I don't know if the child isn't writing to the pipe, or the parent isn't reading from it.
Could you help me please? This is the code:
int main() {
int pid=0;
int pipefd[2];
char* c=(char *)malloc(sizeof(char));
FILE *fp;
pipe(pipefd);
pid=fork();
if (pid==0){
close(pipefd[0]);
dup2(pipefd[1],1);
close(pipefd[1]);
execl("ls -l | cut -c28","ls -l | cut -c28", (char *) 0);
}
else{
close(pipefd[1]);
read(pipefd[0], c, 1);
char* path="/home/random";
char* txt=".txt";
char* root=malloc(strlen(path) + strlen(txt) + sizeof(char));
strcpy(root,path);
strcat(root,c);
strcat(root,txt);
close(pipefd[0]);
fp=fopen(root,"w+");
(...)
}
The problem is that the final root string its only "/home/random.txt" because there is nothing in the char c, and what I want is to open the file "/home/random(number stored in char c).txt".
execl executes a single command, and is not aware of shell concepts such as pipes. If you want to execute a shell command, you will have to execute a shell, as follows:
execl("/bin/sh","/bin/sh","-c","ls -l | cut -c28", (char*) 0);
Always check the return value of the system calls (like execve(2) and derived functions like execl(3)), and use the errno(3) to figure out what went wrong.
In your case the execl line fails.
Using strcpy/strcat seems a bit excessively complex. snprintf can turn those 3 lines into one.
snprintf( root, size_of_buf, "/home/random%s", c );
Additionally, check your error codes. As noted, execl is failing and you don't know it. fork, dup2, ...,can also fail, you want to know sooner rather than later.