Lab Three (3) Open, Close, Read, Write, Pipes, dup2, dup, popen, index
open - open and possibly create a file or device
SYNOPSIS
#include
#include
#include
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
DESCRIPTION
The open() system call is used to convert a pathname into
a file descriptor (a small, non-negative integer for use
in subsequent I/O as with read, write, etc.). When the
call is successful, the file descriptor returned will be
the lowest file descriptor not currently open for the pro
cess. This call creates a new open file, not shared with
any other process. (But shared open files may arise via
the fork(2) system call.) The new file descriptor is set
to remain open across exec functions (see fcntl(2)). The
file offset is set to the beginning of the file.
flags is one of O_RDONLY, O_WRONLY or O_RDWR which request
opening the file read-only, write-only or read/write,
respectively.
read - read from a file descriptor
SYNOPSIS
#include
ssize_t read(int fd, void *buf, size_t count);
DESCRIPTION
read() attempts to read up to count bytes from file
descriptor fd into the buffer starting at buf.
If count is zero, read() returns zero and has no other
results. If count is greater than SSIZE_MAX, the result
is unspecified.
RETURN VALUE
On success, the number of bytes read is returned (zero
indicates end of file), and the file position is advanced
by this number. It is not an error if this number is
smaller than the number of bytes requested; this may hap
pen for example because fewer bytes are actually available
right now (maybe because we were close to end-of-file, or
because we are reading from a pipe, or from a terminal),
or because read() was interrupted by a signal. On error,
-1 is returned, and errno is set appropriately. In this
case it is left unspecified whether the file position (if
any) changes.
Here is an example which demonstrates the read system call.
We will open a file and attempt to read its contents into a buffer
Then we will print this buffer.
We also print the number of characters read in.
//by Nachum Danzig
#include
#include
#include
#include
#include
#include
void main()
{
int fd, number_read_in;
char buffer[256];
fd= open("./file.txt", O_RDONLY);//open file for read only
if(fd<0)//if we fail to open the file
{
printf("file not opened");
exit( 1);
}
//now we will read the contents of the file
number_read_in = read(fd,buffer,255); //size of buffer is 256
close(fd);
buffer[number_read_in]=NULL;//so the printf will work
printf("%d \n", number_read_in);
printf("%s\n", buffer);
}
Here is an example to write from the standard input
into a file.
//by Nachum Danzig
#include
#include
#include
#include
#include
#include //defines the O_WRONLY constant, et al.
void main()
{
int fd, number;
char buffer[256];
fd= open("./file.txt", O_WRONLY);//open file for write (this will
// potentially overwrite its contents)
if(fd<0)
{
printf("file not opened");
exit( 1);
}
//now we open stdin for reading
number = read(0,buffer,256); //std input is zero (0)
printf("%d \n", number);
write(fd,buffer,number);//write to our file
write(1,buffer,number);//write to stdout (i.e. screen)
close(fd);
printf("\nVia printf: %s\n\n", buffer);
}
fopen and fread example
pipe - create pipe
SYNOPSIS
#include
int pipe(int filedes[2]);
DESCRIPTION
pipe creates a pair of file descriptors, pointing to a
pipe inode, and places them in the array pointed to by
filedes. filedes[0] is for reading, filedes[1] is for
writing.
RETURN VALUE
On success, zero is returned. On error, -1 is returned,
and errno is set appropriately.
NOTE:
Always write to filedes[1] and read from fildes[0]
This example demonstrates creating a pipe and using inter process
communication known as IPC. In order to create a set of pipe file
descripters we will call the pipe function. In our example we put
a simple string into the pipe and then read it out character by
character.
IMPORTANT NOTE: When one writes to a pipe it may be open for read as well,
but when reading from the pipe, we must remember to close the write side
in BOTH processes.
#include
#include
#include
#include
void main()
{
int fd[2];//make 2 fd's
char str[]="hello friend\n";
char ch;
pipe(fd); //create pipe (pipe takes an array
// of 2 int's)
write(fd[1],str,strlen(str)); //write the string into pipe
close(fd[1]); //close the write side of the pipe
while(read(fd[0],&ch,1)) //read from the pipe 1 character
printf("%c",ch);
printf("\n");
close(fd[0]);
}
This next example creates a child process which writes via a pipe to
the father.
//by Nachum Danzig and Ari Cirota
#include
#include
#include
#include
void main()
{
int fd[2];
pid_t pid;
char ch;
pipe(fd);
switch(pid=fork())
{
case 0://child
close (fd[0]); //close read side, child only writes to pipe
write(fd[1],"hello father\n",13); //write 13 letters to pipe
close(fd[1]);
break;
default: //father
close (fd[1]); //if we don't do close the write side, the father will never
//die because he will wait around waiting to write
while(0!=read(fd[0],&ch,1)) //read one letter at a time from pipe
{
printf("%c",ch); //print letter
fflush(stdout);
}
close(fd[0]);//we're done
}
}
dup2 - duplicate an open file descriptor
SYNOPSIS
#include
int dup2(int fildes, int fildes2);
DESCRIPTION
The dup2() function causes the file descriptor fildes2 to
refer to the same file as fildes. The fildes argument is a
file descriptor referring to an open file, and fildes2 is a
non-negative integer less than the current value for the
maximum number of open file descriptors allowed the calling
process. See getrlimit(2). If fildes2 already refers to an
open file, not fildes, it is closed first. If fildes2 refers
to fildes, or if fildes is not a valid open file descriptor,
fildes2 will not be closed first.
This example demonstrates diverting standard output to a file descriptor.
stdout of the child process is diverted to fd[1], and stdin of the parent
process is changed to fd[0]. This is carried out by calling the dup2()
function:
//by Nachum Danzig and Ari Cirota
#include
#include
#include
int main()
{
int pd[2];
pid_t pid;
int status;
char str[20];
pipe(pd); //create pipe with two sides, [0] refering to one end [1] to the other
switch(fork())
{
case -1:
printf("fork error\n");
break;
case 0: //child
close(pd[0]); //child only writes to pipe
dup2(pd[1],1); //write std out to pipe p[1]
printf("Hello my father.\n"); //print to stdout will really
//go into the pipe.
close(pd[1]);
break;
default: //father
close(pd[1]); //parent proccess only reads
dup2(pd[0],0); //read std input from pipe p[0]
while(gets(str)) //get from pipe
{
printf("I am the father process.\n");
printf("%s\n",str); //print to std out (the screen this time)
}
close(pd[0]);
}
}
//another example of diverting stdout and stdin
#include
#include
int main()
{
int pd[2];
pid_t pid;
int status;
char ch;
pipe(pd);
switch(fork())
{
case -1:
printf("fork error\n");
break;
case 0://child
close(pd[0]); //child only writes to pipe
dup2(pd[1],1); //stdout -> pipe's write
printf("hello user\n");
close(pd[1]);
break;
default:
close(pd[1]); //parent proccess only reads
dup2(pd[0],0); //stdin -> pipe's read
while(read(pd[0],&ch,1))//read from pipe
{
putchar(ch);
putchar('\n');
}
close(pd[0]);
}
}
Here is a program to lose and regain stdout
#include
#include
#include
#include
#include
#include
//Nachum Danzig
//program to lose and regain stdout
int main (){
int fd = open("file1.txt", O_CREAT | O_WRONLY | O_APPEND);
if (fd == -1 ) return -1;
int a = dup2(fd,1);
printf("Hello File!!\n %s",strerror(errno)); // print to file message and success status
fflush(stdout);//make sure I finished writing
close (1); //now 1 is available
fd = open("/dev/tty", O_WRONLY );//open file for write only , open will get fd = 1
printf("%d \n ",fd); //print to screen now
printf("Hello World !!\n ");
return 0;
}
dup
dup is different from dup2, what dup does is to creates a new file descriptor that duplicates
the file descriptor (fd) given as an argument:
int dup(int fd);
The returned file descriptor is the next lowest number available. For instance,
you program has stdin (fd=0) stdout (fd=1) and stderr (fd=2).
If you write
newfd = dup(2);
Then, now newfd = 4 but has all the attributes of fd=2 (stderr).
//Same as previous example except that I use dup instead of dup2.
//Notice that I have to close stdin and stdout in order for dup to return me the low
//fd (either 1=stdout or 0 =stdin). Otherwise dup would return me 4.
#include
#include
int main()
{
int pd[2];
pid_t pid;
int status;
char ch;
pipe(pd);
switch(fork())
{
case -1:
printf("fork error\n");
break;
case 0://child
close(pd[0]); //child only writes to pipe
close(1);
dup(pd[1]); //stdout -> pipe's write
printf("hello user\n");
close(pd[1]);
break;
default:
close(pd[1]);//parent proccess only reads
close(0);
dup(pd[0]); //stdin -> pipe's read
while(read(pd[0],&ch,1))//read from pipe
{
putchar(ch);
putchar('\n');
}
close(pd[0]);
}
}
popen - Essentially popen combines several other system calls. It creates a pipe and forks
a child process to run a command. the output of that command is then returned via the pipe and
is pointed to by the pointer returned by the call to popen.
pclose - waits for poepne to finish executing.
SYNOPSIS
#include
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
DESCRIPTION
The popen() function opens a process by creating a pipe, forking, and invoking the shell. Since a pipe
is by definition unidirectional, the type argument may specify only reading or writing, not both; the
resulting stream is correspondingly read-only or write-only.
The command argument is a pointer to a null-terminated string containing a shell command line.
The return value from popen() is a normal standard I/O stream in all respects save that it must be closed
with pclose() rather than fclose(). Writing to such a stream writes to the standard input of the com-
mand; the commandbs standard output is the same as that of the process that called popen(), unless this
is altered by the command itself.
The pclose() function waits for the associated process to terminate and returns the exit status of the
command as returned by wait4().
RETURN VALUE
The popen() function returns NULL if the fork(2) or pipe(2) calls fail, or if it cannot allocate memory.
The pclose() function returns -1 if wait4() returns an error, or some other error is detected.
Example 1:
#include
#include
#define BUFSIZE 200
int main()
{
FILE *pfp;
char buf[BUFSIZE];
int c=1;
if ( ( pfp=popen ( "ls -l popen1.c temp.c" , "r" ) ) == NULL )
{
fprintf ( stderr, "The ls command failed\n" );
exit ( 1 );
}
else
{
while ( fgets ( buf , BUFSIZE , pfp ) != NULL )
{
printf ( "File %d in current directory is: %s", c++, buf );
}
pclose ( pfp );
}
return 0;
}
Output:
[root@v-yp /usr/u/jctstaff/danzig/public_html/unix_class]$ ./a.out
File 1 in current directory is: -rw-r--r-- 1 root root 442 Dec 11 11:42 popen1.c
File 2 in current directory is: -rw------- 1 danzig jctstaff 969 Oct 23 2002 temp.c
Example 2:
pfp=popen( "sed -e 's/a/A/g'" , "w" );
fprintf ( pfp , "Apples are tasty.\n" );
pclose ( pfp );
Output:
Apples Are tAsty
index, rindex - locate character in string
SYNOPSIS
#include
char *index(const char *s, int c);
char *rindex(const char *s, int c);
DESCRIPTION
The index() function returns a pointer to the first occurrence of the
character c in the string s.
The rindex() function returns a pointer to the last occurrence of the
character c in the string s.
The terminating NULL character is considered to be a part of the strings.
RETURN VALUE
The index() and rindex() functions return a pointer to the matched character
or NULL if the character is not found.
In this example we will use the index function to get a pointer
to the letter in the array we want, and then we will print the array
from that point on. Note that index is similar to substr() and strstr().
#include
#include
int main(void)
{
char str[]="ABCDEFJHIJKLMNOP";
char *ptr;
printf("The string is %s \nFrom the first 'I' to the end the string is ",str);
ptr=index(str,'I');
printf("%s\n", ptr);
return 0;
}
[root@v-yp /home/localzig]$ ./a.out
The string is ABCDEFJHIJKLMNOP
From the first 'I' to the end the string is IJKLMNOP
getcwd, get_current_dir_name, getwd - Get current working
directory
SYNOPSIS
#include
char *getcwd(char *buf, size_t size);
char *get_current_dir_name(void);
char *getwd(char *buf);
DESCRIPTION
The getcwd() function copies the absolute pathname of the
current working directory to the array pointed to by buf,
which is of length size.
If the current absolute path name would require a buffer
longer than size elements, NULL is returned, and errno is
set to ERANGE; an application should check for this error,
and allocate a larger buffer if necessary.
As an extension to the POSIX.1 standard, getcwd() allo
cates the buffer dynamically using malloc() if buf is NULL
on call. In this case, the allocated buffer has the
length size unless size is zero, when buf is allocated as
big as necessary. It is possible (and, indeed, advisable)
to free() the buffers if they have been obtained this way.
get_current_dir_name, which is only prototyped if
__USE_GNU is defined, will malloc(3) an array big enough
to hold the current directory name. If the environment
variable PWD is set, and its value is correct, then that
value will be returned.
getwd, which is only prototyped if __USE_BSD is defined,
will not malloc(3) any memory. The buf argument should be
a pointer to an array at least PATH_MAX bytes long. getwd
does only return the first PATH_MAX bytes of the actual
pathname.
RETURN VALUE
NULL on failure (for example, if the current directory is
not readable), with errno set accordingly, and buf on success.
NAME
chdir, fchdir - change working directory
SYNOPSIS
#include
int chdir(const char *path);
int fchdir(int fd);
DESCRIPTION
chdir changes the current directory to that specified in path.
fchdir is identical to chdir, only that the directory is
given as an open file descriptor.
RETURN VALUE
On success, zero is returned. On error, -1 is returned,
and errno is set appropriately.
Exercise
A.
Write a C program to create 3 child processes which read from 3
different files and write to the same pipe in the parent process.
Each child should wait a random amount of time (3 -10 seconds) between
writing each 50 characters. The father should read from the pipe and
write everything he gets (from all 3 files) into one new file.
B. Expand Lab 2 B. so that the shell you made accepts the command cd. It should
also write a prompt which is the current working directory instead of simply
writing >. If the user uses the command cd to change the current working
directory, then the prompt must be updated to reflect this.
The shell should also accept any number of pipes. For example, the following command
should be possible: ls | wc | wc
The shell that you will write will run the two commands (ls, wc) such that the
ls will be the side which writes to the pipe and wc will be the side that reads.
In order to do this exercise you must change the stdin of the side which reads
and the stdout of the side which writes such that they relate to a pipe.
This change can be accomplished by using the command dup2 (see man dup2).
The file descriptor (fd) of stdin is 0 and the fd of stdout is 1 (stderr is 2).
Note: A child process inherits the stdin and the stdout of its father.
Do not use the popen function in this exercise.
C. Write a shell as described above but use the popen function instead of pipe and dup.
© Nachum Danzig