select() does not work for multiple named pipes - c

I'm currently trying to implement a chat server/shoutbox via named pipes. The server takes the name of the pipes as a command line argument. It then proceeds with creating the pipes and opens them for reading. The other processes open the pipe for writing and ask the user for input (client). Once the user hits enter, the message is written to the pipe. When the user hits enter without entering any character the client closes the pipe and exits. The server does not need to support reconnects and the clients need to connect in the order they have been specified on the command line. This works fine for exactly one client, but not for a second or third one. My code to implement this behaviour is the following:
Side note: the hashtag plus client name serves to identify the client. It is sent in the beginning to register a client on the server, so that it knows to which client it is talking. In the file fifo.h respectively fifo.c there are my custom implementations of create_fifo, open_fifo and close_fifo which handle the creation, opening and closing of the fifos.
Edit: I'm a step closer now to what I have to achieve. I had to add a few lines to the code which I will mark in the code below by adding a comment. The only problem that persists is that the server does not recognize when the second user leaves the server first and then the first one does. So it leaves the pipes open and does not return to the clean_up function.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/fcntl.h>
#include <string.h>
#include "fifo.h"
void clean_up(int* fifos, char** argv, int n);
int main(int argc, char** argv){
int clients = argc - 1;
int fifos[clients];
char reg[clients][PIPE_BUF];
fd_set active_clients;
FD_ZERO(&active_clients);
for (int i = 0; i < clients; i++){
create_fifo(argv[i + 1]);
fifos[i] = open_fifo(argv[i + 1], O_RDONLY);
FD_SET(fifos[i], &active_clients);
}
fd_set tmpset;
memcpy(&tmpset, &active_clients, sizeof(fd_set)); // <-- I added this line
setbuf(stdout, NULL);
while (clients > 0){
if (select(fifos[clients - 1] + 1, &active_clients, NULL, NULL, NULL) < 0){
fprintf(stderr, "Failed to select fifos\n");
clean_up(fifos, argv, argc);
return EXIT_FAILURE;
}
for (int i = 0; i < clients; i++){
if (FD_ISSET(fifos[i], &active_clients)){
char buf[PIPE_BUF];
read(fifos[i], buf, sizeof(buf));
if (buf[0] == '#'){
strncpy(reg[i], buf + 1, PIPE_BUF);
printf("%s joined the server\n", reg[i]);
continue;
}
if (buf[0] == '\n'){
clients--;
FD_CLR(fifos[i], &tmpset);
FD_CLR(fifos[i], &active_clients);
printf("%s left the server\n", reg[i]);
continue;
}
printf("%s: %s", reg[i], buf);
}
}
memcpy(&active_clients, &tmpset, sizeof(fd_set)); // <-- I added this line
}
clean_up(fifos, argv, argc);
return EXIT_SUCCESS;
}
void clean_up(int* fifos, char** argv, int n){
for (int i = 1; i < n; i++){
close(fifos[i - 1]);
close_fifo(argv[i]);
}
}
And the client side is implemented like this:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include "fifo.h"
int main(int argc, char** argv){
int fp = open_fifo(argv[argc - 1], O_WRONLY);
char id[BUF_SIZE] = "";
char* client_name = argv[argc - 1];
strncat(id, "#", 1);
strncat(id, client_name, strlen(client_name));
write(fp, id, strlen(id) + 1);
while (1){
char buf[PIPE_BUF];
printf("Message: ");
fgets(buf, PIPE_BUF, stdin);
write(fp, buf, strlen(buf) + 1);
if (buf[0] == '\n'){
break;
}
}
close(fp);
return EXIT_SUCCESS;
}
As far as I figured it out, it has something to do with the select command. It blocks until one of the fifos has some input ready, but only for the first client that sends a message. Unfortunately it does not unblock for the other clients. Additionally, I don't know whether my argument passed for nfds on the select command is the right one. I did also try to put in FD_SETSIZE but that didn't change much. Does anybody know what I am doing wrong here?

Related

How can I fork&exec bash shell in C?

Trying to create a new bash shell in C and bring it to the user, this is my code:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
char* secretpass = "password";
char password[50];
printf("%s", "Password: ");
fgets(password, 50, stdin);
password[strcspn(password, "\n")] = 0;
if (!strcmp(password, secretpass)){
pid_t pid = fork();
if (pid == 0){
execl("/bin/bash", "bash", NULL);
}
}
return 0;
}
After running the code (ELF), i get a new bash shell in ps but it's not my shell because echo $$ brings the first shell, what can I do to get the new shell to screen? kernel module will help?
EDIT:
edited my code for more help, /dev/chardev is a char device that come up with the boot process, the driver is also 0666 (.rw.rw.rw.) writable for everyone, the system(cmd) says at there is no permission at console, even if I do the command myself after execve.
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <pwd.h>
#define MAX 50
#define USERNAME 2
int main(int argc, char const *argv[])
{
// Declare variables.
const char* username = argv[USERNAME];
char* password = (char*)calloc(MAX, sizeof(char));
char* cmd = (char*)calloc(5 * MAX, sizeof(char));
char* secretpass = "password";
printf("%s", "Password: ");
fgets(password, MAX, stdin);
password[strcspn(password, "\n")] = 0;
if (!strcmp(password, secretpass)){
int err;
struct passwd* pw_user = getpwnam(username);
//printf("-%s-%s-%d-%d-%s-%s-%s-\n", pw_user->pw_name, pw_user->pw_passwd,
//pw_user->pw_uid, pw_user->pw_gid, pw_user->pw_gecos,
//pw_user->pw_dir, pw_user->pw_shell);
if ( (err = fchown(0, pw_user->pw_uid, pw_user->pw_gid) ) != 0)
printf("%s %d\n", "fchown error", err);
if ( (err = setpgid(0, 0) ) != 0)
printf("%s %d\n", "setpgid error", err);
if ( (err = tcsetpgrp(0, getpid()) ) != 0)
printf("%s %d\n", "tcsetpgrp error", err);
if ( (err = chdir(pw_user->pw_dir) ) != 0)
printf("%s %d\n", "chdir error", err);
if ( (err = setgid(pw_user->pw_gid) ) != 0)
printf("%s %d\n", "setgid error", err);
if ( (err = setuid(pw_user->pw_uid) ) != 0)
printf("%s %d\n", "setuid error", err);
sprintf(cmd, "%s \"%d %d %d\" %s", "echo", pw_user->pw_uid, pw_user->pw_gid, getpid(), "> /dev/chardev");
system(cmd);
const char *args[] = {"bash", "--rcfile", "/etc/bashrc", NULL};
char LOGNAME[MAX];
char HOME[MAX];
char USER[MAX];
sprintf(LOGNAME, "%s%s", "LOGNAME=", pw_user->pw_name);
sprintf(HOME, "%s%s", "HOME=",pw_user->pw_dir);
sprintf(USER, "%s%s", "USER=", pw_user->pw_name);
const char *env[] = {"SHELL=/bin/bash", LOGNAME, HOME, USER, "IFS= ","PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin", "TTY=tty1", NULL}; /* need to generate these; TTY is passed to you */
execve("/bin/bash", args, env);
}
else
execl("/bin/login", "login", NULL);
return 0;
}
always setpgid error and if username isn't root there are also setuid and chdir errors.
From the comments: you're trying to write a login program.
Ok. That's a bit more, and you're going about this all the wrong way. We don't want to fork at all. Let init worry about waiting. Anyway, we get to write a long sequence here:
int targetuid = ... ; /* You need a strategy for getting this */
int targetgid = ... ; /* You need a strategy for getting this */
const char *homdir = ... ; /* You need a strategy for getting this */
if (!strcmp(password, secretpass)){
/* Start up the user's shell */
fchown(0, targetuid, targetgid);
setpgid(0, 0);
tcsetpgrp(0, getpid());
chdir(homedir);
setgid(targetgid);
setuid(targetuid);
const char *args[] = {"-bash", NULL};
const char *env[] = {"SHELL=/bin/bash", "LOGNAME=...", "HOME=...", "USER=...", IFS="...", PATH=/bin:/usr/bin", "TERM=...", NULL }; /* need to generate these; TERM is passed to you */
execve("/bin/bash", args, env);
}
This is very much involved and I actually don't recommend this unless you really have to. I learned a ton when I tried this but it took forever to get it working right.
Particular subpoints: 1) The tty device needs to be owned by the user after a successful login. Thus the fchown(0, ...) call to give ownership to the user. 2) The chdir() first is traditional; you could reverse the order if you wanted to but I don't see why. 3) Starting the shell with a leading - in argv0 tells the shell that it's a login shell. Check in ps -f and you can see this.
I picked up your new code; it actually looks pretty good. The only mistake I can spot is my own; the variable is TERM not TTY (now corrected in my sample above) and the best place to get its value is getenv(). On running your code I only had to make only one correction; that is putting the -bash back. The only error it spits out is the one about chardev; what is chardev?
I guess your failures aren't in this code at all but rather in your kernel.
Info from chat: OP has a custom kernel with a custom /dev/chardev; I can't explain the failures as the code works for me. There may or may not be other changes to the kernel.

popen: intercepting user's input

I have a code which runs bc thru popen(). I can intercept the calculator's output and prepend it with "Output=" text. But how can I intercept what user's is writing to bc?
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *in;
char buff[512];
if(!(in = popen("bc", "r"))){
exit(1);
}
while(fgets(buff, sizeof(buff), in)!=NULL){
printf("Output = %s", buff);
}
pclose(in);
return 0;
}
You can combine bc and echo with a pipe: echo '12*4' | bc
Example typing 12*4:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(void) {
FILE *in;
char buff[512];
char cmd[512];
while (fgets(buff, sizeof(buff), stdin)!=NULL){
strcpy(cmd, "echo '");
strcat(cmd, buff);
strcat(cmd, "' | bc");
if(!(in = popen(cmd, "r"))){
exit(1);
}
fgets(buff, sizeof(buff), in);
printf("output:%s", buff);
}
pclose(in);
return 0;
}
Output:
david#debian:~$ ./demo
12*4
output:48
You need to use pipe() and fork/exec(). However, manual piping is quite complex:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
int write_pipe[2], read_pipe[2];
pipe(read_pipe); pipe(write_pipe);
#define PARENT_READ read_pipe[0]
#define CHILD_WRITE read_pipe[1]
#define CHILD_READ write_pipe[0]
#define PARENT_WRITE write_pipe[1]
int child = fork();
if (child == 0) { /* in child */
close(PARENT_WRITE);
close(PARENT_READ);
dup2(CHILD_READ, 0); close(CHILD_READ);
dup2(CHILD_WRITE, 1); close(CHILD_WRITE);
execl("/usr/bin/bc", "/usr/bin/bc");
} else { /* in parent */
close(CHILD_READ);
close(CHILD_WRITE);
write(PARENT_WRITE, "2+3\n", 4);
char buff[512];
int output_len=read(PARENT_READ, buff, sizeof(buff));
write(1, buff, output_len);
close(PARENT_READ);
}
return 0;
}
What you're looking to do is to start a subprocess, then simultaneously:
When activity occurs on standard input, execute some function on that input before passing it to the subprocess.
When activity occurs on the subprocess output, execute some function on that output before passing it to standard output.
The system call that allows you to wait for activity on two handles is called poll, but before we do that, we need to create the handles and start the subprocess:
int a[2], b[2];
if(pipe(a)==-1)abort(); // for communicating with subprocess input
if(pipe(b)==-1)abort(); // for communicating with subprocess output
switch(fork()) {
case -1: abort();
case 0: dup2(a[0],0), dup2(b[1],1), execlp("/usr/bin/bc", "bc", 0); exit(1);
};
Note how pipe works: Data written to fildes[1] appears on (i.e., can be read from) fildes[0]. This means we want to read from the standard output of our subprocess, b[0] and write to the standard input of our subprocess a[1].
Before we do that, we can use the poll instruction to wait for activity on either standard input (fd #0), or the subprocess output (b[0]):
for(;;) {
struct pollfd p[2]={0};
p[0].fd = 0; p[1].fd = b[0];
p[0].events = p[1].events = POLLIN;
while (poll(p,2,-1) <= 0);
At this point, there is activity on at least one of these file descriptors. You can see which one by examining the .revents member.
if(p[0].revents & POLLIN) {
r = read(0, buffer, sizeof(buffer));
write(a[1], buffer, r); // check for errors, or perhaps modify buffer
}
if(p[1].revents & POLLIN) {
r = read(b[0], buffer, sizeof(buffer));
write(1, buffer, r); // check for errors, or perhaps modify buffer
}
Note especially we use the opposite member a[1] and b[0] from the member we dup2'd onto the subprocesses standard input (0) and standard output (1).
At this point you can loop back up to poll again:
}
Disconnects (like EOF, program crash, etc) will be presented as read() returning 0, so watch carefully for this case, and break; out of the loop if so desired.

Why is stat() returning EFAULT?

I'm writing a program that when run from two separate bash sessions as two separate processes, opens a named pipe between the two to allow strings to be sent from one to the other.
When the process is first executed from one terminal, it checks stat(fname, buf) == -1 to see if a file at path fname exists and if not, creates it. The process then assumes that since it was the one to make the FIFO, it is the one that will be sending messages through it and continues accordingly.
After that occurs, the program can then be run from another terminal that should determine that it will be the receiver of messages through the pipe by checking stat(fname, buf) == -1. The condition should return false now, and stat(fname, buf) itself should return 0 because there exists a file at fname now.
But for reasons I am unable to discern, when the second process is run, stat(fname, buf) still returns -1. The variable errno is set to EFAULT. The man page for stat() only decribes EFAULT as "Bad address." Any help determining why the error occurs or what is meant by "Bad address." would be greaty appreciated.
I've verified that the file is indeed created by the first process as intended. The first process waits at the line pipe = open(fname, O_WRONLY); because it can't continue until the other end of pipe is opened.
Edit: The following is a self-contained implementation of my code. I have confirmed that it compiles and experiences the problem I described here.
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#define MAX_LINE 80
#define oops(m,x) { perror(m); exit(x); }
int main(int argc, char const *argv[]) {
char line[MAX_LINE];
int pipe, pitcher, catcher, initPitcher, quit;
struct stat* buf;
char* fname = "/tmp/absFIFOO";
initPitcher = catcher = pitcher = quit = 0;
while (!quit) {
if (((!pitcher && !catcher && stat(fname, buf) == -1) || pitcher) && !quit) {
// Then file does not exist
if (errno == ENOENT) {
// printf("We're in the file does not exist part\n");
if (!pitcher && !catcher) {
// Then this must be the first time we're running the program. This process will take care of the unlink().
initPitcher = 1;
int stat;
if (stat = mkfifo(fname, 0600) < 0)
oops("Cannot make FIFO", stat);
}
pitcher = 1;
// open a named pipe
pipe = open(fname, O_WRONLY);
printf("Enter line: ");
fgets(line, MAX_LINE, stdin);
if (!strcmp(line, "quit\n")) {
quit = 1;
}
// actually write out the data and close the pipe
write(pipe, line, strlen(line));
close(pipe);
}
} else if (((!pitcher && !catcher) || catcher) && !quit) {
// The first condition is just a check to see if this is the first time we've run the program. We could check if stat(...) == 0, but that would be unnecessary
catcher = 1;
pipe = open("/tmp/absFIFO", O_RDONLY);
// set the mode to blocking (note '~')
int flags;
flags &= ~O_NONBLOCK;
fcntl(pipe, F_SETFL, flags); //what does this do?
// read the data from the pipe
read(pipe, line, MAX_LINE);
if (!strcmp(line, "quit\n")) {
quit = 1;
}
printf("Received line: %s\n", line);
// close the pipe
close(pipe);
}
}
if (initPitcher)
unlink(fname);
return 0;
}
You have this piece of code:
struct stat* buf;
...
if (((!pitcher && !catcher && stat(fname, buf) == -1)
When you call stat(), buf isn't initalized and there's no telling what it points to.
You must allocate some storage for it, so stat() has a valid place to store the result.
The easiest thing is to just allocate it on the stack:
struct stat buf;
...
if (((!pitcher && !catcher && stat(fname, &buf) == -1)
You have not shown your code, but EFAULT means 'bad address'. This indicates that you have not properly allocated (or passed) your buffer for stat or the filename (fname).
buf isn't initialised anywhere. What exactly do you expect to happen?

beginner in C under linux. write function doesn't work properly

I'm trying to write a C program, that make user able to write stuff in a file. My Problem is that after making and running the program the file stay empty ?? any idea how can I solve this.
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
// the user should give a file to write the file
int main (int argc , char**argv)
{
int fd; // file descriptor
char ret; // the character
int offset;
if(argc != 2) {
printf("You have to give the name or the path of the file to work with \n");
printf("Exiting the program \n")
return -1;
}
fd = open (argv[1], O_WRONLY/*write*/|O_CREAT/*create if not found */, S_IRUSR|S_IWUSR/*user can read and write*/);
if (fd == -1) {
printf("can'T open the file ");
return -1;
}
printf("At wich position you want to start ");
scanf("%d",&offset);
lseek(fd,offset,SEEK_SET);
while(1) {
ret = getchar();
if(ret == '1') {
printf("closing the file");
close (fd);
return 1;
}
else
write (fd,red, sizeof(char));
}
return 0;
}
thanks in advance for you help.
I have made some changes,this should work:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
int main (int argc , char**argv)
{
int fd; // file descriptor
char ret; // the character
int offset;
if(argc != 2){
printf("You have to give the name or the path of the file to work with \n");
printf("Exiting the program \n"); **//There was ';' missing here**
return -1;
}
fd = open (argv[1], O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR);
if (fd == -1) {
printf("can'T open the file ");
return -1;
}
printf("At wich position you want to start ");
scanf("%d",&offset);
lseek(fd,offset,SEEK_SET);
while(1){
ret = getchar();
if(ret == '1'){
printf("closing the file");
close (fd);
return 1;
}
else
write (fd,&ret, sizeof(char)); **//red has been changed to &ret**
}
return 0;
}
One error I can notice, the call of write function:
write (fd,red, sizeof(char));
should be:
write (fd, &red, sizeof(char));
You forgot & before red, write need address.
syntax of write: int write( int handle, void *buffer, int nbyte );
This will cause an undefined behavior in your code at run time
Edit: in write function you are using red that is not defined, I think it should be ret variable in your code. correct it as write (fd, &ret, sizeof(char));
second, you forgot ; after printf("Exiting the program \n") in if, but I also think its mistake while posting question as you says you are getting run time error.
side note: If you are using gcc compiler then you can use gcc -Wall -pedantic to generate warnings
It should be:
write (fd,&ret, sizeof(char));
write takes the pointer to the memory position, and since ret is a single char, you need to pass a pointer to it.

changing a program in c, so it takes an optional command line argument *infile*

Now I do have a hw question for everyone...I've been staring at this for a couple of days kind of tinkering and playing around but even with that I end up with a load of errors...
What I'm trying to do is take the program below and change it so that it takes an optional command line argument infile. If infile is given, then copy infile to standard output, otherwise copy standard input to standard output as before.
The trick about this is that the solution must use the original copy loop (lines 9-11) for both cases. One can only insert code, and not change any of the existing code. Any help would be great. Thanks.
/* $begin cpfile */
include "csapp.h"
int main(int argc, char **argv)
{
int n;
rio_t rio;
char buf[MAXLINE];
Rio_readinitb(&rio, STDIN_FILENO); //line 9
while((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0) //line 10
Rio_writen(STDOUT_FILENO, buf, n); //line 11
/* $end cpfile */
exit(0);
/* $begin cpfile */
}
/* $end cpfile */
C programs get command line arguments through the two arguments to main(), traditionally called argc and argv (for argument count and argument vector, respectively).
Arguments are not "named" anything, they're just strings.
A solution for you could look like this:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fileno;
/* ... your definitions should remain here, too */
if(argc > 1)
{
/* Assume first argument is filename, and open it. */
fileno = open(argv[1], O_RDONLY);
if(fileno < 0)
{
printf("Unable to open file, aborting\n");
return 1;
}
}
else
fileno = STDIN_FILENO;
/* ... your code goes here ... */
}
Then you'd of course need to change the call to Rio_readinitb() to use the fileno variable for the file descriptor.
If you literally can't change that line, for whatever reason ... I guess you can use the preprocessor to make the symbol evaluate to the new variable name:
#undef STDIN_FILENO
#define STDIN_FILENO fileno
This is of course not exactly pretty, but should work.
Make sure you put those preprocessor macros after the fileno = STDIN_FILENO; line.
You can insert dup2 before the lines 9 - 11 and it seems that you will not need change code on the lines 9 - 11. This is an example.
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int file_handle;
int dup2_res;
if (argc == 2) {
file_handle = open(argv[1], O_RDONLY);
dup2_res = dup2 (file_handle, STDIN_FILENO);
}
char buffer[100];
ssize_t read_bytes = 1;
while (read_bytes)
{
read_bytes = read(STDIN_FILENO, &buffer, sizeof(buffer) );
buffer[read_bytes] = 0;
printf("%s", buffer);
}
close(file_handle);
return 0;
}
If STDIN_FILENO cannot be reassigned, it sounds like a task for freopen():
freopen(argv[1], "r", stdin);

Resources