Programming Interfaces Guide

Chapter 6 Input/Output Interfaces

This chapter introduces file input/output operations, as provided on systems that do not provide virtual memory services. The chapter discusses the improved input/output method provided by the virtual memory facilities. The chapter describes the older method of locking files and records in Using File and Record Locking.

Files and I/O Interfaces

Files that are organized as a sequence of data are called regular files. Regular files can contain ASCII text, text in some other binary data encoding, executable code, or any combination of text, data, and code.

A regular file is made up of the following components:

The Solaris operating system provides the following basic forms of file input/output interfaces:

Basic File I/O

The following interfaces perform basic operations on files and on character I/O devices.

Table 6–1 Basic File I/O Interfaces

Interface Name 

Purpose 

open(2)

Open a file for reading or writing 

close(2)

Close a file descriptor 

read(2)

Read from a file 

write(2)

Write to a file 

creat(2)

Create a new file or rewrite an existing one 

unlink(2)

Remove a directory entry 

lseek(2)

Move read/write file pointer 

The following code sample demonstrates the use of the basic file I/O interface. read(2) and write(2) both transfer no more than the specified number of bytes, starting at the current offset into the file. The number of bytes actually transferred is returned. The end of a file is indicated on a read(2) by a return value of zero.


Example 6–1 Basic File I/O Interface

#include			<fcntl.h>
#define			MAXSIZE			256

main()
{
    int     fd;
    ssize_t n;
    char	    array[MAXSIZE];

    fd = open ("/etc/motd", O_RDONLY);
    if (fd == -1) {
        perror ("open");
        exit (1);
    }
    while ((n = read (fd, array, MAXSIZE)) > 0)
        if (write (1, array, n) != n)
            perror ("write");
    if (n == -1)
        perror ("read");
    close (fd);
}

When you are done reading or writing a file, always call close(2). Do not call close(2) for a file descriptor that was not returned from a call to open(2).

File pointer offsets into an open file are changed by using read(2), write(2), or by calls to lseek(2). The following example demonstrates the uses of lseek.

off_t     start, n;
struct    record    rec;

/* record current offset in start */
start = lseek (fd, 0L, SEEK_CUR);

/* go back to start */
n = lseek (fd, -start, SEEK_SET);
read (fd, &rec, sizeof (rec));

/* rewrite previous record */
n = lseek (fd, -sizeof (rec), SEEK_CUR);
write (fd, (char *&rec, sizeof (rec));

Advanced File I/O

The following table lists the tasks performed by advanced file I/O interfaces.

Table 6–2 Advanced File I/O Interfaces

Interface Name 

Purpose 

link(2)

Link to a file 

access(2)

Determine accessibility of a file 

mknod(2)

Make a special or ordinary file 

chmod(2)

Change mode of file 

chown(2), lchown(2), fchown(2)

Change owner and group of a file 

utime(2)

Set file access and modification times 

stat(2), lstat(2), fstat(2)

Get file status 

fcntl(2)

Perform file control functions 

ioctl(2)

Control device 

fpathconf(2)

Get configurable path name variables 

opendir(3C), readdir(3C), closedir(3C)

Perform directory operations 

mkdir(2)

Make a directory 

readlink(2)

Read the value of a symbolic link 

rename(2)

Change the name of a file 

rmdir(2)

Remove a directory 

symlink(2)

Make a symbolic link to a file 

File System Control

The file system control interfaces listed in the following table enable the control of various aspects of the file system.

Table 6–3 File System Control Interfaces

Interface Name 

Purpose 

ustat(2)

Get file system statistics 

sync(2)

Update super block 

mount(2)

Mount a file system 

statvfs(2), fstatvfs(2)

Get file system information 

sysfs(2)

Get file system type information 

Using File and Record Locking

You do not need to use traditional file I/O to lock file elements. Use the lighter weight synchronization mechanisms that are described in Multithreaded Programming Guide with mapped files.

Locking files prevents errors that can occur when several users try to update a file at the same time. You can lock a portion of a file.

File locking blocks access to an entire file. Record locking blocks access to a specified segment of the file. In SunOS, all files are a sequence of bytes of data: a record is a concept of the programs that use the file.

Choosing a Lock Type

Mandatory locking suspends a process until the requested file segments are free. Advisory locking returns a result indicating whether the lock was obtained or not. A process can ignore the result of advisory locking. You cannot use both mandatory and advisory file locking on the same file at the same time. The mode of a file at the time the file is opened determines whether locks on a file are treated as mandatory or advisory.

Of the two basic locking calls, fcntl(2) is more portable, more powerful, and less easy to use than lockf(3C). fcntl(2) is specified in POSIX 1003.1 standard. lockf(3C) is provided to be compatible with older applications.

Selecting Advisory or Mandatory Locking

For mandatory locks, the file must be a regular file with the set-group-ID bit on and the group execute permission off. If either condition fails, all record locks are advisory.

Set a mandatory lock as follows.

#include <sys/types.h>
#include <sys/stat.h>

 int mode;
 struct stat buf;
 	...
 	if (stat(filename, &buf) < 0) {
 		perror("program");
 		exit (2);
 	}
 	/* get currently set mode */
 	mode = buf.st_mode;
 	/* remove group execute permission from mode */
 	mode &= ~(S_IEXEC>>3);
 		/* set 'set group id bit' in mode */
 	mode |= S_ISGID;
 	if (chmod(filename, mode) < 0) {
 		perror("program");
 		exit(2);
 	}
 	... 

The operating system ignores record locks when the system is executing a file. Any files with record locks should not have execute permissions set.

The chmod(1) command can also be used to set a file to permit mandatory locking.


$ chmod +l file

This command sets the O20n0 permission bit in the file mode, which indicates mandatory locking on the file. If n is even, the bit is interpreted as enabling mandatory locking. If n is odd, the bit is interpreted as “set group ID on execution.”

The ls(1) command shows this setting when you ask for the long listing format with the -l option:


$ ls -l file

This command displays the following information:


-rw---l--- 1 user group size mod_time file

The letter “l” in the permissions indicates that the set-group-ID bit is on. Since the set-group-ID bit is on, mandatory locking is enabled. Normal semantics of set group ID are also enabled.

Cautions About Mandatory Locking

Keep in mind the following aspects of locking:

Supported File Systems

Both advisory and mandatory locking are supported on the file systems listed in the following table.

Table 6–4 Supported File Systems

File System 

Description 

ufs

The default disk-based file system 

fifofs

A pseudo file system of named pipe files that give processes common access to data 

namefs

A pseudo file system used mostly by STREAMS for dynamic mounts of file descriptors on top of file 

specfs

A pseudo file system that provides access to special character devices and block devices 

Only advisory file locking is supported on NFS. File locking is not supported for the proc and fd file systems.

Opening a File for Locking

You can only request a lock for a file with a valid open descriptor. For read locks, the file must be open with at least read access. For write locks, the file must also be open with write access. In the following example, a file is opened for both read and write access.

...
 	filename = argv[1];
 	fd = open (filename, O_RDWR);
 	if (fd < 0) {
 		perror(filename);
 		exit(2);
 	}
 	...

Setting a File Lock

To lock an entire file, set the offset to zero and set the size to zero.

You can set a lock on a file in several ways. The choice of method depends on how the lock interacts with the rest of the program, performance, and portability. This example uses the POSIX standard-compatible fcntl(2) interface. The interface tries to lock a file until one of the following happens:

Setting and Removing Record Locks

When locking a record, do not set the starting point and length of the lock segment to zero. The locking procedure is otherwise identical to file locking.

Contention for data is why you use record locking. Therefore, you should have a failure response for when you cannot obtain all the required locks:

This example shows a record being locked by using fcntl(2).

{
 	struct flock lck;
   	...
 	lck.l_type = F_WRLCK;	/* setting a write lock */
 	lck.l_whence = 0;	/* offset l_start from beginning of file */
 	lck.l_start = here;
 	lck.l_len = sizeof(struct record);

 	/* lock "this" with write lock */
 	lck.l_start = this;
 	if (fcntl(fd, F_SETLKW, &lck) < 0) {
 		/* "this" lock failed. */
 		return (-1);
 ...
}

The next example shows the lockf(3C) interface.

#include <unistd.h>

{
 ...
 	/* lock "this" */
 	(void) lseek(fd, this, SEEK_SET);
 	if (lockf(fd, F_LOCK, sizeof(struct record)) < 0) {
 		/* Lock on "this" failed. Clear lock on "here". */
 		(void) lseek(fd, here, 0);
 		(void) lockf(fd, F_ULOCK, sizeof(struct record));
 		return (-1);
}
 

You remove locks in the same way the locks were set. Only the lock type is different (F_ULOCK). An unlock cannot be blocked by another process and affects only locks placed by the calling process. The unlock affects only the segment of the file specified in the preceding locking call.

Getting Lock Information

You can determine which process is holding a lock. A lock is set, as in the previous examples, and F_GETLK is used in fcntl(2).

The next example finds and prints identifying data on all the locked segments of a file.


Example 6–2 Printing Locked Segments of a File

struct flock lck;

 	lck.l_whence = 0;
 	lck.l_start = 0L;
 	lck.l_len = 0L;
 	do {
 		lck.l_type = F_WRLCK;
 		(void) fcntl(fd, F_GETLK, &lck);
 		if (lck.l_type != F_UNLCK) {
 			(void) printf("%d %d %c %8ld %8ld\n", lck.l_sysid, lck.l_pid,
            (lck.l_type == F_WRLCK) ? 'W' : 'R', lck.l_start, lck.l_len);
 			/* If this lock goes to the end of the address space, no
 			 * need to look further, so break out. */
 			if (lck.l_len == 0) {
 			/* else, look for new lock after the one just found. */
 					lck.l_start += lck.l_len;
 			}
 		}
 	} while (lck.l_type != F_UNLCK);

fcntl(2) with the F_GETLK command can sleep while waiting for a server to respond. The command can fail, returning ENOLCK, if either the client or the server have a resource shortage.

Use lockf(3C) with the F_TEST command to test if a process is holding a lock. This interface does not return information about the lock's location or ownership.


Example 6–3 Testing a Process With lockf

(void) lseek(fd, 0, 0L);
 /* set the size of the test region to zero (0). to test until the
    end of the file address space. */
if (lockf(fd, (off_t)0, SEEK_SET) < 0) {
    switch (errno) {
        case EACCES:
        case EAGAIN:
            (void) printf("file is locked by another process\n");
            break;
        case EBADF:
            /* bad argument passed to lockf */
            perror("lockf");
            break;
        default:
            (void) printf("lockf: unexpected error <%d>\n", errno);
            break;
    }
}

Process Forking and Locks

When a process forks, the child receives a copy of the file descriptors that the parent opened. Locks are not inherited by the child because the locks are owned by a specific process. The parent and child share a common file pointer for each file. Both processes can try to set locks on the same location in the same file. This problem occurs with both lockf(3C) and fcntl(2). If a program holding a record lock forks, the child process should close the file. After closing the file, the child process should reopen the file to set a new, separate file pointer.

Deadlock Handling

The UNIX locking facilities provide deadlock detection and avoidance. Deadlocks can occur only when the system is ready to put a record-locking interface to sleep. A search is made to determine whether two processes are in a deadlock. If a potential deadlock is detected, the locking interface fails and sets errno to indicate deadlock. Processes setting locks that use F_SETLK do not cause a deadlock because these processes do not wait when the lock cannot be granted immediately.

Terminal I/O Functions

Terminal I/O interfaces deal with a general terminal interface for controlling asynchronous communications ports, as shown in the following table. For more information, see the termios(3C) and termio(7I) man pages.

Table 6–5 Terminal I/O Interfaces

Interface Name 

Purpose 

tcgetattr(3C), tcsetattr(3C)

Get and set terminal attributes 

tcsendbreak(3C), tcdrain(3C), tcflush(3C), tcflow(3C)

Perform line control interfaces 

cfgetospeed(3C), cfgetispeed(3C)cfsetispeed(3C), cfsetospeed(3C)

Get and set baud rate 

tcsetpgrp(3C)

Get and set terminal foreground process group ID 

tcgetsid(3C)

Get terminal session ID 

The following example shows how the server dissociates from the controlling terminal of its invoker in the non-DEBUG mode of operation.


Example 6–4 Dissociating From the Controlling Terminal

   (void) close(0);
   (void) close(1);
   (void) close(2);
   (void) open("/", O_RDONLY);
   (void) dup2(0, 1);
   (void) dup2(0, 2);
   setsid();

This operation mode prevents the server from receiving signals from the process group of the controlling terminal. A server cannot send reports of errors to a terminal after the server has dissociated. The dissociated server must log errors with syslog(3C).