Using the file class
- Introduction
- Creating a File
- Removing a File
- Opening and Closing Files
- File Properties
- Read Access
- Write Access
- Random Access
- File Locks
- Truncating a File
- Buffered File Access
- Optimizing File Access
- Temporary Files
- Links
- Fifos
- Named Pipes
- Convenience Methods
Introduction
The file class provides methods for creating and accessing files.
The filedescriptor class provides methods for generic input and output to file descriptors. The file class inherits from filedescriptor and adds methods that are specific to files. However, when using the file class, you will likely make heavy use of filedescriptor methods such as read() and write().
Creating a File
The following example demonstrates the different ways to create a file.
Note that when creating a file, the file permissions must be supplied. Rudiments supports unix-style file permissions, even on Windows. See the permissions class for more information.
#include <rudiments/file.h>
#include <rudiments/permissions.h>
#include <rudiments/stdio.h>
int main(int argc, const char **argv) {
file f;
// Create a file using the create() method, with read-write
// permissions for everyone.
if (f.create("testfile1",permissions::parsePermString("rw-rw-rw-"))) {
stdoutput.write("testfile1 created\n");
} else {
stdoutput.write("failed to create testfile1\n");
}
// Create a file using the open() method and O_CREAT flag,
// with read-write permissions for everyone.
if (f.open("testfile2",O_CREAT,
permissions::parsePermString("rw-rw-rw-"))) {
stdoutput.write("testfile2 created\n");
} else {
stdoutput.write("failed to create testfile2\n");
}
// An attempt to create a file that already exists will just open
// the file.
if (f.create("testfile1",permissions::parsePermString("rw-rw-rw-"))) {
stdoutput.write("testfile1 opened\n");
} else {
stdoutput.write("failed to open testfile1\n");
}
// If O_EXCL and O_CREAT are used together, then an attempt to create a
// file that already exists will fail.
if (f.open("testfile2",O_CREAT|O_EXCL,
permissions::parsePermString("rw-rw-rw-"))) {
stdoutput.write("testfile2 opened\n");
} else {
stdoutput.write("failed to open testfile2 "
"(this is the correct behavior)\n");
}
}
Removing a File
The file class provides a static method for removing a file.
#include <rudiments/file.h>
#include <rudiments/stdio.h>
int main(int argc, const char **argv) {
// remove testfile1
if (file::remove("testfile1")) {
stdoutput.write("testfile1 removed\n");
} else {
stdoutput.write("failed to remove testfile1\n");
}
// remove testfile2
if (file::remove("testfile2")) {
stdoutput.write("testfile2 removed\n");
} else {
stdoutput.write("failed to remove testfile2\n");
}
}
Opening a File
To open a file, call one of the open() methods.
The open() methods take the filename to open and a set of flags.
Flags may include one or more of the following flags, or'ed together.
- O_RDONLY - Open the file in read-only mode.
- O_WRONLY - Open the file in write-only mode.
- O_RDWR - Open the file in read-write mode.
- O_APPEND - Set the position to the end of the file.
- O_TRUNC - Truncate the file. Requires O_WRONLY or O_RDWR.
- O_CREAT - Creates the file if it doesn't exist. See O_EXCL.
- O_EXCL - Requires O_CREAT. Causes O_CREAT to fail if the file already exists. Without O_EXCL, O_CREAT will succeed, and just open the file, if the file already exists.
Many platforms support additional, platform-specific flags.
There are two varieties of the open() method. They both take filename and flags. The second also takes permissions. If the flags include O_CREAT then the permissions are used when creating the file. If the flags don't include O_CREAT then the permissions are ignored.
Files are closed when the instance of the file class is deleted, or when the same instance is used to open another file, but they can also be closed manually using the close() method.
The following example demonstrates various ways to open and close a file.
#include <rudiments/file.h>
#include <rudiments/permissions.h>
#include <rudiments/stdio.h>
int main(int argc, const char **argv) {
file f;
// Create a file, or open it for write if it already exists.
if (f.open("testfile1",O_WRONLY|O_CREAT,
permissions::parsePermString("rw-rw-rw-"))) {
stdoutput.write("created/opened testfile1\n");
} else {
stdoutput.write("failed to create/open testfile1\n");
}
// Attempt to create a file, and fail if it already exists.
// (the previously open file will be closed automatically)
if (f.open("testfile1",O_WRONLY|O_CREAT|O_EXCL,
permissions::parsePermString("rw-rw-rw-"))) {
stdoutput.write("created/opened testfile1\n");
} else {
stdoutput.write("failed to create/open testfile1 "
"(this is the correct behavior)\n");
}
// Open a file for read, starting at the beginning of the file.
// (the previously open file will be closed automatically)
if (f.open("testfile1",O_RDONLY)) {
stdoutput.write("opened testfile1 for read\n");
} else {
stdoutput.write("failed to open testfile1 for read\n");
}
// Open a file for write, starting at the beginning of the file.
// (the previously open file will be closed automatically)
if (f.open("testfile1",O_WRONLY)) {
stdoutput.write("opened testfile1 for write\n");
} else {
stdoutput.write("failed to open testfile1 for write\n");
}
// You can also close the file manually.
f.close();
// Open a file for read and write,
// starting at the beginning of the file.
if (f.open("testfile1",O_RDWR)) {
stdoutput.write("opened testfile1 for read/write\n");
} else {
stdoutput.write("failed to open testfile1 for read/write\n");
}
// Close the file manually.
f.close();
// Open a file for write, starting at the end of the file.
if (f.open("testfile1",O_WRONLY|O_APPEND)) {
stdoutput.write("opened testfile1 for append\n");
} else {
stdoutput.write("failed to open testfile1 for append\n");
}
// Close the file manually.
f.close();
// Open a file for write,
// first removing the current contents of the file.
if (f.open("testfile1",O_WRONLY|O_TRUNC)) {
stdoutput.write("opened testfile1 for write (truncated)\n");
} else {
stdoutput.write("failed to open testfile1 for write (truncated)\n");
}
// when main() exits, f will be deleted and the file will be closed.
}
File Properties
The file class provides methods for getting various file properties including permissions, ownership, size, type, and access and modification times.
The following example opens a file and prints out various file properties. Note that other classes must be used to convert the codes, ids and raw times to human-readable form.
Note that this program depends on the existence of testfile1, so you'll have to touch it, or run one of the programs above to create it.
#include <rudiments/file.h>
#include <rudiments/permissions.h>
#include <rudiments/userentry.h>
#include <rudiments/groupentry.h>
#include <rudiments/datetime.h>
#include <rudiments/stdio.h>
int main(int argc, const char **argv) {
// open the file
file f;
if (f.open("testfile1",O_RDONLY)) {
// print out various file properties...
// permissions
mode_t perms=f.getPermissions();
char *permstring=permissions::parsePermOctal(perms);
stdoutput.printf("Permissions: %s\n",permstring);
delete[] permstring;
// owner user/group
uid_t user=f.getOwnerUserId();
userentry ou;
ou.open(user);
stdoutput.printf("Owner User: %s\n",ou.getName());
gid_t group=f.getOwnerGroupId();
groupentry og;
og.open(group);
stdoutput.printf("Owner Group: %s\n",og.getName());
// sizes
stdoutput.printf("File Size: %lld\n",
f.getSize());
stdoutput.printf("Block Size: %lld\n",
f.getBlockSize());
stdoutput.printf("Block Count: %lld\n",
f.getBlockCount());
// file type
stdoutput.printf("Is Socket: %s\n",
(f.isSocket())?"yes":"no");
stdoutput.printf("Is Symbolic Link: %s\n",
(f.isSymbolicLink())?"yes":"no");
stdoutput.printf("Is Regular File: %s\n",
(f.isRegularFile())?"yes":"no");
stdoutput.printf("Is Block Device: %s\n",
(f.isBlockDevice())?"yes":"no");
stdoutput.printf("Is Directory: %s\n",
(f.isDirectory())?"yes":"no");
stdoutput.printf("Is Character Device: %s\n",
(f.isCharacterDevice())?"yes":"no");
stdoutput.printf("Is Fifo: %s\n",
(f.isFifo())?"yes":"no");
// access/modification times
time_t access=f.getLastAccessTime();
datetime da;
da.init(access);
stdoutput.printf("Last Access: %s\n",da.getString());
time_t mod=f.getLastModificationTime();
datetime dm;
dm.init(mod);
stdoutput.printf("Last Modification: %s\n",dm.getString());
} else {
stdoutput.write("failed to open testfile1\n");
}
}
There are 3 other methods worth mentioning as well:
- getCurrentProperties()
- By default, the file class fetches file properties when open() or create() are called. If a property is changed, then the old value will still be reflected when the method to get it is called, unless this method is called first.
- dontGetCurrentPropertiesOnOpen()
- By default, the file class fetches file properties when open() or create() are called. If this method is called, then file properties will not be fetched unless getCurrentProperties() is called manually.
- getCurrentPropertiesOnOpen()
- Returns the class to its default behavior of fetching file properties when open() or create() are called.
Read Access
The filedescriptor class (which the file class inherits from) provides read() methods to read data from files and file descriptors.
Methods are provided for reading all primitive types directly. This example opens a file in read-only mode and reads primitive data types from it.
#include <rudiments/file.h>
int main(int argc, const char **argv) {
// open the file
file f;
f.open("testfile",O_RDONLY);
// read a bool
bool b;
f.read(&b);
// read various characters
char c;
f.read(&c);
byte_t uc;
f.read(&uc);
// read various integers
uint16_t ui16;
f.read(&ui16);
uint32_t ui32;
f.read(&ui32);
uint64_t ui64;
f.read(&ui64);
int16_t i16;
f.read(&i16);
int32_t i32;
f.read(&i32);
int64_t i64;
f.read(&i64);
// read various floats
float fl;
f.read(&fl);
double db;
f.read(&db);
}
Methods are also provided for reading arbitrary data into buffers. This example approximates the unix "cat" or Windows "type" utility. It opens a file in read-only mode, reads it in chunks, and prints out each chunk.
#include <rudiments/file.h>
#include <rudiments/stdio.h>
int main(int argc, const char **argv) {
file f;
char buffer[1024];
// for each file specified on the command line...
for (int32_t i=1; i<argc; i++) {
// open the file
if (!f.open(argv[i],O_RDONLY)) {
continue;
}
// read chunks from the file and print each chunk..
ssize_t bytesread=0;
do {
// attempt to read 1024 bytes into the buffer
bytesread=f.read(buffer,1024);
// bytesread will be the number of bytes that were
// actually read, 0 at EOF, or a negative number
// if an error occurred
if (bytesread>0) {
// print the buffer
stdoutput.write(buffer,bytesread);
}
// exit if we read fewer than 1024 bytes
} while (bytesread==1024);
}
}
A read() method is also provided that reads until it encounters a specified terminator. This example reads a file, one line at a time, and prints out each line.
#include <rudiments/file.h>
#include <rudiments/stdio.h>
int main(int argc, const char **argv) {
file f;
// for each file specified on the command line...
for (int32_t i=1; i<argc; i++) {
// open the file
if (!f.open(argv[i],O_RDONLY)) {
continue;
}
// read lines from the file and print each line...
ssize_t bytesread=0;
do {
// attempt to read a line
char *line=NULL;
bytesread=f.read(&line,"\n");
// bytesread will be the number of bytes that were
// actually read, 0 at EOF, or a negative number
// if an error occurred
if (bytesread>0) {
// print the line
stdoutput.write(line);
}
// clean up
delete[] line;
// exit on eof or error
} while (bytesread>0);
}
}
Write Access
The filedescriptor class (which the file class inherits from) provides write() methods to write data to files and file descriptors.
Methods are provided for writing primitive types as well as blocks of character or binary data. This example creates a file in write-only mode and writes various types of data to it.
#include <rudiments/file.h>
#include <rudiments/permissions.h>
int main(int argc, const char **argv) {
// open/create the file
file f;
f.open("testfile",O_WRONLY|O_CREAT,
permissions::parsePermString("rw-rw-rw-"));
// write a bool
bool b=true;
f.write(b);
// write various characters
char c='a';
f.write(c);
byte_t uc='a';
f.write(uc);
// write various integers
uint16_t ui16=16;
f.write(ui16);
uint32_t ui32=32;
f.write(ui32);
uint64_t ui64=64;
f.write(ui64);
int16_t i16=-16;
f.write(i16);
int32_t i32=-32;
f.write(i32);
int64_t i64=-64;
f.write(i64);
// write various floats
float fl=1.234;
f.write(fl);
double db=1.234;
f.write(db);
// write some text
const char *text="hello there";
f.write(text);
// write the first 5 bytes of the same text
f.write(text,5);
// write some binary data
byte_t binary[]={1,2,3,4,5,6,7,8,9,0};
f.write(binary,sizeof(binary));
// write arbitary binary data
uint64_t data[]={12345,67890,12345,67890};
f.write((void *)data,sizeof(data));
}
Random Access
It is possible to open a file for both reading and writing, write to it, jump around, overwrite parts, jump around some more, and read from it.
This example illustrates random file access.
#include <rudiments/file.h>
#include <rudiments/permissions.h>
#include <rudiments/stdio.h>
int main(int argc, const char **argv) {
// open the file
file f;
f.open("testfile",O_RDWR|O_CREAT,
permissions::parsePermString("rw-rw-rw-"));
// write 4 fixed length records to the file,
// each consiting of two 10-character fields
f.write(" ");
f.write(" ");
f.write(" ");
f.write(" ");
// go to the first record
f.setPositionRelativeToBeginning(0);
// overwrite the first record
f.write("goodbye friends ");
// go to the last record
f.setPositionRelativeToEnd(-20);
f.write("hello there ");
// go to the third record
f.setPositionRelativeToBeginning(40);
f.write("bye folks ");
// go to the second record
f.setPositionRelativeToCurrent(-40);
f.write("hi guys ");
// print the records in reverse order
char record[20];
for (off64_t i=1; i<=4; i++) {
f.setPositionRelativeToEnd(-20*i);
f.read(record,20);
stdoutput.write(record,20);
stdoutput.write('\n');
}
}
File Locks
The filedescriptor class provides methods for locking files and regions of files. File locks are advisory, meaning that they only work if all processes that access the file use them.
The lockFile() and unlockFile() methods lock and unlock the entire file. The lockRegion() and unlockRegion() methods lock and unlock a specific range of bytes. The lockRemainder() and unlockRemainder() methods lock and unlock from a specific position to the end of the file.
The checkLockRegion() method can be used to determine if a region can be locked, and if not, to get information about the conflicting lock.
The following example demonstrates whole-file locking, region locking, lock conflict checking, and remainder locking.
#include <rudiments/file.h>
#include <rudiments/permissions.h>
#include <rudiments/stdio.h>
int main(int argc, const char **argv) {
// open/create a file
file f;
f.open("testfile",O_RDWR|O_CREAT,
permissions::parsePermString("rw-rw-rw-"));
f.write("hello world, this is a test file\n");
// lock the entire file for writing
if (f.lockFile(F_WRLCK)) {
stdoutput.write("locked entire file for write\n");
}
// unlock the entire file
if (f.unlockFile()) {
stdoutput.write("unlocked entire file\n\n");
}
// lock a region of the file for reading
if (f.lockRegion(F_RDLCK,0,10)) {
stdoutput.write("locked bytes 0-10 for read\n");
}
// check if the region can be write-locked
int16_t conftype;
int16_t confwhence;
off64_t confstart;
off64_t conflen;
if (!f.checkLockRegion(F_WRLCK,0,10,
&conftype,&confwhence,
&confstart,&conflen)) {
stdoutput.printf("cannot write-lock: "
"conflicting lock at %lld for %lld bytes\n",
confstart,conflen);
}
// unlock the region
if (f.unlockRegion(0,10)) {
stdoutput.write("unlocked bytes 0-10\n\n");
}
// try to lock the remainder of the file from position 5
if (f.lockRemainder(F_WRLCK,5)) {
stdoutput.write("locked remainder from byte 5\n");
}
// unlock the remainder
if (f.unlockRemainder(5)) {
stdoutput.write("unlocked remainder from byte 5\n");
}
}
Truncating a File
If you want to erase the current contents of a file, there are two options. The first is to open the file using the O_TRUNC flag. But, if you want to truncate the file after you've already opened it, you can use the truncate() method.
#include <rudiments/file.h>
#include <rudiments/permissions.h>
int main(int argc, const char **argv) {
// open/create a file
file f;
f.open("testfile",O_WRONLY|O_CREAT,
permissions::parsePermString("rw-rw-rw-"));
// write some text to it
f.write("this is some text\n");
// truncate the file
f.truncate();
// write some different text to it
f.write("this is some different text\n");
}
Buffered File Access
If your program does a lot of reads and writes, then you probably want to buffer them. There are two main reasons for this:
- Accessing storage is much slower than accessing memory.
- System calls are generally slower than library calls.
Both file systems and physical storage are typically organized in blocks. Each block is some number of bytes. A read or write of the entire block costs the same as reading or writing a single byte. If you use buffers that are the same size as the file system's block size, then reading or writing the entire buffer takes only marginally more time than reading or writing a single byte.
Also, even if the kernel or disk does some kind of buffering, each read or write still requires a system call. In effect, the program tells the kernel to do the read or write, hands control over to the kernel, waits for it to do it, and then waits for it to hand control back. The overhead of the switching involved usually makes the whole process slower than if the program read or wrote directly from a buffer under its own control.
Buffering always helps writes and usually helps reads. How much is platform-specific.
The file class makes buffered I/O simple. There are only three methods involved:
- setReadBufferSize()
- Sets the size of the buffer used when reading.
- setWriteBufferSize()
- Sets the size of the buffer used when writing.
- flushWriteBuffer()
- Makes sure that any data that is buffered but hasn't been written to storage, actually gets written to storage.
Reading is completely transparent. The read buffer is filled during the first read, and filled again when all bytes have been read from it.
Writing is nearly transparent. Bytes are written to the buffer. When the buffer is full, it is written to storage. However, when the program is done writing, it should call flushWriteBuffer() to make sure that any data still in the buffer is written to storage.
The example below illustrates buffered I/O.
#include <rudiments/file.h>
#include <rudiments/permissions.h>
#include <rudiments/datetime.h>
#include <rudiments/stdio.h>
int main(int argc, const char **argv) {
file f;
uint32_t i;
uint32_t j;
char c;
// open/create the file
f.open("testfile",O_WRONLY|O_CREAT|O_TRUNC,
permissions::parsePermString("rw-rw-rw-"));
// write 1mb of characters to the file, unbuffered
stdoutput.write("writing unbuffered...\n");
for (i=0; i<1024*1024; i++) {
f.write('a');
}
stdoutput.write("done\n");
// truncate the file
f.truncate();
// write 1mb of characters to the file, buffered
stdoutput.write("writing buffered...\n");
f.setWriteBufferSize(4096);
for (i=0; i<1024*1024; i++) {
f.write('a');
}
f.flushWriteBuffer(-1,-1);
stdoutput.write("done\n");
// read 1mb of characters from the file, unbuffered (10 times)
stdoutput.write("reading unbuffered...\n");
for (i=0; i<10; i++) {
f.setPositionRelativeToBeginning(0);
for (j=0; j<1024*1024; j++) {
f.read(&c);
}
}
stdoutput.write("done\n");
// read 1mb of characters from the file, buffered (10 times)
stdoutput.write("reading buffered...\n");
for (i=0; i<10; i++) {
f.setPositionRelativeToBeginning(0);
for (j=0; j<1024*1024; j++) {
f.read(&c);
}
}
stdoutput.write("done\n");
}
Optimizing File Access
The file class provides several methods for optimizing file access.
The setGetCurrentPropertiesOnOpen() method can be used to disable the automatic fetching of file properties when a file is opened, which can improve performance when opening many files.
Methods are also provided for advising the kernel about expected access patterns: adviseSequentialAccess(), adviseNormalAccess(), adviseWillNeed(), and adviseWontNeed(). These methods allow the kernel to optimize its caching and read-ahead behavior.
The sync() method forces buffered data to be written to storage.
#include <rudiments/file.h>
#include <rudiments/permissions.h>
#include <rudiments/stdio.h>
int main(int argc, const char **argv) {
file f;
// disable getCurrentProperties() on open for a performance
// improvement when opening many files
f.setGetCurrentPropertiesOnOpen(false);
// open a file
f.open("testfile",O_RDWR|O_CREAT,
permissions::parsePermString("rw-rw-rw-"));
f.write("hello world, this is test data for optimization testing\n");
stdoutput.write("opened file with properties-on-open disabled\n\n");
// advise the kernel that we will access the file sequentially
f.adviseSequentialAccess(0,f.getSize());
stdoutput.write("advised sequential access\n");
// advise the kernel that we will need the data soon
f.adviseWillNeed(0,f.getSize());
stdoutput.write("advised will-need\n");
// read the file
char buf[1024];
f.setPositionRelativeToBeginning(0);
ssize_t bytesread=f.read(buf,sizeof(buf)-1);
if (bytesread>0) {
buf[bytesread]='\0';
stdoutput.printf("read: %s",buf);
}
// advise the kernel that we are done with the data
f.adviseWontNeed(0,f.getSize());
stdoutput.write("advised wont-need\n\n");
// reset to normal access pattern
f.adviseNormalAccess(0,f.getSize());
stdoutput.write("advised normal access\n");
// sync data to disk
f.sync();
stdoutput.write("synced to disk\n");
}
Temporary Files
The file class provides static methods for creating temporary files.
The createTemporaryFile() method takes a template filename containing XXXXXX and replaces it with a unique string. An optional permissions argument can be used to set the permissions on the temporary file.
The following example creates temporary files with and without explicit permissions.
#include <rudiments/file.h>
#include <rudiments/permissions.h>
#include <rudiments/stdio.h>
int main(int argc, const char **argv) {
// create a temporary file
char templatename1[]="/tmp/testfileXXXXXX";
int32_t fd=file::createTemporaryFile(templatename1);
if (fd>-1) {
stdoutput.printf("created temp file: %s\n",templatename1);
// clean up
file::remove(templatename1);
} else {
stdoutput.write("failed to create temp file\n");
}
// create a temporary file with specific permissions
char templatename2[]="/tmp/testfileXXXXXX";
fd=file::createTemporaryFile(templatename2,
permissions::parsePermString("rw-------"));
if (fd>-1) {
stdoutput.printf("created temp file: %s\n",templatename2);
stdoutput.write(" permissions: owner read/write only\n");
// clean up
file::remove(templatename2);
} else {
stdoutput.write("failed to create temp file\n");
}
}
Links
The file class provides static methods for creating hard links and symbolic links.
A hard link is an additional directory entry for a file. A symbolic link is a special file that contains a path to another file.
The createHardLink() and createSymbolicLink() methods create each type of link. The resolveSymbolicLink() method returns the target of a symbolic link. The getNumberOfHardLinks() method returns the number of hard links to a file.
#include <rudiments/file.h>
#include <rudiments/permissions.h>
#include <rudiments/stdio.h>
int main(int argc, const char **argv) {
// create a file to link to
file f;
f.create("testfile",permissions::parsePermString("rw-rw-rw-"));
f.write("hello from testfile\n");
f.close();
// create a hard link
if (file::createHardLink("testfile","testfile-hardlink")) {
stdoutput.write("created hard link: testfile-hardlink\n");
} else {
stdoutput.write("failed to create hard link\n");
}
// create a symbolic link
if (file::createSymbolicLink("testfile","testfile-symlink")) {
stdoutput.write("created symbolic link: testfile-symlink\n");
} else {
stdoutput.write("failed to create symbolic link\n");
}
// resolve the symbolic link
char *target=file::resolveSymbolicLink("testfile-symlink");
if (target) {
stdoutput.printf("testfile-symlink -> %s\n\n",target);
delete[] target;
}
// check the number of hard links
f.open("testfile",O_RDONLY);
stdoutput.printf("hard link count: %d\n",
f.getNumberOfHardLinks());
f.close();
// clean up
file::remove("testfile-hardlink");
file::remove("testfile-symlink");
file::remove("testfile");
}
Fifos
A fifo (first in, first out) is a special file that acts as a pipe between processes. One process can write to the fifo and another process can read from it. Fifos persist in the filesystem and can be accessed by unrelated processes.
The file class provides a static createFifo() method for creating a fifo with specific permissions.
#include <rudiments/file.h>
#include <rudiments/permissions.h>
#include <rudiments/stdio.h>
int main(int argc, const char **argv) {
// create a fifo
if (file::createFifo("testfifo",
permissions::parsePermString("rw-rw-rw-"))) {
stdoutput.write("created testfifo\n");
} else {
stdoutput.write("failed to create testfifo\n");
}
// verify that it is a fifo
file f;
f.open("testfifo",O_RDONLY|O_NONBLOCK);
stdoutput.printf("is fifo: %s\n",(f.isFifo())?"yes":"no");
f.close();
// clean up
file::remove("testfifo");
stdoutput.write("removed testfifo\n");
}
Named Pipes
Pipes provide a one-way communication channel between a parent process and a child process.
The filedescriptor class provides a static createPipe() method that creates a pair of file descriptors - one for reading and one for writing. A typical use is to create the pipe, fork a child process, and have the child write to the pipe while the parent reads from it.
The following example creates a pipe, forks a child process, and uses the pipe to send a message from the child to the parent.
#include <rudiments/filedescriptor.h>
#include <rudiments/process.h>
#include <rudiments/stdio.h>
int main(int argc, const char **argv) {
// create a pipe
filedescriptor readfd;
filedescriptor writefd;
if (!filedescriptor::createPipe(&readfd,&writefd)) {
stdoutput.write("failed to create pipe\n");
return 1;
}
stdoutput.write("created pipe\n\n");
// fork a child process
pid_t pid=process::fork();
if (pid==0) {
// child process...
// close the read side
readfd.close();
// write a message to the parent
writefd.write("hello from the child\n");
writefd.close();
process::exit(0);
} else if (pid>0) {
// parent process...
// close the write side
writefd.close();
// read the message from the child
char buf[1024];
ssize_t bytesread=readfd.read(buf,sizeof(buf)-1);
if (bytesread>0) {
buf[bytesread]='\0';
stdoutput.printf("parent received: %s",buf);
}
readfd.close();
// wait for the child to exit
process::wait(pid);
} else {
stdoutput.write("fork failed\n");
}
}
Convenience Methods
The file class provides various static convenience methods for common operations.
Methods are provided for creating a file, checking whether a file exists, and checking whether a file is readable, writeable, or executable.
Methods are also provided for getting parts of a pathname: getDirName(), getBaseName(), and getExtension().
The getContents() method reads the entire contents of a file into a string.
The rename() method renames a file, and the remove() method removes it.
#include <rudiments/file.h>
#include <rudiments/permissions.h>
#include <rudiments/stdio.h>
int main(int argc, const char **argv) {
// create a file using the static method
file::createFile("testfile",
permissions::parsePermString("rw-rw-rw-"));
stdoutput.write("created testfile\n\n");
// check if the file exists and its access properties
stdoutput.printf("exists: %s\n",
(file::exists("testfile"))?"yes":"no");
stdoutput.printf("readable: %s\n",
(file::isReadable("testfile"))?"yes":"no");
stdoutput.printf("writeable: %s\n",
(file::isWriteable("testfile"))?"yes":"no");
stdoutput.printf("executable: %s\n\n",
(file::isExecutable("testfile"))?"yes":"no");
// get parts of a pathname
char *dir=file::getDirName("/home/user/testfile.txt");
char *base=file::getBaseName("/home/user/testfile.txt");
char *ext=file::getExtension("/home/user/testfile.txt");
stdoutput.printf("dirname: %s\n",dir);
stdoutput.printf("basename: %s\n",base);
stdoutput.printf("extension: %s\n\n",ext);
delete[] dir;
delete[] base;
delete[] ext;
// get the contents of a file using the static method
file f;
f.open("testfile",O_WRONLY);
f.write("hello there");
f.close();
char *contents=file::getContents("testfile");
stdoutput.printf("contents: \"%s\"\n\n",contents);
delete[] contents;
// rename the file
file::rename("testfile","testfile-renamed");
stdoutput.write("renamed testfile to testfile-renamed\n");
// remove the file
file::remove("testfile-renamed");
stdoutput.write("removed testfile-renamed\n");
}