Two functions, read() and write(), are provided in <unistd.h>. The write() function writes X number of bytes from a buffer, and the read() function tries to read up to X number of bytes, and stores the data in the specified buffer. Both functions require a file descriptor to work and return -1 if the action has failed.
Partial reads are quite common. The read() function should be checked for two conditions: a negative return value, if it has failed, and 0, if it has reached EOF.
#include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <cstring> #include <stdio.h> #include <stddef.h> using namespace std; const int read_buff_length = 16; int main(void) { int lineCount = 0; int writeFd, readFd, returnVal; char *path = "test.txt"; char writeBuffer[] = "You, the people have the power - the power to create machines. The power to create happiness! You, the people, have the power to make this life free and beautiful, to make this life a wonderful adventure."; char readBuffer[read_buff_length+1]; //----write to the file------------------------------------ if((writeFd = open(path, O_CREAT | O_TRUNC | O_WRONLY)) < 0){ printf("Error opening %s.\n", path); exit(EXIT_FAILURE); } else { printf("File opened!\n"); } returnVal = write(writeFd, writeBuffer, sizeof(writeBuffer) / sizeof(char)); if(returnVal < 0){ printf("Error writing to file.\n"); exit(EXIT_FAILURE); } else { printf("File written to!\n"); } if((close(writeFd))<0){ printf("Error closing %s.\n", path); exit(EXIT_FAILURE); } else { printf("%s closed after writing.\n", path); } //---------read from the file---------------------------------- if((readFd = open(path, O_RDONLY))<0){ printf("Error opening %s.\n", path); exit(EXIT_FAILURE); } else { printf("%s opened!\n", path); } while(true){ returnVal = read(readFd, readBuffer, read_buff_length); if(returnVal>0){ readBuffer[read_buff_length+1]=''; printf("Line %d: %s\n", ++lineCount, readBuffer); } else { if(returnVal==0){ printf("Read completed.\n"); break; } else { printf("Problem reading file.\n"); exit(EXIT_FAILURE); } } } if((close(writeFd))<0){ printf("Error closing %s.\n", path); exit(EXIT_FAILURE); } else { printf("%s closed after reading.\n", path); } return 0; }
The third argument sent to read() and write() is size_t, which defines the number of bytes to be read in or written to the supplied buffer. The size_t type is required by POSIX and is used to store values used in measuring sizes in bytes. The ssize_t type is a signed version of size_t, and is used as the return value type of the read() and write() functions. The maximum value for size_t is SIZE_MAX and the maximum value of ssize_t is SSIZE_MAX. The value of SSIZE_MAX is quite large for a single read or write.
#include <stdio.h> #include <limits.h> int main(void){ printf("SSIZE_MAX = %ld\n", SSIZE_MAX); return 0; }
Note that SSIZE_MAX is found in the limits.h header file.
The open() system call can take a third argument; this argument is the file’s mode, of type mode_t, which is defined as the types.h header file. We can define the mode_t value by OR-ing one or more of the symbolic constants from <sys/stat.h>.
#include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <cstring> #include <stdio.h> #include <sys/stat.h> using namespace std; int main(void) { printf("User permissions: \n"); printf("S_IRWXU = %d\n", S_IRWXU);//448 printf("S_IRUSR = %d\n", S_IRUSR);//256 printf("S_IWUSR = %d\n", S_IWUSR);//128 printf("S_IXUSR = %d\n\n", S_IXUSR);//64 printf("Group permissions: \n"); printf("S_IRWXG = %d\n", S_IRWXG);//56 printf("S_IRGRP = %d\n", S_IRGRP);//32 printf("S_IWGRP = %d\n", S_IWGRP);//16 printf("S_IXGRP = %d\n\n", S_IXGRP);//08 printf("Other permissions: \n"); printf("S_IRWXO = %d\n", S_IRWXO);//07 printf("S_IROTH = %d\n", S_IROTH);//04 printf("S_IWOTH = %d\n", S_IWOTH); //02 printf("S_IXOTH = %d\n\n", S_IXOTH); //01 return 0; }
The S_IRWXU permission allows users to read, write and execute a file; the S_IRWXG permission allows group members to read, write and execute a file, and the S_IRWXO permission allows others to read, write and execute a file.
#include <unistd.h> #include <fcntl.h> #include <sys/stat.h> #include <stdio.h> #include <stdlib.h> int main(void){ char buffer[256]; char *noWrite = "fileOne"; char *noRead = "fileTwo"; //can write, not read mode_t modeOne = S_IWUSR; //can read, not write mode_t modeTwo = S_IRUSR; int fileDescriptor; //-r-------- if((fileDescriptor = open(noWrite, O_CREAT | O_RDONLY,modeTwo))<0){ printf("Could not open file %s.\n", noWrite); exit(EXIT_FAILURE); } if(close(fileDescriptor)<0){ printf("Could not close file %s.\n", noWrite); exit(EXIT_FAILURE); } //--w------- if((fileDescriptor = open(noRead, O_CREAT | O_WRONLY, modeOne))<0){ printf("Could not open file %s.\n", noRead); exit(EXIT_FAILURE); } if(close(fileDescriptor)<0){ printf("Could not close file %s.\n", noRead); exit(EXIT_FAILURE); } //try to open the read file for writing if((fileDescriptor = open(noWrite, O_WRONLY))<0){ printf("Could not open %s for writing.\n", noWrite); } //try to open the write file for reading if((fileDescriptor = open(noRead, O_RDONLY))<0){ printf("Could not open %s for reading.\n", noRead); } return 0; }
Note that the umask defines what permissions cannot be allowed when new files are created. To allow for more permissions than a umask will allow, we must change the permissions after the file has been created.
When a file descriptor is opened in append mode, via the O_APPEND access flag, writes do not occur at the file descriptor’s current file position. Instead, writes occur at the end of the file.
#include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <stdio.h> #include <string.h> int main(void){ int i = 0; pid_t pid; ssize_t retVal; int fd; char buffer[256]; char *path = "xyz"; printf("Starting in process %lld\n", getpid()); if((fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, S_IRWXU))<0){ printf("Error creating file %s\n", path); exit(EXIT_FAILURE); } else { if(!(close(fd)<0)){ printf("File %s created.\n", path); } } if((pid=fork())<0){ printf("Could not fork process.\n"); exit(EXIT_FAILURE); } //in child process if(pid==0){ if((fd = open(path, O_WRONLY | O_APPEND))<0){ printf("Error appending to file %s in %lld\n", path, getpid()); exit(EXIT_FAILURE); } while(i++ < 5){ sprintf(buffer, "Hello from process %d \t (child %d).\n", getpid(), i); if((retVal = write(fd, buffer, strlen(buffer)))<0){ printf("Error writing to file %s in %lld\n", path, getpid()); exit(EXIT_FAILURE); } printf("Writing to file from process %lld.\n", getpid()); sleep(i); } if(close(fd)<0){ printf("Error closing file %s in %lld\n", path, getpid()); exit(EXIT_FAILURE); } } //in parent process if(pid>0){ if((fd = open(path, O_WRONLY | O_APPEND))<0){ printf("Error appending to file %s in %lld\n", path, getpid()); exit(EXIT_FAILURE); } while(i++ < 5){ sprintf(buffer, "Hello from process %d \t (parent %d).\n", getpid(), i); if((retVal = write(fd, buffer, strlen(buffer)))<0){ printf("Error writing to file %s in %lld\n", path, getpid()); exit(EXIT_FAILURE); } printf("Writing to file from process %lld\n", getpid()); sleep(i + 2); } if(close(fd)<0){ printf("Error closing file %s in %lld\n", path, getpid()); exit(EXIT_FAILURE); } } return 0; }
Alright, that’s enough mischief for today. When we next look at C in the *nix environment, we will look at lseek() as well inspecting and modifying file and directory metadata.