• Aucun résultat trouvé

Determining File Type

Dans le document What You Will Learn (Page 147-153)

Indirect System Calls

5.4. Obtaining Information about Files

5.4.4. Determining File Type

Recall that the st_mode field encodes both the file's type and its permissions. <sys/stat.h> defines a number of macros that determine the file's type. In particular, these macros return true or false when applied to the

st_mode field. The macros correspond to each of the file types described earlier. Assume that the following code has been executed:

struct stat stbuf;

char filename[PATH_MAX]; /* PATH_MAX is from <limits.h> */

...fill in filename with a file name...

if (stat(filename, & stbuf) < 0) { /* handle error */

}

Once stbuf has been filled in by the system, the following macros can be called, being passed stbuf.st_mode

as the argument:

S_ISREG(stbuf. st_mode)

Returns true if filename is a regular file.

S_ISDIR(stbuf.st_mode)

Returns true if filename is a directory.

S_ISCHR(stbuf.st_mode)

Returns true if filename is a character device. Devices are shortly discussed in more detail.

S_ISBLK(stbuf.st_mode)

Returns true if filename is a block device.

S_ISFIFO(stbuf.st_mode)

Returns true if filename is a FIFO.

S_ISLNK(stbuf.st_mode)

Returns true if filename is a symbolic link. (This can never return true if stat() or fstat() were used instead of lstat().)

S_ISSOCK(stbuf.st_mode)

Returns true if filename is a socket.

Note

It happens that on GNU/Linux, these macros return 1 for true and 0 for false. However, on other systems, it's possible that they return an arbitrary nonzero value for true, instead of 1. (POSIX specifies only nonzero vs. zero.) Thus, you should always use these macros as standalone tests instead of testing the return value:

if (S_ISREG(stbuf.st_mode)) ... Correct if (S_ISREG(stbuf.st_mode) == 1) ... Incorrect

Along with the macros, <sys/stat.h> provides two sets of bitmasks. One set is for testing permission, and the other set is for testing the type of a file. We saw the permission masks in Section 4.6, "Creating Files," page 106, when we discussed the mode_t type and values for open() and creat(). The bitmasks, their values for GNU/Linux, and their meanings are described in Table 5.2.

Table 5.2. POSIX file-type and permission bitmasks in

<sys/stat.h>

Mask Value Meaning

S_IFMT 0170000 Bitmask for the file type bitfields.

S_IFSOCK 0140000 Socket.

S_IFLNK 0120000 Symbolic link.

S_IFREG 0100000 Regular file.

S_IFBLK 0060000 Block device.

S_IFDIR 0040000 Directory.

S_IFCHR 0020000 Character device.

S_IFIFO 0010000 FIFO.

S_ISUID 0004000 Setuid bit.

S_ISGID 0002000 Setgid bit.

S_ISVTX 0001000 Sticky bit.

S_IRWXU 0000700 Mask for owner

permissions.

S_IRUSR 0000400 Owner read permission.

S_IWUSR 0000200 Owner write permission.

S_IXUSR 0000100 Owner execute

permission.

S_IRWXG 0000070 Mask for group

permissions.

S_IRGRP 0000040 Group read permission.

S_IWGRP 0000020 Group write permission.

S_IXGRP 0000010 Group execute

permission.

S_IRWXO 0000007 Mask for permissions

for others.

S_IROTH 0000004 Other read permission.

S_IWOTH 0000002 Other write permission.

S_IXOTH 0000001 Other execute

permission.

Several of these masks serve to isolate the different sets of bits encoded in the st_mode field:

S_IFMT represents bits 12–15, which are where the different types of files are encoded.

S_IRWXU represents bits 6–8, which are the user's permission (read, write, execute for User).

S_IRWXG represents bits 3–5, which are the group's permission (read, write, execute for Group).

S_IRWXO represents bits 0–2, which are the "other" permission (read, write, execute for Other).

The permission and file type bits are depicted graphically in Figure 5.3.

Figure 5.3. Permission and file-type bits

The file-type masks are standardized primarily for compatibility with older code; they should not be used directly, because such code is less readable than the corresponding macros. It happens that the macros are implemented, logically enough, with the masks, but that's irrelevant for user-level code.

The POSIX standard explicitly states that no new bitmasks will be standardized in the future and that tests for any additional kinds of file types that may be added will be available only as S_ISxxx() macros.

5.4.4.1. Device Information

Because it is meant to apply to non-Unix systems as well as Unix systems, the POSIX standard doesn't define the meaning for the dev_t type. However, it's worthwhile to know what's in a dev_t.

When S_ISBLK(sbuf.st_mode) or S_ISCHR(sbuf.st_mode) is true, then the device information is found in the sbuf.st_rdev field. Otherwise, this field does not contain any useful information.

Traditionally, Unix device files encode a major device number and a minor device number within the dev_t

value. The major number distinguishes the device type, such as "disk drive" or "tape drive." Major numbers also distinguish among different types of devices, such as SCSI disk vs. IDE disk. The minor number distinguishes the unit of that type, for example, the first disk or the second one. You can see these values with 'ls -l':

$ ls -l /dev/hda /dev/hda? Show numbers for first hard disk brw-rw---- 1 root disk 3, 0 Aug 31 2002 /dev/hda

brw-rw---- 1 root disk 3, 1 Aug 31 2002 /dev/hda1 brw-rw---- 1 root disk 3, 2 Aug 31 2002 /dev/hda2 brw-rw---- 1 root disk 3, 3 Aug 31 2002 /dev/hda3 brw-rw---- 1 root disk 3, 4 Aug 31 2002 /dev/hda4 brw-rw---- 1 root disk 3, 5 Aug 31 2002 /dev/hda5 brw-rw---- 1 root disk 3, 6 Aug 31 2002 /dev/hda6 brw-rw---- 1 root disk 3, 7 Aug 31 2002 /dev/hda7 brw-rw---- 1 root disk 3, 8 Aug 31 2002 /dev/hda8 brw-rw---- 1 root disk 3, 9 Aug 31 2002 /dev/hda9

$ ls -l /dev/null Show info for /dev/null, too crw-rw-rw- 1 root root 1, 3 Aug 31 2002 /dev/null

Instead of the file size, ls displays the major and minor numbers. In the case of the hard disk, /dev/hda

represents the whole drive. /dev/hda1, /dev/hda2, and so on, represent partitions within the drive. They all share the same major device number (3), but have different minor device numbers.

Note that the disk devices are block devices, whereas /dev/null is a character device. Block devices and character devices are separate entities; even if a character device and a block device share the same major device

number, they are not necessarily related.

The major and minor device numbers can be extracted from a dev_t value with the major() and minor()

functions defined in <sys/sysmacros.h>:

The makedev() function goes the other way; it takes separate major and minor values and encodes them into a

dev_t value. Its use is otherwise beyond the scope of this book; the morbidly curious should see mknod(2).

The following program, ch05-devnum.c, shows how to use the stat() system call, the file-type test macros, and finally, the major() and minor() macros.

/* ch05-devnum.c --- Demonstrate stat(), major(), minor(). */

#include <stdio.h>

fprintf(stderr, "%s: stat: %s\n", argv[1], strerror(errno));

exit(1);

}

if (S_ISCHR(sbuf.st_mode)) devtype = "char";

else if (S_ISBLK(sbuf.st_mode)) devtype = "block";

else {

fprintf(stderr, "%s is not a block or character device\n", argv[1]);

exit(1);

}

printf("%s: major: %d, minor: %d\n", devtype, major(sbuf.st_rdev), minor(sbuf.st_rdev));

exit(0);

}

Here is what happens when the program is run:

$ ch05-devnum /tmp Try a nondevice /tmp is not a block or character device

$ ch05-devnum /dev/null Character device char: major: 1, minor: 3

$ ch05-devnum /dev/hda2 Block device block: major: 3, minor: 2

Fortunately, the output agrees with that of ls, giving us confidence[7] that we have indeed written correct code.

[7] The technical term is a warm fuzzy.

Reproducing the output of ls is all fine and good, but is it really useful? The answer is yes. Any application that works with file hierarchies must be able to distinguish among all the different types of files. Consider an archiver such as tar or cpio. It would be disastrous if such a program treated a disk device file as a regular file, attempting to read it and store its contents in an archive! Or consider find, which can perform arbitrary actions based on the type and other attributes of files it encounters. (find is a complicated program; see find(1) if you're not familiar with it.) Or even something as simple as a disk space accounting package has to distinguish regular files from everything else.

5.4.4.2. The V7 cat Revisited

In Section 4.4.4, "Example: Unix cat," page 99, we promised to return to the V7 cat program to review its use of the stat() system call. The first group of lines that used it were these:

31 fstat(fileno(stdout), &statb);

32 statb.st_mode &= S_IFMT;

33 if (statb.st_mode!=S_IFCHR && statb.st_mode!=S_IFBLK) { 34 dev = statb.st_dev;

35 ino = statb.st_ino;

36 }

This code should now make sense. Line 31 calls fstat() on the standard output to fill in the statb structure.

Line 32 throws away all the information in statb.st_mode except the file type, by ANDing the mode with the

S_IFMT mask. Line 33 checks that the file being used for standard output is not a device file. In that case, the program saves the device and inode numbers in dev and ino. These values are then checked for each input file in lines 50–56:

50 fstat(fileno(fi), &statb);

51 if (statb.st_dev==dev && statb.st_ino==ino) { 52 fprintf(stderr, "cat: input %s is output\n", 53 fflg?"-": *argv);

54 fclose(fi);

55 continue;

56 }

If an input file's st_dev and st_ino values match those of the output file, then cat complains and continues to the next file named on the command line.

The check is done unconditionally, even though dev and ino are set only if the output is not a device file. This works out OK, because of how those variables are declared:

17 int dev, ino = -1;

Since ino is initialized to -1, no valid inode number will ever be equal to it.[8] That dev is not so initialized is sloppy, but not a problem, since the test on line 51 requires that both the device and inode be equal. (A good compiler will complain that dev is used without being initialized: 'gcc -Wall' does.)

[8] This statement was true for V7; there are no such guarantees on modern systems.

Note also that neither call to fstat() is checked for errors. This too is sloppy, although less so; it is unlikely that

fstat() will fail on a valid file descriptor.

The test for input file equals output file is done only for nondevice files. This makes it possible to use cat to copy input from device files to themselves, such as with terminals:

$ tty Print current terminal device name /dev/pts/3

$ cat /dev/pts/3 > /dev/pts/3 Copy keyboard input to screen this is a line of text Type in a line

this is a line of text cat repeats it

Dans le document What You Will Learn (Page 147-153)