Temas de Instrumentación Electrónica
CURSO 2002
The following tutorial describes various common methods for reading and
writing files and directories on a Unix system. Part of the information
is common C knowledge, and is repeated here for completeness. Other information
is Unix-specific, although DOS programmers will find some of it similar to
what they saw in various DOS compilers. If you are a proficient C programmer,
and know everything about the standard I/O functions, its buffering operations,
and know functions such as fseek()
or fread()
, you
may skip the standard C library I/O functions section. If in doubt, at least
skim through this section, to catch up on things you might not be familiar
with, and at least look at the
standard C library examples.
In the Unix system, all files and directories reside under a single top directory, called root directory, and denoted as "/". Even if the computer has several hard disks attached, they are all combined in a single directories tree. It is up to the system administrator to place all disks on this tree. Each disk is being connected to some directory in the file system. This connection operation is called "mount", and is usually done automatically when the system starts running.
Each directory may contain files, as well as other directories. In addition, each directory also contains two special entries, the entries "." and ".." (i.e. "dot" and "dot dot", respectively). The "." entry refers to the same directory it is placed in. The ".." entry refers to the directory containing it. The sole exception is the root directory, in which the ".." entry still refers to the root directory (after all, the root directory is not contained in any other directory).
A directory is actually a file that has a special attribute (denoting it as being a directory), that contains a list of file names, and "pointers" to these files on the disk.
Besides normal files and directories, a Unix file system may contain various types of special files:
The basic method of reading files and writing into files is by using the standard C library's input and output functions. This works portably across all operating systems, and also gives us some efficiency enhancements - the standard library buffers read and write operations, making file operations faster then if done directly by using system calls to read and write files.
FILE
Structure
The FILE
structure is the basic data type used when handling
files with the standard C library. When we open a file, we get a pointer to
such a structure, that we later use with all other operations on the file,
until we close it. This structure contains information such as the location
in the file from which we will read next (or to which we will write next),
the read buffer, the write buffer, and so on. Sometimes this structure is
also referred to as a "file stream", or just "stream".
In order to work with a file, we must open it first, using the
fopen()
function. We specify the path to the file (full path,
or relative to the current working directory), as well as the mode for
opening the file (open for reading, for writing, for reading and writing,
for appending only, etc.). Here are a few examples of how to use it:
/* FILE structure pointers, for the return value of fopen() */
FILE* f_read;
FILE* f_write;
FILE* f_readwrite;
FILE* f_append;
/* Open the file /home/choo/data.txt for reading */
f_read = fopen("/home/choo/data.txt", "r");
if (!f_read) { /* open operation failed. */
perror("Failed opening file '/home/choo/data.txt' for reading:");
exit(1);
}
/* Open the file logfile in the current directory for writing. */
/* if the file does not exist, it is being created. */
/* if the file already exists, its contents is erased. */
f_write = fopen("logfile", "w");
/* Open the file /usr/local/lib/db/users for both reading and writing */
/* Any data written to the file is written at the beginning of the file, */
/* over-writing the existing data. */
f_readwrite = fopen("/usr/local/lib/db/users", "r+");
/* Open the file /var/adm/messages for appending. */
/* Any data written to the file is appended to its end. */
f_append = fopen("/var/adm/messages", "a");
fopen()
function. The fopen()
function returns a pointer to a
FILE
structure on success, or a NULL pointer in case of failure.
The exact reason for the failure may be anything from "file does not exist"
(in read mode), "permission denied" (if we don't have permission to access
the file or its directory), I/O error (in case of a disk failure), etc. In
such a case, the global variable "errno" is being set to the proper error
code, and the perror()
function may be used to print out
a text string related to the exact error code.
Once we are done working with the file, we need to close it. This has two effects:
fclose()
function, as follows:
if (!fclose(f_readwrite)) {
perror("Failed closing file '/usr/local/lib/db/users':");
exit(1);
}
fclose()
returns 0 on success, or EOF
(usually '-1')
on failure. It will then set "errno" to zero. One may wonder how could closing
a file fail - this may happen if any buffered writes were not saved to disk,
and are being saved during the close operation. Whether the function succeeded
or not, the FILE
structure may not be used any more by the
program.
Once we have a pointer for an open file's structure, we may read from it
using any of several functions. In the following code, assume f_read
and f_readwrite pointers to FILE
structures returned by previous
calls to fopen()
.
/* variables used by the various read operations. */
int c;
char buf[201];
/* read a single character from the file. */
/* variable c will contain its ASCII code, or the value EOF, */
/* if we encountered the end of the file's data. */
c = fgetc(f_read);
/* read one line from the file. A line is all characters up to a new-line */
/* character, or up to the end of the file. At most 200 characters will be */
/* read in (i.e. one less then the number we supply to the function call). */
/* The string read in will be terminated by a null character, so that is */
/* why the buffer was made 201 characters long, not 200. If a new line */
/* character is read in, it is placed in the buffer, not removed. */
/* note that 'stdin' is a FILE
structure pre-allocated by the */
/* C library, and refers to the standard input of the process (normally */
/* input from the keyboard). */
fgets(buf, 201, stdin);
/* place the given character back into the given file stream. The next */
/* operation on this file will return this character. Mostly used by */
/* parsers that analyze a given text, and try to guess what the next */
/* is. If they miss their guess, it is easier to push the last character */
/* back to the file stream, then to make book-keeping operations. */
ungetc(c, stdin);
/* check if the read/write head has reached past the end of the file. */
if (feof(f_read)) {
printf("End of file reached\n");
}
/* read one block of 120 characters from the file stream, into 'buf'. */
/* (the third parameter to fread() is the number of blocks to read). */
char buf[120];
if (fread(buf, 120, 1, f_read) != 1) {
perror("fread");
}
getc()
for
example), but you'll be able to learn them from the on-line manual.
Note that when we read in some text, the C library actually reads it from disk
in full blocks (with a size of 512 characters, or something else, as optimal
for the operating system we work with). For example, if we read 20
consecutive characters using fgetc()
20 times, only one disk
operation is made. The rest of the read operations are made from the buffer
kept in the FILE
structure.
Just like the read operations, we have write operations as well. They are
performed at the current location of the read/write pointer kept in the
FILE
structure, and are also done in a buffered mode - only
if we fill in a full block, the C library's write functions actually write
the data to disk. Yet, we can force it to write data at a given time (e.g.
if we print to the screen and want partially written lines to appear
immediately). In the following example, assume that f_readwrite is a pointer
to a FILE
structure returned from a previous call to
fopen()
.
/* variables used by the various write operations. */
int c;
char buf[201];
/* write the character 'a' to the given file. */
c = 'a';
fputc(c, f_readwrite);
/* write the string "hello world" to the given file. */
strcpy(buf, "hello world");
fputs(buf, f_readwrite);
/* write the string "hi there, mate" to the standard input (screen) */
/* a new-line in placed in the string, to make the cursor move */
/* to the next line on screen after writing the string. */
fprintf(stdout, "hi there, mate\n");
/* write out any buffered writes to the given file stream. */
fflush(stdout);
/* write twice the string "hello, great world. we feel fine!\n" to 'f_readwrite'. */
/* (the third parameter to fwrite() is the number of blocks to write). */
char buf[100];
strcpy(buf, "hello, great world. we feel fine!\n");
if (fwrite(buf, strlen(buf), 2, f_readwrite) != 2) {
perror("fwrite");
}
Note that when the output is to the screen, the buffering is done in line mode, i.e. whenever we write a new-line character, the output is being flushed automatically. This is not the case when our output is to a file, or when the standard output is being redirected to a file. In such cases the buffering is done for larger chunks of data, and is said to be in "block-buffered mode".
Until now we have seen how input and output is done in a serial mode. However, in various occasions we want to be able to move inside the file, and write to different locations, or read from different locations, without having to scan the whole code. This is common in database files, when we have some index telling us the location of each record of data in the file. Traveling in a file stream in such a manner is also called "random access".
The fseek()
function allows us to move the read/write pointer
of a file stream to a desired location, stated as the number of bytes
from the beginning of the file (or from the end of file, or from the current
position of the read/write pointer). The ftell()
function tells
us the current location of the read/write header of the given file stream.
Here is how to use these functions:
/* move the read/write pointer of the file stream to position '30' */
/* in the file. Note that the first position in the file is '0', */
/* not '1'. */
fseek(f_read, 29L, SEEK_START);
/* move the read/write pointer of the file stream 25 characters */
/* forward from its given location. */
fseek(f_read, 25L, SEEK_SET);
/* remember the current read/write pointer's position, move it */
/* to location '520' in the file, write the string "hello world", */
/* and move the pointer back to the previous location. */
long old_position = ftell(f_readwrite);
if (old_position < 0) {
perror("ftell");
exit(0);
}
if (fseek(f_readwrite, 520L, SEEK_SET) < 0) {
perror("fseek(f_readwrite, 520L, SEEK_SET)");
exit(0);
}
fputs("hello world", f_readwrite);
if (fseek(f_readwrite, old_position, SEEK_SET) < 0) {
perror("fseek(f_readwrite, old_position, SEEK_SET)");
exit(0);
}
fseek()
, any
character put to the stream using ungetc()
is lost and
forgotten.
Note: it is ok to seek past the end of a file. If we will try to read from there, we will get an error, but if we try to write there, the file's size will be automatically enlarged to contain the new data we wrote. All characters between the previous end of file and the newly written data will contain nulls ('\0') when read. Note that the size of the file has grown, but the file itself does not occupy so much space on disk - the system knows to leave "holes" in the file. However, if we try to copy the file to a new location using the Unix "cp" command, the new file will have all wholes filled in, and will occupy much more disk space then the original file.
Two examples are given for the usage of the standard C library I/O functions. The first example is a file copying program, that reads a given file one line at a time, and writes these lines to a second file. The source code is found in the file stdc-file-copy.c. Note that this program does not check if a file with the name of the target already exists, and thus viciously erases any existing file. Be careful when running it! Later, when discussing the system calls interface, we will see how to avoid this danger.
The second example manages a small database file with fixed-length records
(i.e. all records have the same size), using the fseek()
function. The source is found in the file
stdc-small-db.c. Functions are supplied for
reading a record and for writing a record, based on an index number. See
the source code for more info. This program uses the fread()
and fwrite()
functions to read data from the file, or write
data to the file. Check the on-line manual page for these functions to
see exactly what they do.
Usually, reading and writing files is done best using the standard C library functions. However, in various occasions we need a more low-level to the files. For example, we cannot check file permissions or file size using the standard C library. Also, you will see that Unix treats various devices in a similar manner to using files, and using the same functions you can read from a file, from a network connection and so on. Thus, it is useful to learn this generic interface.
The basic system object used to manipulate files is called a file
descriptor. This is an integer number that is used by the various I/O
system calls to access a memory area containing data about the open file.
This memory area has a similar role to the FILE
structure in the
standard C library I/O functions, and thus the pointer returned from
fopen()
has a role similar to a file descriptor.
Each process has its own file descriptors table, with each entry pointing to a an entry in a system file descriptor table. This allows several processes to share file descriptors, by having a table entry pointing to the same entry in the system file descriptors table. You will encounter this phenomena, and how it can be used, when learning about multi-process programming.
The value of the file descriptor is a non-negative integer. Usually, three file descriptors are automatically opened by the shell that started the process. File descriptor '0' is used for the standard input of the process. File descriptor '1' is used for the standard output of the process, and file descriptor '2' is used for the standard error of the process. Normally the standard input gets input from the keyboard, while standard output and standard error write data to the terminal from which the process was started.
Opening files using the system call interface is done using the
open()
system call. Similar to fopen()
, it accepts
two parameters. One containing the path to the file to open, the other
contains the mode in which to open the file. The mode may be any
of the following:
O_RDONLY
O_WRONLY
O_RDWR
O_CREAT
O_EXCL
O_CREAT
, the call will fail if the file
already exists.O_TRUNC
O_APPEND
O_NONBLOCK
(or O_NDELAY
)
EAGAIN
. This requires caution on the part of the programmer,
to handle these situations properly.O_SYNC
Unlike the fopen()
function, open()
accepts
one more (optional) parameter, which defines the access permissions that will
be given to the file, in case of file creation. This parameter is a combination
of any of the following flags:
Here are a few examples of using open()
:
/* these hold file descriptors returned from open(). */
int fd_read;
int fd_write;
int fd_readwrite;
int fd_append;
/* Open the file /etc/passwd in read-only mode. */
fd_read = open("/etc/passwd", O_RDONLY);
if (fd_read < 0) {
perror("open");
exit(1);
}
/* Open the file run.log (in the current directory) in write-only mode. */
/* and truncate it, if it has any contents. */
fd_write = open("run.log", O_WRONLY | O_TRUNC);
if (fd_write < 0) {
perror("open");
exit(1);
}
/* Open the file /var/data/food.db in read-write mode. */
fd_readwrite = open("/var/data/food.db", O_RDWR);
if (fd_readwrite < 0) {
perror("open");
exit(1);
}
/* Open the file /var/log/messages in append mode. */
fd_append = open("/var/log/messages", O_WRONLY | O_APPEND);
if (fd_append < 0) {
perror("open");
exit(1);
}
Once we are done working with a file, we need to close it, using the
close()
system call, as follows:
if (close(fd) == -1) {
perror("close");
exit(1);
}
open()
, so no buffer
flushing is required.
Note: If a file that is currently open by a Unix process is being erased (using the Unix "rm" command, for example), the file is not really removed from the disk. Only when the process (or all processes) holding the file open, the file is physically removed from the disk. Until then it is just removed from its directory, not from the disk.
Once we got a file descriptor to an open file (that was opened in read mode),
we may read data from the file using the read()
system call.
This call takes three parameters: the file descriptor to read from, a buffer
to read data into, and the number of characters to read into the buffer.
The buffer must be large enough to contain the data. Here is how to use
this call. We assume 'fd' contains a file descriptor returned from a previous
call to open()
.
/* return value from the read() call. */
size_t rc;
/* buffer to read data into. */
char buf[20];
/* read 20 bytes from the file. */
rc = read(fd, buf, 20);
if (rc == 0) {
printf("End of file encountered\n");
}
else if (rc < 0) {
perror("read");
exit(1);
}
else {
printf("read in '%d' bytes\n", rc);
}
read()
does not always read the number of bytes
we asked it to read. This could be due to a signal interrupting it in the
middle, or the end of the file was encountered. In such a case,
read()
returns the number of bytes it actually read.
Just like we used read()
to read from the file, we use the
write()
system call, to write data to the file. The write
operations is done in the location of the current read/write pointer
of the given file, much like the various standard C library output functions
did. write()
gets the same parameters as read()
does,
and just like read()
, might write only part of the data to
the given file, if interrupted in the middle, or for other reasons. In
such a case it will return the number of bytes actually written to the file.
Here is a usage example:
/* return value from the write() call. */
size_t rc;
/* write the given string to the file. */
rc = write(fd, "hello world\n", strlen("hello world\n"));
if (rc < 0) {
perror("write");
exit(1);
}
else {
printf("wrote in '%d' bytes\n", rc);
}
Sometimes, writing out the data is not enough. We want to be sure the file
on the physical disk gets updated immediately (note that even thought the
system calls do not buffer writes, the operating system still buffers
write operations using its disk cache). In such cases, we may use the
fsync()
system call. It ensures that any write operations
for the given file descriptor that are kept in the system's disk cache,
are actually written to disk, when the fsync()
system call
returns to the caller. Here is how to use it:
#include <unistd.h> /* declaration of fsync() */
.
.
if (fsync(fd) == -1) {
perror("fsync");
}
fsync()
updates both the file's contents, and its
book-keeping data (such as last modification time). If we only need to
assure that the file's contents is written to disk, and don't care about
the last update time, we can use fdatasync()
instead. This
is more efficient, as it will issue one fewer disk write operation. In
applications that need to synchronize data often, this small saving
is important.
Just like we used the fseek()
function to move the read/write
pointer of the file stream, we can use the lseek()
system
call to move the read/write pointer for a file descriptor. Assuming you
understood the fseek()
examples above, here are a few similar
examples using lseek()
. We assume that 'fd_read' is an integer
variable containing a file descriptor to a previously opened file, in read
only mode. 'fd_readwrite' is a similar file descriptor, but for a file
opened in read/write mode.
/* this variable is used for storing locations returned by */
/* lseek(). */
off_t location;
/* move the read/write pointer of the file to position '40' */
/* in the file. Note that the first position in the file is '0', */
/* not '1'. */
location = lseek(fd_read, 39L, SEEK_START);
/* move the read/write pointer of the file stream 67 characters */
/* forward from its given location. */
location = lseek(fd_read, 67L, SEEK_SET);
printf("read/write pointer location: %ld\n", location);
/* remember the current read/write pointer's position, move it */
/* to location '664' in the file, write the string "hello world",*/
/* and move the pointer back to the previous location. */
location = lseek(fd_readwrite, 0L, SEEK_SET);
if (location == -1) {
perror("lseek");
exit(0);
}
if (lseek(fd_readwrite, 663L, SEEK_SET) == -1) {
perror("lseek(fd_readwrite, 663L, SEEK_SET)");
exit(0);
}
rc = write(fd_readwrite, "hello world\n", strlen("hello world\n"));
if (lseek(fd_readwrite, location, SEEK_SET) == -1) {
perror("lseek(fd_readwrite, location, SEEK_SET)");
exit(0);
}
Note that
lseek()
might not always work for a file descriptor (e.g. if this
file descriptor represents the standard input, surely we cannot have
random-access to it). You will encounter other similar cases when you deal
with network programming and inter-process communications, in the future.
Since Unix supports access permissions for files, we would sometimes need
to check these permissions, and perhaps also manipulate them. Two system
calls are used in this context, access()
and chmod()
.
The access()
system call is for checking access permissions to
a file. This system call accepts a path to a file (full or relative), and
a mode mask (made of one or more permission modes). It returns '0' if the
specified permission modes are granted for the calling process, or '-1' if
any of these modes are not granted, the file does not exist, etc. The
access is granted or denied based on the permission flags of the file, and the
ID of the user running the process. Here are a few examples:
/* check if we have read permission to "/home/choo/my_names". */
if (access("/home/choo/my_names", R_OK) == 0)
printf("Read access to file '/home/choo/my_names' granted.\n");
else
printf("Read access to file '/home/choo/my_names' denied.\n");
/* check if we have both read and write permission to "data.db". */
if (access("data.db", R_OK | W_OK) == 0)
printf("Read/Write access to file 'data.db' granted.\n");
else
printf("Either read or write access to file 'data.db' is denied.\n");
/* check if we may execute the program file "runme". */
if (access("runme", X_OK) == 0)
printf("Execute permission to program 'runme' granted.\n");
else
printf("Execute permission to program 'runme' denied.\n");
/* check if we may write new files to directory "/etc/config". */
if (access("/etc/config", W_OK) == 0)
printf("File creation permission to directory '/etc/sysconfig' granted.\n");
else
printf("File creation permission to directory '/etc/sysconfig' denied.\n");
/* check if we may read the contents of directory "/etc/config". */
if (access("/etc/config", R_OK) == 0)
printf("File listing read permission to directory '/etc/sysconfig' granted.\n");
else
printf("File listing read permission to directory '/etc/sysconfig' denied.\n");
/* check if the file "hello.world" in the current directory exists. */
if (access("hello world", F_OK) == 0)
printf("file 'hello world' exists.\n");
else
printf("file 'hello world' does not exist.\n");
Note that we cannot use access()
to check why we got
permissions (i.e. if it was due to the given mode granted to us as the
owner of the file, or due to its group permissions or its word permissions).
For more fine-grained permission tests, see the stat()
system
call mentioned below.
The chmod()
system call is used for changing the access
permissions for a file (or a directory). This call accepts two parameters:
a path to a file, and a mode to set. The mode can be a combination of
read, write and execute permissions for the user, group or others. It
may also contain few special flags, such as the set-user-ID flag or the
'sticky' flag. These permissions will completely override the current
permissions of the file. See the stat()
system call below
to see how to make modifications instead of complete replacement. Here are
a few examples of using chmod()
.
/* give the owner read and write permission to the file "blabla", */
/* and deny access to any other user. */
if (chmod("blabla", S_IRUSR | S_IWUSR) == -1) {
perror("chmod");
}
/* give the owner read and write permission to the file "blabla", */
/* and read-only permission to anyone else. */
if (chmod("blabla", S_IRUSR | S_IWUSR | S_IRGRP | S_IWOTH) == -1) {
perror("chmod");
}
chmod()
,
please refer to its manual page.
We have seen how to manipulate the file's data (write) and its permission
flags (chmod). We saw a primitive way of checking if we may access it (access),
but we often need more then that: what are the exact set of permission flags
of the file? when was it last changed? which user and group owns the file?
how large is the file?
All these questions (and more) are answered by the stat()
system call.
stat()
takes as arguments the full path to the file, and a pointer
to a (how surprising) 'stat' structure. When stat()
returns,
it populates this structure with a lot of interesting (and boring) stuff
about the file. Here are few of the fields found in this structure (for
the rest, read the manual page):
stat()
can be used:
/* structure passed to the stat() system call, to get its results. */
struct stat file_status;
/* check the status information of file "foo.txt", and print its */
/* type on screen. */
if (stat("foo.txt", &file_status) == 0) {
if (S_ISDIR(file_status.st_mode))
printf("foo.txt is a directory\n");
if (S_ISLNK(file_status.st_mode))
printf("foo.txt is a symbolic link\n");
if (S_ISCHR(file_status.st_mode))
printf("foo.txt is a character special file\n");
if (S_ISBLK(file_status.st_mode))
printf("foo.txt is a block special file\n");
if (S_ISFIFO(file_status.st_mode))
printf("foo.txt is a FIFO (named pipe)\n");
if (S_ISSOCK(file_status.st_mode))
printf("foo.txt is a (Unix domain) socket file\n");
if (S_ISREG(file_status.st_mode))
printf("foo.txt is a normal file\n");
}
else { /* stat() call failed and returned '-1'. */
perror("stat");
}
/* add the write permission to the group owner of file "/tmp/parlevouz", */
/* without overriding any of the previous access permission flags. */
if (stat("/tmp/parlevouz", &file_status) == -1) {
perror("stat");
exit(1);
}
if (!S_IWGRP(file_status.st_mode)) { /* the group has no write permission */
umode_t curr_mode = file_status.st_mode & ~S_IFMT
umode_t new_mode = curr_mode | S_IWGRP;
if (chmod("/tmp/parlevouz", new_mode) == -1) {
perror("chmod");
exit(1);
}
}
chmod()
to set the new permission flags for the file.stat()
the file again.
The rename()
system call may be used to change the name
(and possibly the directory) of an existing file. It gets two parameters:
the path to the old location of the file (including the file name), and
a path to the new location of the file (including the new file name).
If the new name points to a an already existing file, that file is deleted
first. We are allowed to name either a file or a directory. Here are
a few examples:
/* rename the file 'logme' to 'logme.1' */
if (rename("logme", "logme1") == -1) {
perror("rename (1):");
exit(1);
}
/* move the file 'data' from the current directory to directory "/old/info" */
if (rename("data", "/old/info/data") == -1) {
perror("rename (2):");
exit(1);
}
Note: If the file we are renaming is a symbolic link, then the symbolic link will be renamed, not the file it is pointing to. Also, if the new path points to an existing symbolic link, this symbolic link will be erased, not the file it is pointing to.
Deleting a file is done using the unlink()
system call.
This one is very simple:
/* remove the file "/tmp/data" */
if (unlink("/tmp/data") == -1) {
perror("unlink");
exit(1);
}
We have encountered symbolic links earlier. lets see how to create them,
with the symlink()
system call:
/* create a symbolic link named "link" in the current directory, */
/* that points to the file "/usr/local/data/datafile". */
if (symlink("/usr/local/data/datafile", "link") == -1) {
perror("symlink");
exit(1);
}
/* create a symbolic link whose full path is "/var/adm/log", */
/* that points to the file "/usr/adm/log". */
if (symlink("/usr/adm/log", "/var/adm/log") == -1) {
perror("symlink");
exit(1);
}
If you created files with open()
or fopen()
,
and you did not supply the mode for the newly created file, you might
wonder how does the system assign access permission flags for the newly
created file. You will also note that these "default" flags are different
on different computers or different account setups. This mysteriousness
is due to the usage of the umask()
system call, or its
equivalent umask
shell command.
The umask()
system call sets a mask for the permission flags
the system will assign to newly created files. By default, newly created files
will have read and write permissions to everyone (i.e. rw-rw-rw- , in the
format reported by 'ls -l'). Using umask()
, we can denote which
flags will be turned off for newly created files. For example, if we
set the mask to 077 (a leading 0 denotes an octal value), newly created files
will get access permission flags of 0600 (i.e. rw-------). If we set the mask
to 027, newly created files will get flags of 0640 (i.e. rw-r-----). Try
translating these values to binary format in order to see what is going
on here.
Here is how to mess with the umask()
system call in a program:
/* set the file permissions mask to '077'. save the original mask */
/* in 'old_mask'. */
int old_mask = umask(077);
/* newly created files will now be readable only by the creating user. */
FILE* f_write = fopen("my_file", "w");
if (f_write) {
fprintf(f_write, "My name is pit stanman.\n");
fprintf(f_write, "My voice is my pass code. Verify me.\n");
fclose(f_write);
}
/* restore the original umask. */
umask(old_mask);
Note: the permissions mask affects also calls to open()
that
specify an exact permissions mask. If we want to create a file whose
permission are less restrictive the the current mask, we need to
use umaks()
to lighten these restrictions, before calling
open()
to create the file.
Note 2: on most systems you will find that the mask is different then
the default. This is because the system administrator has set the
default mask in the system-wide shell startup files, using
the shell's umask
command. You may set a different default
mask for your own account by placing a proper umask
command
in your shell's starup file ("~/.profile" if you're using "sh" or "bash".
"~/.cshrc" if you are using "csh" or "tcsh").
As an example to the usage of the system calls interface for manipulating files, we will show a program that handles simple log file rotation. The program gets one argument - the name of a log file, and assumes it resides in a given directory ("/tmp/var/log"). If the size of the log file is more then 1024KB, it renames it to have a ".old" suffix, and creates a new (empty) log file with the same name as the original file, and the same access permissions. This code demonstrates combining many system calls together to achieve a task. The source code for this program is found in the file rename-log.c.
After we have learned how to write the contents of a file, we might wish to know how to read the contents of a directory. We could open the directory and read its contents directly, but this is not portable. Instead, we have a standard interface for opening a directory and scanning its contents, entry by entry.
DIR
And dirent
Structures
When we want to read the contents of a directory, we have a function that
opens a directory, and returns a DIR
structure. This structure
contains information used by other calls to read the contents of the
directory, and thus this structure is for directory reading, what the
FILE
structure is for files reading.
When we use the DIR
structure to read the contents of a directory,
entry by entry, the data regarding a given entry is returned in a
dirent
structure. The only relevant field in this structure
is d_name
, which is a null-terminated character array, containing
the name of the entry (be it a file or a directory). note - the name, NOT
the path.
In order to read the contents of a directory, we first open it, using
the opendir()
function. We supply the path to the directory,
and get a pointer to a DIR
structure in return (or
NULL
on failure). Here is how:
#include <dirent.h> /* struct DIR, struct dirent, opendir().. */
/* open the directory "/home/users" for reading. */
DIR* dir = opendir("/home/users");
if (!dir) {
perror("opendir");
exit(1);
}
closedir()
function:
if (closedir(dir) == -1) {
perror("closedir");
exit(1);
}
closedir()
will return '0' on success, or '-1' if it failed.
Unless we have done something really silly, failures shouldn't happen,
as we never write to a directory using the DIR
structure.
After we opened the directory, we can start scanning it, entry by entry,
using the readdir()
function. The first call returns the
first entry of the directory. Each successive call returns the next entry
in the directory. When all entries have been read, NULL
is returned. Here is how it is used:
/* this structure is used for storing the name of each entry in turn. */
struct dirent* entry;
/* read the directory's contents, print out the name of each entry. */
printf("Directory contents:\n");
while ( (entry = readdir(dir)) != NULL) {
printf("%s\n", entry->d_name);
}
Note: if we alter the contents of the directory during its traversal, the traversal might skip directory entries. Thus, if you intend to create a file in the directory, you would better not do that while in the middle of a traversal.
After we are done reading the contents of a directory, we can rewind it
for a second pass, using the rewinddir()
function:
rewinddir(dir);
Sometimes we wish to find out the current working directory of a process.
The getcwd()
function is used for that. Other times we wish to
change the working directory of our process. This will allow using short paths
when accessing several files in the same directory. The chdir()
system call is used for this. Here is an example:
/* this buffer is used to store the full path of the current */
/* working directory. */
#define MAX_DIR_PATH 2048;
char cwd[MAX_DIR_PATH+1];
/* store the current working directory. */
if (!getcwd(cwd, MAX_DIR_PATH+1)) {
perror("getcwd");
exit(1);
}
/* change the current directory to "/tmp". */
if (!chdir("/tmp")) {
perror("chdir (1)");
exit(1);
}
/* restore the original working directory. */
if (chdir(cwd) == -1) {
perror("chdir (2)");
exit(1);
}
As an example, we will write a limited version of the Unix 'find' command. This command basically accepts a file name and a directory, and finds all files under that directory (or any of its sub-directories) with the given file name. The original program has zillions of command line options, and can also handle file name patterns. Our version will only be able to handle substrings (that is, finding the files whose names contain the given string). The program changes its working directory to the given directory, reads its contents, and recursively scans each sub-directory it encounters. The program does not traverse across symbolic-links to avoid possible loops. The complete source code for the the program is found in the find-file.c file.
Temas de Instrumentación Electrónica
CURSO 2002
Tomado de: http://users.a
ctcom.co.il/~choo/lupg/tutorials